diff options
author | Shauren <shauren.trinity@gmail.com> | 2019-06-06 16:48:21 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2019-06-08 17:09:24 +0200 |
commit | fc330fd8ff0115804d9c4b53a1f810c00dd63de9 (patch) | |
tree | cfa10998fed66779834bf0b7a9b8b799d33d91d4 | |
parent | 82c7b6c5688495d90c4ee5995a4ff74039348296 (diff) |
Dep/CascLib: Update to ladislav-zezula/CascLib@a1197edf0b3bd4d52c3f39be7fa7b44bb0b98012
72 files changed, 12996 insertions, 14089 deletions
diff --git a/dep/CascLib/CMakeLists.txt b/dep/CascLib/CMakeLists.txt index 0efe93771f1..a78d0508f0c 100644 --- a/dep/CascLib/CMakeLists.txt +++ b/dep/CascLib/CMakeLists.txt @@ -1,10 +1,12 @@ set(HEADER_FILES src/CascCommon.h src/CascLib.h - src/CascMndx.h src/CascPort.h + src/common/Array.h src/common/Common.h + src/common/Csv.h src/common/FileStream.h + src/common/FileTree.h src/common/ListFile.h src/common/Map.h src/jenkins/lookup.h @@ -13,11 +15,10 @@ set(HEADER_FILES set(SRC_FILES src/common/Common.cpp src/common/Directory.cpp - src/common/DumpContext.cpp - src/common/DynamicArray.cpp + src/common/Csv.cpp src/common/FileStream.cpp + src/common/FileTree.cpp src/common/ListFile.cpp - src/common/Map.cpp src/common/RootHandler.cpp src/jenkins/lookup3.c src/CascCommon.cpp @@ -26,26 +27,24 @@ set(SRC_FILES src/CascDumpData.cpp src/CascFiles.cpp src/CascFindFile.cpp + src/CascIndexFiles.cpp src/CascOpenFile.cpp src/CascOpenStorage.cpp src/CascReadFile.cpp src/CascRootFile_Diablo3.cpp - src/CascRootFile_Mndx.cpp - src/CascRootFile_Ovr.cpp - src/CascRootFile_SC1.cpp - src/CascRootFile_WoW6.cpp + src/CascRootFile_Install.cpp + src/CascRootFile_MNDX.cpp + src/CascRootFile_Text.cpp + src/CascRootFile_TVFS.cpp + src/CascRootFile_OW.cpp + src/CascRootFile_WoW.cpp ) -set(TOMCRYPT_FILES - src/libtomcrypt/src/hashes/hash_memory.c - src/libtomcrypt/src/hashes/md5.c - src/libtomcrypt/src/misc/crypt_argchk.c - src/libtomcrypt/src/misc/crypt_hash_descriptor.c - src/libtomcrypt/src/misc/crypt_hash_is_valid.c - src/libtomcrypt/src/misc/crypt_libc.c +set(MD5_FILES + src/md5/md5.cpp ) -add_library(casc STATIC ${SRC_FILES} ${TOMCRYPT_FILES}) +add_library(casc STATIC ${SRC_FILES} ${HEADER_FILES} ${MD5_FILES}) target_include_directories(casc PUBLIC @@ -53,6 +52,8 @@ target_include_directories(casc PRIVATE ${CMAKE_SOURCE_DIR}/dep) +target_compile_definitions(casc PUBLIC -D__SYS_ZLIB) + target_link_libraries(casc PRIVATE trinity-dependency-interface diff --git a/dep/CascLib/src/CascCommon.cpp b/dep/CascLib/src/CascCommon.cpp index 34c3df66b5c..f5e346ec14c 100644 --- a/dep/CascLib/src/CascCommon.cpp +++ b/dep/CascLib/src/CascCommon.cpp @@ -13,78 +13,132 @@ #include "CascCommon.h" //----------------------------------------------------------------------------- -// Conversion of big-endian to integer +// Functions -// Read the 24-bit big-endian offset into ULONGLONG -DWORD ConvertBytesToInteger_3(LPBYTE ValueAsBytes) +LPBYTE LoadInternalFileToMemory(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD * pcbFileData) { - DWORD Value = 0; - - Value = (Value << 0x08) | ValueAsBytes[0]; - Value = (Value << 0x08) | ValueAsBytes[1]; - Value = (Value << 0x08) | ValueAsBytes[2]; - - return Value; -} - -// Read the 32-bit big-endian offset into ULONGLONG -DWORD ConvertBytesToInteger_4(LPBYTE ValueAsBytes) -{ - DWORD Value = 0; - - Value = (Value << 0x08) | ValueAsBytes[0]; - Value = (Value << 0x08) | ValueAsBytes[1]; - Value = (Value << 0x08) | ValueAsBytes[2]; - Value = (Value << 0x08) | ValueAsBytes[3]; - - return Value; -} + 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(); + } -DWORD ConvertBytesToInteger_4_LE(LPBYTE ValueAsBytes) -{ - DWORD Value = 0; + // Handle errors + if(nError != ERROR_SUCCESS) + { + // Free the file data + CASC_FREE(pbFileData); + cbFileData = 0; - Value = (Value << 0x08) | ValueAsBytes[3]; - Value = (Value << 0x08) | ValueAsBytes[2]; - Value = (Value << 0x08) | ValueAsBytes[1]; - Value = (Value << 0x08) | ValueAsBytes[0]; + // Set the last error + SetLastError(nError); + } - return Value; + // Give the loaded file length + if(pcbFileData != NULL) + *pcbFileData = cbFileData; + return pbFileData; } -// Read the 40-bit big-endian offset into ULONGLONG -ULONGLONG ConvertBytesToInteger_5(LPBYTE ValueAsBytes) +LPBYTE LoadFileToMemory(const TCHAR * szFileName, DWORD * pcbFileData) { - ULONGLONG Value = 0; - - Value = (Value << 0x08) | ValueAsBytes[0]; - Value = (Value << 0x08) | ValueAsBytes[1]; - Value = (Value << 0x08) | ValueAsBytes[2]; - Value = (Value << 0x08) | ValueAsBytes[3]; - Value = (Value << 0x08) | ValueAsBytes[4]; - - return Value; -} + 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); + } -void ConvertIntegerToBytes_4(DWORD Value, LPBYTE ValueAsBytes) -{ - ValueAsBytes[0] = (Value >> 0x18) & 0xFF; - ValueAsBytes[1] = (Value >> 0x10) & 0xFF; - ValueAsBytes[2] = (Value >> 0x08) & 0xFF; - ValueAsBytes[3] = (Value >> 0x00) & 0xFF; + // Give out values + if(pcbFileData != NULL) + pcbFileData[0] = cbFileData; + return pbFileData; } -//----------------------------------------------------------------------------- -// Common fre routine of a CASC blob - void FreeCascBlob(PQUERY_KEY pBlob) { if(pBlob != NULL) { - if(pBlob->pbData != NULL) - CASC_FREE(pBlob->pbData); - - pBlob->pbData = NULL; + CASC_FREE(pBlob->pbData); pBlob->cbData = 0; } } diff --git a/dep/CascLib/src/CascCommon.h b/dep/CascLib/src/CascCommon.h index e38094171b8..3ce527061c2 100644 --- a/dep/CascLib/src/CascCommon.h +++ b/dep/CascLib/src/CascCommon.h @@ -23,341 +23,441 @@ #include "CascPort.h" #include "common/Common.h" -#include "common/DynamicArray.h" +#include "common/Array.h" #include "common/Map.h" +#include "common/FileTree.h" #include "common/FileStream.h" #include "common/Directory.h" #include "common/ListFile.h" -#include "common/DumpContext.h" +#include "common/Csv.h" #include "common/RootHandler.h" -// Headers from LibTomCrypt -#include "libtomcrypt/src/headers/tomcrypt.h" +// Headers from Alexander Peslyak's MD5 implementation +#include "md5/md5.h" // For HashStringJenkins #include "jenkins/lookup.h" +// On-disk CASC structures +#include "CascStructs.h" + //----------------------------------------------------------------------------- // CascLib private defines -#define CASC_GAME_HOTS 0x00010000 // Heroes of the Storm -#define CASC_GAME_WOW6 0x00020000 // World of Warcraft - Warlords of Draenor -#define CASC_GAME_DIABLO3 0x00030000 // Diablo 3 since PTR 2.2.0 -#define CASC_GAME_OVERWATCH 0x00040000 // Overwatch since PTR 24919 -#define CASC_GAME_STARCRAFT2 0x00050000 // Starcraft II - Legacy of the Void, since build 38996 -#define CASC_GAME_STARCRAFT1 0x00060000 // Starcraft 1 (remastered) -#define CASC_GAME_MASK 0xFFFF0000 // Mask for getting game ID - -#define CASC_INDEX_COUNT 0x10 -#define CASC_FILE_KEY_SIZE 0x09 // Size of the file key -#define CASC_MAX_DATA_FILES 0x100 -#define CASC_EXTRA_FILES 0x20 // Number of extra entries to be reserved for additionally inserted files - -#define CASC_SEARCH_HAVE_NAME 0x0001 // Indicated that previous search found a name - -#define BLTE_HEADER_SIGNATURE 0x45544C42 // 'BLTE' header in the data files -#define BLTE_HEADER_DELTA 0x1E // Distance of BLTE header from begin of the header area -#define MAX_HEADER_AREA_SIZE 0x2A // Length of the file header area - -// File header area in the data.nnn: -// BYTE HeaderHash[MD5_HASH_SIZE]; // MD5 of the frame array -// DWORD dwFileSize; // Size of the file (see comment before CascGetFileSize for details) -// BYTE SomeSize[4]; // Some size (big endian) -// BYTE Padding[6]; // Padding (?) -// DWORD dwSignature; // Must be "BLTE" -// BYTE HeaderSizeAsBytes[4]; // Header size in bytes (big endian) -// BYTE MustBe0F; // Must be 0x0F. Optional, only if HeaderSizeAsBytes != 0 -// BYTE FrameCount[3]; // Frame count (big endian). Optional, only if HeaderSizeAsBytes != 0 - -// Prevent problems with CRT "min" and "max" functions, -// as they are not defined on all platforms -#define CASCLIB_MIN(a, b) ((a < b) ? a : b) -#define CASCLIB_MAX(a, b) ((a > b) ? a : b) -#define CASCLIB_UNUSED(p) ((void)(p)) - -#define CASC_PACKAGE_BUFFER 0x1000 - -#ifndef _maxchars -#define _maxchars(buff) ((sizeof(buff) / sizeof(buff[0])) - 1) +#ifdef _DEBUG +#define BREAK_ON_XKEY3(CKey, v0, v1, v2) if(CKey[0] == v0 && CKey[1] == v1 && CKey[2] == v2) { __debugbreak(); } +#define BREAKIF(condition) if(condition) { __debugbreak(); } +#else +#define BREAK_ON_XKEY3(CKey, v0, v1, v2) { /* NOTHING */ } +#define BREAKIF(condition) { /* NOTHING */ } #endif //----------------------------------------------------------------------------- // In-memory structures -// See http://pxr.dk/wowdev/wiki/index.php?title=CASC for more information - -struct TFileStream; typedef enum _CBLD_TYPE { CascBuildNone = 0, // No build type found CascBuildInfo, // .build.info CascBuildDb, // .build.db (older storages) + CascVersionsDb // versions (downloaded online) } CBLD_TYPE, *PCBLD_TYPE; -typedef struct _ENCODING_KEY +// Tag file entry, loaded from the DOWNLOAD file +typedef struct _CASC_TAG_ENTRY { - BYTE Value[MD5_HASH_SIZE]; // MD5 of the file + USHORT HashType; // Hash type + char TagName[1]; // Tag name. Variable length. +} CASC_TAG_ENTRY, *PCASC_TAG_ENTRY; -} ENCODING_KEY, *PENCODING_KEY; - -typedef struct _CASC_INDEX_ENTRY +// Normalized header of the index files. +// Both version 1 and version 2 are converted to this structure +typedef struct _CASC_INDEX_HEADER { - BYTE IndexKey[CASC_FILE_KEY_SIZE]; // The first 9 bytes of the encoding key - BYTE FileOffsetBE[5]; // Index of data file and offset within (big endian). - BYTE FileSizeLE[4]; // Size occupied in the storage file (data.###). See comment before CascGetFileSize for details -} CASC_INDEX_ENTRY, *PCASC_INDEX_ENTRY; - -typedef struct _CASC_MAPPING_TABLE + USHORT IndexVersion; // 5 for index v 1.0, 7 for index version 2.0 + BYTE BucketIndex; // Should be the same as the first byte of the hex filename. + BYTE StorageOffsetLength; // Length, in bytes, of the StorageOffset field in the EKey entry + BYTE EncodedSizeLength; // Length, in bytes, of the EncodedSize in the EKey entry + BYTE EKeyLength; // Length, in bytes, of the (trimmed) EKey in the EKey entry + BYTE FileOffsetBits; // Number of bits of the archive file offset in StorageOffset field. Rest is data segment index + BYTE Alignment; + ULONGLONG SegmentSize; // Size of one data segment (aka data.### file) + size_t HeaderLength; // Length of the on-disk header structure, in bytes + size_t HeaderPadding; // Length of padding after the header + size_t EntryLength; // Length of the on-disk EKey entry structure, in bytes + size_t EKeyCount; // Number of EKey entries. Only supplied for index files version 1. + +} CASC_INDEX_HEADER, *PCASC_INDEX_HEADER; + +// Normalized footer of the archive index files (md5.index) +typedef struct _CASC_ARCINDEX_FOOTER { - TCHAR * szFileName; // Name of the key mapping file - LPBYTE pbFileData; // Pointer to the file data - DWORD cbFileData; // Length of the file data - BYTE ExtraBytes; // (?) Extra bytes in the key record - BYTE SpanSizeBytes; // Size of field with file size - BYTE SpanOffsBytes; // Size of field with file offset - BYTE KeyBytes; // Size of the file key - BYTE SegmentBits; // Number of bits for the file offset (rest is archive index) - ULONGLONG MaxFileOffset; - - PCASC_INDEX_ENTRY pIndexEntries; // Sorted array of index entries - DWORD nIndexEntries; // Number of index entries - -} CASC_MAPPING_TABLE, *PCASC_MAPPING_TABLE; - -typedef struct _CASC_FILE_FRAME + BYTE TocHash[MD5_HASH_SIZE]; // Hash of the structure + BYTE FooterHash[MD5_HASH_SIZE]; // MD5 hash of the footer, possibly shortened + 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 FooterHashBytes; // Length, in bytes, of the hash and checksum + BYTE Alignment[3]; + size_t ElementCount; // total number of elements in the index file + size_t PageLength; // Length, in bytes, of the index page + size_t ItemLength; // Length, in bytes, of the single item + size_t FooterLength; // Length, in bytes, of the index footer structure + +} CASC_ARCINDEX_FOOTER, *PCASC_ARCINDEX_FOOTER; + +// Normalized structure for archive index entry +typedef struct _CASC_ARCINDEX_ENTRY { - DWORD FrameArchiveOffset; // Archive file pointer corresponding to the begin of the frame - DWORD FrameFileOffset; // File pointer corresponding to the begin of the frame - DWORD CompressedSize; // Compressed size of the file - DWORD FrameSize; // Size of the frame - BYTE md5[MD5_HASH_SIZE]; // MD5 hash of the file sector -} CASC_FILE_FRAME, *PCASC_FILE_FRAME; + BYTE IndexHash[MD5_HASH_SIZE]; + BYTE EKey[MD5_HASH_SIZE]; + DWORD ArchiveOffset; + DWORD EncodedSize; +} CASC_ARCINDEX_ENTRY, *PCASC_ARCINDEX_ENTRY; -// The encoding file is in the form of: -// * File header -// * String block #1 -// * Table A header -// * Table A entries -// * Table B header -// * Table B entries -// * String block #2 -// http://pxr.dk/wowdev/wiki/index.php?title=CASC#Key_CASC_Files +// Normalized header of the ENCODING file typedef struct _CASC_ENCODING_HEADER { - BYTE Magic[2]; // "EN" - BYTE Version; // Expected to be 1 by CascLib - BYTE ChecksumSizeA; // The length of the checksums in Encoding Table - BYTE ChecksumSizeB; // The length of the checksums in Encoding Layout Table - BYTE Flags_TableA[2]; // Flags for Encoding Table - BYTE Flags_TableB[2]; // Flags for Encoding Layout Table - BYTE Entries_TableA[4]; // Number of segments in Encoding Table (big endian) - BYTE Entries_TableB[4]; // Number of segments in Encoding Layout Table (big endian) - BYTE field_11; - BYTE Size_StringTable1[4]; // Size of the string block #1 - + USHORT Magic; // FILE_MAGIC_ENCODING ('EN') + USHORT Version; // Expected to be 1 by CascLib + BYTE CKeyLength; // The content key length in ENCODING file. Usually 0x10 + BYTE EKeyLength; // The encoded key length in ENCODING file. Usually 0x10 + DWORD CKeyPageSize; // Size of the CKey page in bytes + DWORD EKeyPageSize; // Size of the CKey page in bytes + DWORD CKeyPageCount; // Number of CKey pages in the page table + DWORD EKeyPageCount; // Number of EKey pages in the page table + DWORD ESpecBlockSize; // Size of the ESpec string block, in bytes } CASC_ENCODING_HEADER, *PCASC_ENCODING_HEADER; -typedef struct _CASC_ENCODING_ENTRY +typedef struct _CASC_DOWNLOAD_HEADER { - USHORT KeyCount; // Number of index keys - BYTE FileSizeBE[4]; // Compressed file size (header area + frame headers + compressed frames), in bytes - BYTE EncodingKey[MD5_HASH_SIZE]; // File encoding key + USHORT Magic; // FILE_MAGIC_DOWNLOAD ('DL') + USHORT Version; // Version + USHORT EKeyLength; // Length of the EKey + USHORT EntryHasChecksum; // If nonzero, then the entry has checksum + DWORD EntryCount; + DWORD TagCount; + USHORT FlagByteSize; + USHORT BasePriority; - // Followed by the index keys - // (number of items = KeyCount) - // Followed by the index keys (number of items = KeyCount) -} CASC_ENCODING_ENTRY, *PCASC_ENCODING_ENTRY; + size_t HeaderLength; // Length of the on-disk header, in bytes + size_t EntryLength; // Length of the on-disk entry, in bytes + +} CASC_DOWNLOAD_HEADER, *PCASC_DOWNLOAD_HEADER; + +typedef struct _CASC_DOWNLOAD_ENTRY +{ + BYTE EKey[MD5_HASH_SIZE]; + ULONGLONG EncodedSize; + DWORD Checksum; + DWORD Flags; + BYTE Priority; +} CASC_DOWNLOAD_ENTRY, *PCASC_DOWNLOAD_ENTRY; + +// Capturable tag structure for loading from DOWNLOAD manifest +typedef struct _CASC_TAG_ENTRY1 +{ + const char * szTagName; // Tag name + LPBYTE Bitmap; // Bitmap + size_t BitmapLength; // Length of the bitmap, in bytes + size_t NameLength; // Length of the tag name, in bytes, not including '\0' + size_t TagLength; // Length of the on-disk tag, in bytes + DWORD TagValue; // Tag value +} CASC_TAG_ENTRY1, *PCASC_TAG_ENTRY1; + +// Tag structure for storing in arrays +typedef struct _CASC_TAG_ENTRY2 +{ + size_t NameLength; // Length of the on-disk tag, in bytes + DWORD TagValue; // Tag value + char szTagName[0x08]; // Tag string. This member can be longer than declared. Aligned to 8 bytes. +} CASC_TAG_ENTRY2, *PCASC_TAG_ENTRY2; -// A version of CASC_ENCODING_ENTRY with one index key -typedef struct _CASC_ENCODING_ENTRY_1 +typedef struct _CASC_INSTALL_HEADER { - USHORT KeyCount; // Number of index keys - BYTE FileSizeBE[4]; // Compressed file size (header area + frame headers + compressed frames), in bytes - BYTE EncodingKey[MD5_HASH_SIZE]; // File encoding key - BYTE IndexKey[MD5_HASH_SIZE]; // File index key + USHORT Magic; // FILE_MAGIC_DOWNLOAD ('DL') + BYTE Version; // Version + BYTE EKeyLength; // Length of the EKey + DWORD EntryCount; + DWORD TagCount; -} CASC_ENCODING_ENTRY_1, *PCASC_ENCODING_ENTRY_1; + size_t HeaderLength; // Length of the on-disk header, in bytes +} CASC_INSTALL_HEADER, *PCASC_INSTALL_HEADER; -#define GET_INDEX_KEY(pEncodingEntry) (pEncodingEntry->EncodingKey + MD5_HASH_SIZE) -#define FAKE_ENCODING_ENTRY_SIZE (sizeof(CASC_ENCODING_ENTRY) + MD5_HASH_SIZE) +typedef struct _CASC_FILE_FRAME +{ + CONTENT_KEY FrameHash; // MD5 hash of the file frame + 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; //----------------------------------------------------------------------------- // Structures for CASC storage and CASC file -typedef struct _TCascStorage +struct TCascStorage { - const char * szClassName; // "TCascStorage" + TCascStorage(); + ~TCascStorage(); + TCascStorage * AddRef(); + TCascStorage * Release(); + + static TCascStorage * IsValid(HANDLE hStorage); + + PFNPROGRESSCALLBACK PfnCallback; // Callback to be called during CascOpen(Online)Storage + void * PtrCallbackParam; + const TCHAR * szIndexFormat; // Format of the index file name - TCHAR * szRootPath; // This is the game directory + const char * szClassName; // "TCascStorage" + const char * szProductName; // String representation of the product name + TCHAR * szRootPath; // Path where the build file is TCHAR * szDataPath; // This is the directory where data files are - TCHAR * szBuildFile; // Build file name (.build.info or .build.db) TCHAR * szIndexPath; // This is the directory where index files are - TCHAR * szUrlPath; // URL to the Blizzard servers + TCHAR * szBuildFile; // Build file name (.build.info or .build.db) + TCHAR * szCdnServers; // Multi-SZ list of CDN servers + TCHAR * szCdnPath; // Remote CDN sub path for the product + TCHAR * szCodeName; // Product code name. Only for online storages + char * szRegion; // Product region. Only when "versions" is used as storage root file + CASC_PRODUCT Product; // Product enum value (see CASC_PRODUCT) + DWORD dwBuildNumber; // Product build number DWORD dwRefCount; // Number of references - DWORD dwGameInfo; // Game type - DWORD dwBuildNumber; // Game build number - DWORD dwFileBeginDelta; // This is number of bytes to shift back from archive offset (from index entry) to actual begin of file data 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 - QUERY_KEY CdnConfigKey; - QUERY_KEY CdnBuildKey; - QUERY_KEY ArchivesGroup; // Key array of the "archive-group" + QUERY_KEY CdnConfigKey; // Currently selected CDN config file. Points to "config\%02X\%02X\%s + QUERY_KEY CdnBuildKey; // Currently selected CDN build file. Points to "config\%02X\%02X\%s + + QUERY_KEY ArchiveGroup; // Key array of the "archive-group" QUERY_KEY ArchivesKey; // Key array of the "archives" QUERY_KEY PatchArchivesKey; // Key array of the "patch-archives" QUERY_KEY PatchArchivesGroup; // Key array of the "patch-archive-group" - QUERY_KEY RootKey; - QUERY_KEY PatchKey; - QUERY_KEY DownloadKey; - QUERY_KEY InstallKey; - QUERY_KEY EncodingKey; - - TFileStream * DataFileArray[CASC_MAX_DATA_FILES]; // Data file handles + QUERY_KEY BuildFiles; // List of supported build files - CASC_MAPPING_TABLE KeyMapping[CASC_INDEX_COUNT]; // Key mapping - PCASC_MAP pIndexEntryMap; // Map of index entries + TFileStream * DataFiles[CASC_MAX_DATA_FILES]; // Array of open data files - QUERY_KEY EncodingFile; // Content of the ENCODING file - PCASC_MAP pEncodingMap; // Map of encoding entries - DYNAMIC_ARRAY ExtraEntries; // Extra encoding entries + CASC_CKEY_ENTRY EncodingCKey; // Information about ENCODING file + CASC_CKEY_ENTRY DownloadCKey; // Information about DOWNLOAD file + CASC_CKEY_ENTRY InstallCKey; // Information about INSTALL file + CASC_CKEY_ENTRY PatchFile; // Information about PATCH file + CASC_CKEY_ENTRY RootFile; // Information about ROOT file + CASC_CKEY_ENTRY SizeFile; // Information about SIZE file + CASC_CKEY_ENTRY VfsRoot; // The main VFS root file + CASC_ARRAY VfsRootList; // List of CASC_EKEY_ENTRY for each TVFS sub-root TRootHandler * pRootHandler; // Common handler for various ROOT file formats - -} TCascStorage; - -typedef struct _TCascFile + 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 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 + 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 + + CASC_ARRAY ExtraKeysList; // List additional encryption keys + CASC_MAP EncryptionKeys; // Map of encryption keys + +}; + +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; + + // 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); + } + + static TCascFile * IsValid(HANDLE hFile) + { + TCascFile * hf = (TCascFile *)hFile; + + return (hf != INVALID_HANDLE_VALUE && + hf != NULL && + hf->hs != NULL && + hf->szClassName != NULL && + hf->pCKeyEntry != NULL) ? hf : NULL; + } + TCascStorage * hs; // Pointer to storage structure TFileStream * pStream; // An open data stream const char * szClassName; // "TCascFile" - DWORD FilePointer; // Current file pointer - - DWORD ArchiveIndex; // Index of the archive (data.###) - DWORD HeaderOffset; // Offset of the BLTE header, relative to the begin of the archive - DWORD HeaderSize; // Length of the BLTE header - DWORD FramesOffset; // Offset of the frame data, relative to the begin of the archive - DWORD CompressedSize; // Compressed size of the file (in bytes) - DWORD FileSize; // Size of file, in bytes - BYTE FrameArrayHash[MD5_HASH_SIZE]; // MD5 hash of the frame array - + 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 + 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 LPBYTE pbFileCache; // Pointer to file cache DWORD cbFileCache; // Size of the file cache - DWORD CacheStart; // Starting offset in the cache - DWORD CacheEnd; // Ending offset in the cache - -#ifdef CASCLIB_TEST // Extra fields for analyzing the file size problem - DWORD FileSize_RootEntry; // File size, from the root entry - DWORD FileSize_EncEntry; // File size, from the encoding entry - DWORD FileSize_IdxEntry; // File size, from the index entry - DWORD FileSize_HdrArea; // File size, as stated in the file header area - DWORD FileSize_FrameSum; // File size as sum of frame sizes -#endif -} TCascFile; +}; -typedef struct _TCascSearch +struct TCascSearch { + TCascSearch(TCascStorage * ahs, LPCTSTR aszListFile, const char * aszMask) + { + // Init the class + szClassName = "TCascSearch"; + hs = ahs->AddRef(); + + // Init provider-specific data + pCache = NULL; + nFileIndex = 0; + nSearchState = 0; + bListFileUsed = false; + + // Allocate mask + szListFile = CascNewStr(aszListFile); + szMask = CascNewStr((aszMask != NULL) ? aszMask : "*"); + } + + ~TCascSearch() + { + // Dereference the CASC storage + hs = hs->Release(); + szClassName = NULL; + + // Free the rest of the members + CASC_FREE(szMask); + CASC_FREE(szListFile); + CASC_FREE(pCache); + } + + static TCascSearch * IsValid(HANDLE hFind) + { + TCascSearch * pSearch = (TCascSearch *)hFind; + + return (hFind != INVALID_HANDLE_VALUE && + hFind != NULL && + pSearch->szClassName != NULL && + !strcmp(pSearch->szClassName, "TCascSearch") && + pSearch->szMask != NULL) ? pSearch : NULL; + } + TCascStorage * hs; // Pointer to the storage handle const char * szClassName; // Contains "TCascSearch" TCHAR * szListFile; // Name of the listfile void * pCache; // Listfile cache char * szMask; // Search mask - char szFileName[MAX_PATH]; // Buffer for the file name // Provider-specific data - void * pRootContext; // Root-specific search context - size_t IndexLevel1; // Root-specific search context - size_t IndexLevel2; // Root-specific search context - DWORD dwState; // Pointer to the search state (0 = listfile, 1 = nameless, 2 = done) - - BYTE BitArray[1]; // Bit array of encoding keys. Set for each entry that has already been reported - -} TCascSearch; + size_t nFileIndex; // Root-specific search context + DWORD nSearchState:8; // The current search state (0 = listfile, 1 = nameless, 2 = done) + DWORD bListFileUsed:1; // TRUE: The listfile has already been loaded +}; //----------------------------------------------------------------------------- -// Memory management -// -// We use our own macros for allocating/freeing memory. If you want -// to redefine them, please keep the following rules: -// -// - The memory allocation must return NULL if not enough memory -// (i.e not to throw exception) -// - The allocating function does not need to fill the allocated buffer with zeros -// - The reallocating function must support NULL as the previous block -// - Memory freeing function doesn't have to test the pointer to NULL -// - -#if defined(_MSC_VER) && defined(_DEBUG) - -#define CASC_REALLOC(type, ptr, count) (type *)HeapReAlloc(GetProcessHeap(), 0, ptr, ((count) * sizeof(type))) -#define CASC_ALLOC(type, count) (type *)HeapAlloc(GetProcessHeap(), 0, ((count) * sizeof(type))) -#define CASC_FREE(ptr) HeapFree(GetProcessHeap(), 0, ptr) - -#else - -#define CASC_REALLOC(type, ptr, count) (type *)realloc(ptr, (count) * sizeof(type)) -#define CASC_ALLOC(type, count) (type *)malloc((count) * sizeof(type)) -#define CASC_FREE(ptr) free(ptr) +// Common functions (CascCommon.cpp) -#endif - -//----------------------------------------------------------------------------- -// Big endian number manipulation - -DWORD ConvertBytesToInteger_3(LPBYTE ValueAsBytes); -DWORD ConvertBytesToInteger_4(LPBYTE ValueAsBytes); -DWORD ConvertBytesToInteger_4_LE(LPBYTE ValueAsBytes); -ULONGLONG ConvertBytesToInteger_5(LPBYTE ValueAsBytes); - -void ConvertIntegerToBytes_4(DWORD Value, LPBYTE ValueAsBytes); +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); //----------------------------------------------------------------------------- // Text file parsing (CascFiles.cpp) -int LoadBuildInfo(TCascStorage * hs); +int DownloadFileFromCDN(TCascStorage * hs, const TCHAR * szSubDir, LPBYTE pbEKey, const TCHAR * szExtension, TCHAR * szOutLocalPath, size_t cchOutLocalPath); int CheckGameDirectory(TCascStorage * hs, TCHAR * szDirectory); - -int GetRootVariableIndex(const char * szLinePtr, const char * szLineEnd, const char * szVariableName, int * PtrIndex); -int ParseRootFileLine(const char * szLinePtr, const char * szLineEnd, int nFileNameIndex, PQUERY_KEY pEncodingKey, char * szFileName, size_t nMaxChars); +int LoadCdnsInfo(TCascStorage * hs); +int LoadBuildInfo(TCascStorage * hs); +int LoadCdnConfigFile(TCascStorage * hs); +int LoadCdnBuildFile(TCascStorage * hs); //----------------------------------------------------------------------------- // Internal file functions -TCascStorage * IsValidStorageHandle(HANDLE hStorage); -TCascFile * IsValidFileHandle(HANDLE hFile); +void * ProbeOutputBuffer(void * pvBuffer, size_t cbLength, size_t cbMinLength, size_t * pcbLengthNeeded); + +PCASC_CKEY_ENTRY FindCKeyEntry_CKey(TCascStorage * hs, LPBYTE pbCKey, PDWORD PtrIndex = NULL); +PCASC_CKEY_ENTRY FindCKeyEntry_EKey(TCascStorage * hs, LPBYTE pbEKey, PDWORD PtrIndex = NULL); -PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKey, PDWORD PtrIndex); -PCASC_INDEX_ENTRY FindIndexEntry(TCascStorage * hs, PQUERY_KEY pIndexKey); +size_t GetTagBitmapLength(LPBYTE pbFilePtr, LPBYTE pbFileEnd, DWORD EntryCount); int CascDecompress(LPBYTE pvOutBuffer, PDWORD pcbOutBuffer, LPBYTE pvInBuffer, DWORD cbInBuffer); -int CascDecrypt (LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer, DWORD dwFrameIndex); int 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); + +//----------------------------------------------------------------------------- +// Support for index files + +int 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_CreateWoW6(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask); -int RootHandler_CreateSC1(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); +int RootHandler_CreateStarcraft1(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); +int RootHandler_CreateInstall(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); //----------------------------------------------------------------------------- -// Dumping CASC data structures +// Dumpers (CascDumpData.cpp) #ifdef _DEBUG -void CascDumpSparseArray(const char * szFileName, void * pvSparseArray); -void CascDumpNameFragTable(const char * szFileName, void * pvMarFile); -void CascDumpFileNames(const char * szFileName, void * pvMarFile); -void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs); -void CascDumpEncodingEntry(TCascStorage * hs, TDumpContext * dc, PCASC_ENCODING_ENTRY pEncodingEntry, int nDumpLevel); -void CascDumpFile(const char * szFileName, HANDLE hFile); -#endif // _DEBUG +void CascDumpFile(HANDLE hFile, const char * szDumpFile = NULL); +void CascDumpStorage(HANDLE hStorage, const char * szDumpFile = NULL); +#endif #endif // __CASCCOMMON_H__ diff --git a/dep/CascLib/src/CascDecompress.cpp b/dep/CascLib/src/CascDecompress.cpp index e60adb8f97b..3262c59ddf7 100644 --- a/dep/CascLib/src/CascDecompress.cpp +++ b/dep/CascLib/src/CascDecompress.cpp @@ -18,29 +18,39 @@ int CascDecompress(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer) { z_stream z; // Stream information for zlib + uInt cbOutBuffer = *pcbOutBuffer; int nResult; + int nError = ERROR_FILE_CORRUPT; // Fill the stream structure for zlib z.next_in = pbInBuffer; z.avail_in = cbInBuffer; z.total_in = cbInBuffer; z.next_out = pbOutBuffer; - z.avail_out = *pcbOutBuffer; + z.avail_out = cbOutBuffer; z.total_out = 0; z.zalloc = NULL; z.zfree = NULL; + // Reset the total number of output bytes + cbOutBuffer = 0; + // Initialize the decompression structure if((nResult = inflateInit(&z)) == Z_OK) { // Call zlib to decompress the data nResult = inflate(&z, Z_NO_FLUSH); - inflateEnd(&z); + if (nResult == Z_OK || nResult == Z_STREAM_END) + { + // Give the size of the uncompressed data + cbOutBuffer = z.total_out; + nError = ERROR_SUCCESS; + } - // Give the size of the uncompressed data - *pcbOutBuffer = z.total_out; + inflateEnd(&z); } - // Return an error code - return (nResult == Z_OK || nResult == Z_STREAM_END) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; + // Give the caller the number of bytes needed + pcbOutBuffer[0] = cbOutBuffer; + return nError; } diff --git a/dep/CascLib/src/CascDecrypt.cpp b/dep/CascLib/src/CascDecrypt.cpp index 57040dd21fb..81e098ca178 100644 --- a/dep/CascLib/src/CascDecrypt.cpp +++ b/dep/CascLib/src/CascDecrypt.cpp @@ -15,6 +15,8 @@ //----------------------------------------------------------------------------- // Local structures +#define CASC_EXTRA_KEYS 0x80 + typedef struct _CASC_ENCRYPTION_KEY { ULONGLONG KeyName; // "Name" of the key @@ -31,125 +33,196 @@ typedef struct _CASC_SALSA20 //----------------------------------------------------------------------------- // Known encryption keys. See https://wowdev.wiki/CASC for updates +static const char * szKeyConstant16 = "expand 16-byte k"; +static const char * szKeyConstant32 = "expand 32-byte k"; + static CASC_ENCRYPTION_KEY CascKeys[] = { - // Key Name Encryption key Seen in - // ---------------------- ------------------------------------------------------------------------------------------------ ----------- - - // Battle.net app - { 0x2C547F26A2613E01ULL, { 0x37, 0xC5, 0x0C, 0x10, 0x2D, 0x4C, 0x9E, 0x3A, 0x5A, 0xC0, 0x69, 0xF0, 0x72, 0xB1, 0x41, 0x7D } }, // Battle.net App Alpha 1.5.0 - - // Overwatch - { 0xFB680CB6A8BF81F3ULL, { 0x62, 0xD9, 0x0E, 0xFA, 0x7F, 0x36, 0xD7, 0x1C, 0x39, 0x8A, 0xE2, 0xF1, 0xFE, 0x37, 0xBD, 0xB9 } }, // 0.8.0.24919_retailx64 (hardcoded) - { 0x402CD9D8D6BFED98ULL, { 0xAE, 0xB0, 0xEA, 0xDE, 0xA4, 0x76, 0x12, 0xFE, 0x6C, 0x04, 0x1A, 0x03, 0x95, 0x8D, 0xF2, 0x41 } }, // 0.8.0.24919_retailx64 (hardcoded) - { 0xDBD3371554F60306ULL, { 0x34, 0xE3, 0x97, 0xAC, 0xE6, 0xDD, 0x30, 0xEE, 0xFD, 0xC9, 0x8A, 0x2A, 0xB0, 0x93, 0xCD, 0x3C } }, // 0.8.0.24919_retailx64 (streamed from server) - { 0x11A9203C9881710AULL, { 0x2E, 0x2C, 0xB8, 0xC3, 0x97, 0xC2, 0xF2, 0x4E, 0xD0, 0xB5, 0xE4, 0x52, 0xF1, 0x8D, 0xC2, 0x67 } }, // 0.8.0.24919_retailx64 (streamed from server) - { 0xA19C4F859F6EFA54ULL, { 0x01, 0x96, 0xCB, 0x6F, 0x5E, 0xCB, 0xAD, 0x7C, 0xB5, 0x28, 0x38, 0x91, 0xB9, 0x71, 0x2B, 0x4B } }, // 0.8.0.24919_retailx64 (streamed from server) - { 0x87AEBBC9C4E6B601ULL, { 0x68, 0x5E, 0x86, 0xC6, 0x06, 0x3D, 0xFD, 0xA6, 0xC9, 0xE8, 0x52, 0x98, 0x07, 0x6B, 0x3D, 0x42 } }, // 0.8.0.24919_retailx64 (streamed from server) - { 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) - { 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 } }, // - { 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 - { 0x7758B2CF1E4E3E1BULL, { 0x3D, 0xE6, 0x0D, 0x37, 0xC6, 0x64, 0x72, 0x35, 0x95, 0xF2, 0x7C, 0x5C, 0xDB, 0xF0, 0x8B, 0xFA } }, // 1.2.0.1.30684 - { 0xE5317801B3561125ULL, { 0x7D, 0xD0, 0x51, 0x19, 0x9F, 0x84, 0x01, 0xF9, 0x5E, 0x4C, 0x03, 0xC8, 0x84, 0xDC, 0xEA, 0x33 } }, // 1.4.0.2.32143 Halloween Terror - { 0x16B866D7BA3A8036ULL, { 0x13, 0x95, 0xE8, 0x82, 0xBF, 0x25, 0xB4, 0x81, 0xF6, 0x1A, 0x4D, 0x62, 0x11, 0x41, 0xDA, 0x6E } }, // 1.4.1.0.31804 Bastion Blizzcon 2016 skin - { 0x11131FFDA0D18D30ULL, { 0xC3, 0x2A, 0xD1, 0xB8, 0x25, 0x28, 0xE0, 0xA4, 0x56, 0x89, 0x7B, 0x3C, 0xE1, 0xC2, 0xD2, 0x7E } }, // 1.5.0.1.32795 Sombra - { 0xCAC6B95B2724144AULL, { 0x73, 0xE4, 0xBE, 0xA1, 0x45, 0xDF, 0x2B, 0x89, 0xB6, 0x5A, 0xEF, 0x02, 0xF8, 0x3F, 0xA2, 0x60 } }, // 1.5.0.1.32795 Ecopoint: Antarctica - { 0xB7DBC693758A5C36ULL, { 0xBC, 0x3A, 0x92, 0xBF, 0xE3, 0x02, 0x51, 0x8D, 0x91, 0xCC, 0x30, 0x79, 0x06, 0x71, 0xBF, 0x10 } }, // 1.5.0.1.32795 Genji Oni skin - { 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 } }, // - { 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 - { 0x388B85AEEDCB685DULL, { 0xD9, 0x26, 0xE6, 0x59, 0xD0, 0x4A, 0x09, 0x6B, 0x24, 0xC1, 0x91, 0x51, 0x07, 0x6D, 0x37, 0x9A } }, // 1.8.0.0.34470 Numbani Update (Doomfist teaser) - { 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 } }, // - { 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 - { 0x5694C503F8C80178ULL, { 0x7F, 0x4C, 0xF1, 0xC1, 0xFB, 0xBA, 0xD9, 0x2B, 0x18, 0x43, 0x36, 0xD6, 0x77, 0xEB, 0xF9, 0x37 } }, // 1.13.0.0.37646 Doomfist - { 0x21DBFD65F3E54269ULL, { 0xAB, 0x58, 0x0C, 0x38, 0x37, 0xCA, 0xF8, 0xA4, 0x61, 0xF2, 0x43, 0xA5, 0x66, 0xB2, 0xAE, 0x4D } }, // 1.13.0.0.37646 Summer Games 2017 - // { 0x27ABA5F88DD8D078ULL, {0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??}}, // 1.13.0.0.37646 - { 0x21E1F90E71D33C71ULL, { 0x32, 0x87, 0x42, 0x33, 0x91, 0x62, 0xB3, 0x26, 0x76, 0xC8, 0x03, 0xC2, 0x25, 0x59, 0x31, 0xA6 } }, // 1.14.1.0.39083 Deathmatch - { 0xD9CB055BCDD40B6EULL, { 0x49, 0xFB, 0x44, 0x77, 0xA4, 0xA0, 0x82, 0x53, 0x27, 0xE9, 0xA7, 0x36, 0x82, 0xBE, 0xCD, 0x0C } }, // 1.15.0.0.????? Junkertown - { 0x8175CE3C694C6659ULL, { 0xE3, 0xF3, 0xFA, 0x77, 0x26, 0xC7, 0x0D, 0x26, 0xAE, 0x13, 0x0D, 0x96, 0x9D, 0xDD, 0xF3, 0x99 } }, // 1.16.0.0.40011 Halloween 2017 - { 0xB8DE51690075435AULL, { 0xC0, 0x7E, 0x92, 0x60, 0xBB, 0x71, 0x12, 0x17, 0xE7, 0xDE, 0x6F, 0xED, 0x91, 0x1F, 0x42, 0x96 } }, // 1.16.0.0.????? Winston Blizzcon 2017 skin - { 0xF6CF23955B5D437DULL, { 0xAE, 0xBA, 0x22, 0x73, 0x28, 0xA5, 0xB0, 0xAA, 0x9F, 0x51, 0xDA, 0xE3, 0xF6, 0xA7, 0xDF, 0xE4 } }, // 1.17.0.2.41350 Moira - - // 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 - { 0xFF813F7D062AC0BCULL, { 0xAA, 0x0B, 0x5C, 0x77, 0xF0, 0x88, 0xCC, 0xC2, 0xD3, 0x90, 0x49, 0xBD, 0x26, 0x7F, 0x06, 0x6D } }, // 25 WOW-20740patch7.0.1_Beta - { 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 - { 0xB76729641141CB34ULL, { 0x98, 0x49, 0xD1, 0xAA, 0x7B, 0x1F, 0xD0, 0x98, 0x19, 0xC5, 0xC6, 0x62, 0x83, 0xA3, 0x26, 0xEC } }, // 40 WOW-20740patch7.0.1_Beta Enchanted Pen pet - { 0xFFB9469FF16E6BF8ULL, { 0xD5, 0x14, 0xBD, 0x19, 0x09, 0xA9, 0xE5, 0xDC, 0x87, 0x03, 0xF4, 0xB8, 0xBB, 0x1D, 0xFD, 0x9A } }, // 41 WOW-20740patch7.0.1_Beta - { 0x23C5B5DF837A226CULL, { 0x14, 0x06, 0xE2, 0xD8, 0x73, 0xB6, 0xFC, 0x99, 0x21, 0x7A, 0x18, 0x08, 0x81, 0xDA, 0x8D, 0x62 } }, // 42 WOW-20740patch7.0.1_Beta Enchanted Cauldron pet - // {0x3AE403EF40AC3037ULL, {0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??}}, // 51 WOW-21249patch7.0.3_Beta - { 0xE2854509C471C554ULL, { 0x43, 0x32, 0x65, 0xF0, 0xCD, 0xEB, 0x2F, 0x4E, 0x65, 0xC0, 0xEE, 0x70, 0x08, 0x71, 0x4D, 0x9E } }, // 52 WOW-21249patch7.0.3_Beta Warcraft movie items - { 0x8EE2CB82178C995AULL, { 0xDA, 0x6A, 0xFC, 0x98, 0x9E, 0xD6, 0xCA, 0xD2, 0x79, 0x88, 0x59, 0x92, 0xC0, 0x37, 0xA8, 0xEE } }, // 55 WOW-21531patch7.0.3_Beta BlizzCon 2016 Murlocs - { 0x5813810F4EC9B005ULL, { 0x01, 0xBE, 0x8B, 0x43, 0x14, 0x2D, 0xD9, 0x9A, 0x9E, 0x69, 0x0F, 0xAD, 0x28, 0x8B, 0x60, 0x82 } }, // 56 WOW-21531patch7.0.3_Beta Fel Kitten - { 0x7F9E217166ED43EAULL, { 0x05, 0xFC, 0x92, 0x7B, 0x9F, 0x4F, 0x5B, 0x05, 0x56, 0x81, 0x42, 0x91, 0x2A, 0x05, 0x2B, 0x0F } }, // 57 WOW-21531patch7.0.3_Beta Legion music - { 0xC4A8D364D23793F7ULL, { 0xD1, 0xAC, 0x20, 0xFD, 0x14, 0x95, 0x7F, 0xAB, 0xC2, 0x71, 0x96, 0xE9, 0xF6, 0xE7, 0x02, 0x4A } }, // 58 WOW-21691patch7.0.3_Beta Demon Hunter #1 cinematic (legion_dh1) - { 0x40A234AEBCF2C6E5ULL, { 0xC6, 0xC5, 0xF6, 0xC7, 0xF7, 0x35, 0xD7, 0xD9, 0x4C, 0x87, 0x26, 0x7F, 0xA4, 0x99, 0x4D, 0x45 } }, // 59 WOW-21691patch7.0.3_Beta Demon Hunter #2 cinematic (legion_dh2) - { 0x9CF7DFCFCBCE4AE5ULL, { 0x72, 0xA9, 0x7A, 0x24, 0xA9, 0x98, 0xE3, 0xA5, 0x50, 0x0F, 0x38, 0x71, 0xF3, 0x76, 0x28, 0xC0 } }, // 60 WOW-21691patch7.0.3_Beta Val'sharah #1 cinematic (legion_val_yd) - { 0x4E4BDECAB8485B4FULL, { 0x38, 0x32, 0xD7, 0xC4, 0x2A, 0xAC, 0x92, 0x68, 0xF0, 0x0B, 0xE7, 0xB6, 0xB4, 0x8E, 0xC9, 0xAF } }, // 61 WOW-21691patch7.0.3_Beta Val'sharah #2 cinematic (legion_val_yx) - { 0x94A50AC54EFF70E4ULL, { 0xC2, 0x50, 0x1A, 0x72, 0x65, 0x4B, 0x96, 0xF8, 0x63, 0x50, 0xC5, 0xA9, 0x27, 0x96, 0x2F, 0x7A } }, // 62 WOW-21691patch7.0.3_Beta Sylvanas warchief cinematic (legion_org_vs) - { 0xBA973B0E01DE1C2CULL, { 0xD8, 0x3B, 0xBC, 0xB4, 0x6C, 0xC4, 0x38, 0xB1, 0x7A, 0x48, 0xE7, 0x6C, 0x4F, 0x56, 0x54, 0xA3 } }, // 63 WOW-21691patch7.0.3_Beta Stormheim Sylvanas vs Greymane cinematic (legion_sth) - { 0x494A6F8E8E108BEFULL, { 0xF0, 0xFD, 0xE1, 0xD2, 0x9B, 0x27, 0x4F, 0x6E, 0x7D, 0xBD, 0xB7, 0xFF, 0x81, 0x5F, 0xE9, 0x10 } }, // 64 WOW-21691patch7.0.3_Beta Harbingers Gul'dan video (legion_hrb_g) - { 0x918D6DD0C3849002ULL, { 0x85, 0x70, 0x90, 0xD9, 0x26, 0xBB, 0x28, 0xAE, 0xDA, 0x4B, 0xF0, 0x28, 0xCA, 0xCC, 0x4B, 0xA3 } }, // 65 WOW-21691patch7.0.3_Beta Harbingers Khadgar video (legion_hrb_k) - { 0x0B5F6957915ADDCAULL, { 0x4D, 0xD0, 0xDC, 0x82, 0xB1, 0x01, 0xC8, 0x0A, 0xBA, 0xC0, 0xA4, 0xD5, 0x7E, 0x67, 0xF8, 0x59 } }, // 66 WOW-21691patch7.0.3_Beta Harbingers Illidan video (legion_hrb_i) - { 0x794F25C6CD8AB62BULL, { 0x76, 0x58, 0x3B, 0xDA, 0xCD, 0x52, 0x57, 0xA3, 0xF7, 0x3D, 0x15, 0x98, 0xA2, 0xCA, 0x2D, 0x99 } }, // 67 WOW-21846patch7.0.3_Beta Suramar cinematic (legion_su_i) - { 0xA9633A54C1673D21ULL, { 0x1F, 0x8D, 0x46, 0x7F, 0x5D, 0x6D, 0x41, 0x1F, 0x8A, 0x54, 0x8B, 0x63, 0x29, 0xA5, 0x08, 0x7E } }, // 68 WOW-21846patch7.0.3_Beta legion_su_r cinematic - { 0x5E5D896B3E163DEAULL, { 0x8A, 0xCE, 0x8D, 0xB1, 0x69, 0xE2, 0xF9, 0x8A, 0xC3, 0x6A, 0xD5, 0x2C, 0x08, 0x8E, 0x77, 0xC1 } }, // 69 WOW-21846patch7.0.3_Beta Broken Shore intro cinematic (legion_bs_i) - { 0x0EBE36B5010DFD7FULL, { 0x9A, 0x89, 0xCC, 0x7E, 0x3A, 0xCB, 0x29, 0xCF, 0x14, 0xC6, 0x0B, 0xC1, 0x3B, 0x1E, 0x46, 0x16 } }, // 70 WOW-21846patch7.0.3_Beta Alliance Broken Shore cinematic (legion_bs_a) - { 0x01E828CFFA450C0FULL, { 0x97, 0x2B, 0x6E, 0x74, 0x42, 0x0E, 0xC5, 0x19, 0xE6, 0xF9, 0xD9, 0x7D, 0x59, 0x4A, 0xA3, 0x7C } }, // 71 WOW-21846patch7.0.3_Beta Horde Broken Shore cinematic (legion_bs_h) - { 0x4A7BD170FE18E6AEULL, { 0xAB, 0x55, 0xAE, 0x1B, 0xF0, 0xC7, 0xC5, 0x19, 0xAF, 0xF0, 0x28, 0xC1, 0x56, 0x10, 0xA4, 0x5B } }, // 72 WOW-21846patch7.0.3_Beta Khadgar & Light's Heart cinematic (legion_iq_lv) - { 0x69549CB975E87C4FULL, { 0x7B, 0x6F, 0xA3, 0x82, 0xE1, 0xFA, 0xD1, 0x46, 0x5C, 0x85, 0x1E, 0x3F, 0x47, 0x34, 0xA1, 0xB3 } }, // 73 WOW-21846patch7.0.3_Beta legion_iq_id cinematic - { 0x460C92C372B2A166ULL, { 0x94, 0x6D, 0x56, 0x59, 0xF2, 0xFA, 0xF3, 0x27, 0xC0, 0xB7, 0xEC, 0x82, 0x8B, 0x74, 0x8A, 0xDB } }, // 74 WOW-21952patch7.0.3_Beta Stormheim Alliance cinematic (legion_g_a_sth) - { 0x8165D801CCA11962ULL, { 0xCD, 0x0C, 0x0F, 0xFA, 0xAD, 0x93, 0x63, 0xEC, 0x14, 0xDD, 0x25, 0xEC, 0xDD, 0x2A, 0x5B, 0x62 } }, // 75 WOW-21952patch7.0.3_Beta Stormheim Horde cinematic (legion_g_h_sth) - { 0xA3F1C999090ADAC9ULL, { 0xB7, 0x2F, 0xEF, 0x4A, 0x01, 0x48, 0x8A, 0x88, 0xFF, 0x02, 0x28, 0x0A, 0xA0, 0x7A, 0x92, 0xBB } }, // 81 WOW-22578patch7.1.0_PTR Firecat Mount - // {0x18AFDF5191923610ULL, {0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 82 WOW-22578patch7.1.0_PTR - // {0x3C258426058FBD93ULL, {0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 91 WOW-23436patch7.2.0_PTR - { 0x094E9A0474876B98ULL, { 0xE5, 0x33, 0xBB, 0x6D, 0x65, 0x72, 0x7A, 0x58, 0x32, 0x68, 0x0D, 0x62, 0x0B, 0x0B, 0xC1, 0x0B } }, // 92 WOW-23910patch7.2.5_PTR - { 0x3DB25CB86A40335EULL, { 0x02, 0x99, 0x0B, 0x12, 0x26, 0x0C, 0x1E, 0x9F, 0xDD, 0x73, 0xFE, 0x47, 0xCB, 0xAB, 0x70, 0x24 } }, // 93 WOW-23789patch7.2.0_PTR - { 0x0DCD81945F4B4686ULL, { 0x1B, 0x78, 0x9B, 0x87, 0xFB, 0x3C, 0x92, 0x38, 0xD5, 0x28, 0x99, 0x7B, 0xFA, 0xB4, 0x41, 0x86 } }, // 94 WOW-23789patch7.2.0_PTR - { 0x486A2A3A2803BE89ULL, { 0x32, 0x67, 0x9E, 0xA7, 0xB0, 0xF9, 0x9E, 0xBF, 0x4F, 0xA1, 0x70, 0xE8, 0x47, 0xEA, 0x43, 0x9A } }, // 95 WOW-23789patch7.2.0_PTR - { 0x71F69446AD848E06ULL, { 0xE7, 0x9A, 0xEB, 0x88, 0xB1, 0x50, 0x9F, 0x62, 0x8F, 0x38, 0x20, 0x82, 0x01, 0x74, 0x1C, 0x30 } }, // 97 WOW-24473patch7.3.0_PTR BlizzCon 2017 Mounts (AllianceShipMount and HordeZeppelinMount) - { 0x211FCD1265A928E9ULL, { 0xA7, 0x36, 0xFB, 0xF5, 0x8D, 0x58, 0x7B, 0x39, 0x72, 0xCE, 0x15, 0x4A, 0x86, 0xAE, 0x45, 0x40 } }, // 98 WOW-24473patch7.3.0_PTR "Shadow" fox pet (store) - { 0x0ADC9E327E42E98CULL, { 0x01, 0x7B, 0x34, 0x72, 0xC1, 0xDE, 0xE3, 0x04, 0xFA, 0x0B, 0x2F, 0xF8, 0xE5, 0x3F, 0xF7, 0xD6 } }, // 99 WOW-23910patch7.2.5_PTR - { 0xBAE9F621B60174F1ULL, { 0x38, 0xC3, 0xFB, 0x39, 0xB4, 0x97, 0x17, 0x60, 0xB4, 0xB9, 0x82, 0xFE, 0x9F, 0x09, 0x50, 0x14 } }, // 100 WOW-24473patch7.3.0_PTR Rejection of the Gift cinematic (legion_73_agi) - { 0x34DE1EEADC97115EULL, { 0x2E, 0x3A, 0x53, 0xD5, 0x9A, 0x49, 0x1E, 0x5C, 0xD1, 0x73, 0xF3, 0x37, 0xF7, 0xCD, 0x8C, 0x61 } }, // 101 WOW-24473patch7.3.0_PTR Resurrection of Alleria Windrunner cinematic (legion_73_avt) - // { 0xE07E107F1390A3DFULL, {0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 102 WOW-25079patch7.3.2_PTR Tottle battle pet, Raptor mount, Horse mount (104 files) - { 0x32690BF74DE12530ULL, { 0xA2, 0x55, 0x62, 0x10, 0xAE, 0x54, 0x22, 0xE6, 0xD6, 0x1E, 0xDA, 0xAF, 0x12, 0x2C, 0xB6, 0x37 } }, // 103 WOW-24781patch7.3.0_PTR legion_73_pan - { 0xBF3734B1DCB04696ULL, { 0x48, 0x94, 0x61, 0x23, 0x05, 0x0B, 0x00, 0xA7, 0xEF, 0xB1, 0xC0, 0x29, 0xEE, 0x6C, 0xC4, 0x38 } }, // 104 WOW-25079patch7.3.2_PTR legion_73_afn - { 0x74F4F78002A5A1BEULL, { 0xC1, 0x4E, 0xEC, 0x8D, 0x5A, 0xEE, 0xF9, 0x3F, 0xA8, 0x11, 0xD4, 0x50, 0xB4, 0xE4, 0x6E, 0x91 } }, // 105 WOW-25079patch7.3.2_PTR SilithusPhase01 map - // { 0x423F07656CA27D23ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // WOW-25600patch7.3.5_PTR - // { 0x0691678F83E8A75DULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // WOW-25600patch7.3.5_PTR - // { 0xC02C78F40BEF5998ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // WOW-25600patch7.3.5_PTR - // { 0x324498590F550556ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // WOW-25600patch7.3.5_PTR - // { 0x8E00C6F405873583ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // WOW-25600patch7.3.5_PTR - // { 0x78482170E4CFD4A6ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // WOW-25600patch7.3.5_PTR - // { 0xB1EB52A64BFAF7BFULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // WOW-25600patch7.3.5_PTR - { 0, { 0 } } + // Key Name Encryption key Seen in + // ---------------------- ------------------------------------------------------------------------------------------------ ----------- + + // Battle.net app + { 0x2C547F26A2613E01ULL, { 0x37, 0xC5, 0x0C, 0x10, 0x2D, 0x4C, 0x9E, 0x3A, 0x5A, 0xC0, 0x69, 0xF0, 0x72, 0xB1, 0x41, 0x7D } }, // Battle.net App Alpha 1.5.0 + + // Starcraft +// { 0xD0CAE11366CEEA83ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? }}, // 1.12.3.2609 (build 45364) + + // Overwatch + { 0xFB680CB6A8BF81F3ULL, { 0x62, 0xD9, 0x0E, 0xFA, 0x7F, 0x36, 0xD7, 0x1C, 0x39, 0x8A, 0xE2, 0xF1, 0xFE, 0x37, 0xBD, 0xB9 } }, // 0.8.0.24919_retailx64 (hardcoded) + { 0x402CD9D8D6BFED98ULL, { 0xAE, 0xB0, 0xEA, 0xDE, 0xA4, 0x76, 0x12, 0xFE, 0x6C, 0x04, 0x1A, 0x03, 0x95, 0x8D, 0xF2, 0x41 } }, // 0.8.0.24919_retailx64 (hardcoded) + { 0xDBD3371554F60306ULL, { 0x34, 0xE3, 0x97, 0xAC, 0xE6, 0xDD, 0x30, 0xEE, 0xFD, 0xC9, 0x8A, 0x2A, 0xB0, 0x93, 0xCD, 0x3C } }, // 0.8.0.24919_retailx64 (streamed from server) + { 0x11A9203C9881710AULL, { 0x2E, 0x2C, 0xB8, 0xC3, 0x97, 0xC2, 0xF2, 0x4E, 0xD0, 0xB5, 0xE4, 0x52, 0xF1, 0x8D, 0xC2, 0x67 } }, // 0.8.0.24919_retailx64 (streamed from server) + { 0xA19C4F859F6EFA54ULL, { 0x01, 0x96, 0xCB, 0x6F, 0x5E, 0xCB, 0xAD, 0x7C, 0xB5, 0x28, 0x38, 0x91, 0xB9, 0x71, 0x2B, 0x4B } }, // 0.8.0.24919_retailx64 (streamed from server) + { 0x87AEBBC9C4E6B601ULL, { 0x68, 0x5E, 0x86, 0xC6, 0x06, 0x3D, 0xFD, 0xA6, 0xC9, 0xE8, 0x52, 0x98, 0x07, 0x6B, 0x3D, 0x42 } }, // 0.8.0.24919_retailx64 (streamed from server) + { 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) + { 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 } }, // + { 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 + { 0x7758B2CF1E4E3E1BULL, { 0x3D, 0xE6, 0x0D, 0x37, 0xC6, 0x64, 0x72, 0x35, 0x95, 0xF2, 0x7C, 0x5C, 0xDB, 0xF0, 0x8B, 0xFA } }, // 1.2.0.1.30684 + { 0xE5317801B3561125ULL, { 0x7D, 0xD0, 0x51, 0x19, 0x9F, 0x84, 0x01, 0xF9, 0x5E, 0x4C, 0x03, 0xC8, 0x84, 0xDC, 0xEA, 0x33 } }, // 1.4.0.2.32143 Halloween Terror + { 0x16B866D7BA3A8036ULL, { 0x13, 0x95, 0xE8, 0x82, 0xBF, 0x25, 0xB4, 0x81, 0xF6, 0x1A, 0x4D, 0x62, 0x11, 0x41, 0xDA, 0x6E } }, // 1.4.1.0.31804 Bastion Blizzcon 2016 skin + { 0x11131FFDA0D18D30ULL, { 0xC3, 0x2A, 0xD1, 0xB8, 0x25, 0x28, 0xE0, 0xA4, 0x56, 0x89, 0x7B, 0x3C, 0xE1, 0xC2, 0xD2, 0x7E } }, // 1.5.0.1.32795 Sombra + { 0xCAC6B95B2724144AULL, { 0x73, 0xE4, 0xBE, 0xA1, 0x45, 0xDF, 0x2B, 0x89, 0xB6, 0x5A, 0xEF, 0x02, 0xF8, 0x3F, 0xA2, 0x60 } }, // 1.5.0.1.32795 Ecopoint: Antarctica + { 0xB7DBC693758A5C36ULL, { 0xBC, 0x3A, 0x92, 0xBF, 0xE3, 0x02, 0x51, 0x8D, 0x91, 0xCC, 0x30, 0x79, 0x06, 0x71, 0xBF, 0x10 } }, // 1.5.0.1.32795 Genji Oni skin + { 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 } }, // + { 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 + { 0x388B85AEEDCB685DULL, { 0xD9, 0x26, 0xE6, 0x59, 0xD0, 0x4A, 0x09, 0x6B, 0x24, 0xC1, 0x91, 0x51, 0x07, 0x6D, 0x37, 0x9A } }, // 1.8.0.0.34470 Numbani Update (Doomfist teaser) + { 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 } }, // + { 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 + { 0x5694C503F8C80178ULL, { 0x7F, 0x4C, 0xF1, 0xC1, 0xFB, 0xBA, 0xD9, 0x2B, 0x18, 0x43, 0x36, 0xD6, 0x77, 0xEB, 0xF9, 0x37 } }, // 1.13.0.0.37646 Doomfist + { 0x21DBFD65F3E54269ULL, { 0xAB, 0x58, 0x0C, 0x38, 0x37, 0xCA, 0xF8, 0xA4, 0x61, 0xF2, 0x43, 0xA5, 0x66, 0xB2, 0xAE, 0x4D } }, // 1.13.0.0.37646 Summer Games 2017 +// { 0x27ABA5F88DD8D078ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 1.13.0.0.37646 + { 0x21E1F90E71D33C71ULL, { 0x32, 0x87, 0x42, 0x33, 0x91, 0x62, 0xB3, 0x26, 0x76, 0xC8, 0x03, 0xC2, 0x25, 0x59, 0x31, 0xA6 } }, // 1.14.1.0.39083 Deathmatch + { 0xD9CB055BCDD40B6EULL, { 0x49, 0xFB, 0x44, 0x77, 0xA4, 0xA0, 0x82, 0x53, 0x27, 0xE9, 0xA7, 0x36, 0x82, 0xBE, 0xCD, 0x0C } }, // 1.15.0.0.????? Junkertown + { 0x8175CE3C694C6659ULL, { 0xE3, 0xF3, 0xFA, 0x77, 0x26, 0xC7, 0x0D, 0x26, 0xAE, 0x13, 0x0D, 0x96, 0x9D, 0xDD, 0xF3, 0x99 } }, // 1.16.0.0.40011 Halloween 2017 + { 0xB8DE51690075435AULL, { 0xC0, 0x7E, 0x92, 0x60, 0xBB, 0x71, 0x12, 0x17, 0xE7, 0xDE, 0x6F, 0xED, 0x91, 0x1F, 0x42, 0x96 } }, // 1.16.0.0.????? Winston Blizzcon 2017 skin + { 0xF6CF23955B5D437DULL, { 0xAE, 0xBA, 0x22, 0x73, 0x28, 0xA5, 0xB0, 0xAA, 0x9F, 0x51, 0xDA, 0xE3, 0xF6, 0xA7, 0xDF, 0xE4 } }, // 1.17.0.2.41350 Moira + { 0x0E4D9426F2891F5CULL, { 0x9F, 0xF0, 0x64, 0xC3, 0x8B, 0xE5, 0x2C, 0xCD, 0xF7, 0x37, 0x48, 0x18, 0x0F, 0x62, 0x82, 0x05 } }, // 1.18.1.2.42076 Winter Wonderland 2017 + { 0x9240BA6A2A0CF684ULL, { 0xDF, 0x2E, 0x37, 0xD7, 0x8B, 0x43, 0x10, 0x8F, 0xA6, 0x24, 0x20, 0x68, 0xB7, 0x0D, 0x1F, 0x65 } }, // 1.19.1.3.42563 Overwatch League + { 0x82297FBAB7F5EB80ULL, { 0xB5, 0x34, 0xC2, 0x09, 0x65, 0x85, 0x2F, 0xB1, 0x5A, 0xEC, 0xAC, 0x17, 0xE3, 0x81, 0xB4, 0x17 } }, // 1.19.1.3.42563 Jan 2017 Lootbox Update + { 0x9ADF00AA1A174A69ULL, { 0x9A, 0x4A, 0xC8, 0x99, 0x26, 0x1A, 0x2F, 0x1C, 0x69, 0x69, 0xF3, 0x93, 0x97, 0xC3, 0x58, 0xE7 } }, // 1.19.1.3.42563 Blizzard World + { 0xCFA05AA76B49F881ULL, { 0x52, 0x6D, 0xDD, 0xEF, 0x19, 0xBF, 0x37, 0x3C, 0x25, 0xB6, 0x29, 0xA3, 0x34, 0xCD, 0x72, 0x37 } }, // 1.19.1.3.42563 WoW's Battle For Azeroth Preorder + { 0x493455579DA0B18EULL, { 0xC0, 0xBA, 0xBF, 0x72, 0xAD, 0x2C, 0x05, 0xDF, 0xC1, 0x40, 0x17, 0xD1, 0xAD, 0xBF, 0x59, 0x77 } }, // 1.19.3.1.43036 Inaugural Season Spray/Icon ?? + { 0x6362C5AD65DAE686ULL, { 0x62, 0xF6, 0x03, 0xD5, 0x39, 0x0F, 0x76, 0x3E, 0xD5, 0x17, 0x73, 0xF0, 0x16, 0x4F, 0xED, 0xB5 } }, // 1.19.3.1.43036 White/Gray OWL Skins ?? + { 0x8162E5313A9C135DULL, { 0xF4, 0x07, 0x83, 0x4D, 0x95, 0x21, 0x58, 0x7C, 0x50, 0x12, 0xB0, 0xA5, 0x9D, 0x7E, 0x06, 0x4B } }, // 1.20.0.2.43435 Lunar New Year 2018 (Dog) / Ayutthaya / Comp CTF +// { 0x68EAE8FDC008C381ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 1.20.0.2.43435 + { 0xF412C6327C4BF091ULL, { 0x6F, 0xAF, 0xC6, 0x48, 0xCB, 0xF1, 0xC2, 0x11, 0x5B, 0x76, 0x95, 0x93, 0xC1, 0x70, 0xE7, 0x32 } }, // 1.20.0.2.43435 SC2 20th Anniversary (Kerrigan Skin) + { 0x3B3ED0874091B174ULL, { 0x5D, 0x09, 0xC2, 0x68, 0x8B, 0x1D, 0x9F, 0x1A, 0x4D, 0xB6, 0x46, 0x02, 0xC1, 0x66, 0x1D, 0x24 } }, // 1.21.?.?.????? Brigitte + { 0x37FD04E05D2A6292ULL, { 0xF0, 0x64, 0x55, 0xE5, 0x6C, 0xD1, 0x44, 0x91, 0x42, 0x95, 0xF2, 0xEF, 0x15, 0x3D, 0x23, 0xBA } }, // 1.21.?.?.????? Brigitte Cosmetics + { 0xC0DDC77552BE5794ULL, { 0xF2, 0xBB, 0x7F, 0x35, 0x99, 0x0E, 0x29, 0x00, 0xCB, 0xD8, 0x77, 0xB4, 0xD3, 0xA7, 0x13, 0x9C } }, // 1.22.?.?.????? OWL away skins + { 0x68D8EB839DC15D75ULL, { 0x29, 0xAF, 0xFE, 0xCA, 0x52, 0x99, 0xC4, 0x14, 0x0A, 0x12, 0xA6, 0x6F, 0x95, 0x4E, 0xF1, 0xE3 } }, // 1.22.?.?.????? Archives 2018 (Retribution) + { 0x209F33BBAC9D1295ULL, { 0xBD, 0x53, 0x54, 0x38, 0xD0, 0xCD, 0xEE, 0x0E, 0x95, 0x67, 0xE0, 0xEF, 0x67, 0x1C, 0x80, 0x9F } }, // 1.23.?.?.????? Rialto + { 0xA55F8C6F20454D94ULL, { 0x42, 0xD7, 0x62, 0x85, 0x41, 0x24, 0x61, 0xB0, 0xC7, 0x5A, 0xB7, 0x5F, 0xB5, 0x2F, 0x59, 0x6E } }, // 1.23.?.?.????? Mercy BCRF Items + { 0x3EEEDB8E7C29A09BULL, { 0x83, 0x7A, 0xC7, 0x93, 0x05, 0xE4, 0xBF, 0xBA, 0x3B, 0x22, 0x26, 0xF1, 0xB9, 0x82, 0x00, 0xBF } }, // 1.24.?.?.????? Anniversary 2018 Map/Items + { 0x22C1AF6758F8449EULL, { 0x28, 0xAA, 0x1B, 0xD2, 0xB9, 0xA1, 0xE3, 0x63, 0x39, 0x89, 0xB1, 0xBB, 0xF6, 0x4A, 0xEA, 0xC4 } }, // 1.26.?.?.????? Emily / Comp Season 11 / Comp 3v3 Items + { 0x11B2E01B9331799EULL, { 0x9A, 0x1C, 0x99, 0x30, 0x3D, 0x6A, 0x58, 0x97, 0x8E, 0x29, 0xC9, 0x88, 0x73, 0x32, 0x7A, 0x6A } }, // 1.26.?.?.????? Wrecking Ball + { 0x8498337C740329B3ULL, { 0xEC, 0x73, 0xD6, 0x63, 0xE3, 0xFC, 0x72, 0x41, 0x6B, 0x3E, 0x46, 0x79, 0x15, 0x70, 0x8B, 0x4F } }, // 1.26.?.?.????? Wrecking Ball Cosmetics + { 0xE6E8BCABE3CC96C1ULL, { 0x57, 0x72, 0x7E, 0x52, 0x60, 0x06, 0x65, 0xEA, 0xC0, 0x2B, 0xD8, 0x7F, 0x06, 0x92, 0x89, 0x8F } }, // 1.26.?.?.????? OWL Grand Finals Unlocks / Lucio Emote + { 0x5D4AC0DC6F3113BAULL, { 0xD1, 0xC9, 0xF1, 0xFF, 0x69, 0x58, 0x5D, 0x13, 0x98, 0xEB, 0xA0, 0x46, 0x34, 0x81, 0xF5, 0xCF } }, // 1.27.?.?.????? Summer Games 2018 + { 0x27F9D85973DCD5AFULL, { 0xC5, 0xFE, 0x10, 0x15, 0xBC, 0xE0, 0xB7, 0x84, 0x8F, 0x02, 0x28, 0x68, 0xAF, 0xF6, 0x54, 0xD1 } }, // 1.27.?.?.????? Summer Games 2018 + { 0x67AAD845CC0F03BDULL, { 0x6C, 0xD8, 0xAD, 0x3F, 0x37, 0xF5, 0x4A, 0xBE, 0xC7, 0x63, 0x02, 0x94, 0xD4, 0x90, 0x41, 0xBF } }, // 1.27.?.?.????? D.Va Nano Cola Challenge + { 0xCA13F0C79042A1A0ULL, { 0xFC, 0xD8, 0x9C, 0xE8, 0x81, 0x2E, 0x63, 0x46, 0x07, 0x6F, 0xC8, 0x2D, 0xD7, 0xA9, 0x24, 0x87 } }, // 1.28.?.?.????? Busan + { 0xC4D84093A32684BDULL, { 0x38, 0xE1, 0x82, 0x42, 0x3E, 0xED, 0xD8, 0xE3, 0xF5, 0x7A, 0xC1, 0xD4, 0x07, 0xB4, 0x70, 0xD0 } }, // 1.28.?.?.????? Blizzcon 2018 Sombra Demon Hunter Skin + { 0x0BFE5A2B3C606BA1ULL, { 0xD6, 0x41, 0x9B, 0x8E, 0x42, 0x82, 0x0B, 0x0B, 0x24, 0xE0, 0x8D, 0xA4, 0x44, 0xA0, 0x68, 0x22 } }, // 1.29.?.?.????? Halloween Terror 2018 + { 0x402CD9D8D6BFED98ULL, { 0xAE, 0xB0, 0xEA, 0xDE, 0xA4, 0x76, 0x12, 0xFE, 0x6C, 0x04, 0x1A, 0x03, 0x95, 0x8D, 0xF2, 0x41 } }, // 1.29.?.?.????? + { 0xF1CBDF48147D26C6ULL, { 0x4B, 0x69, 0x44, 0x69, 0x51, 0x57, 0xD4, 0x3D, 0x33, 0xE4, 0x0B, 0x56, 0x92, 0x44, 0x5A, 0xDB } }, // 1.30.?.?.????? 1.30 + World Cup Viewer + { 0x01A48CFAE25F85CDULL, { 0x60, 0xD7, 0x7A, 0x58, 0x06, 0x2D, 0x03, 0xDC, 0x69, 0x3C, 0x01, 0x95, 0x4D, 0xD1, 0x80, 0x21 } }, // 1.30.?.?.????? 1.30 + World Cup Viewer + { 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 + { 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 + { 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 + { 0xB76729641141CB34ULL, { 0x98, 0x49, 0xD1, 0xAA, 0x7B, 0x1F, 0xD0, 0x98, 0x19, 0xC5, 0xC6, 0x62, 0x83, 0xA3, 0x26, 0xEC } }, // 40 WOW-20740patch7.0.1_Beta Enchanted Pen pet + { 0xFFB9469FF16E6BF8ULL, { 0xD5, 0x14, 0xBD, 0x19, 0x09, 0xA9, 0xE5, 0xDC, 0x87, 0x03, 0xF4, 0xB8, 0xBB, 0x1D, 0xFD, 0x9A } }, // 41 WOW-20740patch7.0.1_Beta <not used between 7.0 and 7.3> + { 0x23C5B5DF837A226CULL, { 0x14, 0x06, 0xE2, 0xD8, 0x73, 0xB6, 0xFC, 0x99, 0x21, 0x7A, 0x18, 0x08, 0x81, 0xDA, 0x8D, 0x62 } }, // 42 WOW-20740patch7.0.1_Beta Enchanted Cauldron pet +// { 0x3AE403EF40AC3037ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 51 WOW-21249patch7.0.3_Beta <not used between 7.0 and 7.3> + { 0xE2854509C471C554ULL, { 0x43, 0x32, 0x65, 0xF0, 0xCD, 0xEB, 0x2F, 0x4E, 0x65, 0xC0, 0xEE, 0x70, 0x08, 0x71, 0x4D, 0x9E } }, // 52 WOW-21249patch7.0.3_Beta Warcraft movie items + { 0x8EE2CB82178C995AULL, { 0xDA, 0x6A, 0xFC, 0x98, 0x9E, 0xD6, 0xCA, 0xD2, 0x79, 0x88, 0x59, 0x92, 0xC0, 0x37, 0xA8, 0xEE } }, // 55 WOW-21531patch7.0.3_Beta BlizzCon 2016 Murlocs + { 0x5813810F4EC9B005ULL, { 0x01, 0xBE, 0x8B, 0x43, 0x14, 0x2D, 0xD9, 0x9A, 0x9E, 0x69, 0x0F, 0xAD, 0x28, 0x8B, 0x60, 0x82 } }, // 56 WOW-21531patch7.0.3_Beta Fel Kitten + { 0x7F9E217166ED43EAULL, { 0x05, 0xFC, 0x92, 0x7B, 0x9F, 0x4F, 0x5B, 0x05, 0x56, 0x81, 0x42, 0x91, 0x2A, 0x05, 0x2B, 0x0F } }, // 57 WOW-21531patch7.0.3_Beta Legion music <not used in 7.3> + { 0xC4A8D364D23793F7ULL, { 0xD1, 0xAC, 0x20, 0xFD, 0x14, 0x95, 0x7F, 0xAB, 0xC2, 0x71, 0x96, 0xE9, 0xF6, 0xE7, 0x02, 0x4A } }, // 58 WOW-21691patch7.0.3_Beta Demon Hunter #1 cinematic (legion_dh1) + { 0x40A234AEBCF2C6E5ULL, { 0xC6, 0xC5, 0xF6, 0xC7, 0xF7, 0x35, 0xD7, 0xD9, 0x4C, 0x87, 0x26, 0x7F, 0xA4, 0x99, 0x4D, 0x45 } }, // 59 WOW-21691patch7.0.3_Beta Demon Hunter #2 cinematic (legion_dh2) + { 0x9CF7DFCFCBCE4AE5ULL, { 0x72, 0xA9, 0x7A, 0x24, 0xA9, 0x98, 0xE3, 0xA5, 0x50, 0x0F, 0x38, 0x71, 0xF3, 0x76, 0x28, 0xC0 } }, // 60 WOW-21691patch7.0.3_Beta Val'sharah #1 cinematic (legion_val_yd) + { 0x4E4BDECAB8485B4FULL, { 0x38, 0x32, 0xD7, 0xC4, 0x2A, 0xAC, 0x92, 0x68, 0xF0, 0x0B, 0xE7, 0xB6, 0xB4, 0x8E, 0xC9, 0xAF } }, // 61 WOW-21691patch7.0.3_Beta Val'sharah #2 cinematic (legion_val_yx) + { 0x94A50AC54EFF70E4ULL, { 0xC2, 0x50, 0x1A, 0x72, 0x65, 0x4B, 0x96, 0xF8, 0x63, 0x50, 0xC5, 0xA9, 0x27, 0x96, 0x2F, 0x7A } }, // 62 WOW-21691patch7.0.3_Beta Sylvanas warchief cinematic (legion_org_vs) + { 0xBA973B0E01DE1C2CULL, { 0xD8, 0x3B, 0xBC, 0xB4, 0x6C, 0xC4, 0x38, 0xB1, 0x7A, 0x48, 0xE7, 0x6C, 0x4F, 0x56, 0x54, 0xA3 } }, // 63 WOW-21691patch7.0.3_Beta Stormheim Sylvanas vs Greymane cinematic (legion_sth) + { 0x494A6F8E8E108BEFULL, { 0xF0, 0xFD, 0xE1, 0xD2, 0x9B, 0x27, 0x4F, 0x6E, 0x7D, 0xBD, 0xB7, 0xFF, 0x81, 0x5F, 0xE9, 0x10 } }, // 64 WOW-21691patch7.0.3_Beta Harbingers Gul'dan video (legion_hrb_g) + { 0x918D6DD0C3849002ULL, { 0x85, 0x70, 0x90, 0xD9, 0x26, 0xBB, 0x28, 0xAE, 0xDA, 0x4B, 0xF0, 0x28, 0xCA, 0xCC, 0x4B, 0xA3 } }, // 65 WOW-21691patch7.0.3_Beta Harbingers Khadgar video (legion_hrb_k) + { 0x0B5F6957915ADDCAULL, { 0x4D, 0xD0, 0xDC, 0x82, 0xB1, 0x01, 0xC8, 0x0A, 0xBA, 0xC0, 0xA4, 0xD5, 0x7E, 0x67, 0xF8, 0x59 } }, // 66 WOW-21691patch7.0.3_Beta Harbingers Illidan video (legion_hrb_i) + { 0x794F25C6CD8AB62BULL, { 0x76, 0x58, 0x3B, 0xDA, 0xCD, 0x52, 0x57, 0xA3, 0xF7, 0x3D, 0x15, 0x98, 0xA2, 0xCA, 0x2D, 0x99 } }, // 67 WOW-21846patch7.0.3_Beta Suramar cinematic (legion_su_i) + { 0xA9633A54C1673D21ULL, { 0x1F, 0x8D, 0x46, 0x7F, 0x5D, 0x6D, 0x41, 0x1F, 0x8A, 0x54, 0x8B, 0x63, 0x29, 0xA5, 0x08, 0x7E } }, // 68 WOW-21846patch7.0.3_Beta legion_su_r cinematic + { 0x5E5D896B3E163DEAULL, { 0x8A, 0xCE, 0x8D, 0xB1, 0x69, 0xE2, 0xF9, 0x8A, 0xC3, 0x6A, 0xD5, 0x2C, 0x08, 0x8E, 0x77, 0xC1 } }, // 69 WOW-21846patch7.0.3_Beta Broken Shore intro cinematic (legion_bs_i) + { 0x0EBE36B5010DFD7FULL, { 0x9A, 0x89, 0xCC, 0x7E, 0x3A, 0xCB, 0x29, 0xCF, 0x14, 0xC6, 0x0B, 0xC1, 0x3B, 0x1E, 0x46, 0x16 } }, // 70 WOW-21846patch7.0.3_Beta Alliance Broken Shore cinematic (legion_bs_a) + { 0x01E828CFFA450C0FULL, { 0x97, 0x2B, 0x6E, 0x74, 0x42, 0x0E, 0xC5, 0x19, 0xE6, 0xF9, 0xD9, 0x7D, 0x59, 0x4A, 0xA3, 0x7C } }, // 71 WOW-21846patch7.0.3_Beta Horde Broken Shore cinematic (legion_bs_h) + { 0x4A7BD170FE18E6AEULL, { 0xAB, 0x55, 0xAE, 0x1B, 0xF0, 0xC7, 0xC5, 0x19, 0xAF, 0xF0, 0x28, 0xC1, 0x56, 0x10, 0xA4, 0x5B } }, // 72 WOW-21846patch7.0.3_Beta Khadgar & Light's Heart cinematic (legion_iq_lv) + { 0x69549CB975E87C4FULL, { 0x7B, 0x6F, 0xA3, 0x82, 0xE1, 0xFA, 0xD1, 0x46, 0x5C, 0x85, 0x1E, 0x3F, 0x47, 0x34, 0xA1, 0xB3 } }, // 73 WOW-21846patch7.0.3_Beta legion_iq_id cinematic + { 0x460C92C372B2A166ULL, { 0x94, 0x6D, 0x56, 0x59, 0xF2, 0xFA, 0xF3, 0x27, 0xC0, 0xB7, 0xEC, 0x82, 0x8B, 0x74, 0x8A, 0xDB } }, // 74 WOW-21952patch7.0.3_Beta Stormheim Alliance cinematic (legion_g_a_sth) + { 0x8165D801CCA11962ULL, { 0xCD, 0x0C, 0x0F, 0xFA, 0xAD, 0x93, 0x63, 0xEC, 0x14, 0xDD, 0x25, 0xEC, 0xDD, 0x2A, 0x5B, 0x62 } }, // 75 WOW-21952patch7.0.3_Beta Stormheim Horde cinematic (legion_g_h_sth) + { 0xA3F1C999090ADAC9ULL, { 0xB7, 0x2F, 0xEF, 0x4A, 0x01, 0x48, 0x8A, 0x88, 0xFF, 0x02, 0x28, 0x0A, 0xA0, 0x7A, 0x92, 0xBB } }, // 81 WOW-22578patch7.1.0_PTR Firecat Mount +// { 0x18AFDF5191923610ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 82 WOW-22578patch7.1.0_PTR <not used between 7.1 and 7.3> +// { 0x3C258426058FBD93ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 91 WOW-23436patch7.2.0_PTR <not used between 7.2 and 7.3> + { 0x094E9A0474876B98ULL, { 0xE5, 0x33, 0xBB, 0x6D, 0x65, 0x72, 0x7A, 0x58, 0x32, 0x68, 0x0D, 0x62, 0x0B, 0x0B, 0xC1, 0x0B } }, // 92 WOW-23910patch7.2.5_PTR shadowstalkerpanthermount, shadowstalkerpantherpet + { 0x3DB25CB86A40335EULL, { 0x02, 0x99, 0x0B, 0x12, 0x26, 0x0C, 0x1E, 0x9F, 0xDD, 0x73, 0xFE, 0x47, 0xCB, 0xAB, 0x70, 0x24 } }, // 93 WOW-23789patch7.2.0_PTR legion_72_ots + { 0x0DCD81945F4B4686ULL, { 0x1B, 0x78, 0x9B, 0x87, 0xFB, 0x3C, 0x92, 0x38, 0xD5, 0x28, 0x99, 0x7B, 0xFA, 0xB4, 0x41, 0x86 } }, // 94 WOW-23789patch7.2.0_PTR legion_72_tst + { 0x486A2A3A2803BE89ULL, { 0x32, 0x67, 0x9E, 0xA7, 0xB0, 0xF9, 0x9E, 0xBF, 0x4F, 0xA1, 0x70, 0xE8, 0x47, 0xEA, 0x43, 0x9A } }, // 95 WOW-23789patch7.2.0_PTR legion_72_ars + { 0x71F69446AD848E06ULL, { 0xE7, 0x9A, 0xEB, 0x88, 0xB1, 0x50, 0x9F, 0x62, 0x8F, 0x38, 0x20, 0x82, 0x01, 0x74, 0x1C, 0x30 } }, // 97 WOW-24473patch7.3.0_PTR BlizzCon 2017 Mounts (AllianceShipMount and HordeZeppelinMount) + { 0x211FCD1265A928E9ULL, { 0xA7, 0x36, 0xFB, 0xF5, 0x8D, 0x58, 0x7B, 0x39, 0x72, 0xCE, 0x15, 0x4A, 0x86, 0xAE, 0x45, 0x40 } }, // 98 WOW-24473patch7.3.0_PTR "Shadow" fox pet (store) + { 0x0ADC9E327E42E98CULL, { 0x01, 0x7B, 0x34, 0x72, 0xC1, 0xDE, 0xE3, 0x04, 0xFA, 0x0B, 0x2F, 0xF8, 0xE5, 0x3F, 0xF7, 0xD6 } }, // 99 WOW-23910patch7.2.5_PTR legion_72_tsf + { 0xBAE9F621B60174F1ULL, { 0x38, 0xC3, 0xFB, 0x39, 0xB4, 0x97, 0x17, 0x60, 0xB4, 0xB9, 0x82, 0xFE, 0x9F, 0x09, 0x50, 0x14 } }, // 100 WOW-24727patch7.3.0_PTR Rejection of the Gift cinematic (legion_73_agi) + { 0x34DE1EEADC97115EULL, { 0x2E, 0x3A, 0x53, 0xD5, 0x9A, 0x49, 0x1E, 0x5C, 0xD1, 0x73, 0xF3, 0x37, 0xF7, 0xCD, 0x8C, 0x61 } }, // 101 WOW-24727patch7.3.0_PTR Resurrection of Alleria Windrunner cinematic (legion_73_avt) + { 0xE07E107F1390A3DFULL, { 0x29, 0x0D, 0x27, 0xB0, 0xE8, 0x71, 0xF8, 0xC5, 0xB1, 0x4A, 0x14, 0xE5, 0x14, 0xD0, 0xF0, 0xD9 } }, // 102 WOW-25079patch7.3.2_PTR Tottle battle pet, Raptor mount, Horse mount (104 files) + { 0x32690BF74DE12530ULL, { 0xA2, 0x55, 0x62, 0x10, 0xAE, 0x54, 0x22, 0xE6, 0xD6, 0x1E, 0xDA, 0xAF, 0x12, 0x2C, 0xB6, 0x37 } }, // 103 WOW-24781patch7.3.0_PTR legion_73_pan + { 0xBF3734B1DCB04696ULL, { 0x48, 0x94, 0x61, 0x23, 0x05, 0x0B, 0x00, 0xA7, 0xEF, 0xB1, 0xC0, 0x29, 0xEE, 0x6C, 0xC4, 0x38 } }, // 104 WOW-25079patch7.3.2_PTR legion_73_afn + { 0x74F4F78002A5A1BEULL, { 0xC1, 0x4E, 0xEC, 0x8D, 0x5A, 0xEE, 0xF9, 0x3F, 0xA8, 0x11, 0xD4, 0x50, 0xB4, 0xE4, 0x6E, 0x91 } }, // 105 WOW-25079patch7.3.2_PTR SilithusPhase01 map +// { 0x423F07656CA27D23ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 107 WOW-25600patch7.3.5_PTR bltestmap +// { 0x0691678F83E8A75DULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 108 WOW-25600patch7.3.5_PTR filedataid 1782602-1782603 +// { 0x324498590F550556ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 109 WOW-25600patch7.3.5_PTR filedataid 1782615-1782619 +// { 0xC02C78F40BEF5998ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 110 WOW-25600patch7.3.5_PTR test/testtexture.blp (fdid 1782613) +// { 0x47011412CCAAB541ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 111 WOW-25600patch7.3.5_PTR unused in 25600 +// { 0x23B6F5764CE2DDD6ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 112 WOW-25600patch7.3.5_PTR unused in 25600 +// { 0x8E00C6F405873583ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 113 WOW-25600patch7.3.5_PTR filedataid 1783470-1783472 + { 0x78482170E4CFD4A6ULL, { 0x76, 0x85, 0x40, 0xC2, 0x0A, 0x5B, 0x15, 0x35, 0x83, 0xAD, 0x7F, 0x53, 0x13, 0x0C, 0x58, 0xFE } }, // 114 WOW-25600patch7.3.5_PTR Magni Bronzebeard VO + { 0xB1EB52A64BFAF7BFULL, { 0x45, 0x81, 0x33, 0xAA, 0x43, 0x94, 0x9A, 0x14, 0x16, 0x32, 0xC4, 0xF8, 0x59, 0x6D, 0xE2, 0xB0 } }, // 115 WOW-25600patch7.3.5_PTR dogmount, 50 files + { 0xFC6F20EE98D208F6ULL, { 0x57, 0x79, 0x0E, 0x48, 0xD3, 0x55, 0x00, 0xE7, 0x0D, 0xF8, 0x12, 0x59, 0x4F, 0x50, 0x7B, 0xE7 } }, // 117 WOW-25632patch7.3.5_PTR shop stuff + { 0x402CFABF2020D9B7ULL, { 0x67, 0x19, 0x7B, 0xCD, 0x9D, 0x0E, 0xF0, 0xC4, 0x08, 0x53, 0x78, 0xFA, 0xA6, 0x9A, 0x32, 0x64 } }, // 118 WOW-25678patch7.3.5_PTR filedataid 1854762 + { 0x6FA0420E902B4FBEULL, { 0x27, 0xB7, 0x50, 0x18, 0x4E, 0x53, 0x29, 0xC4, 0xE4, 0x45, 0x5C, 0xBD, 0x3E, 0x1F, 0xD5, 0xAB } }, // 119 WOW-25744patch7.3.5_PTR legion_735_epa / legion_735_eph + { 0x1076074F2B350A2DULL, { 0x88, 0xBF, 0x0C, 0xD0, 0xD5, 0xBA, 0x15, 0x9A, 0xE7, 0xCB, 0x91, 0x6A, 0xFB, 0xE1, 0x38, 0x65 } }, // 121 WOW-26287patch8.0.1_Beta skiff + { 0x816F00C1322CDF52ULL, { 0x6F, 0x83, 0x22, 0x99, 0xA7, 0x57, 0x89, 0x57, 0xEE, 0x86, 0xB7, 0xF9, 0xF1, 0x5B, 0x01, 0x88 } }, // 122 WOW-26287patch8.0.1_Beta snowkid + { 0xDDD295C82E60DB3CULL, { 0x34, 0x29, 0xCC, 0x59, 0x27, 0xD1, 0x62, 0x97, 0x65, 0x97, 0x4F, 0xD9, 0xAF, 0xAB, 0x75, 0x80 } }, // 123 WOW-26287patch8.0.1_Beta redbird + { 0x83E96F07F259F799ULL, { 0x91, 0xF7, 0xD0, 0xE7, 0xA0, 0x2C, 0xDE, 0x0D, 0xE0, 0xBD, 0x36, 0x7F, 0xAB, 0xCB, 0x8A, 0x6E } }, // 124 WOW-26522patch8.0.1_Beta BlizzCon 2018 (Alliance and Horde banners and cloaks) + { 0x49FBFE8A717F03D5ULL, { 0xC7, 0x43, 0x77, 0x70, 0xCF, 0x15, 0x3A, 0x31, 0x35, 0xFA, 0x6D, 0xC5, 0xE4, 0xC8, 0x5E, 0x65 } }, // 225 WOW-27826patch8.1.0_PTR Meatwagon mount (Warcraft 3: Reforged) + { 0xC1E5D7408A7D4484ULL, { 0xA7, 0xD8, 0x8E, 0x52, 0x74, 0x9F, 0xA5, 0x45, 0x9D, 0x64, 0x45, 0x23, 0xF8, 0x35, 0x96, 0x51 } }, // 226 WOW-26871patch8.0.1_Beta Sylvanas Warbringer cinematic + { 0xE46276EB9E1A9854ULL, { 0xCC, 0xCA, 0x36, 0xE3, 0x02, 0xF9, 0x45, 0x9B, 0x1D, 0x60, 0x52, 0x6A, 0x31, 0xBE, 0x77, 0xC8 } }, // 227 WOW-26871patch8.0.1_Beta ltc_a, ltc_h and ltt cinematics + { 0xD245B671DD78648CULL, { 0x19, 0xDC, 0xB4, 0xD4, 0x5A, 0x65, 0x8B, 0x54, 0x35, 0x1D, 0xB7, 0xDD, 0xC8, 0x1D, 0xE7, 0x9E } }, // 228 WOW-26871patch8.0.1_Beta stz, zia, kta, jnm & ja cinematics + { 0x4C596E12D36DDFC3ULL, { 0xB8, 0x73, 0x19, 0x26, 0x38, 0x94, 0x99, 0xCB, 0xD4, 0xAD, 0xBF, 0x50, 0x06, 0xCA, 0x03, 0x91 } }, // 229 WOW-26871patch8.0.1_Beta bar cinematic + { 0x0C9ABD5081C06411ULL, { 0x25, 0xA7, 0x7C, 0xD8, 0x00, 0x19, 0x7E, 0xE6, 0xA3, 0x2D, 0xD6, 0x3F, 0x04, 0xE1, 0x15, 0xFA } }, // 230 WOW-26871patch8.0.1_Beta zcf cinematic + { 0x3C6243057F3D9B24ULL, { 0x58, 0xAE, 0x3E, 0x06, 0x42, 0x10, 0xE3, 0xED, 0xF9, 0xC1, 0x25, 0x9C, 0xDE, 0x91, 0x4C, 0x5D } }, // 231 WOW-26871patch8.0.1_Beta ktf cinematic + { 0x7827FBE24427E27DULL, { 0x34, 0xA4, 0x32, 0x04, 0x20, 0x73, 0xCD, 0x0B, 0x51, 0x62, 0x70, 0x68, 0xD2, 0xE0, 0xBD, 0x3E } }, // 232 WOW-26871patch8.0.1_Beta rot cinematic + { 0xFAF9237E1186CF66ULL, { 0xAE, 0x78, 0x78, 0x40, 0x04, 0x1E, 0x9B, 0x41, 0x98, 0xF4, 0x79, 0x71, 0x4D, 0xAD, 0x56, 0x2C } }, // 233 WOW-28048patch8.1.0_PTR encrypted db2 sections (battle pet?) +// { 0x5DD92EE32BBF9ABDULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 234 WOW-27004patch8.0.1_Subm filedataid 2238294 + { 0x0B68A7AF5F85F7EEULL, { 0x27, 0xAA, 0x01, 0x10, 0x82, 0xF5, 0xE8, 0xBB, 0xBD, 0x71, 0xD1, 0xBA, 0x04, 0xF6, 0xAB, 0xA4 } }, // 236 WOW-28151patch8.1.0_PTR encrypted06 Flying pig mount +// { 0x01531713C83FCC39ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 237 WOW-28151patch8.1.0_PTR fdid 2460009, 2460732 +// { 0x76E4F6739A35E8D7ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 238 WOW-28294patch8.1.0_PTR starts at fdid 2492654, total of 12 fdids + { 0x66033F28DC01923CULL, { 0x9F, 0x95, 0x19, 0x86, 0x14, 0x90, 0xC5, 0xA9, 0xFF, 0xD4, 0xD8, 0x2A, 0x6D, 0x00, 0x67, 0xDB } }, // 239 WOW-28294patch8.1.0_PTR vulpine familiar mount + { 0xFCF34A9B05AE7E6AULL, { 0xE7, 0xC2, 0xC8, 0xF7, 0x7E, 0x30, 0xAC, 0x24, 0x0F, 0x39, 0xEC, 0x23, 0x97, 0x12, 0x96, 0xE5 } }, // 240 WOW-28151patch8.1.0_PTR fdid 2468985, 2471011, 2471012, 2471014, 2471016, 2471018, 2530045 + { 0xE2F6BD41298A2AB9ULL, { 0xC5, 0xDC, 0x1B, 0xB4, 0x3B, 0x8C, 0xF3, 0xF0, 0x85, 0xD6, 0x98, 0x68, 0x26, 0xB9, 0x28, 0xEC } }, // 241 WOW-28151patch8.1.0_PTR fdid 2468988, 2471019, 2471020, 2471021, 2471022, 2471023 + { 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 14 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 fdid 2823166 + { 0x9FD609902B4B2E07ULL, { 0xAB, 0xE0, 0xC5, 0xF9, 0xC1, 0x23, 0xE6, 0xE2, 0x4E, 0x7B, 0xEA, 0x43, 0xC2, 0xBF, 0x00, 0xAC } }, // 250 WOW-29418patch8.1.5_PTR dpr cinematic }; - -static const char * szKeyConstant16 = "expand 16-byte k"; -static const char * szKeyConstant32 = "expand 32-byte k"; //----------------------------------------------------------------------------- // Local functions @@ -159,19 +232,6 @@ static DWORD Rol32(DWORD dwValue, DWORD dwRolCount) return (dwValue << dwRolCount) | (dwValue >> (32 - dwRolCount)); } -static LPBYTE FindCascKey(ULONGLONG KeyName) -{ - // Search the known keys - for(size_t i = 0; CascKeys[i].KeyName != 0; i++) - { - if(CascKeys[i].KeyName == KeyName) - return CascKeys[i].Key; - } - - // Key not found - return NULL; -} - static void Initialize(PCASC_SALSA20 pState, LPBYTE pbKey, DWORD cbKeyLength, LPBYTE pbVector) { const char * szConstants = (cbKeyLength == 32) ? szKeyConstant32 : szKeyConstant16; @@ -296,7 +356,35 @@ static int Decrypt_Salsa20(LPBYTE pbOutBuffer, LPBYTE pbInBuffer, size_t cbInBuf //----------------------------------------------------------------------------- // Public functions -int CascDecrypt(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer, DWORD dwFrameIndex) +int CascLoadEncryptionKeys(TCascStorage * hs) +{ + size_t nKeyCount = (sizeof(CascKeys) / sizeof(CASC_ENCRYPTION_KEY)); + size_t nMaxItems = nKeyCount + CASC_EXTRA_KEYS; + int nError; + + // 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; + + // 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; +} + +LPBYTE CascFindKey(TCascStorage * hs, ULONGLONG KeyName) +{ + PCASC_ENCRYPTION_KEY pKey; + + pKey = (PCASC_ENCRYPTION_KEY)hs->EncryptionKeys.FindObject(&KeyName); + return (pKey != NULL) ? pKey->Key : NULL; +} + +int CascDecrypt(TCascStorage * hs, LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer, DWORD dwFrameIndex) { ULONGLONG KeyName = 0; LPBYTE pbBufferEnd = pbInBuffer + cbInBuffer; @@ -347,14 +435,14 @@ int CascDecrypt(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWOR return ERROR_INSUFFICIENT_BUFFER; // Check if we know the key - pbKey = FindCascKey(KeyName); + pbKey = CascFindKey(hs, KeyName); if(pbKey == NULL) return ERROR_FILE_ENCRYPTED; // Shuffle the Vector with the block index // Note that there's no point to go beyond 32 bits, unless the file has // more than 0xFFFFFFFF frames. - for(int i = 0; i < sizeof(dwFrameIndex); i++) + for(size_t i = 0; i < sizeof(dwFrameIndex); i++) { Vector[i] = Vector[i] ^ (BYTE)((dwFrameIndex >> dwShift) & 0xFF); dwShift += 8; @@ -392,3 +480,38 @@ int CascDirectCopy(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, D return ERROR_SUCCESS; } +bool WINAPI CascAddEncryptionKey(HANDLE hStorage, ULONGLONG KeyName, LPBYTE Key) +{ + PCASC_ENCRYPTION_KEY pEncKey; + TCascStorage * hs; + + // Validate the storage handle + hs = TCascStorage::IsValid(hStorage); + if (hs == NULL) + { + SetLastError(ERROR_INVALID_HANDLE); + return false; + } + + // Don't allow more than CASC_EXTRA_KEYS keys + if (hs->ExtraKeysList.ItemCount() >= CASC_EXTRA_KEYS) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return false; + } + + // Insert the key to the array + pEncKey = (PCASC_ENCRYPTION_KEY)hs->ExtraKeysList.Insert(1); + if (pEncKey == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return false; + } + + // Fill the key + memcpy(pEncKey->Key, Key, sizeof(pEncKey->Key)); + pEncKey->KeyName = KeyName; + + // Also insert the key to the map + return hs->EncryptionKeys.InsertObject(pEncKey, &pEncKey->KeyName); +} diff --git a/dep/CascLib/src/CascDumpData.cpp b/dep/CascLib/src/CascDumpData.cpp index e6b4265d76b..40e791f7a15 100644 --- a/dep/CascLib/src/CascDumpData.cpp +++ b/dep/CascLib/src/CascDumpData.cpp @@ -11,47 +11,80 @@ #define __CASCLIB_SELF__ #include "CascLib.h" #include "CascCommon.h" -#include "CascMndx.h" #ifdef _DEBUG // The entire feature is only valid for debug purposes //----------------------------------------------------------------------------- // Forward definitions -//LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd); +int CaptureEncodingHeader(CASC_ENCODING_HEADER & EnHeader, LPBYTE pbFileData, size_t cbFileData); +int CaptureDownloadHeader(CASC_DOWNLOAD_HEADER & DlHeader, LPBYTE pbFileData, size_t cbFileData); +int CaptureDownloadEntry(CASC_DOWNLOAD_HEADER & DlHeader, CASC_DOWNLOAD_ENTRY & DlEntry, LPBYTE pbFilePtr, LPBYTE pbFileEnd); +int CaptureDownloadTag(CASC_DOWNLOAD_HEADER & DlHeader, CASC_TAG_ENTRY1 & DlTag, LPBYTE pbFilePtr, LPBYTE pbFileEnd); //----------------------------------------------------------------------------- -// Sort compare functions +// Local functions -static int CompareIndexEntries_FilePos(const void *, const void * pvIndexEntry1, const void * pvIndexEntry2) +static char * StringFromLPTSTR(const TCHAR * szString, char * szBuffer, size_t cchBuffer) { - PCASC_INDEX_ENTRY pIndexEntry1 = (PCASC_INDEX_ENTRY)pvIndexEntry1; - PCASC_INDEX_ENTRY pIndexEntry2 = (PCASC_INDEX_ENTRY)pvIndexEntry2; - ULONGLONG FileOffset1 = ConvertBytesToInteger_5(pIndexEntry1->FileOffsetBE); - ULONGLONG FileOffset2 = ConvertBytesToInteger_5(pIndexEntry2->FileOffsetBE); - DWORD ArchIndex1 = (DWORD)(FileOffset1 >> 0x1E); - DWORD ArchIndex2 = (DWORD)(FileOffset2 >> 0x1E); - - // First, compare the archive index - if(ArchIndex1 < ArchIndex2) - return -1; - if(ArchIndex1 > ArchIndex2) - return +1; - - // Second, compare the archive offset - FileOffset1 &= 0x3FFFFFFF; - FileOffset2 &= 0x3FFFFFFF; - if(FileOffset1 < FileOffset2) - return -1; - if(FileOffset1 > FileOffset2) - return +1; - - return 0; + char * szSaveBuffer = szBuffer; + char * szBufferEnd = szBuffer + cchBuffer - 1; + + while (szBuffer < szBufferEnd && szString[0] != 0) + *szBuffer++ = (char)*szString++; + szBuffer[0] = 0; + + return szSaveBuffer; } -//----------------------------------------------------------------------------- -// Public functions +// Either opens a dump file or returns "stdout" +static FILE * OpenDumpFile(const char * szDumpFile) +{ + if(szDumpFile != NULL) + { + return fopen(szDumpFile, "wt"); + } + else + { + return stdout; + } +} + +// Closes the dump file if it was open by OpenDumpFile +static void CloseDumpFile(const char * szDumpFile, FILE * fp) +{ + if(szDumpFile != NULL && fp != NULL) + fclose(fp); +} + +static void DumpKey(FILE * fp, const char * szInFormat, LPBYTE pbData, size_t cbData) +{ + const char * szFormatSpec; + LPBYTE pbDataEnd = pbData + cbData; + char szFormat[0x100]; + char szKey[MD5_STRING_SIZE + 1]; + + // First line: Copy the line with the complete format + CascStrCopy(szFormat, _countof(szFormat), szInFormat); + szFormatSpec = strstr(szFormat, "%s"); + // Dump all keys, every one on a new line + while(pbData < pbDataEnd) + { + // Fump the line + StringFromBinary(pbData, MD5_HASH_SIZE, szKey); + fprintf(fp, szFormat, szKey); + + // If there will be more lines, then we clear the entire part until "%s" + if(szFormatSpec != NULL) + memset(szFormat, ' ', (szFormatSpec - szFormat)); + + // Move pointers + pbData += MD5_HASH_SIZE; + } +} + +/* void CascDumpSparseArray(const char * szFileName, void * pvSparseArray) { TSparseArray * pSparseArray = (TSparseArray *)pvSparseArray; @@ -93,8 +126,8 @@ void CascDumpNameFragTable(const char * szFileName, void * pMarFile) fp = fopen(szFileName, "wt"); if(fp != NULL) { - PNAME_FRAG pNameTable = pDB->NameFragTable.NameFragArray; - const char * szNames = pDB->IndexStruct_174.NameFragments.CharArray; + PNAME_FRAG pNameTable = pDB->NameFragTable.ItemArray; + const char * szNames = pDB->IndexStruct_174.ItemArray; const char * szLastEntry; char szMatchType[0x100]; @@ -114,9 +147,9 @@ void CascDumpNameFragTable(const char * szFileName, void * pMarFile) { // Prepare the entry if(IS_SINGLE_CHAR_MATCH(pDB->NameFragTable, i)) - sprintf(szMatchType, "SINGLE_CHAR (\'%c\')", (pNameTable->FragOffs & 0xFF)); + CascStrPrintf(szMatchType, _countof(szMatchType), "SINGLE_CHAR (\'%c\')", (pNameTable->FragOffs & 0xFF)); else - sprintf(szMatchType, "NAME_FRAGMT (\"%s\")", szNames + pNameTable->FragOffs); + CascStrPrintf(szMatchType, _countof(szMatchType), "NAME_FRAGMT (\"%s\")", szNames + pNameTable->FragOffs); } // Dump the entry @@ -169,147 +202,269 @@ void CascDumpFileNames(const char * szFileName, void * pvMarFile) Struct1C.FreeStruct40(); } -void CascDumpIndexEntry( - TCascStorage * /* hs */, - TDumpContext * dc, - PCASC_INDEX_ENTRY pIndexEntry, - int /* nDumpLevel */) + +void DumpEKeyEntries(TCascStorage * hs, FILE * fp) { - if(pIndexEntry != NULL) + // Dump header + fprintf(fp, "=== Ekey Entries ============================================================\n"); + + // Dump index entries from all index files + for(int file = 0; file < CASC_INDEX_COUNT; file++) { - ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE); - DWORD ArchIndex = (DWORD)(FileOffset >> 0x1E); - DWORD FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE); - - // Mask the file offset - FileOffset &= 0x3FFFFFFF; - dump_print(dc, " data.%03u at 0x%08x (0x%lx bytes)\n", - ArchIndex, - (DWORD)FileOffset, - FileSize); - - //if(nDumpLevel > 2) - //{ - // QueryKey.pbData = pIndexEntry->IndexKey; - // QueryKey.cbData = MD5_HASH_SIZE; - // if(CascOpenFileByIndexKey((HANDLE)hs, &QueryKey, 0, &hFile)) - // { - // // Make sure that the data file is open and frame header loaded - // CascGetFileSize(hFile, NULL); - // hf = IsValidFileHandle(hFile); - // assert(hf->pStream != NULL); - - // // Read the header area - // FileOffset = hf->HeaderOffset - BLTE_HEADER_DELTA; - // FileStream_Read(hf->pStream, &FileOffset, HeaderArea, sizeof(HeaderArea)); - // CascCloseFile(hFile); - - // // Dump the header area - // dump_print(dc, " FileSize: %X Rest: %s\n", - // ConvertBytesToInteger_4_LE(&HeaderArea[0x10]), - // StringFromBinary(&HeaderArea[0x14], 10, szBuffer)); - // } - //} + PCASC_EKEY_ENTRY pEKeyEntry = hs->IndexFile[file].pEKeyEntries; + DWORD nEKeyEntries = hs->IndexFile[file].nEKeyEntries; + + // Only if they are present + if(pEKeyEntry && nEKeyEntries) + { +// fprintf(fp, "--- Index: %03u --------------------------------------------------------------\n", file); + for(DWORD entry = 0; entry < nEKeyEntries; entry++, pEKeyEntry++) + DumpEKeyEntry(fp, pEKeyEntry->EKey, CASC_EKEY_SIZE, pEKeyEntry->StorageOffset, ConvertBytesToInteger_4_LE(pEKeyEntry->EncodedSize)); + } } - else + + // Dump tail + fprintf(fp, "=============================================================================\n\n"); +} +*/ + +//----------------------------------------------------------------------------- +// Dumping the ENCODING manifest +/* +static void DumpESpecEntries(FILE * fp, LPBYTE pbDataPtr, LPBYTE pbDataEnd) +{ + fprintf(fp, "--- ESpec Entries -----------------------------------------------------------\n"); + + while(pbDataPtr < pbDataEnd) { - dump_print(dc, " NO INDEX ENTRY\n"); + const char * szESpecEntry = (const char *)pbDataPtr; + + // Find the end of the entry + while((pbDataPtr < pbDataEnd) && (pbDataPtr[0] != 0)) + { + pbDataPtr++; + } + + // Dump the entry + if(pbDataPtr[0] == 0) + { + fprintf(fp, "%s\n", szESpecEntry); + pbDataPtr++; + } } } -void CascDumpEncodingEntry( - TCascStorage * hs, - TDumpContext * dc, - PCASC_ENCODING_ENTRY pEncodingEntry, - int nDumpLevel) +static void DumpCKeyEntry(PCASC_CKEY_ENTRY pCKeyEntry, FILE * fp, DWORD nIndex) { - PCASC_INDEX_ENTRY pIndexEntry; - QUERY_KEY QueryKey; - LPBYTE pbIndexKey; - char szMd5[MD5_STRING_SIZE+1]; + LPBYTE pbEKey; + char szStringBuff[MD5_STRING_SIZE + 1]; - // If the encoding key exists - if(pEncodingEntry != NULL) + // Print the CKey and number of EKeys + fprintf(fp, "[0x%02X] %s (EKeys: %02u): ", nIndex, StringFromBinary(pCKeyEntry->CKey, MD5_HASH_SIZE, szStringBuff), pCKeyEntry->EKeyCount); + pbEKey = pCKeyEntry->EKey; + + // Print the EKeys + for(USHORT i = 0; i < pCKeyEntry->EKeyCount; i++, pbEKey += MD5_HASH_SIZE) + fprintf(fp, " %s ", StringFromBinary(pbEKey, MD5_HASH_SIZE, szStringBuff)); + fprintf(fp, "\n"); +} + +static void DumpCKeyPages(FILE * fp, CASC_ENCODING_HEADER & EnHeader, LPBYTE pbDataPtr, LPBYTE pbDataEnd) +{ + // Get the CKey page header and the page itself + PFILE_CKEY_PAGE pPageHeader = (PFILE_CKEY_PAGE)pbDataPtr; + LPBYTE pbPageEntry = (LPBYTE)(pPageHeader + EnHeader.CKeyPageCount); + + fprintf(fp, "--- CKey Pages --------------------------------------------------------------\n"); + for(DWORD i = 0; i < EnHeader.CKeyPageCount; i++, pPageHeader++) { - dump_print(dc, " Size %lx Key Count: %u\n", - ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE), - pEncodingEntry->KeyCount); + LPBYTE pbCKeyEntry = pbPageEntry; + LPBYTE pbEndOfPage = pbPageEntry + EnHeader.CKeyPageSize; + DWORD nCKeyIndex = 0; + + // Check if there is enough space in the buffer + if((pbPageEntry + EnHeader.CKeyPageSize) > pbDataEnd) + break; - // Dump all index keys - pbIndexKey = pEncodingEntry->EncodingKey + MD5_HASH_SIZE; - for(DWORD j = 0; j < pEncodingEntry->KeyCount; j++, pbIndexKey += MD5_HASH_SIZE) + DumpKey(fp, "First CKey: %s\n", pPageHeader->FirstKey, EnHeader.CKeyLength); + DumpKey(fp, "Page hash: %s\n", pPageHeader->SegmentHash, MD5_HASH_SIZE); + fprintf(fp, "\n"); + + // Parse all CKey entries + while(pbCKeyEntry <= pbEndOfPage) { - // Dump the index key - dump_print(dc, " %s\n", StringFromMD5(pbIndexKey, szMd5)); + PCASC_CKEY_ENTRY pCKeyEntry = (PCASC_CKEY_ENTRY)pbCKeyEntry; - // Dump the index entry as well - if(nDumpLevel >= DUMP_LEVEL_INDEX_ENTRIES) - { - QueryKey.pbData = pbIndexKey; - QueryKey.cbData = MD5_HASH_SIZE; - pIndexEntry = FindIndexEntry(hs, &QueryKey); - CascDumpIndexEntry(hs, dc, pIndexEntry, nDumpLevel); - } + // Get pointer to the encoding entry + if(pCKeyEntry->EKeyCount == 0) + break; + + // Dump this encoding entry + DumpCKeyEntry(pCKeyEntry, fp, nCKeyIndex++); + + // Move to the next encoding entry + pbCKeyEntry += sizeof(FILE_CKEY_ENTRY) + ((pCKeyEntry->EKeyCount - 1) * EnHeader.CKeyLength); + } + + // Move to the next segment + pbPageEntry += EnHeader.CKeyPageSize; + fprintf(fp, "\n"); + } +} + +void DumpEncodingManifest(TCascStorage * hs, LPBYTE pbData, ULONG cbData, FILE * fp) +{ + // Dump header + fprintf(fp, "=== ENCODING Manifest =======================================================\n"); + + // Dump the encoding file + if(hs->EncodingManifest.pbData && hs->EncodingManifest.cbData) + { + CASC_ENCODING_HEADER EnHeader; + LPBYTE pbFilePtr = hs->EncodingManifest.pbData; + DWORD cbDataSize; + + // Capture the header of the ENCODING file + if(CaptureEncodingHeader(EnHeader, hs->EncodingManifest.pbData, hs->EncodingManifest.cbData) == ERROR_SUCCESS) + { + // Dump the ENCODING file info + fprintf(fp, "Magic: %u\n", EnHeader.Magic); + fprintf(fp, "Version: %u\n", EnHeader.Version); + fprintf(fp, "CKey Length: %02X\n", EnHeader.CKeyLength); + fprintf(fp, "EKey Length: %02X\n", EnHeader.EKeyLength); + fprintf(fp, "CKey page size: %08X\n", EnHeader.CKeyPageSize); + fprintf(fp, "CKey page count: %08X\n", EnHeader.CKeyPageCount); + fprintf(fp, "EKey page size: %08X\n", EnHeader.EKeyPageSize); + fprintf(fp, "EKey page count: %08X\n", EnHeader.EKeyPageCount); + fprintf(fp, "ESpec block size: %08X\n", EnHeader.ESpecBlockSize); + pbFilePtr += sizeof(FILE_ENCODING_HEADER); + + // Dump all ESpec entries + DumpESpecEntries(fp, pbFilePtr, pbFilePtr + EnHeader.ESpecBlockSize); + pbFilePtr += EnHeader.ESpecBlockSize; + + // Parse all CKey pages and dump them + cbDataSize = EnHeader.CKeyPageCount * (sizeof(FILE_CKEY_PAGE) + EnHeader.CKeyPageSize); + DumpCKeyPages(fp, EnHeader, pbFilePtr, pbFilePtr + cbDataSize); + pbFilePtr += cbDataSize; } } else { - dump_print(dc, " NO ENCODING KEYS\n"); + fprintf(fp, "(empty)\n"); } -} -void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs) + // Dump tail + fprintf(fp, "=============================================================================\n\n"); +} +*/ +//----------------------------------------------------------------------------- +// Dumping the DOWNLOAD manifest +/* +static void DumpDownloadEntries(FILE * fp, CASC_DOWNLOAD_HEADER & DlHeader, LPBYTE pbDownloadPtr, LPBYTE pbDownloadEnd) { - PCASC_INDEX_ENTRY * ppIndexEntries; - FILE * fp; - size_t nIndexEntries = hs->pIndexEntryMap->ItemCount; - char szIndexKey[0x40]; + fprintf(fp, "--- DOWNLOAD Entries --------------------------------------------------------\n"); + fprintf(fp, "Index EKey FileSize Checksum Flags Prio\n"); + fprintf(fp, "-------- -------------------------------- ---------- -------- -------- ----\n"); - // Create the dump file - fp = fopen(szFileName, "wt"); - if(fp != NULL) + for(DWORD i = 0; i < DlHeader.EntryCount; i++) { - // Create linear aray - ppIndexEntries = CASC_ALLOC(PCASC_INDEX_ENTRY, nIndexEntries); - if(ppIndexEntries != NULL) - { - // Obtain the linear array of index entries - Map_EnumObjects(hs->pIndexEntryMap, (void **)ppIndexEntries); + CASC_DOWNLOAD_ENTRY DlEntry; + char szEKey[MD5_STRING_SIZE+1]; + char szChksum[0x40]; + char szFlags[0x40]; + + if(CaptureDownloadEntry(DlHeader, DlEntry, pbDownloadPtr, pbDownloadEnd) != ERROR_SUCCESS) + break; + + // Dump the entry + StringFromBinary(DlEntry.EKey, DlHeader.EKeyLength, szEKey); + CascStrPrintf(szChksum, _countof(szChksum), (DlHeader.EntryHasChecksum ? "%08X" : "<none> "), DlEntry.Checksum); + CascStrPrintf(szFlags, _countof(szFlags), (DlHeader.FlagByteSize ? "%08X" : "<none> "), DlEntry.Flags); + fprintf(fp, "[%06X] %-16s %010I64X %s %s %04X\n", i, szEKey, DlEntry.FileSize, szChksum, szFlags, DlEntry.Priority); + + // Move to the next entry + pbDownloadPtr += DlHeader.EntryLength; + } - // Sort the array by archive number and archive offset - qsort_pointer_array((void **)ppIndexEntries, nIndexEntries, CompareIndexEntries_FilePos, NULL); + fprintf(fp, "\n"); +} - // Parse the array - fprintf(fp, "ArNo ArOffset FileSize IndexKey\n==== ======== ======== ================================\n"); - for(size_t i = 0; i < nIndexEntries; i++) - { - PCASC_INDEX_ENTRY pIndexEntry = ppIndexEntries[i]; - ULONGLONG ArchOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE); - DWORD ArchIndex = (DWORD)(ArchOffset >> 0x1E); - DWORD FileSize; +static void DumpDownloadTags(FILE * fp, CASC_DOWNLOAD_HEADER & DlHeader, LPBYTE pbFilePtr, LPBYTE pbFileEnd) +{ + CASC_TAG_ENTRY1 DlTag; - FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE); - ArchOffset &= 0x3FFFFFFF; + fprintf(fp, "--- DOWNLOAD Tags ---------------------------------------------------------\n"); + for(DWORD i = 0; i < DlHeader.TagCount; i++) + { + // Capture the download tag + if(CaptureDownloadTag(DlHeader, DlTag, pbFilePtr, pbFileEnd) != ERROR_SUCCESS) + break; - fprintf(fp, " %02X %08X %08X %s\n", ArchIndex, (DWORD)ArchOffset, FileSize, StringFromBinary(pIndexEntry->IndexKey, CASC_FILE_KEY_SIZE, szIndexKey)); - } + // Dump the tag + fprintf(fp, "[%02X]: %08X = %s\n", i, DlTag.TagValue, DlTag.szTagName); + pbFilePtr += DlTag.TagLength; + } +} - CASC_FREE(ppIndexEntries); - } +void DumpDownloadManifest(TCascStorage * hs, FILE * fp) +{ + // Dump header + fprintf(fp, "=== DOWNLOAD Manifest =======================================================\n"); - fclose(fp); + // Dump the encoding file + if(hs->DownloadManifest.pbData && hs->DownloadManifest.cbData) + { + CASC_DOWNLOAD_HEADER DlHeader; + LPBYTE pbFileEnd = hs->DownloadManifest.pbData + hs->DownloadManifest.cbData; + LPBYTE pbFilePtr = hs->DownloadManifest.pbData; + + // Capture the header of the ENCODING file + if(CaptureDownloadHeader(DlHeader, hs->DownloadManifest.pbData, hs->DownloadManifest.cbData) == ERROR_SUCCESS) + { + // Dump the DOWNLOAD header + fprintf(fp, "Magic: %u\n", DlHeader.Magic); + fprintf(fp, "Version: %u\n", DlHeader.Version); + fprintf(fp, "EKey Length: %02X\n", DlHeader.EKeyLength); + fprintf(fp, "Checksum present: %s\n", DlHeader.EntryHasChecksum ? "Yes" : "No"); + fprintf(fp, "Entry Count: %08X\n", DlHeader.EntryCount); + fprintf(fp, "Tag Count: %08X\n", DlHeader.TagCount); + fprintf(fp, "Flag byte size: %08X\n", DlHeader.FlagByteSize); + fprintf(fp, "Base priority: %08X\n", DlHeader.BasePriority); + fprintf(fp, "Header length: %08X\n", (DWORD)DlHeader.HeaderLength); + fprintf(fp, "Entry length: %08X\n", (DWORD)DlHeader.EntryLength); + fprintf(fp, "\n"); + pbFilePtr += DlHeader.HeaderLength; + + // Dump all download entries + DumpDownloadEntries(fp, DlHeader, pbFilePtr, pbFileEnd); + pbFilePtr += DlHeader.EntryLength * DlHeader.EntryCount; + + // Dump all tags + DumpDownloadTags(fp, DlHeader, pbFilePtr, pbFileEnd); + } + } + else + { + fprintf(fp, "(empty)\n"); } + + // Dump tail + fprintf(fp, "=============================================================================\n\n"); } +*/ +//----------------------------------------------------------------------------- +// Public dumping functions -void CascDumpFile(const char * szFileName, HANDLE hFile) +void CascDumpFile(const char * szDumpFile, HANDLE hFile) { FILE * fp; DWORD dwBytesRead = 1; DWORD dwFilePos = CascSetFilePointer(hFile, 0, NULL, FILE_BEGIN); BYTE Buffer[0x1000]; - // Create the dump file - fp = fopen(szFileName, "wb"); + // Create/open the dump file + fp = OpenDumpFile(szDumpFile); if(fp != NULL) { // Read data as long as we can, write as long as we can @@ -324,11 +479,54 @@ void CascDumpFile(const char * szFileName, HANDLE hFile) break; } - // Close the local file - fclose(fp); - // Restore the file pointer CascSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN); + + // Close the dump file + CloseDumpFile(szDumpFile, fp); + } +} + +void CascDumpStorage(HANDLE hStorage, const char * szDumpFile) +{ + TCascStorage * hs; + FILE * fp = stdout; + char szStringBuff[0x800]; + + // Verify the storage handle + hs = TCascStorage::IsValid(hStorage); + if(hs == NULL) + return; + + // Create/open the dump file + fp = OpenDumpFile(szDumpFile); + if(fp != NULL) + { + // Dump the basic storage info + fprintf(fp, "=== Basic Storage Info ======================================================\n"); + fprintf(fp, "DataPath: %s\n", StringFromLPTSTR(hs->szDataPath, szStringBuff, sizeof(szStringBuff))); + fprintf(fp, "IndexPath: %s\n", StringFromLPTSTR(hs->szIndexPath, szStringBuff, sizeof(szStringBuff))); + fprintf(fp, "BuildFile: %s\n", StringFromLPTSTR(hs->szBuildFile, szStringBuff, sizeof(szStringBuff))); + fprintf(fp, "CDN Server: %s\n", StringFromLPTSTR(hs->szCdnServers, szStringBuff, sizeof(szStringBuff))); + fprintf(fp, "CDN Path: %s\n", StringFromLPTSTR(hs->szCdnPath, szStringBuff, sizeof(szStringBuff))); + DumpKey(fp, "CDN Config Key: %s\n", hs->CdnConfigKey.pbData, hs->CdnConfigKey.cbData); + DumpKey(fp, "CDN Build Key: %s\n", hs->CdnBuildKey.pbData, hs->CdnBuildKey.cbData); + DumpKey(fp, "Archives Key: %s\n", hs->ArchivesKey.pbData, hs->ArchivesKey.cbData); + DumpKey(fp, "ROOT file: %s\n", hs->RootFile.CKey, CASC_CKEY_SIZE); + DumpKey(fp, "PATCH file: %s\n", hs->PatchFile.CKey, CASC_CKEY_SIZE); + DumpKey(fp, "ENCODING file: %s\n", hs->EncodingCKey.CKey, CASC_CKEY_SIZE); + DumpKey(fp, "DOWNLOAD file: %s\n", hs->DownloadCKey.CKey, CASC_CKEY_SIZE); + DumpKey(fp, "INSTALL file: %s\n", hs->InstallCKey.CKey, CASC_CKEY_SIZE); + fprintf(fp, "\n"); + + // Dump the complete ENCODING manifest +// DumpEncodingManifest(hs, fp); + + // Dump the complete ENCODING manifest +// DumpDownloadManifest(hs, fp); + + // Close the dump file + CloseDumpFile(szDumpFile, fp); } } diff --git a/dep/CascLib/src/CascFiles.cpp b/dep/CascLib/src/CascFiles.cpp index 3132992db4c..33f065b66d3 100644 --- a/dep/CascLib/src/CascFiles.cpp +++ b/dep/CascLib/src/CascFiles.cpp @@ -14,9 +14,13 @@ #include "CascCommon.h" //----------------------------------------------------------------------------- -// Local functions +// Local defines + +typedef int (*PARSETEXTFILE)(TCascStorage * hs, void * pvListFile); +typedef int (*PARSECSVFILE)(TCascStorage * hs, CASC_CSV & Csv); +typedef int (*PARSE_VARIABLE)(TCascStorage * hs, const char * szVariableName, const char * szDataBegin, const char * szDataEnd, void * pvParam); -typedef int (*PARSEINFOFILE)(TCascStorage * hs, void * pvListFile); +#define MAX_VAR_NAME 80 //----------------------------------------------------------------------------- // Local structures @@ -27,47 +31,62 @@ struct TBuildFileInfo CBLD_TYPE BuildFileType; }; -struct TGameIdString +struct TGameLocaleString { - const char * szGameInfo; - size_t cchGameInfo; - DWORD dwGameInfo; + const char * szLocale; + DWORD dwLocale; }; static const TBuildFileInfo BuildTypes[] = { {_T(".build.info"), CascBuildInfo}, // Since HOTS build 30027, the game uses .build.info file for storage info {_T(".build.db"), CascBuildDb}, // Older CASC storages + {_T("versions"), CascVersionsDb}, // Older CASC storages {NULL, CascBuildNone} }; static const TCHAR * DataDirs[] = { - _T("SC2Data"), // Starcraft II (Legacy of the Void) build 38749 - _T("Data\\Casc"), // Overwatch + _T("data") _T(PATH_SEP_STRING) _T("casc"), // Overwatch + _T("data"), // TACT casc (for Linux systems) _T("Data"), // World of Warcraft, Diablo + _T("SC2Data"), // Starcraft II (Legacy of the Void) build 38749 _T("HeroesData"), // Heroes of the Storm _T("BNTData"), // Heroes of the Storm, until build 30414 NULL, }; -static const TGameIdString GameIds[] = +static const TGameLocaleString LocaleStrings[] = { - {"Hero", 0x04, CASC_GAME_HOTS}, // Alpha build of Heroes of the Storm - {"WoW", 0x03, CASC_GAME_WOW6}, // Alpha build of World of Warcraft - Warlords of Draenor - {"Diablo3", 0x07, CASC_GAME_DIABLO3}, // Diablo III BETA 2.2.0 - {"Prometheus", 0x0A, CASC_GAME_OVERWATCH}, // Overwatch BETA since build 24919 - {"SC2", 0x03, CASC_GAME_STARCRAFT2}, // Starcraft II - Legacy of the Void - {"Starcraft1", 0x0A, CASC_GAME_STARCRAFT1}, // Starcraft 1 (remake) - {NULL, 0, 0}, + {"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 +static bool inline IsWhiteSpace(const char * szVarValue) +{ + return (0 <= szVarValue[0] && szVarValue[0] <= 0x20); +} + static bool inline IsValueSeparator(const char * szVarValue) { - return ((0 <= szVarValue[0] && szVarValue[0] <= 0x20) || (szVarValue[0] == '|')); + return (IsWhiteSpace(szVarValue) || (szVarValue[0] == '|')); } static bool IsCharDigit(BYTE OneByte) @@ -75,597 +94,606 @@ static bool IsCharDigit(BYTE OneByte) return ('0' <= OneByte && OneByte <= '9'); } -static DWORD GetLocaleMask(const char * szTag) +static const char * CaptureDecimalInteger(const char * szDataPtr, const char * szDataEnd, PDWORD PtrValue) { - if(!strcmp(szTag, "enUS")) - return CASC_LOCALE_ENUS; + const char * szSaveDataPtr = szDataPtr; + DWORD TotalValue = 0; + DWORD AddValue = 0; - if(!strcmp(szTag, "koKR")) - return CASC_LOCALE_KOKR; + // Skip all spaces + while (szDataPtr < szDataEnd && szDataPtr[0] == ' ') + szDataPtr++; - if(!strcmp(szTag, "frFR")) - return CASC_LOCALE_FRFR; + // Load the number + while (szDataPtr < szDataEnd && szDataPtr[0] != ' ') + { + // Must only contain decimal digits ('0' - '9') + if (!IsCharDigit(szDataPtr[0])) + break; - if(!strcmp(szTag, "deDE")) - return CASC_LOCALE_DEDE; + // Get the next value and verify overflow + AddValue = szDataPtr[0] - '0'; + if ((TotalValue + AddValue) < TotalValue) + return NULL; - if(!strcmp(szTag, "zhCN")) - return CASC_LOCALE_ZHCN; + TotalValue = (TotalValue * 10) + AddValue; + szDataPtr++; + } - if(!strcmp(szTag, "esES")) - return CASC_LOCALE_ESES; + // If there were no decimal digits, we consider it failure + if(szDataPtr == szSaveDataPtr) + return NULL; - if(!strcmp(szTag, "zhTW")) - return CASC_LOCALE_ZHTW; + // Give the result + PtrValue[0] = TotalValue; + return szDataPtr; +} - if(!strcmp(szTag, "enGB")) - return CASC_LOCALE_ENGB; +static const char * CaptureSingleString(const char * szDataPtr, const char * szDataEnd, char * szBuffer, size_t ccBuffer) +{ + char * szBufferEnd = szBuffer + ccBuffer - 1; - if(!strcmp(szTag, "enCN")) - return CASC_LOCALE_ENCN; + // Skip all whitespaces + while (szDataPtr < szDataEnd && IsWhiteSpace(szDataPtr)) + szDataPtr++; - if(!strcmp(szTag, "enTW")) - return CASC_LOCALE_ENTW; + // Copy the string + while (szDataPtr < szDataEnd && szBuffer < szBufferEnd && !IsWhiteSpace(szDataPtr) && szDataPtr[0] != '=') + *szBuffer++ = *szDataPtr++; - if(!strcmp(szTag, "esMX")) - return CASC_LOCALE_ESMX; + szBuffer[0] = 0; + return szDataPtr; +} - if(!strcmp(szTag, "ruRU")) - return CASC_LOCALE_RURU; +static const char * CaptureSingleHash(const char * szDataPtr, const char * szDataEnd, LPBYTE HashValue, size_t HashLength) +{ + const char * szHashString; + size_t HashStringLength = HashLength * 2; - if(!strcmp(szTag, "ptBR")) - return CASC_LOCALE_PTBR; + // Skip all whitespaces + while (szDataPtr < szDataEnd && IsWhiteSpace(szDataPtr)) + szDataPtr++; + szHashString = szDataPtr; - if(!strcmp(szTag, "itIT")) - return CASC_LOCALE_ITIT; + // Count all hash characters + for (size_t i = 0; i < HashStringLength; i++) + { + if (szDataPtr >= szDataEnd || isxdigit(szDataPtr[0]) == 0) + return NULL; + szDataPtr++; + } - if(!strcmp(szTag, "ptPT")) - return CASC_LOCALE_PTPT; + // There must be a separator or end-of-string + if (szDataPtr > szDataEnd || IsWhiteSpace(szDataPtr) == false) + return NULL; - return 0; + // Give the values + ConvertStringToBinary(szHashString, HashStringLength, HashValue); + return szDataPtr; } -static bool IsInfoVariable(const char * szLineBegin, const char * szLineEnd, const char * szVarName, const char * szVarType) +static const char * CaptureHashCount(const char * szDataPtr, const char * szDataEnd, size_t * PtrHashCount) { - size_t nLength; + BYTE HashValue[MD5_HASH_SIZE]; + size_t HashCount = 0; - // Check the variable name - nLength = strlen(szVarName); - if((size_t)(szLineEnd - szLineBegin) > nLength) + // Capculate the hash count + while (szDataPtr < szDataEnd) { - // Check the variable name - if(!_strnicmp(szLineBegin, szVarName, nLength)) - { - // Skip variable name and the exclamation mark - szLineBegin += nLength; - if(szLineBegin < szLineEnd && szLineBegin[0] == '!') - { - // Skip the exclamation mark - szLineBegin++; + // Check one hash + szDataPtr = CaptureSingleHash(szDataPtr, szDataEnd, HashValue, MD5_HASH_SIZE); + if (szDataPtr == NULL) + return NULL; - // Check the variable type - nLength = strlen(szVarType); - if((size_t)(szLineEnd - szLineBegin) > nLength) - { - // Check the variable name - if(!_strnicmp(szLineBegin, szVarType, nLength)) - { - // Skip variable type and the doublecolon - szLineBegin += nLength; - return (szLineBegin < szLineEnd && szLineBegin[0] == ':'); - } - } - } - } + // Skip all whitespaces + while (szDataPtr < szDataEnd && IsWhiteSpace(szDataPtr)) + szDataPtr++; + + HashCount++; } - return false; + // Give results + PtrHashCount[0] = HashCount; + return szDataPtr; } -static const char * SkipInfoVariable(const char * szLineBegin, const char * szLineEnd) +static DWORD GetLocaleMask(const char * szTag) { - while(szLineBegin < szLineEnd) + for(size_t i = 0; LocaleStrings[i].szLocale != NULL; i++) { - if(szLineBegin[0] == '|') - return szLineBegin + 1; - - szLineBegin++; + if(!strncmp(LocaleStrings[i].szLocale, szTag, 4)) + { + return LocaleStrings[i].dwLocale; + } } - return NULL; + return 0; } -static TCHAR * CheckForIndexDirectory(TCascStorage * hs, const TCHAR * szSubDir) +static bool CheckConfigFileVariable( + TCascStorage * hs, // Pointer to storage structure + const char * szLinePtr, // Pointer to the begin of the line + const char * szLineEnd, // Pointer to the end of the line + const char * szVarName, // Pointer to the variable to check + PARSE_VARIABLE PfnParseProc, // Pointer to the parsing function + void * pvParseParam) // Pointer to the parameter passed to parsing function { - TCHAR * szIndexPath; + char szVariableName[MAX_VAR_NAME]; - // Cpmbine the index path - szIndexPath = CombinePath(hs->szDataPath, szSubDir); - if(DirectoryExists(szIndexPath)) - { - hs->szIndexPath = szIndexPath; - return hs->szIndexPath; - } + // Capture the variable from the line + szLinePtr = CaptureSingleString(szLinePtr, szLineEnd, szVariableName, sizeof(szVariableName)); + if (szLinePtr == NULL) + return false; - CASC_FREE(szIndexPath); - return NULL; -} + // Verify whether this is the variable + if (!CascCheckWildCard(szVariableName, szVarName)) + return false; -TCHAR * AppendBlobText(TCHAR * szBuffer, LPBYTE pbData, DWORD cbData, TCHAR chSeparator) -{ - // Put the separator, if any - if(chSeparator != 0) - *szBuffer++ = chSeparator; - - // Copy the blob data as text - for(DWORD i = 0; i < cbData; i++) - { - *szBuffer++ = IntToHexChar[pbData[0] >> 0x04]; - *szBuffer++ = IntToHexChar[pbData[0] & 0x0F]; - pbData++; - } + // Skip the spaces and '=' + while (szLinePtr < szLineEnd && (IsWhiteSpace(szLinePtr) || szLinePtr[0] == '=')) + szLinePtr++; - // Terminate the string - *szBuffer = 0; + // Call the parsing function only if there is some data + if (szLinePtr >= szLineEnd) + return false; - // Return new buffer position - return szBuffer; + return (PfnParseProc(hs, szVariableName, szLinePtr, szLineEnd, pvParseParam) == ERROR_SUCCESS); } -static const char * CheckLineVariable(const char * szLineBegin, const char * szLineEnd, const char * szVarName) +static int LoadHashArray( + PQUERY_KEY pBlob, + const char * szLinePtr, + const char * szLineEnd, + size_t HashCount) { - size_t nLineLength = (size_t)(szLineEnd - szLineBegin); - size_t nNameLength = strlen(szVarName); + int nError = ERROR_NOT_ENOUGH_MEMORY; - // If the line longer than the variable name? - if(nLineLength > nNameLength) + // Allocate the blob buffer + pBlob->cbData = (DWORD)(HashCount * MD5_HASH_SIZE); + pBlob->pbData = CASC_ALLOC(BYTE, pBlob->cbData); + if (pBlob->pbData != NULL) { - if(!_strnicmp((const char *)szLineBegin, szVarName, nNameLength)) - { - // Skip the variable name - szLineBegin += nNameLength; - - // Skip the separator(s) - while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin)) - szLineBegin++; - - // Check if there is "=" - if(szLineBegin >= szLineEnd || szLineBegin[0] != '=') - return NULL; - szLineBegin++; - - // Skip the separator(s) - while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin)) - szLineBegin++; + LPBYTE pbBuffer = pBlob->pbData; - // Check if there is "=" - if(szLineBegin >= szLineEnd) - return NULL; + for (size_t i = 0; i < HashCount; i++) + { + // Capture the hash value + szLinePtr = CaptureSingleHash(szLinePtr, szLineEnd, pbBuffer, MD5_HASH_SIZE); + if (szLinePtr == NULL) + return ERROR_BAD_FORMAT; - // Return the begin of the variable - return szLineBegin; + // Move buffer + pbBuffer += MD5_HASH_SIZE; } + + nError = ERROR_SUCCESS; } - return NULL; + return nError; } -static int LoadInfoVariable(PQUERY_KEY pVarBlob, const char * szLineBegin, const char * szLineEnd, bool bHexaValue) +static int LoadMultipleHashes(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd) { - const char * szLinePtr = szLineBegin; - - // Sanity checks - assert(pVarBlob->pbData == NULL); - assert(pVarBlob->cbData == 0); + size_t HashCount = 0; + int nError = ERROR_SUCCESS; - // Check length of the variable - while(szLinePtr < szLineEnd && szLinePtr[0] != '|') - szLinePtr++; + // Retrieve the hash count + if (CaptureHashCount(szLineBegin, szLineEnd, &HashCount) == NULL) + return ERROR_BAD_FORMAT; - // Allocate space for the blob - if(bHexaValue) + // Do nothing if there is no data + if(HashCount != 0) { - // Initialize the blob - pVarBlob->pbData = CASC_ALLOC(BYTE, (szLinePtr - szLineBegin) / 2); - pVarBlob->cbData = (DWORD)((szLinePtr - szLineBegin) / 2); - return ConvertStringToBinary(szLineBegin, (size_t)(szLinePtr - szLineBegin), pVarBlob->pbData); + nError = LoadHashArray(pBlob, szLineBegin, szLineEnd, HashCount); } - // Initialize the blob - pVarBlob->pbData = CASC_ALLOC(BYTE, (szLinePtr - szLineBegin) + 1); - pVarBlob->cbData = (DWORD)(szLinePtr - szLineBegin); - - // Check for success - if(pVarBlob->pbData == NULL) - return ERROR_NOT_ENOUGH_MEMORY; + return nError; +} - // Copy the string - memcpy(pVarBlob->pbData, szLineBegin, pVarBlob->cbData); - pVarBlob->pbData[pVarBlob->cbData] = 0; - return ERROR_SUCCESS; +// 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) +{ + return LoadMultipleHashes((PQUERY_KEY)pvParam, szDataBegin, szDataEnd); } -static void AppendConfigFilePath(TCHAR * szFileName, PQUERY_KEY pFileKey) +static int LoadCKeyEntry(TCascStorage * hs, const char * szVariableName, const char * szDataPtr, const char * szDataEnd, void * pvParam) { - size_t nLength = _tcslen(szFileName); + PCASC_CKEY_ENTRY pCKeyEntry = (PCASC_CKEY_ENTRY)pvParam; + size_t nLength = strlen(szVariableName); + size_t HashCount = 0; + + // If the variable ends at "-size", it means we need to capture the size + if((nLength > 5) && !strcmp(szVariableName + nLength - 5, "-size")) + { + DWORD ContentSize = CASC_INVALID_SIZE; + DWORD EncodedSize = CASC_INVALID_SIZE; - // If there is no slash, append if - if(nLength > 0 && szFileName[nLength - 1] != '\\' && szFileName[nLength - 1] != '/') - szFileName[nLength++] = _T('/'); + // 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; + } + } + else + { + // Get the number of hashes. It is expected to be 1 or 2 + if(CaptureHashCount(szDataPtr, szDataEnd, &HashCount) != NULL) + { + // Capture the CKey + if(HashCount >= 1) + { + // Load the CKey. This should alway be there + szDataPtr = CaptureSingleHash(szDataPtr, szDataEnd, pCKeyEntry->CKey, MD5_HASH_SIZE); + if(szDataPtr == NULL) + return ERROR_BAD_FORMAT; + pCKeyEntry->Flags |= CASC_CE_HAS_CKEY; + } - // Get to the end of the file name - szFileName = szFileName + nLength; + // Capture EKey, if any + if(HashCount == 2) + { + // Load the EKey into the structure + szDataPtr = CaptureSingleHash(szDataPtr, szDataEnd, pCKeyEntry->EKey, MD5_HASH_SIZE); + if(szDataPtr == NULL) + return ERROR_BAD_FORMAT; + pCKeyEntry->Flags |= CASC_CE_HAS_EKEY; + + // Increment the number of EKey entries loaded from text build file + hs->EKeyEntries++; + } - // Append the "config" directory - _tcscpy(szFileName, _T("config")); - szFileName += 6; + return (HashCount == 1 || HashCount == 2) ? ERROR_SUCCESS : ERROR_BAD_FORMAT; + } + } - // Append the first level directory - szFileName = AppendBlobText(szFileName, pFileKey->pbData, 1, _T('/')); - szFileName = AppendBlobText(szFileName, pFileKey->pbData + 1, 1, _T('/')); - szFileName = AppendBlobText(szFileName, pFileKey->pbData, pFileKey->cbData, _T('/')); + // Unrecognized + return ERROR_BAD_FORMAT; } -static DWORD GetBlobCount(const char * szLineBegin, const char * szLineEnd) +static int LoadVfsRootEntry(TCascStorage * hs, const char * szVariableName, const char * szDataPtr, const char * szDataEnd, void * pvParam) { - DWORD dwBlobCount = 0; - - // Until we find an end of the line - while(szLineBegin < szLineEnd) + PCASC_CKEY_ENTRY pCKeyEntry; + CASC_ARRAY * pArray = (CASC_ARRAY *)pvParam; + const char * szVarPtr = szVariableName; + const char * szVarEnd = szVarPtr + strlen(szVarPtr); + DWORD VfsRootIndex = CASC_INVALID_INDEX; + + // Skip the "vfs-" part + if (!strncmp(szVariableName, "vfs-", 4)) { - // Skip the blob - while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin) == false) - szLineBegin++; + // Then, there must be a decimal number as index + if ((szVarPtr = CaptureDecimalInteger(szVarPtr + 4, szVarEnd, &VfsRootIndex)) != NULL) + { + // We expect the array to be initialized + assert(pArray->IsInitialized()); - // Increment the number of blobs - dwBlobCount++; + // The index of the key must not be NULL + if(VfsRootIndex != 0) + { + // Make sure that he entry is in the array + pCKeyEntry = (PCASC_CKEY_ENTRY)pArray->ItemAt(VfsRootIndex - 1); + if(pCKeyEntry == NULL) + { + // Insert a new entry + pCKeyEntry = (PCASC_CKEY_ENTRY)pArray->InsertAt(VfsRootIndex - 1); + if(pCKeyEntry == NULL) + return ERROR_NOT_ENOUGH_MEMORY; + + // Initialize the new entry + pCKeyEntry->Init(); + } - // Skip the separator - while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin)) - szLineBegin++; + // Call just a normal parse function + return LoadCKeyEntry(hs, szVariableName, szDataPtr, szDataEnd, pCKeyEntry); + } + } } - return dwBlobCount; + return ERROR_SUCCESS; } -static int LoadBlobArray( - PQUERY_KEY pBlob, - const char * szLineBegin, - const char * szLineEnd, - DWORD dwMaxBlobs) +static int LoadBuildProductId(TCascStorage * hs, const char * /* szVariableName */, const char * szDataBegin, const char * szDataEnd, void * /* pvParam */) { - LPBYTE pbBufferEnd = pBlob->pbData + pBlob->cbData; - LPBYTE pbBuffer = pBlob->pbData; - int nError = ERROR_SUCCESS; + DWORD dwBuildUid = 0; - // Sanity check - assert(pBlob->pbData != NULL); - assert(pBlob->cbData != 0); - - // Until we find an end of the line - while(szLineBegin < szLineEnd && dwMaxBlobs > 0) + // Convert up to 4 chars to DWORD + for(size_t i = 0; i < 4 && szDataBegin < szDataEnd; i++) { - const char * szBlobEnd = szLineBegin; - - // Find the end of the text blob - while(szBlobEnd < szLineEnd && IsValueSeparator(szBlobEnd) == false) - szBlobEnd++; - - // Verify the length of the found blob - if((szBlobEnd - szLineBegin) != MD5_STRING_SIZE) - return ERROR_BAD_FORMAT; - - // Verify if there is enough space in the buffer - if((pbBufferEnd - pbBuffer) < MD5_HASH_SIZE) - return ERROR_NOT_ENOUGH_MEMORY; - - // Perform the conversion - nError = ConvertStringToBinary(szLineBegin, MD5_STRING_SIZE, pbBuffer); - if(nError != ERROR_SUCCESS) - return nError; + dwBuildUid = (dwBuildUid << 0x08) | (BYTE)szDataBegin[0]; + szDataBegin++; + } - // Move pointers - pbBuffer += MD5_HASH_SIZE; - dwMaxBlobs--; + // Product-specific. See https://wowdev.wiki/TACT#Products + switch(dwBuildUid) + { + 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; - // Skip the separator - while(szBlobEnd < szLineEnd && IsValueSeparator(szBlobEnd)) - szBlobEnd++; - szLineBegin = szBlobEnd; - } + case 0x64737432: // 'dst2': + hs->szProductName = "Destiny 2"; + hs->Product = Destiny2; + break; - return nError; -} + 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; -static int LoadMultipleBlobs(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd, DWORD dwBlobCount) -{ - size_t nLength = (szLineEnd - szLineBegin); + 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; - // We expect each blob to have length of the encoding key and one space between - if(nLength > (dwBlobCount * MD5_STRING_SIZE) + ((dwBlobCount - 1) * sizeof(char))) - return ERROR_INVALID_PARAMETER; + 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; - // Allocate the blob buffer - pBlob->pbData = CASC_ALLOC(BYTE, dwBlobCount * MD5_HASH_SIZE); - if(pBlob->pbData == NULL) - return ERROR_NOT_ENOUGH_MEMORY; + 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; - // Set the buffer size and load the blob array - pBlob->cbData = dwBlobCount * MD5_HASH_SIZE; - return LoadBlobArray(pBlob, szLineBegin, szLineEnd, dwBlobCount); -} + case 0x76697065: // "viper", "viperdev", "viperv1": Call of Duty Black Ops 4 + hs->szProductName = "Call Of Duty Black Ops 4"; + hs->Product = CallOfDutyBlackOps4; + break; -static int LoadMultipleBlobs(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd) -{ - return LoadMultipleBlobs(pBlob, szLineBegin, szLineEnd, GetBlobCount(szLineBegin, szLineEnd)); -} + 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; -static int LoadSingleBlob(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd) -{ - return LoadMultipleBlobs(pBlob, szLineBegin, szLineEnd, 1); -} + 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; -static int GetGameType(TCascStorage * hs, const char * szVarBegin, const char * szLineEnd) -{ - // Go through all games that we support - for(size_t i = 0; GameIds[i].szGameInfo != NULL; i++) - { - // Check the length of the variable - if((size_t)(szLineEnd - szVarBegin) == GameIds[i].cchGameInfo) - { - // Check the string - if(!_strnicmp(szVarBegin, GameIds[i].szGameInfo, GameIds[i].cchGameInfo)) - { - hs->dwGameInfo = GameIds[i].dwGameInfo; - return ERROR_SUCCESS; - } - } + default: + hs->szProductName = "Unknown Product"; + hs->Product = UnknownProduct; + break; } - // Unknown/unsupported game - assert(false); - return ERROR_BAD_FORMAT; + return ERROR_SUCCESS; } // "B29049" // "WOW-18125patch6.0.1" // "30013_Win32_2_2_0_Ptr_ptr" // "prometheus-0_8_0_0-24919" -static int GetBuildNumber(TCascStorage * hs, const char * szVarBegin, const char * szLineEnd) +static int LoadBuildNumber(TCascStorage * hs, const char * /* szVariableName */, const char * szDataBegin, const char * szDataEnd, void * /* pvParam */) { DWORD dwBuildNumber = 0; + DWORD dwMaxValue = 0; - // Skip all non-digit characters - while(szVarBegin < szLineEnd) + // Parse the string and take the largest decimal numeric value + // "build-name = 1.21.5.4037-retail" + while(szDataBegin < szDataEnd) { // There must be at least three digits (build 99 anyone?) - if(IsCharDigit(szVarBegin[0]) && IsCharDigit(szVarBegin[1]) && IsCharDigit(szVarBegin[2])) + if(IsCharDigit(szDataBegin[0])) { - // Convert the build number string to value - while(szVarBegin < szLineEnd && IsCharDigit(szVarBegin[0])) - dwBuildNumber = (dwBuildNumber * 10) + (*szVarBegin++ - '0'); - break; + dwBuildNumber = (dwBuildNumber * 10) + (szDataBegin[0] - '0'); + dwMaxValue = CASCLIB_MAX(dwBuildNumber, dwMaxValue); + } + else + { + // Reset build number when we find non-digit char + dwBuildNumber = 0; } // Move to the next - szVarBegin++; + szDataBegin++; } - assert(dwBuildNumber != 0); - hs->dwBuildNumber = dwBuildNumber; - return (dwBuildNumber != 0) ? ERROR_SUCCESS : ERROR_BAD_FORMAT; + // If not there, just take value from build file + if((hs->dwBuildNumber = dwMaxValue) == 0) + hs->dwBuildNumber = hs->CdnBuildKey.pbData[0] % 100; + return ERROR_BAD_FORMAT; } -static int GetDefaultLocaleMask(TCascStorage * hs, PQUERY_KEY pTagsString) +static int LoadQueryKey(const CASC_CSV_COLUMN & Column, QUERY_KEY & Key) { - char * szTagEnd = (char *)pTagsString->pbData + pTagsString->cbData; - char * szTagPtr = (char *)pTagsString->pbData; - char * szNext; + // Check the input data + if (Column.szValue == NULL) + return ERROR_BUFFER_OVERFLOW; + if (Column.nLength != MD5_STRING_SIZE) + return ERROR_BAD_FORMAT; + + return LoadHashArray(&Key, Column.szValue, Column.szValue + Column.nLength, 1); +} + +static int GetDefaultLocaleMask(TCascStorage * hs, const CASC_CSV_COLUMN & Column) +{ + const char * szTagEnd = Column.szValue + Column.nLength; + const char * szTagPtr = Column.szValue; DWORD dwLocaleMask = 0; while(szTagPtr < szTagEnd) { - // Get the next part - szNext = strchr(szTagPtr, ' '); - if(szNext != NULL) - *szNext++ = 0; - - // Check whether the current tag is a language identifier - dwLocaleMask = dwLocaleMask | GetLocaleMask(szTagPtr); - - // Get the next part - if(szNext == NULL) - break; - - // Skip spaces - while(szNext < szTagEnd && szNext[0] == ' ') - szNext++; - szTagPtr = szNext; + // Could this be a locale string? + if((szTagPtr + 4) <= szTagEnd && (szTagPtr[4] == ',' || szTagPtr[4] == ' ')) + { + // Check whether the current tag is a language identifier + dwLocaleMask = dwLocaleMask | GetLocaleMask(szTagPtr); + szTagPtr += 4; + } + else + { + szTagPtr++; + } } hs->dwDefaultLocale = dwLocaleMask; return ERROR_SUCCESS; } -static void * FetchAndVerifyConfigFile(TCascStorage * hs, PQUERY_KEY pFileKey) +static int ParseFile_CDNS(TCascStorage * hs, CASC_CSV & Csv) { - TCHAR * szFileName; - void * pvListFile = NULL; + const char * szWantedRegion = hs->szRegion; + TCHAR szCdnServers[MAX_PATH]; + TCHAR szCdnPath[MAX_PATH]; + size_t nLineCount; - // Construct the local file name - szFileName = CascNewStr(hs->szDataPath, 8 + 3 + 3 + 32); - if(szFileName != NULL) - { - // Add the part where the config file path is - AppendConfigFilePath(szFileName, pFileKey); + // Fix the region + if(szWantedRegion == NULL || !_stricmp(szWantedRegion, "beta") || !_stricmp(szWantedRegion, "xx")) + szWantedRegion = "us"; + + // Determine the row count + nLineCount = Csv.GetLineCount(); - // Load and verify the external listfile - pvListFile = ListFile_OpenExternal(szFileName); - if(pvListFile != NULL) + // Find the active config + for (size_t i = 0; i < nLineCount; i++) + { + // Is it the version we are looking for? + if(!strcmp(Csv[i]["Name!STRING:0"].szValue, szWantedRegion)) { - if(!ListFile_VerifyMD5(pvListFile, pFileKey->pbData)) - { - ListFile_Free(pvListFile); - pvListFile = NULL; - } - } + // Save the list of CDN servers + CascStrCopy(szCdnServers, _countof(szCdnServers), Csv[i]["Hosts!STRING:0"].szValue); + hs->szCdnServers = CascNewStr(szCdnServers); + + // Save the CDN subpath + CascStrCopy(szCdnPath, _countof(szCdnPath), Csv[i]["Path!STRING:0"].szValue); + hs->szCdnPath = CascNewStr(szCdnPath); - // Free the file name - CASC_FREE(szFileName); + // Check and return result + return (hs->szCdnServers && hs->szCdnPath) ? ERROR_SUCCESS : ERROR_BAD_FORMAT; + } } - return pvListFile; + return ERROR_FILE_NOT_FOUND; } -static int ParseFile_BuildInfo(TCascStorage * hs, void * pvListFile) +static int ParseFile_BuildInfo(TCascStorage * hs, CASC_CSV & Csv) { - QUERY_KEY Active = {NULL, 0}; - QUERY_KEY TagString = {NULL, 0}; - QUERY_KEY CdnHost = {NULL, 0}; - QUERY_KEY CdnPath = {NULL, 0}; - const char * szLinePtr1; - const char * szLineEnd1; - const char * szLinePtr2; - const char * szLineEnd2; - size_t nLength1; - size_t nLength2; - int nError = ERROR_BAD_FORMAT; - - // Extract the first line, cotaining the headers - nLength1 = ListFile_GetNextLine(pvListFile, &szLinePtr1, &szLineEnd1); - if(nLength1 == 0) - return ERROR_BAD_FORMAT; + size_t nLineCount = Csv.GetLineCount(); + int nError; - // Now parse the second and the next lines. We are looking for line - // with "Active" set to 1 - for(;;) + // Find the active config + for(size_t i = 0; i < nLineCount; i++) { - // Read the next line - nLength2 = ListFile_GetNextLine(pvListFile, &szLinePtr2, &szLineEnd2); - if(nLength2 == 0) - break; - - // Parse all variables - while(szLinePtr1 < szLineEnd1) + // Is that build config active? + if (!strcmp(Csv[i]["Active!DEC:1"].szValue, "1")) { - // Check for variables we need - if(IsInfoVariable(szLinePtr1, szLineEnd1, "Active", "DEC")) - LoadInfoVariable(&Active, szLinePtr2, szLineEnd2, false); - if(IsInfoVariable(szLinePtr1, szLineEnd1, "Build Key", "HEX")) - LoadInfoVariable(&hs->CdnBuildKey, szLinePtr2, szLineEnd2, true); - if(IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Key", "HEX")) - LoadInfoVariable(&hs->CdnConfigKey, szLinePtr2, szLineEnd2, true); - if(IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Hosts", "STRING")) - LoadInfoVariable(&CdnHost, szLinePtr2, szLineEnd2, false); - if(IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Path", "STRING")) - LoadInfoVariable(&CdnPath, szLinePtr2, szLineEnd2, false); - if(IsInfoVariable(szLinePtr1, szLineEnd1, "Tags", "STRING")) - LoadInfoVariable(&TagString, szLinePtr2, szLineEnd2, false); - - // Move both line pointers - szLinePtr1 = SkipInfoVariable(szLinePtr1, szLineEnd1); - if(szLinePtr1 == NULL) - break; - - szLinePtr2 = SkipInfoVariable(szLinePtr2, szLineEnd2); - if(szLinePtr2 == NULL) - break; - } + // Extract the CDN build key + nError = LoadQueryKey(Csv[i]["Build Key!HEX:16"], hs->CdnBuildKey); + if (nError != ERROR_SUCCESS) + return nError; + + // Extract the CDN config key + nError = LoadQueryKey(Csv[i]["CDN Key!HEX:16"], hs->CdnConfigKey); + if (nError != ERROR_SUCCESS) + return nError; + + // If we found tags, we can extract language build from it + GetDefaultLocaleMask(hs, Csv[i]["Tags!STRING:0"]); + + // If we found version, extract a build number + const CASC_CSV_COLUMN & VerColumn = Csv[i]["Version!STRING:0"]; + if(VerColumn.szValue && VerColumn.nLength) + { + LoadBuildNumber(hs, NULL, VerColumn.szValue, VerColumn.szValue + VerColumn.nLength, NULL); + } - // Stop parsing if found active config - if(Active.pbData != NULL && *Active.pbData == '1') - break; + // Verify all variables + return (hs->CdnBuildKey.pbData != NULL && hs->CdnConfigKey.pbData != NULL) ? ERROR_SUCCESS : ERROR_BAD_FORMAT; + } + } - // Free the blobs - FreeCascBlob(&Active); - FreeCascBlob(&hs->CdnBuildKey); - FreeCascBlob(&hs->CdnConfigKey); - FreeCascBlob(&CdnHost); - FreeCascBlob(&CdnPath); - FreeCascBlob(&TagString); + return ERROR_FILE_NOT_FOUND; +} - // Rewind column names pointer back to start of line - szLinePtr1 = szLineEnd1 - nLength1; - } +static int ParseFile_VersionsDb(TCascStorage * hs, CASC_CSV & Csv) +{ + size_t nLineCount = Csv.GetLineCount(); + int nError; - // All four must be present - if(hs->CdnBuildKey.pbData != NULL && - hs->CdnConfigKey.pbData != NULL && - CdnHost.pbData != NULL && - CdnPath.pbData != NULL) + // Find the active config + for (size_t i = 0; i < nLineCount; i++) { - // Merge the CDN host and CDN path - hs->szUrlPath = CASC_ALLOC(TCHAR, CdnHost.cbData + CdnPath.cbData + 1); - if(hs->szUrlPath != NULL) + // Either take the version required or take the first one + if (hs->szRegion == NULL || !strcmp(Csv[i]["Region!STRING:0"].szValue, hs->szRegion)) { - CopyString(hs->szUrlPath, (char *)CdnHost.pbData, CdnHost.cbData); - CopyString(hs->szUrlPath + CdnHost.cbData, (char *)CdnPath.pbData, CdnPath.cbData); - nError = ERROR_SUCCESS; + // Extract the CDN build key + nError = LoadQueryKey(Csv[i]["BuildConfig!HEX:16"], hs->CdnBuildKey); + if (nError != ERROR_SUCCESS) + return nError; + + // Extract the CDN config key + nError = LoadQueryKey(Csv[i]["CDNConfig!HEX:16"], hs->CdnConfigKey); + if (nError != ERROR_SUCCESS) + return nError; + + const CASC_CSV_COLUMN & VerColumn = Csv[i]["VersionsName!String:0"]; + if (VerColumn.szValue && VerColumn.nLength) + { + LoadBuildNumber(hs, NULL, VerColumn.szValue, VerColumn.szValue + VerColumn.nLength, NULL); + } + + // Verify all variables + return (hs->CdnBuildKey.pbData != NULL && hs->CdnConfigKey.pbData != NULL) ? ERROR_SUCCESS : ERROR_BAD_FORMAT; } } - // If we found tags, we can extract language build from it - if(TagString.pbData != NULL) - GetDefaultLocaleMask(hs, &TagString); - - FreeCascBlob(&CdnHost); - FreeCascBlob(&CdnPath); - FreeCascBlob(&TagString); - FreeCascBlob(&Active); - return nError; + return ERROR_FILE_NOT_FOUND; } -static int ParseFile_BuildDb(TCascStorage * hs, void * pvListFile) +static int ParseFile_BuildDb(TCascStorage * hs, CASC_CSV & Csv) { - const char * szLinePtr; - const char * szLineEnd; - char szOneLine[0x200]; - size_t nLength; int nError; - // Load the single line from the text file - nLength = ListFile_GetNextLine(pvListFile, szOneLine, _maxchars(szOneLine)); - if(nLength == 0) - return ERROR_BAD_FORMAT; - - // Set the line range - szLinePtr = szOneLine; - szLineEnd = szOneLine + nLength; - // Extract the CDN build key - nError = LoadInfoVariable(&hs->CdnBuildKey, szLinePtr, szLineEnd, true); - if(nError == ERROR_SUCCESS) - { - // Skip the variable - szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd); - - // Load the CDN config hash - nError = LoadInfoVariable(&hs->CdnConfigKey, szLinePtr, szLineEnd, true); - if(nError == ERROR_SUCCESS) - { - // Skip the variable - szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd); + nError = LoadQueryKey(Csv[CSV_ZERO][CSV_ZERO], hs->CdnBuildKey); + if(nError != ERROR_SUCCESS) + return nError; - // Skip the Locale/OS/code variable - szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd); + // Extract the CDN config key + nError = LoadQueryKey(Csv[CSV_ZERO][1], hs->CdnConfigKey); + if (nError != ERROR_SUCCESS) + return nError; - // Load the URL - hs->szUrlPath = CascNewStrFromAnsi(szLinePtr, szLineEnd); - if(hs->szUrlPath == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; - } - } + // Load the the tags + GetDefaultLocaleMask(hs, Csv[CSV_ZERO][2]); // Verify all variables - if(hs->CdnBuildKey.pbData == NULL || hs->CdnConfigKey.pbData == NULL || hs->szUrlPath == NULL) - nError = ERROR_BAD_FORMAT; - return nError; + return (hs->CdnBuildKey.pbData != NULL && hs->CdnConfigKey.pbData != NULL) ? ERROR_SUCCESS : ERROR_BAD_FORMAT; } -static int LoadCdnConfigFile(TCascStorage * hs, void * pvListFile) +static int ParseFile_CdnConfig(TCascStorage * hs, void * pvListFile) { const char * szLineBegin; - const char * szVarBegin; const char * szLineEnd; int nError = ERROR_SUCCESS; @@ -676,37 +704,28 @@ static int LoadCdnConfigFile(TCascStorage * hs, void * pvListFile) if(!ListFile_GetNextLine(pvListFile, &szLineBegin, &szLineEnd)) break; - // Archive group - szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "archive-group"); - if(szVarBegin != NULL) - { - nError = LoadSingleBlob(&hs->ArchivesGroup, szVarBegin, szLineEnd); + // CDN key of ARCHIVE-GROUP. Archive-group is actually a very special '.index' file. + // It is essentially a merger of all .index files, with a structure change + // When ".index" added after the ARCHIVE-GROUP, we get file name in "indices" folder + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "archive-group", LoadQueryKey, &hs->ArchiveGroup)) continue; - } - // Archives - szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "archives"); - if(szVarBegin != NULL) - { - nError = LoadMultipleBlobs(&hs->ArchivesKey, szVarBegin, szLineEnd); + // CDN key of all archives. When ".index" added to the string, we get file name in "indices" folder + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "archives", LoadQueryKey, &hs->ArchivesKey)) continue; - } - // Patch archive group - szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "patch-archive-group"); - if(szVarBegin != NULL) - { - LoadSingleBlob(&hs->PatchArchivesGroup, szVarBegin, szLineEnd); + // CDN keys of patch archives (needs research) + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "patch-archives", LoadQueryKey, &hs->PatchArchivesKey)) continue; - } - // Patch archives - szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "patch-archives"); - if(szVarBegin != NULL) - { - nError = LoadMultipleBlobs(&hs->PatchArchivesKey, szVarBegin, szLineEnd); + // CDN key of probably the combined patch index file (needs research) + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "patch-archive-group", LoadQueryKey, &hs->PatchArchivesGroup)) + continue; + + // List of build configs this config supports (optional) + // Points to file: "data\config\%02X\%02X\%s + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "builds", LoadQueryKey, &hs->BuildFiles)) continue; - } } // Check if all required fields are present @@ -716,78 +735,79 @@ static int LoadCdnConfigFile(TCascStorage * hs, void * pvListFile) return nError; } -static int LoadCdnBuildFile(TCascStorage * hs, void * pvListFile) +static int ParseFile_CdnBuild(TCascStorage * hs, void * pvListFile) { const char * szLineBegin; - const char * szVarBegin; const char * szLineEnd = NULL; - int nError = ERROR_SUCCESS; + int nError; - for(;;) + // Initialize the empty VFS array + nError = hs->VfsRootList.Create<CASC_CKEY_ENTRY>(0x10); + if (nError != ERROR_SUCCESS) + return nError; + + // Parse all variables + while(ListFile_GetNextLine(pvListFile, &szLineBegin, &szLineEnd) != 0) { - // Get the next line - if(!ListFile_GetNextLine(pvListFile, &szLineBegin, &szLineEnd)) - break; + // Product name and build name + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "build-uid", LoadBuildProductId, NULL)) + continue; + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "build-name", LoadBuildNumber, NULL)) + continue; - // Game name - szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "build-product"); - if(szVarBegin != NULL) - { - GetGameType(hs, szVarBegin, szLineEnd); + // Content key of the ROOT file. Look this up in ENCODING to get the encoded key + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "root*", LoadCKeyEntry, &hs->RootFile)) continue; - } - // Game build number - szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "build-name"); - if(szVarBegin != NULL) - { - GetBuildNumber(hs, szVarBegin, szLineEnd); + // Content key [+ encoded key] of the INSTALL file + // If EKey is absent, you need to query the ENCODING file for it + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "install*", LoadCKeyEntry, &hs->InstallCKey)) continue; - } - // Root - szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "root"); - if(szVarBegin != NULL) - { - LoadSingleBlob(&hs->RootKey, szVarBegin, szLineEnd); + // Content key [+ encoded key] of the DOWNLOAD file + // If EKey is absent, you need to query the ENCODING file for it + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "download*", LoadCKeyEntry, &hs->DownloadCKey)) continue; - } - // Patch - szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "patch"); - if(szVarBegin != NULL) - { - LoadSingleBlob(&hs->PatchKey, szVarBegin, szLineEnd); + // Content key + encoded key of the ENCODING file. Contains CKey+EKey + // If either none or 1 is found, the game (at least Wow) switches to plain-data(?). Seen in build 20173 + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "encoding*", LoadCKeyEntry, &hs->EncodingCKey)) continue; - } - // Download - szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "download"); - if(szVarBegin != NULL) - { - LoadSingleBlob(&hs->DownloadKey, szVarBegin, szLineEnd); + // PATCH file + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "patch*", LoadCKeyEntry, &hs->PatchFile)) continue; - } - // Install - szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "install"); - if(szVarBegin != NULL) - { - LoadSingleBlob(&hs->InstallKey, szVarBegin, szLineEnd); + // SIZE file + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "size*", LoadCKeyEntry, &hs->SizeFile)) continue; - } - // Encoding keys - szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "encoding"); - if(szVarBegin != NULL) - { - nError = LoadMultipleBlobs(&hs->EncodingKey, szVarBegin, szLineEnd, 2); + // VFS root file (the root file of the storage VFS) + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "vfs-root*", LoadCKeyEntry, &hs->VfsRoot)) + continue; + + // Load a directory entry to the VFS + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "vfs-*", LoadVfsRootEntry, &hs->VfsRootList)) continue; + } + + // 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->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; + } } } - // Check the encoding keys - if(hs->EncodingKey.pbData == NULL || hs->EncodingKey.cbData != MD5_HASH_SIZE * 2) + // 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; } @@ -807,6 +827,7 @@ static int CheckDataDirectory(TCascStorage * hs, TCHAR * szDirectory) // Does that directory exist? if(DirectoryExists(szDataPath)) { + hs->szRootPath = CascNewStr(szDirectory); hs->szDataPath = szDataPath; return ERROR_SUCCESS; } @@ -819,91 +840,328 @@ static int CheckDataDirectory(TCascStorage * hs, TCHAR * szDirectory) return nError; } +static int LoadCsvFile(TCascStorage * hs, const TCHAR * szFileName, PARSECSVFILE PfnParseProc, bool bHasHeader) +{ + CASC_CSV Csv(0x40, bHasHeader); + int nError = Csv.Load(szFileName); -//----------------------------------------------------------------------------- -// Public functions + // Load the external file to memory + if (nError == ERROR_SUCCESS) + nError = PfnParseProc(hs, Csv); + return nError; +} -int LoadBuildInfo(TCascStorage * hs) +static const TCHAR * ExtractCdnServerName(TCHAR * szServerName, size_t cchServerName, const TCHAR * szCdnServers) { - PARSEINFOFILE PfnParseProc = NULL; - void * pvListFile; - int nError = ERROR_SUCCESS; + const TCHAR * szSeparator; - switch(hs->BuildFileType) + if(szCdnServers[0] != 0) { - case CascBuildInfo: - PfnParseProc = ParseFile_BuildInfo; - break; + szSeparator = _tcschr(szCdnServers, _T(' ')); + if(szSeparator != NULL) + { + // Copy one server name + CascStrCopy(szServerName, cchServerName, szCdnServers, (szSeparator - szCdnServers)); - case CascBuildDb: - PfnParseProc = ParseFile_BuildDb; - break; + // Skip all separators + while(szSeparator[0] == ' ' || szSeparator[0] == 0x09) + szSeparator++; + return szSeparator; + } + else + { + CascStrCopy(szServerName, MAX_PATH, szCdnServers); + return szCdnServers + _tcslen(szCdnServers); + } + } - default: - nError = ERROR_NOT_SUPPORTED; - break; + return NULL; +} + +static int ForcePathExist(const TCHAR * szFileName, bool bIsFileName) +{ + TCHAR * szLocalPath; + size_t nIndex; + bool bFirstSeparator = false; + int nError = ERROR_NOT_ENOUGH_MEMORY; + + // Sanity checks + if(szFileName && szFileName[0]) + { + szLocalPath = CascNewStr(szFileName); + if(szLocalPath != NULL) + { + // Get the end of search + if(bIsFileName) + CutLastPathPart(szLocalPath); + + // Check the entire path + if(_taccess(szLocalPath, 0) != 0) + { + // Searth the entire path + for(nIndex = 0; szLocalPath[nIndex] != 0; nIndex++) + { + if(szLocalPath[nIndex] == '\\' || szLocalPath[nIndex] == '/') + { + // Cut the path and verify whether the folder/file exists + szLocalPath[nIndex] = 0; + + // Skip the very first separator + if(bFirstSeparator == true) + { + // Is it there? + if(DirectoryExists(szLocalPath) == false && MakeDirectory(szLocalPath) == false) + { + nError = ERROR_PATH_NOT_FOUND; + break; + } + } + + // Restore the character + szLocalPath[nIndex] = PATH_SEP_CHAR; + bFirstSeparator = true; + nError = ERROR_SUCCESS; + } + } + + // Now check the final path + if(DirectoryExists(szLocalPath) || MakeDirectory(szLocalPath)) + { + nError = ERROR_SUCCESS; + } + } + else + { + nError = ERROR_SUCCESS; + } + + CASC_FREE(szLocalPath); + } } - // Parse the appropriate build file - if(nError == ERROR_SUCCESS) + return nError; +} + +static int DownloadFile( + const TCHAR * szRemoteName, + const TCHAR * szLocalName, + PULONGLONG PtrByteOffset, + DWORD cbReadSize, + DWORD dwPortFlags) +{ + TFileStream * pRemStream; + TFileStream * pLocStream; + LPBYTE pbFileData; + int nError = ERROR_NOT_ENOUGH_MEMORY; + + // Open the remote stream + pRemStream = FileStream_OpenFile(szRemoteName, BASE_PROVIDER_HTTP | STREAM_PROVIDER_FLAT | dwPortFlags); + if (pRemStream != NULL) { - pvListFile = ListFile_OpenExternal(hs->szBuildFile); - if(pvListFile != NULL) + // Will we download the entire file or just a part of it? + if (PtrByteOffset == NULL) { - // Parse the info file - nError = PfnParseProc(hs, pvListFile); - ListFile_Free(pvListFile); + ULONGLONG FileSize = 0; + + // Retrieve the file size + if (FileStream_GetSize(pRemStream, &FileSize) && 0 < FileSize && FileSize < 0x10000000) + { + // Cut the file size down to 32 bits + cbReadSize = (DWORD)FileSize; + } } - else - nError = ERROR_FILE_NOT_FOUND; + + // Shall we read something? + if ((cbReadSize != 0) && (pbFileData = CASC_ALLOC(BYTE, cbReadSize)) != NULL) + { + // Read all required data from the remote file + if (FileStream_Read(pRemStream, PtrByteOffset, pbFileData, cbReadSize)) + { + pLocStream = FileStream_CreateFile(szLocalName, BASE_PROVIDER_FILE | STREAM_PROVIDER_FLAT); + if (pLocStream != NULL) + { + if (FileStream_Write(pLocStream, NULL, pbFileData, cbReadSize)) + nError = ERROR_SUCCESS; + + FileStream_Close(pLocStream); + } + } + + // Free the data buffer + CASC_FREE(pbFileData); + } + + // Close the remote stream + FileStream_Close(pRemStream); + } + else + { + nError = GetLastError(); } - // If the .build.info OR .build.db file has been loaded, - // proceed with loading the CDN config file and CDN build file + return nError; +} + +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) +{ + 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 + CombineUrlPath(szRemotePath, _countof(szRemotePath), szServerName, szServerPath1, szServerPath2); + CombineFilePath(szLocalPath, _countof(szLocalPath), hs->szRootPath, NULL, szLocalPath2); + + // Make sure that the path exists + ForcePathExist(szLocalPath, true); + + // 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 + { + nError = DownloadFile(szRemotePath, szLocalPath, PtrByteOffset, cbReadSize, dwPortFlags); + } + + // If succeeded, give the local path if(nError == ERROR_SUCCESS) { - // Load the configuration file. Note that we don't - // need it for anything, really, so we don't care if it fails - pvListFile = FetchAndVerifyConfigFile(hs, &hs->CdnConfigKey); - if(pvListFile != NULL) + // If the caller wanted local path, give it to him + if (szOutLocalPath && cchOutLocalPath) { - nError = LoadCdnConfigFile(hs, pvListFile); - ListFile_Free(pvListFile); + nLength = _tcslen(szLocalPath); + if ((nLength + 1) <= cchOutLocalPath) + { + CascStrCopy(szOutLocalPath, cchOutLocalPath, szLocalPath); + } } } - // Load the build file - if(nError == ERROR_SUCCESS) + return nError; +} + +static int FetchAndLoadConfigFile(TCascStorage * hs, PQUERY_KEY pFileKey, PARSETEXTFILE PfnParseProc) +{ + const TCHAR * szPathType = _T("config"); + TCHAR szLocalPath[MAX_PATH]; + TCHAR szSubDir[0x80] = _T(""); + void * pvListFile = NULL; + int nError = 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; + } + else { - pvListFile = FetchAndVerifyConfigFile(hs, &hs->CdnBuildKey); - if(pvListFile != NULL) + CreateCascSubdirectoryName(szSubDir, _countof(szSubDir), szPathType, NULL, pFileKey->pbData); + CombineFilePath(szLocalPath, _countof(szLocalPath), hs->szDataPath, szSubDir); + } + + // Load and verify the external listfile + pvListFile = ListFile_OpenExternal(szLocalPath); + if (pvListFile != NULL) + { + if (ListFile_VerifyMD5(pvListFile, pFileKey->pbData)) { - nError = LoadCdnBuildFile(hs, pvListFile); - ListFile_Free(pvListFile); + nError = PfnParseProc(hs, pvListFile); } else - nError = ERROR_FILE_NOT_FOUND; + { + nError = ERROR_FILE_CORRUPT; + } + CASC_FREE(pvListFile); + } + else + { + nError = ERROR_FILE_NOT_FOUND; } - // Fill the index directory - if(nError == ERROR_SUCCESS) + return nError; +} + +//----------------------------------------------------------------------------- +// Public functions + +int DownloadFileFromCDN(TCascStorage * hs, const TCHAR * szSubDir, LPBYTE pbEKey, const TCHAR * szExtension, TCHAR * szOutLocalPath, size_t cchOutLocalPath) +{ + PCASC_ARCINDEX_ENTRY pIndexEntry; + const TCHAR * szCdnServers = hs->szCdnServers; + TCHAR szRemoteFolder[MAX_PATH]; + TCHAR szLocalFolder[MAX_PATH]; + TCHAR szServerName[MAX_PATH]; + int nError = ERROR_CAN_NOT_COMPLETE; + + // Try all download servers + while((szCdnServers = ExtractCdnServerName(szServerName, _countof(szServerName), szCdnServers)) != NULL) { - // First, check for more common "data" subdirectory - if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("data"))) != NULL) - return ERROR_SUCCESS; + // Create the local subdirectory + CreateCascSubdirectoryName(szLocalFolder, _countof(szLocalFolder), szSubDir, szExtension, pbEKey); - // Second, try the "darch" subdirectory (older builds of HOTS - Alpha) - if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("darch"))) != NULL) - return ERROR_SUCCESS; + // 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; + nError = DownloadFile(hs, + szServerName, + hs->szCdnPath, + szRemoteFolder, + szLocalFolder, + &ByteOffset, + pIndexEntry->EncodedSize, + szOutLocalPath, + cchOutLocalPath, 0, false, false); + } + else + { + nError = DownloadFile(hs, + szServerName, + hs->szCdnPath, + szLocalFolder, + szLocalFolder, + NULL, + 0, + szOutLocalPath, + cchOutLocalPath, 0, false, false); + } - nError = ERROR_FILE_NOT_FOUND; + if (nError == ERROR_SUCCESS) + break; } return nError; } // Checks whether there is a ".build.info" or ".build.db". -// If yes, the function sets "szRootPath" and "szDataPath" +// If yes, the function sets "szDataPath" and "szIndexPath" // in the storage structure and returns ERROR_SUCCESS int CheckGameDirectory(TCascStorage * hs, TCHAR * szDirectory) { @@ -912,22 +1170,22 @@ int CheckGameDirectory(TCascStorage * hs, TCHAR * szDirectory) int nError = ERROR_FILE_NOT_FOUND; // Try to find any of the root files used in the history - for(size_t i = 0; BuildTypes[i].szFileName != NULL; i++) + for (size_t i = 0; BuildTypes[i].szFileName != NULL; i++) { // Create the full name of the .agent.db file szBuildFile = CombinePath(szDirectory, BuildTypes[i].szFileName); - if(szBuildFile != NULL) + if (szBuildFile != NULL) { // Attempt to open the file pStream = FileStream_OpenFile(szBuildFile, STREAM_FLAG_READ_ONLY); - if(pStream != NULL) + if (pStream != NULL) { // Free the stream FileStream_Close(pStream); // Check for the data directory nError = CheckDataDirectory(hs, szDirectory); - if(nError == ERROR_SUCCESS) + if (nError == ERROR_SUCCESS) { hs->szBuildFile = szBuildFile; hs->BuildFileType = BuildTypes[i].BuildFileType; @@ -942,76 +1200,95 @@ int CheckGameDirectory(TCascStorage * hs, TCHAR * szDirectory) return nError; } -//----------------------------------------------------------------------------- -// Helpers for a config files that have multiple variables separated by "|" -// The line structure is (Overwatch 24919): "#MD5|CHUNK_ID|FILENAME|INSTALLPATH" -// The line structure is (Overwatch 27759): "#MD5|CHUNK_ID|PRIORITY|MPRIORITY|FILENAME|INSTALLPATH" -// The line has all preceding spaces removed - -// Retrieves the index of a variable from the initial line -int GetRootVariableIndex(const char * szLinePtr, const char * szLineEnd, const char * szVariableName, int * PtrIndex) +int LoadBuildInfo(TCascStorage * hs) { - size_t nLength = strlen(szVariableName); - int nIndex = 0; - - while(szLinePtr < szLineEnd) + PARSECSVFILE PfnParseProc = NULL; + bool bHasHeader = true; + int nError; + + // If the storage is online storage, we need to download "versions" + if(hs->dwFeatures & CASC_FEATURE_ONLINE) { - // Check the variable there - if(!_strnicmp(szLinePtr, szVariableName, nLength)) + // Inform the user about loading the build.info/build.db/versions + if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "Downloading the \"versions\" file", NULL, 0, 0)) + return ERROR_CANCELLED; + + // Attempt to download the "versions" file + nError = DownloadFile(hs, _T("us.patch.battle.net"), hs->szCodeName, _T("versions"), NULL, NULL, 0, NULL, 0, STREAM_FLAG_USE_PORT_1119, true, false); + if(nError != ERROR_SUCCESS) { - // Does the length match? - if(szLinePtr[nLength] == '|' || szLinePtr[nLength] == '0') - { - PtrIndex[0] = nIndex; - return ERROR_SUCCESS; - } + return nError; } + } + + // We support either ".build.info" or ".build.db" + switch (hs->BuildFileType) + { + case CascBuildInfo: + PfnParseProc = ParseFile_BuildInfo; + break; + + case CascVersionsDb: + PfnParseProc = ParseFile_VersionsDb; + break; - // Get the next variable - szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd); - if(szLinePtr == NULL) + case CascBuildDb: + PfnParseProc = ParseFile_BuildDb; + bHasHeader = false; break; - nIndex++; + + default: + return ERROR_NOT_SUPPORTED; } - return ERROR_BAD_FORMAT; + return LoadCsvFile(hs, hs->szBuildFile, PfnParseProc, bHasHeader); } -// Parses single line from Overwatch. -int ParseRootFileLine(const char * szLinePtr, const char * szLineEnd, int nFileNameIndex, PQUERY_KEY PtrEncodingKey, char * szFileName, size_t nMaxChars) +int LoadCdnsInfo(TCascStorage * hs) { - int nIndex = 0; - int nError; - - // Extract the MD5 (aka encoding key) - if(szLinePtr[MD5_STRING_SIZE] != '|') - return ERROR_BAD_FORMAT; + TCHAR szLocalPath[MAX_PATH]; + int nError = ERROR_SUCCESS; - // Convert the encoding key to binary - PtrEncodingKey->cbData = MD5_HASH_SIZE; - nError = ConvertStringToBinary(szLinePtr, MD5_STRING_SIZE, PtrEncodingKey->pbData); - if(nError != ERROR_SUCCESS) - return nError; + // Sanity checks + assert(hs->dwFeatures & CASC_FEATURE_ONLINE); - // Skip the variable - szLinePtr += MD5_STRING_SIZE + 1; - nIndex = 1; + // Inform the user about what we are doing + if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "Downloading the \"cdns\" file", NULL, 0, 0)) + return ERROR_CANCELLED; - // Skip the variables until we find the file name - while(szLinePtr < szLineEnd && nIndex < nFileNameIndex) + // Download file and parse it + nError = 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(nError == ERROR_SUCCESS) { - if(szLinePtr[0] == '|') - nIndex++; - szLinePtr++; + nError = LoadCsvFile(hs, szLocalPath, ParseFile_CDNS, true); } - // Extract the file name - while(szLinePtr < szLineEnd && szLinePtr[0] != '|' && nMaxChars > 1) - { - *szFileName++ = *szLinePtr++; - nMaxChars--; - } + return nError; +} - *szFileName = 0; - return ERROR_SUCCESS; +int LoadCdnConfigFile(TCascStorage * hs) +{ + // The CKey for the CDN config should have been loaded already + assert(hs->CdnConfigKey.pbData != NULL && hs->CdnConfigKey.cbData == MD5_HASH_SIZE); + + // Inform the user about what we are doing + if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "Downloading CDN config file", NULL, 0, 0)) + return ERROR_CANCELLED; + + // Load the CDN config file + return FetchAndLoadConfigFile(hs, &hs->CdnConfigKey, ParseFile_CdnConfig); +} + +int LoadCdnBuildFile(TCascStorage * hs) +{ + // The CKey for the CDN config should have been loaded already + assert(hs->CdnBuildKey.pbData != NULL && hs->CdnBuildKey.cbData == MD5_HASH_SIZE); + + // Inform the user about what we are doing + if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "Downloading 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); } diff --git a/dep/CascLib/src/CascFindFile.cpp b/dep/CascLib/src/CascFindFile.cpp index a5ec5766654..129403188c5 100644 --- a/dep/CascLib/src/CascFindFile.cpp +++ b/dep/CascLib/src/CascFindFile.cpp @@ -15,192 +15,124 @@ //----------------------------------------------------------------------------- // Local functions -static TCascSearch * IsValidSearchHandle(HANDLE hFind) +// Reset the search structure. Called before each search +static void ResetFindData(PCASC_FIND_DATA pFindData) { - TCascSearch * pSearch = (TCascSearch *)hFind; - - return (pSearch != NULL && pSearch->szClassName != NULL && !strcmp(pSearch->szClassName, "TCascSearch") && pSearch->szMask != NULL) ? pSearch : NULL; + // Reset the variables + ZeroMemory16(pFindData->CKey); + ZeroMemory16(pFindData->EKey); + pFindData->szFileName[0] = 0; + pFindData->szPlainName = pFindData->szFileName; + pFindData->TagBitMask = 0; + pFindData->dwFileDataId = CASC_INVALID_ID; + pFindData->dwFileSize = CASC_INVALID_SIZE; + pFindData->dwLocaleFlags = CASC_INVALID_ID; + pFindData->dwContentFlags = CASC_INVALID_ID; + pFindData->NameType = CascNameFull; + pFindData->bFileAvailable = false; + pFindData->bCanOpenByName = false; + pFindData->bCanOpenByDataId = false; + pFindData->bCanOpenByCKey = false; + pFindData->bCanOpenByEKey = false; } -static void FreeSearchHandle(TCascSearch * pSearch) +static void SupplyFakeFileName(PCASC_FIND_DATA pFindData) { - // Only if the storage handle is valid - assert(pSearch != NULL); - - // Close (dereference) the archive handle - if(pSearch->hs != NULL) + // If the file can be open by file data id, create fake file name + if(pFindData->bCanOpenByDataId) { - // Give root handler chance to free their stuff - RootHandler_EndSearch(pSearch->hs->pRootHandler, pSearch); + CascStrPrintf(pFindData->szFileName, _countof(pFindData->szFileName), "FILE%08X.dat", pFindData->dwFileDataId); + pFindData->NameType = CascNameDataId; + return; + } - // Dereference the storage handle - CascCloseStorage((HANDLE)pSearch->hs); - pSearch->hs = NULL; + // If the file can be open by CKey, convert the CKey to file name + if(pFindData->bCanOpenByCKey) + { + StringFromBinary(pFindData->CKey, MD5_HASH_SIZE, pFindData->szFileName); + pFindData->NameType = CascNameCKey; + return; } - // Free the file cache and frame array - if(pSearch->szMask != NULL) - CASC_FREE(pSearch->szMask); - if(pSearch->szListFile != NULL) - CASC_FREE(pSearch->szListFile); -// if(pSearch->pStruct1C != NULL) -// delete pSearch->pStruct1C; - if(pSearch->pCache != NULL) - ListFile_Free(pSearch->pCache); - - // Free the structure itself - pSearch->szClassName = NULL; - CASC_FREE(pSearch); + // CKey should be always present + StringFromBinary(pFindData->EKey, MD5_HASH_SIZE, pFindData->szFileName); + pFindData->NameType = CascNameEKey; + assert(pFindData->bCanOpenByEKey != false); } -static TCascSearch * AllocateSearchHandle(TCascStorage * hs, const TCHAR * szListFile, const char * szMask) +static bool CopyCKeyEntryToFindData(PCASC_FIND_DATA pFindData, PCASC_CKEY_ENTRY pCKeyEntry) { - TCascSearch * pSearch; - size_t cbToAllocate; - - // When using the MNDX info, do not allocate the extra bit array - cbToAllocate = sizeof(TCascSearch) + ((hs->pEncodingMap->TableSize + 7) / 8); - pSearch = (TCascSearch *)CASC_ALLOC(BYTE, cbToAllocate); - if(pSearch != NULL) - { - // Initialize the structure - memset(pSearch, 0, cbToAllocate); - pSearch->szClassName = "TCascSearch"; - - // Save the search handle - pSearch->hs = hs; - hs->dwRefCount++; - - // If the mask was not given, use default - if(szMask == NULL) - szMask = "*"; - - // Save the other variables - if(szListFile != NULL) - { - pSearch->szListFile = CascNewStr(szListFile, 0); - if(pSearch->szListFile == NULL) - { - FreeSearchHandle(pSearch); - return NULL; - } - } - - // Allocate the search mask - pSearch->szMask = CascNewStr(szMask, 0); - if(pSearch->szMask == NULL) - { - FreeSearchHandle(pSearch); - return NULL; - } - } - - return pSearch; + // 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; + + // Supply the plain name. Only do that if the found name is not a CKey/EKey + if(pFindData->szFileName[0] != 0) + pFindData->szPlainName = (char *)GetPlainFileName(pFindData->szFileName); + + // 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; + + // 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); + return true; } // Perform searching using root-specific provider. // The provider may need the listfile static bool DoStorageSearch_RootFile(TCascSearch * pSearch, PCASC_FIND_DATA pFindData) { - PCASC_ENCODING_ENTRY pEncodingEntry; - PCASC_INDEX_ENTRY pIndexEntry; - QUERY_KEY EncodingKey; - QUERY_KEY IndexKey; - LPBYTE pbEncodingKey; - DWORD EncodingIndex = 0; - DWORD ByteIndex; - DWORD BitMask; + PCASC_CKEY_ENTRY pCKeyEntry; + TCascStorage * hs = pSearch->hs; + + // Reset the search structure + ResetFindData(pFindData); + // Enumerate over all files for(;;) { - DWORD LocaleFlags = 0; - DWORD FileDataId = CASC_INVALID_ID; - DWORD FileSize = CASC_INVALID_SIZE; - - // Attempt to find (the next) file from the root entry - pbEncodingKey = RootHandler_Search(pSearch->hs->pRootHandler, pSearch, &FileSize, &LocaleFlags, &FileDataId); - if(pbEncodingKey == NULL) + // Attempt to find (the next) file from the root handler + pCKeyEntry = hs->pRootHandler->Search(pSearch, pFindData); + if(pCKeyEntry == NULL) return false; - // Verify whether the encoding key exists in the encoding table - EncodingKey.pbData = pbEncodingKey; - EncodingKey.cbData = MD5_HASH_SIZE; - pEncodingEntry = FindEncodingEntry(pSearch->hs, &EncodingKey, &EncodingIndex); - if(pEncodingEntry != NULL) - { - // Mark the item as already found - // Note: Duplicate items are allowed while we are searching using file names - // Do not exclude items from search if they were found before - ByteIndex = (DWORD)(EncodingIndex / 8); - BitMask = 1 << (EncodingIndex & 0x07); - pSearch->BitArray[ByteIndex] |= BitMask; - - // Locate the index entry - IndexKey.pbData = GET_INDEX_KEY(pEncodingEntry); - IndexKey.cbData = MD5_HASH_SIZE; - pIndexEntry = FindIndexEntry(pSearch->hs, &IndexKey); - if(pIndexEntry == NULL) - continue; - - // If we retrieved the file size directly from the root provider, use it - // Otherwise, we need to retrieve it from the encoding entry - if(FileSize == CASC_INVALID_SIZE) - FileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE); - - // Fill-in the found file - strcpy(pFindData->szFileName, pSearch->szFileName); - memcpy(pFindData->EncodingKey, pEncodingEntry->EncodingKey, MD5_HASH_SIZE); - pFindData->szPlainName = (char *)GetPlainFileName(pFindData->szFileName); - pFindData->dwLocaleFlags = LocaleFlags; - pFindData->dwFileDataId = FileDataId; - pFindData->dwFileSize = FileSize; - return true; - } + // The entry is expected to be referenced by the root directory + assert(pCKeyEntry->RefCount != 0); + + // Copy the CKey entry to the find data and return it + return CopyCKeyEntryToFindData(pFindData, pCKeyEntry); } } -static bool DoStorageSearch_EncodingKey(TCascSearch * pSearch, PCASC_FIND_DATA pFindData) +static bool DoStorageSearch_CKey(TCascSearch * pSearch, PCASC_FIND_DATA pFindData) { - PCASC_ENCODING_ENTRY pEncodingEntry; - PCASC_INDEX_ENTRY pIndexEntry; + PCASC_CKEY_ENTRY pCKeyEntry; TCascStorage * hs = pSearch->hs; - QUERY_KEY IndexKey; - DWORD ByteIndex; - DWORD BitMask; + size_t nTotalItems = hs->CKeyArray.ItemCount(); - // Check for encoding keys that haven't been found yet - while(pSearch->IndexLevel1 < hs->pEncodingMap->TableSize) + // Reset the find data structure + ResetFindData(pFindData); + + // Check for CKeys that haven't been found yet + while(pSearch->nFileIndex < nTotalItems) { - // Check if that entry has been reported before - ByteIndex = (DWORD)(pSearch->IndexLevel1 / 8); - BitMask = 1 << (pSearch->IndexLevel1 & 0x07); - if((pSearch->BitArray[ByteIndex] & BitMask) == 0) + // Locate the n-th CKey entry. If this entry is not referenced by the root handler, we include it in the search result + pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.ItemAt(pSearch->nFileIndex++); + if((pCKeyEntry->Flags & CASC_CE_FOLDER_ENTRY) == 0 && pCKeyEntry->RefCount == 0) { - // Locate the index entry - pEncodingEntry = (PCASC_ENCODING_ENTRY)hs->pEncodingMap->HashTable[pSearch->IndexLevel1]; - if(pEncodingEntry != NULL) - { - IndexKey.pbData = GET_INDEX_KEY(pEncodingEntry); - IndexKey.cbData = MD5_HASH_SIZE; - pIndexEntry = FindIndexEntry(pSearch->hs, &IndexKey); - if(pIndexEntry != NULL) - { - // Fill-in the found file - memcpy(pFindData->EncodingKey, pEncodingEntry->EncodingKey, MD5_HASH_SIZE); - pFindData->szFileName[0] = 0; - pFindData->szPlainName = pFindData->szFileName; - pFindData->dwLocaleFlags = CASC_LOCALE_NONE; - pFindData->dwFileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE); - - // Mark the entry as already-found - pSearch->BitArray[ByteIndex] |= BitMask; - return true; - } - } + return CopyCKeyEntryToFindData(pFindData, pCKeyEntry); } - - // Go to the next encoding entry - pSearch->IndexLevel1++; } // Nameless search ended @@ -210,36 +142,37 @@ static bool DoStorageSearch_EncodingKey(TCascSearch * pSearch, PCASC_FIND_DATA p static bool DoStorageSearch(TCascSearch * pSearch, PCASC_FIND_DATA pFindData) { // State 0: No search done yet - if(pSearch->dwState == 0) + if(pSearch->nSearchState == 0) { // Does the search specify listfile? if(pSearch->szListFile != NULL) pSearch->pCache = ListFile_OpenExternal(pSearch->szListFile); // Move the search phase to the listfile searching - pSearch->IndexLevel1 = 0; - pSearch->dwState++; + pSearch->nSearchState = 1; + pSearch->nFileIndex = 0; } // State 1: Searching the list file - if(pSearch->dwState == 1) + if(pSearch->nSearchState == 1) { if(DoStorageSearch_RootFile(pSearch, pFindData)) return true; // Move to the nameless search state - pSearch->IndexLevel1 = 0; - pSearch->dwState++; + pSearch->nSearchState = 2; + pSearch->nFileIndex = 0; } - // State 2: Searching the remaining entries - if(pSearch->dwState == 2 && (pSearch->szMask == NULL || !strcmp(pSearch->szMask, "*"))) + // State 2: Searching the remaining entries by CKey + if(pSearch->nSearchState == 2 && (pSearch->szMask == NULL || !strcmp(pSearch->szMask, "*"))) { - if(DoStorageSearch_EncodingKey(pSearch, pFindData)) + if(DoStorageSearch_CKey(pSearch, pFindData)) return true; // Move to the final search state - pSearch->dwState++; + pSearch->nSearchState++; + pSearch->nFileIndex = 0; } return false; @@ -250,16 +183,16 @@ static bool DoStorageSearch(TCascSearch * pSearch, PCASC_FIND_DATA pFindData) HANDLE WINAPI CascFindFirstFile( HANDLE hStorage, - const char * szMask, + LPCSTR szMask, PCASC_FIND_DATA pFindData, - const TCHAR * szListFile) + LPCTSTR szListFile) { TCascStorage * hs; TCascSearch * pSearch = NULL; int nError = ERROR_SUCCESS; // Check parameters - if((hs = IsValidStorageHandle(hStorage)) == NULL) + if((hs = TCascStorage::IsValid(hStorage)) == NULL) nError = ERROR_INVALID_HANDLE; if(szMask == NULL || pFindData == NULL) nError = ERROR_INVALID_PARAMETER; @@ -267,11 +200,8 @@ HANDLE WINAPI CascFindFirstFile( // Init the search structure and search handle if(nError == ERROR_SUCCESS) { - // Clear the entire search structure - memset(pFindData, 0, sizeof(CASC_FIND_DATA)); - // Allocate the search handle - pSearch = AllocateSearchHandle(hs, szListFile, szMask); + pSearch = new TCascSearch(hs, szListFile, szMask); if(pSearch == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } @@ -285,8 +215,7 @@ HANDLE WINAPI CascFindFirstFile( if(nError != ERROR_SUCCESS) { - if(pSearch != NULL) - FreeSearchHandle(pSearch); + delete pSearch; pSearch = (TCascSearch *)INVALID_HANDLE_VALUE; } @@ -299,7 +228,7 @@ bool WINAPI CascFindNextFile( { TCascSearch * pSearch; - pSearch = IsValidSearchHandle(hFind); + pSearch = TCascSearch::IsValid(hFind); if(pSearch == NULL || pFindData == NULL) { SetLastError(ERROR_INVALID_PARAMETER); @@ -314,13 +243,13 @@ bool WINAPI CascFindClose(HANDLE hFind) { TCascSearch * pSearch; - pSearch = IsValidSearchHandle(hFind); + pSearch = TCascSearch::IsValid(hFind); if(pSearch == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return false; } - FreeSearchHandle(pSearch); + delete pSearch; return true; } diff --git a/dep/CascLib/src/CascIndexFiles.cpp b/dep/CascLib/src/CascIndexFiles.cpp new file mode 100644 index 00000000000..c54c874ec8c --- /dev/null +++ b/dep/CascLib/src/CascIndexFiles.cpp @@ -0,0 +1,774 @@ +/*****************************************************************************/ +/* CascIndexFiles.cpp Copyright (c) Ladislav Zezula 2019 */ +/*---------------------------------------------------------------------------*/ +/* Index file support */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 23.05.19 1.00 Lad Created */ +/*****************************************************************************/ + +#define __CASCLIB_SELF__ +#include "CascLib.h" +#include "CascCommon.h" + +//----------------------------------------------------------------------------- +// Local variables + +static const TCHAR * szAllowedHexChars = _T("0123456789aAbBcCdDeEfF"); +static const TCHAR * szIndexFormat_V1 = _T("data.i%x%x"); +static const TCHAR * szIndexFormat_V2 = _T("%02x%08x.idx"); + +// Limit for "orphaned" items - those that are in index files, but are not in ENCODING manifest +#define CASC_MAX_ORPHANED_ITEMS 0x100 + +//----------------------------------------------------------------------------- +// Local functions + +// "data.iXY" +static bool IsIndexFileName_V1(const TCHAR * szFileName) +{ + // Check if the name looks like a valid index file + return (_tcslen(szFileName) == 8 && + _tcsnicmp(szFileName, _T("data.i"), 6) == 0 && + _tcsspn(szFileName + 6, szAllowedHexChars) == 2); +} + +static bool IsIndexFileName_V2(const TCHAR * szFileName) +{ + // Check if the name looks like a valid index file + return (_tcslen(szFileName) == 14 && + _tcsspn(szFileName, _T("0123456789aAbBcCdDeEfF")) == 0x0A && + _tcsicmp(szFileName + 0x0A, _T(".idx")) == 0); +} + +static bool IndexDirectory_OnFileFound( + const TCHAR * szFileName, + PDWORD IndexArray, + PDWORD OldIndexArray, + void * pvContext) +{ + TCascStorage * hs = (TCascStorage *)pvContext; + DWORD IndexValue = 0; + DWORD IndexVersion = 0; + + // Auto-detect the format of the index file name + if(hs->szIndexFormat == NULL) + { + if(IsIndexFileName_V2(szFileName)) + hs->szIndexFormat = szIndexFormat_V2; + else if(IsIndexFileName_V1(szFileName)) + hs->szIndexFormat = szIndexFormat_V1; + else + return false; + } + + if(hs->szIndexFormat == szIndexFormat_V2) + { + // Check the index file name format + if(!IsIndexFileName_V2(szFileName)) + return false; + + // Get the main index from the first two digits + if(ConvertStringToInt32(szFileName, 2, &IndexValue) != ERROR_SUCCESS) + return false; + if(ConvertStringToInt32(szFileName + 2, 8, &IndexVersion) != ERROR_SUCCESS) + return false; + } + else if(hs->szIndexFormat == szIndexFormat_V1) + { + // Check the index file name format + if(!IsIndexFileName_V1(szFileName)) + return false; + + // Get the main index from the first two digits + if(ConvertDigitToInt32(szFileName + 6, &IndexValue) != ERROR_SUCCESS) + return false; + if(ConvertDigitToInt32(szFileName + 7, &IndexVersion) != ERROR_SUCCESS) + return false; + } + else + { + // Should never happen + assert(false); + return false; + } + + // The index value must not be greater than 0x0F + if(IndexValue >= CASC_INDEX_COUNT) + return false; + + // If the new subindex is greater than the previous one, + // use this one instead + if(IndexVersion > IndexArray[IndexValue]) + { + OldIndexArray[IndexValue] = IndexArray[IndexValue]; + IndexArray[IndexValue] = IndexVersion; + } + else if(IndexVersion > OldIndexArray[IndexValue]) + { + OldIndexArray[IndexValue] = IndexVersion; + } + + // Note: WoW6 only keeps last two index files + // Any additional index files are deleted at this point + return true; +} + +static TCHAR * CreateIndexFileName(TCascStorage * hs, DWORD IndexValue, DWORD IndexVersion) +{ + TCHAR szPlainName[0x40]; + + // Sanity checks + assert(hs->szIndexFormat != NULL); + assert(hs->szIndexPath != NULL); + assert(IndexValue <= 0x0F); + + // Create the full path + CascStrPrintf(szPlainName, _countof(szPlainName), hs->szIndexFormat, IndexValue, IndexVersion); + return CombinePath(hs->szIndexPath, szPlainName); +} + +// Verifies a guarded block - data availability and checksum match +static LPBYTE CaptureGuardedBlock1(LPBYTE pbFileData, LPBYTE pbFileEnd) +{ + PFILE_INDEX_GUARDED_BLOCK pBlock = (PFILE_INDEX_GUARDED_BLOCK)pbFileData; + + // Check the guarded block + if((pbFileData + sizeof(FILE_INDEX_GUARDED_BLOCK)) >= pbFileEnd) + return NULL; + if((pbFileData + sizeof(FILE_INDEX_GUARDED_BLOCK) + pBlock->BlockSize) > pbFileEnd) + return NULL; + + // Verify the hash + if(hashlittle(pbFileData + sizeof(FILE_INDEX_GUARDED_BLOCK), pBlock->BlockSize, 0) != pBlock->BlockHash) + return NULL; + + // Give the output + return (LPBYTE)(pBlock + 1); +} + +// Second method of checking a guarded block; hash is calculated entry-by-entry. +// Unfortunately, due to implementation in hashlittle2(), we cannot calculate the hash of a continuous block +static LPBYTE CaptureGuardedBlock2(LPBYTE pbFileData, LPBYTE pbFileEnd, size_t EntryLength, PDWORD PtrBlockSize = NULL) +{ + PFILE_INDEX_GUARDED_BLOCK pBlock = (PFILE_INDEX_GUARDED_BLOCK)pbFileData; + LPBYTE pbEntryPtr; + size_t EntryCount; + unsigned int HashHigh = 0; + unsigned int HashLow = 0; + + // Check the guarded block. There must be enough bytes to contain FILE_INDEX_GUARDED_BLOCK + // and also the block length must not be NULL + if ((pbFileData + sizeof(FILE_INDEX_GUARDED_BLOCK)) >= pbFileEnd) + return NULL; + if (pBlock->BlockSize == 0 || (pbFileData + sizeof(FILE_INDEX_GUARDED_BLOCK) + pBlock->BlockSize) > pbFileEnd) + return NULL; + + // Compute the hash entry-by-entry + pbEntryPtr = pbFileData + sizeof(FILE_INDEX_GUARDED_BLOCK); + EntryCount = pBlock->BlockSize / EntryLength; + for (size_t i = 0; i < EntryCount; i++) + { + hashlittle2(pbEntryPtr, EntryLength, &HashHigh, &HashLow); + pbEntryPtr += EntryLength; + } + + // Verify hash + if (HashHigh != pBlock->BlockHash) + return NULL; + + // Give the output + if (PtrBlockSize != NULL) + PtrBlockSize[0] = pBlock->BlockSize; + return (LPBYTE)(pBlock + 1); +} + +// Third method of checking a guarded block; There is 32-bit hash, followed by EKey entry +// The hash covers the EKey entry plus one byte +static LPBYTE CaptureGuardedBlock3(LPBYTE pbFileData, LPBYTE pbFileEnd, size_t EntryLength) +{ + PDWORD PtrEntryHash = (PDWORD)pbFileData; + DWORD EntryHash; + + // Check the guarded block. There must be enough bytes to contain single entry and the hash + // Also, the hash must be present + if ((pbFileData + sizeof(DWORD) + EntryLength) >= pbFileEnd) + return NULL; + if (PtrEntryHash[0] == 0) + return NULL; + + EntryHash = hashlittle(pbFileData + sizeof(DWORD), EntryLength+1, 0) | 0x80000000; + if(EntryHash != PtrEntryHash[0]) + return NULL; + + // Give the output + return (LPBYTE)(PtrEntryHash + 1); +} + +static bool CaptureIndexEntry(CASC_INDEX_HEADER & InHeader, PCASC_CKEY_ENTRY pCKeyEntry, 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; + + // We ignore items that have EncodedSize of 0x1E + return (pCKeyEntry->EncodedSize > FIELD_OFFSET(BLTE_ENCODED_HEADER, Signature)); +} + +static void CheckForEncodingManifestCKey(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry) +{ + // If the encoding file was not found yet + if(hs->EncodingCKey.StorageOffset == CASC_INVALID_OFFS64) + { + if(!memcmp(pCKeyEntry->EKey, hs->EncodingCKey.EKey, hs->EKeyLength)) + { + hs->EncodingCKey.StorageOffset = pCKeyEntry->StorageOffset; + hs->EncodingCKey.EncodedSize = pCKeyEntry->EncodedSize; + hs->EncodingCKey.Flags |= CASC_CE_FILE_IS_LOCAL; + } + } +} + +static int LoadIndexItems(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, LPBYTE pbEKeyEntry, LPBYTE pbEKeyEnd) +{ + size_t EntryLength = InHeader.EntryLength; + + while((pbEKeyEntry + EntryLength) <= pbEKeyEnd) + { + CASC_CKEY_ENTRY CKeyEntry; + + // Capture the index entry and verify it. + if(CaptureIndexEntry(InHeader, &CKeyEntry, pbEKeyEntry)) + { + // Insert new entry to the array of CKey entries + if(hs->IndexArray.Insert(&CKeyEntry, 1) == NULL) + return ERROR_NOT_ENOUGH_MEMORY; + + // Verify whether the key is not a CKEy entry for ENCODING file + CheckForEncodingManifestCKey(hs, &CKeyEntry); + } + + pbEKeyEntry += EntryLength; + } + + return ERROR_SUCCESS; +} + +static int CaptureIndexHeader_V1(CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData, DWORD cbFileData, DWORD BucketIndex) +{ + PFILE_INDEX_HEADER_V1 pIndexHeader = (PFILE_INDEX_HEADER_V1)pbFileData; + LPBYTE pbKeyEntries; + LPBYTE pbFileEnd = pbFileData + cbFileData; + size_t cbKeyEntries; + DWORD HeaderHash; + + // Check the available size. Note that the index file can be just a header. + if((pbFileData + sizeof(FILE_INDEX_HEADER_V1)) > pbFileEnd) + return ERROR_BAD_FORMAT; + if(pIndexHeader->IndexVersion != 0x05 || pIndexHeader->BucketIndex != (BYTE)BucketIndex || pIndexHeader->field_8 == 0) + return ERROR_BAD_FORMAT; + if(pIndexHeader->EncodedSizeLength != 0x04 || pIndexHeader->StorageOffsetLength != 0x05 || pIndexHeader->EKeyLength != 0x09) + return ERROR_NOT_SUPPORTED; + + // Verify the header hash + HeaderHash = pIndexHeader->HeaderHash; + pIndexHeader->HeaderHash = 0; + if(hashlittle(pbFileData, sizeof(FILE_INDEX_HEADER_V1), 0) != HeaderHash) + return ERROR_BAD_FORMAT; + + // Return the header hash back + pIndexHeader->HeaderHash = HeaderHash; + + // Copy the fields + InHeader.IndexVersion = pIndexHeader->IndexVersion; + InHeader.BucketIndex = pIndexHeader->BucketIndex; + InHeader.StorageOffsetLength = pIndexHeader->StorageOffsetLength; + InHeader.EncodedSizeLength = pIndexHeader->EncodedSizeLength; + InHeader.EKeyLength = pIndexHeader->EKeyLength; + InHeader.FileOffsetBits = pIndexHeader->FileOffsetBits; + InHeader.Alignment = 0; + InHeader.SegmentSize = pIndexHeader->SegmentSize; + + // Determine the size of the header + InHeader.HeaderLength = sizeof(FILE_INDEX_HEADER_V1); + InHeader.HeaderPadding = 0; + InHeader.EntryLength = pIndexHeader->EKeyLength + pIndexHeader->StorageOffsetLength + pIndexHeader->EncodedSizeLength; + InHeader.EKeyCount = pIndexHeader->EKeyCount1 + pIndexHeader->EKeyCount2; + + // Verify the entries hash - 1st block + pbKeyEntries = pbFileData + InHeader.HeaderLength; + cbKeyEntries = pIndexHeader->EKeyCount1 * InHeader.EntryLength; + if((pbKeyEntries + cbKeyEntries) > pbFileEnd) + return ERROR_FILE_CORRUPT; + if(hashlittle(pbKeyEntries, cbKeyEntries, 0) != pIndexHeader->KeysHash1) + return ERROR_FILE_CORRUPT; + + // Verify the entries hash - 2nd block + pbKeyEntries = pbKeyEntries + cbKeyEntries; + cbKeyEntries = pIndexHeader->EKeyCount2 * InHeader.EntryLength; + if((pbKeyEntries + cbKeyEntries) > pbFileEnd) + return ERROR_FILE_CORRUPT; + if(hashlittle(pbKeyEntries, cbKeyEntries, 0) != pIndexHeader->KeysHash2) + return ERROR_FILE_CORRUPT; + + return ERROR_SUCCESS; +} + +static int CaptureIndexHeader_V2(CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData, DWORD cbFileData, DWORD BucketIndex) +{ + PFILE_INDEX_HEADER_V2 pIndexHeader; + LPBYTE pbFileEnd = pbFileData + cbFileData; + + // Check for guarded block + if((pbFileData = CaptureGuardedBlock1(pbFileData, pbFileEnd)) == NULL) + return ERROR_FILE_CORRUPT; + pIndexHeader = (PFILE_INDEX_HEADER_V2)pbFileData; + + // Verify the content of the index header + if(pIndexHeader->IndexVersion != 0x07 || pIndexHeader->BucketIndex != (BYTE)BucketIndex || pIndexHeader->ExtraBytes != 0x00) + return ERROR_BAD_FORMAT; + if(pIndexHeader->EncodedSizeLength != 0x04 || pIndexHeader->StorageOffsetLength != 0x05 || pIndexHeader->EKeyLength != 0x09) + return ERROR_BAD_FORMAT; + + // Capture the values from the index header + InHeader.IndexVersion = pIndexHeader->IndexVersion; + InHeader.BucketIndex = pIndexHeader->BucketIndex; + InHeader.StorageOffsetLength = pIndexHeader->StorageOffsetLength; + InHeader.EncodedSizeLength = pIndexHeader->EncodedSizeLength; + InHeader.EKeyLength = pIndexHeader->EKeyLength; + InHeader.FileOffsetBits = pIndexHeader->FileOffsetBits; + InHeader.Alignment = 0; + InHeader.SegmentSize = pIndexHeader->SegmentSize; + + // Supply the lengths + InHeader.HeaderLength = sizeof(FILE_INDEX_GUARDED_BLOCK) + sizeof(FILE_INDEX_HEADER_V2); + InHeader.HeaderPadding = 8; + InHeader.EntryLength = pIndexHeader->EKeyLength + pIndexHeader->StorageOffsetLength + pIndexHeader->EncodedSizeLength; + InHeader.EKeyCount = 0; + return ERROR_SUCCESS; +} + +static int 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; + + // 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) +{ + LPBYTE pbEKeyEntry; + LPBYTE pbFileEnd = pbFileData + cbFileData; + LPBYTE pbFilePtr = pbFileData + InHeader.HeaderLength + InHeader.HeaderPadding; + size_t EKeyEntriesLength; + DWORD BlockSize = 0; + int nError = ERROR_NOT_SUPPORTED; + + // Remember the values from the index header + hs->FileOffsetBits = InHeader.FileOffsetBits; + hs->EKeyLength = InHeader.EKeyLength; + + // Get the pointer to the first block of EKey entries + if((pbEKeyEntry = CaptureGuardedBlock2(pbFilePtr, pbFileEnd, InHeader.EntryLength, &BlockSize)) != NULL) + { + // Supply the number of EKey entries + InHeader.HeaderPadding += sizeof(FILE_INDEX_GUARDED_BLOCK); + + // Load the continuous array of EKeys + return LoadIndexItems(hs, InHeader, pbEKeyEntry, pbEKeyEntry + BlockSize); + } + + // Get the pointer to the second block of EKey entries. + // They are alway at the position aligned to 4096 + EKeyEntriesLength = pbFileEnd - pbFilePtr; + if(EKeyEntriesLength >= 0x7800) + { + LPBYTE pbStartPage = pbFileData + 0x1000; + LPBYTE pbEndPage = pbStartPage + FILE_INDEX_PAGE_SIZE; + size_t AlignedLength = ALIGN_TO_SIZE(InHeader.EntryLength, 4); + + // Parse the chunks with the EKey entries + while(pbStartPage < pbFileEnd) + { + pbEKeyEntry = pbStartPage; + + while(pbEKeyEntry < pbEndPage) + { + CASC_CKEY_ENTRY CKeyEntry; + + // Check the EKey entry protected by 32-bit hash + if((pbEKeyEntry = CaptureGuardedBlock3(pbEKeyEntry, pbEndPage, InHeader.EntryLength)) == NULL) + break; + + // CASC\\0001: Encoding + //BREAK_ON_XKEY3(pbEKeyEntry, 0xbc, 0xe8, 0x23); + + // Capture the index entry and verify it. + if(CaptureIndexEntry(InHeader, &CKeyEntry, 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); + } + + // Move to the next entry + pbEKeyEntry += AlignedLength; + } + + // Move to the next chunk + pbStartPage += FILE_INDEX_PAGE_SIZE; + } + nError = ERROR_SUCCESS; + } + + return nError; +} + +static int LoadIndexFile(TCascStorage * hs, LPBYTE pbFileData, DWORD cbFileData, DWORD BucketIndex) +{ + CASC_INDEX_HEADER InHeader; + + // Check for CASC version 2 + if(CaptureIndexHeader_V2(InHeader, pbFileData, cbFileData, BucketIndex) == ERROR_SUCCESS) + return LoadIndexFile_V2(hs, InHeader, pbFileData, cbFileData); + + // Check for CASC index version 1 + if(CaptureIndexHeader_V1(InHeader, pbFileData, cbFileData, BucketIndex) == ERROR_SUCCESS) + return LoadIndexFile_V1(hs, InHeader, pbFileData, cbFileData); + + // Should never happen + assert(false); + return ERROR_BAD_FORMAT; +} + +static int LoadIndexFile(TCascStorage * hs, const TCHAR * szFileName, DWORD BucketIndex) +{ + LPBYTE pbFileData; + DWORD cbFileData; + int nError = 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); + CASC_FREE(pbFileData); + } + else + { + nError = GetLastError(); + } + + return nError; +} + +static int LoadLocalIndexFiles(TCascStorage * hs) +{ + TCHAR * szFileName; + DWORD OldIndexArray[CASC_INDEX_COUNT]; + DWORD IndexArray[CASC_INDEX_COUNT]; + int nError; + + // 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) + { + // 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++) + { + // Create the name of the index file + if((szFileName = CreateIndexFileName(hs, i, IndexArray[i])) != NULL) + { + // Inform the user about what we are doing + if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "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); + } + } + + // Remember the number of files that are present locally + hs->LocalFiles = hs->IndexArray.ItemCount(); + } + } + + return nError; +} + +//----------------------------------------------------------------------------- +// Online index files + +static int CaptureArcIndexFooter(CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndexFile, DWORD cbIndexFile) +{ + FILE_INDEX_FOOTER<0x08> * pFooter08; + BYTE checksum_data[0x40] = { 0 }; + BYTE md5_hash[MD5_HASH_SIZE]; + DWORD checksum_data_length; + + // Clear the entire structure + memset(&InFooter, 0, sizeof(CASC_ARCINDEX_FOOTER)); + + // Check the variant for checksum == 0x08 + pFooter08 = (FILE_INDEX_FOOTER<0x08> *)(pbIndexFile + cbIndexFile - sizeof(FILE_INDEX_FOOTER<0x08>)); + if (pFooter08->Version == 1 && pFooter08->Reserved[0] == 0 && pFooter08->Reserved[1] == 0 && pFooter08->FooterHashBytes == 8) + { + // Copy the entire structure + memcpy(InFooter.TocHash, pFooter08->TocHash, MD5_HASH_SIZE); + memcpy(InFooter.FooterHash, pFooter08->FooterHash, pFooter08->FooterHashBytes); + InFooter.Version = pFooter08->Version; + InFooter.OffsetBytes = pFooter08->OffsetBytes; + InFooter.SizeBytes = pFooter08->SizeBytes; + InFooter.EKeyBytes = pFooter08->EKeySizeBytes; + InFooter.FooterHashBytes = pFooter08->FooterHashBytes; + InFooter.PageLength = pFooter08->PageSizeKB << 10; + InFooter.ItemLength = pFooter08->EKeySizeBytes + pFooter08->OffsetBytes + pFooter08->SizeBytes; + InFooter.FooterLength = sizeof(FILE_INDEX_FOOTER<0x08>); + InFooter.ElementCount = ConvertBytesToInteger_4_LE(pFooter08->ElementCount); + + // Verify the hash. FooterHash needs to be cleared in order to calculate footer hash properly + checksum_data_length = FIELD_OFFSET(FILE_INDEX_FOOTER<0x08>, FooterHash) - FIELD_OFFSET(FILE_INDEX_FOOTER<0x08>, Version); + memcpy(checksum_data, &pFooter08->Version, checksum_data_length); + CascCalculateDataBlockHash(checksum_data, sizeof(FILE_INDEX_FOOTER<0x08>) - MD5_HASH_SIZE, md5_hash); + if(!memcmp(md5_hash, InFooter.FooterHash, InFooter.FooterHashBytes)) + return ERROR_SUCCESS; + } + + assert(false); + return ERROR_BAD_FORMAT; +} + +static int CaptureArcIndexEntry(CASC_ARCINDEX_FOOTER & InFooter, CASC_ARCINDEX_ENTRY & InEntry, LPBYTE pbIndexPage, LPBYTE pbIndexPageEnd) +{ + // 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; + + // 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) + return ERROR_BAD_FORMAT; + + // Is there a valid hash? + return CascIsValidMD5(InEntry.EKey) ? ERROR_SUCCESS : ERROR_BAD_FORMAT; +} + +static int VerifyIndexSize(CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndexFile, size_t cbIndexFile, LPBYTE * PtrIndexEnd) +{ + size_t nPageCount; + + // Set the new length (without the footer) + cbIndexFile = cbIndexFile - InFooter.FooterLength; + nPageCount = cbIndexFile / (InFooter.PageLength + MD5_HASH_SIZE); + + // There must be equal or more pages + if(((InFooter.PageLength + MD5_HASH_SIZE) * nPageCount) > cbIndexFile) + return ERROR_BAD_FORMAT; + + // Return the end-of-index + nPageCount = cbIndexFile / (InFooter.PageLength + MD5_HASH_SIZE); + PtrIndexEnd[0] = pbIndexFile + (nPageCount * InFooter.PageLength); + return ERROR_SUCCESS; +} + +static int LoadArchiveIndexPage(TCascStorage * hs, CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndexHash, LPBYTE pbIndexPage, LPBYTE pbIndexPageEnd) +{ + CASC_ARCINDEX_ENTRY InEntry; + int nError; + + while (pbIndexPage < pbIndexPageEnd) + { + // Capture the index entry + nError = CaptureArcIndexEntry(InFooter, InEntry, pbIndexPage, pbIndexPageEnd); + if (nError != ERROR_SUCCESS) + break; + + // Insert the index entry to the array + memcpy(InEntry.IndexHash, pbIndexHash, MD5_HASH_SIZE); + if (hs->ArcIndexArray.Insert(&InEntry, 1) == NULL) + return ERROR_NOT_ENOUGH_MEMORY; + + // Move to the next entry + pbIndexPage += InFooter.ItemLength; + } + + return ERROR_SUCCESS; +} + +static int LoadArchiveIndexFile(TCascStorage * hs, LPBYTE pbIndexHash, LPBYTE pbIndexFile, DWORD cbIndexFile) +{ + CASC_ARCINDEX_FOOTER InFooter; + LPBYTE pbIndexEnd = NULL; + int nError; + + // Validate and capture the footer + nError = CaptureArcIndexFooter(InFooter, pbIndexFile, cbIndexFile); + if (nError != ERROR_SUCCESS) + return nError; + + // Verify the size of the index file + nError = VerifyIndexSize(InFooter, pbIndexFile, cbIndexFile, &pbIndexEnd); + if (nError != ERROR_SUCCESS) + return nError; + + // Parse all pages + while (pbIndexFile < pbIndexEnd) + { + // Load the entire page + nError = LoadArchiveIndexPage(hs, InFooter, pbIndexHash, pbIndexFile, pbIndexFile + InFooter.PageLength); + if (nError != ERROR_SUCCESS) + break; + + // Move to the next page + pbIndexFile += InFooter.PageLength; + } + + return ERROR_SUCCESS; +} + +static int BuildMapOfArcIndices(TCascStorage * hs) +{ + PCASC_ARCINDEX_ENTRY pEntry; + size_t nItemCount = hs->ArcIndexArray.ItemCount(); + int nError; + + // Create the map + nError = hs->ArcIndexMap.Create(nItemCount, MD5_HASH_SIZE, FIELD_OFFSET(CASC_ARCINDEX_ENTRY, EKey)); + if (nError != ERROR_SUCCESS) + return nError; + + // Insert all items + for(size_t i = 0; i < nItemCount; i++) + { + pEntry = (PCASC_ARCINDEX_ENTRY)hs->ArcIndexArray.ItemAt(i); + if (pEntry != NULL) + { + if (!hs->ArcIndexMap.InsertObject(pEntry, pEntry->EKey)) + { + return ERROR_NOT_ENOUGH_MEMORY; + } + } + } + + return nError; +} + +static int 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; + + // Create the array object for the indices + nError = hs->ArcIndexArray.Create(sizeof(CASC_ARCINDEX_ENTRY), 10000); + if (nError != ERROR_SUCCESS) + return nError; + + // Load all the indices + for (size_t i = 0; i < nArchiveCount; i++) + { + LPBYTE pbIndexHash = hs->ArchivesKey.pbData + (i * MD5_HASH_SIZE); + + // Inform the user about what we are doing + if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "Downloading archive indexes", NULL, (DWORD)(i), (DWORD)(nArchiveCount))) + { + nError = 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) + { + // Load the index file to memory + pbFileData = LoadFileToMemory(szLocalPath, &cbFileData); + if (pbFileData && cbFileData) + { + nError = LoadArchiveIndexFile(hs, pbIndexHash, pbFileData, cbFileData); + CASC_FREE(pbFileData); + } + } + + // Break if an error + if (nError != ERROR_SUCCESS) + break; + } + + // Build map of EKey -> CASC_ARCINDEX_ENTRY + if (nError == ERROR_SUCCESS) + nError = BuildMapOfArcIndices(hs); + + return nError; +} + +//----------------------------------------------------------------------------- +// Public functions + +int LoadIndexFiles(TCascStorage * hs) +{ + if (hs->dwFeatures & CASC_FEATURE_ONLINE) + { + return LoadArchiveIndexFiles(hs); + } + else + { + return LoadLocalIndexFiles(hs); + } +}
\ No newline at end of file diff --git a/dep/CascLib/src/CascLib.def b/dep/CascLib/src/CascLib.def index cb5f9166e49..12e7904cb84 100644 --- a/dep/CascLib/src/CascLib.def +++ b/dep/CascLib/src/CascLib.def @@ -9,12 +9,13 @@ LIBRARY CascLib.dll EXPORTS CascOpenStorage + CascOpenOnlineStorage CascGetStorageInfo + CascAddEncryptionKey CascCloseStorage - CascOpenFileByIndexKey - CascOpenFileByEncodingKey CascOpenFile + CascGetFileInfo CascGetFileSize CascSetFilePointer CascReadFile diff --git a/dep/CascLib/src/CascLib.h b/dep/CascLib/src/CascLib.h index 272d4b4ad4d..1fb1911eade 100644 --- a/dep/CascLib/src/CascLib.h +++ b/dep/CascLib/src/CascLib.h @@ -29,14 +29,17 @@ extern "C" { //----------------------------------------------------------------------------- // Defines -#define CASCLIB_VERSION 0x0100 // Current version of CascLib (1.0) -#define CASCLIB_VERSION_STRING "1.00" // String version of CascLib version - -// Values for CascOpenStorage -#define CASC_STOR_XXXXX 0x00000001 // Not used +#define CASCLIB_VERSION 0x0132 // Current version of CascLib (1.50) +#define CASCLIB_VERSION_STRING "1.50" // String version of CascLib version // Values for CascOpenFile -#define CASC_OPEN_BY_ENCODING_KEY 0x00000001 // The name is just the encoding key; skip ROOT file processing +#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_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 #define CASC_LOCALE_ALL 0xFFFFFFFF #define CASC_LOCALE_NONE 0x00000000 @@ -58,25 +61,14 @@ extern "C" { #define CASC_LOCALE_ITIT 0x00008000 #define CASC_LOCALE_PTPT 0x00010000 -#define CASC_LOCALE_BIT_ENUS 0x01 -#define CASC_LOCALE_BIT_KOKR 0x02 -#define CASC_LOCALE_BIT_RESERVED 0x03 -#define CASC_LOCALE_BIT_FRFR 0x04 -#define CASC_LOCALE_BIT_DEDE 0x05 -#define CASC_LOCALE_BIT_ZHCN 0x06 -#define CASC_LOCALE_BIT_ESES 0x07 -#define CASC_LOCALE_BIT_ZHTW 0x08 -#define CASC_LOCALE_BIT_ENGB 0x09 -#define CASC_LOCALE_BIT_ENCN 0x0A -#define CASC_LOCALE_BIT_ENTW 0x0B -#define CASC_LOCALE_BIT_ESMX 0x0C -#define CASC_LOCALE_BIT_RURU 0x0D -#define CASC_LOCALE_BIT_PTBR 0x0E -#define CASC_LOCALE_BIT_ITIT 0x0F -#define CASC_LOCALE_BIT_PTPT 0x10 - - -#define MAX_CASC_KEY_LENGTH 0x10 // Maximum length of the key (equal to MD5 hash) +// Content flags on WoW +#define CASC_CFLAG_LOAD_ON_WINDOWS 0x08 +#define CASC_CFLAG_LOAD_ON_MAC 0x10 +#define CASC_CFLAG_LOW_VIOLENCE 0x80 +#define CASC_CFLAG_DONT_LOAD 0x100 +#define CASC_CFLAG_NO_NAME_HASH 0x10000000 +#define CASC_CFLAG_BUNDLE 0x40000000 +#define CASC_CFLAG_NO_COMPRESSION 0x80000000 #ifndef MD5_HASH_SIZE #define MD5_HASH_SIZE 0x10 @@ -87,79 +79,205 @@ extern "C" { #define SHA1_DIGEST_SIZE 0x14 // 160 bits #endif -#ifndef LANG_NEUTRAL -#define LANG_NEUTRAL 0x00 // Neutral locale -#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 - -// Flags for CascGetStorageInfo -#define CASC_FEATURE_LISTFILE 0x00000001 // The storage supports listfile +#define CASC_INVALID_OFFS64 0xFFFFFFFFFFFFFFFF + +// Flags for CASC_STORAGE_FEATURES::dwFeatures +#define CASC_FEATURE_FILE_NAMES 0x00000001 // File names are supported by the storage +#define CASC_FEATURE_ROOT_CKEY 0x00000002 // Present if the storage's ROOT returns CKey +#define CASC_FEATURE_TAGS 0x00000002 // Tags are supported by the storage +#define CASC_FEATURE_FNAME_HASHES 0x00000004 // The storage contains file name hashes on ALL files +#define CASC_FEATURE_FNAME_HASHES_OPTIONAL 0x00000008 // The storage contains file name hashes for SOME files +#define CASC_FEATURE_FILE_DATA_IDS 0x00000010 // The storage indexes files by FileDataId +#define CASC_FEATURE_LOCALE_FLAGS 0x00000020 // Locale flags are supported +#define CASC_FEATURE_CONTENT_FLAGS 0x00000040 // Content flags are supported +#define CASC_FEATURE_ONLINE 0x00000080 // The storage is an online storage + +// Macro to convert FileDataId to the argument of CascOpenFile +#define CASC_FILE_DATA_ID(FileDataId) ((LPCSTR)(size_t)FileDataId) +#define CASC_FILE_DATA_ID_FROM_STRING(szFileName) ((DWORD)(size_t)szFileName) //----------------------------------------------------------------------------- // Structures typedef enum _CASC_STORAGE_INFO_CLASS { - CascStorageFileCount, + // Returns the number of local files in the storage. Note that files + // can exist under different names, so the total number of files in the archive + // can be higher than the value returned by this info class + CascStorageLocalFileCount, + + // Returns the total file count, including the offline files + CascStorageTotalFileCount, + + // Returns the CASC_STORAGE_FEATURES structure. CascStorageFeatures, - CascStorageGameInfo, - CascStorageGameBuild, CascStorageInstalledLocales, + CascStorageProduct, // Gives CASC_STORAGE_PRODUCT + CascStorageTags, // Gives CASC_STORAGE_TAGS structure CascStorageInfoClassMax } CASC_STORAGE_INFO_CLASS, *PCASC_STORAGE_INFO_CLASS; - -typedef struct _QUERY_KEY +typedef enum _CASC_FILE_INFO_CLASS +{ + CascFileContentKey, + CascFileEncodedKey, + CascFileFullInfo, // Gives CASC_FILE_FULL_INFO structure + CascFileInfoClassMax +} CASC_FILE_INFO_CLASS, *PCASC_FILE_INFO_CLASS; + +// See https://wowdev.wiki/TACT#Products +typedef enum _CASC_PRODUCT { - LPBYTE pbData; - DWORD cbData; -} QUERY_KEY, *PQUERY_KEY; + 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 +typedef enum _CASC_NAME_TYPE +{ + CascNameFull, // Fully qualified file name + CascNameDataId, // Name created from file data id (FILE%08X.dat) + CascNameCKey, // Name created as string representation of CKey + CascNameEKey // Name created as string representation of EKey +} CASC_NAME_TYPE, *PCASC_NAME_TYPE; // Structure for SFileFindFirstFile and SFileFindNextFile typedef struct _CASC_FIND_DATA { - char szFileName[MAX_PATH]; // Full name of the found file - char * szPlainName; // Plain name of the found file - BYTE EncodingKey[MD5_HASH_SIZE]; // Encoding key - DWORD dwLocaleFlags; // Locale flags (WoW only) - DWORD dwFileDataId; // File data ID (WoW only) - DWORD dwFileSize; // Size of the file + // Full name of the found file. In case when this is CKey/EKey, + // this will be just string representation of the key stored in 'FileKey' + char szFileName[MAX_PATH]; + + // Content key. This is present if the CASC_FEATURE_ROOT_CKEY is present + BYTE CKey[MD5_HASH_SIZE]; + + // Encoded key. This is always present. + BYTE EKey[MD5_HASH_SIZE]; + + // Tag mask. Only valid if the storage supports tags, otherwise 0 + ULONGLONG TagBitMask; + + // 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; + CASC_NAME_TYPE NameType; } CASC_FIND_DATA, *PCASC_FIND_DATA; -//----------------------------------------------------------------------------- -// Callback functions +typedef struct _CASC_STORAGE_TAG +{ + LPCSTR szTagName; // Tag name (zero terminated, ANSI) + DWORD TagNameLength; // Length of the tag name + DWORD TagValue; // Tag value +} CASC_STORAGE_TAG, *PCASC_STORAGE_TAG; -typedef struct TFileStream TFileStream; -typedef void (WINAPI * STREAM_DOWNLOAD_CALLBACK)(void * pvUserData, ULONGLONG ByteOffset, DWORD dwTotalBytes); +typedef struct _CASC_STORAGE_TAGS +{ + size_t TagCount; // Number of items in the Tags array + size_t Reserved; // Reserved for future use -//----------------------------------------------------------------------------- -// We have our own qsort implementation, optimized for sorting array of pointers + CASC_STORAGE_TAG Tags[1]; // Array of CASC tags + +} CASC_STORAGE_TAGS, *PCASC_STORAGE_TAGS; + +typedef struct _CASC_STORAGE_PRODUCT +{ + LPCSTR szProductName; + DWORD dwBuildNumber; + CASC_PRODUCT Product; + +} CASC_STORAGE_PRODUCT, *PCASC_STORAGE_PRODUCT; -void qsort_pointer_array(void ** base, size_t num, int (*compare)(const void *, const void *, const void *), const void * context); +typedef struct _CASC_FILE_FULL_INFO +{ + BYTE CKey[MD5_HASH_SIZE]; // CKey + BYTE EKey[MD5_HASH_SIZE]; // EKey + char DataFileName[0x10]; // Plain name of the data file where the file is stored + ULONGLONG StorageOffset; // Offset of the file over the entire storage + 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 + DWORD SegmentIndex; // Index of the segment file (aka 0 = "data.000") + 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; + +//----------------------------------------------------------------------------- +// Some operations (e.g. opening an online storage) may take long time. +// This callback allows an application to be notified about loading progress. +// This callback only works for a single CascOpen(Online)Storage call. + +typedef bool (WINAPI * PFNPROGRESSCALLBACK)( // Return 'true' to cancel the loading process + void * PtrUserParam, // User-specific parameter passed to the callback + LPCSTR szWork, // Text for the current activity (example: "Loading "ENCODING" file") + LPCSTR szObject, // (optional) name of the object tied to the activity (example: index file name) + DWORD nCurrent, // (optional) current object being processed + DWORD nTotal // (optional) If non-zero, this is the total number of objects to process + ); + +void WINAPI CascSetProgressCallback( + PFNPROGRESSCALLBACK PtrUserCallback, // Pointer to the callback function that will be called during opening the storage + void * PtrUserParam // Arbitrary user parameter that will be passed to the callback + ); //----------------------------------------------------------------------------- // Functions for storage manipulation -bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwLocaleMask, HANDLE * phStorage); +bool WINAPI CascOpenStorage(LPCTSTR szDataPath, DWORD dwLocaleMask, HANDLE * phStorage); +bool WINAPI CascOpenOnlineStorage(LPCTSTR szLocalCache, LPCSTR szCodeName, LPCSTR szRegion, 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 CascCloseStorage(HANDLE hStorage); -bool WINAPI CascOpenFileByIndexKey(HANDLE hStorage, PQUERY_KEY pIndexKey, DWORD dwFlags, HANDLE * phFile); -bool WINAPI CascOpenFileByEncodingKey(HANDLE hStorage, PQUERY_KEY pEncodingKey, DWORD dwFlags, HANDLE * phFile); -bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocale, DWORD dwFlags, HANDLE * phFile); +bool WINAPI CascOpenFile(HANDLE hStorage, const void * pvFileName, DWORD dwLocaleFlags, DWORD dwOpenFlags, HANDLE * phFile); +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 CascGetFileId(HANDLE hStorage, const char * szFileName); DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod); bool WINAPI CascReadFile(HANDLE hFile, void * lpBuffer, DWORD dwToRead, PDWORD pdwRead); bool WINAPI CascCloseFile(HANDLE hFile); -HANDLE WINAPI CascFindFirstFile(HANDLE hStorage, const char * szMask, PCASC_FIND_DATA pFindData, const TCHAR * szListFile); +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); @@ -168,8 +286,8 @@ bool WINAPI CascFindClose(HANDLE hFind); #ifndef PLATFORM_WINDOWS -int GetLastError(); -void SetLastError(int nError); +DWORD GetLastError(); +void SetLastError(DWORD dwErrCode); #endif // PLATFORM_WINDOWS diff --git a/dep/CascLib/src/CascMndx.h b/dep/CascLib/src/CascMndx.h deleted file mode 100644 index 2ce268e40a6..00000000000 --- a/dep/CascLib/src/CascMndx.h +++ /dev/null @@ -1,359 +0,0 @@ -/*****************************************************************************/ -/* CascMndxRoot.h Copyright (c) Ladislav Zezula 2014 */ -/*---------------------------------------------------------------------------*/ -/* Interface file for MNDX structures */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 15.05.14 1.00 Lad Created */ -/*****************************************************************************/ - -#ifndef __CASC_MNDX_ROOT__ -#define __CASC_MNDX_ROOT__ - -class TFileNameDatabase; - -#define CASC_MAX_MAR_FILES 3 // Maximum of 3 MAR files are supported -#define CASC_MNDX_SIGNATURE 0x58444E4D // 'MNDX' - -#define CASC_MAX_ENTRIES(type) (0xFFFFFFFF / sizeof(type)) - -#define CASC_SEARCH_INITIALIZING 0 -#define CASC_SEARCH_SEARCHING 2 -#define CASC_SEARCH_FINISHED 4 - -typedef struct _TRIPLET -{ - DWORD BaseValue; - DWORD Value2; - DWORD Value3; -} TRIPLET, *PTRIPLET; - -typedef struct _NAME_FRAG -{ - DWORD ItemIndex; // Back index to various tables - DWORD NextIndex; // The following item index - DWORD FragOffs; // Higher 24 bits are 0xFFFFFF00 --> A single matching character - // Otherwise --> Offset to the name fragment table -} NAME_FRAG, *PNAME_FRAG; - -typedef struct _PATH_STOP -{ - DWORD ItemIndex; - DWORD field_4; - DWORD field_8; - DWORD field_C; - DWORD field_10; -} PATH_STOP, *PPATH_STOP; - -typedef union _ARRAY_POINTER -{ - LPBYTE Bytes; // Pointer to an octet - char * Chars; // Pointer to a character - PDWORD Uint32s; // Pointer to a DWORD - PTRIPLET Triplets; // Pointer to TRIPLET - PNAME_FRAG NameFrags; // Pointer to name fragment entry - PPATH_STOP PathStopPtr; // Pointer to path checkpoint - PULONGLONG Int64Ptr; // Pointer to 64-bit integer - -} ARRAY_POINTER, *PARRAY_POINTER; - -// Simple access to various tables within TGenericArray -#define ByteArray ArrayPointer.Bytes -#define CharArray ArrayPointer.Chars -#define Uint32Array ArrayPointer.Uint32s -#define TripletArray ArrayPointer.Triplets -#define NameFragArray ArrayPointer.NameFrags - -class TByteStream -{ - public: - - TByteStream(); - - void ExchangeWith(TByteStream & Target); - int GetBytes(DWORD cbByteCount, PARRAY_POINTER PtrArray); - int SkipBytes(DWORD cbByteCount); - int SetByteBuffer(LPBYTE pbNewMarData, DWORD cbNewMarData); - int GetValue_DWORD(DWORD & Value); - int GetValue_ItemCount(DWORD & NumberOfBytes, DWORD & ItemCount, DWORD ItemSize); - int GetArray_DWORDs(PARRAY_POINTER PtrArray, DWORD ItemCount); - int GetArray_Triplets(PARRAY_POINTER PtrArray, DWORD ItemCount); - int GetArray_NameTable(PARRAY_POINTER PtrArray, DWORD ItemCount); - int GetArray_BYTES(PARRAY_POINTER PtrArray, DWORD ItemCount); - - LPBYTE pbByteData; - void * pvMappedFile; - DWORD cbByteData; - DWORD field_C; - HANDLE hFile; - HANDLE hMap; -}; - -class TGenericArray -{ - public: - - TGenericArray(); - ~TGenericArray(); - - int SetArrayValid(); - - void ExchangeWith(TGenericArray & Target); - void CopyFrom(TGenericArray & Source); - - void SetMaxItems_CHARS(DWORD NewMaxItemCount); - void SetMaxItems_PATH_STOP(DWORD NewMaxItemCount); - - void InsertOneItem_CHAR(char OneChar); - void InsertOneItem_PATH_STOP(PATH_STOP & NewItem); - - void sub_19583A0(DWORD NewItemCount); - - int LoadDwordsArray(TByteStream & InStream); - int LoadTripletsArray(TByteStream & InStream); - int LoadByteArray(TByteStream & InStream); - int LoadFragmentInfos(TByteStream & InStream); - int LoadStrings(TByteStream & InStream); - - int LoadDwordsArray_Copy(TByteStream & InStream); - int LoadTripletsArray_Copy(TByteStream & InStream); - int LoadBytes_Copy(TByteStream & InStream); - int LoadFragmentInfos_Copy(TByteStream & InStream); - int LoadStringsWithCopy(TByteStream & InStream); - - ARRAY_POINTER DataBuffer; - ARRAY_POINTER FirstValid; - - ARRAY_POINTER ArrayPointer; - DWORD ItemCount; // Number of items in the array - DWORD MaxItemCount; // Capacity of the array - bool bIsValidArray; -}; - -class TBitEntryArray : public TGenericArray -{ - public: - - TBitEntryArray(); - ~TBitEntryArray(); - - DWORD GetBitEntry(DWORD EntryIndex) - { - DWORD dwItemIndex = (EntryIndex * BitsPerEntry) >> 0x05; - DWORD dwStartBit = (EntryIndex * BitsPerEntry) & 0x1F; - DWORD dwEndBit = dwStartBit + BitsPerEntry; - DWORD dwResult; - - // If the end bit index is greater than 32, - // we also need to load from the next 32-bit item - if(dwEndBit > 0x20) - { - dwResult = (Uint32Array[dwItemIndex + 1] << (0x20 - dwStartBit)) | (Uint32Array[dwItemIndex] >> dwStartBit); - } - else - { - dwResult = Uint32Array[dwItemIndex] >> dwStartBit; - } - - // Now we also need to mask the result by the bit mask - return dwResult & EntryBitMask; - } - - void ExchangeWith(TBitEntryArray & Target); - int LoadFromStream(TByteStream & InStream); - int LoadFromStream_Exchange(TByteStream & InStream); - - DWORD BitsPerEntry; - DWORD EntryBitMask; - DWORD TotalEntries; -}; - -class TStruct40 -{ - public: - - TStruct40(); - - void InitSearchBuffers(); - - TGenericArray array_00; - TGenericArray PathStops; // Array of path checkpoints - DWORD ItemIndex; // Current name fragment: Index to various tables - DWORD CharIndex; - DWORD ItemCount; - DWORD SearchPhase; // 0 = initializing, 2 = searching, 4 = finished -}; - -class TMndxFindResult -{ - public: - - TMndxFindResult(); - ~TMndxFindResult(); - - int CreateStruct40(); - void FreeStruct40(); - - int SetSearchPath(const char * szNewSearchPath, size_t cchNewSearchPath); - - const char * szSearchMask; // Search mask without wildcards - size_t cchSearchMask; // Length of the search mask - DWORD field_8; - const char * szFoundPath; // Found path name - size_t cchFoundPath; // Length of the found path name - DWORD FileNameIndex; // Index of the file name - TStruct40 * pStruct40; -}; - -class TSparseArray -{ - public: - - TSparseArray(); - - void ExchangeWith(TSparseArray & TargetObject); - int LoadFromStream(TByteStream & InStream); - int LoadFromStream_Exchange(TByteStream & InStream); - - // Returns true if the item at n-th position is present - DWORD IsItemPresent(DWORD ItemIndex) - { - return (ItemBits.Uint32Array[ItemIndex >> 0x05] & (1 << (ItemIndex & 0x1F))); - } - - DWORD GetItemValue(DWORD ItemIndex); - - TGenericArray ItemBits; // Bit array for each item (1 = item is present) - DWORD TotalItemCount; // Total number of items in the array - DWORD ValidItemCount; // Number of present items in the array - TGenericArray BaseValues; // Array of base values for item indexes >= 0x200 - TGenericArray ArrayDwords_38; - TGenericArray ArrayDwords_50; -}; - -class TNameIndexStruct -{ - public: - - TNameIndexStruct(); - ~TNameIndexStruct(); - - bool CheckNameFragment(TMndxFindResult * pStruct1C, DWORD dwFragOffs); - bool CheckAndCopyNameFragment(TMndxFindResult * pStruct1C, DWORD dwFragOffs); - void CopyNameFragment(TMndxFindResult * pStruct1C, DWORD dwFragOffs); - - void ExchangeWith(TNameIndexStruct & Target); - int LoadFromStream(TByteStream & InStream); - int LoadFromStream_Exchange(TByteStream & InStream); - - TGenericArray NameFragments; - TSparseArray Struct68; -}; - -class TStruct10 -{ - public: - - TStruct10(); - - void CopyFrom(TStruct10 & Target); - int sub_1956FD0(DWORD dwBitMask); - int sub_1957050(DWORD dwBitMask); - int sub_19572E0(DWORD dwBitMask); - int sub_1957800(DWORD dwBitMask); - - DWORD field_0; - DWORD field_4; - DWORD field_8; - DWORD field_C; -}; - -class TFileNameDatabasePtr -{ - public: - - TFileNameDatabasePtr(); - ~TFileNameDatabasePtr(); - - int FindFileInDatabase(TMndxFindResult * pStruct1C); - int sub_1956CE0(TMndxFindResult * pStruct1C, bool * pbFindResult); - - int GetFileNameCount(PDWORD PtrFileNameCount); - int CreateDatabase(LPBYTE pbMarData, DWORD cbMarData); - int SetDatabase(TFileNameDatabase * pNewDB); - - TFileNameDatabase * pDB; -}; - -class TFileNameDatabase -{ - public: - - TFileNameDatabase(); - - void ExchangeWith(TFileNameDatabase & Target); - int LoadFromStream(TByteStream & InStream); - int LoadFromStream_Exchange(TByteStream & InStream); - - DWORD sub_1959CB0(DWORD dwHashValue); - DWORD sub_1959F50(DWORD arg_0); - - // Retrieves the name fragment distance - // HOTS: 19573D0/inlined - DWORD GetNameFragmentOffsetEx(DWORD LoBitsIndex, DWORD HiBitsIndex) - { - return (FrgmDist_HiBits.GetBitEntry(HiBitsIndex) << 0x08) | FrgmDist_LoBits.ByteArray[LoBitsIndex]; - } - - // HOTS: 1957350, inlined - DWORD GetNameFragmentOffset(DWORD LoBitsIndex) - { - return GetNameFragmentOffsetEx(LoBitsIndex, Struct68_D0.GetItemValue(LoBitsIndex)); - } - - bool sub_1957B80(TMndxFindResult * pStruct1C, DWORD dwKey); - bool CheckNextPathFragment(TMndxFindResult * pStruct1C); - bool FindFileInDatabase(TMndxFindResult * pStruct1C); - - void sub_1958D70(TMndxFindResult * pStruct1C, DWORD arg_4); - bool sub_1959010(TMndxFindResult * pStruct1C, DWORD arg_4); - bool sub_1958B00(TMndxFindResult * pStruct1C); - bool sub_1959460(TMndxFindResult * pStruct1C); - - TSparseArray Struct68_00; - TSparseArray FileNameIndexes; // Array of file name indexes - TSparseArray Struct68_D0; - - // This pair of arrays serves for fast conversion from name hash to fragment offset - TGenericArray FrgmDist_LoBits; // Array of lower 8 bits of name fragment offset - TBitEntryArray FrgmDist_HiBits; // Array of upper x bits of name fragment offset - - TNameIndexStruct IndexStruct_174; - TFileNameDatabasePtr NextDB; - - TGenericArray NameFragTable; - - DWORD NameFragIndexMask; - DWORD field_214; - TStruct10 Struct10; - TByteStream MarStream; -}; - -typedef struct _MAR_FILE -{ - TFileNameDatabasePtr * pDatabasePtr; - LPBYTE pbMarData; - DWORD cbMarData; -} MAR_FILE, *PMAR_FILE; - -//----------------------------------------------------------------------------- -// Macros - -// Returns nonzero if the name fragment match is a single-char match -inline bool IS_SINGLE_CHAR_MATCH(TGenericArray & Table, DWORD ItemIndex) -{ - return ((Table.NameFragArray[ItemIndex].FragOffs & 0xFFFFFF00) == 0xFFFFFF00); -} - -#endif // __CASC_MNDX_ROOT__ diff --git a/dep/CascLib/src/CascOpenFile.cpp b/dep/CascLib/src/CascOpenFile.cpp index 9db3691af48..741ab1e1005 100644 --- a/dep/CascLib/src/CascOpenFile.cpp +++ b/dep/CascLib/src/CascOpenFile.cpp @@ -15,196 +15,63 @@ //----------------------------------------------------------------------------- // Local functions -TCascFile * IsValidFileHandle(HANDLE hFile) +PCASC_CKEY_ENTRY FindCKeyEntry_CKey(TCascStorage * hs, LPBYTE pbCKey, PDWORD PtrIndex) { - TCascFile * hf = (TCascFile *)hFile; - - return (hf != NULL && hf->hs != NULL && hf->szClassName != NULL && !strcmp(hf->szClassName, "TCascFile")) ? hf : NULL; + return (PCASC_CKEY_ENTRY)hs->CKeyMap.FindObject(pbCKey, PtrIndex); } -PCASC_INDEX_ENTRY FindIndexEntry(TCascStorage * hs, PQUERY_KEY pIndexKey) +PCASC_CKEY_ENTRY FindCKeyEntry_EKey(TCascStorage * hs, LPBYTE pbEKey, PDWORD PtrIndex) { - PCASC_INDEX_ENTRY pIndexEntry = NULL; - - if(hs->pIndexEntryMap != NULL) - pIndexEntry = (PCASC_INDEX_ENTRY)Map_FindObject(hs->pIndexEntryMap, pIndexKey->pbData, NULL); - - return pIndexEntry; -} - -PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKey, PDWORD PtrIndex) -{ - PCASC_ENCODING_ENTRY pEncodingEntry = NULL; - - if(hs->pEncodingMap != NULL) - pEncodingEntry = (PCASC_ENCODING_ENTRY)Map_FindObject(hs->pEncodingMap, pEncodingKey->pbData, PtrIndex); - - return pEncodingEntry; + return (PCASC_CKEY_ENTRY)hs->EKeyMap.FindObject(pbEKey, PtrIndex); } -static TCascFile * CreateFileHandle(TCascStorage * hs, PCASC_INDEX_ENTRY pIndexEntry) +bool OpenFileByCKeyEntry(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD dwOpenFlags, HANDLE * PtrFileHandle) { - ULONGLONG FileOffsMask = ((ULONGLONG)1 << hs->KeyMapping[0].SegmentBits) - 1; - ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE); - TCascFile * hf; + TCascFile * hf = NULL; + int nError = ERROR_FILE_NOT_FOUND; - // Allocate the CASC file structure - hf = (TCascFile *)CASC_ALLOC(TCascFile, 1); - if(hf != NULL) + // If the CKey entry is NULL, we consider the file non-existant + if(pCKeyEntry != NULL) { - // Initialize the structure - memset(hf, 0, sizeof(TCascFile)); - hf->ArchiveIndex = (DWORD)(FileOffset >> hs->KeyMapping[0].SegmentBits); - hf->HeaderOffset = (DWORD)(FileOffset & FileOffsMask); - hf->szClassName = "TCascFile"; - - // Copy the file size. Note that for all files except ENCODING, - // this is the compressed file size - hf->CompressedSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE); - - // For now, we set the file size to be equal to compressed size - // This is used when loading the ENCODING file, which does not - // have entry in the encoding table - hf->FileSize = hf->CompressedSize; - - // Increment the number of references to the archive - hs->dwRefCount++; - hf->hs = hs; - } - - return hf; -} - -static bool OpenFileByIndexKey(TCascStorage * hs, PQUERY_KEY pIndexKey, DWORD dwFlags, TCascFile ** ppCascFile) -{ - PCASC_INDEX_ENTRY pIndexEntry; - int nError = ERROR_SUCCESS; - - CASCLIB_UNUSED(dwFlags); - - // Find the key entry in the array of file keys - pIndexEntry = FindIndexEntry(hs, pIndexKey); - if(pIndexEntry == NULL) - nError = ERROR_FILE_NOT_FOUND; - - // Create the file handle structure - if(nError == ERROR_SUCCESS) - { - ppCascFile[0] = CreateFileHandle(hs, pIndexEntry); - if(ppCascFile[0] == NULL) - nError = ERROR_FILE_NOT_FOUND; + // Create the file handle structure + if((hf = new TCascFile(hs, pCKeyEntry)) != NULL) + { + hf->bVerifyIntegrity = (dwOpenFlags & CASC_STRICT_DATA_CHECK) ? true : false; + hf->bDownloadFileIf = (hs->dwFeatures & CASC_FEATURE_ONLINE) ? true : false; + nError = ERROR_SUCCESS; + } + else + { + nError = ERROR_NOT_ENOUGH_MEMORY; + } } -#ifdef CASCLIB_TEST - if(nError == ERROR_SUCCESS && ppCascFile[0] != NULL) - { - ppCascFile[0]->FileSize_IdxEntry = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE); - } -#endif + // Give the output parameter, no matter what + PtrFileHandle[0] = (HANDLE)hf; + // Handle last error if(nError != ERROR_SUCCESS) SetLastError(nError); return (nError == ERROR_SUCCESS); } -static bool OpenFileByEncodingKey(TCascStorage * hs, PQUERY_KEY pEncodingKey, DWORD dwFlags, TCascFile ** ppCascFile) -{ - PCASC_ENCODING_ENTRY pEncodingEntry; - QUERY_KEY IndexKey; - - // Find the encoding entry - pEncodingEntry = FindEncodingEntry(hs, pEncodingKey, NULL); - if(pEncodingEntry == NULL) - { - SetLastError(ERROR_FILE_NOT_FOUND); - return false; - } - - // Prepare the file index and open the file by index - // Note: We don't know what to do if there is more than just one index key - // We always take the first file present. Is that correct? - IndexKey.pbData = GET_INDEX_KEY(pEncodingEntry); - IndexKey.cbData = MD5_HASH_SIZE; - if(OpenFileByIndexKey(hs, &IndexKey, dwFlags, ppCascFile)) - { - // Check if the file handle was created - if(ppCascFile[0] != NULL) - { - // Fill-in the file size. For all files except ENCODING, - // this overrides the value stored in the index entry. - ppCascFile[0]->FileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE); - -#ifdef CASCLIB_TEST - ppCascFile[0]->FileSize_EncEntry = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE); -#endif - return true; - } - } - - return false; -} - //----------------------------------------------------------------------------- // Public functions -bool WINAPI CascOpenFileByIndexKey(HANDLE hStorage, PQUERY_KEY pIndexKey, DWORD dwFlags, HANDLE * phFile) -{ - TCascStorage * hs; - - // Validate the storage handle - hs = IsValidStorageHandle(hStorage); - if(hs == NULL) - { - SetLastError(ERROR_INVALID_HANDLE); - return false; - } - - // Validate the other parameters - if(pIndexKey == NULL || pIndexKey->pbData == NULL || pIndexKey->cbData == 0 || phFile == NULL) - { - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } - - // Use the internal function to open the file - return OpenFileByIndexKey(hs, pIndexKey, dwFlags, (TCascFile **)phFile); -} - -bool WINAPI CascOpenFileByEncodingKey(HANDLE hStorage, PQUERY_KEY pEncodingKey, DWORD dwFlags, HANDLE * phFile) +bool WINAPI CascOpenFile(HANDLE hStorage, const void * pvFileName, DWORD dwLocaleFlags, DWORD dwOpenFlags, HANDLE * PtrFileHandle) { + PCASC_CKEY_ENTRY pCKeyEntry = NULL; TCascStorage * hs; - - // Validate the storage handle - hs = IsValidStorageHandle(hStorage); - if(hs == NULL) - { - SetLastError(ERROR_INVALID_HANDLE); - return false; - } - - // Validate the other parameters - if(pEncodingKey == NULL || pEncodingKey->pbData == NULL || pEncodingKey->cbData == 0 || phFile == NULL) - { - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } - - // Use the internal function fo open the file - return OpenFileByEncodingKey(hs, pEncodingKey, dwFlags, (TCascFile **)phFile); -} - -bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocale, DWORD dwFlags, HANDLE * phFile) -{ - TCascStorage * hs; - QUERY_KEY EncodingKey; - LPBYTE pbEncodingKey; - BYTE KeyBuffer[MD5_HASH_SIZE]; + const char * szFileName; + DWORD FileDataId = CASC_INVALID_ID; + BYTE CKeyEKeyBuffer[MD5_HASH_SIZE]; int nError = ERROR_SUCCESS; - CASCLIB_UNUSED(dwLocale); + // This parameter is not used + CASCLIB_UNUSED(dwLocaleFlags); // Validate the storage handle - hs = IsValidStorageHandle(hStorage); + hs = TCascStorage::IsValid(hStorage); if(hs == NULL) { SetLastError(ERROR_INVALID_HANDLE); @@ -212,107 +79,104 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal } // Validate the other parameters - if(szFileName == NULL || szFileName[0] == 0 || phFile == NULL) + if(PtrFileHandle == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return false; } - // If the user is opening the file via encoding key, skip the ROOT file processing - if((dwFlags & CASC_OPEN_BY_ENCODING_KEY) == 0) - { - // Let the root directory provider get us the encoding key - pbEncodingKey = RootHandler_GetKey(hs->pRootHandler, szFileName); - if(pbEncodingKey == NULL) - { + // Retrieve the CKey/EKey from the file name in different modes + switch(dwOpenFlags & CASC_OPEN_TYPE_MASK) + { + case CASC_OPEN_BY_NAME: + + // The 'pvFileName' must be zero terminated ANSI file name + szFileName = (const char *)pvFileName; + if(szFileName == NULL || szFileName[0] == 0) + { + SetLastError(ERROR_INVALID_PARAMETER); + return false; + } + + // The first chance: Try to find the file by name (using the root handler) + pCKeyEntry = hs->pRootHandler->GetFile(hs, szFileName); + if(pCKeyEntry != NULL) + break; + + // Second chance: If the file name is actually a file data id, we convert it to file data ID + if(IsFileDataIdName(szFileName, FileDataId)) + { + pCKeyEntry = hs->pRootHandler->GetFile(hs, FileDataId); + if(pCKeyEntry != NULL) + break; + } + + // Third chance: If the file name is a string representation of CKey/EKey, we try to query for CKey + if(IsFileCKeyEKeyName(szFileName, CKeyEKeyBuffer)) + { + pCKeyEntry = FindCKeyEntry_CKey(hs, CKeyEKeyBuffer); + if(pCKeyEntry != NULL) + break; + + pCKeyEntry = FindCKeyEntry_EKey(hs, CKeyEKeyBuffer); + if(pCKeyEntry != NULL) + break; + } + SetLastError(ERROR_FILE_NOT_FOUND); return false; - } - // Setup the encoding key - EncodingKey.pbData = pbEncodingKey; - EncodingKey.cbData = MD5_HASH_SIZE; - } - else - { - // Check the length of the file name - if(strlen(szFileName) < MD5_STRING_SIZE) - { - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } + case CASC_OPEN_BY_CKEY: - // Convert the file name to binary blob - EncodingKey.pbData = KeyBuffer; - EncodingKey.cbData = MD5_HASH_SIZE; - nError = ConvertStringToBinary(szFileName, MD5_STRING_SIZE, KeyBuffer); - } + // The 'pvFileName' must be a pointer to 16-byte CKey or EKey + if(pvFileName == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return false; + } - // Use the encoding key to find the file in the encoding table entry - if(nError == ERROR_SUCCESS) - { - if(!OpenFileByEncodingKey(hs, &EncodingKey, dwFlags, (TCascFile **)phFile)) - { - assert(GetLastError() != ERROR_SUCCESS); - nError = GetLastError(); - } - } + // Search the CKey map in order to find the CKey entry + pCKeyEntry = FindCKeyEntry_CKey(hs, (LPBYTE)pvFileName); + break; -#ifdef CASCLIB_TEST -// if(phFile[0] != NULL && pRootEntryMndx != NULL) -// { -// ((TCascFile *)(phFile[0]))->FileSize_RootEntry = pRootEntryMndx->FileSize; -// } -#endif + case CASC_OPEN_BY_EKEY: - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} + // The 'pvFileName' must be a pointer to 16-byte CKey or EKey + if(pvFileName == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return false; + } -DWORD WINAPI CascGetFileId(HANDLE hStorage, const char * szFileName) -{ - TCascStorage * hs; + // Search the CKey map in order to find the CKey entry + pCKeyEntry = FindCKeyEntry_EKey(hs, (LPBYTE)pvFileName); + break; - // Validate the storage handle - hs = IsValidStorageHandle(hStorage); - if (hs == NULL) - { - SetLastError(ERROR_INVALID_HANDLE); - return false; - } + case CASC_OPEN_BY_FILEID: - // Validate the other parameters - if (szFileName == NULL || szFileName[0] == 0) - { - SetLastError(ERROR_INVALID_PARAMETER); - return false; + // Retrieve the file CKey/EKey + pCKeyEntry = hs->pRootHandler->GetFile(hs, CASC_FILE_DATA_ID_FROM_STRING(pvFileName)); + break; + + default: + + // Unknown open mode + nError = ERROR_INVALID_PARAMETER; + break; } - return RootHandler_GetFileId(hs->pRootHandler, szFileName); + // Perform the open operation + return OpenFileByCKeyEntry(hs, pCKeyEntry, dwOpenFlags, PtrFileHandle); } bool WINAPI CascCloseFile(HANDLE hFile) { TCascFile * hf; - hf = IsValidFileHandle(hFile); - if(hf != NULL) + hf = TCascFile::IsValid(hFile); + if (hf != NULL) { - // Close (dereference) the archive handle - if(hf->hs != NULL) - CascCloseStorage((HANDLE)hf->hs); - hf->hs = NULL; - - // Free the file cache and frame array - if(hf->pbFileCache != NULL) - CASC_FREE(hf->pbFileCache); - if(hf->pFrames != NULL) - CASC_FREE(hf->pFrames); - - // Free the structure itself - hf->szClassName = NULL; - CASC_FREE(hf); + delete hf; return true; } diff --git a/dep/CascLib/src/CascOpenStorage.cpp b/dep/CascLib/src/CascOpenStorage.cpp index ec277f67c13..53031169cbb 100644 --- a/dep/CascLib/src/CascOpenStorage.cpp +++ b/dep/CascLib/src/CascOpenStorage.cpp @@ -15,1101 +15,1221 @@ #include "CascCommon.h" //----------------------------------------------------------------------------- -// Local structures +// Local defines -// Size of one segment in the ENCODING table -// The segment is filled by entries of type -#define CASC_ENCODING_SEGMENT_SIZE 0x1000 +// Limit for "orphaned" items - those that are in index files, but are not in ENCODING manifest +#define CASC_MAX_ORPHANED_ITEMS 0x100 -typedef struct _BLOCK_SIZE_AND_HASH -{ - DWORD cbBlockSize; - DWORD dwBlockHash; +//----------------------------------------------------------------------------- +// Local variables -} BLOCK_SIZE_AND_HASH, *PBLOCK_SIZE_AND_HASH; +static PFNPROGRESSCALLBACK PfnProgressCallback = NULL; +static void * PtrProgressParam = NULL; -typedef struct _FILE_INDEX_HEADER_V1 -{ - USHORT field_0; - BYTE KeyIndex; // Key index (0 for data.i0x, 1 for data.i1x, 2 for data.i2x etc.) - BYTE align_3; - DWORD field_4; - ULONGLONG field_8; - ULONGLONG MaxFileOffset; - BYTE SpanSizeBytes; - BYTE SpanOffsBytes; - BYTE KeyBytes; - BYTE SegmentBits; // Number of bits for file offset - DWORD KeyCount1; - DWORD KeyCount2; - DWORD KeysHash1; - DWORD KeysHash2; - DWORD dwHeaderHash; -} FILE_INDEX_HEADER_V1, *PFILE_INDEX_HEADER_V1; - -typedef struct _FILE_INDEX_HEADER_V2 -{ - USHORT IndexVersion; // Must be 0x07 - BYTE KeyIndex; // Must be equal to the file key index - BYTE ExtraBytes; // (?) Extra bytes in the key record - BYTE SpanSizeBytes; // Size of field with file size - BYTE SpanOffsBytes; // Size of field with file offset - BYTE KeyBytes; // Size of the file key (bytes) - BYTE SegmentBits; // Number of bits for the file offset (rest is archive index) - ULONGLONG MaxFileOffset; - -} FILE_INDEX_HEADER_V2, *PFILE_INDEX_HEADER_V2; - -typedef struct _FILE_ENCODING_SEGMENT +//----------------------------------------------------------------------------- +// TCascStorage service functions + +TCascStorage::TCascStorage() { - BYTE FirstEncodingKey[MD5_HASH_SIZE]; // The first encoding key in the segment - BYTE SegmentHash[MD5_HASH_SIZE]; // MD5 hash of the entire segment + // Prepare the base storage parameters + szClassName = "TCascStorage"; + 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; + + // Take the callback param and data. Zero the global pointers + PfnCallback = PfnProgressCallback; + PtrCallbackParam = PtrProgressParam; + PfnProgressCallback = NULL; + PtrProgressParam = NULL; +} -} FILE_ENCODING_SEGMENT, *PFILE_ENCODING_SEGMENT; +TCascStorage::~TCascStorage() +{ + // Free the root handler + if(pRootHandler != NULL) + delete pRootHandler; + pRootHandler = NULL; -//----------------------------------------------------------------------------- -// Local variables + // Close all data files + for(size_t i = 0; i < CASC_MAX_DATA_FILES; i++) + { + FileStream_Close(DataFiles[i]); + DataFiles[i] = NULL; + } -static const TCHAR * szAllowedHexChars = _T("0123456789aAbBcCdDeEfF"); -static const TCHAR * szIndexFormat_V1 = _T("data.i%x%x"); -static const TCHAR * szIndexFormat_V2 = _T("%02x%08x.idx"); + // Free the file paths + CASC_FREE(szDataPath); + CASC_FREE(szRootPath); + CASC_FREE(szBuildFile); + CASC_FREE(szIndexPath); + CASC_FREE(szCdnServers); + CASC_FREE(szCdnPath); + CASC_FREE(szCodeName); + CASC_FREE(szRegion); + + // Free the blobs + FreeCascBlob(&CdnConfigKey); + FreeCascBlob(&CdnBuildKey); + + FreeCascBlob(&ArchiveGroup); + FreeCascBlob(&ArchivesKey); + FreeCascBlob(&PatchArchivesKey); + FreeCascBlob(&PatchArchivesGroup); + FreeCascBlob(&BuildFiles); + szClassName = NULL; +} -//----------------------------------------------------------------------------- -// Local functions +TCascStorage * TCascStorage::AddRef() +{ + dwRefCount++; + return this; +} -inline void CopyFileKey(LPBYTE Trg, LPBYTE Src) +TCascStorage * TCascStorage::Release() { - Trg[0x00] = Src[0x00]; - Trg[0x01] = Src[0x01]; - Trg[0x02] = Src[0x02]; - Trg[0x03] = Src[0x03]; - Trg[0x04] = Src[0x04]; - Trg[0x05] = Src[0x05]; - Trg[0x06] = Src[0x06]; - Trg[0x07] = Src[0x07]; - Trg[0x08] = Src[0x08]; - Trg[0x09] = Src[0x09]; - Trg[0x0A] = Src[0x0A]; - Trg[0x0B] = Src[0x0B]; - Trg[0x0C] = Src[0x0C]; - Trg[0x0D] = Src[0x0D]; - Trg[0x0E] = Src[0x0E]; - Trg[0x0F] = Src[0x0F]; + if (dwRefCount == 1) + { + delete this; + return NULL; + } + + dwRefCount--; + return NULL; } -TCascStorage * IsValidStorageHandle(HANDLE hStorage) +TCascStorage * TCascStorage::IsValid(HANDLE hStorage) { TCascStorage * hs = (TCascStorage *)hStorage; return (hs != NULL && hs->szClassName != NULL && !strcmp(hs->szClassName, "TCascStorage")) ? hs : NULL; } -// "data.iXY" -static bool IsIndexFileName_V1(const TCHAR * szFileName) -{ - // Check if the name looks like a valid index file - return (_tcslen(szFileName) == 8 && - _tcsnicmp(szFileName, _T("data.i"), 6) == 0 && - _tcsspn(szFileName + 6, szAllowedHexChars) == 2); -} +//----------------------------------------------------------------------------- +// Local functions -static bool IsIndexFileName_V2(const TCHAR * szFileName) +void * ProbeOutputBuffer(void * pvBuffer, size_t cbLength, size_t cbMinLength, size_t * pcbLengthNeeded) { - // Check if the name looks like a valid index file - return (_tcslen(szFileName) == 14 && - _tcsspn(szFileName, _T("0123456789aAbBcCdDeEfF")) == 0x0A && - _tcsicmp(szFileName + 0x0A, _T(".idx")) == 0); + // Verify the output length + if(cbLength < cbMinLength) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + pvBuffer = NULL; + } + + // Give the output length and return result + if(pcbLengthNeeded != NULL) + pcbLengthNeeded[0] = cbMinLength; + return pvBuffer; } -static bool IsRootFile_Starcraft1(LPBYTE pbFileData, DWORD cbFileData) +static TCHAR * CheckForIndexDirectory(TCascStorage * hs, const TCHAR * szSubDir) { - LPBYTE pbFileEnd = pbFileData + cbFileData; - LPBYTE pbHashName; - LPBYTE pbHashEnd; - - // Skip the file name - while(pbFileData < pbFileEnd && pbFileData[0] != '|') - pbFileData++; - if(pbFileData[0] != '|') - return false; + TCHAR * szIndexPath; - // Then, a MD5 must follow - pbHashName = pbHashEnd = pbFileData + 1; - while(pbHashEnd < pbFileEnd && pbHashEnd[0] != 0x0A) - pbHashEnd++; - if(pbHashEnd[0] != 0x0A) - return false; + // Combine the index path + szIndexPath = CombinePath(hs->szDataPath, szSubDir); + if (!DirectoryExists(szIndexPath)) + { + CASC_FREE(szIndexPath); + } - // The length must be exactly 32 characters - return ((pbHashEnd - pbHashName) == 32); + return szIndexPath; } -static bool IsCascIndexHeader_V1(LPBYTE pbFileData, DWORD cbFileData) +static int CreateCKeyMaps(TCascStorage * hs, CASC_ENCODING_HEADER & EnHeader) { - PFILE_INDEX_HEADER_V1 pIndexHeader = (PFILE_INDEX_HEADER_V1)pbFileData; - DWORD dwHeaderHash; - bool bResult = false; + size_t nEstimatedEntries = (EnHeader.CKeyPageCount * EnHeader.CKeyPageSize) / sizeof(FILE_CKEY_ENTRY); + size_t nIxEntries = hs->IndexArray.ItemCount(); + int nError; - // Check the size - if(cbFileData >= sizeof(FILE_INDEX_HEADER_V1)) + // 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) { - // Save the header hash - dwHeaderHash = pIndexHeader->dwHeaderHash; - pIndexHeader->dwHeaderHash = 0; + nEstimatedEntries = nEstimatedEntries + nIxEntries; + hs->bAllowOrphans = true; + } - // Calculate the hash - if(hashlittle(pIndexHeader, sizeof(FILE_INDEX_HEADER_V1), 0) == dwHeaderHash) - bResult = true; + // Allow some room for extra entries + nEstimatedEntries += CASC_MAX_ORPHANED_ITEMS; - // Put the hash back - pIndexHeader->dwHeaderHash = dwHeaderHash; - } + // Create the array of CKey items + nError = hs->CKeyArray.Create(sizeof(CASC_CKEY_ENTRY), nEstimatedEntries); + if(nError != ERROR_SUCCESS) + return nError; + + // 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; + + // 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; - return bResult; + return ERROR_SUCCESS; } -static bool IsCascIndexHeader_V2(LPBYTE pbFileData, DWORD cbFileData) +int CaptureEncodingHeader(CASC_ENCODING_HEADER & EnHeader, LPBYTE pbFileData, size_t cbFileData) { - PBLOCK_SIZE_AND_HASH pSizeAndHash = (PBLOCK_SIZE_AND_HASH)pbFileData; - unsigned int HashHigh = 0; - unsigned int HashLow = 0; + PFILE_ENCODING_HEADER pFileHeader = (PFILE_ENCODING_HEADER)pbFileData; - // Check for the header - if(cbFileData < sizeof(BLOCK_SIZE_AND_HASH) || pSizeAndHash->cbBlockSize < 0x10) - return false; - if(cbFileData < pSizeAndHash->cbBlockSize + sizeof(BLOCK_SIZE_AND_HASH)) - return false; + // Check the signature ('EN') and version + if(cbFileData < sizeof(FILE_ENCODING_HEADER) || pFileHeader->Magic != FILE_MAGIC_ENCODING || pFileHeader->Version != 0x01) + return ERROR_BAD_FORMAT; - // The index header for CASC v 2.0 begins with length and checksum - hashlittle2(pSizeAndHash + 1, pSizeAndHash->cbBlockSize, &HashHigh, &HashLow); - return (HashHigh == pSizeAndHash->dwBlockHash); + // Note that we don't support CKey and EKey sizes other than 0x10 in the ENCODING file + if(pFileHeader->CKeyLength != MD5_HASH_SIZE || pFileHeader->EKeyLength != MD5_HASH_SIZE) + return ERROR_BAD_FORMAT; + + EnHeader.Magic = pFileHeader->Magic; + EnHeader.Version = pFileHeader->Version; + EnHeader.CKeyLength = pFileHeader->CKeyLength; + EnHeader.EKeyLength = pFileHeader->EKeyLength; + EnHeader.CKeyPageCount = ConvertBytesToInteger_4(pFileHeader->CKeyPageCount); + EnHeader.CKeyPageSize = ConvertBytesToInteger_2(pFileHeader->CKeyPageSize) * 1024; + EnHeader.EKeyPageCount = ConvertBytesToInteger_4(pFileHeader->EKeyPageCount); + EnHeader.EKeyPageSize = ConvertBytesToInteger_2(pFileHeader->EKeyPageSize) * 1024; + EnHeader.ESpecBlockSize = ConvertBytesToInteger_4(pFileHeader->ESpecBlockSize); + return ERROR_SUCCESS; } -static bool CutLastPathPart(TCHAR * szWorkPath) +static int LoadEncodingCKeyPage(TCascStorage * hs, CASC_ENCODING_HEADER & EnHeader, LPBYTE pbPageBegin, LPBYTE pbEndOfPage) { - size_t nLength = _tcslen(szWorkPath); + PCASC_CKEY_ENTRY pCKeyEntry; + PFILE_CKEY_ENTRY pFileEntry; + LPBYTE pbFileEntry = pbPageBegin; + + // Sanity checks + assert(hs->CKeyMap.IsInitialized()); + assert(hs->EKeyMap.IsInitialized()); + + // Parse all encoding entries + while(pbFileEntry < pbEndOfPage) + { + // Get pointer to the encoding entry + pFileEntry = (PFILE_CKEY_ENTRY)pbFileEntry; + if(pFileEntry->EKeyCount == 0) + break; + + // 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); - // Go one character back - if(nLength > 0) - nLength--; + // 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); + } - // Cut ending (back)slashes, if any - while(nLength > 0 && (szWorkPath[nLength] == _T('\\') || szWorkPath[nLength] == _T('/'))) - nLength--; + // Move to the next encoding entry + pbFileEntry = pbFileEntry + 2 + 4 + EnHeader.CKeyLength + (pFileEntry->EKeyCount * EnHeader.EKeyLength); + } + 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; - // Cut the last path part - while(nLength > 0) + if(pSourceEntry->Flags & CASC_CE_HAS_EKEY) { - // End of path? - if(szWorkPath[nLength] == _T('\\') || szWorkPath[nLength] == _T('/')) + // If there is that item already, reuse it + pCKeyEntry = FindCKeyEntry_EKey(hs, pSourceEntry->EKey); + if(pCKeyEntry == NULL) { - szWorkPath[nLength] = 0; - return true; + // 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; - // Go one character back - nLength--; + 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 false; + return pCKeyEntry; } -static int InsertExtraFile( - TCascStorage * hs, - const char * szFileName, - PQUERY_KEY pQueryKey) +static int CopyBuildFileItemsToCKeyArray(TCascStorage * hs) { - // If the given key is not encoding key (aka, it's an index key), - // we need to create a fake encoding entry - if(pQueryKey->cbData == MD5_HASH_SIZE * 2) + // 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_ENCODING_ENTRY pNewEntry; - PCASC_INDEX_ENTRY pIndexEntry; - QUERY_KEY IndexKey; - - // Find the entry in the index table in order to get the file size - IndexKey.pbData = pQueryKey->pbData + MD5_HASH_SIZE; - IndexKey.cbData = MD5_HASH_SIZE; - pIndexEntry = FindIndexEntry(hs, &IndexKey); - if(pIndexEntry == NULL) - return ERROR_FILE_NOT_FOUND; - - // Create a fake entry in the encoding map - pNewEntry = (PCASC_ENCODING_ENTRY)Array_Insert(&hs->ExtraEntries, NULL, 1); - if(pNewEntry == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Fill the encoding entry - pNewEntry->KeyCount = 1; - pNewEntry->FileSizeBE[0] = pIndexEntry->FileSizeLE[3]; - pNewEntry->FileSizeBE[1] = pIndexEntry->FileSizeLE[2]; - pNewEntry->FileSizeBE[2] = pIndexEntry->FileSizeLE[1]; - pNewEntry->FileSizeBE[3] = pIndexEntry->FileSizeLE[0]; - memcpy(pNewEntry->EncodingKey, pQueryKey->pbData, MD5_HASH_SIZE); - memcpy(pNewEntry + 1, pQueryKey->pbData + MD5_HASH_SIZE, MD5_HASH_SIZE); - - // Insert the entry to the map of encoding keys - Map_InsertObject(hs->pEncodingMap, pNewEntry, pNewEntry->EncodingKey); + PCASC_CKEY_ENTRY pCKeyEntry = (PCASC_CKEY_ENTRY)hs->VfsRootList.ItemAt(i); + CopyBuildFileItemToCKeyArray(hs, pCKeyEntry); } - // Now we need to insert the entry to the root handler in order - // to be able to translate file name to encoding key - return RootHandler_Insert(hs->pRootHandler, szFileName, pQueryKey->pbData); + return ERROR_SUCCESS; } -static int InitializeCascDirectories(TCascStorage * hs, const TCHAR * szDataPath) +static int CopyIndexItemsToCKeyArray(TCascStorage * hs) { - TCHAR * szWorkPath; - int nError = ERROR_NOT_ENOUGH_MEMORY; + PCASC_CKEY_ENTRY pIndexEntry; + PCASC_CKEY_ENTRY pCKeyEntry; + size_t nItemCount = hs->IndexArray.ItemCount(); + bool bAllocatedNewEntry = false; - // Find the root directory of the storage. The root directory - // is the one where ".build.info" is. - szWorkPath = CascNewStr(szDataPath, 0); - if(szWorkPath != NULL) + // Iterate over all index items + for(size_t i = 0; i < nItemCount; i++) { - // Get the length and go up until we find the ".build.info" or ".build.db" - for(;;) + // 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) { - // Is this a game directory? - nError = CheckGameDirectory(hs, szWorkPath); - if(nError == ERROR_SUCCESS) + // 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) { - nError = ERROR_SUCCESS; - break; + pCKeyEntry->StorageOffset = pIndexEntry->StorageOffset; + pCKeyEntry->EncodedSize = pIndexEntry->EncodedSize; } - // Cut one path part - if(!CutLastPathPart(szWorkPath)) + if(bAllocatedNewEntry) { - nError = ERROR_FILE_NOT_FOUND; - break; + 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); } - } - // Free the work path buffer - CASC_FREE(szWorkPath); + // Mark the file as available locally + pCKeyEntry->Flags |= CASC_CE_FILE_IS_LOCAL; + } } - return nError; + // We free the index array at this point + hs->IndexArray.Free(); + return ERROR_SUCCESS; } -static bool IndexDirectory_OnFileFound( - const TCHAR * szFileName, - PDWORD IndexArray, - PDWORD OldIndexArray, - void * pvContext) +static int LoadEncodingManifest(TCascStorage * hs) { - TCascStorage * hs = (TCascStorage *)pvContext; - DWORD IndexValue = 0; - DWORD IndexVersion = 0; + LPBYTE pbEncodingFile; + DWORD cbEncodingFile = 0; + int nError = ERROR_SUCCESS; - // Auto-detect the format of the index file name - if(hs->szIndexFormat == NULL) - { - if(IsIndexFileName_V1(szFileName)) - hs->szIndexFormat = szIndexFormat_V1; - else if(IsIndexFileName_V2(szFileName)) - hs->szIndexFormat = szIndexFormat_V2; - else - return false; - } + // Inform the user about what we are doing + if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "Loading ENCODING manifest", NULL, 0, 0)) + return ERROR_CANCELLED; - if(hs->szIndexFormat == szIndexFormat_V1) + // Load the entire encoding file to memory + pbEncodingFile = LoadInternalFileToMemory(hs, &hs->EncodingCKey, &cbEncodingFile); + if(pbEncodingFile != NULL && cbEncodingFile != 0) { - // Check the index file name format - if(!IsIndexFileName_V1(szFileName)) - return false; + CASC_ENCODING_HEADER EnHeader; - // Get the main index from the first two digits - if(ConvertDigitToInt32(szFileName + 6, &IndexValue) != ERROR_SUCCESS) - return false; - if(ConvertDigitToInt32(szFileName + 7, &IndexVersion) != ERROR_SUCCESS) - return false; - } - - else if(hs->szIndexFormat == szIndexFormat_V2) - { - // Check the index file name format - if(!IsIndexFileName_V2(szFileName)) - return false; + // Capture the header of the ENCODING file + nError = CaptureEncodingHeader(EnHeader, pbEncodingFile, cbEncodingFile); + if(nError == 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++) + { + 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; + } + + // 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 if the CKey matches with the expected first value + if(memcmp(pCKeyEntry->CKey, pPageHeader[i].FirstKey, CASC_CKEY_SIZE)) + { + nError = 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; + + // Move to the next CKey page + pbCKeyPage += EnHeader.CKeyPageSize; + } + } + } - // Get the main index from the first two digits - if(ConvertStringToInt32(szFileName, 2, &IndexValue) != ERROR_SUCCESS) - return false; - if(ConvertStringToInt32(szFileName + 2, 8, &IndexVersion) != ERROR_SUCCESS) - return false; - } - else - { - // Should never happen - assert(false); - return false; - } + // 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) + { + nError = CopyBuildFileItemsToCKeyArray(hs); + } - // The index value must not be greater than 0x0F - if(IndexValue >= CASC_INDEX_COUNT) - return false; + // Now supply all the entries from the index files + if(nError == ERROR_SUCCESS) + { + nError = CopyIndexItemsToCKeyArray(hs); + } - // If the new subindex is greater than the previous one, - // use this one instead - if(IndexVersion > IndexArray[IndexValue]) - { - OldIndexArray[IndexValue] = IndexArray[IndexValue]; - IndexArray[IndexValue] = IndexVersion; + // Free the loaded ENCODING file + CASC_FREE(pbEncodingFile); } - else if(IndexVersion > OldIndexArray[IndexValue]) + else { - OldIndexArray[IndexValue] = IndexVersion; + nError = GetLastError(); } - // Note: WoW6 only keeps last two index files - // Any additional index files are deleted at this point - return true; + return nError; } -static TCHAR * CreateIndexFileName(TCascStorage * hs, DWORD IndexValue, DWORD IndexVersion) +size_t GetTagBitmapLength(LPBYTE pbFilePtr, LPBYTE pbFileEnd, DWORD EntryCount) { - TCHAR szPlainName[0x40]; - - // Sanity checks - assert(hs->szIndexFormat != NULL); - assert(hs->szIndexPath != NULL); - assert(IndexValue <= 0x0F); - - // Create the full path - _stprintf(szPlainName, hs->szIndexFormat, IndexValue, IndexVersion); - return CombinePath(hs->szIndexPath, szPlainName); -} + size_t nBitmapLength; -static int VerifyAndParseKeyMapping_V1(PCASC_MAPPING_TABLE pKeyMapping, DWORD KeyIndex) -{ - PFILE_INDEX_HEADER_V1 pIndexHeader = (PFILE_INDEX_HEADER_V1)pKeyMapping->pbFileData; - DWORD dwDataHash1; - DWORD dwDataHash2; - - // Verify the format - if(pIndexHeader->field_0 != 0x0005) - return ERROR_NOT_SUPPORTED; - if(pIndexHeader->KeyIndex != KeyIndex) - return ERROR_NOT_SUPPORTED; - if(pIndexHeader->field_8 == 0) - return ERROR_NOT_SUPPORTED; - - // Verofy the bit counts - if(pIndexHeader->SpanSizeBytes != 0x04 || - pIndexHeader->SpanOffsBytes != 0x05 || - pIndexHeader->KeyBytes != 0x09) - return ERROR_NOT_SUPPORTED; - - pKeyMapping->ExtraBytes = 0; - pKeyMapping->SpanSizeBytes = pIndexHeader->SpanSizeBytes; - pKeyMapping->SpanOffsBytes = pIndexHeader->SpanOffsBytes; - pKeyMapping->KeyBytes = pIndexHeader->KeyBytes; - pKeyMapping->SegmentBits = pIndexHeader->SegmentBits; - pKeyMapping->MaxFileOffset = pIndexHeader->MaxFileOffset; - - // Get the pointer to the key entry array - pKeyMapping->nIndexEntries = pIndexHeader->KeyCount1 + pIndexHeader->KeyCount2; - if(pKeyMapping->nIndexEntries != 0) - pKeyMapping->pIndexEntries = (PCASC_INDEX_ENTRY)(pKeyMapping->pbFileData + sizeof(FILE_INDEX_HEADER_V1)); - - // Verify hashes - dwDataHash1 = hashlittle(pKeyMapping->pIndexEntries, pIndexHeader->KeyCount1 * sizeof(CASC_INDEX_ENTRY), 0); - dwDataHash2 = hashlittle(pKeyMapping->pIndexEntries + pIndexHeader->KeyCount1, pIndexHeader->KeyCount2 * sizeof(CASC_INDEX_ENTRY), 0); - if(dwDataHash1 != pIndexHeader->KeysHash1 || dwDataHash2 != pIndexHeader->KeysHash2) - return ERROR_FILE_CORRUPT; + nBitmapLength = (EntryCount / 8) + ((EntryCount & 0x07) ? 1 : 0); + if ((pbFilePtr + nBitmapLength) > pbFileEnd) + nBitmapLength = (pbFileEnd - pbFilePtr); - return ERROR_SUCCESS; + return nBitmapLength; } -static int VerifyAndParseKeyMapping_V2(PCASC_MAPPING_TABLE pKeyMapping, DWORD KeyIndex) +int CaptureDownloadHeader(CASC_DOWNLOAD_HEADER & DlHeader, LPBYTE pbFileData, size_t cbFileData) { - PFILE_INDEX_HEADER_V2 pIndexHeader = (PFILE_INDEX_HEADER_V2)pKeyMapping->pbFileData; - PBLOCK_SIZE_AND_HASH pSizeAndHash; - LPBYTE pbLastPartEnd; - LPBYTE pbLastPart; - DWORD FilePosition; - DWORD LastPartLength; - unsigned int HashHigh = 0; - unsigned int HashLow = 0; - - // The index header v2 begins after the SizeAndHash - pSizeAndHash = (PBLOCK_SIZE_AND_HASH)pKeyMapping->pbFileData; - pIndexHeader = (PFILE_INDEX_HEADER_V2)(pSizeAndHash + 1); - if(pIndexHeader->IndexVersion != 0x07 || pIndexHeader->KeyIndex != KeyIndex) - return ERROR_BAD_FORMAT; + PFILE_DOWNLOAD_HEADER pFileHeader = (PFILE_DOWNLOAD_HEADER)pbFileData; - if(pIndexHeader->ExtraBytes != 0x00 || - pIndexHeader->SpanSizeBytes != 0x04 || - pIndexHeader->SpanOffsBytes != 0x05 || - pIndexHeader->KeyBytes != CASC_FILE_KEY_SIZE) + // Check the signature ('DL') and version + if(cbFileData < sizeof(FILE_DOWNLOAD_HEADER) || pFileHeader->Magic != FILE_MAGIC_DOWNLOAD || pFileHeader->Version > 3) return ERROR_BAD_FORMAT; - // Remember the sizes - pKeyMapping->ExtraBytes = pIndexHeader->ExtraBytes; - pKeyMapping->SpanSizeBytes = pIndexHeader->SpanSizeBytes; - pKeyMapping->SpanOffsBytes = pIndexHeader->SpanOffsBytes; - pKeyMapping->KeyBytes = pIndexHeader->KeyBytes; - pKeyMapping->SegmentBits = pIndexHeader->SegmentBits; - pKeyMapping->MaxFileOffset = pIndexHeader->MaxFileOffset; - - // Get the data position - FilePosition = (sizeof(BLOCK_SIZE_AND_HASH) + pSizeAndHash->cbBlockSize + 0x0F) & 0xFFFFFFF0; - if((FilePosition + 0x08) > pKeyMapping->cbFileData) + // Note that we don't support CKey sizes greater than 0x10 in the DOWNLOAD file + if(pFileHeader->EKeyLength > MD5_HASH_SIZE) return ERROR_BAD_FORMAT; - // Get the pointer to "size+hash" block - pSizeAndHash = (PBLOCK_SIZE_AND_HASH)(pKeyMapping->pbFileData + FilePosition); - FilePosition += 0x08; + // Capture the header version 1 + memset(&DlHeader, 0, sizeof(CASC_DOWNLOAD_HEADER)); + DlHeader.Magic = pFileHeader->Magic; + DlHeader.Version = pFileHeader->Version; + DlHeader.EKeyLength = pFileHeader->EKeyLength; + DlHeader.EntryHasChecksum = pFileHeader->EntryHasChecksum; + DlHeader.EntryCount = ConvertBytesToInteger_4(pFileHeader->EntryCount); + DlHeader.TagCount = ConvertBytesToInteger_2(pFileHeader->TagCount); + DlHeader.HeaderLength = FIELD_OFFSET(FILE_DOWNLOAD_HEADER, FlagByteSize); + DlHeader.EntryLength = DlHeader.EKeyLength + 5 + 1 + (DlHeader.EntryHasChecksum ? 4 : 0); + + // Capture header version 2 + if (pFileHeader->Version >= 2) + { + DlHeader.FlagByteSize = pFileHeader->FlagByteSize; + DlHeader.HeaderLength = FIELD_OFFSET(FILE_DOWNLOAD_HEADER, BasePriority); + DlHeader.EntryLength += DlHeader.FlagByteSize; - if((FilePosition + pSizeAndHash->cbBlockSize) > pKeyMapping->cbFileData) - return ERROR_BAD_FORMAT; - if(pSizeAndHash->cbBlockSize < sizeof(CASC_INDEX_ENTRY)) - return ERROR_BAD_FORMAT; + // Capture header version 3 + if (pFileHeader->Version >= 3) + { + DlHeader.BasePriority = pFileHeader->BasePriority; + DlHeader.HeaderLength = sizeof(FILE_DOWNLOAD_HEADER); + } + } - // Remember the array of file keys - pKeyMapping->pIndexEntries = (PCASC_INDEX_ENTRY)(pKeyMapping->pbFileData + FilePosition); - pKeyMapping->nIndexEntries = pSizeAndHash->cbBlockSize / sizeof(CASC_INDEX_ENTRY); - FilePosition += pSizeAndHash->cbBlockSize; + return ERROR_SUCCESS; +} - // Verify the integrity of the key array - for(DWORD i = 0; i < pKeyMapping->nIndexEntries; i++) - hashlittle2(pKeyMapping->pIndexEntries + i, sizeof(CASC_INDEX_ENTRY), &HashHigh, &HashLow); - if(HashHigh != pSizeAndHash->dwBlockHash) +int CaptureDownloadEntry(CASC_DOWNLOAD_HEADER & DlHeader, CASC_DOWNLOAD_ENTRY & DlEntry, LPBYTE pbFilePtr, LPBYTE pbFileEnd) +{ + // Check the range + if((pbFilePtr + DlHeader.EntryLength) >= pbFileEnd) return ERROR_BAD_FORMAT; + memset(&DlEntry, 0, sizeof(CASC_DOWNLOAD_ENTRY)); - // Align the data position up to next 0x1000 - FilePosition = ALIGN_TO_SIZE(FilePosition, 0x1000); - if(FilePosition > pKeyMapping->cbFileData) - return ERROR_BAD_FORMAT; + // Copy the EKey + memcpy(DlEntry.EKey, pbFilePtr, DlHeader.EKeyLength); + pbFilePtr += DlHeader.EKeyLength; - LastPartLength = pKeyMapping->cbFileData - FilePosition; - if(LastPartLength < 0x7800) - return ERROR_BAD_FORMAT; + // Convert the file size + DlEntry.EncodedSize = ConvertBytesToInteger_5(pbFilePtr); + pbFilePtr += 5; - pbLastPart = pKeyMapping->pbFileData + FilePosition; - pbLastPartEnd = pbLastPart + ((LastPartLength >> 0x09) << 0x09); + // Copy the file priority + DlEntry.Priority = pbFilePtr[0]; + pbFilePtr++; - while(pbLastPart < pbLastPartEnd) + // Copy the checksum + if(DlHeader.EntryHasChecksum) { - for(int i = 0; i < 0x1F8; i += 0x18) - { - PDWORD PtrLastPart = (PDWORD)pbLastPart; - if(PtrLastPart[0] == 0) - return ERROR_SUCCESS; - - HashLow = hashlittle(PtrLastPart + i, 0x13, 0) | 0x80000000; - if(HashLow != PtrLastPart[0]) - return ERROR_BAD_FORMAT; - } - - pbLastPart += 0x200; + DlEntry.Checksum = ConvertBytesToInteger_4(pbFilePtr); + pbFilePtr += 4; } + // Copy the flags + DlEntry.Flags = ConvertBytesToInteger_X(pbFilePtr, DlHeader.FlagByteSize); return ERROR_SUCCESS; } -static int VerifyAndParseKeyMapping(PCASC_MAPPING_TABLE pKeyMapping, DWORD KeyIndex) +int CaptureDownloadTag(CASC_DOWNLOAD_HEADER & DlHeader, CASC_TAG_ENTRY1 & DlTag, LPBYTE pbFilePtr, LPBYTE pbFileEnd) { - // Sanity checks - assert(pKeyMapping->pbFileData != NULL); - assert(pKeyMapping->cbFileData != 0); + LPBYTE pbSaveFilePtr = pbFilePtr; - // Check for CASC version 2 - if(IsCascIndexHeader_V2(pKeyMapping->pbFileData, pKeyMapping->cbFileData)) - return VerifyAndParseKeyMapping_V2(pKeyMapping, KeyIndex); + // Prepare the tag structure + memset(&DlTag, 0, sizeof(CASC_TAG_ENTRY1)); + DlTag.szTagName = (const char *)pbFilePtr; - // Check for CASC version 1 - if(IsCascIndexHeader_V1(pKeyMapping->pbFileData, pKeyMapping->cbFileData)) - return VerifyAndParseKeyMapping_V1(pKeyMapping, KeyIndex); + // Skip the tag string + while(pbFilePtr < pbFileEnd && pbFilePtr[0] != 0) + pbFilePtr++; + if(pbFilePtr >= pbFileEnd) + return ERROR_BAD_FORMAT; + + // Save the length of the tag name + DlTag.NameLength = (pbFilePtr - pbSaveFilePtr); + pbFilePtr++; - // Unknown CASC version - assert(false); - return ERROR_BAD_FORMAT; + // Get the tag value + if((pbFilePtr + sizeof(DWORD)) > pbFileEnd) + return ERROR_BAD_FORMAT; + DlTag.TagValue = ConvertBytesToInteger_2(pbFilePtr); + pbFilePtr += 2; + + // Get the bitmap + DlTag.Bitmap = pbFilePtr; + + // Get the bitmap length. + // If the bitmap is last in the list and it's shorter than declared, we make it shorter + DlTag.BitmapLength = GetTagBitmapLength(pbFilePtr, pbFileEnd, DlHeader.EntryCount); + + // Get the entry length + DlTag.TagLength = (pbFilePtr - pbSaveFilePtr) + DlTag.BitmapLength; + return ERROR_SUCCESS; } -static int LoadKeyMapping(PCASC_MAPPING_TABLE pKeyMapping, DWORD KeyIndex) +static int LoadDownloadManifest(TCascStorage * hs, CASC_DOWNLOAD_HEADER & DlHeader, LPBYTE pbFileData, LPBYTE pbFileEnd) { - TFileStream * pStream; - ULONGLONG FileSize = 0; + PCASC_TAG_ENTRY1 TagArray = NULL; + LPBYTE pbEntries = pbFileData + DlHeader.HeaderLength; + LPBYTE pbEntry = pbEntries; + LPBYTE pbTags = pbEntries + DlHeader.EntryLength * DlHeader.EntryCount; + LPBYTE pbTag = pbTags; + size_t nMaxNameLength = 0; + size_t nTagEntryLengh = 0; int nError = ERROR_SUCCESS; - // Sanity checks - assert(pKeyMapping->szFileName != NULL && pKeyMapping->szFileName[0] != 0); - - // Open the stream for read-only access and read the file - pStream = FileStream_OpenFile(pKeyMapping->szFileName, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE); - if(pStream != NULL) + // Does the storage support tags? + if(DlHeader.TagCount != 0) { - // Retrieve the file size - FileStream_GetSize(pStream, &FileSize); - if(0 < FileSize && FileSize <= 0x200000) + // Remember that we support tags + hs->dwFeatures |= CASC_FEATURE_TAGS; + + // Allocate space for the tag array + TagArray = CASC_ALLOC(CASC_TAG_ENTRY1, DlHeader.TagCount); + if(TagArray != NULL) { - // WoW6 actually reads THE ENTIRE file to memory - // Verified on Mac build (x64) - pKeyMapping->pbFileData = CASC_ALLOC(BYTE, (DWORD)FileSize); - pKeyMapping->cbFileData = (DWORD)FileSize; + // Get the longest tag name + for(DWORD i = 0; i < DlHeader.TagCount; i++) + { + if(CaptureDownloadTag(DlHeader, TagArray[i], pbTag, pbFileEnd) == ERROR_SUCCESS) + nMaxNameLength = CASCLIB_MAX(nMaxNameLength, TagArray[i].NameLength); + pbTag = pbTag + TagArray[i].TagLength; + } + + // Determine the tag entry length + nTagEntryLengh = FIELD_OFFSET(CASC_TAG_ENTRY2, szTagName) + nMaxNameLength; + nTagEntryLengh = ALIGN_TO_SIZE(nTagEntryLengh, 8); - // Load the data to memory and parse it - if(pKeyMapping->pbFileData != NULL) + // Load the tags into array in the storage structure + nError = hs->TagsArray.Create(nTagEntryLengh, DlHeader.TagCount); + if(nError == ERROR_SUCCESS) { - if(FileStream_Read(pStream, NULL, pKeyMapping->pbFileData, pKeyMapping->cbFileData)) + // Convert the array of CASC_DOWNLOAD_TAG1 to array of CASC_DOWNLOAD_TAG2 + for(DWORD i = 0; i < DlHeader.TagCount; i++) { - nError = VerifyAndParseKeyMapping(pKeyMapping, KeyIndex); + PCASC_TAG_ENTRY1 pSourceTag = &TagArray[i]; + PCASC_TAG_ENTRY2 pTargetTag; + + // Insert the tag to the array + pTargetTag = (PCASC_TAG_ENTRY2)hs->TagsArray.Insert(1); + if(pTargetTag == NULL) + { + nError = ERROR_NOT_ENOUGH_MEMORY; + break; + } + + // Copy the tag structure + memset(pTargetTag, 0, nTagEntryLengh); + memcpy(pTargetTag->szTagName, pSourceTag->szTagName, pSourceTag->NameLength); + pTargetTag->NameLength = pSourceTag->NameLength; + pTargetTag->TagValue = pSourceTag->TagValue; } } - else - nError = ERROR_NOT_ENOUGH_MEMORY; } else { - assert(false); - nError = ERROR_BAD_FORMAT; + nError = ERROR_NOT_ENOUGH_MEMORY; } - - // Close the file stream - FileStream_Close(pStream); } - else - nError = GetLastError(); - - return ERROR_SUCCESS; -} -static int CreateArrayOfIndexEntries(TCascStorage * hs) -{ - PCASC_MAP pMap; - DWORD TotalCount = 0; - int nError = ERROR_NOT_ENOUGH_MEMORY; + // Now parse all entries. For each entry, mark the corresponding tag bit in the EKey table + for(DWORD i = 0; i < DlHeader.EntryCount; i++) + { + CASC_DOWNLOAD_ENTRY DlEntry; + PCASC_CKEY_ENTRY pCKeyEntry; + size_t BitMaskOffset = (i / 8); + BYTE BitMaskBit = 0x80 >> (i % 8); - // Count the total number of files in the storage - for(size_t i = 0; i < CASC_INDEX_COUNT; i++) - TotalCount += hs->KeyMapping[i].nIndexEntries; + // Capture the download entry + if(CaptureDownloadEntry(DlHeader, DlEntry, pbEntry, pbFileEnd) != ERROR_SUCCESS) + break; - // Create the map of all index entries - pMap = Map_Create(TotalCount, CASC_FILE_KEY_SIZE, FIELD_OFFSET(CASC_INDEX_ENTRY, IndexKey)); - if(pMap != NULL) - { - // Put all index entries in the map - for(size_t i = 0; i < CASC_INDEX_COUNT; i++) + // Make sure we have the entry in CKey table + pCKeyEntry = FindCKeyEntry_EKey(hs, DlEntry.EKey); + if(pCKeyEntry != NULL) { - PCASC_INDEX_ENTRY pIndexEntry = hs->KeyMapping[i].pIndexEntries; - DWORD nIndexEntries = hs->KeyMapping[i].nIndexEntries; + ULONGLONG TagBit = 1; + size_t TagItemCount = hs->TagsArray.ItemCount(); - for(DWORD j = 0; j < nIndexEntries; j++) + // Supply the tag bits + for(size_t j = 0; j < TagItemCount; j++) { - // Insert the index entry to the map - // Note that duplicate entries will not be inserted to the map - // - // Duplicate entries in WoW-WOD build 18179: - // 9e dc a7 8f e2 09 ad d8 b7 (encoding file) - // f3 5e bb fb d1 2b 3f ef 8b - // c8 69 9f 18 a2 5e df 7e 52 - Map_InsertObject(pMap, pIndexEntry, pIndexEntry->IndexKey); - - // Move to the next entry - pIndexEntry++; + // Set the bit in the entry, if the tag for it is present + if((BitMaskOffset < TagArray[j].BitmapLength) && (TagArray[j].Bitmap[BitMaskOffset] & BitMaskBit)) + pCKeyEntry->TagBitMask |= TagBit; + + // 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; } - // Store the map to the storage handle - hs->pIndexEntryMap = pMap; - nError = ERROR_SUCCESS; + // Move to the next entry + pbEntry += DlHeader.EntryLength; } + // Free the tag array, if any + CASC_FREE(TagArray); + + // Remember the total file count + hs->TotalFiles = hs->CKeyArray.ItemCount(); return nError; } -static int CreateMapOfEncodingKeys(TCascStorage * hs, PFILE_ENCODING_SEGMENT pEncodingSegment, DWORD dwNumSegments) +static int LoadDownloadManifest(TCascStorage * hs) { - PCASC_ENCODING_ENTRY pEncodingEntry; - DWORD dwMaxEntries; + PCASC_CKEY_ENTRY pCKeyEntry = FindCKeyEntry_CKey(hs, hs->DownloadCKey.CKey); + LPBYTE pbDownloadFile = NULL; + DWORD cbDownloadFile = 0; int nError = ERROR_SUCCESS; - // Sanity check - assert(hs->pIndexEntryMap != NULL); - assert(hs->pEncodingMap == NULL); - - // Calculate the largest eventual number of encoding entries - // Add space for extra entries - dwMaxEntries = (dwNumSegments * CASC_ENCODING_SEGMENT_SIZE) / (sizeof(CASC_ENCODING_ENTRY) + MD5_HASH_SIZE); + // Inform the user about what we are doing + if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "Loading DOWNLOAD manifest", NULL, 0, 0)) + return ERROR_CANCELLED; - // Create the map of the encoding entries - hs->pEncodingMap = Map_Create(dwMaxEntries + CASC_EXTRA_FILES, MD5_HASH_SIZE, FIELD_OFFSET(CASC_ENCODING_ENTRY, EncodingKey)); - if(hs->pEncodingMap != NULL) + // Load the entire DOWNLOAD file to memory + pbDownloadFile = LoadInternalFileToMemory(hs, pCKeyEntry, &cbDownloadFile); + if(pbDownloadFile != NULL && cbDownloadFile != 0) { - LPBYTE pbStartOfSegment = (LPBYTE)(pEncodingSegment + dwNumSegments); + CASC_DOWNLOAD_HEADER DlHeader; - // Parse all segments - for(DWORD i = 0; i < dwNumSegments; i++) + // Capture the header of the DOWNLOAD file + nError = CaptureDownloadHeader(DlHeader, pbDownloadFile, cbDownloadFile); + if(nError == ERROR_SUCCESS) { - LPBYTE pbEncodingEntry = pbStartOfSegment; - LPBYTE pbEndOfSegment = pbStartOfSegment + CASC_ENCODING_SEGMENT_SIZE - sizeof(CASC_ENCODING_ENTRY) - MD5_HASH_SIZE; - - // Parse all encoding entries - while(pbEncodingEntry <= pbEndOfSegment) - { - // Get pointer to the encoding entry - pEncodingEntry = (PCASC_ENCODING_ENTRY)pbEncodingEntry; - if(pEncodingEntry->KeyCount == 0) - break; - - // Insert the pointer the array - Map_InsertObject(hs->pEncodingMap, pEncodingEntry, pEncodingEntry->EncodingKey); - - // Move to the next encoding entry - pbEncodingEntry += sizeof(CASC_ENCODING_ENTRY) + (pEncodingEntry->KeyCount * MD5_HASH_SIZE); - } - - // Move to the next segment - pbStartOfSegment += CASC_ENCODING_SEGMENT_SIZE; + // Parse the entire download manifest + nError = LoadDownloadManifest(hs, DlHeader, pbDownloadFile, pbDownloadFile + cbDownloadFile); } + + // Free the loaded manifest + CASC_FREE(pbDownloadFile); } - else - nError = ERROR_NOT_ENOUGH_MEMORY; + // If the DOWNLOAD manifest is not present, we won't abort the downloading process. return nError; } -static int LoadIndexFiles(TCascStorage * hs) +//----------------------------------------------------------------------------- +// INSTALL manifest. This is a replacement for ROOT, if loading ROOT fails +// https://wowdev.wiki/TACT#Install_manifest + +static int LoadInstallManifest(TCascStorage * hs) { - DWORD IndexArray[CASC_INDEX_COUNT]; - DWORD OldIndexArray[CASC_INDEX_COUNT]; - int nError; - int i; + PCASC_CKEY_ENTRY pCKeyEntry = FindCKeyEntry_CKey(hs, hs->InstallCKey.CKey); + LPBYTE pbInstallFile = NULL; + DWORD cbInstallFile = 0; + int nError = ERROR_SUCCESS; - // Scan all index files - memset(IndexArray, 0, sizeof(IndexArray)); - memset(OldIndexArray, 0, sizeof(OldIndexArray)); - nError = ScanIndexDirectory(hs->szIndexPath, IndexDirectory_OnFileFound, IndexArray, OldIndexArray, hs); - if(nError == ERROR_SUCCESS) + // Inform the user about what we are doing + if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "Loading INSTALL manifest", NULL, 0, 0)) + return ERROR_CANCELLED; + + // Load the entire DOWNLOAD file to memory + pbInstallFile = LoadInternalFileToMemory(hs, pCKeyEntry, &cbInstallFile); + if (pbInstallFile != NULL && cbInstallFile != 0) { - // Load each index file - for(i = 0; i < CASC_INDEX_COUNT; i++) - { - hs->KeyMapping[i].szFileName = CreateIndexFileName(hs, i, IndexArray[i]); - if(hs->KeyMapping[i].szFileName != NULL) - { - nError = LoadKeyMapping(&hs->KeyMapping[i], i); - if(nError != ERROR_SUCCESS) - break; - } - } + nError = RootHandler_CreateInstall(hs, pbInstallFile, cbInstallFile); + CASC_FREE(pbInstallFile); } - - // Now we need to build the map of the index entries - if(nError == ERROR_SUCCESS) + else { - nError = CreateArrayOfIndexEntries(hs); + nError = GetLastError(); } return nError; } -static LPBYTE LoadEncodingFileToMemory(HANDLE hFile, DWORD * pcbEncodingFile) +static bool InsertWellKnownFile(TCascStorage * hs, const char * szFileName, CASC_CKEY_ENTRY & FakeCKeyEntry) { - CASC_ENCODING_HEADER EncodingHeader; - LPBYTE pbEncodingFile = NULL; - DWORD cbEncodingFile = 0; - DWORD dwSegmentPos = 0; - DWORD dwNumSegments = 0; - DWORD dwBytesRead = 0; - int nError = ERROR_BAD_FORMAT; + PCASC_CKEY_ENTRY pCKeyEntry = NULL; - // Read the encoding header - CascReadFile(hFile, &EncodingHeader, sizeof(CASC_ENCODING_HEADER), &dwBytesRead); - if(dwBytesRead == sizeof(CASC_ENCODING_HEADER)) + // We need to find the CKey entry in the central array + if(FakeCKeyEntry.Flags & CASC_CE_HAS_CKEY) { - // Check the version and sizes - if(EncodingHeader.Version != 0x01 || EncodingHeader.ChecksumSizeA != MD5_HASH_SIZE || EncodingHeader.ChecksumSizeB != MD5_HASH_SIZE) + // Did we find anything? + pCKeyEntry = FindCKeyEntry_CKey(hs, FakeCKeyEntry.CKey); + if(pCKeyEntry != NULL) { - assert(false); - return 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); + return true; } - - // Get the number of segments - dwNumSegments = ConvertBytesToInteger_4(EncodingHeader.Entries_TableA); - dwSegmentPos = ConvertBytesToInteger_4(EncodingHeader.Size_StringTable1); - if(EncodingHeader.Magic[0] == 'E' && EncodingHeader.Magic[1] == 'N' && dwSegmentPos != 0 && dwNumSegments != 0) - nError = ERROR_SUCCESS; - } - - // Calculate and allocate space for the entire file - if(nError == ERROR_SUCCESS) - { - cbEncodingFile = sizeof(CASC_ENCODING_HEADER) + - dwSegmentPos + - dwNumSegments * (sizeof(FILE_ENCODING_SEGMENT) + CASC_ENCODING_SEGMENT_SIZE); - pbEncodingFile = CASC_ALLOC(BYTE, cbEncodingFile); - if(pbEncodingFile == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; } - // If all went OK, we load the entire file to memory - if(nError == ERROR_SUCCESS) - { - // Copy the header itself - memcpy(pbEncodingFile, &EncodingHeader, sizeof(CASC_ENCODING_HEADER)); - - // Read the rest of the data - CascReadFile(hFile, pbEncodingFile + sizeof(CASC_ENCODING_HEADER), cbEncodingFile - sizeof(CASC_ENCODING_HEADER), &dwBytesRead); - if(dwBytesRead != (cbEncodingFile - sizeof(CASC_ENCODING_HEADER))) - nError = ERROR_FILE_CORRUPT; - } - - // Give the loaded file length - if(pcbEncodingFile != NULL) - *pcbEncodingFile = cbEncodingFile; - return pbEncodingFile; + return false; } -static LPBYTE LoadRootFileToMemory(HANDLE hFile, DWORD * pcbRootFile) +static int LoadBuildManifest(TCascStorage * hs, DWORD dwLocaleMask) { + PCASC_CKEY_ENTRY pCKeyEntry; + PDWORD FileSignature; LPBYTE pbRootFile = NULL; DWORD cbRootFile = 0; - DWORD dwBytesRead = 0; - int nError = ERROR_SUCCESS; + int nError = ERROR_BAD_FORMAT; - // Retrieve the size of the ROOT file - cbRootFile = CascGetFileSize(hFile, NULL); - if(cbRootFile == 0) - nError = ERROR_BAD_FORMAT; + // Sanity checks + assert(hs->CKeyMap.IsInitialized() == true); + assert(hs->pRootHandler == NULL); - // Allocate space for the entire file - if(nError == ERROR_SUCCESS) - { - pbRootFile = CASC_ALLOC(BYTE, cbRootFile); - if(pbRootFile == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; - } + // 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; - // If all went OK, we load the entire file to memory - if(nError == ERROR_SUCCESS) + // Prioritize the VFS root over legacy ROOT file + pCKeyEntry = (hs->VfsRoot.ContentSize != CASC_INVALID_SIZE) ? &hs->VfsRoot : &hs->RootFile; + pCKeyEntry = FindCKeyEntry_CKey(hs, pCKeyEntry->CKey); + + // Inform the user about what we are doing + if(hs->PfnCallback && hs->PfnCallback(hs->PtrCallbackParam, "Loading ROOT manifest", NULL, 0, 0)) + return ERROR_CANCELLED; + + // Load the entire ROOT file to memory + pbRootFile = LoadInternalFileToMemory(hs, pCKeyEntry, &cbRootFile); + if(pbRootFile != NULL) { - // Read the entire file to memory - CascReadFile(hFile, pbRootFile, cbRootFile, &dwBytesRead); - if(dwBytesRead != cbRootFile) - nError = ERROR_FILE_CORRUPT; - } + // Ignore ROOT files that contain just a MD5 hash + if(cbRootFile > MD5_STRING_SIZE) + { + // Check the type of the ROOT file + FileSignature = (PDWORD)pbRootFile; + switch(FileSignature[0]) + { + case CASC_MNDX_ROOT_SIGNATURE: + nError = RootHandler_CreateMNDX(hs, pbRootFile, cbRootFile); + break; - // Give the loaded file length - if(pcbRootFile != NULL) - *pcbRootFile = cbRootFile; - return pbRootFile; -} + case CASC_DIABLO3_ROOT_SIGNATURE: + nError = RootHandler_CreateDiablo3(hs, pbRootFile, cbRootFile); + break; -static int LoadEncodingFile(TCascStorage * hs) -{ - PFILE_ENCODING_SEGMENT pEncodingSegment; - QUERY_KEY EncodingKey; - LPBYTE pbStartOfSegment; - LPBYTE pbEncodingFile = NULL; - HANDLE hFile = NULL; - DWORD cbEncodingFile = 0; - DWORD dwNumSegments = 0; - DWORD dwSegmentsPos = 0; - int nError = ERROR_SUCCESS; + case CASC_TVFS_ROOT_SIGNATURE: + nError = RootHandler_CreateTVFS(hs, pbRootFile, cbRootFile); + break; - // Open the encoding file - EncodingKey.pbData = hs->EncodingKey.pbData + MD5_HASH_SIZE; - EncodingKey.cbData = MD5_HASH_SIZE; - if(!CascOpenFileByIndexKey((HANDLE)hs, &EncodingKey, 0, &hFile)) - nError = GetLastError(); + case CASC_WOW82_ROOT_SIGNATURE: + nError = RootHandler_CreateWoW(hs, pbRootFile, cbRootFile, dwLocaleMask); + break; - // Load the entire ENCODING file to memory - if(nError == ERROR_SUCCESS) - { - // Load the necessary part of the ENCODING file to memory - pbEncodingFile = LoadEncodingFileToMemory(hFile, &cbEncodingFile); - if(pbEncodingFile == NULL || cbEncodingFile <= sizeof(CASC_ENCODING_HEADER)) - nError = ERROR_FILE_CORRUPT; + default: + + // + // Each of these handler creators must verify their format first. + // If the format was not recognized, they need to return ERROR_BAD_FORMAT + // + + nError = RootHandler_CreateOverwatch(hs, pbRootFile, cbRootFile); + if(nError == ERROR_BAD_FORMAT) + { + nError = RootHandler_CreateStarcraft1(hs, pbRootFile, cbRootFile); + if(nError == ERROR_BAD_FORMAT) + { + nError = RootHandler_CreateWoW(hs, pbRootFile, cbRootFile, dwLocaleMask); + } + } + break; + } + } - // Close the encoding file - CascCloseFile(hFile); + // Free the root file + CASC_FREE(pbRootFile); } - - // Verify all encoding segments - if(nError == ERROR_SUCCESS) + else { - PCASC_ENCODING_HEADER pEncodingHeader = (PCASC_ENCODING_HEADER)pbEncodingFile; - - // Convert size and offset - dwNumSegments = ConvertBytesToInteger_4(pEncodingHeader->Entries_TableA); - dwSegmentsPos = ConvertBytesToInteger_4(pEncodingHeader->Size_StringTable1); + nError = GetLastError(); + } - // Store the encoding file to the CASC storage - hs->EncodingFile.pbData = pbEncodingFile; - hs->EncodingFile.cbData = cbEncodingFile; + return nError; +} - // Allocate the array of encoding segments - pEncodingSegment = (PFILE_ENCODING_SEGMENT)(pbEncodingFile + sizeof(CASC_ENCODING_HEADER) + dwSegmentsPos); - pbStartOfSegment = (LPBYTE)(pEncodingSegment + dwNumSegments); +static int InitializeLocalDirectories(TCascStorage * hs, const TCHAR * szPath) +{ + TCHAR * szWorkPath; + int nError = ERROR_NOT_ENOUGH_MEMORY; - // Go through all encoding segments and verify them - for(DWORD i = 0; i < dwNumSegments; i++) + // Find the root directory of the storage. The root directory + // is the one with ".build.info" or ".build.db". + szWorkPath = CascNewStr(szPath); + if(szWorkPath != NULL) + { + // Get the length and go up until we find the ".build.info" or ".build.db" + for(;;) { - PCASC_ENCODING_ENTRY pEncodingEntry = (PCASC_ENCODING_ENTRY)pbStartOfSegment; - - // Check if there is enough space in the buffer - if((pbStartOfSegment + CASC_ENCODING_SEGMENT_SIZE) > (pbEncodingFile + cbEncodingFile)) + // Is this a game directory? + nError = CheckGameDirectory(hs, szWorkPath); + if(nError == ERROR_SUCCESS) { - nError = ERROR_FILE_CORRUPT; + nError = ERROR_SUCCESS; break; } - // Check the hash of the entire segment - // Note that verifying takes considerable time of the storage loading -// if(!VerifyDataBlockHash(pbStartOfSegment, CASC_ENCODING_SEGMENT_SIZE, pEncodingSegment->SegmentHash)) -// { -// nError = ERROR_FILE_CORRUPT; -// break; -// } - - // Check if the encoding key matches with the expected first value - if(memcmp(pEncodingEntry->EncodingKey, pEncodingSegment->FirstEncodingKey, MD5_HASH_SIZE)) + // Cut one path part + if(!CutLastPathPart(szWorkPath)) { - nError = ERROR_FILE_CORRUPT; + nError = ERROR_FILE_NOT_FOUND; break; } + } + + // Find the index directory + if (nError == ERROR_SUCCESS) + { + // First, check for more common "data" subdirectory + if ((hs->szIndexPath = CheckForIndexDirectory(hs, _T("data"))) != NULL) + nError = ERROR_SUCCESS; + + // Second, try the "darch" subdirectory (older builds of HOTS - Alpha) + else if ((hs->szIndexPath = CheckForIndexDirectory(hs, _T("darch"))) != NULL) + nError = ERROR_SUCCESS; - // Move to the next segment - pbStartOfSegment += CASC_ENCODING_SEGMENT_SIZE; - pEncodingSegment++; + else + nError = ERROR_FILE_NOT_FOUND; } - } - // Create the map of the encoding keys - // Note that the array of encoding keys is already sorted - no need to sort it - if(nError == ERROR_SUCCESS) - { - pEncodingSegment = (PFILE_ENCODING_SEGMENT)(pbEncodingFile + sizeof(CASC_ENCODING_HEADER) + dwSegmentsPos); - nError = CreateMapOfEncodingKeys(hs, pEncodingSegment, dwNumSegments); + // Free the work path buffer + CASC_FREE(szWorkPath); } return nError; } -static int LoadRootFile(TCascStorage * hs, DWORD dwLocaleMask) +static int InitializeOnlineDirectories(TCascStorage * hs, LPCTSTR szLocalCache, LPCSTR szCodeName, LPCSTR szRegion) { - PDWORD FileSignature; - HANDLE hFile = NULL; - LPBYTE pbRootFile = NULL; - DWORD cbRootFile = 0; - int nError = ERROR_SUCCESS; + TCHAR szCodeNameT[0x40]; - // Sanity checks - assert(hs->pEncodingMap != NULL); - 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; - - // Load the entire ROOT file to memory - if(!CascOpenFileByEncodingKey((HANDLE)hs, &hs->RootKey, 0, &hFile)) - nError = GetLastError(); - - // Load the entire file to memory - if(nError == ERROR_SUCCESS) + CascStrCopy(szCodeNameT, _countof(szCodeNameT), szCodeName); + hs->szRootPath = CombinePath(szLocalCache, szCodeNameT); + if (hs->szRootPath != NULL) { - pbRootFile = LoadRootFileToMemory(hFile, &cbRootFile); - CascCloseFile(hFile); + // Create the name of the build file + hs->szBuildFile = CombinePath(hs->szRootPath, _T("versions")); + if(hs->szBuildFile != NULL) + { + hs->szCodeName = CascNewStr(szCodeNameT); + hs->szRegion = CascNewStr(szRegion); + if(hs->szCodeName != NULL) + { + hs->BuildFileType = CascVersionsDb; + hs->dwFeatures |= CASC_FEATURE_ONLINE; + return ERROR_SUCCESS; + } + } } - // Check if the version of the ROOT file - if(nError == ERROR_SUCCESS && pbRootFile != NULL) - { - FileSignature = (PDWORD)pbRootFile; - switch(FileSignature[0]) - { - case CASC_MNDX_ROOT_SIGNATURE: - nError = RootHandler_CreateMNDX(hs, pbRootFile, cbRootFile); - break; + return ERROR_NOT_ENOUGH_MEMORY; +} - case CASC_DIABLO3_ROOT_SIGNATURE: - nError = RootHandler_CreateDiablo3(hs, pbRootFile, cbRootFile); - break; +static DWORD GetStorageTotalFileCount(TCascStorage * hs) +{ + PCASC_CKEY_ENTRY pCKeyEntry; + size_t nItemCount = hs->CKeyArray.ItemCount(); + DWORD TotalFileCount = 0; - case CASC_OVERWATCH_ROOT_SIGNATURE: - nError = RootHandler_CreateOverwatch(hs, pbRootFile, cbRootFile); - break; + for(size_t i = 0; i < nItemCount; i++) + { + if((pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.ItemAt(i)) != NULL) + { + if((pCKeyEntry->Flags & CASC_CE_FOLDER_ENTRY) == 0) + { + // 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 + DWORD RefCount = (pCKeyEntry->RefCount > 0) ? pCKeyEntry->RefCount : 1; - default: - if(IsRootFile_Starcraft1(pbRootFile, cbRootFile)) - nError = RootHandler_CreateSC1(hs, pbRootFile, cbRootFile); - else - nError = RootHandler_CreateWoW6(hs, pbRootFile, cbRootFile, dwLocaleMask); - break; + // Add the number of references to the total file count + TotalFileCount += RefCount; + } } } - // Insert entry for the - if(nError == ERROR_SUCCESS) - { - InsertExtraFile(hs, "ENCODING", &hs->EncodingKey); - InsertExtraFile(hs, "ROOT", &hs->RootKey); - InsertExtraFile(hs, "DOWNLOAD", &hs->DownloadKey); - InsertExtraFile(hs, "INSTALL", &hs->InstallKey); - } + return TotalFileCount; +} -#ifdef _DEBUG - if(nError == ERROR_SUCCESS) +static bool GetStorageProduct(TCascStorage * hs, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded) +{ + PCASC_STORAGE_PRODUCT pProductInfo; + + // Verify whether we have enough space in the buffer + pProductInfo = (PCASC_STORAGE_PRODUCT)ProbeOutputBuffer(pvStorageInfo, cbStorageInfo, sizeof(CASC_STORAGE_PRODUCT), pcbLengthNeeded); + if(pProductInfo != NULL) { - //RootFile_Dump(hs, - // pbRootFile, - // cbRootFile, - // _T("\\casc_root_%build%.txt"), - // _T("\\Ladik\\Appdir\\CascLib\\listfile\\listfile-wow6.txt"), - // DUMP_LEVEL_INDEX_ENTRIES); + pProductInfo->szProductName = hs->szProductName; + pProductInfo->dwBuildNumber = hs->dwBuildNumber; + pProductInfo->Product = hs->Product; } -#endif - // Free the root file - CASC_FREE(pbRootFile); - return nError; + return (pProductInfo != NULL); } -static TCascStorage * FreeCascStorage(TCascStorage * hs) +static bool GetStorageTags(TCascStorage * hs, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded) { - size_t i; + PCASC_STORAGE_TAGS pTags; + PCASC_TAG_ENTRY2 pTag; + char * szNameBuffer; + size_t cbMinLength; - if(hs != NULL) + // Does the storage support tags? + if(hs->TagsArray.IsInitialized() == false) { - // Free the root handler - if(hs->pRootHandler != NULL) - RootHandler_Close(hs->pRootHandler); - hs->pRootHandler = NULL; - - // Free the extra encoding entries - Array_Free(&hs->ExtraEntries); - - // Free the pointers to file entries - if(hs->pEncodingMap != NULL) - Map_Free(hs->pEncodingMap); - if(hs->EncodingFile.pbData != NULL) - CASC_FREE(hs->EncodingFile.pbData); - if(hs->pIndexEntryMap != NULL) - Map_Free(hs->pIndexEntryMap); - - // Close all data files - for(i = 0; i < CASC_MAX_DATA_FILES; i++) - { - if(hs->DataFileArray[i] != NULL) - { - FileStream_Close(hs->DataFileArray[i]); - hs->DataFileArray[i] = NULL; - } - } + SetLastError(ERROR_NOT_SUPPORTED); + return false; + } + + // Calculate the length of the tags + cbMinLength = FIELD_OFFSET(CASC_STORAGE_TAGS, Tags) + hs->TagsArray.ItemCount() * sizeof(CASC_STORAGE_TAG); + szNameBuffer = (char *)pvStorageInfo + cbMinLength; + + // Also include the tag length + for(size_t i = 0; i < hs->TagsArray.ItemCount(); i++) + { + pTag = (PCASC_TAG_ENTRY2)hs->TagsArray.ItemAt(i); + cbMinLength = cbMinLength + pTag->NameLength + 1; + } - // Close all key mappings - for(i = 0; i < CASC_INDEX_COUNT; i++) + // Verify whether we have enough space in the buffer + pTags = (PCASC_STORAGE_TAGS)ProbeOutputBuffer(pvStorageInfo, cbStorageInfo, cbMinLength, pcbLengthNeeded); + if(pTags != NULL) + { + // Fill the output structure + pTags->TagCount = hs->TagsArray.ItemCount(); + pTags->Reserved = 0; + + // Copy the tags + for(size_t i = 0; i < hs->TagsArray.ItemCount(); i++) { - if(hs->KeyMapping[i].szFileName != NULL) - CASC_FREE(hs->KeyMapping[i].szFileName); - if(hs->KeyMapping[i].pbFileData != NULL) - CASC_FREE(hs->KeyMapping[i].pbFileData); - hs->KeyMapping[i].pIndexEntries = NULL; + // Get the source tag + pTag = (PCASC_TAG_ENTRY2)hs->TagsArray.ItemAt(i); + + // Fill the target tag + pTags->Tags[i].szTagName = szNameBuffer; + pTags->Tags[i].TagNameLength = (DWORD)pTag->NameLength; + pTags->Tags[i].TagValue = pTag->TagValue; + + // Copy the tag name + memcpy(szNameBuffer, pTag->szTagName, pTag->NameLength); + szNameBuffer[pTag->NameLength] = 0; + szNameBuffer = szNameBuffer + pTag->NameLength + 1; } - - // Free the file paths - if(hs->szRootPath != NULL) - CASC_FREE(hs->szRootPath); - if(hs->szDataPath != NULL) - CASC_FREE(hs->szDataPath); - if(hs->szBuildFile != NULL) - CASC_FREE(hs->szBuildFile); - if(hs->szIndexPath != NULL) - CASC_FREE(hs->szIndexPath); - if(hs->szUrlPath != NULL) - CASC_FREE(hs->szUrlPath); - - // Free the blobs - FreeCascBlob(&hs->CdnConfigKey); - FreeCascBlob(&hs->CdnBuildKey); - FreeCascBlob(&hs->ArchivesGroup); - FreeCascBlob(&hs->ArchivesKey); - FreeCascBlob(&hs->PatchArchivesKey); - FreeCascBlob(&hs->PatchArchivesGroup); - FreeCascBlob(&hs->RootKey); - FreeCascBlob(&hs->PatchKey); - FreeCascBlob(&hs->DownloadKey); - FreeCascBlob(&hs->InstallKey); - FreeCascBlob(&hs->EncodingKey); - - // Free the storage structure - hs->szClassName = NULL; - CASC_FREE(hs); } - return NULL; + return (pTags != NULL); } -//----------------------------------------------------------------------------- -// Public functions - -bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwLocaleMask, HANDLE * phStorage) +static int LoadCascStorage(TCascStorage * hs, DWORD dwLocaleMask) { - TCascStorage * hs; int nError = ERROR_SUCCESS; - // Allocate the storage structure - hs = (TCascStorage *)CASC_ALLOC(TCascStorage, 1); - if(hs == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; - - // Load the storage configuration - if(nError == ERROR_SUCCESS) + // For online storages, we need to load CDN servers + if ((nError == ERROR_SUCCESS) && (hs->dwFeatures & CASC_FEATURE_ONLINE)) { - // Prepare the base storage parameters - memset(hs, 0, sizeof(TCascStorage)); - hs->szClassName = "TCascStorage"; - hs->dwFileBeginDelta = 0xFFFFFFFF; - hs->dwDefaultLocale = CASC_LOCALE_ENUS | CASC_LOCALE_ENGB; - hs->dwRefCount = 1; - nError = InitializeCascDirectories(hs, szDataPath); + nError = LoadCdnsInfo(hs); } - // Now we need to load the root file so we know the config files + // Now, load the main storage file ".build.info" (or ".build.db" in old storages) if(nError == ERROR_SUCCESS) { nError = LoadBuildInfo(hs); } - // Load the index files + // If the .build.info OR .build.db file has been loaded, + // proceed with loading the CDN config file + if (nError == ERROR_SUCCESS) + { + nError = LoadCdnConfigFile(hs); + } + + // Proceed with loading the CDN build file + if (nError == ERROR_SUCCESS) + { + nError = LoadCdnBuildFile(hs); + } + + // Load the index files. Store information from the index files to the CKeyArray. if(nError == ERROR_SUCCESS) { nError = LoadIndexFiles(hs); } - // Load the index files + // Load the ENCODING manifest if(nError == ERROR_SUCCESS) { - nError = LoadEncodingFile(hs); + nError = LoadEncodingManifest(hs); } - // Initialize the dynamic array for extra files - // Reserve space for 0x20 encoding entries + // We need to load the DOWNLOAD manifest. This will give us the information about + // how many physical files are in the storage, so we can start building file tables if(nError == ERROR_SUCCESS) { - nError = Array_Create(&hs->ExtraEntries, CASC_ENCODING_ENTRY_1, CASC_EXTRA_FILES); + nError = LoadDownloadManifest(hs); } - // Load the index files + // Load the build manifest ("ROOT" file) if(nError == ERROR_SUCCESS) { - nError = LoadRootFile(hs, dwLocaleMask); + // If we fail to load the ROOT file, we take the file names from the INSTALL manifest + nError = LoadBuildManifest(hs, dwLocaleMask); + if (nError != ERROR_SUCCESS) + { + nError = LoadInstallManifest(hs); + } } - // If something failed, free the storage and return - if(nError != ERROR_SUCCESS) + // Insert entries for files with well-known names. Their CKeys are in the BUILD file + // See https://wowdev.wiki/TACT#Encoding_table for their list + if (nError == ERROR_SUCCESS) + { + InsertWellKnownFile(hs, "ENCODING", hs->EncodingCKey); + InsertWellKnownFile(hs, "DOWNLOAD", hs->DownloadCKey); + InsertWellKnownFile(hs, "INSTALL", hs->InstallCKey); + InsertWellKnownFile(hs, "PATCH", hs->PatchFile); + InsertWellKnownFile(hs, "ROOT", hs->RootFile); + InsertWellKnownFile(hs, "SIZE", hs->SizeFile); + + // Also reset the total file count. CascGetStorageInfo will update it on next call + hs->TotalFiles = 0; + } + + // Load the encryption keys + if (nError == ERROR_SUCCESS) + { + nError = CascLoadEncryptionKeys(hs); + } + + return nError; +} + +//----------------------------------------------------------------------------- +// Public functions + +void WINAPI CascSetProgressCallback(PFNPROGRESSCALLBACK PtrUserCallback, void * PtrUserParam) +{ + PfnProgressCallback = PtrUserCallback; + PtrProgressParam = PtrUserParam; +} + +bool WINAPI CascOpenStorage(LPCTSTR szPath, DWORD dwLocaleMask, HANDLE * phStorage) +{ + TCascStorage * hs; + int nError = ERROR_NOT_ENOUGH_MEMORY; + + // Allocate the storage structure + if((hs = new TCascStorage()) != NULL) { - hs = FreeCascStorage(hs); - SetLastError(nError); + // Setup the directories + nError = InitializeLocalDirectories(hs, szPath); + if(nError == ERROR_SUCCESS) + { + nError = LoadCascStorage(hs, dwLocaleMask); + if(nError == ERROR_SUCCESS) + { + *phStorage = (HANDLE)hs; + return true; + } + } + + // Delete the so-far-allocated storage + hs = hs->Release(); } - *phStorage = (HANDLE)hs; - return (nError == ERROR_SUCCESS); + // Failed + SetLastError(nError); + *phStorage = NULL; + return false; +} + +// Allows to browse an online CDN storage +// szLocalCache: Local folder, where the online file will be cached. +// szCodeName: Product code name, e.g. "agent" for Battle.net Agent. More info: https://wowdev.wiki/TACT#Products +// szRegion: The region (or subvariant) of the product. Corresponds to the first column of the "versions" file. +bool WINAPI CascOpenOnlineStorage(LPCTSTR szLocalCache, LPCSTR szCodeName, LPCSTR szRegion, DWORD dwLocaleMask, HANDLE * phStorage) +{ + TCascStorage * hs; + int nError = ERROR_NOT_ENOUGH_MEMORY; + + // Allocate the storage structure + if((hs = new TCascStorage()) != NULL) + { + // Setup the directories + nError = InitializeOnlineDirectories(hs, szLocalCache, szCodeName, szRegion); + if(nError == ERROR_SUCCESS) + { + nError = LoadCascStorage(hs, dwLocaleMask); + if(nError == ERROR_SUCCESS) + { + *phStorage = (HANDLE)hs; + return true; + } + } + + // Delete the so-far-allocated storage + hs = hs->Release(); + } + + // Failed + SetLastError(nError); + *phStorage = NULL; + return false; } bool WINAPI CascGetStorageInfo( @@ -1120,10 +1240,11 @@ bool WINAPI CascGetStorageInfo( size_t * pcbLengthNeeded) { TCascStorage * hs; + PDWORD PtrOutputValue; DWORD dwInfoValue = 0; // Verify the storage handle - hs = IsValidStorageHandle(hStorage); + hs = TCascStorage::IsValid(hStorage); if(hs == NULL) { SetLastError(ERROR_INVALID_HANDLE); @@ -1133,45 +1254,43 @@ bool WINAPI CascGetStorageInfo( // Differentiate between info classes switch(InfoClass) { - case CascStorageFileCount: - dwInfoValue = (DWORD)hs->pIndexEntryMap->ItemCount; - break; - - case CascStorageFeatures: - dwInfoValue |= (hs->pRootHandler->dwRootFlags & ROOT_FLAG_HAS_NAMES) ? CASC_FEATURE_LISTFILE : 0; + case CascStorageLocalFileCount: + dwInfoValue = (DWORD)hs->LocalFiles; break; - case CascStorageGameInfo: - dwInfoValue = hs->dwGameInfo; + case CascStorageTotalFileCount: + if(hs->TotalFiles == 0) + hs->TotalFiles = GetStorageTotalFileCount(hs); + dwInfoValue = (DWORD)hs->TotalFiles; break; - case CascStorageGameBuild: - dwInfoValue = hs->dwBuildNumber; + case CascStorageFeatures: + dwInfoValue = hs->dwFeatures | hs->pRootHandler->GetFeatures(); break; case CascStorageInstalledLocales: dwInfoValue = hs->dwDefaultLocale; break; + case CascStorageProduct: + return GetStorageProduct(hs, pvStorageInfo, cbStorageInfo, pcbLengthNeeded); + + case CascStorageTags: + return GetStorageTags(hs, pvStorageInfo, cbStorageInfo, pcbLengthNeeded); + default: SetLastError(ERROR_INVALID_PARAMETER); return false; } // - // Return the required DWORD value + // Default: return a 32-bit unsigned value // - if(cbStorageInfo < sizeof(DWORD)) - { - *pcbLengthNeeded = sizeof(DWORD); - SetLastError(ERROR_INSUFFICIENT_BUFFER); - return false; - } - - // Give the number of files - *(PDWORD)pvStorageInfo = dwInfoValue; - return true; + PtrOutputValue = (PDWORD)ProbeOutputBuffer(pvStorageInfo, cbStorageInfo, sizeof(DWORD), pcbLengthNeeded); + if(PtrOutputValue != NULL) + PtrOutputValue[0] = dwInfoValue; + return (PtrOutputValue != NULL); } bool WINAPI CascCloseStorage(HANDLE hStorage) @@ -1179,7 +1298,7 @@ bool WINAPI CascCloseStorage(HANDLE hStorage) TCascStorage * hs; // Verify the storage handle - hs = IsValidStorageHandle(hStorage); + hs = TCascStorage::IsValid(hStorage); if(hs == NULL) { SetLastError(ERROR_INVALID_PARAMETER); @@ -1187,14 +1306,6 @@ bool WINAPI CascCloseStorage(HANDLE hStorage) } // Only free the storage if the reference count reaches 0 - if(hs->dwRefCount == 1) - { - FreeCascStorage(hs); - return true; - } - - // Just decrement number of references - hs->dwRefCount--; + hs->Release(); return true; } - diff --git a/dep/CascLib/src/CascPort.h b/dep/CascLib/src/CascPort.h index 5d9a7104f7b..87a2f2fddbd 100644 --- a/dep/CascLib/src/CascPort.h +++ b/dep/CascLib/src/CascPort.h @@ -37,12 +37,20 @@ #define WIN32_LEAN_AND_MEAN #endif +//#pragma warning(disable:4995) // warning C4995: 'sprintf': name was marked as #pragma deprecated + #include <tchar.h> #include <assert.h> + #include <intrin.h> // Support for intrinsic functions #include <ctype.h> + #include <io.h> #include <stdio.h> + #include <stdlib.h> + #include <direct.h> + #include <malloc.h> #include <windows.h> #include <wininet.h> + #include <strsafe.h> #include <sys/types.h> #define PLATFORM_LITTLE_ENDIAN @@ -52,8 +60,10 @@ #define PLATFORM_32BIT #endif - #define PATH_SEPARATOR '\\' - #define CREATE_DIRECTORY(name) CreateDirectory(name, NULL); + #define PATH_SEP_CHAR '\\' + #define PATH_SEP_STRING "\\" + + #pragma intrinsic(memcmp, memcpy) #define PLATFORM_WINDOWS #define PLATFORM_DEFINED // The platform is known now @@ -75,6 +85,8 @@ #include <dirent.h> #include <errno.h> #include <stddef.h> + #include <string.h> + #include <cassert> // Support for PowerPC on Max OS X #if (__ppc__ == 1) || (__POWERPC__ == 1) || (_ARCH_PPC == 1) @@ -90,9 +102,9 @@ #define PLATFORM_LITTLE_ENDIAN #endif - #define PATH_SEPARATOR '/' - #define CREATE_DIRECTORY(name) mkdir(name, 0755) - + #define PATH_SEP_CHAR '/' + #define PATH_SEP_STRING "/" + #define PLATFORM_MAC #define PLATFORM_DEFINED // The platform is known now @@ -103,7 +115,6 @@ // Assumption: we are not on Windows nor Macintosh, so this must be linux *grin* #if !defined(PLATFORM_DEFINED) - #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> @@ -117,12 +128,13 @@ #include <stdarg.h> #include <string.h> #include <ctype.h> + #include <wchar.h> #include <assert.h> #include <errno.h> - #define PATH_SEPARATOR '/' - #define CREATE_DIRECTORY(name) mkdir(name, 0755) - + #define PATH_SEP_CHAR '/' + #define PATH_SEP_STRING "/" + #define PLATFORM_LITTLE_ENDIAN #define PLATFORM_LINUX #define PLATFORM_DEFINED @@ -145,20 +157,19 @@ typedef unsigned short USHORT; typedef int LONG; typedef unsigned int DWORD; - typedef unsigned long DWORD_PTR; - typedef long LONG_PTR; - typedef long INT_PTR; typedef long long LONGLONG; typedef unsigned long long ULONGLONG; typedef unsigned long long *PULONGLONG; typedef void * HANDLE; - typedef void * LPOVERLAPPED; // Unsupported on Linux and Mac typedef char TCHAR; typedef unsigned int LCID; typedef LONG * PLONG; typedef DWORD * PDWORD; typedef BYTE * LPBYTE; typedef char * LPSTR; + typedef const char * LPCSTR; + typedef TCHAR * LPTSTR; + typedef const TCHAR * LPCTSTR; #ifdef PLATFORM_32BIT #define _LZMA_UINT32_IS_ULONG @@ -179,7 +190,6 @@ #define _T(x) x #define _tcslen strlen - #define _tcscpy strcpy #define _tcscat strcat #define _tcschr strchr #define _tcsrchr strrchr @@ -187,9 +197,9 @@ #define _tcsspn strspn #define _tcsncmp strncmp #define _tprintf printf - #define _stprintf sprintf #define _tremove remove - #define _tmkdir mkdir + #define _taccess access + #define _access access #define _stricmp strcasecmp #define _strnicmp strncasecmp @@ -212,6 +222,7 @@ #if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) #define ERROR_SUCCESS 0 #define ERROR_FILE_NOT_FOUND ENOENT + #define ERROR_PATH_NOT_FOUND ENOENT #define ERROR_ACCESS_DENIED EPERM #define ERROR_INVALID_HANDLE EBADF #define ERROR_NOT_ENOUGH_MEMORY ENOMEM @@ -220,18 +231,34 @@ #define ERROR_DISK_FULL ENOSPC #define ERROR_ALREADY_EXISTS EEXIST #define ERROR_INSUFFICIENT_BUFFER ENOBUFS - #define ERROR_BAD_FORMAT 1000 // No such error code under Linux - #define ERROR_NO_MORE_FILES 1001 // No such error code under Linux - #define ERROR_HANDLE_EOF 1002 // No such error code under Linux - #define ERROR_CAN_NOT_COMPLETE 1003 // No such error code under Linux - #define ERROR_FILE_CORRUPT 1004 // No such error code under Linux - #define ERROR_FILE_ENCRYPTED 1005 // Returned by encrypted stream when can't find file key + #define ERROR_BAD_FORMAT 1000 // No such error code under Linux + #define ERROR_NO_MORE_FILES 1001 // No such error code under Linux + #define ERROR_HANDLE_EOF 1002 // No such error code under Linux + #define ERROR_CAN_NOT_COMPLETE 1003 // No such error code under Linux + #define ERROR_FILE_CORRUPT 1004 // No such error code under Linux + #define ERROR_FILE_ENCRYPTED 1005 // Returned by encrypted stream when can't find file key #endif #ifndef ERROR_FILE_INCOMPLETE -#define ERROR_FILE_INCOMPLETE 1006 // The required file part is missing +#define ERROR_FILE_INCOMPLETE 1006 // The required file part is missing +#endif + +#ifndef ERROR_FILE_OFFLINE +#define ERROR_FILE_OFFLINE 1007 // The file is not available in the local storage #endif +#ifndef ERROR_BUFFER_OVERFLOW +#define ERROR_BUFFER_OVERFLOW 1008 +#endif + +#ifndef ERROR_CANCELLED +#define ERROR_CANCELLED 1009 +#endif + +#ifndef _countof +#define _countof(x) (sizeof(x) / sizeof(x[0])) +#endif + //----------------------------------------------------------------------------- // Swapping functions @@ -273,4 +300,13 @@ #define BSWAP_ARRAY64_UNSIGNED(a,b) ConvertUInt64Buffer((a),(b)) #endif +//----------------------------------------------------------------------------- +// Forbidden functions, do not use + +#ifdef __CASCLIB_SELF__ +#define strcpy UNSAFE_DO_NOT_USE_STRCPY +#define strcat UNSAFE_DO_NOT_USE_STRCAT +#define sprintf UNSAFE_DO_NOT_USE_SPRINTF +#endif + #endif // __CASCPORT_H__ diff --git a/dep/CascLib/src/CascReadFile.cpp b/dep/CascLib/src/CascReadFile.cpp index 1b13e873f02..2720d6d5adb 100644 --- a/dep/CascLib/src/CascReadFile.cpp +++ b/dep/CascLib/src/CascReadFile.cpp @@ -1,4 +1,4 @@ -/*****************************************************************************/ +/****************************************************************************/ /* CascOpenFile.cpp Copyright (c) Ladislav Zezula 2014 */ /*---------------------------------------------------------------------------*/ /* System-dependent directory functions for CascLib */ @@ -13,302 +13,453 @@ #include "CascCommon.h" //----------------------------------------------------------------------------- -// Local structures - -typedef struct _BLTE_FRAME -{ - BYTE CompressedSize[4]; // Compressed file size as big endian - BYTE FrameSize[4]; // File size as big endian - BYTE md5[MD5_HASH_SIZE]; // Hash of the compressed frame - -} BLTE_FRAME, *PBLTE_FRAME; - -//----------------------------------------------------------------------------- // Local functions -TCascFile * IsValidFileHandle(HANDLE hFile); // In CascOpenFile.cpp - static int EnsureDataStreamIsOpen(TCascFile * hf) { TCascStorage * hs = hf->hs; TFileStream * pStream = NULL; + ULONGLONG EncodedSize = 0; TCHAR * szDataFile; - TCHAR szPlainName[0x40]; + TCHAR szCachePath[MAX_PATH]; + TCHAR szPlainName[0x80]; + int nError; - // If the file is not open yet, do it - if(hs->DataFileArray[hf->ArchiveIndex] == NULL) + // 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) { - // Prepare the name of the data file - _stprintf(szPlainName, _T("data.%03u"), hf->ArchiveIndex); - szDataFile = CombinePath(hs->szIndexPath, szPlainName); + // If the file is not open yet, do it + if(hs->DataFiles[hf->ArchiveIndex] == NULL) + { + // Prepare the name of the data file + CascStrPrintf(szPlainName, _countof(szPlainName), _T("data.%03u"), hf->ArchiveIndex); + szDataFile = CombinePath(hs->szIndexPath, szPlainName); - // Open the data file - if(szDataFile != 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); + } + } + + // Return error or success + hf->pStream = hs->DataFiles[hf->ArchiveIndex]; + return (hf->pStream != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND; + } + else + { + if(hf->bDownloadFileIf) { - // Open the stream - pStream = FileStream_OpenFile(szDataFile, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE); - hs->DataFileArray[hf->ArchiveIndex] = pStream; - CASC_FREE(szDataFile); + // 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) + { + hf->pStream = FileStream_OpenFile(szCachePath, BASE_PROVIDER_FILE | STREAM_PROVIDER_FLAT); + if(hf->pStream != NULL) + { + // Supply the file size, if unknown yet + if(hf->EncodedSize == CASC_INVALID_SIZE) + { + FileStream_GetSize(hf->pStream, &EncodedSize); + hf->pCKeyEntry->EncodedSize = (DWORD)EncodedSize; + hf->EncodedSize = (DWORD)EncodedSize; + } + + hf->bLocalFileStream = true; + return ERROR_SUCCESS; + } + } } + + return ERROR_FILE_OFFLINE; } +} - // Return error or success - hf->pStream = hs->DataFileArray[hf->ArchiveIndex]; - return (hf->pStream != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND; +#ifdef _DEBUG +static unsigned int table_16C57A8[0x10] = +{ + 0x049396B8, 0x72A82A9B, 0xEE626CCA, 0x9917754F, + 0x15DE40B1, 0xF5A8A9B6, 0x421EAC7E, 0xA9D55C9A, + 0x317FD40C, 0x04FAF80D, 0x3D6BE971, 0x52933CFD, + 0x27F64B7D, 0xC6F5C11B, 0xD5757E3A, 0x6C388745 +}; + +// Obtained from Agent.exe v 2.15.0.6296 (d14ec9d9a1b396a42964b05f40ea55f37eae5478d550c07ebb6cb09e50968d62) +// Note the "Checksum" value probably won't match with older game versions. +static void VerifyHeaderSpan(PBLTE_ENCODED_HEADER pBlteHeader, ULONGLONG HeaderOffset) +{ + LPBYTE pbBlteHeader = (LPBYTE)pBlteHeader; + DWORD dwInt32; + BYTE EncodedOffset[4] = { 0 }; + BYTE HashedHeader[4] = { 0 }; + BYTE JenkinsHash[4]; + BYTE Checksum[4]; + size_t i, j; + + // Seems to be hardcoded to zero + assert(pBlteHeader->field_15 == 0); + + // Calculate the Jenkins hash and write it to the header + dwInt32 = hashlittle(pbBlteHeader, FIELD_OFFSET(BLTE_ENCODED_HEADER, JenkinsHash), 0x3D6BE971); + ConvertIntegerToBytes_4_LE(dwInt32, JenkinsHash); +// assert(memcmp(pBlteHeader->JenkinsHash, JenkinsHash, sizeof(JenkinsHash)) == 0); + + // Encode the lower 32-bits of the offset + dwInt32 = (DWORD)(HeaderOffset + FIELD_OFFSET(BLTE_ENCODED_HEADER, Signature)); + dwInt32 = table_16C57A8[dwInt32 & 0x0F] ^ dwInt32; + ConvertIntegerToBytes_4_LE(dwInt32, EncodedOffset); + + // Calculate checksum of the so-far filled structure + for (i = 0; i < FIELD_OFFSET(BLTE_ENCODED_HEADER, Checksum); i++) + HashedHeader[i & 3] ^= pbBlteHeader[i]; + + // XOR the two values together to get the final checksum. + for (j = 0; j < 4; j++, i++) + Checksum[j] = HashedHeader[i & 3] ^ EncodedOffset[i & 3]; +// assert(memcmp(pBlteHeader->Checksum, Checksum, sizeof(Checksum)) == 0); } +#endif -static int LoadFileFrames(TCascFile * hf) +static int ParseBlteHeader(TCascFile * hf, ULONGLONG HeaderOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t * pcbHeaderSize) +{ + PBLTE_ENCODED_HEADER pEncodedHeader = (PBLTE_ENCODED_HEADER)pbEncodedBuffer; + PBLTE_HEADER pBlteHeader = (PBLTE_HEADER)pbEncodedBuffer; + DWORD ExpectedHeaderSize; + DWORD ExHeaderSize = 0; + DWORD HeaderSize; + DWORD FrameCount = 0; + + CASCLIB_UNUSED(HeaderOffset); + + // On files within storage segments ("data.###"), there is BLTE_ENCODED_HEADER + // On local files, there is just PBLTE_HEADER + if(ConvertBytesToInteger_4_LE(pBlteHeader->Signature) != BLTE_HEADER_SIGNATURE) + { + // There must be at least some bytes + if (cbEncodedBuffer < FIELD_OFFSET(BLTE_ENCODED_HEADER, MustBe0F)) + return ERROR_BAD_FORMAT; + if (pEncodedHeader->EncodedSize != hf->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); + VerifyHeaderSpan(pEncodedHeader, HeaderOffset); +#endif + // Capture the EKey + ExHeaderSize = FIELD_OFFSET(BLTE_ENCODED_HEADER, Signature); + pBlteHeader = (PBLTE_HEADER)(pbEncodedBuffer + ExHeaderSize); + } + + // Verify the signature + if(ConvertBytesToInteger_4_LE(pBlteHeader->Signature) != BLTE_HEADER_SIGNATURE) + return ERROR_BAD_FORMAT; + + // Capture the header size. If this is non-zero, then array + // of chunk headers follow. Otherwise, the file is just one chunk + HeaderSize = ConvertBytesToInteger_4(pBlteHeader->HeaderSize); + if (HeaderSize != 0) + { + if (pBlteHeader->MustBe0F != 0x0F) + return ERROR_BAD_FORMAT; + + // Verify the header size + FrameCount = ConvertBytesToInteger_3(pBlteHeader->FrameCount); + ExpectedHeaderSize = 0x0C + FrameCount * sizeof(BLTE_FRAME); + if (ExpectedHeaderSize != HeaderSize) + return ERROR_BAD_FORMAT; + + // Give the values + pcbHeaderSize[0] = ExHeaderSize + FIELD_OFFSET(BLTE_HEADER, MustBe0F) + sizeof(DWORD); + } + else + { + pcbHeaderSize[0] = ExHeaderSize + FIELD_OFFSET(BLTE_HEADER, MustBe0F); + } + + // Give the frame count + hf->FrameCount = FrameCount; + return ERROR_SUCCESS; +} + +static LPBYTE ReadMissingHeaderData(TCascFile * hf, ULONGLONG DataFileOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t cbTotalHeaderSize) +{ + LPBYTE pbNewBuffer; + + // Reallocate the buffer + pbNewBuffer = CASC_REALLOC(BYTE, pbEncodedBuffer, cbTotalHeaderSize); + if (pbNewBuffer != NULL) + { + // Load the missing data + DataFileOffset += cbEncodedBuffer; + if (FileStream_Read(hf->pStream, &DataFileOffset, pbNewBuffer + cbEncodedBuffer, (DWORD)(cbTotalHeaderSize - cbEncodedBuffer))) + { + return pbNewBuffer; + } + } + + // If anything failed, we free the original buffer and return NULL; + CASC_FREE(pbEncodedBuffer); + return NULL; +} + +static int LoadFileFrames(TCascFile * hf, ULONGLONG DataFileOffset, LPBYTE pbFramePtr, LPBYTE pbFrameEnd, size_t cbHeaderSize) { - PBLTE_FRAME pFileFrames; PBLTE_FRAME pFileFrame; - ULONGLONG ArchiveFileOffset; - DWORD FrameOffset = 0; - DWORD FileSize = 0; + DWORD ContentSize = 0; + DWORD FileOffset = 0; int nError = ERROR_SUCCESS; assert(hf != NULL); assert(hf->pStream != NULL); - assert(hf->pFrames != NULL); + assert(hf->pFrames == NULL); - // Allocate frame array - pFileFrames = pFileFrame = CASC_ALLOC(BLTE_FRAME, hf->FrameCount); - if(pFileFrames != NULL) + if (hf->FrameCount != 0) { - // Load the frame array - ArchiveFileOffset = hf->FramesOffset; - if(FileStream_Read(hf->pStream, &ArchiveFileOffset, pFileFrames, hf->FrameCount * sizeof(BLTE_FRAME))) - { - // Move the raw archive offset - ArchiveFileOffset += (hf->FrameCount * sizeof(BLTE_FRAME)); + // Move the raw archive offset + DataFileOffset += (hf->FrameCount * sizeof(BLTE_FRAME)); + // Allocate array of file frames + hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, hf->FrameCount); + if (hf->pFrames != NULL) + { // Copy the frames to the file structure - for(DWORD i = 0; i < hf->FrameCount; i++, pFileFrame++) + for (DWORD i = 0; i < hf->FrameCount; i++, pbFramePtr += sizeof(BLTE_FRAME)) { - hf->pFrames[i].FrameArchiveOffset = (DWORD)ArchiveFileOffset; - hf->pFrames[i].FrameFileOffset = FrameOffset; - hf->pFrames[i].CompressedSize = ConvertBytesToInteger_4(pFileFrame->CompressedSize); - hf->pFrames[i].FrameSize = ConvertBytesToInteger_4(pFileFrame->FrameSize); - memcpy(hf->pFrames[i].md5, pFileFrame->md5, MD5_HASH_SIZE); - - ArchiveFileOffset += hf->pFrames[i].CompressedSize; - FrameOffset += hf->pFrames[i].FrameSize; - FileSize += hf->pFrames[i].FrameSize; + // 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; } - } - else - nError = GetLastError(); - // Note: on ENCODING file, this value is almost always bigger - // then the real size of ENCODING. We handle this problem - // by calculating size of the ENCODIG file from its header. - hf->FileSize = FileSize; + // Save the content size of the file + if(hf->pCKeyEntry->ContentSize == CASC_INVALID_SIZE) + { + hf->pCKeyEntry->ContentSize = ContentSize; + hf->ContentSize = ContentSize; + } + } + } + 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) + { + assert(false); + return ERROR_CAN_NOT_COMPLETE; + } -#ifdef CASCLIB_TEST - hf->FileSize_FrameSum = FileSize; -#endif + // Save the number of file frames + hf->FrameCount = 1; - // Free the array - CASC_FREE(pFileFrames); + // Allocate single "dummy" frame + hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, 1); + if (hf->pFrames != NULL) + { + 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; + } } - else - nError = ERROR_NOT_ENOUGH_MEMORY; + if (hf->pFrames == NULL) + nError = ERROR_NOT_ENOUGH_MEMORY; return nError; } -static int EnsureHeaderAreaIsLoaded(TCascFile * hf) +static int LoadEncodedHeaderAndFileFrames(TCascFile * hf) { - TCascStorage * hs = hf->hs; - ULONGLONG FileOffset = hf->HeaderOffset; - LPBYTE pbHeaderArea; - DWORD FileSignature; - DWORD FileSize; - BYTE HeaderArea[MAX_HEADER_AREA_SIZE]; - int nError; - - // We need the data file to be open - nError = EnsureDataStreamIsOpen(hf); - if(nError != ERROR_SUCCESS) - return nError; - - // Make sure that we already know the shift - // to the begin of file data. - // Note that older builds of Heroes of the Storm have entries pointing - // to the beginning of the header area. - // Newer versions of HOTS have encoding entries pointing directly to - // the BLTE header - if(hs->dwFileBeginDelta == 0xFFFFFFFF) - { - FileSignature = 0; - FileOffset = hf->HeaderOffset; - if(!FileStream_Read(hf->pStream, &FileOffset, &FileSignature, sizeof(DWORD))) - return ERROR_FILE_CORRUPT; + LPBYTE pbEncodedBuffer; + size_t cbEncodedBuffer = MAX_ENCODED_HEADER; + int nError = ERROR_SUCCESS; - hs->dwFileBeginDelta = (FileSignature == BLTE_HEADER_SIGNATURE) ? BLTE_HEADER_DELTA : 0; - } + // Should only be called when the file frames are NOT loaded + assert(hf->pFrames == NULL); + assert(hf->FrameCount == 0); - // If the file size is not loaded yet, do it - if(hf->FrameCount == 0) + // Allocate the initial buffer for the encoded headers + pbEncodedBuffer = CASC_ALLOC(BYTE, MAX_ENCODED_HEADER); + if (pbEncodedBuffer != NULL) { - // Load the part before BLTE header + header itself - FileOffset = hf->HeaderOffset - hs->dwFileBeginDelta; - if(!FileStream_Read(hf->pStream, &FileOffset, HeaderArea, sizeof(HeaderArea))) - return ERROR_FILE_CORRUPT; - - // Copy the MD5 hash of the frame array - memcpy(hf->FrameArrayHash, HeaderArea, MD5_HASH_SIZE); - pbHeaderArea = HeaderArea + MD5_HASH_SIZE; - - // Copy the file size - FileSize = ConvertBytesToInteger_4_LE(pbHeaderArea); - pbHeaderArea += 0x0E; - - // Verify the BLTE signature - if(ConvertBytesToInteger_4_LE(pbHeaderArea) != BLTE_HEADER_SIGNATURE) - return ERROR_BAD_FORMAT; - pbHeaderArea += sizeof(DWORD); + ULONGLONG ReadOffset = hf->ArchiveOffset; + size_t cbTotalHeaderSize; + size_t cbHeaderSize = 0; - // Load the size of the frame headers - hf->HeaderSize = ConvertBytesToInteger_4(pbHeaderArea); - if(hf->HeaderSize & 0x80000000) - return ERROR_BAD_FORMAT; - pbHeaderArea += sizeof(DWORD); + // At this point, we expect encoded size to be known + assert(hf->EncodedSize != CASC_INVALID_SIZE); - // Read the header size - assert(hs->dwFileBeginDelta <= BLTE_HEADER_DELTA); - hf->HeaderOffset += (BLTE_HEADER_DELTA - hs->dwFileBeginDelta); - hf->FrameCount = 1; + // Do not read more than encoded size + cbEncodedBuffer = CASCLIB_MIN(cbEncodedBuffer, hf->EncodedSize); - // Retrieve the frame count, if different from 1 - if(hf->HeaderSize != 0) + // 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)) { - // The next byte must be 0x0F - if(pbHeaderArea[0] != 0x0F) - return ERROR_BAD_FORMAT; - pbHeaderArea++; + // Parse the BLTE header + nError = ParseBlteHeader(hf, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, &cbHeaderSize); + if (nError == 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 (cbTotalHeaderSize > cbEncodedBuffer) + { + pbEncodedBuffer = ReadMissingHeaderData(hf, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, cbTotalHeaderSize); + if (pbEncodedBuffer == NULL) + nError = GetLastError(); + cbEncodedBuffer = cbTotalHeaderSize; + } - // Next three bytes form number of frames - hf->FrameCount = ConvertBytesToInteger_3(pbHeaderArea); + // Load the array of frame headers + if (nError == ERROR_SUCCESS) + { + nError = LoadFileFrames(hf, ReadOffset + cbHeaderSize, pbEncodedBuffer + cbHeaderSize, pbEncodedBuffer + cbEncodedBuffer, cbHeaderSize); + } + } + } + else + { + nError = ERROR_FILE_CORRUPT; } -#ifdef CASCLIB_TEST - hf->FileSize_HdrArea = FileSize; -#endif + // Free the frame buffer + CASC_FREE(pbEncodedBuffer); + } + else + { + nError = ERROR_NOT_ENOUGH_MEMORY; } - return ERROR_SUCCESS; + return nError; } -static int EnsureFrameHeadersLoaded(TCascFile * hf) +static int EnsureFileFramesLoaded(TCascFile * hf) { - int nError; - - // Make sure we have header area loaded - nError = EnsureHeaderAreaIsLoaded(hf); - if(nError != ERROR_SUCCESS) - return nError; + int nError = ERROR_SUCCESS; - // If the frame headers are not loaded yet, do it + // If the encoded frames are not loaded, do it now if(hf->pFrames == NULL) { - // Allocate the frame array - hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, hf->FrameCount); - if(hf->pFrames != NULL) - { - // Either load the frames from the file or supply them on our own - if(hf->HeaderSize != 0) - { - hf->FramesOffset = hf->HeaderOffset + sizeof(DWORD) + sizeof(DWORD) + sizeof(DWORD); - nError = LoadFileFrames(hf); - } - else - { - // Offset of the first frame is right after the file frames - hf->FramesOffset = hf->HeaderOffset + sizeof(DWORD) + sizeof(DWORD); - - hf->pFrames[0].FrameArchiveOffset = hf->FramesOffset; - hf->pFrames[0].FrameFileOffset = 0; - hf->pFrames[0].CompressedSize = hf->CompressedSize; - hf->pFrames[0].FrameSize = hf->FileSize; - memset(hf->pFrames[0].md5, 0, MD5_HASH_SIZE); - } - } + // We need the data file to be open + nError = EnsureDataStreamIsOpen(hf); + if(nError != ERROR_SUCCESS) + return nError; - // Return result - return (hf->pFrames != NULL) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; + // Make sure we have header area loaded + nError = LoadEncodedHeaderAndFileFrames(hf); } - return ERROR_SUCCESS; + return nError; } -static PCASC_FILE_FRAME FindFileFrame(TCascFile * hf, DWORD FilePointer) +static int LoadEncodedFrame(TFileStream * pStream, PCASC_FILE_FRAME pFrame, LPBYTE pbEncodedFrame, bool bVerifyIntegrity) { - PCASC_FILE_FRAME pFrame = hf->pFrames; - DWORD FrameBegin; - DWORD FrameEnd; - - // Sanity checks - assert(hf->pFrames != NULL); - assert(hf->FrameCount != 0); + ULONGLONG FileOffset = pFrame->DataFileOffset; + int nError = ERROR_SUCCESS; - // Find the frame where to read from - for(DWORD i = 0; i < hf->FrameCount; i++, pFrame++) + // Load the encoded frame to memory + if(FileStream_Read(pStream, &FileOffset, pbEncodedFrame, pFrame->EncodedSize)) { - // Does the read request fit into the current frame? - FrameBegin = pFrame->FrameFileOffset; - FrameEnd = FrameBegin + pFrame->FrameSize; - if(FrameBegin <= FilePointer && FilePointer < FrameEnd) - return pFrame; + if (bVerifyIntegrity) + { + if (!CascVerifyDataBlockHash(pbEncodedFrame, pFrame->EncodedSize, pFrame->FrameHash.Value)) + nError = ERROR_FILE_CORRUPT; + } + } + else + { + nError = GetLastError(); } - // Not found, sorry - return NULL; + return nError; } static int ProcessFileFrame( + TCascStorage * hs, LPBYTE pbOutBuffer, DWORD cbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer, DWORD dwFrameIndex) { - LPBYTE pbTempBuffer; - LPBYTE pbWorkBuffer; - DWORD cbTempBuffer = CASCLIB_MAX(cbInBuffer, cbOutBuffer); - DWORD cbWorkBuffer = cbOutBuffer + 1; + LPBYTE pbWorkBuffer = NULL; + DWORD cbOutBufferExpected = 0; + DWORD cbWorkBuffer = 0; DWORD dwStepCount = 0; bool bWorkComplete = false; int nError = ERROR_SUCCESS; - // Allocate the temporary buffer that will serve as output - pbWorkBuffer = pbTempBuffer = CASC_ALLOC(BYTE, cbTempBuffer); - if(pbWorkBuffer == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - // Perform the loop - for(;;) + while(bWorkComplete == false) { - // Set the output buffer. - // Even operations: extract to temporary buffer - // Odd operations: extract to output buffer - pbWorkBuffer = (dwStepCount & 0x01) ? pbOutBuffer : pbTempBuffer; - cbWorkBuffer = (dwStepCount & 0x01) ? cbOutBuffer : cbTempBuffer; + // There should never be a 3rd step + assert(dwStepCount < 2); - // Perform the operation specific to the operation ID + // Perform the operation specific by the first byte switch(pbInBuffer[0]) { case 'E': // Encrypted files - nError = CascDecrypt(pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1, dwFrameIndex); - bWorkComplete = (nError != ERROR_SUCCESS); + + // The work buffer should not have been allocated by any step + assert(pbWorkBuffer == NULL && cbWorkBuffer == 0); + + // Allocate temporary buffer to decrypt into + // Example storage: "2016 - WoW/23420", File: "4ee6bc9c6564227f1748abd0b088e950" + pbWorkBuffer = CASC_ALLOC(BYTE, cbInBuffer - 1); + cbWorkBuffer = cbInBuffer - 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) + { + bWorkComplete = true; + break; + } + + // 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; break; case 'Z': // ZLIB compressed files - nError = CascDecompress(pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1); + + // 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); + + // 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)); bWorkComplete = true; break; case 'N': // Normal stored files - nError = CascDirectCopy(pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1); + nError = CascDirectCopy(pbOutBuffer, &cbOutBuffer, pbInBuffer + 1, cbInBuffer - 1); bWorkComplete = true; break; @@ -320,48 +471,131 @@ static int ProcessFileFrame( break; } - // Are we done? - if(bWorkComplete) - break; - - // Set the input buffer to the work buffer - pbInBuffer = pbWorkBuffer; - cbInBuffer = cbWorkBuffer; + // Increment the step count dwStepCount++; } - // If the data are currently in the temporary buffer, - // we need to copy them to output buffer - if(nError == ERROR_SUCCESS && pbWorkBuffer != pbOutBuffer) + // Free the temporary buffer + CASC_FREE(pbWorkBuffer); + return nError; +} + +static bool GetFileFullInfo(TCascFile * hf, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded) +{ + PCASC_FILE_FULL_INFO pFileInfo; + PCASC_CKEY_ENTRY pCKeyEntry = hf->pCKeyEntry; + TCascStorage * hs = hf->hs; + + // Verify whether we have enough space in the buffer + pFileInfo = (PCASC_FILE_FULL_INFO)ProbeOutputBuffer(pvFileInfo, cbFileInfo, sizeof(CASC_FILE_FULL_INFO), pcbLengthNeeded); + if(pFileInfo != NULL) { - if(cbWorkBuffer != cbOutBuffer) - nError = ERROR_INSUFFICIENT_BUFFER; - memcpy(pbOutBuffer, pbWorkBuffer, cbOutBuffer); + // Reset the entire structure + CopyMemory16(pFileInfo->CKey, pCKeyEntry->CKey); + CopyMemory16(pFileInfo->EKey, pCKeyEntry->EKey); + pFileInfo->FileDataId = CASC_INVALID_ID; + pFileInfo->LocaleFlags = CASC_INVALID_ID; + pFileInfo->ContentFlags = CASC_INVALID_ID; + + // Supply information not depending on root + CascStrPrintf(pFileInfo->DataFileName, _countof(pFileInfo->DataFileName), "data.%03u", hf->ArchiveIndex); + pFileInfo->StorageOffset = pCKeyEntry->StorageOffset; + pFileInfo->SegmentOffset = hf->ArchiveOffset; + pFileInfo->FileNameHash = 0; + pFileInfo->TagBitMask = pCKeyEntry->TagBitMask; + pFileInfo->SegmentIndex = hf->ArchiveIndex; + pFileInfo->ContentSize = hf->ContentSize; + pFileInfo->EncodedSize = hf->EncodedSize; + + // Supply the root-specific information + hs->pRootHandler->GetInfo(pCKeyEntry, pFileInfo); } - // Free the temporary buffer - CASC_FREE(pbTempBuffer); - return nError; + return (pFileInfo != NULL); } //----------------------------------------------------------------------------- // Public functions +bool WINAPI CascGetFileInfo(HANDLE hFile, CASC_FILE_INFO_CLASS InfoClass, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded) +{ + TCascFile * hf; + LPBYTE pbOutputValue = NULL; + LPBYTE pbInfoValue = NULL; + size_t cbInfoValue = 0; + + // Validate the file handle + if((hf = TCascFile::IsValid(hFile)) == NULL) + { + SetLastError(ERROR_INVALID_HANDLE); + return false; + } + + // Differentiate between info classes + switch(InfoClass) + { + case CascFileContentKey: + + // Do we have content key at all? + if(hf->pCKeyEntry == NULL || (hf->pCKeyEntry->Flags & CASC_CE_HAS_CKEY) == 0) + { + SetLastError(ERROR_NOT_SUPPORTED); + return false; + } + + // Give the content key + pbInfoValue = hf->pCKeyEntry->CKey; + cbInfoValue = CASC_CKEY_SIZE; + break; + + case CascFileEncodedKey: + + // Do we have content key at all? + if(hf->pCKeyEntry == NULL || (hf->pCKeyEntry->Flags & CASC_CE_HAS_EKEY) == 0) + { + SetLastError(ERROR_NOT_SUPPORTED); + return false; + } + + // Give the content key + pbInfoValue = hf->pCKeyEntry->EKey; + cbInfoValue = CASC_CKEY_SIZE; + break; + + case CascFileFullInfo: + return GetFileFullInfo(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded); + + default: + SetLastError(ERROR_INVALID_PARAMETER); + return false; + } + + // Sanity check + assert(pbInfoValue != NULL); + assert(cbInfoValue != 0); + + // Give the result + pbOutputValue = (LPBYTE)ProbeOutputBuffer(pvFileInfo, cbFileInfo, cbInfoValue, pcbLengthNeeded); + if(pbOutputValue != NULL) + memcpy(pbOutputValue, pbInfoValue, cbInfoValue); + return (pbOutputValue != NULL); +} + // // THE FILE SIZE PROBLEM // // There are members called "FileSize" in many CASC-related structure // For various files, these variables have different meaning. // -// Storage FileName FileSize FrameSum HdrArea IdxEntry EncEntry RootEntry -// ----------- -------- ---------- -------- -------- -------- -------- --------- -// HotS(29049) ENCODING 0x0024BA45 - 0x0024b98a 0x0024BA45 0x0024BA45 n/a n/a -// HotS(29049) ROOT 0x00193340 - 0x00193340 0x0010db65 0x0010db65 0x00193340 n/a -// HotS(29049) (other) 0x00001080 - 0x00001080 0x000008eb 0x000008eb 0x00001080 0x00001080 -// -// WoW(18888) ENCODING 0x030d487b - 0x030dee79 0x030d487b 0x030d487b n/a n/a -// WoW(18888) ROOT 0x016a9800 - n/a 0x0131313d 0x0131313d 0x016a9800 n/a -// WoW(18888) (other) 0x000007d0 - 0x000007d0 0x00000397 0x00000397 0x000007d0 n/a +// Storage FileName FileSize FrameSum HdrArea CKeyEntry EKeyEntry RootEntry +// ----------- -------- ---------- -------- -------- ---------- ---------- ---------- +// HotS(29049) ENCODING 0x0024BA45 - 0x0024b98a 0x0024BA45 n/a 0x0024BA45 n/a +// HotS(29049) ROOT 0x00193340 - 0x00193340 0x0010db65 0x00193340 0x0010db65 n/a +// HotS(29049) (other) 0x00001080 - 0x00001080 0x000008eb 0x00001080 0x000008eb 0x00001080 +// +// WoW(18888) ENCODING 0x030d487b - 0x030dee79 0x030d487b n/a 0x030d487b n/a +// WoW(18888) ROOT 0x016a9800 - n/a 0x0131313d 0x016a9800 0x0131313d n/a +// WoW(18888) (other) 0x000007d0 - 0x000007d0 0x00000397 0x000007d0 0x00000397 n/a // DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh) @@ -372,24 +606,32 @@ DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh) CASCLIB_UNUSED(pdwFileSizeHigh); // Validate the file handle - if((hf = IsValidFileHandle(hFile)) == NULL) + if((hf = TCascFile::IsValid(hFile)) == NULL) { SetLastError(ERROR_INVALID_HANDLE); return CASC_INVALID_SIZE; } - // Make sure that the file header area is loaded - nError = EnsureFrameHeadersLoaded(hf); - if(nError != ERROR_SUCCESS) + // 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) { - SetLastError(nError); - return CASC_INVALID_SIZE; + // Make sure that the file header area is loaded + nError = EnsureFileFramesLoaded(hf); + if(nError != ERROR_SUCCESS) + { + SetLastError(nError); + return CASC_INVALID_SIZE; + } + + // The content size should be loaded from the frames + assert(hf->ContentSize != CASC_INVALID_SIZE); } // Give the file size to the caller if(pdwFileSizeHigh != NULL) *pdwFileSizeHigh = 0; - return hf->FileSize; + return hf->ContentSize; } DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod) @@ -400,7 +642,7 @@ DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHig DWORD dwFilePosHi; // If the hFile is not a valid file handle, return an error. - hf = IsValidFileHandle(hFile); + hf = TCascFile::IsValid(hFile); if(hf == NULL) { SetLastError(ERROR_INVALID_HANDLE); @@ -419,7 +661,7 @@ DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHig break; case FILE_END: - FilePosition = hf->FileSize; + FilePosition = hf->ContentSize; break; default: @@ -458,16 +700,7 @@ DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHig bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDWORD pdwBytesRead) { - PCASC_FILE_FRAME pFrame = NULL; - ULONGLONG StreamSize; - ULONGLONG FileOffset; TCascFile * hf; - LPBYTE pbBuffer = (LPBYTE)pvBuffer; - DWORD dwStartPointer = 0; - DWORD dwFilePointer = 0; - DWORD dwEndPointer = 0; - DWORD dwFrameSize; - bool bReadResult; int nError = ERROR_SUCCESS; // The buffer must be valid @@ -478,7 +711,7 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW } // Validate the file handle - if((hf = IsValidFileHandle(hFile)) == NULL) + if((hf = TCascFile::IsValid(hFile)) == NULL) { SetLastError(ERROR_INVALID_HANDLE); return false; @@ -487,134 +720,116 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW // If the file frames are not loaded yet, do it now if(nError == ERROR_SUCCESS) { - nError = EnsureFrameHeadersLoaded(hf); + nError = EnsureFileFramesLoaded(hf); } // If the file position is at or beyond end of file, do nothing - if(nError == ERROR_SUCCESS && hf->FilePointer >= hf->FileSize) + if(nError == ERROR_SUCCESS) { - *pdwBytesRead = 0; - return true; + // 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; + } } - // Find the file frame where to read from + // 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) { - // Get the frame - pFrame = FindFileFrame(hf, hf->FilePointer); - if(pFrame == NULL || pFrame->CompressedSize < 1) - nError = ERROR_FILE_CORRUPT; + 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; + } } - // Perform the read + // Load all frames that are not loaded yet if(nError == ERROR_SUCCESS) { - // If not enough bytes in the file remaining, cut them - dwStartPointer = dwFilePointer = hf->FilePointer; - dwEndPointer = dwStartPointer + dwBytesToRead; - if(dwEndPointer > hf->FileSize) - dwEndPointer = hf->FileSize; - - // Perform block read from each file frame - while(dwFilePointer < dwEndPointer) + PCASC_FILE_FRAME pFrame = hf->pFrames; + DWORD StartFrameOffset = 0; + DWORD StartReadOffset = hf->FilePointer; + DWORD EndReadOffset = hf->FilePointer + dwBytesToRead; + + for(DWORD i = 0; (i < hf->FrameCount) && (nError == ERROR_SUCCESS); i++, pFrame++) { - LPBYTE pbFrameData = NULL; - DWORD dwFrameStart = pFrame->FrameFileOffset; - DWORD dwFrameEnd = pFrame->FrameFileOffset + pFrame->FrameSize; + LPBYTE pbDecodedFrame = hf->pbFileCache + StartFrameOffset; + LPBYTE pbEncodedFrame; + DWORD EndFrameOffset = StartFrameOffset + pFrame->ContentSize; - // Shall we populate the cache with a new data? - if(dwFrameStart != hf->CacheStart || hf->CacheEnd != dwFrameEnd) + // Does that frame belong to the range? + if(StartReadOffset < EndFrameOffset && EndReadOffset > StartFrameOffset) { - // Shall we reallocate the cache buffer? - if(pFrame->FrameSize > hf->cbFileCache) - { - if(hf->pbFileCache != NULL) - CASC_FREE(hf->pbFileCache); - - hf->pbFileCache = CASC_ALLOC(BYTE, pFrame->FrameSize); - hf->cbFileCache = pFrame->FrameSize; - } - - // We also need to allocate buffer for the raw data - pbFrameData = CASC_ALLOC(BYTE, pFrame->CompressedSize); - if(pbFrameData == NULL) - { - nError = ERROR_NOT_ENOUGH_MEMORY; - break; - } - - // Load the raw file data to memory - FileOffset = pFrame->FrameArchiveOffset; - bReadResult = FileStream_Read(hf->pStream, &FileOffset, pbFrameData, pFrame->CompressedSize); - - // Note: The raw file data size could be less than expected - // Happened in WoW build 19342 with the ROOT file. MD5 in the frame header - // is zeroed, which means it should not be checked - // Frame File: data.029 - // Frame Offs: 0x013ED9F0 size 0x01325B32 - // Frame End: 0x02713522 - // File Size: 0x027134FC - if(bReadResult == false && GetLastError() == ERROR_HANDLE_EOF && !IsValidMD5(pFrame->md5)) + // Is the frame already loaded? + if (pFrame->FileOffset == CASC_INVALID_POS) { - // Get the size of the remaining file - FileStream_GetSize(hf->pStream, &StreamSize); - dwFrameSize = (DWORD)(StreamSize - FileOffset); - - // If the frame offset is before EOF and frame end is beyond EOF, correct it - if(FileOffset < StreamSize && dwFrameSize < pFrame->CompressedSize) + // Allocate space for the encoded frame + pbEncodedFrame = CASC_ALLOC(BYTE, pFrame->EncodedSize); + if (pbEncodedFrame != NULL) { - memset(pbFrameData + dwFrameSize, 0, (pFrame->CompressedSize - dwFrameSize)); - bReadResult = true; + // 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)); + if (nError == ERROR_SUCCESS) + { + // Mark the frame as loaded + pFrame->FileOffset = StartFrameOffset; + } + } + + // Free the frame buffer + CASC_FREE(pbEncodedFrame); } - } - - // If the read result failed, we cannot finish reading it - if(bReadResult && VerifyDataBlockHash(pbFrameData, pFrame->CompressedSize, pFrame->md5)) - { - // Convert the source frame to the file cache - nError = ProcessFileFrame(hf->pbFileCache, - pFrame->FrameSize, - pbFrameData, - pFrame->CompressedSize, - (DWORD)(pFrame - hf->pFrames)); - if(nError == ERROR_SUCCESS) + else { - // Set the start and end of the cache - hf->CacheStart = dwFrameStart; - hf->CacheEnd = dwFrameEnd; + nError = ERROR_NOT_ENOUGH_MEMORY; } } - else - { - nError = ERROR_FILE_CORRUPT; - } - - // Free the raw frame data - CASC_FREE(pbFrameData); } - // Copy the decompressed data - if(dwFrameEnd > dwEndPointer) - dwFrameEnd = dwEndPointer; - memcpy(pbBuffer, hf->pbFileCache + (dwFilePointer - dwFrameStart), (dwFrameEnd - dwFilePointer)); - pbBuffer += (dwFrameEnd - dwFilePointer); - - // Move pointers - dwFilePointer = dwFrameEnd; - pFrame++; + // If the frame start is past the read offset, stop the loop + if ((StartFrameOffset + pFrame->ContentSize) >= EndReadOffset) + break; + StartFrameOffset += pFrame->ContentSize; } } - // Update the file position + // Now all frames have been loaded into the cache; copy the entire block to the output buffer if(nError == ERROR_SUCCESS) { + // Copy the entire data + memcpy(pvBuffer, hf->pbFileCache + hf->FilePointer, dwBytesToRead); + hf->FilePointer += dwBytesToRead; + + // Give the number of bytes read if(pdwBytesRead != NULL) - *pdwBytesRead = (dwFilePointer - dwStartPointer); - hf->FilePointer = dwFilePointer; + *pdwBytesRead = dwBytesToRead; + return true; } - - if(nError != ERROR_SUCCESS) + else + { SetLastError(nError); - return (nError == ERROR_SUCCESS); + return false; + } } - diff --git a/dep/CascLib/src/CascRootFile_Diablo3.cpp b/dep/CascLib/src/CascRootFile_Diablo3.cpp index f695ad978b1..bbe369c7646 100644 --- a/dep/CascLib/src/CascRootFile_Diablo3.cpp +++ b/dep/CascLib/src/CascRootFile_Diablo3.cpp @@ -20,61 +20,36 @@ #define DIABLO3_SUBDIR_SIGNATURE 0xEAF1FE87 #define DIABLO3_PACKAGES_SIGNATURE 0xAABB0002 #define DIABLO3_MAX_SUBDIRS 0x20 - -#define DIABLO3_INVALID_INDEX 0xFFFFFFFF -#define DIABLO3_INVALID_FILE 0xFFFFFFFF #define DIABLO3_MAX_ASSETS 70 // Maximum possible number of assets -#define DIABLO3_MAX_LEVEL0_LENGTH 0x10 // Maximum length of the level-0 directory name - -#define INVALID_FILE_INDEX 0xFFFFFFFF -#define INVALID_ASSET_INDEX 0xFF - -#define ENTRY_FLAG_DIRECTORY_ENTRY 0x80 // The file is actually a directory entry -#define ENTRY_FLAG_PLAIN_NAME 0x01 // If set, the file entry contains offset of the plain file name -#define ENTRY_FLAG_FULL_NAME 0x02 // If set, the file entry contains offset of the full name -#define ENTRY_FLAG_FLAGS_MASK 0xF0 // Mask for the entry flags -#define ENTRY_FLAG_NAME_MASK 0x0F // Mask for the entry file name type - -// Values for CASC_FILE_ENTRY::dwFlags -#define CASC_ENTRY_SHORT_NAME 0x000000001 // If set, the name is in format XXYYplain-name[\sub-index].ext -#define CASC_ENTRY_HAS_SUBINDEX 0x000000002 // If set, the subitem is present in the file name (i.e. XXYYplain-name\sub-index.ext) - -#define SEARCH_PHASE_NAMES 0 // Searching named entry -#define SEARCH_PHASE_FILE_IDS 1 // Searching filed by ID - -// Macro for constructing 64-bit integer from root-index, file-index and sub-index -// The result value is RRAAAAAAAASSSSSS -#define MAKE_INDEX64(ri, fi, si) (((ULONGLONG)ri << 0x38) | ((ULONGLONG)fi << 0x18) | ((ULONGLONG)si)) -#define INDEX64_ROOT_INDEX(hash) (DWORD)((hash >> 0x38) & 0x000000FF) -#define INDEX64_FILE_INDEX(hash) (DWORD)((hash >> 0x18) & 0xFFFFFFFF) -#define INDEX64_SUB_INDEX(hash) (DWORD)((hash >> 0x00) & 0x00FFFFFF) +#define DIABLO3_MAX_ROOT_FOLDERS 0x20 // Maximum count of root directory named entries // On-disk structure for a file given by file number -typedef struct _DIABLO3_FILEID1_ENTRY +typedef struct _DIABLO3_ASSET_ENTRY { - ENCODING_KEY EncodingKey; // Encoding key for the file - DWORD FileIndex; // File index -} DIABLO3_FILEID1_ENTRY, *PDIABLO3_FILEID1_ENTRY; + CONTENT_KEY CKey; // Content key for the file + DWORD FileIndex; // File index +} DIABLO3_ASSET_ENTRY, *PDIABLO3_ASSET_ENTRY; // On-disk structure for a file given by file number and suffix -typedef struct _DIABLO3_FILEID2_ENTRY +typedef struct _DIABLO3_ASSETIDX_ENTRY { - ENCODING_KEY EncodingKey; // Encoding key for the file - DWORD FileIndex; // File index - DWORD SubIndex; // File subindex, like "SoundBank\3D Ambience\0000.smp" -} DIABLO3_FILEID2_ENTRY, *PDIABLO3_FILEID2_ENTRY; + CONTENT_KEY CKey; // Content key for the file + DWORD FileIndex; // File index + DWORD SubIndex; // File subindex, like "SoundBank\3D Ambience\0000.smp" +} DIABLO3_ASSETIDX_ENTRY, *PDIABLO3_ASSETIDX_ENTRY; -// On-disk structure of the named entry +// In-memory structure of the named entry typedef struct _DIABLO3_NAMED_ENTRY { - ENCODING_KEY EncodingKey; // Encoding key for the file - BYTE szFileName[1]; // ASCIIZ file name (variable length) + PCONTENT_KEY pCKey; // Pointer to the content key + const char * szFileName; // Pointer to the zero-terminated file name + const char * szFileEnd; // Position of the zero terminator (aka end of the file name) } DIABLO3_NAMED_ENTRY, *PDIABLO3_NAMED_ENTRY; // On-disk structure of CoreToc.dat header typedef struct _DIABLO3_CORE_TOC_HEADER { - DWORD EntryCounts[DIABLO3_MAX_ASSETS]; // Array of number of entries (files) for each asset (level-1 directory) + DWORD EntryCounts[DIABLO3_MAX_ASSETS]; // Array of number of entries (files) for each asset DWORD EntryOffsets[DIABLO3_MAX_ASSETS]; // Array of offsets of each DIABLO3_CORE_TOC_ENTRY, relative to data after header DWORD Unknowns[DIABLO3_MAX_ASSETS]; // Unknown DWORD Alignment; @@ -89,33 +64,19 @@ typedef struct _DIABLO3_CORE_TOC_ENTRY } DIABLO3_CORE_TOC_ENTRY, *PDIABLO3_CORE_TOC_ENTRY; -// In-memory structure of parsed directory header -typedef struct _DIABLO3_DIR_HEADER -{ - LPBYTE pbEntries1; - LPBYTE pbEntries2; - LPBYTE pbEntries3; - DWORD dwEntries1; - DWORD dwEntries2; - DWORD dwEntries3; -} DIABLO3_DIR_HEADER, *PDIABLO3_DIR_HEADER; - -// In-memory structure of loaded CoreTOC.dat -typedef struct _DIABLO3_CORE_TOC +// In-memory structure of parsed directory data +typedef struct _DIABLO3_DIRECTORY { - DIABLO3_CORE_TOC_HEADER Hdr; // Header of CoreTOC.dat - - LPBYTE pbCoreToc; // Content of the CoreTOC.dat file - DIABLO3_CORE_TOC_ENTRY Entries[1]; // Buffer for storing the entries (variable length) - -} DIABLO3_CORE_TOC, *PDIABLO3_CORE_TOC; - -// On-disk structure of Packages.dat header -typedef struct _DIABLO3_PACKAGES_DAT_HEADER -{ - DWORD Signature; - DWORD NumberOfNames; -} DIABLO3_PACKAGES_DAT_HEADER, *PDIABLO3_PACKAGES_DAT_HEADER; + LPBYTE pbDirectoryData; // The begin of the directory data block + LPBYTE pbDirectoryEnd; // The end of the directory data block + LPBYTE pbAssetEntries; // Pointer to asset entries without subitem number. Example: "SoundBank\SoundFile.smp" + LPBYTE pbAssetIdxEntries; // Pointer to asset entries with subitem number + LPBYTE pbNamedEntries; // Pointer to named entries. These are for files with arbitrary names, and they do not belong to an asset + DWORD dwAssetEntries; // Number of asset entries without subitem number + DWORD dwAssetIdxEntries; + DWORD dwNamedEntries; + DWORD dwNodeIndex; // Index of file node for this folder +} DIABLO3_DIRECTORY, *PDIABLO3_DIRECTORY; // Structure for conversion DirectoryID -> Directory name typedef struct _DIABLO3_ASSET_INFO @@ -126,35 +87,6 @@ typedef struct _DIABLO3_ASSET_INFO } DIABLO3_ASSET_INFO; typedef const DIABLO3_ASSET_INFO * PDIABLO3_ASSET_INFO; -// In-memory structure of a file entry in the linear file list -typedef struct _CASC_FILE_ENTRY -{ - ENCODING_KEY EncodingKey; // Encoding key - ULONGLONG FileNameHash; // Hash of the full file name - DWORD dwFileName; // Offset of the name (in name's dynamic array) - DWORD dwFlags; // Entry flags (see CASC_ENTRY_XXXX) - - DWORD NameOffset; // Offset of the name (in name's dynamic array) - USHORT SubIndex; // File\SubFile index - BYTE AssetIndex; // Asset index (aka directory index) - BYTE EntryFlags; // Entry flags -} CASC_FILE_ENTRY, *PCASC_FILE_ENTRY; - -//----------------------------------------------------------------------------- -// Structure definitions for Diablo3 root file - -struct TRootHandler_Diablo3 : public TRootHandler -{ - // Linear global list of all files - DYNAMIC_ARRAY FileTable; - - // Linear global list of names - DYNAMIC_ARRAY FileNames; - - // Global map of FileName -> FileEntry - PCASC_MAP pRootMap; -}; - //----------------------------------------------------------------------------- // Local variables @@ -231,969 +163,727 @@ static const DIABLO3_ASSET_INFO Assets[] = {"Accolade", "aco"}, // 0x42 }; -static const DIABLO3_ASSET_INFO UnknownAsset = {"Unknown", "unk"}; - #define DIABLO3_ASSET_COUNT (sizeof(Assets) / sizeof(Assets[0])) //----------------------------------------------------------------------------- -// Local functions - -static PDIABLO3_ASSET_INFO GetAssetInfo(DWORD dwAssetIndex) -{ - if(dwAssetIndex < DIABLO3_ASSET_COUNT && Assets[dwAssetIndex].szDirectoryName != NULL) - return &Assets[dwAssetIndex]; - return &UnknownAsset; -} - -static DWORD VerifyNamedFileEntry(LPBYTE pbNamedEntry, LPBYTE pbFileEnd) -{ - LPBYTE pbFileName = ((PDIABLO3_NAMED_ENTRY)pbNamedEntry)->szFileName; - - // Find the end of the name - while(pbFileName < pbFileEnd && pbFileName[0] != 0) - pbFileName++; +// Handler definitions for Diablo3 root file - // Did we get past the end of the root file? - if(pbFileName >= pbFileEnd) - return 0; - pbFileName++; - - // Return the length of the structure - return (DWORD)(pbFileName - pbNamedEntry); -} - -static char * FindPackageName( - PCASC_MAP pPackageMap, - const char * szAssetName, - const char * szPlainName) +struct TDiabloRoot : public TFileTreeRoot { - char szFileName[MAX_PATH+1]; - size_t nLength; + public: - // Construct the name without extension and find it in the map - nLength = sprintf(szFileName, "%s\\%s", szAssetName, szPlainName); - return (char *)Map_FindString(pPackageMap, szFileName, szFileName + nLength); -} + TDiabloRoot() : TFileTreeRoot(0) + { + memset(RootFolders, 0, sizeof(RootFolders)); + pFileIndices = NULL; + pbCoreTocFile = NULL; + pbCoreTocData = NULL; + nFileIndices = 0; + cbCoreTocFile = 0; + + // Map for searching a real file extension + memset(&PackagesMap, 0, sizeof(CASC_MAP)); + pbPackagesDat = NULL; + cbPackagesDat = 0; + + // We have file names and return CKey as result of search + dwFeatures |= (CASC_FEATURE_FILE_NAMES | CASC_FEATURE_ROOT_CKEY); + } -static size_t CreateShortName( - PCASC_MAP pPackageMap, - DWORD dwRootIndex, // Level-0-dir: Index of the root subdirectory - DWORD dwAssetIndex, // Level-1-dir: Index of the asset name - const char * szPlainName, // Plain name of the file, without extension - DWORD dwSubIndex, - char * szBuffer) -{ - PDIABLO3_ASSET_INFO pAssetInfo = GetAssetInfo(dwAssetIndex); - const char * szPackageName = NULL; - const char * szFormat; - size_t nLength; - - // Write the level-0 directory index as 2-digit hexa number - assert(dwRootIndex < 0x100); - *szBuffer++ = IntToHexChar[dwRootIndex >> 0x04]; - *szBuffer++ = IntToHexChar[dwRootIndex & 0x0F]; - - // Write the level-1 directory index as 2-digit hexa number - assert(dwAssetIndex < 0x100); - *szBuffer++ = IntToHexChar[dwAssetIndex >> 0x04]; - *szBuffer++ = IntToHexChar[dwAssetIndex & 0x0F]; - - // Construct the file name with ending "." for extension - szFormat = (dwSubIndex != DIABLO3_INVALID_INDEX) ? "%s\\%04u." : "%s."; - nLength = sprintf(szBuffer, szFormat, szPlainName, dwSubIndex); - - // Try to fixup the file extension from the package name. - // File extensions are not predictable because for subitems, - // they are not always equal to the main items: - // - // SoundBank\3D Ambience.sbk - // SoundBank\3D Ambience\0000.smp - // SoundBank\3D Ambience\0002.smp - // ... - // SoundBank\Angel.sbk - // SoundBank\Angel\0000.fsb - // SoundBank\Angel\0002.fsb - // - // We use the Base\Data_D3\PC\Misc\Packages.dat for real file extensions, where possible - // - if(pPackageMap != NULL) + ~TDiabloRoot() { - // Retrieve the asset name - szPackageName = FindPackageName(pPackageMap, pAssetInfo->szDirectoryName, szBuffer); - if(szPackageName != NULL) - { - strcpy(szBuffer, szPackageName + strlen(pAssetInfo->szDirectoryName) + 1); - nLength = strlen(szBuffer); - } + FreeLoadingStuff(); } - // If we havent't found the package, we either use the default asset extension or "unk" - if(szPackageName == NULL) + void AppendBackslashToTotalPath(PATH_BUFFER & PathBuffer) { - if(dwSubIndex == DIABLO3_INVALID_INDEX) + if(PathBuffer.szPtr < PathBuffer.szEnd) { - strcpy(szBuffer + nLength, pAssetInfo->szExtension); - nLength += strlen(pAssetInfo->szExtension); - } - else - { - strcpy(szBuffer + nLength, "unk"); - nLength += 3; + PathBuffer.szPtr[0] = '\\'; + PathBuffer.szPtr++; } } - // Return the length of the short file name - return nLength + 4; -} - -static size_t CreateFileName( - TRootHandler_Diablo3 * pRootHandler, - const char * szShortName, // Short file name of the file - char * szBuffer) -{ - PCASC_FILE_ENTRY pRootEntry; - const char * szNameLevel0; - const char * szNameLevel1 = NULL; - DWORD dwRootIndex0 = 0; - DWORD dwAssetIndex = 0; - - // Retrieve the level-0 and level-1 directory indexes - ConvertStringToInt08(szShortName+0, &dwRootIndex0); - ConvertStringToInt08(szShortName+2, &dwAssetIndex); - - // Retrieve the name of the level-0 directory (aka root subdirectory) - pRootEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, dwRootIndex0); - szNameLevel0 = (char *)Array_ItemAt(&pRootHandler->FileNames, pRootEntry->dwFileName); - - // Retrieve the name of the level-1 directory (aka asset name) - if(dwAssetIndex < DIABLO3_ASSET_COUNT) - szNameLevel1 = Assets[dwAssetIndex].szDirectoryName; - if(szNameLevel1 == NULL) - szNameLevel1 = UnknownAsset.szDirectoryName; - - // Copy the rest of the name as-is - return sprintf(szBuffer, "%s\\%s\\%s", szNameLevel0, szNameLevel1, szShortName + 4); -} - - -// Creates a map of String -> Pointer -static PCASC_MAP CreatePackageMap( - LPBYTE pbPackagesDat, - LPBYTE pbPackagesEnd) -{ - PDIABLO3_PACKAGES_DAT_HEADER pDatHeader = (PDIABLO3_PACKAGES_DAT_HEADER)pbPackagesDat; - PCASC_MAP pPackageMap; - - // Get the header - if((pbPackagesDat + sizeof(DIABLO3_PACKAGES_DAT_HEADER)) >= pbPackagesEnd) - return NULL; - pbPackagesDat += sizeof(DIABLO3_PACKAGES_DAT_HEADER); - - // Check the signature and name count - if(pDatHeader->Signature != DIABLO3_PACKAGES_SIGNATURE) - return NULL; - - // Create the map for fast search of the file name - pPackageMap = Map_Create(pDatHeader->NumberOfNames, KEY_LENGTH_STRING, 0); - if(pPackageMap != NULL) + void AppendPathToTotalPath(PATH_BUFFER & PathBuffer, const char * szFileName, const char * szFileEnd) { - char * szFileName = (char *)pbPackagesDat; + char * szPathPtr = PathBuffer.szPtr; + size_t nLength = (szFileEnd - szFileName); - // Go as long as there is something - for(DWORD i = 0; i < pDatHeader->NumberOfNames; i++) + // Append the name + if((szPathPtr + nLength) < PathBuffer.szEnd) { - // Get the file extension - if((LPBYTE)szFileName >= pbPackagesEnd) - break; - - // Insert the file name to the map. The file extension is not included - Map_InsertString(pPackageMap, szFileName, true); - szFileName = szFileName + strlen(szFileName) + 1; + memcpy(szPathPtr, szFileName, nLength); + szPathPtr += nLength; } - } - - return pPackageMap; -} - -// Insert an entry with file name as-is -static int InsertFileEntry( - TRootHandler_Diablo3 * pRootHandler, - ENCODING_KEY & EncodingKey, - const char * szFileName, - size_t cchFileName) -{ - PCASC_FILE_ENTRY pFileEntry; - // We must not allow the file name array to be reallocated. - // Reallocating the array would cause pointers in TRootHandler_Diablo3::pRootMap - // become invalid - if(pRootHandler->FileTable.ItemCount >= pRootHandler->FileTable.ItemCountMax) - { - assert(false); - return ERROR_NOT_ENOUGH_MEMORY; + // Append zero terminator + if(szPathPtr < PathBuffer.szEnd) + szPathPtr[0] = 0; + PathBuffer.szPtr = szPathPtr; } - // Insert the plain name to the root handler's global name list - szFileName = (const char *)Array_Insert(&pRootHandler->FileNames, szFileName, cchFileName); - if(szFileName == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Make sure that we don't exceed the file limit at this phase - pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1); - assert(pFileEntry != NULL); - - // Store the info into the file entry - pFileEntry->EncodingKey = EncodingKey; - pFileEntry->FileNameHash = CalcFileNameHash(szFileName); - pFileEntry->dwFileName = (DWORD)Array_IndexOf(&pRootHandler->FileNames, szFileName); - pFileEntry->dwFlags = 0; - - // Verify collisions (debug version only) - assert(Map_FindObject(pRootHandler->pRootMap, &pFileEntry->FileNameHash, NULL) == NULL); - - // Calculate the file name hash - Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash); - - // Success - return ERROR_SUCCESS; -} - -static int ParseDirEntries_FileId1( - TRootHandler_Diablo3 * pRootHandler, - LPBYTE pbFileEntries, - DWORD dwFileEntries, - DWORD dwRootDirIndex) -{ - PDIABLO3_FILEID1_ENTRY pEntry = (PDIABLO3_FILEID1_ENTRY)pbFileEntries; - PCASC_FILE_ENTRY pFileEntry; - - // Overflow test - if((pRootHandler->FileTable.ItemCount + dwFileEntries) >= pRootHandler->FileTable.ItemCountMax) + PDIABLO3_ASSET_INFO GetAssetInfo(DWORD dwAssetIndex) { - assert(false); - return ERROR_NOT_ENOUGH_MEMORY; + if(dwAssetIndex < DIABLO3_ASSET_COUNT && Assets[dwAssetIndex].szDirectoryName != NULL) + return &Assets[dwAssetIndex]; + return NULL; } - // Parse the all ID1 entries in the file - for(DWORD i = 0; i < dwFileEntries; i++, pEntry++) + char * FindPackageName(const char * szAssetName, const char * szPlainName) { - // Insert the file entry to the global list - pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1); - assert(pFileEntry != NULL); - - // Fill the index entry - pFileEntry->EncodingKey = pEntry->EncodingKey; - pFileEntry->FileNameHash = MAKE_INDEX64(dwRootDirIndex, pEntry->FileIndex, 0); - pFileEntry->dwFlags = CASC_ENTRY_SHORT_NAME; - } - - return ERROR_SUCCESS; -} + char szFileName[MAX_PATH+1]; + size_t nLength; -static int ParseDirEntries_FileId2( - TRootHandler_Diablo3 * pRootHandler, - LPBYTE pbFileEntries, - DWORD dwFileEntries, - DWORD dwRootDirIndex) -{ - PDIABLO3_FILEID2_ENTRY pEntry = (PDIABLO3_FILEID2_ENTRY)pbFileEntries; - PCASC_FILE_ENTRY pFileEntry; - - // Overflow test - if((pRootHandler->FileTable.ItemCount + dwFileEntries) >= pRootHandler->FileTable.ItemCountMax) - { - assert(false); - return ERROR_NOT_ENOUGH_MEMORY; + // Construct the name without extension and find it in the map + nLength = CascStrPrintf(szFileName, _countof(szFileName), "%s\\%s", szAssetName, szPlainName); + return (char *)PackagesMap.FindString(szFileName, szFileName + nLength); } - // Parse the all ID1 entries in the file - for(DWORD i = 0; i < dwFileEntries; i++, pEntry++) + LPBYTE LoadFileToMemory(TCascStorage * hs, const char * szFileName, DWORD * pcbFileData) { - // Insert the file entry to the global list - pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1); - assert(pFileEntry != NULL); - - // Fill the index entry - pFileEntry->EncodingKey = pEntry->EncodingKey; - pFileEntry->FileNameHash = MAKE_INDEX64(dwRootDirIndex, pEntry->FileIndex, pEntry->SubIndex); - pFileEntry->dwFlags = CASC_ENTRY_SHORT_NAME | CASC_ENTRY_HAS_SUBINDEX; - } + PCASC_CKEY_ENTRY pCKeyEntry; + LPBYTE pbFileData = NULL; - return ERROR_SUCCESS; -} - -static int ParseDirEntries_Named( - TRootHandler_Diablo3 * pRootHandler, - LPBYTE pbFileEntries, - LPBYTE pbFileEnd, - DWORD dwFileEntries, - DWORD dwRootDirIndex) -{ - char szFileName[MAX_PATH+1]; - char * szNamePtr = szFileName; - DWORD cbFileEntry; - int nError = ERROR_SUCCESS; + // Try to find CKey for the file + pCKeyEntry = GetFile(hs, szFileName); + if(pCKeyEntry != NULL) + pbFileData = LoadInternalFileToMemory(hs, pCKeyEntry, pcbFileData); - // Overflow test - if((pRootHandler->FileTable.ItemCount + dwFileEntries) >= pRootHandler->FileTable.ItemCountMax) - { - assert(false); - return ERROR_NOT_ENOUGH_MEMORY; + return pbFileData; } - // If we the file is not in the root directory itself, - // prepare the prefix for the root directory. - if(dwRootDirIndex != DIABLO3_INVALID_INDEX) + static LPBYTE CaptureDirectoryData( + DIABLO3_DIRECTORY & DirHeader, + LPBYTE pbDirectory, + DWORD cbDirectory) { - PCASC_FILE_ENTRY pRootEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, dwRootDirIndex); - const char * szRootName = (const char *)Array_ItemAt(&pRootHandler->FileNames, pRootEntry->dwFileName); + LPBYTE pbDirectoryData = pbDirectory; + LPBYTE pbDataEnd = pbDirectory + cbDirectory; + DWORD Signature = 0; + + // + // Structure of a Diablo3 directory header + // 1) Signature (4 bytes) + // 2) Number of DIABLO3_ASSET_ENTRY entries (4 bytes) + // 3) Array of DIABLO3_ASSET_ENTRY entries + // 4) Number of DIABLO3_ASSETIDX_ENTRY entries (4 bytes) + // 5) Array of DIABLO3_ASSETIDX_ENTRY entries + // 6) Number of DIABLO3_NAMED_ENTRY entries (4 bytes) + // 7) Array of DIABLO3_NAMED_ENTRY entries + // + + // Prepare the header signature + memset(&DirHeader, 0, sizeof(DIABLO3_DIRECTORY)); + + // Get the header signature + pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &Signature); + if((pbDirectory == NULL) || (Signature != CASC_DIABLO3_ROOT_SIGNATURE && Signature != DIABLO3_SUBDIR_SIGNATURE)) + return NULL; + + // Subdirectories have extra two arrays + if(Signature == DIABLO3_SUBDIR_SIGNATURE) + { + // Capture the number of DIABLO3_ASSET_ENTRY items + pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &DirHeader.dwAssetEntries); + if(pbDirectory == NULL) + return NULL; + + // Capture the array of DIABLO3_ASSET_ENTRY + pbDirectory = CaptureArray(pbDirectory, pbDataEnd, &DirHeader.pbAssetEntries, DIABLO3_ASSET_ENTRY, DirHeader.dwAssetEntries); + if(pbDirectory == NULL) + return NULL; + + // Capture the number of DIABLO3_ASSETIDX_ENTRY items + pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &DirHeader.dwAssetIdxEntries); + if(pbDirectory == NULL) + return NULL; + + // Capture the array of DIABLO3_ASSETIDX_ENTRY + pbDirectory = CaptureArray(pbDirectory, pbDataEnd, &DirHeader.pbAssetIdxEntries, DIABLO3_ASSETIDX_ENTRY, DirHeader.dwAssetIdxEntries); + if(pbDirectory == NULL) + return NULL; + } - // Copy the root directory name - while(szRootName[0] != 0) - *szNamePtr++ = *szRootName++; + // Capture the number of DIABLO3_NAMED_ENTRY array + pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &DirHeader.dwNamedEntries); + if(pbDirectory == NULL) + return NULL; - // Append the backslash - *szNamePtr++ = '\\'; - } + // Note: Do not capture the array here. We will do that later, + // when we will be parsing the directory + DirHeader.pbNamedEntries = pbDirectory; - // Parse the file entry - while(pbFileEntries < pbFileEnd) - { - PDIABLO3_NAMED_ENTRY pNamedEntry = (PDIABLO3_NAMED_ENTRY)pbFileEntries; - DWORD cchFileName; - - // Verify the named entry whether it does not go beyond the EOF - cbFileEntry = VerifyNamedFileEntry(pbFileEntries, pbFileEnd); - if(cbFileEntry == 0) - return ERROR_FILE_CORRUPT; - - // Append the file name to the prepared file name - // This way we obtain the full name and the name lookup - // will be fully operational - memcpy(szNamePtr, pNamedEntry->szFileName, (cbFileEntry - sizeof(ENCODING_KEY))); - cchFileName = (DWORD)((szNamePtr - szFileName) + (cbFileEntry - sizeof(ENCODING_KEY))); - - // Insert the named entry to the global file table - nError = InsertFileEntry(pRootHandler, - pNamedEntry->EncodingKey, - szFileName, - cchFileName); - if(nError != ERROR_SUCCESS) - return nError; - - // Move the pointer to the next entry - pbFileEntries += cbFileEntry; + // Put the directory range + DirHeader.pbDirectoryData = pbDirectoryData; + DirHeader.pbDirectoryEnd = pbDirectoryData + cbDirectory; + return pbDirectory; } - return ERROR_SUCCESS; -} - -static void ResolveFullFileNames( - TRootHandler_Diablo3 * pRootHandler, - PDIABLO3_CORE_TOC_ENTRY pCoreTocEntries, - PCASC_MAP pPackageMap, - LPBYTE pbCoreTocFile, - DWORD dwFileIndexes) -{ - PCASC_FILE_ENTRY pFileEntry; - char * szPlainName; - char * szNamePtr; - size_t nLength; - DWORD dwRootIndex; - DWORD dwFileIndex; - DWORD dwSubIndex; - char szShortName[MAX_PATH+1]; - char szFullName[MAX_PATH+1]; - - // Keep compiler happy - CASCLIB_UNUSED(dwFileIndexes); - - // Parse the entire file table - for(size_t i = 0; i < pRootHandler->FileTable.ItemCount; i++) + LPBYTE CaptureCoreTocHeader( + PDIABLO3_CORE_TOC_HEADER * PtrHeader, + PDWORD PtrMaxIndex, + LPBYTE pbDataPtr, + LPBYTE pbDataEnd) { - // Retrieve the file entry at n-th position - pFileEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, i); + PDIABLO3_CORE_TOC_HEADER pTocHeader = (PDIABLO3_CORE_TOC_HEADER)pbDataPtr; + DWORD dwMaxFileIndex = 0; - // Skip the items that already have full name - if(pFileEntry->dwFlags & CASC_ENTRY_SHORT_NAME) - { - // Retrieve the file index of that file - dwRootIndex = INDEX64_ROOT_INDEX(pFileEntry->FileNameHash); - dwFileIndex = INDEX64_FILE_INDEX(pFileEntry->FileNameHash); - dwSubIndex = (pFileEntry->dwFlags & CASC_ENTRY_HAS_SUBINDEX) ? INDEX64_SUB_INDEX(pFileEntry->FileNameHash) : DIABLO3_INVALID_INDEX; - assert(dwFileIndex < dwFileIndexes); - - // Get the plain name of the file - szPlainName = (char *)(pbCoreTocFile + pCoreTocEntries[dwFileIndex].NameOffset); - - // Create the short file name - nLength = CreateShortName(pPackageMap, - dwRootIndex, - pCoreTocEntries[dwFileIndex].AssetIndex, - szPlainName, - dwSubIndex, - szShortName); - - // Insert the short name to the list of the names - szNamePtr = (char *)Array_Insert(&pRootHandler->FileNames, szShortName, nLength + 1); - pFileEntry->dwFileName = (DWORD)Array_IndexOf(&pRootHandler->FileNames, szNamePtr); - - // Create the full file name - nLength = CreateFileName(pRootHandler, szShortName, szFullName); - pFileEntry->FileNameHash = CalcFileNameHash(szFullName); - - // Insert the entry to the name map. Use the mapping of FullName -> FileHash - Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash); - } - } -} + // Check the space for header + if((pbDataPtr + sizeof(DIABLO3_CORE_TOC_HEADER)) > pbDataEnd) + return NULL; + pbDataPtr += sizeof(DIABLO3_CORE_TOC_HEADER); -static LPBYTE LoadFileToMemory(TCascStorage * hs, LPBYTE pbEncodingKey, DWORD * pcbFileData) -{ - QUERY_KEY EncodingKey; - LPBYTE pbFileData = NULL; - HANDLE hFile; - DWORD cbBytesRead = 0; - DWORD cbFileData = 0; - - // Open the file by encoding key - EncodingKey.pbData = pbEncodingKey; - EncodingKey.cbData = MD5_HASH_SIZE; - if(CascOpenFileByEncodingKey((HANDLE)hs, &EncodingKey, 0, &hFile)) - { - // Retrieve the file size - cbFileData = CascGetFileSize(hFile, NULL); - if(cbFileData > 0) + // Verify all asset arrays + for(size_t i = 0; i < DIABLO3_MAX_ASSETS; i++) { - pbFileData = CASC_ALLOC(BYTE, cbFileData); - if(pbFileData != NULL) + PDIABLO3_CORE_TOC_ENTRY pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbDataPtr + pTocHeader->EntryOffsets[i]); + DWORD EntryOffset = pTocHeader->EntryOffsets[i]; + DWORD EntryCount = pTocHeader->EntryCounts[i]; + + // Verify file range + if((pbDataPtr + EntryOffset + EntryCount * sizeof(DIABLO3_CORE_TOC_ENTRY)) > pbDataEnd) + return NULL; + + // Find out the entry with the maximum index + for(DWORD n = 0; n < EntryCount; n++) { - CascReadFile(hFile, pbFileData, cbFileData, &cbBytesRead); + if(pTocEntry->FileIndex >= dwMaxFileIndex) + dwMaxFileIndex = pTocEntry->FileIndex; + pTocEntry++; } } - // Close the file - CascCloseFile(hFile); + // Give data and return + PtrMaxIndex[0] = dwMaxFileIndex; + PtrHeader[0] = pTocHeader; + return pbDataPtr; } - // Give the file to the caller - if(pcbFileData != NULL) - pcbFileData[0] = cbBytesRead; - return pbFileData; -} - -static LPBYTE LoadFileToMemory(TCascStorage * hs, const char * szFileName, DWORD * pcbFileData) -{ - LPBYTE pbEncodingKey = NULL; - LPBYTE pbFileData = NULL; - - // Try to find encoding key for the file - pbEncodingKey = RootHandler_GetKey(hs->pRootHandler, szFileName); - if(pbEncodingKey != NULL) - pbFileData = LoadFileToMemory(hs, pbEncodingKey, pcbFileData); - - return pbFileData; -} - -static int ParseDirectoryHeader( - PDIABLO3_DIR_HEADER pDirHeader, - LPBYTE pbDirFile, - LPBYTE pbFileEnd) -{ - DWORD dwSignature = 0; - - // - // Structure of a Diablo3 directory file - // 1) Signature (4 bytes) - // 2) Number of DIABLO3_FILEID1_ENTRY entries (4 bytes) - // 3) Array of DIABLO3_FILEID1_ENTRY entries - // 4) Number of DIABLO3_FILEID2_ENTRY entries (4 bytes) - // 5) Array of DIABLO3_FILEID2_ENTRY entries - // 6) Number of DIABLO3_NAMED_ENTRY entries (4 bytes) - // 7) Array of DIABLO3_NAMED_ENTRY entries - // - - // Prepare the header signature - memset(pDirHeader, 0, sizeof(DIABLO3_DIR_HEADER)); - - // Get the signature - if((pbDirFile + sizeof(DWORD)) >= pbFileEnd) - return ERROR_BAD_FORMAT; - dwSignature = *(PDWORD)pbDirFile; - - // Check the signature - if(dwSignature != CASC_DIABLO3_ROOT_SIGNATURE && dwSignature != DIABLO3_SUBDIR_SIGNATURE) - return ERROR_BAD_FORMAT; - pbDirFile += sizeof(DWORD); - - // Subdirectories have extra two arrays - if(dwSignature == DIABLO3_SUBDIR_SIGNATURE) + LPBYTE CaptureNamedEntry( + LPBYTE pbDataPtr, + LPBYTE pbDataEnd, + PDIABLO3_NAMED_ENTRY pEntry) { - // Get the number of DIABLO3_FILEID1_ENTRY items - if((pbDirFile + sizeof(DWORD)) >= pbFileEnd) - return ERROR_BAD_FORMAT; - pDirHeader->dwEntries1 = *(PDWORD)pbDirFile; - - // Get the array of DIABLO3_FILEID1_ENTRY - pDirHeader->pbEntries1 = (pbDirFile + sizeof(DWORD)); - pbDirFile = pbDirFile + sizeof(DWORD) + pDirHeader->dwEntries1 * sizeof(DIABLO3_FILEID1_ENTRY); - - // Get the number of DIABLO3_FILEID2_ENTRY items - if((pbDirFile + sizeof(DWORD)) >= pbFileEnd) - return ERROR_BAD_FORMAT; - pDirHeader->dwEntries2 = *(PDWORD)pbDirFile; - - // Get the array of DIABLO3_FILEID2_ENTRY - pDirHeader->pbEntries2 = (pbDirFile + sizeof(DWORD)); - pbDirFile = pbDirFile + sizeof(DWORD) + pDirHeader->dwEntries2 * sizeof(DIABLO3_FILEID2_ENTRY); - } + // Capture the content key + pbDataPtr = CaptureContentKey(pbDataPtr, pbDataEnd, &pEntry->pCKey); + if(pbDataPtr == NULL) + return NULL; + + // Capture file name. Must be ASCIIZ file name + pEntry->szFileName = (const char *)pbDataPtr; + while(pbDataPtr < pbDataEnd && pbDataPtr[0] != 0) + pbDataPtr++; + + // Did we find a zero char? + if(pbDataPtr < pbDataEnd && pbDataPtr[0] == 0) + { + pEntry->szFileEnd = (const char *)pbDataPtr; + return pbDataPtr + 1; + } - // Get the pointer and length DIABLO3_NAMED_ENTRY array - if((pbDirFile + sizeof(DWORD)) >= pbFileEnd) - return ERROR_BAD_FORMAT; - pDirHeader->dwEntries3 = *(PDWORD)pbDirFile; - pDirHeader->pbEntries3 = (pbDirFile + sizeof(DWORD)); - return ERROR_SUCCESS; -} + return NULL; + } -static DWORD ScanDirectoryFile( - TCascStorage * hs, - LPBYTE pbRootFile, - LPBYTE pbFileEnd) -{ - PDIABLO3_NAMED_ENTRY pNamedEntry; - DIABLO3_DIR_HEADER RootHeader; - DIABLO3_DIR_HEADER DirHeader; - LPBYTE pbSubDir; - DWORD dwTotalFileCount; - DWORD cbNamedEntry; - DWORD cbSubDir; - int nError; - - // Parse the directory header in order to retrieve the items - nError = ParseDirectoryHeader(&RootHeader, pbRootFile, pbFileEnd); - if(nError != ERROR_SUCCESS) - return 0; - - // Add the root directory's entries - dwTotalFileCount = RootHeader.dwEntries1 + RootHeader.dwEntries2 + RootHeader.dwEntries3; - - // Parse the named entries - for(DWORD i = 0; i < RootHeader.dwEntries3; i++) + int LoadDirectoryFile(TCascStorage * hs, DIABLO3_DIRECTORY & DirHeader, PCASC_CKEY_ENTRY pCKeyEntry) { - // Get the this named entry - if((cbNamedEntry = VerifyNamedFileEntry(RootHeader.pbEntries3, pbFileEnd)) == 0) - return 0; - pNamedEntry = (PDIABLO3_NAMED_ENTRY)RootHeader.pbEntries3; - RootHeader.pbEntries3 += cbNamedEntry; - - // Load the subdirectory to memory - pbSubDir = LoadFileToMemory(hs, pNamedEntry->EncodingKey.Value, &cbSubDir); - if(pbSubDir != NULL) + LPBYTE pbData; + DWORD cbData = 0; + + // Load the n-th folder + pbData = LoadInternalFileToMemory(hs, pCKeyEntry, &cbData); + if(pbData && cbData) { - // Count the files in the subdirectory - if(ParseDirectoryHeader(&DirHeader, pbSubDir, pbSubDir + cbSubDir) == ERROR_SUCCESS) + if(CaptureDirectoryData(DirHeader, pbData, cbData) == NULL) { - dwTotalFileCount += DirHeader.dwEntries1 + DirHeader.dwEntries2 + DirHeader.dwEntries3; + // Clear the directory + CASC_FREE(pbData); + return ERROR_BAD_FORMAT; } - - // Free the subdirectory - CASC_FREE(pbSubDir); } + return ERROR_SUCCESS; } - // Return the total number of entries - return dwTotalFileCount; -} + bool CreateAssetFileName( + PATH_BUFFER & 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; + + // Find and check the entry + pTocEntry = pFileIndices + FileIndex; + if(pTocEntry->FileIndex == FileIndex) + { + // Retrieve the asset information + 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; + } -static int ParseDirectoryFile( - TRootHandler_Diablo3 * pRootHandler, - LPBYTE pbDirFile, - LPBYTE pbFileEnd, - DWORD dwRootDirIndex) -{ - DIABLO3_DIR_HEADER DirHeader; - int nError; + // 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); + + // Try to fixup the file extension from the package name. + // File extensions are not predictable because for subitems, + // they are not always equal to the main items: + // + // SoundBank\3D Ambience.sbk + // SoundBank\3D Ambience\0000.smp + // SoundBank\3D Ambience\0002.smp + // ... + // SoundBank\Angel.sbk + // SoundBank\Angel\0000.fsb + // SoundBank\Angel\0002.fsb + // + // We use the Base\Data_D3\PC\Misc\Packages.dat for real file extensions, where possible + // + + if(PackagesMap.IsInitialized() && pAssetInfo != NULL) + { + // Retrieve the asset name + szPackageName = FindPackageName(pAssetInfo->szDirectoryName, szPathPtr); + if(szPackageName != NULL) + { + CascStrCopy(PathBuffer.szPtr, (PathBuffer.szEnd - PathBuffer.szPtr), szPackageName); + return true; + } + } - // Sanity checks - assert(pRootHandler->FileTable.ItemArray != NULL); - assert(pRootHandler->FileTable.ItemCount < pRootHandler->FileTable.ItemCountMax); + // Use the extension from the AssetInfo, if we have any + if(pAssetInfo != NULL && pAssetInfo->szExtension != NULL) + { + szPathPtr[nLength++] = '.'; + CascStrCopy(szPathPtr + nLength, (szPathEnd - (szPathPtr + nLength)), pAssetInfo->szExtension); + return true; + } - // Parse the directory header in order to retrieve the items - nError = ParseDirectoryHeader(&DirHeader, pbDirFile, pbFileEnd); - if(nError != ERROR_SUCCESS) - return nError; + // Otherwise, supply "a##" + CascStrPrintf(szPathPtr + nLength, (szPathEnd - (szPathPtr + nLength)), ".a%02u", pTocEntry->AssetIndex); + return true; + } - // Process all DIABLO3_FILEID1_ENTRY entries. These are for files - // belonging to an asset group, without subitem number. - // Example: "SoundBank\SoundFile.smp" - // We skip inserting them to the name map, because the names are not known yet - if(DirHeader.pbEntries1 && DirHeader.dwEntries1) - { - assert(dwRootDirIndex != DIABLO3_INVALID_INDEX); - nError = ParseDirEntries_FileId1(pRootHandler, DirHeader.pbEntries1, DirHeader.dwEntries1, dwRootDirIndex); - if(nError != ERROR_SUCCESS) - return nError; + return false; } - // Parse all DIABLO3_FILEID2_ENTRY entries. These are for files - // belonging to an asset group, with a subitem number. - // Example: "SoundBank\SoundFile\0001.smp" - // We skip inserting them to the name map, because the names are not known yet - if(DirHeader.pbEntries2 && DirHeader.dwEntries2) + // Parse the asset entries + int ParseAssetEntries( + TCascStorage * hs, + DIABLO3_DIRECTORY & Directory, + PATH_BUFFER & PathBuffer) { - assert(dwRootDirIndex != DIABLO3_INVALID_INDEX); - nError = ParseDirEntries_FileId2(pRootHandler, DirHeader.pbEntries2, DirHeader.dwEntries2, dwRootDirIndex); - if(nError != ERROR_SUCCESS) - return nError; - } + PDIABLO3_ASSET_ENTRY pEntry = (PDIABLO3_ASSET_ENTRY)Directory.pbAssetEntries; + PCASC_CKEY_ENTRY pCKeyEntry; + DWORD dwEntries = Directory.dwAssetEntries; + // Do nothing if there is no entries + if(pEntry != NULL && dwEntries != 0) + { + // Insert all asset entries to the file tree + for(DWORD i = 0; i < dwEntries; i++, pEntry++) + { + pCKeyEntry = FindCKeyEntry_CKey(hs, pEntry->CKey.Value); + if(pCKeyEntry != NULL) + { + // Construct the full path name of the entry + if(CreateAssetFileName(PathBuffer, pEntry->FileIndex, CASC_INVALID_INDEX)) + { + // Insert the entry to the file tree + FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin); + } + } + } + } - // Parse all named entries. These are for files with arbitrary names, - // and they do not belong to an asset. - if(DirHeader.pbEntries3 && DirHeader.dwEntries3) - { - nError = ParseDirEntries_Named(pRootHandler, DirHeader.pbEntries3, pbFileEnd, DirHeader.dwEntries3, dwRootDirIndex); - if(nError != ERROR_SUCCESS) - return nError; + return ERROR_SUCCESS; } - // Give the directory to the caller - return nError; -} - -static int ParseCoreTOC( - TRootHandler_Diablo3 * pRootHandler, - PCASC_MAP pPackageMap, - LPBYTE pbCoreTocFile, - LPBYTE pbCoreTocEnd) -{ - PDIABLO3_CORE_TOC_HEADER pTocHeader; - PDIABLO3_CORE_TOC_ENTRY pSortedEntries; - PDIABLO3_CORE_TOC_ENTRY pTocEntry; - LPBYTE pbCoreTocNames; - DWORD dwFileIndexes = 0; - DWORD i; - - // Check the space for header - if((pbCoreTocFile + sizeof(DIABLO3_CORE_TOC_HEADER)) > pbCoreTocEnd) - return ERROR_FILE_CORRUPT; - pTocHeader = (PDIABLO3_CORE_TOC_HEADER)pbCoreTocFile; - pbCoreTocFile += sizeof(DIABLO3_CORE_TOC_HEADER); - - // Calculate space needed for allocation - for(i = 0; i < DIABLO3_MAX_ASSETS; i++) + int ParseAssetAndIdxEntries( + TCascStorage * hs, + DIABLO3_DIRECTORY & Directory, + PATH_BUFFER & PathBuffer) { - // Get the first entry - pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbCoreTocFile + pTocHeader->EntryOffsets[i]); + PDIABLO3_ASSETIDX_ENTRY pEntry = (PDIABLO3_ASSETIDX_ENTRY)Directory.pbAssetIdxEntries; + PCASC_CKEY_ENTRY pCKeyEntry; + DWORD dwEntries = Directory.dwAssetIdxEntries; - // Find out the entry with the maximum index - for(DWORD n = 0; n < pTocHeader->EntryCounts[i]; n++) + // Do nothing if there is no entries + if(pEntry != NULL && dwEntries != 0) { - if(pTocEntry->FileIndex >= dwFileIndexes) - dwFileIndexes = pTocEntry->FileIndex + 1; - pTocEntry++; + // Insert all asset entries to the file tree + for(DWORD i = 0; i < dwEntries; i++, pEntry++) + { + pCKeyEntry = FindCKeyEntry_CKey(hs, pEntry->CKey.Value); + if(pCKeyEntry != NULL) + { + // Construct the full path name of the entry + if(CreateAssetFileName(PathBuffer, pEntry->FileIndex, pEntry->SubIndex)) + { + // Insert the entry to the file tree +// fprintf(fp, "%08u %04u %s\n", pEntry->FileIndex, pEntry->SubIndex, PathBuffer.szBegin); + FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin); + } + } + } } + + return ERROR_SUCCESS; } - // Allocate and populate the array of DIABLO3_CORE_TOC_ENTRYs - pSortedEntries = CASC_ALLOC(DIABLO3_CORE_TOC_ENTRY, dwFileIndexes); - if(pSortedEntries != NULL) + // Parse the named entries of all folders + int ParseDirectory_Phase1( + TCascStorage * hs, + DIABLO3_DIRECTORY & Directory, + PATH_BUFFER & PathBuffer, + bool bIsRootDirectory) { - // Initialize all entries to invalid - memset(pSortedEntries, 0xFF, dwFileIndexes * sizeof(DIABLO3_CORE_TOC_ENTRY)); + DIABLO3_NAMED_ENTRY NamedEntry; + char * szSavePtr = PathBuffer.szPtr; + size_t nFolderIndex = 0; + int nError = ERROR_SUCCESS; - // Populate the linear array with the entries - for(i = 0; i < DIABLO3_MAX_ASSETS; i++) + // Do nothing if there is no named headers + if(Directory.pbNamedEntries && Directory.dwNamedEntries) { - // Set the pointers - pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbCoreTocFile + pTocHeader->EntryOffsets[i]); - pbCoreTocNames = (LPBYTE)(pTocEntry + pTocHeader->EntryCounts[i]); - - // Setup the entries - for(DWORD n = 0; n < pTocHeader->EntryCounts[i]; n++) + PCASC_CKEY_ENTRY pCKeyEntry; + PCASC_FILE_NODE pFileNode; + LPBYTE pbDataPtr = Directory.pbNamedEntries; + LPBYTE pbDataEnd = Directory.pbDirectoryEnd; + DWORD dwNodeIndex; + + // Parse all entries + while(pbDataPtr < pbDataEnd) { - pSortedEntries[pTocEntry->FileIndex].AssetIndex = pTocEntry->AssetIndex; - pSortedEntries[pTocEntry->FileIndex].FileIndex = pTocEntry->FileIndex; - pSortedEntries[pTocEntry->FileIndex].NameOffset = (DWORD)(pbCoreTocNames - pbCoreTocFile) + pTocEntry->NameOffset; - pTocEntry++; + // Capture the named entry + pbDataPtr = CaptureNamedEntry(pbDataPtr, pbDataEnd, &NamedEntry); + if(pbDataPtr == NULL) + return ERROR_BAD_FORMAT; + + // Append the path fragment to the total path + AppendPathToTotalPath(PathBuffer, NamedEntry.szFileName, NamedEntry.szFileEnd); + + // 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); + 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; + + // Parse the sub-directory file + nError = ParseDirectory_Phase1(hs, RootFolders[nFolderIndex], PathBuffer, false); + if(nError != ERROR_SUCCESS) + return nError; + + // Also save the item pointer and increment the folder index + RootFolders[nFolderIndex].dwNodeIndex = dwNodeIndex; + nFolderIndex++; + } + + // Restore the path pointer + PathBuffer.szPtr = szSavePtr; + szSavePtr[0] = 0; + } } } - // Now use the linear array to resolve the asset indexes and plain names - ResolveFullFileNames(pRootHandler, pSortedEntries, pPackageMap, pbCoreTocFile, dwFileIndexes); - CASC_FREE(pSortedEntries); + return nError; } - return ERROR_SUCCESS; -} - -//----------------------------------------------------------------------------- -// Implementation of Diablo III root file - -static int D3Handler_Insert(TRootHandler_Diablo3 * pRootHandler, const char * szFileName, LPBYTE pbEncodingKey) -{ - ENCODING_KEY EncodingKey; - DWORD dwFileIndex; - - // Don't let the number of items to overflow - if(pRootHandler->FileTable.ItemCount >= pRootHandler->FileTable.ItemCountMax) - return ERROR_NOT_ENOUGH_MEMORY; - - // Insert the item - EncodingKey = *(PENCODING_KEY)pbEncodingKey; - dwFileIndex = InsertFileEntry(pRootHandler, - EncodingKey, - szFileName, - strlen(szFileName) + 1); - return (dwFileIndex != INVALID_FILE_INDEX) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY; -} - -static LPBYTE D3Handler_Search(TRootHandler_Diablo3 * pRootHandler, TCascSearch * pSearch, PDWORD /* PtrFileSize */, PDWORD /* PtrLocaleFlags */, PDWORD /* PtrFileDataId */) -{ - PCASC_FILE_ENTRY pFileEntry; - const char * szSrcName = NULL; - - // Are we still inside the root directory range? - while(pSearch->IndexLevel1 < pRootHandler->FileTable.ItemCount) + // Parse the nameless entries of all folders + int ParseDirectory_Phase2(TCascStorage * hs) { - // Get the n-th directory and the file name - pFileEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, pSearch->IndexLevel1); - szSrcName = (char *)Array_ItemAt(&pRootHandler->FileNames, pFileEntry->dwFileName); + PATH_BUFFER PathBuffer; + char szPathBuffer[MAX_PATH]; - // This is either a full file name or an abbreviated name - if(pFileEntry->dwFlags & CASC_ENTRY_SHORT_NAME) + // Parse each root subdirectory + for(size_t i = 0; i < DIABLO3_MAX_ROOT_FOLDERS; i++) { - CreateFileName(pRootHandler, szSrcName, pSearch->szFileName); - } - else - { - strcpy(pSearch->szFileName, szSrcName); + // 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); + } + + // Array of DIABLO3_ASSET_ENTRY entries. + // These are for files belonging to an asset, without subitem number. + // Example: "SoundBank\SoundFile.smp" + ParseAssetEntries(hs, RootFolders[i], PathBuffer); + + // Array of DIABLO3_ASSETIDX_ENTRY entries. + // These are for files belonging to an asset, with a subitem number. + // Example: "SoundBank\SoundFile\0001.smp" + ParseAssetAndIdxEntries(hs, RootFolders[i], PathBuffer); + } } - // Prepare for the next search - pSearch->IndexLevel1++; - return pFileEntry->EncodingKey.Value; + return ERROR_SUCCESS; } - // No more entries - return NULL; -} + // 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) + { + PDIABLO3_CORE_TOC_HEADER pTocHeader = NULL; + LPBYTE pbCoreTocPtr = pbCoreTocFile; + DWORD dwMaxFileIndex = 0; + int nError = ERROR_CAN_NOT_COMPLETE; -static void D3Handler_EndSearch(TRootHandler_Diablo3 * /* pRootHandler */, TCascSearch * /* pSearch */) -{ - // Do nothing -} + // Load the entire file to memory + pbCoreTocFile = pbCoreTocPtr = LoadFileToMemory(hs, szFileName, &cbCoreTocFile); + if(pbCoreTocFile && cbCoreTocFile) + { + LPBYTE pbCoreTocEnd = pbCoreTocFile + cbCoreTocFile; -static LPBYTE D3Handler_GetKey(TRootHandler_Diablo3 * pRootHandler, const char * szFileName) -{ - PCASC_FILE_ENTRY pFileEntry; - ULONGLONG FileNameHash = CalcFileNameHash(szFileName); + // Capture the header + if((pbCoreTocPtr = CaptureCoreTocHeader(&pTocHeader, &dwMaxFileIndex, pbCoreTocPtr, pbCoreTocEnd)) == NULL) + return ERROR_BAD_FORMAT; - // Find the file in the name table - pFileEntry = (PCASC_FILE_ENTRY)Map_FindObject(pRootHandler->pRootMap, &FileNameHash, NULL); - return (pFileEntry != NULL) ? pFileEntry->EncodingKey.Value : NULL; -} + // If there are no indices, return NULL + if(dwMaxFileIndex == 0) + return ERROR_SUCCESS; -static DWORD D3Handler_GetFileId(TRootHandler_Diablo3 * /* pRootHandler */, const char * /* szFileName */) -{ - // Not implemented for D3 - return 0; -} + // Allocate and populate the array of DIABLO3_CORE_TOC_ENTRYs + pFileIndices = CASC_ALLOC(DIABLO3_CORE_TOC_ENTRY, dwMaxFileIndex + 1); + if(pFileIndices != NULL) + { + // Initialize all entries to invalid + memset(pFileIndices, 0xFF, (dwMaxFileIndex + 1) * sizeof(DIABLO3_CORE_TOC_ENTRY)); + + // Populate the linear array with the file indices + for(size_t i = 0; i < DIABLO3_MAX_ASSETS; i++) + { + PDIABLO3_CORE_TOC_ENTRY pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbCoreTocPtr + pTocHeader->EntryOffsets[i]); + LPBYTE pbCoreTocNames = (LPBYTE)(pTocEntry + pTocHeader->EntryCounts[i]); + + // Setup the entries + for(DWORD n = 0; n < pTocHeader->EntryCounts[i]; n++) + { + DWORD dwFileIndex = pTocEntry->FileIndex; + + pFileIndices[dwFileIndex].AssetIndex = pTocEntry->AssetIndex; + pFileIndices[dwFileIndex].FileIndex = pTocEntry->FileIndex; + pFileIndices[dwFileIndex].NameOffset = (DWORD)(pbCoreTocNames - pbCoreTocPtr) + pTocEntry->NameOffset; + pTocEntry++; + } + } + + // Save the file to the root handler + pbCoreTocData = pbCoreTocPtr; + nFileIndices = dwMaxFileIndex; + nError = ERROR_SUCCESS; + } + } + return nError; + } -static void D3Handler_Close(TRootHandler_Diablo3 * pRootHandler) -{ - if(pRootHandler != NULL) + // Packages.dat contains a list of full file names (without locale prefix). + // They are not sorted, nor they correspond to file IDs. + // Does the sort order mean something? Perhaps we could use them as listfile? + int CreateMapOfRealNames(TCascStorage * hs, const char * szFileName) { - // Free the file map - Map_Free(pRootHandler->pRootMap); + DWORD Signature = 0; + DWORD NumberOfNames = 0; - // Free the array of the file entries and file names - Array_Free(&pRootHandler->FileTable); - Array_Free(&pRootHandler->FileNames); + // Load the entire file to memory + pbPackagesDat = LoadFileToMemory(hs, szFileName, &cbPackagesDat); + if(pbPackagesDat && cbPackagesDat) + { + 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; + if((pbPackagesPtr = CaptureInteger32(pbPackagesPtr, pbPackagesEnd, &NumberOfNames)) == NULL) + return ERROR_BAD_FORMAT; + if(Signature != DIABLO3_PACKAGES_SIGNATURE || NumberOfNames == 0) + return ERROR_BAD_FORMAT; + + // Create the map for fast search of the file name + if(PackagesMap.Create(NumberOfNames, 0, 0, KeyIsString) == ERROR_SUCCESS) + { + const char * szPackageName = (const char *)pbPackagesPtr; + + // Go as long as there is something + for(DWORD i = 0; i < NumberOfNames; i++) + { + // Get the file extension + if((LPBYTE)szPackageName >= pbPackagesEnd) + break; + + // Insert the file name to the map. The file extension is not included + PackagesMap.InsertString(szPackageName, true); + szPackageName = szPackageName + strlen(szPackageName) + 1; + } + } + } - // Free the root file itself - CASC_FREE(pRootHandler); + return ERROR_SUCCESS; } -} -/* -static void DumpRootFile(TDumpContext * dc, LPBYTE pbFileData, LPBYTE pbFileDataEnd) -{ - char szMD5Buffer[MD5_STRING_SIZE+1]; - DWORD dwSignature; - DWORD dwItemCount; - DWORD i; - - dwSignature = *(PDWORD)pbFileData; - if(dwSignature != CASC_DIABLO3_SUBDIR_SIGNATURE) - return; - pbFileData += sizeof(DWORD); - - // Dump items that contain EncodingKey + AssetId - dwItemCount = *(PDWORD)pbFileData; - pbFileData += sizeof(DWORD); - for(i = 0; i < dwItemCount; i++) + int Load(TCascStorage * hs, DIABLO3_DIRECTORY & RootDirectory) { - PCASC_DIABLO3_ASSET_ENTRY pEntry = (PCASC_DIABLO3_ASSET_ENTRY)pbFileData; + 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; + + // 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) + { + // 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) + { + // The file "Base\Data_D3\PC\Misc\Packages.dat" contains the file names + // (without level-0 and level-1 directory). + // We can use these names for supplying the missing extensions + CreateMapOfRealNames(hs, "Base\\Data_D3\\PC\\Misc\\Packages.dat"); + + // Now parse all folders and resolve the full names + ParseDirectory_Phase2(hs); + } - if((pbFileData + sizeof(*pEntry)) > pbFileDataEnd) - return; - pbFileData += sizeof(*pEntry); + // Free all stuff that was used during loading of the ROOT file + FreeLoadingStuff(); + } - dump_print(dc, "%s %08X\n", StringFromMD5(pEntry->EncodingKey, szMD5Buffer), pEntry->AssetId); + return nError; } - // Terminate with two newlines - dump_print(dc, "\n"); - - // Dump items that contain EncodingKey + AssetId + FileNumber - dwItemCount = *(PDWORD)pbFileData; - pbFileData += sizeof(DWORD); - for(i = 0; i < dwItemCount; i++) + void FreeLoadingStuff() { - PCASC_DIABLO3_ASSET_ENTRY2 pEntry = (PCASC_DIABLO3_ASSET_ENTRY2)pbFileData; + // Free the captured root sub-directories + for(size_t i = 0; i < DIABLO3_MAX_SUBDIRS; i++) + CASC_FREE(RootFolders[i].pbDirectoryData); - if((pbFileData + sizeof(*pEntry)) > pbFileDataEnd) - return; - pbFileData += sizeof(*pEntry); + // Free the package map + PackagesMap.Free(); - dump_print(dc, "%s %08X %08X\n", StringFromMD5((LPBYTE)pEntry->EncodingKey, szMD5Buffer), pEntry->AssetId, pEntry->FileNumber); - } - - // Terminate with two newlines - dump_print(dc, "\n"); + // Free the array of file indices + CASC_FREE(pFileIndices); - // Dump items that contain EncodingKey + FileName - dwItemCount = *(PDWORD)pbFileData; - pbFileData += sizeof(DWORD); - for(i = 0; i < dwItemCount; i++) - { - PDIABLO3_NAMED_ENTRY pEntry = (PDIABLO3_NAMED_ENTRY)pbFileData; - DWORD dwEntrySize = VerifyNamedFileEntry(pbFileData, pbFileDataEnd); + // Free the loaded CoreTOC.dat file + CASC_FREE(pbCoreTocFile); - if((pbFileData + dwEntrySize) > pbFileDataEnd) - return; - pbFileData += dwEntrySize; - - dump_print(dc, "%s %s\n", StringFromMD5((LPBYTE)pEntry->EncodingKey, szMD5Buffer), pEntry->szFileName); + // Free the loaded Packages.dat file + CASC_FREE(pbPackagesDat); } - dump_print(dc, "\n\n"); -} -*/ + // Array of root directory subdirectories + DIABLO3_DIRECTORY RootFolders[DIABLO3_MAX_ROOT_FOLDERS]; + + // Array of DIABLO3_TOC_ENTRY structures, sorted by the file index + // Used for converting FileIndex -> Asset+PlainName during loading + PDIABLO3_CORE_TOC_ENTRY pFileIndices; + LPBYTE pbCoreTocFile; + LPBYTE pbCoreTocData; + size_t nFileIndices; + DWORD cbCoreTocFile; + + // Map for searching a real file extension + CASC_MAP PackagesMap; + LPBYTE pbPackagesDat; + DWORD cbPackagesDat; +}; + //----------------------------------------------------------------------------- // Public functions int RootHandler_CreateDiablo3(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) { - TRootHandler_Diablo3 * pRootHandler; - PCASC_MAP pPackageMap = NULL; - LPBYTE pbRootFileEnd = pbRootFile + cbRootFile; - LPBYTE pbPackagesDat = NULL; - DWORD dwTotalFileCount; - DWORD cbPackagesDat = 0; - int nError; - - // Allocate the root handler object - hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_Diablo3, 1); - if(pRootHandler == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Fill-in the handler functions - memset(pRootHandler, 0, sizeof(TRootHandler_Diablo3)); - pRootHandler->Insert = (ROOT_INSERT)D3Handler_Insert; - pRootHandler->Search = (ROOT_SEARCH)D3Handler_Search; - pRootHandler->EndSearch = (ROOT_ENDSEARCH)D3Handler_EndSearch; - pRootHandler->GetKey = (ROOT_GETKEY)D3Handler_GetKey; - pRootHandler->Close = (ROOT_CLOSE)D3Handler_Close; - pRootHandler->GetFileId = (ROOT_GETFILEID)D3Handler_GetFileId; - - // Fill-in the flags - pRootHandler->dwRootFlags |= ROOT_FLAG_HAS_NAMES; - - // Scan the total number of files in the root directories - // Reserve space for extra files - dwTotalFileCount = ScanDirectoryFile(hs, pbRootFile, pbRootFileEnd); - if(dwTotalFileCount == 0) - return ERROR_FILE_CORRUPT; - dwTotalFileCount += CASC_EXTRA_FILES; - - // Allocate the global linear file table - // Note: This is about 18 MB of memory for Diablo III PTR build 30013 - nError = Array_Create(&pRootHandler->FileTable, CASC_FILE_ENTRY, dwTotalFileCount); - if(nError != ERROR_SUCCESS) - return nError; + TDiabloRoot * pRootHandler = NULL; + DIABLO3_DIRECTORY RootDirectory; + int nError = ERROR_BAD_FORMAT; - // Allocate global buffer for file names. - // The size of the buffer was taken from Diablo III build 30013 - nError = Array_Create(&pRootHandler->FileNames, char, 0x01000000); - if(nError != ERROR_SUCCESS) - return nError; - - // Create map of ROOT_ENTRY -> FileEntry - pRootHandler->pRootMap = Map_Create(dwTotalFileCount, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_ENTRY, FileNameHash)); - if(pRootHandler->pRootMap == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Parse the ROOT file and insert all entries in the file table - nError = ParseDirectoryFile(pRootHandler, pbRootFile, pbRootFileEnd, DIABLO3_INVALID_INDEX); - if(nError == ERROR_SUCCESS) + // Verify the header of the ROOT file + if(TDiabloRoot::CaptureDirectoryData(RootDirectory, pbRootFile, cbRootFile) != NULL) { - size_t dwRootEntries = pRootHandler->FileTable.ItemCount; - - // We expect the number of level-0 to be less than maximum - assert(dwRootEntries < DIABLO3_MAX_SUBDIRS); - - // Now parse the all root items and load them - for(DWORD i = 0; i < dwRootEntries; i++) + // Allocate the root handler object + pRootHandler = new TDiabloRoot(); + if(pRootHandler != NULL) { - PCASC_FILE_ENTRY pRootEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, i); - - // Load the entire file to memory - pbRootFile = LoadFileToMemory(hs, pRootEntry->EncodingKey.Value, &cbRootFile); - if(pbRootFile != NULL) + // Load the root directory. If load failed, we free the object + nError = pRootHandler->Load(hs, RootDirectory); + if(nError != ERROR_SUCCESS) { - nError = ParseDirectoryFile(pRootHandler, pbRootFile, pbRootFile + cbRootFile, i); - CASC_FREE(pbRootFile); + delete pRootHandler; + pRootHandler = NULL; } } } - // Note: The file "Base\Data_D3\PC\Misc\Packages.dat" contains the names - // of the files (without level-0 and level-1 directory). We can use these - // names for supplying the missing extensions - if(nError == ERROR_SUCCESS) - { - // Load the entire file to memory - pbPackagesDat = LoadFileToMemory(hs, "Base\\Data_D3\\PC\\Misc\\Packages.dat", &cbPackagesDat); - if(pbPackagesDat != NULL) - { - pPackageMap = CreatePackageMap(pbPackagesDat, pbPackagesDat + cbPackagesDat); - } - } - - // Vast majorify of files at this moment don't have names. - // We can load the Base\CoreTOC.dat file in order - // to get directory asset indexes, file names and extensions - if(nError == ERROR_SUCCESS) - { - LPBYTE pbCoreTOC; - DWORD cbCoreTOC = 0; - - // Load the entire file to memory - pbCoreTOC = LoadFileToMemory(hs, "Base\\CoreTOC.dat", &cbCoreTOC); - if(pbCoreTOC != NULL) - { - ParseCoreTOC(pRootHandler, pPackageMap, pbCoreTOC, pbCoreTOC + cbCoreTOC); - CASC_FREE(pbCoreTOC); - } - } - - // Free the packages map - if(pPackageMap != NULL) - Map_Free(pPackageMap); - if(pbPackagesDat != NULL) - CASC_FREE(pbPackagesDat); + // Assign the root directory (or NULL) and return error + hs->pRootHandler = pRootHandler; return nError; } diff --git a/dep/CascLib/src/CascRootFile_Install.cpp b/dep/CascLib/src/CascRootFile_Install.cpp new file mode 100644 index 00000000000..a0b00f73168 --- /dev/null +++ b/dep/CascLib/src/CascRootFile_Install.cpp @@ -0,0 +1,121 @@ +/*****************************************************************************/ +/* CascRootFile_Install.cpp Copyright (c) Ladislav Zezula 2018 */ +/*---------------------------------------------------------------------------*/ +/* Support for ROOT handler based on INSTALL manifest */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 21.05.19 1.00 Lad The first version of CascRootFile_Install.cpp */ +/*****************************************************************************/ + +#define __CASCLIB_SELF__ +#include "CascLib.h" +#include "CascCommon.h" + +//----------------------------------------------------------------------------- +// Handler definitions for Starcraft I root file + +struct TRootHandler_Install : public TFileTreeRoot +{ + public: + + TRootHandler_Install() : TFileTreeRoot(0) + { + // We have file names and return CKey as result of search + 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) + { + PCASC_CKEY_ENTRY pCKeyEntry; + const char * szString; + size_t nBitmapLength; + size_t nFileCount = InHeader.EntryCount; + + // Skip the header + pbInstallFile += InHeader.HeaderLength; + + // Skip the tags + for (DWORD i = 0; i < InHeader.TagCount; i++) + { + szString = (const char *)pbInstallFile; + nBitmapLength = GetTagBitmapLength(pbInstallFile, pbInstallEnd, InHeader.EntryCount); + pbInstallFile = pbInstallFile + strlen(szString) + 1 + sizeof(USHORT) + nBitmapLength; + } + + // Load the names and insert them to the root handler + while(nFileCount > 0 && pbInstallFile < pbInstallEnd) + { + // File Name and CKey + szString = (const char *)pbInstallFile; + pbInstallFile += strlen(szString) + 1; + + // Verify whether it is a known entry + pCKeyEntry = FindCKeyEntry_CKey(hs, pbInstallFile); + pbInstallFile += MD5_HASH_SIZE + sizeof(DWORD); + + // Insert the FileName+CKey to the file tree + if (pCKeyEntry != NULL) + FileTree.InsertByName(pCKeyEntry, szString); + nFileCount--; + } + + return ERROR_SUCCESS; + } +}; + +//----------------------------------------------------------------------------- +// 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 +// + +int RootHandler_CreateInstall(TCascStorage * hs, LPBYTE pbInstallFile, DWORD cbInstallFile) +{ + CASC_INSTALL_HEADER InHeader; + TRootHandler_Install * pRootHandler = NULL; + int nError = ERROR_BAD_FORMAT; + + // Capture the header of the DOWNLOAD file + nError = TRootHandler_Install::CaptureInstallHeader(InHeader, pbInstallFile, cbInstallFile); + if (nError == 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); + hs->pRootHandler = pRootHandler; + } + } + + return nError; +} diff --git a/dep/CascLib/src/CascRootFile_Mndx.cpp b/dep/CascLib/src/CascRootFile_Mndx.cpp index bc81d088fc0..9dca8d30eb4 100644 --- a/dep/CascLib/src/CascRootFile_Mndx.cpp +++ b/dep/CascLib/src/CascRootFile_Mndx.cpp @@ -1,94 +1,102 @@ /*****************************************************************************/ -/* CascMndxRoot.cpp Copyright (c) Ladislav Zezula 2014 */ +/* CascRootFile_MNDX.cpp Copyright (c) Ladislav Zezula 2014 */ /*---------------------------------------------------------------------------*/ /* Common functions for CascLib */ /* Note: "HOTS" refers to Play.exe, v2.5.0.29049 (Heroes of the Storm Alpha) */ /*---------------------------------------------------------------------------*/ /* Date Ver Who Comment */ /* -------- ---- --- ------- */ -/* 18.05.14 1.00 Lad The first version of CascMndxRoot.cpp */ +/* 18.05.14 1.00 Lad The first version of CascRootFile_MNDX.cpp */ /*****************************************************************************/ #define __CASCLIB_SELF__ #include "CascLib.h" #include "CascCommon.h" -#include "CascMndx.h" //----------------------------------------------------------------------------- // Local defines -#define CASC_MAR_SIGNATURE 0x0052414d // 'MAR\0' +#define MNDX_MAR_SIGNATURE 0x0052414d // 'MAR\0' +#define MAR_PACKAGE_NAMES 0 // MAR with package names only +#define MAR_STRIPPED_NAMES 1 // MAR with names where packages were stripped +#define MAR_FULL_NAMES 2 // MAR with full file names +#define MAR_COUNT 3 // Maximum of 3 MAR files are supported + +#define MNDX_SEARCH_INITIALIZING 0 +#define MNDX_SEARCH_SEARCHING 2 +#define MNDX_SEARCH_FINISHED 4 + +#define MNDX_MAX_ENTRIES(type) (0xFFFFFFFF / sizeof(type)) + +#define MNDX_INVALID_SIZE_T ((size_t)(-1)) + +#define MNDX_LAST_CKEY_ENTRY 0x80000000 //----------------------------------------------------------------------------- // Local structures -typedef struct _FILE_MNDX_HEADER +typedef union _SETBITS { - DWORD Signature; // 'MNDX' - DWORD HeaderVersion; // Must be <= 2 - DWORD FormatVersion; + struct + { + DWORD Lower08 : 8; // Number of set bits in the lower 1 byte + DWORD Lower16 : 8; // Number of set bits in the lower 2 bytes + DWORD Lower24 : 8; // Number of set bits in the lower 3 bytes + DWORD Lower32 : 8; // Number of set bits in the 32-bit integer + } u; -} FILE_MNDX_HEADER, *PFILE_MNDX_HEADER; + DWORD SetBitsAll; // The total set bits mask -typedef struct _FILE_MAR_INFO +} SETBITS, *PSETBITS; + +typedef struct _HASH_ENTRY { - DWORD MarIndex; - DWORD MarDataSize; - DWORD MarDataSizeHi; - DWORD MarDataOffset; - DWORD MarDataOffsetHi; -} FILE_MAR_INFO, *PFILE_MAR_INFO; + DWORD NodeIndex; // Index of the path node + DWORD NextIndex; // ID of the first subnode in the hash table + + union + { + DWORD FragmentOffset; // Offset of the path fragment in the TPathFragmentTable + DWORD ChildTableIndex; // Starting search index for the child database (if child database is present) + char SingleChar; // If the upper 24 bits of the FragmentOffset is 0xFFFFFFFF, this single character + }; + // Otherwise --> Offset to the name fragment table +} HASH_ENTRY, *PHASH_ENTRY; -typedef struct _CASC_MNDX_INFO +typedef struct _FILE_MNDX_HEADER { - BYTE RootFileName[MD5_HASH_SIZE]; // Name (aka MD5) of the root file + DWORD Signature; // 'MNDX' DWORD HeaderVersion; // Must be <= 2 DWORD FormatVersion; - DWORD field_1C; - DWORD field_20; - DWORD MarInfoOffset; // Offset of the first MAR entry info - DWORD MarInfoCount; // Number of the MAR info entries - DWORD MarInfoSize; // Size of the MAR info entry - DWORD MndxEntriesOffset; - DWORD MndxEntriesTotal; // Total number of MNDX root entries - DWORD MndxEntriesValid; // Number of valid MNDX root entries - DWORD MndxEntrySize; // Size of one MNDX root entry - struct _MAR_FILE * pMarFile1; // File name list for the packages - struct _MAR_FILE * pMarFile2; // File name list for names stripped of package names - struct _MAR_FILE * pMarFile3; // File name list for complete names -// PCASC_ROOT_ENTRY_MNDX pMndxEntries; -// PCASC_ROOT_ENTRY_MNDX * ppValidEntries; - bool bRootFileLoaded; // true if the root info file was properly loaded - -} CASC_MNDX_INFO, *PCASC_MNDX_INFO; - -typedef struct _CASC_MNDX_PACKAGE -{ - char * szFileName; // Pointer to file name - size_t nLength; // Length of the file name -} CASC_MNDX_PACKAGE, *PCASC_MNDX_PACKAGE; +} FILE_MNDX_HEADER, *PFILE_MNDX_HEADER; -typedef struct _CASC_MNDX_PACKAGES +typedef struct _MNDX_PACKAGE { - char * szNameBuffer; // Pointer to the buffer for file names - size_t NameEntries; // Number of name entries in Names - size_t NameBufferUsed; // Number of bytes used in the name buffer - size_t NameBufferMax; // Total size of the name buffer - - CASC_MNDX_PACKAGE Packages[1]; // List of packages + char * szFileName; // Pointer to file name + size_t nLength; // Length of the file name + size_t nIndex; // Package index -} CASC_MNDX_PACKAGES, *PCASC_MNDX_PACKAGES; +} MNDX_PACKAGE, *PMNDX_PACKAGE; // Root file entry for CASC storages with MNDX root file (Heroes of the Storm) // Corresponds to the in-file structure -typedef struct _CASC_ROOT_ENTRY_MNDX +typedef struct _MNDX_CKEY_ENTRY { DWORD Flags; // High 8 bits: Flags, low 24 bits: package index - BYTE EncodingKey[MD5_HASH_SIZE]; // Encoding key for the file - DWORD FileSize; // Uncompressed file size, in bytes + BYTE CKey[MD5_HASH_SIZE]; // Content key for the file + DWORD ContentSize; // Uncompressed file size, in bytes + +} MNDX_CKEY_ENTRY, *PMNDX_CKEY_ENTRY; -} CASC_ROOT_ENTRY_MNDX, *PCASC_ROOT_ENTRY_MNDX; +typedef struct _FILE_MAR_INFO +{ + DWORD MarIndex; + DWORD MarDataSize; + DWORD MarDataSizeHi; + DWORD MarDataOffset; + DWORD MarDataOffsetHi; +} FILE_MAR_INFO, *PFILE_MAR_INFO; //----------------------------------------------------------------------------- // Local variables @@ -229,3396 +237,2745 @@ unsigned char table_1BA1818[0x800] = // Local functions - Number of set bits in an integer // HOTS: inlined -DWORD GetNumberOfSetBits(DWORD Value32) +static SETBITS GetNumberOfSetBits(DWORD Value32) { + SETBITS SetBits; + Value32 = ((Value32 >> 1) & 0x55555555) + (Value32 & 0x55555555); Value32 = ((Value32 >> 2) & 0x33333333) + (Value32 & 0x33333333); Value32 = ((Value32 >> 4) & 0x0F0F0F0F) + (Value32 & 0x0F0F0F0F); + SetBits.SetBitsAll = Value32 * 0x01010101; - return (Value32 * 0x01010101); -} - -#define GetNumbrOfSetBits32(x) (GetNumberOfSetBits(x) >> 0x18) - -//----------------------------------------------------------------------------- -// Local functions - common - -static bool RootFileRead(LPBYTE pbFilePointer, LPBYTE pbFileEnd, void * pvBuffer, size_t dwBytesToRead) -{ - // False if the file pointer is beyond the end - if(pbFilePointer > pbFileEnd) - return false; - - // False if there is not enough bytes available - if((size_t)(pbFileEnd - pbFilePointer) < dwBytesToRead) - return false; - - memcpy(pvBuffer, pbFilePointer, dwBytesToRead); - return true; -} - -//----------------------------------------------------------------------------- -// Local functions - TMndxFindResult - -// HOTS: 01956EE0 -TMndxFindResult::TMndxFindResult() -{ - szSearchMask = NULL; - cchSearchMask = 0; - field_8 = 0; - szFoundPath = NULL; - cchFoundPath = 0; - FileNameIndex = 0; - pStruct40 = NULL; + return SetBits; } -// HOTS: 01956F00 -TMndxFindResult::~TMndxFindResult() +static DWORD GetNumberOfSetBits32(DWORD Value32) { - FreeStruct40(); + return GetNumberOfSetBits(Value32).u.Lower32; } -// HOTS: 01956F30 -int TMndxFindResult::CreateStruct40() +static LPBYTE CaptureData(LPBYTE pbRootPtr, LPBYTE pbRootEnd, void * pvBuffer, size_t cbLength) { - if(pStruct40 != NULL) - return ERROR_INVALID_PARAMETER; + // Check whether there is enough data in the buffer + if((pbRootPtr + cbLength) > pbRootEnd) + return NULL; - pStruct40 = new TStruct40(); - return (pStruct40 != NULL) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY; + // Copy the data + memcpy(pvBuffer, pbRootPtr, cbLength); + return pbRootPtr + cbLength; } -void TMndxFindResult::FreeStruct40() -{ - if(pStruct40 != NULL) - delete pStruct40; - pStruct40 = NULL; -} +//----------------------------------------------------------------------------- +// The TPathStop structure -// HOTS: 01956E70 -int TMndxFindResult::SetSearchPath( - const char * szNewSearchMask, - size_t cchNewSearchMask) +struct TPathStop { - if(szSearchMask == NULL && cchSearchMask != 0) - return ERROR_INVALID_PARAMETER; + TPathStop() + { + LoBitsIndex = 0; + field_4 = 0; + Count = 0; + HiBitsIndex_PathFragment = CASC_INVALID_INDEX; + field_10 = 0xFFFFFFFF; + } - if(pStruct40 != NULL) - pStruct40->SearchPhase = CASC_SEARCH_INITIALIZING; + TPathStop(DWORD arg_0, DWORD arg_4, DWORD arg_8) + { + LoBitsIndex = arg_0; + field_4 = arg_4; + Count = arg_8; + HiBitsIndex_PathFragment = CASC_INVALID_INDEX; + field_10 = 0xFFFFFFFF; + } - szSearchMask = szNewSearchMask; - cchSearchMask = cchNewSearchMask; - return ERROR_SUCCESS; -} + DWORD LoBitsIndex; + DWORD field_4; + DWORD Count; + DWORD HiBitsIndex_PathFragment; + DWORD field_10; +}; //----------------------------------------------------------------------------- -// TByteStream functions +// Basic array implementations -// HOTS: 01959990 -TByteStream::TByteStream() +class TByteStream { - pbByteData = NULL; - pvMappedFile = NULL; - cbByteData = 0; - field_C = 0; - hFile = 0; - hMap = 0; -} + public: -// HOTS: 19599F0 -void TByteStream::ExchangeWith(TByteStream & Target) -{ - TByteStream WorkBuff; - - WorkBuff = *this; - *this = Target; - Target = WorkBuff; -} + // HOTS: 01959990 + TByteStream() + { + pbByteData = NULL; + pvMappedFile = NULL; + cbByteData = 0; + field_C = 0; + hFile = 0; + hMap = 0; + } -// HOTS: 19599F0 -int TByteStream::GetBytes(DWORD cbByteCount, PARRAY_POINTER PtrArray) -{ - if(cbByteData < cbByteCount) - return ERROR_BAD_FORMAT; + // HOTS: 19599F0 + template <typename T> + int GetBytes(size_t length, T ** Pointer) + { + // Is there enough bytes in the array? + if(length > cbByteData) + return ERROR_BAD_FORMAT; - // Give the buffer to the caller - PtrArray->Bytes = pbByteData; + // Give the buffer to the caller + Pointer[0] = (T *)(pbByteData); - // Move pointers - pbByteData += cbByteCount; - cbByteData -= cbByteCount; - return ERROR_SUCCESS; -} + // Move pointers + pbByteData += length; + cbByteData -= length; + return ERROR_SUCCESS; + } -// HOTS: 1957190 -int TByteStream::GetArray_DWORDs(PARRAY_POINTER PtrArray, DWORD ItemCount) -{ - if(PtrArray == NULL && ItemCount != 0) - return ERROR_INVALID_PARAMETER; - if(ItemCount > CASC_MAX_ENTRIES(DWORD)) - return ERROR_NOT_ENOUGH_MEMORY; + int CopyBytes(void * value, size_t length) + { + // Is there enough bytes in the array? + if(length > cbByteData) + return ERROR_BAD_FORMAT; - return GetBytes(ItemCount * 4, PtrArray); -} + // Give the buffer to the caller + memcpy(value, pbByteData, length); -// HOTS: 19571E0 -int TByteStream::GetArray_Triplets(PARRAY_POINTER PtrArray, DWORD ItemCount) -{ - if(PtrArray == NULL && ItemCount != 0) - return ERROR_INVALID_PARAMETER; - if(ItemCount > CASC_MAX_ENTRIES(TRIPLET)) - return ERROR_NOT_ENOUGH_MEMORY; + // Move pointers + pbByteData += length; + cbByteData -= length; + return ERROR_SUCCESS; + } - return GetBytes(ItemCount * sizeof(TRIPLET), PtrArray); -} + // HOTS: 1959A60 + int SkipBytes(size_t cbByteCount) + { + LPBYTE Pointer; -// HOTS: 1957230 -int TByteStream::GetArray_BYTES(PARRAY_POINTER PtrArray, DWORD ItemCount) -{ - if(PtrArray == NULL && ItemCount != 0) - return ERROR_INVALID_PARAMETER; - if(ItemCount > CASC_MAX_ENTRIES(BYTE)) - return ERROR_NOT_ENOUGH_MEMORY; + return GetBytes<BYTE>(cbByteCount, &Pointer); + } - return GetBytes(ItemCount, PtrArray); -} + // HOTS: 1959AF0 + int SetByteBuffer(LPBYTE pbNewByteData, size_t cbNewByteData) + { + if(pbNewByteData != NULL || cbNewByteData == 0) + { + pbByteData = pbNewByteData; + cbByteData = cbNewByteData; + return ERROR_SUCCESS; + } -// HOTS: 1957280 -int TByteStream::GetArray_NameTable(PARRAY_POINTER PtrArray, DWORD ItemCount) -{ - if(PtrArray == NULL && ItemCount != 0) return ERROR_INVALID_PARAMETER; - if(ItemCount > CASC_MAX_ENTRIES(NAME_FRAG)) - return ERROR_NOT_ENOUGH_MEMORY; - - return GetBytes(ItemCount * sizeof(NAME_FRAG), PtrArray); -} + } -// HOTS: 1959A60 -int TByteStream::SkipBytes(DWORD cbByteCount) -{ - ARRAY_POINTER Dummy; + // HOTS: 1957160 <DWORD> + template <typename T> + int GetValue(T & Value) + { + T * Pointer; + int nError; - return GetBytes(cbByteCount, &Dummy); -} + nError = GetBytes(sizeof(T), (LPBYTE *)(&Pointer)); + if(nError != ERROR_SUCCESS) + return nError; -// HOTS: 1959AF0 -int TByteStream::SetByteBuffer(LPBYTE pbNewByteData, DWORD cbNewByteData) -{ - if(pbNewByteData != NULL || cbNewByteData == 0) - { - pbByteData = pbNewByteData; - cbByteData = cbNewByteData; + Value = Pointer[0]; return ERROR_SUCCESS; } - return ERROR_INVALID_PARAMETER; -} - - -// HOTS: 1957160 -int TByteStream::GetValue_DWORD(DWORD & Value) -{ - ARRAY_POINTER Pointer; - int nError; - - nError = GetBytes(sizeof(DWORD), &Pointer); - if(nError != ERROR_SUCCESS) - return nError; - - Value = Pointer.Uint32s[0]; - return ERROR_SUCCESS; -} - -int TByteStream::GetValue_ItemCount(DWORD & NumberOfBytes, DWORD & ItemCount, DWORD ItemSize) -{ - ARRAY_POINTER Pointer; - ULONGLONG ByteCount; - int nError; - - // Verify if there is at least - 8 bytes - nError = GetBytes(sizeof(ULONGLONG), &Pointer); - if(nError != ERROR_SUCCESS) - return nError; - - // Extract the number of bytes - ByteCount = Pointer.Int64Ptr[0]; - if(ByteCount > 0xFFFFFFFF || (ByteCount % ItemSize) != 0) - return ERROR_BAD_FORMAT; + // Retrieves the item count in the array + template <typename T> + int GetArrayItemCount(DWORD & ArraySize, DWORD & ItemCount) + { + ULONGLONG ByteCount; + int nError; - // Give the result to the caller - NumberOfBytes = (DWORD)ByteCount; - ItemCount = (DWORD)(ByteCount / ItemSize); - return ERROR_SUCCESS; -} + // The first 8 bytes is the byte size of the array + nError = GetValue<ULONGLONG>(ByteCount); + if(nError != ERROR_SUCCESS) + return nError; -//----------------------------------------------------------------------------- -// TGenericArray functions + // Extract the number of bytes + if(ByteCount > 0xFFFFFFFF || (ByteCount % sizeof(T)) != 0) + return ERROR_BAD_FORMAT; -TGenericArray::TGenericArray() -{ - DataBuffer.Bytes = NULL; - FirstValid.Bytes = NULL; - ArrayPointer.Bytes = NULL; - ItemCount = 0; - MaxItemCount = 0; - bIsValidArray = false; -} + // Give the result to the caller + ItemCount = (DWORD)(ByteCount / sizeof(T)); + ArraySize = (DWORD)(ByteCount); + return ERROR_SUCCESS; + } -TGenericArray::~TGenericArray() -{ - if(DataBuffer.Bytes != NULL) - CASC_FREE(DataBuffer.Bytes); -} + // HOTS: 1957190: <DWORD> + // HOTS: 19571E0: <BASEVALS> + // HOTS: 1957230: <BYTE> + // HOTS: 1957280: <HASH_ENTRY> + template <typename T> + int GetArray(T ** Pointer, size_t ItemCount) + { + int nError = ERROR_SUCCESS; -// HOTS: inlined -void TGenericArray::ExchangeWith(TGenericArray & Target) -{ - TGenericArray WorkBuff; + // Verify parameters + if(Pointer == NULL && ItemCount != 0) + return ERROR_INVALID_PARAMETER; + if(ItemCount > MNDX_MAX_ENTRIES(T)) + return ERROR_NOT_ENOUGH_MEMORY; - WorkBuff = *this; - *this = Target; - Target = WorkBuff; -} + // Allocate bytes for the array + if (Pointer != NULL) + { + Pointer[0] = CASC_ALLOC(T, ItemCount); + if (Pointer[0] == NULL) + return ERROR_NOT_ENOUGH_MEMORY; -// HOTS: Inlined -void TGenericArray::CopyFrom(TGenericArray & Source) -{ - if(DataBuffer.Bytes != NULL) - CASC_FREE(DataBuffer.Bytes); - *this = Source; -} + // Get the pointer to the array + nError = CopyBytes(Pointer[0], sizeof(T) * ItemCount); + } -// HOTS: 1957090 (SetDwordsValid) -// HOTS: 19570B0 (SetTripletsValid) -// HOTS: 19570D0 (? SetBitsValid ?) -// HOTS: 19570F0 (SetNameFragmentsValid) -int TGenericArray::SetArrayValid() -{ - if(bIsValidArray != 0) - return 1; + return nError; + } - bIsValidArray = true; - return ERROR_SUCCESS; -} + LPBYTE pbByteData; + void * pvMappedFile; + size_t cbByteData; + DWORD field_C; + HANDLE hFile; + HANDLE hMap; +}; +//----------------------------------------------------------------------------- +// TGenericArray interface/implementation -// HOTS: 19575A0 -void TGenericArray::SetMaxItems_CHARS(DWORD NewMaxItemCount) +template <typename T> +class TGenericArray { - ARRAY_POINTER OldDataBuffer = DataBuffer; - ARRAY_POINTER NewDataBuffer; + public: - // Allocate new data buffer - NewDataBuffer.Chars = CASC_ALLOC(char, NewMaxItemCount); - if(NewDataBuffer.Chars != NULL) + TGenericArray() { - // Copy the old items to the buffer - for(DWORD i = 0; i < ItemCount; i++) - { - NewDataBuffer.Chars[i] = FirstValid.Chars[i]; - } + ItemArray = NULL; + ItemCount = 0; + MaxItemCount = 0; + bIsValidArray = false; } - DataBuffer = NewDataBuffer; - FirstValid = NewDataBuffer; - ArrayPointer = NewDataBuffer; - MaxItemCount = NewMaxItemCount; - CASC_FREE(OldDataBuffer.Chars); -} - -// HOTS: 1957600 -void TGenericArray::SetMaxItems_PATH_STOP(DWORD NewMaxItemCount) -{ - ARRAY_POINTER OldDataBuffer = DataBuffer; - ARRAY_POINTER NewDataBuffer; + ~TGenericArray() + { + CASC_FREE(ItemArray); + } - // Allocate new data buffer - NewDataBuffer.PathStopPtr = CASC_ALLOC(PATH_STOP, NewMaxItemCount); - if(NewDataBuffer.PathStopPtr != NULL) + T & operator[] (size_t index) { - // Copy the old items to the buffer - for(DWORD i = 0; i < ItemCount; i++) - { - NewDataBuffer.PathStopPtr[i] = FirstValid.PathStopPtr[i]; - } + assert(index < ItemCount); + return ItemArray[index]; } - DataBuffer = NewDataBuffer; - FirstValid = NewDataBuffer; - ArrayPointer = NewDataBuffer; - MaxItemCount = NewMaxItemCount; - CASC_FREE(OldDataBuffer.PathStopPtr); -} + // HOTS: 1957090 (SetDwordsValid) + // HOTS: 19570B0 (SetBaseValsValid) + // HOTS: 19570D0 (? SetBitsValid ?) + // HOTS: 19570F0 (SetPathFragmentsValid) + int SetArrayValid() + { + if(bIsValidArray != 0) + return ERROR_ALREADY_EXISTS; -// HOTS: inline -void TGenericArray::InsertOneItem_CHAR(char NewItem) -{ - DWORD NewMaxItemCount; - DWORD NewItemCount; + bIsValidArray = true; + return ERROR_SUCCESS; + } - NewItemCount = ItemCount + 1; - if(NewItemCount > MaxItemCount) + // HOTS: 19575A0 (char) + // HOTS: 1957600 (TPathStop) + void SetMaxItems(DWORD NewMaxItemCount) { - NewMaxItemCount = NewItemCount; + T * OldArray = ItemArray; + T * NewArray; - if(MaxItemCount > (NewItemCount / 2)) + // Allocate new data buffer + NewArray = CASC_ALLOC(T, NewMaxItemCount); + if(NewArray != NULL) { - if(MaxItemCount <= (CASC_MAX_ENTRIES(BYTE) / 2)) - NewMaxItemCount = MaxItemCount + MaxItemCount; - else - NewMaxItemCount = CASC_MAX_ENTRIES(BYTE); + // Copy the old items to the buffer + for(size_t i = 0; i < ItemCount; i++) + { + NewArray[i] = ItemArray[i]; + } } - SetMaxItems_CHARS(NewMaxItemCount); + ItemArray = NewArray; + MaxItemCount = NewMaxItemCount; + CASC_FREE(OldArray); } - // Put the character to the slot that has been reserved - FirstValid.Chars[ItemCount++] = NewItem; -} - -// HOTS: 1958330, inline -void TGenericArray::InsertOneItem_PATH_STOP(PATH_STOP & NewItem) -{ - DWORD NewMaxItemCount; - DWORD NewItemCount; - - NewItemCount = ItemCount + 1; - if(NewItemCount > MaxItemCount) + // HOTS: 19575A0 (char) + // HOTS: 1957600 (TPathStop) + void SetMaxItemsIf(DWORD NewMaxItemCount) { - NewMaxItemCount = NewItemCount; - - if(MaxItemCount > (NewItemCount / 2)) + if(NewMaxItemCount > MaxItemCount) { - if(MaxItemCount <= (CASC_MAX_ENTRIES(PATH_STOP) / 2)) - NewMaxItemCount = MaxItemCount + MaxItemCount; - else - NewMaxItemCount = CASC_MAX_ENTRIES(PATH_STOP); - } + if(MaxItemCount > (NewMaxItemCount / 2)) + { + if(MaxItemCount <= (MNDX_MAX_ENTRIES(T) / 2)) + NewMaxItemCount = MaxItemCount + MaxItemCount; + else + NewMaxItemCount = MNDX_MAX_ENTRIES(T); + } - SetMaxItems_PATH_STOP(NewMaxItemCount); + SetMaxItems(NewMaxItemCount); + } } - // Put the structure to the slot that has been reserved - FirstValid.PathStopPtr[ItemCount++] = NewItem; -} + // HOTS: inline <char> + // HOTS: 1958330 <TPathStop> + void Insert(T NewItem) + { + // Make sure we have enough capacity for the new item + SetMaxItemsIf(ItemCount + 1); -// HOTS: 19583A0 -void TGenericArray::sub_19583A0(DWORD NewItemCount) -{ - DWORD OldMaxItemCount = MaxItemCount; + // Put the character to the slot that has been reserved + ItemArray[ItemCount++] = NewItem; + } - if(NewItemCount > MaxItemCount) + // HOTS: 19583A0 <TPathStop> + void GrowArray(DWORD NewItemCount) { - DWORD NewMaxItemCount = NewItemCount; + DWORD OldMaxItemCount = MaxItemCount; - if(MaxItemCount > (NewItemCount / 2)) + // Make sure we have enough capacity for new items + SetMaxItemsIf(NewItemCount); + + // Initialize the newly inserted items + for(DWORD i = OldMaxItemCount; i < NewItemCount; i++) { - if(MaxItemCount <= (CASC_MAX_ENTRIES(PATH_STOP) / 2)) - NewMaxItemCount = MaxItemCount + MaxItemCount; - else - NewMaxItemCount = CASC_MAX_ENTRIES(PATH_STOP); + ItemArray[i] = T(); } - SetMaxItems_PATH_STOP(NewMaxItemCount); + ItemCount = NewItemCount; } - // Initialize the newly inserted items - for(DWORD i = OldMaxItemCount; i < NewItemCount; i++) + // HOTS: 1957440 <DWORD> + // HOTS: 19574E0 <BASEVALS> + // HOTS: 1957690 <BYTE> + // HOTS: 1957700 <HASH_ENTRY> + // HOTS: 195A220 <char> + // HOTS: 1958580 <TBitStream, DWORD> + int LoadFromStream(TByteStream & InStream) { - FirstValid.PathStopPtr[i].ItemIndex = 0; - FirstValid.PathStopPtr[i].field_4 = 0; - FirstValid.PathStopPtr[i].field_8 = 0; - FirstValid.PathStopPtr[i].field_C = 0xFFFFFFFF; - FirstValid.PathStopPtr[i].field_10 = 0xFFFFFFFF; - } - - ItemCount = NewItemCount; -} - -// HOTS: 1957440 -int TGenericArray::LoadDwordsArray(TByteStream & InStream) -{ - DWORD NumberOfBytes; - int nError; - - // Get and verify the number of items - nError = InStream.GetValue_ItemCount(NumberOfBytes, ItemCount, sizeof(DWORD)); - if(nError != ERROR_SUCCESS) - return nError; - - nError = InStream.GetArray_DWORDs(&ArrayPointer, ItemCount); - if(nError != ERROR_SUCCESS) - return nError; + DWORD NumberOfBytes; + int nError; - nError = InStream.SkipBytes((0 - (DWORD)NumberOfBytes) & 0x07); - if(nError != ERROR_SUCCESS) - return nError; - - return SetArrayValid(); -} - -// HOTS: 19574E0 -int TGenericArray::LoadTripletsArray(TByteStream & InStream) -{ - DWORD NumberOfBytes; - int nError; - - // Get and verify the number of items - nError = InStream.GetValue_ItemCount(NumberOfBytes, ItemCount, sizeof(TRIPLET)); - if(nError != ERROR_SUCCESS) - return nError; - - nError = InStream.GetArray_Triplets(&ArrayPointer, ItemCount); - if(nError != ERROR_SUCCESS) - return nError; - - nError = InStream.SkipBytes((0 - (DWORD)NumberOfBytes) & 0x07); - if(nError != ERROR_SUCCESS) - return nError; - - return SetArrayValid(); -} - -// HOTS: 1957690 -int TGenericArray::LoadByteArray(TByteStream & InStream) -{ - DWORD NumberOfBytes; - int nError; + // Get and verify the number of items + nError = InStream.GetArrayItemCount<T>(NumberOfBytes, ItemCount); + if(nError != ERROR_SUCCESS) + return nError; - // Get and verify the number of items - nError = InStream.GetValue_ItemCount(NumberOfBytes, ItemCount, sizeof(BYTE)); - if(nError != ERROR_SUCCESS) - return nError; + // Get the pointer to the array + nError = InStream.GetArray<T>(&ItemArray, ItemCount); + if(nError != ERROR_SUCCESS) + return nError; - nError = InStream.GetArray_BYTES(&ArrayPointer, ItemCount); - if(nError != ERROR_SUCCESS) - return nError; + nError = InStream.SkipBytes((0 - (DWORD)NumberOfBytes) & 0x07); + if(nError != ERROR_SUCCESS) + return nError; - nError = InStream.SkipBytes((0 - (DWORD)NumberOfBytes) & 0x07); - if(nError != ERROR_SUCCESS) - return nError; + return SetArrayValid(); + } - return SetArrayValid(); -} + T * ItemArray; + DWORD ItemCount; // Number of items in the array + DWORD MaxItemCount; // Capacity of the array + bool bIsValidArray; +}; -// HOTS: 1957700 -int TGenericArray::LoadFragmentInfos(TByteStream & InStream) +class TBitEntryArray : public TGenericArray<DWORD> { - DWORD NumberOfBytes; - int nError; + public: - // Get and verify the number of items - nError = InStream.GetValue_ItemCount(NumberOfBytes, ItemCount, sizeof(NAME_FRAG)); - if(nError != ERROR_SUCCESS) - return nError; - - nError = InStream.GetArray_NameTable(&ArrayPointer, ItemCount); - if(nError != ERROR_SUCCESS) - return nError; + TBitEntryArray() : TGenericArray() + { + BitsPerEntry = 0; + EntryBitMask = 0; + TotalEntries = 0; + } - nError = InStream.SkipBytes((0 - (DWORD)NumberOfBytes) & 0x07); - if(nError != ERROR_SUCCESS) - return nError; + ~TBitEntryArray() + {} - return SetArrayValid(); -} + DWORD GetItem(DWORD EntryIndex) + { + DWORD dwItemIndex = (EntryIndex * BitsPerEntry) >> 0x05; + DWORD dwStartBit = (EntryIndex * BitsPerEntry) & 0x1F; + DWORD dwEndBit = dwStartBit + BitsPerEntry; + DWORD dwResult; -// HOTS: 195A220 -int TGenericArray::LoadStrings(TByteStream & InStream) -{ - DWORD NumberOfBytes; - int nError; + // If the end bit index is greater than 32, + // we also need to load from the next 32-bit item + if(dwEndBit > 0x20) + { + dwResult = (ItemArray[dwItemIndex + 1] << (0x20 - dwStartBit)) | (ItemArray[dwItemIndex] >> dwStartBit); + } + else + { + dwResult = ItemArray[dwItemIndex] >> dwStartBit; + } - // Get and verify the number of items - nError = InStream.GetValue_ItemCount(NumberOfBytes, ItemCount, sizeof(char)); - if(nError != ERROR_SUCCESS) - return nError; + // Now we also need to mask the result by the bit mask + return dwResult & EntryBitMask; + } - nError = InStream.GetArray_BYTES(&ArrayPointer, ItemCount); - if(nError != ERROR_SUCCESS) - return nError; + int LoadBitsFromStream(TByteStream & InStream) + { + ULONGLONG Value64 = 0; + int nError; - nError = InStream.SkipBytes((0 - (DWORD)NumberOfBytes) & 0x07); - if(nError != ERROR_SUCCESS) - return nError; + nError = LoadFromStream(InStream); + if(nError != ERROR_SUCCESS) + return nError; - return SetArrayValid(); -} + nError = InStream.GetValue<DWORD>(BitsPerEntry); + if(nError != ERROR_SUCCESS) + return nError; + if(BitsPerEntry > 0x20) + return ERROR_BAD_FORMAT; -// HOTS: 19581C0 -int TGenericArray::LoadDwordsArray_Copy(TByteStream & InStream) -{ - TGenericArray TempArray; - int nError; + nError = InStream.GetValue<DWORD>(EntryBitMask); + if(nError != ERROR_SUCCESS) + return nError; - nError = TempArray.LoadDwordsArray(InStream); - if(nError != ERROR_SUCCESS) - return nError; + nError = InStream.GetValue<ULONGLONG>(Value64); + if(nError != ERROR_SUCCESS) + return nError; + if(Value64 > 0xFFFFFFFF) + return ERROR_BAD_FORMAT; + TotalEntries = (DWORD)Value64; - CopyFrom(TempArray); - return ERROR_SUCCESS; -} + assert((BitsPerEntry * TotalEntries) / 32 <= ItemCount); + return ERROR_SUCCESS; + } -// HOTS: 1958250 -int TGenericArray::LoadTripletsArray_Copy(TByteStream & InStream) -{ - TGenericArray TempArray; - int nError; + DWORD BitsPerEntry; + DWORD EntryBitMask; + DWORD TotalEntries; +}; - nError = TempArray.LoadTripletsArray(InStream); - if(nError != ERROR_SUCCESS) - return nError; +//----------------------------------------------------------------------------- +// TSparseArray functions - CopyFrom(TempArray); - return ERROR_SUCCESS; -} +#define INDEX_TO_GROUP(val) (val >> 9) +#define GROUP_TO_INDEX(grp) (grp << 9) -// HOTS: 1958420 -int TGenericArray::LoadBytes_Copy(TByteStream & InStream) +// For each 0x200-th bit, this contains information about amount of "1" bits +typedef struct _BASEVALS { - TGenericArray TempArray; - int nError; + DWORD BaseValue200; // Item value of every 0x200-th item - nError = TempArray.LoadByteArray(InStream); - if(nError != ERROR_SUCCESS) - return nError; + DWORD AddValue40 : 7; // For each 0x40 items (above the base200), + DWORD AddValue80 : 8; // we have extra shortcut to the item value + DWORD AddValueC0 : 8; // that is to be added to BaseValue200 + DWORD AddValue100 : 9; + DWORD AddValue140 : 9; + DWORD AddValue180 : 9; + DWORD AddValue1C0 : 9; - CopyFrom(TempArray); - return 0; -} + DWORD __xalignment : 5; // Filling +} BASEVALS, *PBASEVALS; -// HOTS: 19584F0 -int TGenericArray::LoadFragmentInfos_Copy(TByteStream & InStream) +class TSparseArray { - TGenericArray TempArray; - int nError; - - nError = TempArray.LoadFragmentInfos(InStream); - if(nError != ERROR_SUCCESS) - return nError; + public: - CopyFrom(TempArray); - return ERROR_SUCCESS; -} + TSparseArray() + { + TotalItemCount = 0; + ValidItemCount = 0; + } -// HOTS: 195A360 -int TGenericArray::LoadStringsWithCopy(TByteStream & InStream) -{ - TGenericArray TempArray; - int nError; + // HOTS: 1958630 + int LoadFromStream(TByteStream & InStream) + { + DWORD total_count = 0; + DWORD valid_count = 0; + int nError; - nError = TempArray.LoadStrings(InStream); - if(nError != ERROR_SUCCESS) - return nError; + nError = ItemBits.LoadFromStream(InStream); + if(nError != ERROR_SUCCESS) + return nError; - CopyFrom(TempArray); - return ERROR_SUCCESS; -} + nError = InStream.GetValue<DWORD>(total_count); + if(nError != ERROR_SUCCESS) + return nError; + nError = InStream.GetValue<DWORD>(valid_count); + if(nError != ERROR_SUCCESS) + return nError; + if(valid_count > total_count) + return ERROR_FILE_CORRUPT; -//----------------------------------------------------------------------------- -// TBitEntryArray functions + TotalItemCount = total_count; + ValidItemCount = valid_count; -TBitEntryArray::TBitEntryArray() -{ - BitsPerEntry = 0; - EntryBitMask = 0; - TotalEntries = 0; -} + nError = BaseVals.LoadFromStream(InStream); + if(nError != ERROR_SUCCESS) + return nError; -TBitEntryArray::~TBitEntryArray() -{} + nError = IndexToItem0.LoadFromStream(InStream); + if(nError != ERROR_SUCCESS) + return nError; -// HOTS: 01957D20 -void TBitEntryArray::ExchangeWith(TBitEntryArray & Target) -{ - TBitEntryArray WorkBuff; + nError = IndexToItem1.LoadFromStream(InStream); + if(nError != ERROR_SUCCESS) + return nError; - WorkBuff = *this; - *this = Target; - Target = WorkBuff; -} + return ERROR_SUCCESS; + } -// HOTS: 1958580 -int TBitEntryArray::LoadFromStream(TByteStream & InStream) -{ - ARRAY_POINTER Pointer; - ULONGLONG Value = 0; - int nError; + // Returns true if the array is empty + bool IsEmpty() + { + return (TotalItemCount == 0); + } - nError = LoadDwordsArray_Copy(InStream); - if(nError != ERROR_SUCCESS) - return nError; + // Returns true if the item at n-th position is present + bool IsItemPresent(size_t index) + { + // (index >> 0x05) gives the DWORD, (1 << (ItemIndex & 0x1F)) gives the bit + return (ItemBits[index >> 0x05] & (1 << (index & 0x1F))) ? true : false; + } - nError = InStream.GetBytes(sizeof(DWORD), &Pointer); - if(nError != ERROR_SUCCESS) - return nError; + // Retrieves the value of the n-th item in the sparse array. + // Note that for items that are not present, the value is equal + // to the nearest lower present value + DWORD GetItemValueAt(size_t index) + { + BASEVALS & SetBitsCount = BaseVals[index >> 0x09]; + DWORD IntValue; + DWORD BitMask; - BitsPerEntry = Pointer.Uint32s[0]; - if(BitsPerEntry > 0x20) - return ERROR_BAD_FORMAT; + // + // Since we don't want to count bits for the entire array, + // there are item value shortcuts every 0x200 items, + // and then every 0x40 items above the 0x200 base + // - nError = InStream.GetBytes(sizeof(DWORD), &Pointer); - if(nError != ERROR_SUCCESS) - return nError; - EntryBitMask = Pointer.Uint32s[0]; + // 1) We have base value for every 0x200-th item + IntValue = SetBitsCount.BaseValue200; - nError = InStream.GetBytes(sizeof(ULONGLONG), &Pointer); - if(nError == ERROR_SUCCESS) - Value = Pointer.Int64Ptr[0]; - if(Value > 0xFFFFFFFF) - return ERROR_BAD_FORMAT; - TotalEntries = (DWORD)Value; + // 2) Add the base value for each 0x40-th item above the 0x200 base + switch(((index >> 0x06) & 0x07) - 1) + { + case 0: // Add the 1st value (7 bits) + IntValue += SetBitsCount.AddValue40; + break; - assert((BitsPerEntry * TotalEntries) / 32 <= ItemCount); - return ERROR_SUCCESS; -} + case 1: // Add the 2nd value (8 bits) + IntValue += SetBitsCount.AddValue80; + break; -// HOTS: 1959300 -int TBitEntryArray::LoadFromStream_Exchange(TByteStream & InStream) -{ - TBitEntryArray TempArray; - int nError; + case 2: // Add the 3rd value (8 bits) + IntValue += SetBitsCount.AddValueC0; + break; - nError = TempArray.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + case 3: // Add the 4th value (9 bits) + IntValue += SetBitsCount.AddValue100; + break; - ExchangeWith(TempArray); - return ERROR_SUCCESS; -} + case 4: // Add the 5th value (9 bits) + IntValue += SetBitsCount.AddValue140; + break; -//----------------------------------------------------------------------------- -// TStruct40 functions + case 5: // Add the 6th value (9 bits) + IntValue += SetBitsCount.AddValue180; + break; -TStruct40::TStruct40() -{ - ItemIndex = 0; - CharIndex = 0; - ItemCount = 0; - SearchPhase = CASC_SEARCH_INITIALIZING; -} + case 6: // Add the 7th value (9 bits) + IntValue += SetBitsCount.AddValue1C0; + break; + } -// HOTS: 19586B0 -void TStruct40::InitSearchBuffers() -{ - DWORD NewMaxItemCount; + // 3) Count the bits of the higher DWORD, if the index 0x20 - 0x30 above the 0x200 base + if(index & 0x20) + IntValue += GetNumberOfSetBits32(ItemBits[(index >> 0x05) - 1]); - array_00.ItemCount = 0; + // 4) Count the bits in the current DWORD (masked by bit index mask) + BitMask = (1 << (index & 0x1F)) - 1; + return IntValue + GetNumberOfSetBits32(ItemBits[index >> 0x05] & BitMask); + } - // HOTS: 19586BD - if(array_00.MaxItemCount < 0x40) + DWORD FindGroup_Items0(DWORD index) { - // HOTS: 19586C2 - NewMaxItemCount = 0x40; + // Setup the group range to search + DWORD minGroup = (IndexToItem0[INDEX_TO_GROUP(index) + 0]) >> 9; + DWORD maxGroup = (IndexToItem0[INDEX_TO_GROUP(index) + 1] + 0x1FF) >> 9; - if(array_00.MaxItemCount > 0x20) + // Search the groups and find the BASEVALS structure + // For spans less than 10 groups, use sequential search, otherwise binary search. + if ((maxGroup - minGroup) < 10) { - if(array_00.MaxItemCount <= 0x7FFFFFFF) - NewMaxItemCount = array_00.MaxItemCount + array_00.MaxItemCount; - else - NewMaxItemCount = CASC_MAX_ENTRIES(BYTE); + // HOTS: 1959CF7 + while (index >= GROUP_TO_INDEX(minGroup) - BaseVals[minGroup + 1].BaseValue200 + 0x200) + { + // HOTS: 1959D14 + minGroup++; + } } + else + { + // HOTS: 1959D2E + while ((minGroup + 1) < maxGroup) + { + // HOTS: 1959D38 + DWORD middleValue = (maxGroup + minGroup) >> 1; - array_00.SetMaxItems_CHARS(NewMaxItemCount); - } + if (index < (maxGroup << 0x09) - BaseVals[maxGroup].BaseValue200) + { + // HOTS: 01959D4B + maxGroup = middleValue; + } + else + { + // HOTS: 1959D50 + minGroup = middleValue; + } + } + } - // HOTS: 19586E1 - // Set the new item count - PathStops.sub_19583A0(0); + return minGroup; + } - if(PathStops.MaxItemCount < 4) + DWORD FindGroup_Items1(DWORD index) { - // HOTS: 19586F2 - NewMaxItemCount = 4; + DWORD groupIndex = (index >> 0x09); + DWORD startValue = IndexToItem1[groupIndex] >> 9; + DWORD nextValue = (IndexToItem1[groupIndex + 1] + 0x1FF) >> 9; - // HOTS: 19586EA - if(PathStops.MaxItemCount > 2) + // Find the BASEVALS structure which the start index belongs to + // For less than 10 values, use sequential search. Otherwise, use binary search + if ((nextValue - startValue) < 10) { - if(PathStops.MaxItemCount <= 0x6666666) - NewMaxItemCount = PathStops.MaxItemCount + PathStops.MaxItemCount; - else - NewMaxItemCount = CASC_MAX_ENTRIES(PATH_STOP); + // HOTS: 01959F94 + while (index >= BaseVals[startValue + 1].BaseValue200) + { + // HOTS: 1959FA3 + startValue++; + } } + else + { + // Binary search (HOTS: 1959FAD) + if ((startValue + 1) < nextValue) + { + // HOTS: 1959FB4 + DWORD middleValue = (nextValue + startValue) >> 1; - // HOTS: 195870B - PathStops.SetMaxItems_PATH_STOP(NewMaxItemCount); - } - - ItemIndex = 0; - CharIndex = 0; - ItemCount = 0; - SearchPhase = CASC_SEARCH_SEARCHING; -} - -//----------------------------------------------------------------------------- -// TSparseArray functions - -TSparseArray::TSparseArray() -{ - TotalItemCount = 0; - ValidItemCount = 0; -} - -// HOTS: 1957DA0 -void TSparseArray::ExchangeWith(TSparseArray & Target) -{ - TSparseArray WorkBuff; - - WorkBuff = *this; - *this = Target; - Target = WorkBuff; -} - -// HOTS: 1958630 -int TSparseArray::LoadFromStream(TByteStream & InStream) -{ - ARRAY_POINTER Pointer; - int nError; - - nError = ItemBits.LoadDwordsArray_Copy(InStream); - if(nError != ERROR_SUCCESS) - return nError; - - nError = InStream.GetBytes(sizeof(DWORD), &Pointer); - if(nError != ERROR_SUCCESS) - return nError; - TotalItemCount = Pointer.Uint32s[0]; - - nError = InStream.GetBytes(sizeof(DWORD), &Pointer); - if(nError != ERROR_SUCCESS) - return nError; - ValidItemCount = Pointer.Uint32s[0]; - - if(ValidItemCount > TotalItemCount) - return ERROR_FILE_CORRUPT; - - nError = BaseValues.LoadTripletsArray_Copy(InStream); - if(nError != ERROR_SUCCESS) - return nError; - - nError = ArrayDwords_38.LoadDwordsArray_Copy(InStream); - if(nError != ERROR_SUCCESS) - return nError; - - nError = ArrayDwords_50.LoadDwordsArray_Copy(InStream); - if(nError != ERROR_SUCCESS) - return nError; - - return ERROR_SUCCESS; -} + if (index < BaseVals[middleValue].BaseValue200) + { + // HOTS: 1959FC4 + nextValue = middleValue; + } + else + { + // HOTS: 1959FC8 + startValue = middleValue; + } + } + } -// HOTS: 1959380 -int TSparseArray::LoadFromStream_Exchange(TByteStream & InStream) -{ - TSparseArray NewStruct68; - int nError; + return startValue; + } - nError = NewStruct68.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + // Returns the value of Item0[index] (HOTS: 1959CB0) + DWORD GetItem0(DWORD index) + { + SETBITS zeroBits; + DWORD groupIndex; + DWORD dwordIndex; + DWORD itemIndex; + DWORD bitGroup; + DWORD edx = index; - ExchangeWith(NewStruct68); - return ERROR_SUCCESS; -} +#ifdef _DEBUG + //if (TotalItemCount > 0x200) + //{ + // FILE * fp = fopen("e:\\Ladik\\Appdir\\CascLib\\doc\\mndx-sparse-array.txt", "wt"); + // Dump(fp); + // fclose(fp); + //} +#endif -// HOTS: 1959B60 -DWORD TSparseArray::GetItemValue(DWORD ItemIndex) -{ - PTRIPLET pTriplet; - DWORD DwordIndex; - DWORD BaseValue; - DWORD BitMask; + // If the index is at begin of the group, we just return the start value + if ((index & 0x1FF) == 0) + return IndexToItem0[INDEX_TO_GROUP(index)]; - // - // Divide the low-8-bits index to four parts: - // - // |-----------------------|---|------------| - // | A (23 bits) | B | C | - // |-----------------------|---|------------| - // - // A (23-bits): Index to the table (60 bits per entry) - // - // Layout of the table entry: - // |--------------------------------|-------|--------|--------|---------|---------|---------|---------|-----| - // | Base Value | val[0]| val[1] | val[2] | val[3] | val[4] | val[5] | val[6] | - | - // | 32 bits | 7 bits| 8 bits | 8 bits | 9 bits | 9 bits | 9 bits | 9 bits |5bits| - // |--------------------------------|-------|--------|--------|---------|---------|---------|---------|-----| - // - // B (3 bits) : Index of the variable-bit value in the array (val[#], see above) - // - // C (32 bits): Number of bits to be checked (up to 0x3F bits). - // Number of set bits is then added to the values obtained from A and B + // Find the group where the index belongs to + groupIndex = FindGroup_Items0(index); - // Upper 23 bits contain index to the table - pTriplet = BaseValues.TripletArray + (ItemIndex >> 0x09); - BaseValue = pTriplet->BaseValue; + // HOTS: 1959D5F + edx += BaseVals[groupIndex].BaseValue200 - (groupIndex << 0x09); + dwordIndex = (groupIndex << 4); - // Next 3 bits contain the index to the VBR - switch(((ItemIndex >> 0x06) & 0x07) - 1) - { - case 0: // Add the 1st value (7 bits) - BaseValue += (pTriplet->Value2 & 0x7F); - break; + if (edx < 0x100 - BaseVals[groupIndex].AddValue100) + { + // HOTS: 1959D8C + if (edx < 0x80 - BaseVals[groupIndex].AddValue80) + { + // HOTS: 01959DA2 + if (edx >= 0x40 - BaseVals[groupIndex].AddValue40) + { + // HOTS: 01959DB7 + dwordIndex += 2; + edx = edx + BaseVals[groupIndex].AddValue40 - 0x40; + } + } + else + { + // HOTS: 1959DC0 + if (edx < 0xC0 - BaseVals[groupIndex].AddValueC0) + { + // HOTS: 1959DD3 + dwordIndex += 4; + edx = edx + BaseVals[groupIndex].AddValue80 - 0x80; + } + else + { + // HOTS: 1959DD3 + dwordIndex += 6; + edx = edx + BaseVals[groupIndex].AddValueC0 - 0xC0; + } + } + } + else + { + // HOTS: 1959DE8 + if (edx < 0x180 - BaseVals[groupIndex].AddValue180) + { + // HOTS: 01959E00 + if (edx < 0x140 - BaseVals[groupIndex].AddValue140) + { + // HOTS: 1959E11 + dwordIndex += 8; + edx = edx + BaseVals[groupIndex].AddValue100 - 0x100; + } + else + { + // HOTS: 1959E1D + dwordIndex += 10; + edx = edx + BaseVals[groupIndex].AddValue140 - 0x140; + } + } + else + { + // HOTS: 1959E29 + if (edx < 0x1C0 - BaseVals[groupIndex].AddValue1C0) + { + // HOTS: 1959E3D + dwordIndex += 12; + edx = edx + BaseVals[groupIndex].AddValue180 - 0x180; + } + else + { + // HOTS: 1959E49 + dwordIndex += 14; + edx = edx + BaseVals[groupIndex].AddValue1C0 - 0x1C0; + } + } + } - case 1: // Add the 2nd value (8 bits) - BaseValue += (pTriplet->Value2 >> 0x07) & 0xFF; - break; + // HOTS: 1959E53: + // Calculate the number of bits set in the value of "bitGroup" + bitGroup = ~ItemBits[dwordIndex]; + zeroBits = GetNumberOfSetBits(bitGroup); - case 2: // Add the 3rd value (8 bits) - BaseValue += (pTriplet->Value2 >> 0x0F) & 0xFF; - break; + if (edx >= zeroBits.u.Lower32) + { + // HOTS: 1959ea4 + bitGroup = ~ItemBits[++dwordIndex]; + edx = edx - zeroBits.u.Lower32; + zeroBits = GetNumberOfSetBits(bitGroup); + } - case 3: // Add the 4th value (9 bits) - BaseValue += (pTriplet->Value2 >> 0x17); - break; + // Re-calculate the item index + itemIndex = (dwordIndex << 0x05); - case 4: // Add the 5th value (9 bits) - BaseValue += (pTriplet->Value3 & 0x1FF); - break; + // HOTS: 1959eea + if (edx < zeroBits.u.Lower16) + { + // HOTS: 1959EFC + if (edx >= zeroBits.u.Lower08) + { + // HOTS: 1959F05 + bitGroup >>= 0x08; + itemIndex += 0x08; + edx -= zeroBits.u.Lower08; + } + } + else + { + // HOTS: 1959F0D + if (edx < zeroBits.u.Lower24) + { + // HOTS: 1959F19 + bitGroup >>= 0x10; + itemIndex += 0x10; + edx -= zeroBits.u.Lower16; + } + else + { + // HOTS: 1959F23 + bitGroup >>= 0x18; + itemIndex += 0x18; + edx -= zeroBits.u.Lower24; + } + } - case 5: // Add the 6th value (9 bits) - BaseValue += (pTriplet->Value3 >> 0x09) & 0x1FF; - break; + // HOTS: 1959f2b + edx = edx << 0x08; + bitGroup = bitGroup & 0xFF; - case 6: // Add the 7th value (9 bits) - BaseValue += (pTriplet->Value3 >> 0x12) & 0x1FF; - break; + // BUGBUG: Possible buffer overflow here. Happens when dwItemIndex >= 0x9C. + // The same happens in Heroes of the Storm (build 29049), so I am not sure + // if this is a bug or a case that never happens + assert((bitGroup + edx) < sizeof(table_1BA1818)); + return table_1BA1818[bitGroup + edx] + itemIndex; } - // - // Take the upper 27 bits as an index to DWORD array, take lower 5 bits - // as number of bits to mask. Then calculate number of set bits in the value - // masked value. - // - - // Get the index into the array of DWORDs - DwordIndex = (ItemIndex >> 0x05); - - // Add number of set bits in the masked value up to 0x3F bits - if(ItemIndex & 0x20) - BaseValue += GetNumbrOfSetBits32(ItemBits.Uint32Array[DwordIndex - 1]); - - BitMask = (1 << (ItemIndex & 0x1F)) - 1; - return BaseValue + GetNumbrOfSetBits32(ItemBits.Uint32Array[DwordIndex] & BitMask); -} + DWORD GetItem1(DWORD index) + { + SETBITS setBits; + DWORD distFromBase; + DWORD groupIndex; + DWORD dwordIndex; + DWORD itemIndex; + DWORD bitGroup; -//----------------------------------------------------------------------------- -// TNameIndexStruct functions + // If the index is at begin of the group, we just return the start value + if ((index & 0x1FF) == 0) + return IndexToItem1[INDEX_TO_GROUP(index)]; -// HOTS: 0195A290 -TNameIndexStruct::TNameIndexStruct() -{} + // Find the group where the index belongs to + groupIndex = FindGroup_Items1(index); -// HOTS: inlined -TNameIndexStruct::~TNameIndexStruct() -{} + // Calculate the base200 dword index (HOTS: 1959FD4) + distFromBase = index - BaseVals[groupIndex].BaseValue200; + dwordIndex = groupIndex << 0x04; -// HOTS: 195A180 -bool TNameIndexStruct::CheckNameFragment(TMndxFindResult * pStruct1C, DWORD dwFragOffs) -{ - TStruct40 * pStruct40 = pStruct1C->pStruct40; - const char * szPathFragment; - const char * szSearchMask; + // Calculate the dword index including the sub-checkpoint + if (distFromBase < BaseVals[groupIndex].AddValue100) + { + // HOTS: 1959FF1 + if (distFromBase < BaseVals[groupIndex].AddValue80) + { + // HOTS: 0195A000 + if (distFromBase >= BaseVals[groupIndex].AddValue40) + { + // HOTS: 195A007 + distFromBase = distFromBase - BaseVals[groupIndex].AddValue40; + dwordIndex += 2; + } + } + else + { + // HOTS: 195A00E + if (distFromBase < BaseVals[groupIndex].AddValueC0) + { + // HOTS: 195A01A + distFromBase = distFromBase - BaseVals[groupIndex].AddValue80; + dwordIndex += 4; + } + else + { + // HOTS: 195A01F + distFromBase = distFromBase - BaseVals[groupIndex].AddValueC0; + dwordIndex += 6; + } + } + } + else + { + // HOTS: 195A026 + if (distFromBase < BaseVals[groupIndex].AddValue180) + { + // HOTS: 195A037 + if (distFromBase < BaseVals[groupIndex].AddValue140) + { + // HOTS: 195A041 + distFromBase = distFromBase - BaseVals[groupIndex].AddValue100; + dwordIndex += 8; + } + else + { + // HOTS: 195A048 + distFromBase = distFromBase - BaseVals[groupIndex].AddValue140; + dwordIndex += 10; + } + } + else + { + // HOTS: 195A04D + if (distFromBase < BaseVals[groupIndex].AddValue1C0) + { + // HOTS: 195A05A + distFromBase = distFromBase - BaseVals[groupIndex].AddValue180; + dwordIndex += 12; + } + else + { + // HOTS: 195A061 + distFromBase = distFromBase - BaseVals[groupIndex].AddValue1C0; + dwordIndex += 14; + } + } + } - if(!Struct68.TotalItemCount) - { - // Get the offset of the fragment to compare. For convenience with pStruct40->CharIndex, - // subtract the CharIndex from the fragment offset - szPathFragment = (NameFragments.CharArray + dwFragOffs - pStruct40->CharIndex); - szSearchMask = pStruct1C->szSearchMask; + // HOTS: 195A066 + bitGroup = ItemBits[dwordIndex]; + setBits = GetNumberOfSetBits(bitGroup); - // Keep searching as long as the name matches with the fragment - while(szPathFragment[pStruct40->CharIndex] == szSearchMask[pStruct40->CharIndex]) + // Get total number of set bits in the bit group + if (distFromBase >= setBits.u.Lower32) { - // Move to the next character - pStruct40->CharIndex++; - - // Is it the end of the fragment or end of the path? - if(szPathFragment[pStruct40->CharIndex] == 0) - return true; - if(pStruct40->CharIndex >= pStruct1C->cchSearchMask) - return false; + // HOTS: 195A0B2 + bitGroup = ItemBits[++dwordIndex]; + distFromBase = distFromBase - setBits.u.Lower32; + setBits = GetNumberOfSetBits(bitGroup); } - return false; - } - else - { - // Get the offset of the fragment to compare. - szPathFragment = (const char *)(NameFragments.CharArray); - szSearchMask = pStruct1C->szSearchMask; + // Calculate the item index + itemIndex = (dwordIndex << 0x05); - // Keep searching as long as the name matches with the fragment - while(szPathFragment[dwFragOffs] == szSearchMask[pStruct40->CharIndex]) + // Get the number of set bits in the lower word (HOTS: 195A0F6) + if (distFromBase < setBits.u.Lower16) + { + // HOTS: 195A111 + if (distFromBase >= setBits.u.Lower08) + { + // HOTS: 195A111 + itemIndex = itemIndex + 0x08; + bitGroup = bitGroup >> 0x08; + distFromBase = distFromBase - setBits.u.Lower08; + } + } + else { - // Move to the next character - pStruct40->CharIndex++; - - // Is it the end of the fragment or end of the path? - if(Struct68.IsItemPresent(dwFragOffs++)) - return true; - if(dwFragOffs >= pStruct1C->cchSearchMask) - return false; + // HOTS: 195A119 + if (distFromBase < setBits.u.Lower24) + { + // HOTS: 195A125 + bitGroup = bitGroup >> 0x10; + itemIndex = itemIndex + 0x10; + distFromBase = distFromBase - setBits.u.Lower16; + } + else + { + // HOTS: 195A12F + bitGroup = bitGroup >> 0x18; + itemIndex = itemIndex + 0x18; + distFromBase = distFromBase - setBits.u.Lower24; + } } - return false; - } -} + bitGroup = bitGroup & 0xFF; + distFromBase = distFromBase << 0x08; -// HOTS: 195A570 -bool TNameIndexStruct::CheckAndCopyNameFragment(TMndxFindResult * pStruct1C, DWORD dwFragOffs) -{ - TStruct40 * pStruct40 = pStruct1C->pStruct40; - const char * szPathFragment; - const char * szSearchMask; + // BUGBUG: Potential buffer overflow + // Happens in Heroes of the Storm when index == 0x5B + assert((bitGroup + distFromBase) < sizeof(table_1BA1818)); + return table_1BA1818[bitGroup + distFromBase] + itemIndex; + } - if(!Struct68.TotalItemCount) +#ifdef _DEBUG + void Dump(FILE * fp) { - // Get the offset of the fragment to compare. For convenience with pStruct40->CharIndex, - // subtract the CharIndex from the fragment offset - szPathFragment = (const char *)(NameFragments.CharArray + dwFragOffs - pStruct40->CharIndex); - szSearchMask = pStruct1C->szSearchMask; + size_t * ArrayNormal; + size_t * ArrayInvert; + size_t IndexNormal = 0; + size_t IndexInvert = 0; - // Keep copying as long as we don't reach the end of the search mask - while(pStruct40->CharIndex < pStruct1C->cchSearchMask) + // Output numbers of set bits for every 0x200-th item + fprintf(fp, "Number of set bits for every 0x200-th index\n" + "========================================================\n" + " Index Base200h +40 +80 +C0 +100 +140 +180 +1C0\n" + "--------------------------------------------------------\n"); + for (size_t i = 0; i < BaseVals.ItemCount; i++) { - // HOTS: 195A5A0 - if(szPathFragment[pStruct40->CharIndex] != szSearchMask[pStruct40->CharIndex]) - return false; - - // HOTS: 195A5B7 - pStruct40->array_00.InsertOneItem_CHAR(szPathFragment[pStruct40->CharIndex]); - pStruct40->CharIndex++; - - if(szPathFragment[pStruct40->CharIndex] == 0) - return true; + fprintf(fp, "[%08zX]: %08x %04x %04x %04x %04x %04x %04x %04x\n", GROUP_TO_INDEX(i), BaseVals[i].BaseValue200, + BaseVals[i].AddValue40, + BaseVals[i].AddValue80, + BaseVals[i].AddValueC0, + BaseVals[i].AddValue100, + BaseVals[i].AddValue140, + BaseVals[i].AddValue180, + BaseVals[i].AddValue1C0); } + fprintf(fp, "\n"); - // Fixup the address of the fragment - szPathFragment += pStruct40->CharIndex; - - // HOTS: 195A660 - // Now we need to copy the rest of the fragment - while(szPathFragment[0] != 0) + // Output values of Item1 and Item0 for every 0x200-th index + fprintf(fp, "Item0 and Item1 for every 0x200-th index\n" + "========================================\n" + " Index Item0 Item1\n" + "-----------------------------\n"); + for (size_t i = 0; i < IndexToItem0.ItemCount; i++) { - pStruct40->array_00.InsertOneItem_CHAR(szPathFragment[0]); - szPathFragment++; + fprintf(fp, "[%08zX]: %08x %08x\n", GROUP_TO_INDEX(i), IndexToItem0[i], IndexToItem1[i]); } - } - else - { - // Get the offset of the fragment to compare - // HOTS: 195A6B7 - szPathFragment = NameFragments.CharArray; - szSearchMask = pStruct1C->szSearchMask; + fprintf(fp, "\n"); - // Keep copying as long as we don't reach the end of the search mask - while(dwFragOffs < pStruct1C->cchSearchMask) - { - if(szPathFragment[dwFragOffs] != szSearchMask[pStruct40->CharIndex]) - return false; - pStruct40->array_00.InsertOneItem_CHAR(szPathFragment[dwFragOffs]); - pStruct40->CharIndex++; + // Output values of Item1 and Item0 for every index + ArrayNormal = new size_t[TotalItemCount]; + ArrayInvert = new size_t[TotalItemCount]; + if (ArrayNormal && ArrayInvert) + { + // Invalidate both arrays + memset(ArrayNormal, 0xFF, TotalItemCount * sizeof(size_t)); + memset(ArrayInvert, 0xFF, TotalItemCount * sizeof(size_t)); - // Keep going as long as the given bit is not set - if(Struct68.IsItemPresent(dwFragOffs++)) - return true; - } + // Load the both arrays + for (size_t i = 0; i < TotalItemCount; i++) + { + if (IsItemPresent(i)) + ArrayNormal[IndexNormal++] = i; + else + ArrayInvert[IndexInvert++] = i; + } - // Fixup the address of the fragment - szPathFragment += dwFragOffs; + // Output both arrays + fprintf(fp, "Item0 and Item1 for every index\n" + "========================================\n" + " Index Item0 Item1\n" + "-----------------------------\n"); + for (size_t i = 0; i < TotalItemCount; i++) + { + char NormalValue[0x20]; + char InvertValue[0x20]; - // Now we need to copy the rest of the fragment - while(Struct68.IsItemPresent(dwFragOffs++) == 0) - { - // HOTS: 195A7A6 - pStruct40->array_00.InsertOneItem_CHAR(szPathFragment[0]); - szPathFragment++; + if (ArrayNormal[i] == MNDX_INVALID_SIZE_T && ArrayInvert[i] == MNDX_INVALID_SIZE_T) + break; + fprintf(fp, "[%08zX]: %8s %8s\n", i, DumpValue(InvertValue, _countof(InvertValue), ArrayInvert[i]), DumpValue(NormalValue, _countof(NormalValue), ArrayNormal[i])); + } + fprintf(fp, "\n"); } - } - return true; -} + // Free both arrays + delete[] ArrayNormal; + delete[] ArrayInvert; -// HOTS: 195A3F0 -void TNameIndexStruct::CopyNameFragment(TMndxFindResult * pStruct1C, DWORD dwFragOffs) -{ - TStruct40 * pStruct40 = pStruct1C->pStruct40; - const char * szPathFragment; - - // HOTS: 195A3FA - if(!Struct68.TotalItemCount) - { - // HOTS: 195A40C - szPathFragment = NameFragments.CharArray + dwFragOffs; - while(szPathFragment[0] != 0) + // Output array of all values + fprintf(fp, "Item List: Index -> Value\n==========================\n"); + for (size_t i = 0; i < TotalItemCount; i++) { - // Insert the character to the path being built - pStruct40->array_00.InsertOneItem_CHAR(*szPathFragment++); + if (IsItemPresent(i)) + { + fprintf(fp, "[%08zX]: %08x\n", i, GetItemValueAt(i)); + } + else + { + fprintf(fp, "[%08zX]: NOT PRESENT\n", i); + } } + fprintf(fp, "\n"); } - else - { - // HOTS: 195A4B3 - for(;;) - { - // Insert the character to the path being built - pStruct40->array_00.InsertOneItem_CHAR(NameFragments.CharArray[dwFragOffs]); - // Keep going as long as the given bit is not set - if(Struct68.IsItemPresent(dwFragOffs++)) - break; - } + char * DumpValue(char * szBuffer, size_t cchBuffer, size_t value) + { + CascStrPrintf(szBuffer, cchBuffer, (value != MNDX_INVALID_SIZE_T) ? "%08zX" : " - ", value); + return szBuffer; } -} - -// HOTS: 0195A300 -void TNameIndexStruct::ExchangeWith(TNameIndexStruct & Target) -{ - TNameIndexStruct WorkBuff; - - WorkBuff = *this; - *this = Target; - Target = WorkBuff; -} - -// HOTS: 0195A820 -int TNameIndexStruct::LoadFromStream(TByteStream & InStream) -{ - int nError; - - nError = NameFragments.LoadStringsWithCopy(InStream); - if(nError != ERROR_SUCCESS) - return nError; - - return Struct68.LoadFromStream_Exchange(InStream); -} - -// HOTS: 195A850 -int TNameIndexStruct::LoadFromStream_Exchange(TByteStream & InStream) -{ - TNameIndexStruct TempIndexStruct; - int nError; - - nError = TempIndexStruct.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; +#endif - ExchangeWith(TempIndexStruct); - return ERROR_SUCCESS; -} + TGenericArray<DWORD> ItemBits; // A bit array for each item. 1 if the item is present. + size_t TotalItemCount; // Total number of items in the array + size_t ValidItemCount; // Number of present items in the array + TGenericArray<BASEVALS> BaseVals; // For each 0x200-th item, this contains the number of set bits up to that 0x200-th item + TGenericArray<DWORD> IndexToItem0; // Mapping of index to invert item. An "invert" item is an item whose bit in "ItemBits" is zero. + TGenericArray<DWORD> IndexToItem1; // Mapping of index to normal item. An "normal" item is an item whose bit in "ItemBits" is set. +}; //----------------------------------------------------------------------------- -// TStruct10 functions - -TStruct10::TStruct10() -{ - field_0 = 0x03; - field_4 = 0x200; - field_8 = 0x1000; - field_C = 0x20000; -} +// TStruct40 functions -// HOTS: inline -void TStruct10::CopyFrom(TStruct10 & Target) +class TStruct40 { - field_0 = Target.field_0; - field_4 = Target.field_4; - field_8 = Target.field_8; - field_C = Target.field_C; -} + public: -// HOTS: 1956FD0 -int TStruct10::sub_1956FD0(DWORD dwBitMask) -{ - switch(dwBitMask & 0xF80) + TStruct40() { - case 0x00: - field_4 = 0x200; - return ERROR_SUCCESS; - - case 0x80: - field_4 = 0x80; - return ERROR_SUCCESS; - - case 0x100: - field_4 = 0x100; - return ERROR_SUCCESS; - - case 0x200: - field_4 = 0x200; - return ERROR_SUCCESS; - - case 0x400: - field_4 = 0x400; - return ERROR_SUCCESS; - - case 0x800: - field_4 = 0x800; - return ERROR_SUCCESS; + NodeIndex = 0; + ItemCount = 0; + PathLength = 0; + SearchPhase = MNDX_SEARCH_INITIALIZING; } - return ERROR_INVALID_PARAMETER; -} - -// HOTS: 1957050 -int TStruct10::sub_1957050(DWORD dwBitMask) -{ - switch(dwBitMask & 0xF0000) + // HOTS: 19586B0 + void BeginSearch() { - case 0x00: - field_C = 0x20000; - return ERROR_SUCCESS; + // HOTS: 19586BD + PathBuffer.ItemCount = 0; + PathBuffer.SetMaxItemsIf(0x40); - case 0x10000: - field_C = 0x10000; - return ERROR_SUCCESS; + // HOTS: 19586E1 + // Set the new item count + PathStops.GrowArray(0); + PathStops.SetMaxItemsIf(4); - case 0x20000: - field_C = 0x20000; - return ERROR_SUCCESS; + PathLength = 0; + NodeIndex = 0; + ItemCount = 0; + SearchPhase = MNDX_SEARCH_SEARCHING; } - return ERROR_INVALID_PARAMETER; -} - -// HOTS: 19572E0 -int TStruct10::sub_19572E0(DWORD dwBitMask) -{ - DWORD dwSubMask; - int nError; + DWORD CalcHashValue(const char * szPath) + { + return (BYTE)(szPath[PathLength]) ^ (NodeIndex << 0x05) ^ NodeIndex; + } - if(dwBitMask & 0xFFF00000) - return ERROR_INVALID_PARAMETER; + TGenericArray<TPathStop> PathStops; // Array of path checkpoints + TGenericArray<char> PathBuffer; // Buffer for building a file name + DWORD NodeIndex; // ID of a path node being searched; starting with 0 + DWORD PathLength; // Length of the path in the PathBuffer + DWORD ItemCount; + DWORD SearchPhase; // 0 = initializing, 2 = searching, 4 = finished +}; - dwSubMask = dwBitMask & 0x7F; - if(dwSubMask) - field_0 = dwSubMask; +//----------------------------------------------------------------------------- +// Local functions - TMndxSearch - nError = sub_1956FD0(dwBitMask); - if(nError != ERROR_SUCCESS) - return nError; +class TMndxSearch +{ + public: - dwSubMask = dwBitMask & 0xF000; - if(dwSubMask == 0 || dwSubMask == 0x1000) + // HOTS: 01956EE0 + TMndxSearch() { - field_8 = 0x1000; - return sub_1957050(dwBitMask); + szSearchMask = NULL; + cchSearchMask = 0; + szFoundPath = NULL; + cchFoundPath = 0; + nIndex = 0; } - if(dwSubMask == 0x2000) + // HOTS: 01956F00 + ~TMndxSearch() + {} + + // HOTS: 01956E70 + int SetSearchMask( + const char * szNewSearchMask, + size_t cchNewSearchMask) { - field_8 = 0x2000; - return sub_1957050(dwBitMask); - } + if(szSearchMask == NULL && cchSearchMask != 0) + return ERROR_INVALID_PARAMETER; - return ERROR_INVALID_PARAMETER; -} + Struct40.SearchPhase = MNDX_SEARCH_INITIALIZING; -// HOTS: 1957800 -int TStruct10::sub_1957800(DWORD dwBitMask) -{ - TStruct10 TempStruct; - int nError; - - nError = TempStruct.sub_19572E0(dwBitMask); - if(nError != ERROR_SUCCESS) - return nError; + szSearchMask = szNewSearchMask; + cchSearchMask = cchNewSearchMask; + return ERROR_SUCCESS; + } - CopyFrom(TempStruct); - return ERROR_SUCCESS; -} + TStruct40 Struct40; + const char * szSearchMask; // Search mask without wildcards + size_t cchSearchMask; // Length of the search mask + const char * szFoundPath; // Found path name + size_t cchFoundPath; // Length of the found path name + DWORD nIndex; // Index of the file name +}; //----------------------------------------------------------------------------- -// TFileNameDatabase functions +// TPathFragmentTable class. This class implements table of the path fragments. +// These path fragments can either by terminated by zeros (ASCIIZ) +// or can be marked by the external "PathMarks" structure -// HOTS: 01958730 -TFileNameDatabase::TFileNameDatabase() -{ - NameFragIndexMask = 0; - field_214 = 0; -} - -// HOTS: inlined -void TFileNameDatabase::ExchangeWith(TFileNameDatabase & Target) +class TPathFragmentTable { - TFileNameDatabasePtr TempPtr; - DWORD dwTemp; - - Struct68_00.ExchangeWith(Target.Struct68_00); - FileNameIndexes.ExchangeWith(Target.FileNameIndexes); - Struct68_D0.ExchangeWith(Target.Struct68_D0); - - FrgmDist_LoBits.ExchangeWith(Target.FrgmDist_LoBits); - FrgmDist_HiBits.ExchangeWith(Target.FrgmDist_HiBits); - - IndexStruct_174.ExchangeWith(Target.IndexStruct_174); - - TempPtr = NextDB; - NextDB = Target.NextDB; - Target.NextDB = TempPtr; - - NameFragTable.ExchangeWith(Target.NameFragTable); + public: - dwTemp = NameFragIndexMask; - NameFragIndexMask = Target.NameFragIndexMask; - Target.NameFragIndexMask = dwTemp; + // HOTS: 0195A290 + TPathFragmentTable() + {} - dwTemp = field_214; - field_214 = Target.field_214; - Target.field_214 = dwTemp; + // HOTS: inlined + ~TPathFragmentTable() + {} - Struct10.CopyFrom(Target.Struct10); -} + // HOTS: 195A180 + bool ComparePathFragment(TMndxSearch * pSearch, size_t nFragmentOffset) + { + TStruct40 * pStruct40 = &pSearch->Struct40; -// HOTS: 1959CB0 -DWORD TFileNameDatabase::sub_1959CB0(DWORD dwItemIndex) -{ - PTRIPLET pTriplet; - DWORD dwKeyShifted = (dwItemIndex >> 9); - DWORD eax, ebx, ecx, edx, esi, edi; - - // If lower 9 is zero - edx = dwItemIndex; - if((edx & 0x1FF) == 0) - return Struct68_00.ArrayDwords_38.Uint32Array[dwKeyShifted]; - - eax = Struct68_00.ArrayDwords_38.Uint32Array[dwKeyShifted] >> 9; - esi = (Struct68_00.ArrayDwords_38.Uint32Array[dwKeyShifted + 1] + 0x1FF) >> 9; - dwItemIndex = esi; - - if((eax + 0x0A) >= esi) - { - // HOTS: 1959CF7 - pTriplet = Struct68_00.BaseValues.TripletArray + eax + 1; - edi = (eax << 0x09); - ebx = edi - pTriplet->BaseValue + 0x200; - while(edx >= ebx) + // Do we have path fragment separators in an external structure? + if(PathMarks.IsEmpty()) { - // HOTS: 1959D14 - edi += 0x200; - pTriplet++; + // Keep searching as long as the name matches with the fragment + while(PathFragments[nFragmentOffset] == pSearch->szSearchMask[pStruct40->PathLength]) + { + // Move to the next character + pStruct40->PathLength++; + nFragmentOffset++; - ebx = edi - pTriplet->BaseValue + 0x200; - eax++; + // Is it the end of the fragment or end of the path? + if(PathFragments[nFragmentOffset] == 0) + return true; + if(pStruct40->PathLength >= pSearch->cchSearchMask) + return false; + } + + return false; } - } - else - { - // HOTS: 1959D2E - while((eax + 1) < esi) + else { - // HOTS: 1959D38 - // ecx = Struct68_00.BaseValues.TripletArray; - esi = (esi + eax) >> 1; - ebx = (esi << 0x09) - Struct68_00.BaseValues.TripletArray[esi].BaseValue; - if(edx < ebx) + // Keep searching as long as the name matches with the fragment + while(PathFragments[nFragmentOffset] == pSearch->szSearchMask[pStruct40->PathLength]) { - // HOTS: 01959D4B - dwItemIndex = esi; - } - else - { - // HOTS: 1959D50 - eax = esi; - esi = dwItemIndex; + // Move to the next character + pStruct40->PathLength++; + + // Is it the end of the path fragment? + if(PathMarks.IsItemPresent(nFragmentOffset++)) + return true; + if(nFragmentOffset >= pSearch->cchSearchMask) + return false; } + + return false; } } - // HOTS: 1959D5F - pTriplet = Struct68_00.BaseValues.TripletArray + eax; - edx += pTriplet->BaseValue - (eax << 0x09); - edi = (eax << 4); - - eax = pTriplet->Value2; - ecx = (eax >> 0x17); - ebx = 0x100 - ecx; - if(edx < ebx) + // HOTS: 195A3F0 + void CopyPathFragment(TMndxSearch * pSearch, size_t nFragmentOffset) { - // HOTS: 1959D8C - ecx = ((eax >> 0x07) & 0xFF); - esi = 0x80 - ecx; - if(edx < esi) + TStruct40 * pStruct40 = &pSearch->Struct40; + + // Do we have path fragment separators in an external structure? + if (PathMarks.IsEmpty()) { - // HOTS: 01959DA2 - eax = eax & 0x7F; - ecx = 0x40 - eax; - if(edx >= ecx) + // HOTS: 195A40C + while (PathFragments[nFragmentOffset] != 0) { - // HOTS: 01959DB7 - edi += 2; - edx = edx + eax - 0x40; + // Insert the character to the path being built + pStruct40->PathBuffer.Insert(PathFragments[nFragmentOffset++]); } } else { - // HOTS: 1959DC0 - eax = (eax >> 0x0F) & 0xFF; - esi = 0xC0 - eax; - if(edx < esi) + // HOTS: 195A4B3 + while(!PathMarks.IsItemPresent(nFragmentOffset)) { - // HOTS: 1959DD3 - edi += 4; - edx = edx + ecx - 0x80; - } - else - { - // HOTS: 1959DD3 - edi += 6; - edx = edx + eax - 0xC0; + // Insert the character to the path being built + pStruct40->PathBuffer.Insert(PathFragments[nFragmentOffset++]); } } } - else + + // HOTS: 195A570 + bool CompareAndCopyPathFragment(TMndxSearch * pSearch, size_t nFragmentOffset) { - // HOTS: 1959DE8 - esi = pTriplet->Value3; - eax = ((esi >> 0x09) & 0x1FF); - ebx = 0x180 - eax; - if(edx < ebx) + TStruct40 * pStruct40 = &pSearch->Struct40; + + // Do we have path fragment separators in an external structure? + if(PathMarks.IsEmpty()) { - // HOTS: 01959E00 - esi = esi & 0x1FF; - eax = (0x140 - esi); - if(edx < eax) + // Keep copying as long as we don't reach the end of the search mask + while(pStruct40->PathLength < pSearch->cchSearchMask) { - // HOTS: 1959E11 - edi = edi + 8; - edx = edx + ecx - 0x100; + // HOTS: 195A5A0 + if(PathFragments[nFragmentOffset] != pSearch->szSearchMask[pStruct40->PathLength]) + return false; + + // HOTS: 195A5B7 + pStruct40->PathBuffer.Insert(PathFragments[nFragmentOffset++]); + pStruct40->PathLength++; + + // If we found the end of the fragment, return success + if(PathFragments[nFragmentOffset] == 0) + return true; } - else + + // HOTS: 195A660 + // Now we need to copy the rest of the fragment + while(PathFragments[nFragmentOffset] != 0) { - // HOTS: 1959E1D - edi = edi + 0x0A; - edx = edx + esi - 0x140; + pStruct40->PathBuffer.Insert(PathFragments[nFragmentOffset]); + nFragmentOffset++; } } else { - // HOTS: 1959E29 - esi = (esi >> 0x12) & 0x1FF; - ecx = (0x1C0 - esi); - if(edx < ecx) + // Keep copying as long as we don't reach the end of the search mask + while(nFragmentOffset < pSearch->cchSearchMask) { - // HOTS: 1959E3D - edi = edi + 0x0C; - edx = edx + eax - 0x180; + if(PathFragments[nFragmentOffset] != pSearch->szSearchMask[pStruct40->PathLength]) + return false; + + pStruct40->PathBuffer.Insert(PathFragments[nFragmentOffset]); + pStruct40->PathLength++; + + // If we found the end of the fragment, return success + if(PathMarks.IsItemPresent(nFragmentOffset++)) + return true; } - else + + // Now we need to copy the rest of the fragment + while(!PathMarks.IsItemPresent(nFragmentOffset)) { - // HOTS: 1959E49 - edi = edi + 0x0E; - edx = edx + esi - 0x1C0; + // HOTS: 195A7A6 + pStruct40->PathBuffer.Insert(PathFragments[nFragmentOffset++]); } } - } - // HOTS: 1959E53: - // Calculate the number of bits set in the value of "ecx" - ecx = ~Struct68_00.ItemBits.Uint32Array[edi]; - eax = GetNumberOfSetBits(ecx); - esi = eax >> 0x18; + return true; + } - if(edx >= esi) + // HOTS: 0195A820 + int LoadFromStream(TByteStream & InStream) { - // HOTS: 1959ea4 - ecx = ~Struct68_00.ItemBits.Uint32Array[++edi]; - edx = edx - esi; - eax = GetNumberOfSetBits(ecx); + int nError; + + nError = PathFragments.LoadFromStream(InStream); + if(nError != ERROR_SUCCESS) + return nError; + + return PathMarks.LoadFromStream(InStream); } - // HOTS: 1959eea - // ESI gets the number of set bits in the lower 16 bits of ECX - esi = (eax >> 0x08) & 0xFF; - edi = edi << 0x05; - if(edx < esi) + TGenericArray<char> PathFragments; + TSparseArray PathMarks; +}; + +//----------------------------------------------------------------------------- +// TStruct10 functions + +class TStruct10 +{ + public: + + TStruct10() { - // HOTS: 1959EFC - eax = eax & 0xFF; - if(edx >= eax) - { - // HOTS: 1959F05 - ecx >>= 0x08; - edi += 0x08; - edx -= eax; - } + field_0 = 0x03; + field_4 = 0x200; + field_8 = 0x1000; + field_C = 0x20000; } - else + + // HOTS: 1956FD0 + int sub_1956FD0(DWORD dwBitMask) { - // HOTS: 1959F0D - eax = (eax >> 0x10) & 0xFF; - if(edx < eax) + switch(dwBitMask & 0xF80) { - // HOTS: 1959F19 - ecx >>= 0x10; - edi += 0x10; - edx -= esi; - } - else - { - // HOTS: 1959F23 - ecx >>= 0x18; - edi += 0x18; - edx -= eax; - } - } - - // HOTS: 1959f2b - edx = edx << 0x08; - ecx = ecx & 0xFF; + case 0x00: + field_4 = 0x200; + return ERROR_SUCCESS; - // BUGBUG: Possible buffer overflow here. Happens when dwItemIndex >= 0x9C. - // The same happens in Heroes of the Storm (build 29049), so I am not sure - // if this is a bug or a case that never happens - assert((ecx + edx) < sizeof(table_1BA1818)); - return table_1BA1818[ecx + edx] + edi; -} + case 0x80: + field_4 = 0x80; + return ERROR_SUCCESS; -DWORD TFileNameDatabase::sub_1959F50(DWORD arg_0) -{ - PTRIPLET pTriplet; - PDWORD ItemArray; - DWORD eax, ebx, ecx, edx, esi, edi; + case 0x100: + field_4 = 0x100; + return ERROR_SUCCESS; - edx = arg_0; - eax = arg_0 >> 0x09; - if((arg_0 & 0x1FF) == 0) - return Struct68_00.ArrayDwords_50.Uint32Array[eax]; + case 0x200: + field_4 = 0x200; + return ERROR_SUCCESS; - ItemArray = Struct68_00.ArrayDwords_50.Uint32Array + eax; - eax = (ItemArray[0] >> 0x09); - edi = (ItemArray[1] + 0x1FF) >> 0x09; + case 0x400: + field_4 = 0x400; + return ERROR_SUCCESS; - if((eax + 0x0A) > edi) - { - // HOTS: 01959F94 - pTriplet = Struct68_00.BaseValues.TripletArray + eax + 1; - while(edx >= pTriplet->BaseValue) - { - // HOTS: 1959FA3 - pTriplet++; - eax++; + case 0x800: + field_4 = 0x800; + return ERROR_SUCCESS; } + + return ERROR_INVALID_PARAMETER; } - else + + // HOTS: 1957050 + int sub_1957050(DWORD dwBitMask) { - // Binary search - // HOTS: 1959FAD - if((eax + 1) < edi) + switch(dwBitMask & 0xF0000) { - // HOTS: 1959FB4 - esi = (edi + eax) >> 1; - if(edx < Struct68_00.BaseValues.TripletArray[esi].BaseValue) - { - // HOTS: 1959FC4 - edi = esi; - } - else - { - // HOTS: 1959FC8 - eax = esi; - } + case 0x00: + field_C = 0x20000; + return ERROR_SUCCESS; + + case 0x10000: + field_C = 0x10000; + return ERROR_SUCCESS; + + case 0x20000: + field_C = 0x20000; + return ERROR_SUCCESS; } + + return ERROR_INVALID_PARAMETER; } - // HOTS: 1959FD4 - pTriplet = Struct68_00.BaseValues.TripletArray + eax; - edx = edx - pTriplet->BaseValue; - edi = eax << 0x04; - eax = pTriplet->Value2; - ebx = (eax >> 0x17); - if(edx < ebx) + // HOTS: 19572E0 + int sub_19572E0(DWORD dwBitMask) { - // HOTS: 1959FF1 - esi = (eax >> 0x07) & 0xFF; - if(edx < esi) + DWORD dwSubMask; + int nError; + + if(dwBitMask & 0xFFF00000) + return ERROR_INVALID_PARAMETER; + + dwSubMask = dwBitMask & 0x7F; + if(dwSubMask) + field_0 = dwSubMask; + + nError = sub_1956FD0(dwBitMask); + if(nError != ERROR_SUCCESS) + return nError; + + dwSubMask = dwBitMask & 0xF000; + if(dwSubMask == 0 || dwSubMask == 0x1000) { - // HOTS: 0195A000 - eax = eax & 0x7F; - if(edx >= eax) - { - // HOTS: 195A007 - edi = edi + 2; - edx = edx - eax; - } + field_8 = 0x1000; + return sub_1957050(dwBitMask); } - else + + if(dwSubMask == 0x2000) { - // HOTS: 195A00E - eax = (eax >> 0x0F) & 0xFF; - if(edx < eax) - { - // HOTS: 195A01A - edi += 4; - edx = edx - esi; - } - else - { - // HOTS: 195A01F - edi += 6; - edx = edx - eax; - } + field_8 = 0x2000; + return sub_1957050(dwBitMask); } + + return ERROR_INVALID_PARAMETER; } - else + + // HOTS: 1957800 + int sub_1957800(DWORD dwBitMask) { - // HOTS: 195A026 - esi = pTriplet->Value3; - eax = (pTriplet->Value3 >> 0x09) & 0x1FF; - if(edx < eax) - { - // HOTS: 195A037 - esi = esi & 0x1FF; - if(edx < esi) - { - // HOTS: 195A041 - edi = edi + 8; - edx = edx - ebx; - } - else - { - // HOTS: 195A048 - edi = edi + 0x0A; - edx = edx - esi; - } - } - else - { - // HOTS: 195A04D - esi = (esi >> 0x12) & 0x1FF; - if(edx < esi) - { - // HOTS: 195A05A - edi = edi + 0x0C; - edx = edx - eax; - } - else - { - // HOTS: 195A061 - edi = edi + 0x0E; - edx = edx - esi; - } - } + return sub_19572E0(dwBitMask); } - // HOTS: 195A066 - esi = Struct68_00.ItemBits.Uint32Array[edi]; - eax = GetNumberOfSetBits(esi); - ecx = eax >> 0x18; + DWORD field_0; + DWORD field_4; + DWORD field_8; + DWORD field_C; +}; + +//----------------------------------------------------------------------------- +// TFileNameDatabase interface/implementation + +class TFileNameDatabase +{ + public: - if(edx >= ecx) + // HOTS: 01958730 + TFileNameDatabase() { - // HOTS: 195A0B2 - esi = Struct68_00.ItemBits.Uint32Array[++edi]; - edx = edx - ecx; - eax = GetNumberOfSetBits(esi); + HashTableMask = 0; + field_214 = 0; + pChildDB = NULL; } - // HOTS: 195A0F6 - ecx = (eax >> 0x08) & 0xFF; + ~TFileNameDatabase() + { + delete pChildDB; + } - edi = (edi << 0x05); - if(edx < ecx) + // Returns nonzero if the name fragment match is a single-char match + bool IsPathFragmentSingleChar(HASH_ENTRY * pHashEntry) { - // HOTS: 195A111 - eax = eax & 0xFF; - if(edx >= eax) - { - // HOTS: 195A111 - edi = edi + 0x08; - esi = esi >> 0x08; - edx = edx - eax; - } + return ((pHashEntry->FragmentOffset & 0xFFFFFF00) == 0xFFFFFF00); + } + + // Returns true if the given collision path fragment is a string (aka more than 1 char) + bool IsPathFragmentString(size_t index) + { + return CollisionHiBitsIndexes.IsItemPresent(index); } - else + + // HOTS: 1957350, inlined + DWORD GetPathFragmentOffset1(DWORD index_lobits) { - // HOTS: 195A119 - eax = (eax >> 0x10) & 0xFF; - if(edx < eax) + DWORD index_hibits = CollisionHiBitsIndexes.GetItemValueAt(index_lobits); + + return (HiBitsTable.GetItem(index_hibits) << 0x08) | LoBitsTable[index_lobits]; + } + + // Retrieves fragment_offset/subtable_index of the path fragment, with check for starting value + DWORD GetPathFragmentOffset2(DWORD & index_hibits, DWORD index_lobits) + { + // If the hi-bits index is invalid, we need to get its starting value + if (index_hibits == CASC_INVALID_INDEX) { - // HOTS: 195A125 - esi = esi >> 0x10; - edi = edi + 0x10; - edx = edx - ecx; +/* + printf("\n"); + for (DWORD i = 0; i < CollisionHiBitsIndexes.TotalItemCount; i++) + { + if (CollisionHiBitsIndexes.IsItemPresent(i)) + printf("[%02X] = %02X\n", i, CollisionHiBitsIndexes.GetIntValueAt(i)); + else + printf("[%02X] = NOT_PRESENT\n", i); + } +*/ + index_hibits = CollisionHiBitsIndexes.GetItemValueAt(index_lobits); } else { - // HOTS: 195A12F - esi = esi >> 0x18; - edi = edi + 0x18; - edx = edx - eax; + index_hibits++; } + + // Now we use both NodeIndex and HiBits index for retrieving the path fragment index + return (HiBitsTable.GetItem(index_hibits) << 0x08) | LoBitsTable[index_lobits]; } - esi = esi & 0xFF; - edx = edx << 0x08; + // HOTS: 1956DA0 + int Load(LPBYTE pbMarData, size_t cbMarData) + { + TByteStream ByteStream; + DWORD dwSignature; + int nError; - // BUGBUG: Potential buffer overflow - // Happens in Heroes of the Storm when arg_0 == 0x5B - assert((esi + edx) < sizeof(table_1BA1818)); - return table_1BA1818[esi + edx] + edi; -} + if(pbMarData == NULL && cbMarData != 0) + return ERROR_INVALID_PARAMETER; -// HOTS: 1957970 -bool TFileNameDatabase::CheckNextPathFragment(TMndxFindResult * pStruct1C) -{ - TStruct40 * pStruct40 = pStruct1C->pStruct40; - LPBYTE pbPathName = (LPBYTE)pStruct1C->szSearchMask; - DWORD CollisionIndex; - DWORD NameFragIndex; - DWORD SaveCharIndex; - DWORD HiBitsIndex; - DWORD FragOffs; - - // Calculate index of the next name fragment in the name fragment table - NameFragIndex = ((pStruct40->ItemIndex << 0x05) ^ pStruct40->ItemIndex ^ pbPathName[pStruct40->CharIndex]) & NameFragIndexMask; - - // Does the hash value match? - if(NameFragTable.NameFragArray[NameFragIndex].ItemIndex == pStruct40->ItemIndex) - { - // Check if there is single character match - if(IS_SINGLE_CHAR_MATCH(NameFragTable, NameFragIndex)) + nError = ByteStream.SetByteBuffer(pbMarData, cbMarData); + if(nError == ERROR_SUCCESS) { - pStruct40->ItemIndex = NameFragTable.NameFragArray[NameFragIndex].NextIndex; - pStruct40->CharIndex++; - return true; - } + // Get pointer to MAR signature + nError = ByteStream.GetValue<DWORD>(dwSignature); + if(nError != ERROR_SUCCESS) + return nError; - // Check if there is a name fragment match - if(NextDB.pDB != NULL) - { - if(!NextDB.pDB->sub_1957B80(pStruct1C, NameFragTable.NameFragArray[NameFragIndex].FragOffs)) - return false; - } - else - { - if(!IndexStruct_174.CheckNameFragment(pStruct1C, NameFragTable.NameFragArray[NameFragIndex].FragOffs)) - return false; + // Verify the signature + if(dwSignature != MNDX_MAR_SIGNATURE) + return ERROR_BAD_FORMAT; + + // HOTS: 1956E11 + nError = LoadFromStream(ByteStream); } - pStruct40->ItemIndex = NameFragTable.NameFragArray[NameFragIndex].NextIndex; - return true; + return nError; } - // - // Conflict: Multiple hashes give the same table index - // + // HOTS: 19584B0 + int SetChildDatabase(TFileNameDatabase * pNewDB) + { + if(pNewDB != NULL && pChildDB == pNewDB) + return ERROR_INVALID_PARAMETER; - // HOTS: 1957A0E - CollisionIndex = sub_1959CB0(pStruct40->ItemIndex) + 1; - if(!Struct68_00.IsItemPresent(CollisionIndex)) - return false; + if(pChildDB != NULL) + delete pChildDB; + pChildDB = pNewDB; + return ERROR_SUCCESS; + } - pStruct40->ItemIndex = (CollisionIndex - pStruct40->ItemIndex - 1); - HiBitsIndex = 0xFFFFFFFF; + // HOTS: 1957970 + bool ComparePathFragment(TMndxSearch * pSearch) + { + TStruct40 * pStruct40 = &pSearch->Struct40; + PHASH_ENTRY pHashEntry; + DWORD ColTableIndex; + DWORD HiBitsIndex; + DWORD NodeIndex; -// CascDumpSparseArray("E:\\casc-array-68.txt", &FileNameIndexes); -// CascDumpSparseArray("E:\\casc-array-D0.txt", &Struct68_D0); + // Calculate the item hash from the current char and fragment ID + NodeIndex = pStruct40->CalcHashValue(pSearch->szSearchMask) & HashTableMask; + pHashEntry = &HashTable[NodeIndex]; - // HOTS: 1957A41: - do - { - // HOTS: 1957A41 - // Check if the low 8 bits if the fragment offset contain a single character - // or an offset to a name fragment - if(Struct68_D0.IsItemPresent(pStruct40->ItemIndex)) + // Does the hash value ID match? + if(pHashEntry->NodeIndex == pStruct40->NodeIndex) { - if(HiBitsIndex == 0xFFFFFFFF) + // Check if there is single character match + if (!IsPathFragmentSingleChar(pHashEntry)) { - // HOTS: 1957A6C - HiBitsIndex = Struct68_D0.GetItemValue(pStruct40->ItemIndex); + // Check if there is a name fragment match + if (pChildDB != NULL) + { + if (!pChildDB->ComparePathFragmentByIndex(pSearch, pHashEntry->ChildTableIndex)) + return false; + } + else + { + if (!PathFragmentTable.ComparePathFragment(pSearch, pHashEntry->FragmentOffset)) + return false; + } } else { - // HOTS: 1957A7F - HiBitsIndex++; + pStruct40->PathLength++; } - // HOTS: 1957A83 - SaveCharIndex = pStruct40->CharIndex; + pStruct40->NodeIndex = pHashEntry->NextIndex; + return true; + } + + // + // Conflict: Multiple node IDs give the same table index + // - // Get the name fragment offset as combined value from lower 8 bits and upper bits - FragOffs = GetNameFragmentOffsetEx(pStruct40->ItemIndex, HiBitsIndex); + // HOTS: 1957A0E + ColTableIndex = CollisionTable.GetItem0(pStruct40->NodeIndex) + 1; + pStruct40->NodeIndex = (ColTableIndex - pStruct40->NodeIndex - 1); + HiBitsIndex = CASC_INVALID_INDEX; - // Compare the string with the fragment name database - if(NextDB.pDB != NULL) + // HOTS: 1957A41: + while(CollisionTable.IsItemPresent(ColTableIndex)) + { + // HOTS: 1957A41 + // Check if the low 8 bits if the fragment offset contain a single character + // or an offset to a name fragment + if(IsPathFragmentString(pStruct40->NodeIndex)) { - // HOTS: 1957AEC - if(NextDB.pDB->sub_1957B80(pStruct1C, FragOffs)) - return true; + DWORD FragmentOffset = GetPathFragmentOffset2(HiBitsIndex, pStruct40->NodeIndex); + DWORD SavePathLength = pStruct40->PathLength; // HOTS: 1957A83 + + // Do we have a child database? + if(pChildDB != NULL) + { + // HOTS: 1957AEC + if(pChildDB->ComparePathFragmentByIndex(pSearch, FragmentOffset)) + return true; + } + else + { + // HOTS: 1957AF7 + if(PathFragmentTable.ComparePathFragment(pSearch, FragmentOffset)) + return true; + } + + // HOTS: 1957B0E + // If there was partial match with the fragment, end the search + if(pStruct40->PathLength != SavePathLength) + return false; } else { - // HOTS: 1957AF7 - if(IndexStruct_174.CheckNameFragment(pStruct1C, FragOffs)) + // HOTS: 1957B1C + if(LoBitsTable[pStruct40->NodeIndex] == pSearch->szSearchMask[pStruct40->PathLength]) + { + pStruct40->PathLength++; return true; + } } - // HOTS: 1957B0E - // If there was partial match with the fragment, end the search - if(pStruct40->CharIndex != SaveCharIndex) - return false; - } - else - { - // HOTS: 1957B1C - if(FrgmDist_LoBits.ByteArray[pStruct40->ItemIndex] == pStruct1C->szSearchMask[pStruct40->CharIndex]) - { - pStruct40->CharIndex++; - return true; - } + // HOTS: 1957B32 + pStruct40->NodeIndex++; + ColTableIndex++; } - // HOTS: 1957B32 - pStruct40->ItemIndex++; - CollisionIndex++; + return false; } - while(Struct68_00.IsItemPresent(CollisionIndex)); - return false; -} - -// HOTS: 1957B80 -bool TFileNameDatabase::sub_1957B80(TMndxFindResult * pStruct1C, DWORD arg_4) -{ - TStruct40 * pStruct40 = pStruct1C->pStruct40; - PNAME_FRAG pNameEntry; - DWORD FragOffs; - DWORD eax, edi; - - edi = arg_4; - // HOTS: 1957B95 - for(;;) + // HOTS: 1957B80 + bool ComparePathFragmentByIndex(TMndxSearch * pSearch, DWORD TableIndex) { - pNameEntry = NameFragTable.NameFragArray + (edi & NameFragIndexMask); - if(edi == pNameEntry->NextIndex) + TStruct40 * pStruct40 = &pSearch->Struct40; + PHASH_ENTRY pHashEntry; + DWORD eax; + + // HOTS: 1957B95 + for (;;) { - // HOTS: 01957BB4 - if((pNameEntry->FragOffs & 0xFFFFFF00) != 0xFFFFFF00) + // Get the hasn table item + pHashEntry = &HashTable[TableIndex & HashTableMask]; + + // + if (TableIndex == pHashEntry->NextIndex) { - // HOTS: 1957BC7 - if(NextDB.pDB != NULL) + // HOTS: 01957BB4 + if (!IsPathFragmentSingleChar(pHashEntry)) { - // HOTS: 1957BD3 - if(!NextDB.pDB->sub_1957B80(pStruct1C, pNameEntry->FragOffs)) - return false; + // HOTS: 1957BC7 + if (pChildDB != NULL) + { + // HOTS: 1957BD3 + if (!pChildDB->ComparePathFragmentByIndex(pSearch, pHashEntry->ChildTableIndex)) + return false; + } + else + { + // HOTS: 1957BE0 + if (!PathFragmentTable.ComparePathFragment(pSearch, pHashEntry->FragmentOffset)) + return false; + } } else { - // HOTS: 1957BE0 - if(!IndexStruct_174.CheckNameFragment(pStruct1C, pNameEntry->FragOffs)) + // HOTS: 1957BEE + if (pSearch->szSearchMask[pStruct40->PathLength] != pHashEntry->SingleChar) return false; + pStruct40->PathLength++; } - } - else - { - // HOTS: 1957BEE - if(pStruct1C->szSearchMask[pStruct40->CharIndex] != (char)pNameEntry->FragOffs) - return false; - pStruct40->CharIndex++; - } - // HOTS: 1957C05 - edi = pNameEntry->ItemIndex; - if(edi == 0) - return true; + // HOTS: 1957C05 + TableIndex = pHashEntry->NodeIndex; + if (TableIndex == 0) + return true; - if(pStruct40->CharIndex >= pStruct1C->cchSearchMask) - return false; - } - else - { - // HOTS: 1957C30 - if(Struct68_D0.IsItemPresent(edi)) + if (pStruct40->PathLength >= pSearch->cchSearchMask) + return false; + } + else { - // HOTS: 1957C4C - if(NextDB.pDB != NULL) + // HOTS: 1957C30 + if (IsPathFragmentString(TableIndex)) { - // HOTS: 1957C58 - FragOffs = GetNameFragmentOffset(edi); - if(!NextDB.pDB->sub_1957B80(pStruct1C, FragOffs)) - return false; + DWORD FragmentOffset = GetPathFragmentOffset1(TableIndex); + + // HOTS: 1957C4C + if (pChildDB != NULL) + { + // HOTS: 1957C58 + if (!pChildDB->ComparePathFragmentByIndex(pSearch, FragmentOffset)) + return false; + } + else + { + // HOTS: 1957350 + if (!PathFragmentTable.ComparePathFragment(pSearch, FragmentOffset)) + return false; + } } else { - // HOTS: 1957350 - FragOffs = GetNameFragmentOffset(edi); - if(!IndexStruct_174.CheckNameFragment(pStruct1C, FragOffs)) + // HOTS: 1957C8E + if (LoBitsTable[TableIndex] != pSearch->szSearchMask[pStruct40->PathLength]) return false; - } - } - else - { - // HOTS: 1957C8E - if(FrgmDist_LoBits.ByteArray[edi] != pStruct1C->szSearchMask[pStruct40->CharIndex]) - return false; - pStruct40->CharIndex++; - } + pStruct40->PathLength++; + } - // HOTS: 1957CB2 - if(edi <= field_214) - return true; + // HOTS: 1957CB2 + if (TableIndex <= field_214) + return true; - if(pStruct40->CharIndex >= pStruct1C->cchSearchMask) - return false; + if (pStruct40->PathLength >= pSearch->cchSearchMask) + return false; - eax = sub_1959F50(edi); - edi = (eax - edi - 1); + eax = CollisionTable.GetItem1(TableIndex); + TableIndex = (eax - TableIndex - 1); + } } } -} -// HOTS: 1958D70 -void TFileNameDatabase::sub_1958D70(TMndxFindResult * pStruct1C, DWORD arg_4) -{ - TStruct40 * pStruct40 = pStruct1C->pStruct40; - PNAME_FRAG pNameEntry; - - // HOTS: 1958D84 - for(;;) + // HOTS: 1958D70 + void CopyPathFragmentByIndex(TMndxSearch * pSearch, DWORD TableIndex) { - pNameEntry = NameFragTable.NameFragArray + (arg_4 & NameFragIndexMask); - if(arg_4 == pNameEntry->NextIndex) + TStruct40 * pStruct40 = &pSearch->Struct40; + PHASH_ENTRY pHashEntry; + + // HOTS: 1958D84 + for (;;) { - // HOTS: 1958DA6 - if((pNameEntry->FragOffs & 0xFFFFFF00) != 0xFFFFFF00) + pHashEntry = &HashTable[TableIndex & HashTableMask]; + if (TableIndex == pHashEntry->NextIndex) { - // HOTS: 1958DBA - if(NextDB.pDB != NULL) + // HOTS: 1958DA6 + if (!IsPathFragmentSingleChar(pHashEntry)) { - NextDB.pDB->sub_1958D70(pStruct1C, pNameEntry->FragOffs); + // HOTS: 1958DBA + if (pChildDB != NULL) + { + pChildDB->CopyPathFragmentByIndex(pSearch, pHashEntry->ChildTableIndex); + } + else + { + PathFragmentTable.CopyPathFragment(pSearch, pHashEntry->FragmentOffset); + } } else { - IndexStruct_174.CopyNameFragment(pStruct1C, pNameEntry->FragOffs); + // HOTS: 1958DE7 + // Insert the low 8 bits to the path being built + pStruct40->PathBuffer.Insert(pHashEntry->SingleChar); } + + // HOTS: 1958E71 + TableIndex = pHashEntry->NodeIndex; + if (TableIndex == 0) + return; } else { - // HOTS: 1958DE7 - // Insert the low 8 bits to the path being built - pStruct40->array_00.InsertOneItem_CHAR((char)(pNameEntry->FragOffs & 0xFF)); - } - - // HOTS: 1958E71 - arg_4 = pNameEntry->ItemIndex; - if(arg_4 == 0) - return; - } - else - { - // HOTS: 1958E8E - if(Struct68_D0.IsItemPresent(arg_4)) - { - DWORD FragOffs; - - // HOTS: 1958EAF - FragOffs = GetNameFragmentOffset(arg_4); - if(NextDB.pDB != NULL) + // HOTS: 1958E8E + if (IsPathFragmentString(TableIndex)) { - NextDB.pDB->sub_1958D70(pStruct1C, FragOffs); + DWORD FragmentOffset = GetPathFragmentOffset1(TableIndex); + + // HOTS: 1958EAF + if (pChildDB != NULL) + { + pChildDB->CopyPathFragmentByIndex(pSearch, FragmentOffset); + } + else + { + PathFragmentTable.CopyPathFragment(pSearch, FragmentOffset); + } } else { - IndexStruct_174.CopyNameFragment(pStruct1C, FragOffs); + // HOTS: 1958F50 + // Insert one character to the path being built + pStruct40->PathBuffer.Insert(LoBitsTable[TableIndex]); } - } - else - { - // HOTS: 1958F50 - // Insert one character to the path being built - pStruct40->array_00.InsertOneItem_CHAR(FrgmDist_LoBits.CharArray[arg_4]); - } - // HOTS: 1958FDE - if(arg_4 <= field_214) - return; + // HOTS: 1958FDE + if (TableIndex <= field_214) + return; - arg_4 = 0xFFFFFFFF - arg_4 + sub_1959F50(arg_4); + TableIndex = 0xFFFFFFFF - TableIndex + CollisionTable.GetItem1(TableIndex); + } } } -} - -// HOTS: 1959010 -bool TFileNameDatabase::sub_1959010(TMndxFindResult * pStruct1C, DWORD arg_4) -{ - TStruct40 * pStruct40 = pStruct1C->pStruct40; - PNAME_FRAG pNameEntry; - // HOTS: 1959024 - for(;;) + // HOTS: 1958B00 + bool CompareAndCopyPathFragment(TMndxSearch * pSearch) { - pNameEntry = NameFragTable.NameFragArray + (arg_4 & NameFragIndexMask); - if(arg_4 == pNameEntry->NextIndex) + TStruct40 * pStruct40 = &pSearch->Struct40; + PHASH_ENTRY pHashEntry; + DWORD HiBitsIndex; + DWORD ColTableIndex; + DWORD TableIndex; +/* + FILE * fp = fopen("E:\\PathFragmentTable.txt", "wt"); + if (fp != NULL) { - // HOTS: 1959047 - if((pNameEntry->FragOffs & 0xFFFFFF00) != 0xFFFFFF00) + for (DWORD i = 0; i < HashTable.ItemCount; i++) { - // HOTS: 195905A - if(NextDB.pDB != NULL) - { - if(!NextDB.pDB->sub_1959010(pStruct1C, pNameEntry->FragOffs)) - return false; - } - else + FragOffs = HashTable[i].FragOffs; + fprintf(fp, "%02x ('%c') %08X %08X %08X", i, (0x20 <= i && i < 0x80) ? i : 0x20, HashTable[i].ItemIndex, HashTable[i].NextIndex, FragOffs); + + if(FragOffs != 0x00800000) { - if(!IndexStruct_174.CheckAndCopyNameFragment(pStruct1C, pNameEntry->FragOffs)) - return false; + if((FragOffs & 0xFFFFFF00) == 0xFFFFFF00) + fprintf(fp, " '%c'", (char)(FragOffs & 0xFF)); + else + fprintf(fp, " %s", &PathFragmentTable.PathFragments[FragOffs]); } - } - else - { - // HOTS: 1959092 - if((char)(pNameEntry->FragOffs & 0xFF) != pStruct1C->szSearchMask[pStruct40->CharIndex]) - return false; - - // Insert the low 8 bits to the path being built - pStruct40->array_00.InsertOneItem_CHAR((char)(pNameEntry->FragOffs & 0xFF)); - pStruct40->CharIndex++; + fprintf(fp, "\n"); } - // HOTS: 195912E - arg_4 = pNameEntry->ItemIndex; - if(arg_4 == 0) - return true; + fclose(fp); } - else +*/ + // Calculate the item hash from the current char and fragment ID + TableIndex = pStruct40->CalcHashValue(pSearch->szSearchMask) & HashTableMask; + pHashEntry = &HashTable[TableIndex]; + + // Does the hash value ID match? + if(pStruct40->NodeIndex == pHashEntry->NodeIndex) { - // HOTS: 1959147 - if(Struct68_D0.IsItemPresent(arg_4)) + // If the higher 24 bits are set, then the fragment is just one letter, + // contained directly in the table. + if(!IsPathFragmentSingleChar(pHashEntry)) { - DWORD FragOffs; - - // HOTS: 195917C - FragOffs = GetNameFragmentOffset(arg_4); - if(NextDB.pDB != NULL) + // HOTS: 1958B59 + if (pChildDB != NULL) { - if(!NextDB.pDB->sub_1959010(pStruct1C, FragOffs)) + if (!pChildDB->CompareAndCopyPathFragmentByIndex(pSearch, pHashEntry->ChildTableIndex)) return false; } else { - if(!IndexStruct_174.CheckAndCopyNameFragment(pStruct1C, FragOffs)) + if (!PathFragmentTable.CompareAndCopyPathFragment(pSearch, pHashEntry->FragmentOffset)) return false; } } else { - // HOTS: 195920E - if(FrgmDist_LoBits.CharArray[arg_4] != pStruct1C->szSearchMask[pStruct40->CharIndex]) - return false; - - // Insert one character to the path being built - pStruct40->array_00.InsertOneItem_CHAR(FrgmDist_LoBits.CharArray[arg_4]); - pStruct40->CharIndex++; - } - - // HOTS: 19592B6 - if(arg_4 <= field_214) - return true; - - arg_4 = 0xFFFFFFFF - arg_4 + sub_1959F50(arg_4); - } - - // HOTS: 19592D5 - if(pStruct40->CharIndex >= pStruct1C->cchSearchMask) - break; - } - - sub_1958D70(pStruct1C, arg_4); - return true; -} - -// HOTS: 1959460 -bool TFileNameDatabase::sub_1959460(TMndxFindResult * pStruct1C) -{ - TStruct40 * pStruct40 = pStruct1C->pStruct40; - PPATH_STOP pPathStop; - PATH_STOP PathStop; - DWORD NewMaxItemCount; - DWORD FragOffs; - DWORD edi; - - if(pStruct40->SearchPhase == CASC_SEARCH_FINISHED) - return false; - - if(pStruct40->SearchPhase != CASC_SEARCH_SEARCHING) - { - // HOTS: 1959489 - pStruct40->InitSearchBuffers(); - - // If the caller passed a part of the search path, we need to find that one - while(pStruct40->CharIndex < pStruct1C->cchSearchMask) - { - if(!sub_1958B00(pStruct1C)) - { - pStruct40->SearchPhase = CASC_SEARCH_FINISHED; - return false; + // HOTS: 1958B88 + pStruct40->PathBuffer.Insert(pHashEntry->SingleChar); + pStruct40->PathLength++; } - } - - // HOTS: 19594b0 - PathStop.ItemIndex = pStruct40->ItemIndex; - PathStop.field_4 = 0; - PathStop.field_8 = pStruct40->array_00.ItemCount; - PathStop.field_C = 0xFFFFFFFF; - PathStop.field_10 = 0xFFFFFFFF; - pStruct40->PathStops.InsertOneItem_PATH_STOP(PathStop); - pStruct40->ItemCount = 1; - if(FileNameIndexes.IsItemPresent(pStruct40->ItemIndex)) - { - pStruct1C->szFoundPath = pStruct40->array_00.FirstValid.Chars; - pStruct1C->cchFoundPath = pStruct40->array_00.ItemCount; - pStruct1C->FileNameIndex = FileNameIndexes.GetItemValue(pStruct40->ItemIndex); + // HOTS: 1958BCA + pStruct40->NodeIndex = pHashEntry->NextIndex; return true; } - } - // HOTS: 1959522 - for(;;) - { - // HOTS: 1959530 - if(pStruct40->ItemCount == pStruct40->PathStops.ItemCount) - { - PPATH_STOP pLastStop; - DWORD CollisionIndex; - - pLastStop = pStruct40->PathStops.FirstValid.PathStopPtr + pStruct40->PathStops.ItemCount - 1; - CollisionIndex = sub_1959CB0(pLastStop->ItemIndex) + 1; - - // Insert a new structure - PathStop.ItemIndex = CollisionIndex - pLastStop->ItemIndex - 1;; - PathStop.field_4 = CollisionIndex; - PathStop.field_8 = 0; - PathStop.field_C = 0xFFFFFFFF; - PathStop.field_10 = 0xFFFFFFFF; - pStruct40->PathStops.InsertOneItem_PATH_STOP(PathStop); - } - - // HOTS: 19595BD - pPathStop = pStruct40->PathStops.FirstValid.PathStopPtr + pStruct40->ItemCount; + // HOTS: 1958BE5 + ColTableIndex = CollisionTable.GetItem0(pStruct40->NodeIndex) + 1; + pStruct40->NodeIndex = (ColTableIndex - pStruct40->NodeIndex - 1); + HiBitsIndex = CASC_INVALID_INDEX; - // HOTS: 19595CC - if(Struct68_00.IsItemPresent(pPathStop->field_4++)) + // Keep searching while we have a valid collision table entry + while(CollisionTable.IsItemPresent(ColTableIndex)) { - // HOTS: 19595F2 - pStruct40->ItemCount++; - - if(Struct68_D0.IsItemPresent(pPathStop->ItemIndex)) + // If we have high bits in the the bit at NodeIndex is set, it means that there is fragment offset + // If not, the byte in LoBitsTable is the character + if(IsPathFragmentString(pStruct40->NodeIndex)) { - // HOTS: 1959617 - if(pPathStop->field_C == 0xFFFFFFFF) - pPathStop->field_C = Struct68_D0.GetItemValue(pPathStop->ItemIndex); - else - pPathStop->field_C++; + DWORD FragmentOffset = GetPathFragmentOffset2(HiBitsIndex, pStruct40->NodeIndex); + DWORD SavePathLength = pStruct40->PathLength; // HOTS: 1958C62 - // HOTS: 1959630 - FragOffs = GetNameFragmentOffsetEx(pPathStop->ItemIndex, pPathStop->field_C); - if(NextDB.pDB != NULL) + // Do we have a child database? + if(pChildDB != NULL) { - // HOTS: 1959649 - NextDB.pDB->sub_1958D70(pStruct1C, FragOffs); + // HOTS: 1958CCB + if(pChildDB->CompareAndCopyPathFragmentByIndex(pSearch, FragmentOffset)) + return true; } else { - // HOTS: 1959654 - IndexStruct_174.CopyNameFragment(pStruct1C, FragOffs); + // HOTS: 1958CD6 + if(PathFragmentTable.CompareAndCopyPathFragment(pSearch, FragmentOffset)) + return true; } + + // HOTS: 1958CED + if(SavePathLength != pStruct40->PathLength) + return false; } else { - // HOTS: 1959665 - // Insert one character to the path being built - pStruct40->array_00.InsertOneItem_CHAR(FrgmDist_LoBits.CharArray[pPathStop->ItemIndex]); + // HOTS: 1958CFB + if(LoBitsTable[pStruct40->NodeIndex] == pSearch->szSearchMask[pStruct40->PathLength]) + { + // HOTS: 1958D11 + pStruct40->PathBuffer.Insert(LoBitsTable[pStruct40->NodeIndex]); + pStruct40->PathLength++; + return true; + } } - // HOTS: 19596AE - pPathStop->field_8 = pStruct40->array_00.ItemCount; + // HOTS: 1958D11 + pStruct40->NodeIndex++; + ColTableIndex++; + } + + return false; + } + + // HOTS: 1959010 + bool CompareAndCopyPathFragmentByIndex(TMndxSearch * pSearch, DWORD TableIndex) + { + TStruct40 * pStruct40 = &pSearch->Struct40; + PHASH_ENTRY pHashEntry; - // HOTS: 19596b6 - if(FileNameIndexes.IsItemPresent(pPathStop->ItemIndex)) + // HOTS: 1959024 + for(;;) + { + pHashEntry = &HashTable[TableIndex & HashTableMask]; + if(TableIndex == pHashEntry->NextIndex) { - // HOTS: 19596D1 - if(pPathStop->field_10 == 0xFFFFFFFF) + // HOTS: 1959047 + if(!IsPathFragmentSingleChar(pHashEntry)) { - // HOTS: 19596D9 - pPathStop->field_10 = FileNameIndexes.GetItemValue(pPathStop->ItemIndex); + // HOTS: 195905A + if(pChildDB != NULL) + { + if(!pChildDB->CompareAndCopyPathFragmentByIndex(pSearch, pHashEntry->ChildTableIndex)) + return false; + } + else + { + if(!PathFragmentTable.CompareAndCopyPathFragment(pSearch, pHashEntry->FragmentOffset)) + return false; + } } else { - pPathStop->field_10++; + // HOTS: 1959092 + if(pHashEntry->SingleChar != pSearch->szSearchMask[pStruct40->PathLength]) + return false; + + // Insert the low 8 bits to the path being built + pStruct40->PathBuffer.Insert(pHashEntry->SingleChar); + pStruct40->PathLength++; } - // HOTS: 1959755 - pStruct1C->szFoundPath = pStruct40->array_00.FirstValid.Chars; - pStruct1C->cchFoundPath = pStruct40->array_00.ItemCount; - pStruct1C->FileNameIndex = pPathStop->field_10; - return true; - } - } - else - { - // HOTS: 19596E9 - if(pStruct40->ItemCount == 1) - { - pStruct40->SearchPhase = CASC_SEARCH_FINISHED; - return false; + // HOTS: 195912E + TableIndex = pHashEntry->NodeIndex; + if(TableIndex == 0) + return true; } - - // HOTS: 19596F5 - pPathStop = pStruct40->PathStops.FirstValid.PathStopPtr + pStruct40->ItemCount - 1; - pPathStop->ItemIndex++; - - pPathStop = pStruct40->PathStops.FirstValid.PathStopPtr + pStruct40->ItemCount - 2; - edi = pPathStop->field_8; - - if(edi > pStruct40->array_00.MaxItemCount) + else { - // HOTS: 1959717 - NewMaxItemCount = edi; - - if(pStruct40->array_00.MaxItemCount > (edi / 2)) + // HOTS: 1959147 + if(IsPathFragmentString(TableIndex)) { - if(pStruct40->array_00.MaxItemCount > 0x7FFFFFFF) + // HOTS: 195917C + DWORD FragmentOffset = GetPathFragmentOffset1(TableIndex); + + if(pChildDB != NULL) { - NewMaxItemCount = 0xFFFFFFFF; + if(!pChildDB->CompareAndCopyPathFragmentByIndex(pSearch, FragmentOffset)) + return false; } else { - NewMaxItemCount = pStruct40->array_00.MaxItemCount + pStruct40->array_00.MaxItemCount; + if(!PathFragmentTable.CompareAndCopyPathFragment(pSearch, FragmentOffset)) + return false; } } + else + { + // HOTS: 195920E + if(LoBitsTable[TableIndex] != pSearch->szSearchMask[pStruct40->PathLength]) + return false; - pStruct40->array_00.SetMaxItems_CHARS(NewMaxItemCount); - } + // Insert one character to the path being built + pStruct40->PathBuffer.Insert(LoBitsTable[TableIndex]); + pStruct40->PathLength++; + } - // HOTS: 1959749 - pStruct40->array_00.ItemCount = edi; - pStruct40->ItemCount--; - } - } -} + // HOTS: 19592B6 + if(TableIndex <= field_214) + return true; -// HOTS: 1958B00 -bool TFileNameDatabase::sub_1958B00(TMndxFindResult * pStruct1C) -{ - TStruct40 * pStruct40 = pStruct1C->pStruct40; - LPBYTE pbPathName = (LPBYTE)pStruct1C->szSearchMask; - DWORD CollisionIndex; - DWORD FragmentOffset; - DWORD SaveCharIndex; - DWORD ItemIndex; - DWORD FragOffs; - DWORD var_4; - - ItemIndex = pbPathName[pStruct40->CharIndex] ^ (pStruct40->ItemIndex << 0x05) ^ pStruct40->ItemIndex; - ItemIndex = ItemIndex & NameFragIndexMask; - if(pStruct40->ItemIndex == NameFragTable.NameFragArray[ItemIndex].ItemIndex) - { - // HOTS: 1958B45 - FragmentOffset = NameFragTable.NameFragArray[ItemIndex].FragOffs; - if((FragmentOffset & 0xFFFFFF00) == 0xFFFFFF00) - { - // HOTS: 1958B88 - pStruct40->array_00.InsertOneItem_CHAR((char)FragmentOffset); - pStruct40->ItemIndex = NameFragTable.NameFragArray[ItemIndex].NextIndex; - pStruct40->CharIndex++; - return true; - } + TableIndex = 0xFFFFFFFF - TableIndex + CollisionTable.GetItem1(TableIndex); + } - // HOTS: 1958B59 - if(NextDB.pDB != NULL) - { - if(!NextDB.pDB->sub_1959010(pStruct1C, FragmentOffset)) - return false; - } - else - { - if(!IndexStruct_174.CheckAndCopyNameFragment(pStruct1C, FragmentOffset)) - return false; + // HOTS: 19592D5 + if(pStruct40->PathLength >= pSearch->cchSearchMask) + break; } - // HOTS: 1958BCA - pStruct40->ItemIndex = NameFragTable.NameFragArray[ItemIndex].NextIndex; + CopyPathFragmentByIndex(pSearch, TableIndex); return true; } - // HOTS: 1958BE5 - CollisionIndex = sub_1959CB0(pStruct40->ItemIndex) + 1; - if(!Struct68_00.IsItemPresent(CollisionIndex)) - return false; - - pStruct40->ItemIndex = (CollisionIndex - pStruct40->ItemIndex - 1); - var_4 = 0xFFFFFFFF; - - // HOTS: 1958C20 - for(;;) + // HOTS: 1959460 + bool DoSearch(TMndxSearch * pSearch) { - if(Struct68_D0.IsItemPresent(pStruct40->ItemIndex)) + TStruct40 * pStruct40 = &pSearch->Struct40; + TPathStop * pPathStop; + DWORD edi; + + // Perform action based on the search phase + switch (pStruct40->SearchPhase) { - // HOTS: 1958C0E - if(var_4 == 0xFFFFFFFF) - { - // HOTS: 1958C4B - var_4 = Struct68_D0.GetItemValue(pStruct40->ItemIndex); - } - else + case MNDX_SEARCH_INITIALIZING: { - var_4++; - } + // HOTS: 1959489 + pStruct40->BeginSearch(); - // HOTS: 1958C62 - SaveCharIndex = pStruct40->CharIndex; + // If the caller passed a part of the search path, we need to find that one + while (pStruct40->PathLength < pSearch->cchSearchMask) + { + if (!CompareAndCopyPathFragment(pSearch)) + { + pStruct40->SearchPhase = MNDX_SEARCH_FINISHED; + return false; + } + } - FragOffs = GetNameFragmentOffsetEx(pStruct40->ItemIndex, var_4); - if(NextDB.pDB != NULL) - { - // HOTS: 1958CCB - if(NextDB.pDB->sub_1959010(pStruct1C, FragOffs)) - return true; - } - else - { - // HOTS: 1958CD6 - if(IndexStruct_174.CheckAndCopyNameFragment(pStruct1C, FragOffs)) + // HOTS: 19594b0 + TPathStop PathStop(pStruct40->NodeIndex, 0, pStruct40->PathBuffer.ItemCount); + pStruct40->PathStops.Insert(PathStop); + pStruct40->ItemCount = 1; + + if (FileNameIndexes.IsItemPresent(pStruct40->NodeIndex)) + { + pSearch->szFoundPath = &pStruct40->PathBuffer[0]; + pSearch->cchFoundPath = pStruct40->PathBuffer.ItemCount; + pSearch->nIndex = FileNameIndexes.GetItemValueAt(pStruct40->NodeIndex); return true; + } } + // No break here, go straight to the MNDX_SEARCH_SEARCHING - // HOTS: 1958CED - if(SaveCharIndex != pStruct40->CharIndex) - return false; - } - else - { - // HOTS: 1958CFB - if(FrgmDist_LoBits.ByteArray[pStruct40->ItemIndex] == pStruct1C->szSearchMask[pStruct40->CharIndex]) + case MNDX_SEARCH_SEARCHING: { - // HOTS: 1958D11 - pStruct40->array_00.InsertOneItem_CHAR(FrgmDist_LoBits.ByteArray[pStruct40->ItemIndex]); - pStruct40->CharIndex++; - return true; - } - } + // HOTS: 1959522 + for (;;) + { + // HOTS: 1959530 + if (pStruct40->ItemCount == pStruct40->PathStops.ItemCount) + { + TPathStop * pLastStop; + DWORD ColTableIndex; - // HOTS: 1958D11 - pStruct40->ItemIndex++; - CollisionIndex++; + pLastStop = &pStruct40->PathStops[pStruct40->PathStops.ItemCount - 1]; - if(!Struct68_00.IsItemPresent(CollisionIndex)) - break; - } + ColTableIndex = CollisionTable.GetItem0(pLastStop->LoBitsIndex) + 1; - return false; -} + // Insert a new structure + TPathStop PathStop(ColTableIndex - pLastStop->LoBitsIndex - 1, ColTableIndex, 0); + pStruct40->PathStops.Insert(PathStop); + } -// HOTS: 1957EF0 -bool TFileNameDatabase::FindFileInDatabase(TMndxFindResult * pStruct1C) -{ - TStruct40 * pStruct40 = pStruct1C->pStruct40; + // HOTS: 19595BD + pPathStop = &pStruct40->PathStops[pStruct40->ItemCount]; - pStruct40->ItemIndex = 0; - pStruct40->CharIndex = 0; - pStruct40->SearchPhase = CASC_SEARCH_INITIALIZING; + // HOTS: 19595CC + if (CollisionTable.IsItemPresent(pPathStop->field_4++)) + { + // HOTS: 19595F2 + pStruct40->ItemCount++; + + if (IsPathFragmentString(pPathStop->LoBitsIndex)) + { + DWORD FragmentOffset = GetPathFragmentOffset2(pPathStop->HiBitsIndex_PathFragment, pPathStop->LoBitsIndex); + + // HOTS: 1959630 + if (pChildDB != NULL) + { + // HOTS: 1959649 + pChildDB->CopyPathFragmentByIndex(pSearch, FragmentOffset); + } + else + { + // HOTS: 1959654 + PathFragmentTable.CopyPathFragment(pSearch, FragmentOffset); + } + } + else + { + // HOTS: 1959665 + // Insert one character to the path being built + pStruct40->PathBuffer.Insert(LoBitsTable[pPathStop->LoBitsIndex]); + } + + // HOTS: 19596AE + pPathStop->Count = pStruct40->PathBuffer.ItemCount; + + // HOTS: 19596b6 + if (FileNameIndexes.IsItemPresent(pPathStop->LoBitsIndex)) + { + // HOTS: 19596D1 + if (pPathStop->field_10 == 0xFFFFFFFF) + { + // HOTS: 19596D9 + pPathStop->field_10 = FileNameIndexes.GetItemValueAt(pPathStop->LoBitsIndex); + } + else + { + pPathStop->field_10++; + } + + // HOTS: 1959755 + pSearch->szFoundPath = &pStruct40->PathBuffer[0]; + pSearch->cchFoundPath = pStruct40->PathBuffer.ItemCount; + pSearch->nIndex = pPathStop->field_10; + return true; + } + } + else + { + // HOTS: 19596E9 + if (pStruct40->ItemCount == 1) + { + pStruct40->SearchPhase = MNDX_SEARCH_FINISHED; + return false; + } + + // HOTS: 19596F5 + pStruct40->PathStops[pStruct40->ItemCount - 1].LoBitsIndex++; + edi = pStruct40->PathStops[pStruct40->ItemCount - 2].Count; + pStruct40->PathBuffer.SetMaxItemsIf(edi); + + // HOTS: 1959749 + pStruct40->PathBuffer.ItemCount = edi; + pStruct40->ItemCount--; + } + } + } - if(pStruct1C->cchSearchMask > 0) - { - while(pStruct40->CharIndex < pStruct1C->cchSearchMask) - { - // HOTS: 01957F12 - if(!CheckNextPathFragment(pStruct1C)) - return false; + case MNDX_SEARCH_FINISHED: + break; } - } - // HOTS: 1957F26 - if(!FileNameIndexes.IsItemPresent(pStruct40->ItemIndex)) return false; + } - pStruct1C->szFoundPath = pStruct1C->szSearchMask; - pStruct1C->cchFoundPath = pStruct1C->cchSearchMask; - pStruct1C->FileNameIndex = FileNameIndexes.GetItemValue(pStruct40->ItemIndex); - return true; -} - -// HOTS: 1959790 -int TFileNameDatabase::LoadFromStream(TByteStream & InStream) -{ - DWORD dwBitMask; - int nError; - - nError = Struct68_00.LoadFromStream_Exchange(InStream); - if(nError != ERROR_SUCCESS) - return nError; + // HOTS: 1957EF0 + bool FindFileInDatabase(TMndxSearch * pSearch) + { + TStruct40 * pStruct40 = &pSearch->Struct40; - nError = FileNameIndexes.LoadFromStream_Exchange(InStream); - if(nError != ERROR_SUCCESS) - return nError; + pStruct40->NodeIndex = 0; + pStruct40->PathLength = 0; + pStruct40->SearchPhase = MNDX_SEARCH_INITIALIZING; - nError = Struct68_D0.LoadFromStream_Exchange(InStream); - if(nError != ERROR_SUCCESS) - return nError; - - // HOTS: 019597CD - nError = FrgmDist_LoBits.LoadBytes_Copy(InStream); - if(nError != ERROR_SUCCESS) - return nError; + if(pSearch->cchSearchMask > 0) + { + while(pStruct40->PathLength < pSearch->cchSearchMask) + { + // HOTS: 01957F12 + if(!ComparePathFragment(pSearch)) + return false; + } + } - nError = FrgmDist_HiBits.LoadFromStream_Exchange(InStream); - if(nError != ERROR_SUCCESS) - return nError; + // HOTS: 1957F26 + if(!FileNameIndexes.IsItemPresent(pStruct40->NodeIndex)) + return false; - // HOTS: 019597F5 - nError = IndexStruct_174.LoadFromStream_Exchange(InStream); - if(nError != ERROR_SUCCESS) - return nError; + pSearch->szFoundPath = pSearch->szSearchMask; + pSearch->cchFoundPath = pSearch->cchSearchMask; + pSearch->nIndex = FileNameIndexes.GetItemValueAt(pStruct40->NodeIndex); + return true; + } - // HOTS: 0195980A - if(Struct68_D0.ValidItemCount != 0 && IndexStruct_174.NameFragments.ItemCount == 0) + // HOTS: 1959790 + int LoadFromStream(TByteStream & InStream) { - TFileNameDatabase * pNextDB = new TFileNameDatabase; + DWORD dwBitMask; + int nError; - nError = NextDB.SetDatabase(pNextDB); + nError = CollisionTable.LoadFromStream(InStream); if(nError != ERROR_SUCCESS) return nError; - if(NextDB.pDB == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - nError = NextDB.pDB->LoadFromStream(InStream); + nError = FileNameIndexes.LoadFromStream(InStream); if(nError != ERROR_SUCCESS) return nError; - } - // HOTS: 0195986B - nError = NameFragTable.LoadFragmentInfos_Copy(InStream); - if(nError != ERROR_SUCCESS) - return nError; - - NameFragIndexMask = NameFragTable.ItemCount - 1; - - nError = InStream.GetValue_DWORD(field_214); - if(nError != ERROR_SUCCESS) - return nError; - - nError = InStream.GetValue_DWORD(dwBitMask); - if(nError != ERROR_SUCCESS) - return nError; - - return Struct10.sub_1957800(dwBitMask); -} - -// HOTS: 19598D0 -int TFileNameDatabase::LoadFromStream_Exchange(TByteStream & InStream) -{ - TFileNameDatabase TempDatabase; - ARRAY_POINTER Pointer; - DWORD dwSignature; - int nError; - - // Get pointer to MAR signature - nError = InStream.GetBytes(sizeof(DWORD), &Pointer); - if(nError != ERROR_SUCCESS) - return nError; - - // Verify the signature - dwSignature = Pointer.Uint32s[0]; - if(dwSignature != CASC_MAR_SIGNATURE) - return ERROR_BAD_FORMAT; - - nError = TempDatabase.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; - - MarStream.ExchangeWith(InStream); - ExchangeWith(TempDatabase); - return ERROR_SUCCESS; -} - -//----------------------------------------------------------------------------- -// TFileNameDatabasePtr functions - -// HOTS: 01956D70 -TFileNameDatabasePtr::TFileNameDatabasePtr() -{ - pDB = NULL; -} - -TFileNameDatabasePtr::~TFileNameDatabasePtr() -{ - delete pDB; -} + nError = CollisionHiBitsIndexes.LoadFromStream(InStream); + if(nError != ERROR_SUCCESS) + return nError; -// HOTS: 1956C60 -int TFileNameDatabasePtr::FindFileInDatabase(TMndxFindResult * pStruct1C) -{ - int nError = ERROR_SUCCESS; + // HOTS: 019597CD + nError = LoBitsTable.LoadFromStream(InStream); + if(nError != ERROR_SUCCESS) + return nError; - if(pDB == NULL) - return ERROR_INVALID_PARAMETER; + nError = HiBitsTable.LoadBitsFromStream(InStream); + if(nError != ERROR_SUCCESS) + return nError; - nError = pStruct1C->CreateStruct40(); - if(nError != ERROR_SUCCESS) - return nError; + // HOTS: 019597F5 + nError = PathFragmentTable.LoadFromStream(InStream); + if(nError != ERROR_SUCCESS) + return nError; - if(!pDB->FindFileInDatabase(pStruct1C)) - nError = ERROR_FILE_NOT_FOUND; + // HOTS: 0195980A + if(CollisionHiBitsIndexes.ValidItemCount != 0 && PathFragmentTable.PathFragments.ItemCount == 0) + { + TFileNameDatabase * pNewDB; - pStruct1C->FreeStruct40(); - return nError; -} + pNewDB = new TFileNameDatabase; + if (pNewDB == NULL) + return ERROR_NOT_ENOUGH_MEMORY; -// HOTS: 1956CE0 -int TFileNameDatabasePtr::sub_1956CE0(TMndxFindResult * pStruct1C, bool * pbFindResult) -{ - int nError = ERROR_SUCCESS; + nError = SetChildDatabase(pNewDB); + if(nError != ERROR_SUCCESS) + return nError; - if(pDB == NULL) - return ERROR_INVALID_PARAMETER; + nError = pChildDB->LoadFromStream(InStream); + if(nError != ERROR_SUCCESS) + return nError; + } - // Create the pStruct40, if not initialized yet - if(pStruct1C->pStruct40 == NULL) - { - nError = pStruct1C->CreateStruct40(); + // HOTS: 0195986B + nError = HashTable.LoadFromStream(InStream); if(nError != ERROR_SUCCESS) return nError; - } - *pbFindResult = pDB->sub_1959460(pStruct1C); - return nError; -} + HashTableMask = HashTable.ItemCount - 1; -// HOTS: 1956D20 -int TFileNameDatabasePtr::GetFileNameCount(PDWORD PtrFileNameCount) -{ - if(pDB == NULL) - return ERROR_INVALID_PARAMETER; + nError = InStream.GetValue<DWORD>(field_214); + if(nError != ERROR_SUCCESS) + return nError; - PtrFileNameCount[0] = pDB->FileNameIndexes.ValidItemCount; - return ERROR_SUCCESS; -} + nError = InStream.GetValue<DWORD>(dwBitMask); + if(nError != ERROR_SUCCESS) + return nError; -// HOTS: 1956DA0 -int TFileNameDatabasePtr::CreateDatabase(LPBYTE pbMarData, DWORD cbMarData) -{ - TFileNameDatabase * pDatabase; - TByteStream ByteStream; - int nError; + return Struct10.sub_1957800(dwBitMask); + } - if(pbMarData == NULL && cbMarData != 0) - return ERROR_INVALID_PARAMETER; + TSparseArray CollisionTable; // Table of valid collisions, indexed by NodeIndex + TSparseArray FileNameIndexes; // Array of file name indexes + TSparseArray CollisionHiBitsIndexes; // Table of indexes of high bits (above 8 bits) for collisions - pDatabase = new TFileNameDatabase; - if(pDatabase != NULL) - { - nError = ByteStream.SetByteBuffer(pbMarData, cbMarData); - if(nError == ERROR_SUCCESS) - { - // HOTS: 1956E11 - nError = pDatabase->LoadFromStream_Exchange(ByteStream); - if(nError == ERROR_SUCCESS) - { - pDB = pDatabase; - return ERROR_SUCCESS; - } - } + // This pair of arrays serves for fast conversion from node index to FragmentOffset / FragmentChar + TGenericArray<BYTE> LoBitsTable; // Array of lower 8 bits of name fragment offset + TBitEntryArray HiBitsTable; // Array of upper x bits of name fragment offset - delete pDatabase; - return nError; - } - else - { - return ERROR_NOT_ENOUGH_MEMORY; - } -} + TPathFragmentTable PathFragmentTable; + TFileNameDatabase * pChildDB; -// HOTS: 19584B0 -int TFileNameDatabasePtr::SetDatabase(TFileNameDatabase * pNewDB) -{ - if(pNewDB != NULL && pDB == pNewDB) - return ERROR_INVALID_PARAMETER; + TGenericArray<HASH_ENTRY> HashTable; // Hash table for searching name fragments - if(pDB != NULL) - delete pDB; - pDB = pNewDB; - return ERROR_SUCCESS; -} + DWORD HashTableMask; // Mask to get hash table index from hash value + DWORD field_214; + TStruct10 Struct10; +}; //----------------------------------------------------------------------------- // Local functions - MAR file -// HOTS: 00E94180 -static void MAR_FILE_CreateDatabase(PMAR_FILE pMarFile) +class TMndxMarFile { - pMarFile->pDatabasePtr = new TFileNameDatabasePtr; - if(pMarFile->pDatabasePtr != NULL) - pMarFile->pDatabasePtr->CreateDatabase(pMarFile->pbMarData, pMarFile->cbMarData); -} + public: -static int MAR_FILE_SearchFile(PMAR_FILE pMarFile, TMndxFindResult * pStruct1C) -{ - return pMarFile->pDatabasePtr->FindFileInDatabase(pStruct1C); -} - -static void MAR_FILE_Destructor(PMAR_FILE pMarFile) -{ - if(pMarFile != NULL) + TMndxMarFile() { - if(pMarFile->pDatabasePtr != NULL) - delete pMarFile->pDatabasePtr; - if(pMarFile->pbMarData != NULL) - CASC_FREE(pMarFile->pbMarData); - - CASC_FREE(pMarFile); + pDatabase = NULL; + pbMarData = NULL; + cbMarData = 0; } -} - -//----------------------------------------------------------------------------- -// Package functions - -// TODO: When working, increment these values to lower number of (re)allocations -#define CASC_PACKAGES_INIT 0x10 -#define CASC_PACKAGES_DELTA 0x10 -static PCASC_MNDX_PACKAGES AllocatePackages(size_t nNameEntries, size_t nNameBufferMax) -{ - PCASC_MNDX_PACKAGES pPackages; - size_t cbToAllocate; - - // Allocate space - cbToAllocate = sizeof(CASC_MNDX_PACKAGES) + (nNameEntries * sizeof(CASC_MNDX_PACKAGE)) + nNameBufferMax; - pPackages = (PCASC_MNDX_PACKAGES)CASC_ALLOC(BYTE, cbToAllocate); - if(pPackages != NULL) + ~TMndxMarFile() { - // Fill the structure - memset(pPackages, 0, cbToAllocate); - - // Init the list entries - pPackages->szNameBuffer = (char *)(&pPackages->Packages[nNameEntries]); - pPackages->NameEntries = nNameEntries; - pPackages->NameBufferUsed = 0; - pPackages->NameBufferMax = nNameBufferMax; + if(pDatabase != NULL) + delete pDatabase; + CASC_FREE(pbMarData); } - return pPackages; -} - -static PCASC_MNDX_PACKAGES InsertToPackageList( - PCASC_MNDX_PACKAGES pPackages, - const char * szFileName, - size_t cchFileName, - size_t nPackageIndex) -{ - size_t nNewNameEntries = pPackages->NameEntries; - size_t nNewNameBufferMax = pPackages->NameBufferMax; - size_t cbToAllocate; - char * szNameBuffer; - - // Need to reallocate? - while(nPackageIndex >= nNewNameEntries) - nNewNameEntries = nNewNameEntries + CASC_PACKAGES_DELTA; - if((pPackages->NameBufferUsed + cchFileName + 1) > nNewNameBufferMax) - nNewNameBufferMax = nNewNameBufferMax + 0x1000; - - // If any of the two variables overflowed, we need to reallocate the name list - if(nNewNameEntries > pPackages->NameEntries || nNewNameBufferMax > pPackages->NameBufferMax) - { - PCASC_MNDX_PACKAGES pOldPackages = pPackages; - - // Allocate new name list - cbToAllocate = sizeof(CASC_MNDX_PACKAGES) + (nNewNameEntries * sizeof(CASC_MNDX_PACKAGE)) + nNewNameBufferMax; - pPackages = (PCASC_MNDX_PACKAGES)CASC_ALLOC(BYTE, cbToAllocate); - if(pPackages == NULL) - return NULL; - - // Copy the old entries - memset(pPackages, 0, cbToAllocate); - pPackages->szNameBuffer = szNameBuffer = (char *)(&pPackages->Packages[nNewNameEntries]); - memcpy(pPackages->szNameBuffer, pOldPackages->szNameBuffer, pOldPackages->NameBufferUsed); + // HOTS: 00E94180 + int LoadRootData(FILE_MAR_INFO & MarInfo, LPBYTE pbRootFile, LPBYTE pbRootEnd) + { + // Allocate the MAR data + pbMarData = CASC_ALLOC(BYTE, MarInfo.MarDataSize); + cbMarData = MarInfo.MarDataSize; + if(pbMarData == NULL) + return ERROR_NOT_ENOUGH_MEMORY; - // Copy the old entries - for(size_t i = 0; i < pOldPackages->NameEntries; i++) - { - if(pOldPackages->Packages[i].szFileName != NULL) - { - pPackages->Packages[i].szFileName = pPackages->szNameBuffer + (pOldPackages->Packages[i].szFileName - pOldPackages->szNameBuffer); - pPackages->Packages[i].nLength = pOldPackages->Packages[i].nLength; - } - } + // Capture the MAR data + if(!CaptureData(pbRootFile + MarInfo.MarDataOffset, pbRootEnd, pbMarData, cbMarData)) + return ERROR_FILE_CORRUPT; - // Fill the limits - pPackages->NameEntries = nNewNameEntries; - pPackages->NameBufferUsed = pOldPackages->NameBufferUsed; - pPackages->NameBufferMax = nNewNameBufferMax; + // Create the file name database + pDatabase = new TFileNameDatabase(); + if(pDatabase == NULL) + return ERROR_NOT_ENOUGH_MEMORY; - // Switch the name lists - CASC_FREE(pOldPackages); + return pDatabase->Load(pbMarData, cbMarData); } - // The slot is expected to be empty at the moment - assert(pPackages->Packages[nPackageIndex].szFileName == NULL); - assert(pPackages->Packages[nPackageIndex].nLength == 0); + // HOTS: 1956C60 + int SearchFile(TMndxSearch * pSearch) + { + int nError = ERROR_SUCCESS; - // Set the file name entry - szNameBuffer = pPackages->szNameBuffer + pPackages->NameBufferUsed; - pPackages->Packages[nPackageIndex].szFileName = szNameBuffer; - pPackages->Packages[nPackageIndex].nLength = cchFileName; - memcpy(szNameBuffer, szFileName, cchFileName); - pPackages->NameBufferUsed += (cchFileName + 1); - return pPackages; -} + if(pDatabase == NULL) + return ERROR_INVALID_PARAMETER; -static int LoadPackageNames(PCASC_MNDX_INFO pMndxInfo, PCASC_MNDX_PACKAGES * ppPackages) -{ - TMndxFindResult Struct1C; - PCASC_MNDX_PACKAGES pPackages = NULL; - PMAR_FILE pMarFile; + if(!pDatabase->FindFileInDatabase(pSearch)) + nError = ERROR_FILE_NOT_FOUND; - // Sanity checks - assert(pMndxInfo != NULL); + return nError; + } - // Prepare the file name search in the top level directory - pMarFile = pMndxInfo->pMarFile1; - Struct1C.SetSearchPath("", 0); + // HOTS: 1956CE0 + int DoSearch(TMndxSearch * pSearch, bool * pbFindResult) + { + int nError = ERROR_SUCCESS; - // Allocate initial name list structure - pPackages = AllocatePackages(CASC_PACKAGES_INIT, 0x1000); - if(pPackages == NULL) - return ERROR_NOT_ENOUGH_MEMORY; + if(pDatabase == NULL) + return ERROR_INVALID_PARAMETER; - // Keep searching as long as we find something - for(;;) - { - bool bFindResult = false; + *pbFindResult = pDatabase->DoSearch(pSearch); + return nError; + } - // Search the next file name - pMarFile->pDatabasePtr->sub_1956CE0(&Struct1C, &bFindResult); - if(bFindResult == false) - break; + // HOTS: 1956D20 + int GetFileNameCount(size_t * PtrFileNameCount) + { + if(pDatabase == NULL) + return ERROR_INVALID_PARAMETER; - // Insert the found name to the top level directory list - pPackages = InsertToPackageList(pPackages, Struct1C.szFoundPath, Struct1C.cchFoundPath, Struct1C.FileNameIndex); - if(pPackages == NULL) - return ERROR_NOT_ENOUGH_MEMORY; + PtrFileNameCount[0] = pDatabase->FileNameIndexes.ValidItemCount; + return ERROR_SUCCESS; } - // Give the packages to the caller - if(ppPackages != NULL) - ppPackages[0] = pPackages; - return ERROR_SUCCESS; -} +// protected: + TFileNameDatabase * pDatabase; + LPBYTE pbMarData; + size_t cbMarData; +}; //----------------------------------------------------------------------------- // Implementation of root file functions -struct TRootHandler_MNDX : public TRootHandler +typedef struct _FILE_MNDX_INFO { - CASC_MNDX_INFO MndxInfo; + BYTE RootFileName[MD5_HASH_SIZE]; // Name (aka MD5) of the root file + DWORD HeaderVersion; // Must be <= 2 + DWORD FormatVersion; + DWORD field_1C; + DWORD field_20; + DWORD MarInfoOffset; // Offset of the first MAR entry info + DWORD MarInfoCount; // Number of the MAR info entries + DWORD MarInfoSize; // Size of the MAR info entry + DWORD CKeyEntriesOffset; // Offset of the CKey entries, relative to begin of the root file + DWORD CKeyEntriesCount; // Number of CKeys (files) in the root file + DWORD FileNameCount; // Number of unique file names. More files with the same name in the different packages can exist + DWORD CKeyEntrySize; // Size of one CKey root entry + TMndxMarFile * MarFiles[MAR_COUNT]; // File name list for the packages - PCASC_ROOT_ENTRY_MNDX * ppValidEntries; - PCASC_ROOT_ENTRY_MNDX pMndxEntries; - PCASC_MNDX_PACKAGES pPackages; // Linear list of present packages -}; +} FILE_MNDX_INFO, *PFILE_MNDX_INFO; -PCASC_MNDX_PACKAGE FindMndxPackage(TRootHandler_MNDX * pRootHandler, const char * szFileName) +struct TMndxHandler { - PCASC_MNDX_PACKAGE pMatching = NULL; - PCASC_MNDX_PACKAGE pPackage; - size_t nMaxLength = 0; - size_t nLength = strlen(szFileName); - - // Packages must be loaded - assert(pRootHandler->pPackages != NULL); - pPackage = pRootHandler->pPackages->Packages; - - //FILE * fp = fopen("E:\\packages.txt", "wt"); - //for(size_t i = 0; i < hs->pPackages->NameEntries; i++, pPackage++) - //{ - // if(pPackage->szFileName != NULL) - // fprintf(fp, "%s\n", pPackage->szFileName); - //} - //fclose(fp); - - // Find the longest matching name - for(size_t i = 0; i < pRootHandler->pPackages->NameEntries; i++, pPackage++) - { - if(pPackage->szFileName != NULL && pPackage->nLength < nLength && pPackage->nLength > nMaxLength) - { - // Compare the package name - if(!strncmp(szFileName, pPackage->szFileName, pPackage->nLength)) - { - pMatching = pPackage; - nMaxLength = pPackage->nLength; - } - } - } + public: - // Give the package pointer or NULL if not found - return pMatching; -} + // + // Constructor and destructor + // -int SearchMndxInfo(TRootHandler_MNDX * pRootHandler, const char * szFileName, DWORD dwPackage, PCASC_ROOT_ENTRY_MNDX * ppRootEntry) -{ - PCASC_ROOT_ENTRY_MNDX pRootEntry; - PCASC_MNDX_INFO pMndxInfo = &pRootHandler->MndxInfo; - TMndxFindResult Struct1C; + TMndxHandler() + { + memset(this, 0, sizeof(TMndxHandler)); + } - // Search the database for the file name - if(pMndxInfo->bRootFileLoaded) + ~TMndxHandler() { - Struct1C.SetSearchPath(szFileName, strlen(szFileName)); + PMNDX_PACKAGE pPackage; + size_t i; - // Search the file name in the second MAR info (the one with stripped package names) - if(MAR_FILE_SearchFile(pMndxInfo->pMarFile2, &Struct1C) != ERROR_SUCCESS) - return ERROR_FILE_NOT_FOUND; + for(i = 0; i < MAR_COUNT; i++) + delete MndxInfo.MarFiles[i]; + CASC_FREE(FileNameIndexToCKeyIndex); + pCKeyEntries = NULL; - // The found MNDX index must fall into range of valid MNDX entries - if(Struct1C.FileNameIndex < pMndxInfo->MndxEntriesValid) + for(i = 0; i < Packages.ItemCount(); i++) { - // HOTS: E945F4 - pRootEntry = pRootHandler->ppValidEntries[Struct1C.FileNameIndex]; - while((pRootEntry->Flags & 0x00FFFFFF) != dwPackage) - { - // The highest bit serves as a terminator if set - if(pRootEntry->Flags & 0x80000000) - return ERROR_FILE_NOT_FOUND; - - pRootEntry++; - } - - // Give the root entry pointer to the caller - if(ppRootEntry != NULL) - ppRootEntry[0] = pRootEntry; - return ERROR_SUCCESS; + pPackage = (PMNDX_PACKAGE)Packages.ItemAt(i); + CASC_FREE(pPackage->szFileName); } + Packages.Free(); } - return ERROR_FILE_NOT_FOUND; -} - -static LPBYTE FillFindData(TRootHandler_MNDX * pRootHandler, TCascSearch * pSearch, TMndxFindResult * pStruct1C, PDWORD PtrFileSize) -{ - PCASC_ROOT_ENTRY_MNDX pRootEntry = NULL; - PCASC_MNDX_PACKAGE pPackage; - char * szStrippedPtr; - char szStrippedName[MAX_PATH+1]; - int nError; - - // Sanity check - assert(pStruct1C->cchFoundPath < MAX_PATH); - - // Fill the file name - memcpy(pSearch->szFileName, pStruct1C->szFoundPath, pStruct1C->cchFoundPath); - pSearch->szFileName[pStruct1C->cchFoundPath] = 0; - - // Fill the file size - pPackage = FindMndxPackage(pRootHandler, pSearch->szFileName); - if(pPackage == NULL) - return NULL; - - // Cut the package name off the full path - szStrippedPtr = pSearch->szFileName + pPackage->nLength; - while(szStrippedPtr[0] == '/') - szStrippedPtr++; - - // We need to convert the stripped name to lowercase, replacing backslashes with slashes - NormalizeFileName_LowerSlash(szStrippedName, szStrippedPtr, MAX_PATH); - - // Search the package - nError = SearchMndxInfo(pRootHandler, szStrippedName, (DWORD)(pPackage - pRootHandler->pPackages->Packages), &pRootEntry); - if(nError != ERROR_SUCCESS) - return NULL; - - // Give the file size - if(PtrFileSize != NULL) - PtrFileSize[0] = pRootEntry->FileSize; - return pRootEntry->EncodingKey; -} - -static int MndxHandler_Insert(TRootHandler_MNDX *, const char *, LPBYTE) -{ - return ERROR_NOT_SUPPORTED; -} - -static LPBYTE MndxHandler_Search(TRootHandler_MNDX * pRootHandler, TCascSearch * pSearch, PDWORD PtrFileSize, PDWORD /* PtrLocaleFlags */, PDWORD /* PtrFileDataId */) -{ - TMndxFindResult * pStruct1C = NULL; - PCASC_MNDX_INFO pMndxInfo = &pRootHandler->MndxInfo; - PMAR_FILE pMarFile = pMndxInfo->pMarFile3; - bool bFindResult = false; + // + // Helper functions + // - // If the first time, allocate the structure for the search result - if(pSearch->pRootContext == NULL) + static LPBYTE CaptureRootHeader(FILE_MNDX_HEADER & MndxHeader, LPBYTE pbRootPtr, LPBYTE pbRootEnd) { - // Create the new search structure - pStruct1C = new TMndxFindResult; - if(pStruct1C == NULL) + // Capture the root header + pbRootPtr = CaptureData(pbRootPtr, pbRootEnd, &MndxHeader, sizeof(FILE_MNDX_HEADER)); + if (pbRootPtr == NULL) return NULL; - // Setup the search mask - pStruct1C->SetSearchPath("", 0); - pSearch->pRootContext = pStruct1C; - } - - // Make shortcut for the search structure - assert(pSearch->pRootContext != NULL); - pStruct1C = (TMndxFindResult *)pSearch->pRootContext; - - // Keep searching - for(;;) - { - // Search the next file name (our code) - pMarFile->pDatabasePtr->sub_1956CE0(pStruct1C, &bFindResult); - if (bFindResult == false) + // Check signature and version + if (MndxHeader.Signature != CASC_MNDX_ROOT_SIGNATURE || MndxHeader.FormatVersion > 2 || MndxHeader.FormatVersion < 1) return NULL; - // If we have no wild mask, we found it - if (pSearch->szMask == NULL || pSearch->szMask[0] == 0) - break; - - // Copy the found name to the buffer - memcpy(pSearch->szFileName, pStruct1C->szFoundPath, pStruct1C->cchFoundPath); - pSearch->szFileName[pStruct1C->cchFoundPath] = 0; - if (CheckWildCard(pSearch->szFileName, pSearch->szMask)) - break; + // Passed + return pbRootPtr + sizeof(FILE_MNDX_HEADER); } - // Give the file size and encoding key - return FillFindData(pRootHandler, pSearch, pStruct1C, PtrFileSize); -} - -static void MndxHandler_EndSearch(TRootHandler_MNDX * /* pRootHandler */, TCascSearch * pSearch) -{ - if(pSearch != NULL) - delete (TMndxFindResult *)pSearch->pRootContext; - pSearch->pRootContext = NULL; -} - -static DWORD MndxHandler_GetFileId(TRootHandler_MNDX * /* pRootHandler */, const char * /* szFileName */) -{ - // Not implemented for HOTS - return 0; -} - -static LPBYTE MndxHandler_GetKey(TRootHandler_MNDX * pRootHandler, const char * szFileName) -{ - PCASC_ROOT_ENTRY_MNDX pRootEntry = NULL; - PCASC_MNDX_PACKAGE pPackage; - char * szStrippedName; - char szNormName[MAX_PATH+1]; - int nError; - - // Convert the file name to lowercase + slashes - NormalizeFileName_LowerSlash(szNormName, szFileName, MAX_PATH); - - // Find the package number - pPackage = FindMndxPackage(pRootHandler, szNormName); - if(pPackage == NULL) - return NULL; - - // Cut the package name off the full path - szStrippedName = szNormName + pPackage->nLength; - while(szStrippedName[0] == '/') - szStrippedName++; + int LoadPackageNames() + { + TMndxMarFile * pMarFile = MndxInfo.MarFiles[MAR_PACKAGE_NAMES]; + TMndxSearch Search; + PMNDX_PACKAGE pPackage; + size_t nPackageCount = 0x40; + bool bFindResult = false; + int nError; - // Find the root entry - nError = SearchMndxInfo(pRootHandler, szStrippedName, (DWORD)(pPackage - pRootHandler->pPackages->Packages), &pRootEntry); - if(nError != ERROR_SUCCESS || pRootEntry == NULL) - return NULL; + // Prepare the file name search in the top level directory + Search.SetSearchMask("", 0); - // Return the encoding key - return pRootEntry->EncodingKey; -} + // Allocate initial name list structure + pMarFile->GetFileNameCount(&nPackageCount); + nError = Packages.Create<MNDX_PACKAGE>(nPackageCount); + if(nError != ERROR_SUCCESS) + return nError; -static void MndxHandler_Close(TRootHandler_MNDX * pRootHandler) -{ - if(pRootHandler->MndxInfo.pMarFile1 != NULL) - MAR_FILE_Destructor(pRootHandler->MndxInfo.pMarFile1); - if(pRootHandler->MndxInfo.pMarFile2 != NULL) - MAR_FILE_Destructor(pRootHandler->MndxInfo.pMarFile2); - if(pRootHandler->MndxInfo.pMarFile3 != NULL) - MAR_FILE_Destructor(pRootHandler->MndxInfo.pMarFile3); - if(pRootHandler->ppValidEntries != NULL) - CASC_FREE(pRootHandler->ppValidEntries); - if(pRootHandler->pMndxEntries != NULL) - CASC_FREE(pRootHandler->pMndxEntries); - if(pRootHandler->pPackages != NULL) - CASC_FREE(pRootHandler->pPackages); - - CASC_FREE(pRootHandler); -} + // Reset the package array + Packages.Reset(); -//----------------------------------------------------------------------------- -// Public functions - MNDX info + // Keep searching as long as we find something + while(pMarFile->DoSearch(&Search, &bFindResult) == ERROR_SUCCESS && bFindResult) + { + // Insert new package to the array + assert(Search.nIndex < nPackageCount); + pPackage = (PMNDX_PACKAGE)Packages.InsertAt(Search.nIndex); + if (pPackage != NULL) + { + // The package mut not be initialized yet + assert(pPackage->szFileName == NULL); + + // Allocate space for the file name + pPackage->szFileName = CASC_ALLOC(char, Search.cchFoundPath + 1); + if (pPackage->szFileName == NULL) + return ERROR_NOT_ENOUGH_MEMORY; + + // Fill the package structure + memcpy(pPackage->szFileName, Search.szFoundPath, Search.cchFoundPath); + pPackage->szFileName[Search.cchFoundPath] = 0; + pPackage->nLength = Search.cchFoundPath; + pPackage->nIndex = Search.nIndex; + } + } -int RootHandler_CreateMNDX(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) -{ - PFILE_MNDX_HEADER pMndxHeader = (PFILE_MNDX_HEADER)pbRootFile; - PCASC_MNDX_INFO pMndxInfo; - TRootHandler_MNDX * pRootHandler; - FILE_MAR_INFO MarInfo; - PMAR_FILE pMarFile; - LPBYTE pbRootFileEnd = pbRootFile + cbRootFile; - DWORD cbToAllocate; - DWORD dwFilePointer = 0; - DWORD i; - int nError = ERROR_SUCCESS; - - // Check signature and the other variables - if(pMndxHeader->Signature != CASC_MNDX_SIGNATURE || pMndxHeader->FormatVersion > 2 || pMndxHeader->FormatVersion < 1) - return ERROR_BAD_FORMAT; - - // Allocate the structure for the MNDX root file - hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_MNDX, 1); - if(pRootHandler == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Fill-in the handler functions - memset(pRootHandler, 0, sizeof(TRootHandler_MNDX)); - pRootHandler->Insert = (ROOT_INSERT)MndxHandler_Insert; - pRootHandler->Search = (ROOT_SEARCH)MndxHandler_Search; - pRootHandler->EndSearch = (ROOT_ENDSEARCH)MndxHandler_EndSearch; - pRootHandler->GetKey = (ROOT_GETKEY)MndxHandler_GetKey; - pRootHandler->Close = (ROOT_CLOSE)MndxHandler_Close; - pRootHandler->GetFileId = (ROOT_GETFILEID)MndxHandler_GetFileId; - - pMndxInfo = &pRootHandler->MndxInfo; - - // Fill-in the flags - pRootHandler->dwRootFlags |= ROOT_FLAG_HAS_NAMES; - - // Copy the header into the MNDX info - pMndxInfo->HeaderVersion = pMndxHeader->HeaderVersion; - pMndxInfo->FormatVersion = pMndxHeader->FormatVersion; - dwFilePointer += sizeof(FILE_MNDX_HEADER); - - // Header version 2 has 2 extra fields that we need to load - if(pMndxInfo->HeaderVersion == 2) - { - if(!RootFileRead(pbRootFile + dwFilePointer, pbRootFileEnd, &pMndxInfo->field_1C, sizeof(DWORD) + sizeof(DWORD))) - return ERROR_FILE_CORRUPT; - dwFilePointer += sizeof(DWORD) + sizeof(DWORD); + // Give the packages to the caller + return ERROR_SUCCESS; } - // Load the rest of the file header - if(!RootFileRead(pbRootFile + dwFilePointer, pbRootFileEnd, &pMndxInfo->MarInfoOffset, 0x1C)) - return ERROR_FILE_CORRUPT; - - // Verify the structure - if(pMndxInfo->MarInfoCount > CASC_MAX_MAR_FILES || pMndxInfo->MarInfoSize != sizeof(FILE_MAR_INFO)) - return ERROR_FILE_CORRUPT; - - // Load all MAR infos - for(i = 0; i < pMndxInfo->MarInfoCount; i++) + int Load(const FILE_MNDX_HEADER & MndxHeader, LPBYTE pbRootFile, LPBYTE pbRootEnd) { - // Load the n-th MAR info - dwFilePointer = pMndxInfo->MarInfoOffset + (pMndxInfo->MarInfoSize * i); - if(!RootFileRead(pbRootFile + dwFilePointer, pbRootFileEnd, &MarInfo, sizeof(FILE_MAR_INFO))) - return ERROR_FILE_CORRUPT; + TMndxMarFile * pMarFile; + FILE_MAR_INFO MarInfo; + size_t nFilePointer = 0; + DWORD i; + int nError = ERROR_SUCCESS; - // Allocate MAR_FILE structure - pMarFile = CASC_ALLOC(MAR_FILE, 1); - if(pMarFile == NULL) - { - nError = ERROR_NOT_ENOUGH_MEMORY; - break; - } + // Copy the header into the MNDX info + MndxInfo.HeaderVersion = MndxHeader.HeaderVersion; + MndxInfo.FormatVersion = MndxHeader.FormatVersion; + nFilePointer += sizeof(FILE_MNDX_HEADER); - // Allocate space for the MAR data - pMarFile->pDatabasePtr = NULL; - pMarFile->pbMarData = CASC_ALLOC(BYTE, MarInfo.MarDataSize); - pMarFile->cbMarData = MarInfo.MarDataSize; - if(pMarFile->pbMarData == NULL) + // Header version 2 has 2 extra fields that we need to load + if(MndxInfo.HeaderVersion == 2) { - nError = ERROR_NOT_ENOUGH_MEMORY; - break; + if(!CaptureData(pbRootFile + nFilePointer, pbRootEnd, &MndxInfo.field_1C, sizeof(DWORD) + sizeof(DWORD))) + return ERROR_FILE_CORRUPT; + nFilePointer += sizeof(DWORD) + sizeof(DWORD); } - // Read the MAR data - if(!RootFileRead(pbRootFile + MarInfo.MarDataOffset, pbRootFileEnd, pMarFile->pbMarData, pMarFile->cbMarData)) + // Load the rest of the file header + if(!CaptureData(pbRootFile + nFilePointer, pbRootEnd, &MndxInfo.MarInfoOffset, 0x1C)) + return ERROR_FILE_CORRUPT; + + // Verify the structure + if(MndxInfo.MarInfoCount > MAR_COUNT || MndxInfo.MarInfoSize != sizeof(FILE_MAR_INFO)) + return ERROR_FILE_CORRUPT; + + // Load all MAR infos + for(i = 0; i < MndxInfo.MarInfoCount; i++) { - nError = ERROR_FILE_CORRUPT; - break; - } + // Capture the n-th MAR info + nFilePointer = MndxInfo.MarInfoOffset + (MndxInfo.MarInfoSize * i); + if(!CaptureData(pbRootFile + nFilePointer, pbRootEnd, &MarInfo, sizeof(FILE_MAR_INFO))) + return ERROR_FILE_CORRUPT; - // HOTS: 00E94FF1 - MAR_FILE_CreateDatabase(pMarFile); - if(i == 0) - pMndxInfo->pMarFile1 = pMarFile; - if(i == 1) - pMndxInfo->pMarFile2 = pMarFile; - if(i == 2) - pMndxInfo->pMarFile3 = pMarFile; - } + // Allocate MAR_FILE structure + pMarFile = new TMndxMarFile(); + if(pMarFile == NULL) + { + nError = ERROR_NOT_ENOUGH_MEMORY; + break; + } - // All three MAR files must be loaded - // HOTS: 00E9503B - if(nError == ERROR_SUCCESS) - { - if(pMndxInfo->pMarFile1 == NULL || pMndxInfo->pMarFile2 == NULL || pMndxInfo->pMarFile3 == NULL) - nError = ERROR_BAD_FORMAT; - if(pMndxInfo->MndxEntrySize != sizeof(CASC_ROOT_ENTRY_MNDX)) - nError = ERROR_BAD_FORMAT; - } + // Create the database from the MAR data + nError = pMarFile->LoadRootData(MarInfo, pbRootFile, pbRootEnd); + if(nError != ERROR_SUCCESS) + break; - // Load the complete array of MNDX entries - if(nError == ERROR_SUCCESS) - { - TFileNameDatabasePtr * pDbPtr = pMndxInfo->pMarFile2->pDatabasePtr; - DWORD FileNameCount; + // Assign the MAR file to the MNDX info structure + MndxInfo.MarFiles[i] = pMarFile; + } - nError = pDbPtr->GetFileNameCount(&FileNameCount); - if(nError == ERROR_SUCCESS && FileNameCount == pMndxInfo->MndxEntriesValid) + // All three MAR files must be loaded + // HOTS: 00E9503B + if(nError == ERROR_SUCCESS) { - cbToAllocate = pMndxInfo->MndxEntriesTotal * pMndxInfo->MndxEntrySize; - pRootHandler->pMndxEntries = (PCASC_ROOT_ENTRY_MNDX)CASC_ALLOC(BYTE, cbToAllocate); - if(pRootHandler->pMndxEntries != NULL) - { - if(!RootFileRead(pbRootFile + pMndxInfo->MndxEntriesOffset, pbRootFileEnd, pRootHandler->pMndxEntries, cbToAllocate)) - nError = ERROR_FILE_CORRUPT; - } - else - nError = ERROR_NOT_ENOUGH_MEMORY; + if(MndxInfo.MarFiles[MAR_PACKAGE_NAMES] == NULL || MndxInfo.MarFiles[MAR_STRIPPED_NAMES] == NULL || MndxInfo.MarFiles[MAR_FULL_NAMES] == NULL) + nError = ERROR_BAD_FORMAT; + if(MndxInfo.CKeyEntrySize != sizeof(MNDX_CKEY_ENTRY)) + nError = ERROR_BAD_FORMAT; } - else - nError = ERROR_FILE_CORRUPT; - } - // Pick the valid MNDX entries and put them to a separate array - if(nError == ERROR_SUCCESS) - { - assert(pMndxInfo->MndxEntriesValid <= pMndxInfo->MndxEntriesTotal); - pRootHandler->ppValidEntries = CASC_ALLOC(PCASC_ROOT_ENTRY_MNDX, pMndxInfo->MndxEntriesValid + 1); - if(pRootHandler->ppValidEntries != NULL) + // 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) { - PCASC_ROOT_ENTRY_MNDX pRootEntry = pRootHandler->pMndxEntries; - DWORD ValidEntryCount = 1; // edx - DWORD nIndex1 = 0; + size_t CKeyEntriesSize; + size_t FileNameCount = 0; - // The first entry is always valid - pRootHandler->ppValidEntries[nIndex1++] = pRootHandler->pMndxEntries; + pMarFile = MndxInfo.MarFiles[MAR_STRIPPED_NAMES]; + nError = ERROR_FILE_CORRUPT; - // Put the remaining entries - for(i = 0; i < pMndxInfo->MndxEntriesTotal; i++, pRootEntry++) + // Capture the array of CKey entries + if(pMarFile->GetFileNameCount(&FileNameCount) == ERROR_SUCCESS && FileNameCount == MndxInfo.FileNameCount) { - if(ValidEntryCount > pMndxInfo->MndxEntriesValid) - break; - - if(pRootEntry->Flags & 0x80000000) + CKeyEntriesSize = MndxInfo.CKeyEntriesCount * MndxInfo.CKeyEntrySize; + if ((pbRootFile + MndxInfo.CKeyEntriesOffset + CKeyEntriesSize) <= pbRootEnd) { - pRootHandler->ppValidEntries[nIndex1++] = pRootEntry + 1; - ValidEntryCount++; + pCKeyEntries = (PMNDX_CKEY_ENTRY)(pbRootFile + MndxInfo.CKeyEntriesOffset); + nError = ERROR_SUCCESS; } } - - // Verify the final number of valid entries - if((ValidEntryCount - 1) != pMndxInfo->MndxEntriesValid) - nError = ERROR_BAD_FORMAT; } - else - nError = ERROR_NOT_ENOUGH_MEMORY; - } - // Load the MNDX packages - if(nError == ERROR_SUCCESS) - { - nError = LoadPackageNames(pMndxInfo, &pRootHandler->pPackages); - pMndxInfo->bRootFileLoaded = (nError == ERROR_SUCCESS); - } - -#if defined(_DEBUG) && defined(_X86_) && defined(CASCLIB_TEST) -// CascDumpNameFragTable("E:\\casc-name-fragment-table.txt", pMndxInfo->pMarFile1); -// CascDumpFileNames("E:\\casc-listfile.txt", pMndxInfo->pMarFile1); -// TestMndxRootFile(pRootHandler); -#endif + // Pick the CKey entries that are the first with a given name + if(nError == ERROR_SUCCESS) + { + assert(MndxInfo.FileNameCount <= MndxInfo.CKeyEntriesCount); + FileNameIndexToCKeyIndex = CASC_ALLOC(PMNDX_CKEY_ENTRY, MndxInfo.FileNameCount + 1); + if(FileNameIndexToCKeyIndex != NULL) + { + PMNDX_CKEY_ENTRY pRootEntry = pCKeyEntries; + DWORD nFileNameIndex = 0; - // Return the result - return nError; -} + // The first entry is always beginning of a file name group + FileNameIndexToCKeyIndex[nFileNameIndex++] = pRootEntry; -//---------------------------------------------------------------------------- -// Unit tests + // Get the remaining file name groups + for(i = 0; i < MndxInfo.CKeyEntriesCount; i++, pRootEntry++) + { + if (nFileNameIndex > MndxInfo.FileNameCount) + break; -#if defined(_DEBUG) && defined(_X86_) && defined(CASCLIB_TEST) -/* -extern "C" { - bool _cdecl sub_1958B00_x86(TFileNameDatabase * pDB, TMndxFindResult * pStruct1C); - DWORD _cdecl sub_19573D0_x86(TFileNameDatabase * pDB, DWORD arg_0, DWORD arg_4); - DWORD _cdecl sub_1957EF0_x86(TFileNameDatabase * pDB, TMndxFindResult * pStruct1C); - bool _cdecl sub_1959460_x86(TFileNameDatabase * pDB, TMndxFindResult * pStruct1C); - DWORD _cdecl GetItemValue_x86(TSparseArray * pStruct, DWORD dwKey); - DWORD _cdecl sub_1959CB0_x86(TFileNameDatabase * pDB, DWORD dwKey); - DWORD _cdecl sub_1959F50_x86(TFileNameDatabase * pDB, DWORD arg_0); -} + if (pRootEntry->Flags & MNDX_LAST_CKEY_ENTRY) + { + FileNameIndexToCKeyIndex[nFileNameIndex++] = pRootEntry + 1; + } + } -extern "C" void * allocate_zeroed_memory_x86(size_t bytes) -{ - void * ptr = CASC_ALLOC(BYTE, bytes); + // Verify the final number of file names + if ((nFileNameIndex - 1) != MndxInfo.FileNameCount) + nError = ERROR_BAD_FORMAT; + } + else + nError = ERROR_NOT_ENOUGH_MEMORY; + } - if(ptr != NULL) - memset(ptr, 0, bytes); - return ptr; -} + // Load the package names from the 0-th MAR file + if(nError == ERROR_SUCCESS) + { + nError = LoadPackageNames(); + } -extern "C" void free_memory_x86(void * ptr) -{ - if(ptr != NULL) - { - CASC_FREE(ptr); + return nError; } -} - -static int sub_1956CE0_x86(TFileNameDatabasePtr * pDatabasePtr, TMndxFindResult * pStruct1C, bool * pbFindResult) -{ - int nError = ERROR_SUCCESS; - - if(pDatabasePtr->pDB == NULL) - return ERROR_INVALID_PARAMETER; - // Create the pStruct40, if not initialized yet - if(pStruct1C->pStruct40 == NULL) + int LoadFileNames(TCascStorage * hs, CASC_FILE_TREE & FileTree) { - nError = pStruct1C->CreateStruct40(); - if(nError != ERROR_SUCCESS) - return nError; - } - - *pbFindResult = sub_1959460_x86(pDatabasePtr->pDB, pStruct1C); - return nError; -} + PCASC_CKEY_ENTRY pCKeyEntry; + PMNDX_CKEY_ENTRY pRootEntry; + PMNDX_CKEY_ENTRY pRootEnd = pCKeyEntries + MndxInfo.CKeyEntriesCount; + PMNDX_PACKAGE pPackage; + TMndxMarFile * pMarFile = MndxInfo.MarFiles[MAR_STRIPPED_NAMES]; + TMndxSearch Search; + char szFileName[MAX_PATH]; + bool bFindResult = false; + int nError; -static void TestFileSearch_SubStrings(PMAR_FILE pMarFile, char * szFileName, size_t nLength) -{ - TMndxFindResult Struct1C_1; - TMndxFindResult Struct1C_2; + // Setup the search mask + Search.SetSearchMask("", 0); -// if(strcmp(szFileName, "mods/heroes.stormmod/base.stormassets/assets/textures/storm_temp_war3_btnstatup.dds")) -// return; + // Keep searching ad long as we found something + while ((nError = pMarFile->DoSearch(&Search, &bFindResult)) == ERROR_SUCCESS && bFindResult) + { + // Sanity check + assert(Search.cchFoundPath < MAX_PATH); - // Perform search on anything, that is longer than 4 chars - while(nLength >= 4) - { - // Set a substring as search name - Struct1C_1.SetSearchPath(szFileName, nLength); - Struct1C_2.SetSearchPath(szFileName, nLength); - szFileName[nLength] = 0; + // The found file name index must fall into range of file names + if (Search.nIndex < MndxInfo.FileNameCount) + { + // Retrieve the first-in-group CKey entry of that name + pRootEntry = FileNameIndexToCKeyIndex[Search.nIndex]; - // Keep searching - for(;;) - { - bool bFindResult1 = false; - bool bFindResult2 = false; + // Now take all files of that name, prepend their package name and insert to file tree + while(pRootEntry < pRootEnd) + { + // Find the appropriate CKey entry in the central storage + pCKeyEntry = FindCKeyEntry_CKey(hs, pRootEntry->CKey); + if (pCKeyEntry != NULL) + { + size_t nPackageIndex = pRootEntry->Flags & 0x00FFFFFF; - // Search the next file name (orig HOTS code) - sub_1956CE0_x86(pMarFile->pDatabasePtr, &Struct1C_1, &bFindResult1); + // Retrieve the package for this entry + pPackage = (PMNDX_PACKAGE)Packages.ItemAt(nPackageIndex); + if (pPackage != NULL) + { + // Sanity check + assert(pPackage->nIndex == nPackageIndex); - // Search the next file name (our code) - pMarFile->pDatabasePtr->sub_1956CE0(&Struct1C_2, &bFindResult2); + // Merge the package name and file name + MakeFileName(szFileName, _countof(szFileName), pPackage, &Search); - // Check the result - assert(bFindResult1 == bFindResult2); - assert(Struct1C_1.cchFoundPath == Struct1C_1.cchFoundPath); - assert(Struct1C_1.FileNameIndex == Struct1C_2.FileNameIndex); - assert(strncmp(Struct1C_1.szFoundPath, Struct1C_2.szFoundPath, Struct1C_1.cchFoundPath) == 0); - assert(Struct1C_1.cchFoundPath < MAX_PATH); + // Insert the entry to the file tree + FileTree.InsertByName(pCKeyEntry, szFileName); + } + } - // Stop the search in case of failure - if(bFindResult1 == false || bFindResult2 == false) - break; + // Is this the last-in-group entry? + if (pRootEntry->Flags & MNDX_LAST_CKEY_ENTRY) + break; + pRootEntry++; + } + } } - // Free the search structures - Struct1C_1.FreeStruct40(); - Struct1C_2.FreeStruct40(); - nLength--; + return nError; } -} - -static void TestFindPackage(PMAR_FILE pMarFile, const char * szPackageName) -{ - TMndxFindResult Struct1C; - - // Search the database for the file name - Struct1C.SetSearchPath(szPackageName, strlen(szPackageName)); - - // Search the file name in the second MAR info (the one with stripped package names) - MAR_FILE_SearchFile(pMarFile, &Struct1C); -} -static void TestFileSearch(PMAR_FILE pMarFile, const char * szFileName) -{ - TMndxFindResult Struct1C_1; - TMndxFindResult Struct1C_2; - size_t nLength = strlen(szFileName); - char szNameBuff[MAX_PATH + 1]; - - // Set an empty path as search mask (?) - Struct1C_1.SetSearchPath(szFileName, nLength); - Struct1C_2.SetSearchPath(szFileName, nLength); + // + // Helper functions + // - // Keep searching - for(;;) + void MakeFileName(char * szBuffer, size_t cchBuffer, PMNDX_PACKAGE pPackage, TMndxSearch * pSearch) { - bool bFindResult1 = false; - bool bFindResult2 = false; - - // Search the next file name (orig HOTS code) - sub_1956CE0_x86(pMarFile->pDatabasePtr, &Struct1C_1, &bFindResult1); + char * szBufferEnd = szBuffer + cchBuffer - 1; - // Search the next file name (our code) - pMarFile->pDatabasePtr->sub_1956CE0(&Struct1C_2, &bFindResult2); - - assert(bFindResult1 == bFindResult2); - assert(Struct1C_1.cchFoundPath == Struct1C_1.cchFoundPath); - assert(Struct1C_1.FileNameIndex == Struct1C_2.FileNameIndex); - assert(strncmp(Struct1C_1.szFoundPath, Struct1C_2.szFoundPath, Struct1C_1.cchFoundPath) == 0); - assert(Struct1C_1.cchFoundPath < MAX_PATH); - - // Stop the search in case of failure - if(bFindResult1 == false || bFindResult2 == false) - break; - - // Printf the found file name - memcpy(szNameBuff, Struct1C_2.szFoundPath, Struct1C_2.cchFoundPath); - szNameBuff[Struct1C_2.cchFoundPath] = 0; -// printf("%s \r", szNameBuff); - - // Perform sub-searches on this string and its substrings that are longer than 4 chars -// TestFileSearch_SubStrings(pMarFile, szNameBuff, Struct1C_2.cchFoundPath); - } + // Buffer length check + assert((pPackage->nLength + 1 + pSearch->cchFoundPath + 1) < cchBuffer); - // Free the search structures - Struct1C_1.FreeStruct40(); - Struct1C_2.FreeStruct40(); -} - -static void TestMarFile(PMAR_FILE pMarFile, const char * szFileName, size_t nLength) -{ - TFileNameDatabase * pDB = pMarFile->pDatabasePtr->pDB; - DWORD dwFileNameIndex1 = 0xFFFFFFFF; - DWORD dwFileNameIndex2 = 0xFFFFFFFF; + // Copy the package name + if ((szBuffer + pPackage->nLength) < szBufferEnd) + { + memcpy(szBuffer, pPackage->szFileName, pPackage->nLength); + szBuffer += pPackage->nLength; + } - // Perform the search using original HOTS code - { - TMndxFindResult Struct1C; + // Append slash + if ((szBuffer + 1) < szBufferEnd) + *szBuffer++ = '/'; - Struct1C.CreateStruct40(); - Struct1C.SetSearchPath(szFileName, nLength); + // Append file name + if ((szBuffer + pSearch->cchFoundPath) < szBufferEnd) + { + memcpy(szBuffer, pSearch->szFoundPath, pSearch->cchFoundPath); + szBuffer += pSearch->cchFoundPath; + } - // Call the original HOTS function - sub_1957EF0_x86(pDB, &Struct1C); - dwFileNameIndex1 = Struct1C.FileNameIndex; + szBuffer[0] = 0; } - // Perform the search using our code - { - TMndxFindResult Struct1C; + protected: - Struct1C.CreateStruct40(); - Struct1C.SetSearchPath(szFileName, nLength); + FILE_MNDX_INFO MndxInfo; - // Call our function - pDB->FindFileInDatabase(&Struct1C); - dwFileNameIndex2 = Struct1C.FileNameIndex; - } + PMNDX_CKEY_ENTRY * FileNameIndexToCKeyIndex; + PMNDX_CKEY_ENTRY pCKeyEntries; + CASC_ARRAY Packages; // Linear list of present packages +}; - // Compare both - assert(dwFileNameIndex1 == dwFileNameIndex2); -} +//----------------------------------------------------------------------------- +// Handler definition for MNDX root file -static void TestMndxFunctions(PMAR_FILE pMarFile) +struct TRootHandler_MNDX : public TFileTreeRoot { - TFileNameDatabase * pDB = pMarFile->pDatabasePtr->pDB; + public: - // Exercise function sub_19573D0 - for(DWORD arg_0 = 0; arg_0 < 0x100; arg_0++) + TRootHandler_MNDX() : TFileTreeRoot(0) { - for(DWORD arg_4 = 0; arg_4 < 0x100; arg_4++) - { - DWORD dwResult1 = sub_19573D0_x86(pDB, arg_0, arg_4); - DWORD dwResult2 = pDB->GetNameFragmentOffsetEx(arg_0, arg_4); - - assert(dwResult1 == dwResult2); - } + // MNDX supports file names and CKeys + dwFeatures |= CASC_FEATURE_FILE_NAMES | CASC_FEATURE_ROOT_CKEY; } - // Exercise function GetItemValue - for(DWORD i = 0; i < 0x10000; i++) + int Load(TCascStorage * hs, const FILE_MNDX_HEADER & MndxHeader, LPBYTE pbRootFile, LPBYTE pbRootEnd) { - DWORD dwResult1 = GetItemValue_x86(&pDB->Struct68_D0, i); - DWORD dwResult2 = pDB->Struct68_D0.GetItemValue(i); + TMndxHandler Handler; + int nError; - assert(dwResult1 == dwResult2); - } - - // Exercise function sub_1959CB0 - for(DWORD i = 0; i < 0x9C; i++) - { - DWORD dwResult1 = sub_1959CB0_x86(pDB, i); - DWORD dwResult2 = pDB->sub_1959CB0(i); + // Load and parse the entire MNDX structure + nError = Handler.Load(MndxHeader, pbRootFile, pbRootEnd); + if (nError == ERROR_SUCCESS) + { + // Search all file names and insert them into the file tree + nError = Handler.LoadFileNames(hs, FileTree); + } - assert(dwResult1 == dwResult2); + return nError; } +}; - // Exercise function sub_1959F50 - for(DWORD i = 0; i < 0x40; i++) - { - DWORD dwResult1 = sub_1959F50_x86(pDB, i); - DWORD dwResult2 = pDB->sub_1959F50(i); - - assert(dwResult1 == dwResult2); - } -} +//----------------------------------------------------------------------------- +// Public functions - MNDX info -void TestMndxRootFile(PCASC_MNDX_INFO pMndxInfo) +int RootHandler_CreateMNDX(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) { - size_t nLength; - char szFileName[MAX_PATH+1]; - void * pvListFile; - - // Exercise low level functions and compare their results - // with original code from Heroes of the Storm - TestMndxFunctions(pMndxInfo->pMarFile1); - TestMndxFunctions(pMndxInfo->pMarFile2); - TestMndxFunctions(pMndxInfo->pMarFile3); - - // Find a "mods" in the package array - TestFindPackage(pMndxInfo->pMarFile3, "mods/heroes.stormmod/base.stormassets/assets/textures/glow_green2.dds"); - TestMarFile(pMndxInfo->pMarFile3, "mods/heroes.stormmod/base.stormassets/assets/textures/glow_green2.dds", 69); + TRootHandler_MNDX * pRootHandler = NULL; + FILE_MNDX_HEADER MndxHeader; + LPBYTE pbRootEnd = pbRootFile + cbRootFile; + int nError = ERROR_BAD_FORMAT; - // Search the package MAR file aith a path shorter than a fragment - TestFileSearch(pMndxInfo->pMarFile1, "mods/heroes.s"); - - // Test the file search - TestFileSearch(pMndxInfo->pMarFile1, ""); - TestFileSearch(pMndxInfo->pMarFile2, ""); - TestFileSearch(pMndxInfo->pMarFile3, ""); - - // False file search - TestFileSearch(pMndxInfo->pMarFile2, "assets/textures/storm_temp_hrhu"); - - // Open the listfile stream and initialize the listfile cache - pvListFile = ListFile_OpenExternal(_T("e:\\Ladik\\Appdir\\CascLib\\listfile\\listfile-hots-29049.txt")); - if(pvListFile != NULL) + // Verify the header of the ROOT file + if(TMndxHandler::CaptureRootHeader(MndxHeader, pbRootFile, pbRootEnd) != NULL) { - // Check every file in the database - while((nLength = ListFile_GetNext(pvListFile, "*", szFileName, MAX_PATH)) != 0) + // Allocate the root handler object + pRootHandler = new TRootHandler_MNDX(); + if(pRootHandler != NULL) { - // Normalize the file name: ToLower + BackSlashToSlash - NormalizeFileName_LowerSlash(szFileName, szFileName, MAX_PATH); - - // Check the file with all three MAR files - TestMarFile(pMndxInfo->pMarFile1, szFileName, nLength); - TestMarFile(pMndxInfo->pMarFile2, szFileName, nLength); - TestMarFile(pMndxInfo->pMarFile3, szFileName, nLength); + // Load the root directory. If load failed, we free the object + nError = pRootHandler->Load(hs, MndxHeader, pbRootFile, pbRootEnd); + if(nError != ERROR_SUCCESS) + { + delete pRootHandler; + pRootHandler = NULL; + } } - - ListFile_Free(pvListFile); } + + // Assign the root directory (or NULL) and return error + hs->pRootHandler = pRootHandler; + return nError; } -*/ -#endif // defined(_DEBUG) && defined(_X86_) && defined(CASCLIB_TEST) diff --git a/dep/CascLib/src/CascRootFile_OW.cpp b/dep/CascLib/src/CascRootFile_OW.cpp new file mode 100644 index 00000000000..24f5af81b34 --- /dev/null +++ b/dep/CascLib/src/CascRootFile_OW.cpp @@ -0,0 +1,605 @@ +/*****************************************************************************/ +/* CascRootFile_Text.cpp Copyright (c) Ladislav Zezula 2017 */ +/*---------------------------------------------------------------------------*/ +/* Support for loading ROOT files in plain text */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 28.10.15 1.00 Lad The first version of CascRootFile_Text.cpp */ +/*****************************************************************************/ + +#define __CASCLIB_SELF__ +#include "CascLib.h" +#include "CascCommon.h" + +//----------------------------------------------------------------------------- +// Structure definitions for CMF files + +#define MAX_LINE_ELEMENTS 8 + +typedef struct _CMF_HEADER_V3 +{ + DWORD BuildVersion; + DWORD Unknown0; + DWORD Unknown1; + DWORD Unknown2; + DWORD Unknown3; + DWORD DataCount; + DWORD Unknown4; + DWORD EntryCount; + DWORD Magic; +} CMF_HEADER_V3, *PCMF_HEADER_V3; + +typedef struct _CMF_HEADER_V2 +{ + DWORD BuildVersion; + DWORD Unknown0; + DWORD Unknown1; + DWORD Unknown2; + DWORD DataCount; + DWORD Unknown3; + DWORD EntryCount; + DWORD Magic; +} CMF_HEADER_V2, *PCMF_HEADER_V2; + +typedef struct _CMF_HEADER_V1 +{ + DWORD BuildVersion; + DWORD Unknown0; + DWORD DataCount; + DWORD Unknown1; + DWORD EntryCount; + DWORD Magic; +} CMF_HEADER_V1, *PCMF_HEADER_V1; + +//----------------------------------------------------------------------------- +// Structure definitions for APM files + +// In-memory format +typedef struct _APM_ENTRY +{ + DWORD Index; + ULONGLONG HashA; + ULONGLONG HashB; +} APM_ENTRY, *PAPM_ENTRY; + +// On-disk format, size = 0x14 +typedef struct _APM_ENTRY_V2 +{ + DWORD Index; + DWORD HashA_Lo; // Must split the hashes in order to make this structure properly aligned + DWORD HashA_Hi; + DWORD HashB_Lo; + DWORD HashB_Hi; +} APM_ENTRY_V2, *PAPM_ENTRY_V2; + +// On-disk format, size = 0x0C +typedef struct _APM_ENTRY_V1 +{ + DWORD Index; + DWORD HashA_Lo; // Must split the hashes in order to make this structure properly aligned + DWORD HashA_Hi; +} APM_ENTRY_V1, *PAPM_ENTRY_V1; + +// In-memory format +typedef struct _APM_PACKAGE_ENTRY +{ + ULONGLONG PackageGUID; // 077 file + ULONGLONG Unknown1; + DWORD Unknown2; + DWORD Unknown3; + ULONGLONG Unknown4; +} APM_PACKAGE_ENTRY, *PAPM_PACKAGE_ENTRY; + +// On-disk format +typedef struct _APM_PACKAGE_ENTRY_V2 +{ + ULONGLONG PackageGUID; // 077 file + ULONGLONG Unknown1; + DWORD Unknown2; + DWORD Unknown3; + ULONGLONG Unknown4; +} APM_PACKAGE_ENTRY_V2, *PAPM_PACKAGE_ENTRY_V2; + +// On-disk format +typedef struct _APM_PACKAGE_ENTRY_V1 +{ + ULONGLONG EntryPointGUID; // virtual most likely + ULONGLONG PrimaryGUID; // real + ULONGLONG SecondaryGUID; // real + ULONGLONG Key; // encryption + ULONGLONG PackageGUID; // 077 file + ULONGLONG Unknown1; + DWORD Unknown2; +} APM_PACKAGE_ENTRY_V1, *PAPM_PACKAGE_ENTRY_V1; + +typedef struct _APM_HEADER_V3 +{ + ULONGLONG BuildNumber; // Build number of the game + ULONGLONG ZeroValue1; + DWORD ZeroValue2; + DWORD PackageCount; + DWORD ZeroValue3; + DWORD EntryCount; + DWORD Checksum; + + // Followed by the array of APM_ENTRY (count is in "EntryCount") + // Followed by the array of APM_PACKAGE (count is in "PackageCount") + +} APM_HEADER_V3, *PAPM_HEADER_V3; + +typedef struct _APM_HEADER_V2 +{ + ULONGLONG BuildNumber; // Build number of the game + ULONGLONG ZeroValue1; + DWORD PackageCount; + DWORD ZeroValue2; + DWORD EntryCount; + DWORD Checksum; + + // Followed by the array of APM_ENTRY (count is in "EntryCount") + // Followed by the array of APM_PACKAGE (count is in "PackageCount") + +} APM_HEADER_V2, *PAPM_HEADER_V2; + +typedef struct _APM_HEADER_V1 +{ + ULONGLONG BuildNumber; // Build number of the game + DWORD BuildVersion; + DWORD PackageCount; + DWORD EntryCount; + DWORD Checksum; + + // Followed by the array of APM_ENTRY (count is in "EntryCount") + // Followed by the array of APM_PACKAGE (count is in "PackageCount") + +} APM_HEADER_V1, *PAPM_HEADER_V1; + +//----------------------------------------------------------------------------- +// Handler classes + +/* +struct TCmfFile +{ + TCmfFile() + { + memset(this, 0, sizeof(TCmfFile)); + } + + LPBYTE CaptureHeader(LPBYTE pbCmfData, LPBYTE pbCmfEnd) + { + DWORD BuildNumber = *(PDWORD)pbCmfData; + + // Check the newest header version + if(BuildNumber >= 45104 && BuildNumber != 45214) + { + PCMF_HEADER_V3 pHeader3 = (PCMF_HEADER_V3)pbCmfData; + + if ((LPBYTE)(pHeader3 + 1) > pbCmfEnd) + return NULL; + + BuildVersion = pHeader3->BuildVersion; + DataCount = pHeader3->DataCount; + EntryCount = pHeader3->EntryCount; + Magic = pHeader3->Magic; + return (LPBYTE)(pHeader3 + 1); + } + + else if(BuildNumber >= 39028) + { + // TODO + assert(false); + return NULL; + } + + else + { + // TODO + assert(false); + return NULL; + } + } + + DWORD BuildVersion; + DWORD DataCount; + DWORD EntryCount; + DWORD Magic; +}; + +struct TApmFile +{ + TApmFile() + { + memset(this, 0, sizeof(TApmFile)); + } + + ~TApmFile() + { + CASC_FREE(pApmPackages); + CASC_FREE(pApmEntries); + } + + LPBYTE CaptureHeader(LPBYTE pbApmData, LPBYTE pbApmEnd) + { + // Check the data size for the largest possible header size + if((pbApmData + sizeof(APM_HEADER_V3)) < pbApmEnd) + { + // Try the version 3 + PAPM_HEADER_V3 pApmFile3 = (PAPM_HEADER_V3)(pbApmData); + if(pApmFile3->ZeroValue1 == 0 && pApmFile3->ZeroValue2 == 0 && pApmFile3->PackageCount && pApmFile3->EntryCount && pApmFile3->Checksum) + { + BuildNumber = pApmFile3->BuildNumber; + PackageCount = pApmFile3->PackageCount; + EntryCount = pApmFile3->EntryCount; + Checksum = pApmFile3->Checksum; + return pbApmData + 0x24; + } + + // Try the version 2 + PAPM_HEADER_V2 pApmFile2 = (PAPM_HEADER_V2)(pbApmData); + if(pApmFile2->ZeroValue1 == 0 && pApmFile2->PackageCount && pApmFile2->EntryCount && pApmFile2->Checksum) + { + BuildNumber = pApmFile2->BuildNumber; + PackageCount = pApmFile2->PackageCount; + EntryCount = pApmFile2->EntryCount; + Checksum = pApmFile2->Checksum; + return pbApmData + 0x20; + } + + // Try the version 1 (build 24919) + PAPM_HEADER_V1 pApmHeader1 = (PAPM_HEADER_V1)(pbApmData); + if(pApmHeader1->BuildVersion != 0 && pApmHeader1->PackageCount && pApmHeader1->EntryCount && pApmHeader1->Checksum) + { + BuildNumber = pApmHeader1->BuildNumber; + PackageCount = pApmHeader1->PackageCount; + EntryCount = pApmHeader1->EntryCount; + Checksum = pApmHeader1->Checksum; + return pbApmData + 0x18; + } + } + + return NULL; + } + + LPBYTE CaptureArrayOfEntries(LPBYTE pbArrayOfEntries, LPBYTE pbApmEnd) + { + // Allocate array of entries + pApmEntries = CASC_ALLOC(APM_ENTRY, EntryCount); + if(pApmEntries != NULL) + { + // The newest format + if(BuildNumber > 45104 && BuildNumber != 45214) + { + PAPM_ENTRY_V2 pEntry2 = (PAPM_ENTRY_V2)pbArrayOfEntries; + LPBYTE pbEntriesEnd = (LPBYTE)(pEntry2 + EntryCount); + + if(pbEntriesEnd <= pbApmEnd) + { + for(DWORD i = 0; i < EntryCount; i++) + { + pApmEntries[i].Index = pEntry2->Index; + pApmEntries[i].HashA = MAKE_OFFSET64(pEntry2->HashA_Hi, pEntry2->HashA_Lo); + pApmEntries[i].HashB = MAKE_OFFSET64(pEntry2->HashB_Hi, pEntry2->HashB_Lo); + } + + return pbEntriesEnd; + } + } + else + { + PAPM_ENTRY_V1 pEntry1 = (PAPM_ENTRY_V1)pbArrayOfEntries; + LPBYTE pbEntriesEnd = (LPBYTE)(pEntry1 + EntryCount); + + if(pbEntriesEnd <= pbApmEnd) + { + for(DWORD i = 0; i < EntryCount; i++) + { + pApmEntries[i].Index = pEntry1->Index; + pApmEntries[i].HashA = MAKE_OFFSET64(pEntry1->HashA_Hi, pEntry1->HashA_Lo); + pApmEntries[i].HashB = 0; + } + + return pbEntriesEnd; + } + } + } + + return NULL; + } + + LPBYTE CapturePackageEntries(LPBYTE pbArrayOfEntries, LPBYTE pbApmEnd) + { + // Allocate array of entries + pApmPackages = CASC_ALLOC(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) + { + PAPM_PACKAGE_ENTRY_V2 pEntry2 = (PAPM_PACKAGE_ENTRY_V2)pbArrayOfEntries; + LPBYTE pbEntriesEnd = (LPBYTE)(pEntry2 + PackageCount); + + if(pbEntriesEnd <= pbApmEnd) + { + for(DWORD i = 0; i < PackageCount; i++) + { + pApmPackages[i].PackageGUID = pEntry2[i].PackageGUID; + pApmPackages[i].Unknown1 = pEntry2[i].Unknown1; + pApmPackages[i].Unknown2 = pEntry2[i].Unknown2; + pApmPackages[i].Unknown3 = pEntry2[i].Unknown3; + pApmPackages[i].Unknown4 = pEntry2[i].Unknown4; + } + + return pbEntriesEnd; + } + } + else + { + PAPM_PACKAGE_ENTRY_V1 pEntry1 = (PAPM_PACKAGE_ENTRY_V1)pbArrayOfEntries; + LPBYTE pbEntriesEnd = (LPBYTE)(pEntry1 + PackageCount); + + if(pbEntriesEnd <= pbApmEnd) + { + for(DWORD i = 0; i < PackageCount; i++) + { + // TODO!!! + pApmPackages[i].PackageGUID = pEntry1->PackageGUID; + } + + return pbEntriesEnd; + } + } + } + + return NULL; + } + + PAPM_ENTRY pApmEntries; + PAPM_PACKAGE_ENTRY pApmPackages; + ULONGLONG BuildNumber; + DWORD PackageCount; + DWORD EntryCount; + DWORD Checksum; + size_t HeaderSize; + + // Followed by the array of APM_ENTRY (count is in "EntryCount") + // Followed by the array of APM_PACKAGE (count is in "PackageCount") + +}; +*/ + +//----------------------------------------------------------------------------- +// Handler definition for OVERWATCH root file + +// +// ------------------------------------- +// Overwatch ROOT file (build 24919): +// ------------------------------------- +// #MD5|CHUNK_ID|FILENAME|INSTALLPATH +// FE3AD8A77EEF77B383DF4929AED816FD|0|RetailClient/GameClientApp.exe|GameClientApp.exe +// 5EDDEFECA544B6472C5CD52BE63BC02F|0|RetailClient/Overwatch Launcher.exe|Overwatch Launcher.exe +// 6DE09F0A67F33F874F2DD8E2AA3B7AAC|0|RetailClient/ca-bundle.crt|ca-bundle.crt +// 99FE9EB6A4BB20209202F8C7884859D9|0|RetailClient/ortp_x64.dll|ortp_x64.dll +// +// ------------------------------------- +// Overwatch ROOT file (build 47161): +// ------------------------------------- +// #FILEID|MD5|CHUNK_ID|PRIORITY|MPRIORITY|FILENAME|INSTALLPATH +// RetailClient/Overwatch.exe|807F96661280C07E762A8C129FEBDA6F|0|0|255|RetailClient/Overwatch.exe|Overwatch.exe +// RetailClient/Overwatch Launcher.exe|5EDDEFECA544B6472C5CD52BE63BC02F|0|0|255|RetailClient/Overwatch Launcher.exe|Overwatch Launcher.exe +// RetailClient/ortp_x64.dll|7D1B5DEC267480F3E8DAD6B95143A59C|0|0|255|RetailClient/ortp_x64.dll|ortp_x64.dll +// + +struct TRootHandler_OW : public TFileTreeRoot +{ + TRootHandler_OW() : TFileTreeRoot(0) + { + // We have file names and return CKey as result of search + dwFeatures |= (CASC_FEATURE_FILE_NAMES | CASC_FEATURE_ROOT_CKEY); + } +/* + bool IsManifestFolderName(const char * szFileName, const char * szManifestFolder, size_t nLength) + { + if(!_strnicmp(szFileName, szManifestFolder, nLength)) + { + return (szFileName[nLength] == '\\' || szFileName[nLength] == '/'); + } + return false; + } + + bool IsApmFileName(const char * szFileName) + { + const char * szExtension; + + if(IsManifestFolderName(szFileName, "Manifest", 8) || IsManifestFolderName(szFileName, "TactManifest", 12)) + { + szExtension = GetFileExtension(szFileName); + if(!_stricmp(szExtension, ".apm")) + { + return true; + } + } + + return false; + } + + int LoadApmFile(TCascStorage * hs, CONTENT_KEY & CKey, const char * szFileName) + { + TApmFile ApmFile; + LPBYTE pbApmData; + DWORD cbApmData = 0; + int nError = ERROR_BAD_FORMAT; + + pbApmData = LoadInternalFileToMemory(hs, CKey.Value, CASC_OPEN_BY_CKEY, &cbApmData); + if(pbApmData != NULL) + { + LPBYTE pbApmEnd = pbApmData + cbApmData; + LPBYTE pbApmPtr = pbApmData; + + pbApmPtr = ApmFile.CaptureHeader(pbApmPtr, pbApmEnd); + if(pbApmPtr == NULL) + return ERROR_BAD_FORMAT; + + // Read the array of entries + pbApmPtr = ApmFile.CaptureArrayOfEntries(pbApmPtr, pbApmEnd); + if(pbApmPtr == NULL) + return ERROR_BAD_FORMAT; + + // Read the array of package entries + pbApmPtr = ApmFile.CapturePackageEntries(pbApmPtr, pbApmEnd); + if(pbApmPtr == NULL) + return ERROR_BAD_FORMAT; + + CASC_FREE(pbApmData); + } + + return nError; + } + + static int LoadCmfFile(TCascStorage * hs, CONTENT_KEY & CKey, const char * szFileName) + { + TCmfFile CmfFile; + LPBYTE pbCmfData; + DWORD cbCmfData = 0; + + int nError = ERROR_BAD_FORMAT; + + pbCmfData = LoadInternalFileToMemory(hs, CKey.Value, CASC_OPEN_BY_CKEY, &cbCmfData); + if(pbCmfData != NULL) + { + LPBYTE pbCmfEnd = pbCmfData + cbCmfData; + LPBYTE pbCmfPtr = pbCmfData; + + // Capture the CMF header + pbCmfPtr = CmfFile.CaptureHeader(pbCmfPtr, pbCmfEnd); + if(pbCmfPtr == NULL) + return ERROR_BAD_FORMAT; + +// if(CmfFile.Magic >= 0x636D6614) +// DecryptCmfFile( + + CASC_FREE(pbCmfData); + } + + return nError; + } +*/ + int Load(TCascStorage * hs, CASC_CSV & Csv, size_t nFileNameIndex, size_t nCKeyIndex) + { + PCASC_CKEY_ENTRY pCKeyEntry; +// size_t ApmFiles[0x80]; +// size_t nApmFiles = 0; + BYTE CKey[MD5_HASH_SIZE]; + + CASCLIB_UNUSED(hs); + + // Keep loading every line until there is something + while(Csv.LoadNextLine()) + { + const CASC_CSV_COLUMN & FileName = Csv[CSV_ZERO][nFileNameIndex]; + const CASC_CSV_COLUMN & CKeyStr = Csv[CSV_ZERO][nCKeyIndex]; + + // Retrieve the file name and the content key + if(FileName.szValue && CKeyStr.szValue && CKeyStr.nLength == MD5_STRING_SIZE) + { + // Convert the string CKey to binary + if(ConvertStringToBinary(CKeyStr.szValue, MD5_STRING_SIZE, CKey) == ERROR_SUCCESS) + { + // Find the item in the tree + if((pCKeyEntry = FindCKeyEntry_CKey(hs, CKey)) != NULL) + { + // Insert the file name and the CKey into the tree + FileTree.InsertByName(pCKeyEntry, FileName.szValue); + + // If the file name is actually an asset, we need to parse that asset and load files in it +// if(IsApmFileName(szFileName)) +// { +// ApmFiles[nApmFiles++] = FileTree_IndexOf(&pRootHandler->FileTree, pFileNode1); +// } + } + } + } + } +/* + // Load all CMF+APM files + if(nError == ERROR_SUCCESS) + { + for(size_t i = 0; i < nApmFiles; i++) + { + char szApmFile[MAX_PATH + 1]; + char szCmfFile[MAX_PATH + 1]; + + // Get the n-th item and its name + pFileNode1 = (PCASC_FILE_NODE)FileTree_PathAt(&pRootHandler->FileTree, szApmFile, MAX_PATH, ApmFiles[i]); + if(pFileNode1 == NULL) + break; + + if(strcmp(szApmFile, "TactManifest\\Win_SPWin_RDEV_LenUS_EExt.apm")) + continue; + + // Get the name of thew CMF file + CascStrCopy(szCmfFile, _countof(szCmfFile), szApmFile); + CascStrCopy((char *)GetFileExtension(szCmfFile), 5, ".cmf"); + pFileNode2 = (PCASC_FILE_NODE)FileTree_Find(&pRootHandler->FileTree, szCmfFile); + if(pFileNode2 == NULL) + break; + + // Create the map of CMF entries + nError = LoadCmfFile(hs, pFileNode2->CKey, szCmfFile); + if(nError != ERROR_SUCCESS) + break; + + } + } +*/ + return ERROR_SUCCESS; + } +}; + +//----------------------------------------------------------------------------- +// 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) +{ + TRootHandler_OW * pRootHandler = NULL; + CASC_CSV Csv(0, true); + size_t Indices[2]; + int nError; + + // Load the ROOT file + nError = Csv.Load(pbRootFile, cbRootFile); + if(nError == ERROR_SUCCESS) + { + // Retrieve the indices of the file name and MD5 columns + Indices[0] = Csv.GetColumnIndex("FILENAME"); + Indices[1] = Csv.GetColumnIndex("MD5"); + + // If both indices were found OK, then load the root file + if(Indices[0] != CSV_INVALID_INDEX && Indices[1] != CSV_INVALID_INDEX) + { + pRootHandler = new TRootHandler_OW(); + 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) + { + delete pRootHandler; + pRootHandler = NULL; + } + } + } + else + { + nError = ERROR_BAD_FORMAT; + } + } + + // Assign the root directory (or NULL) and return error + hs->pRootHandler = pRootHandler; + return nError; +} diff --git a/dep/CascLib/src/CascRootFile_Ovr.cpp b/dep/CascLib/src/CascRootFile_Ovr.cpp deleted file mode 100644 index 05ce13bd67a..00000000000 --- a/dep/CascLib/src/CascRootFile_Ovr.cpp +++ /dev/null @@ -1,213 +0,0 @@ -/*****************************************************************************/ -/* CascRootFile_Ovr.cpp Copyright (c) Ladislav Zezula 2015 */ -/*---------------------------------------------------------------------------*/ -/* Support for loading Overwatch ROOT file */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 28.10.15 1.00 Lad The first version of CascRootFile_Ovr.cpp */ -/*****************************************************************************/ - -#define __CASCLIB_SELF__ -#include "CascLib.h" -#include "CascCommon.h" - -//----------------------------------------------------------------------------- -// Structure definitions for Overwatch root file - -typedef struct _CASC_FILE_ENTRY -{ - ENCODING_KEY EncodingKey; // Encoding key - ULONGLONG FileNameHash; // File name hash - DWORD dwFileName; // Offset of the file name in the name cache -} CASC_FILE_ENTRY, *PCASC_FILE_ENTRY; - -struct TRootHandler_Ovr : public TRootHandler -{ - // Linear global list of file entries - DYNAMIC_ARRAY FileTable; - - // Linear global list of names - DYNAMIC_ARRAY FileNames; - - // Global map of FileName -> FileEntry - PCASC_MAP pRootMap; -}; - -//----------------------------------------------------------------------------- -// Local functions - -static int InsertFileEntry( - TRootHandler_Ovr * pRootHandler, - const char * szFileName, - LPBYTE pbEncodingKey) -{ - PCASC_FILE_ENTRY pFileEntry; - size_t nLength = strlen(szFileName); - - // Attempt to insert the file name to the global buffer - szFileName = (char *)Array_Insert(&pRootHandler->FileNames, szFileName, nLength + 1); - if(szFileName == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Attempt to insert the entry to the array of file entries - pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1); - if(pFileEntry == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Fill the file entry - pFileEntry->EncodingKey = *(PENCODING_KEY)pbEncodingKey; - pFileEntry->FileNameHash = CalcFileNameHash(szFileName); - pFileEntry->dwFileName = (DWORD)Array_IndexOf(&pRootHandler->FileNames, szFileName); - - // Insert the file entry to the map - assert(Map_FindObject(pRootHandler->pRootMap, &pFileEntry->FileNameHash, NULL) == NULL); - Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash); - return ERROR_SUCCESS; -} - -//----------------------------------------------------------------------------- -// Implementation of Overwatch root file - -static int OvrHandler_Insert( - TRootHandler_Ovr * pRootHandler, - const char * szFileName, - LPBYTE pbEncodingKey) -{ - return InsertFileEntry(pRootHandler, szFileName, pbEncodingKey); -} - -static LPBYTE OvrHandler_Search(TRootHandler_Ovr * pRootHandler, TCascSearch * pSearch, PDWORD /* PtrFileSize */, PDWORD /* PtrLocaleFlags */, PDWORD /* PtrFileDataId */) -{ - PCASC_FILE_ENTRY pFileEntry; - - // Are we still inside the root directory range? - while(pSearch->IndexLevel1 < pRootHandler->FileTable.ItemCount) - { - // Retrieve the file item - pFileEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, pSearch->IndexLevel1); - strcpy(pSearch->szFileName, (char *)Array_ItemAt(&pRootHandler->FileNames, pFileEntry->dwFileName)); - - // Prepare the pointer to the next search - pSearch->IndexLevel1++; - return pFileEntry->EncodingKey.Value; - } - - // No more entries - return NULL; -} - -static void OvrHandler_EndSearch(TRootHandler_Ovr * /* pRootHandler */, TCascSearch * /* pSearch */) -{ - // Do nothing -} - -static LPBYTE OvrHandler_GetKey(TRootHandler_Ovr * pRootHandler, const char * szFileName) -{ - ULONGLONG FileNameHash = CalcFileNameHash(szFileName); - - return (LPBYTE)Map_FindObject(pRootHandler->pRootMap, &FileNameHash, NULL); -} - -static DWORD OvrHandler_GetFileId(TRootHandler_Ovr * /* pRootHandler */, const char * /* szFileName */) -{ - // Not implemented for Overwatch - return 0; -} - -static void OvrHandler_Close(TRootHandler_Ovr * pRootHandler) -{ - if(pRootHandler != NULL) - { - // Free the file map - if(pRootHandler->pRootMap) - Map_Free(pRootHandler->pRootMap); - pRootHandler->pRootMap = NULL; - - // Free the array of the file names and file items - Array_Free(&pRootHandler->FileTable); - Array_Free(&pRootHandler->FileNames); - - // Free the root file itself - CASC_FREE(pRootHandler); - } -} - -//----------------------------------------------------------------------------- -// Public functions - -int RootHandler_CreateOverwatch(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) -{ - TRootHandler_Ovr * pRootHandler; - ENCODING_KEY KeyBuffer; - QUERY_KEY EncodingKey = {KeyBuffer.Value, MD5_HASH_SIZE}; - void * pTextFile; - size_t nLength; - char szOneLine[0x200]; - char szFileName[MAX_PATH+1]; - DWORD dwFileCountMax = (DWORD)hs->pEncodingMap->TableSize; - int nFileNameIndex; - int nError = ERROR_SUCCESS; - - // Allocate the root handler object - hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_Ovr, 1); - if(pRootHandler == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Fill-in the handler functions - memset(pRootHandler, 0, sizeof(TRootHandler_Ovr)); - pRootHandler->Insert = (ROOT_INSERT)OvrHandler_Insert; - pRootHandler->Search = (ROOT_SEARCH)OvrHandler_Search; - pRootHandler->EndSearch = (ROOT_ENDSEARCH)OvrHandler_EndSearch; - pRootHandler->GetKey = (ROOT_GETKEY)OvrHandler_GetKey; - pRootHandler->Close = (ROOT_CLOSE)OvrHandler_Close; - pRootHandler->GetFileId = (ROOT_GETFILEID)OvrHandler_GetFileId; - - // Fill-in the flags - pRootHandler->dwRootFlags |= ROOT_FLAG_HAS_NAMES; - - // Allocate the linear array of file entries - nError = Array_Create(&pRootHandler->FileTable, CASC_FILE_ENTRY, 0x10000); - if(nError != ERROR_SUCCESS) - return nError; - - // Allocate the buffer for the file names - nError = Array_Create(&pRootHandler->FileNames, char, 0x10000); - if(nError != ERROR_SUCCESS) - return nError; - - // Create map of ROOT_ENTRY -> FileEntry - pRootHandler->pRootMap = Map_Create(dwFileCountMax, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_ENTRY, FileNameHash)); - if(pRootHandler->pRootMap == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Parse the ROOT file - pTextFile = ListFile_FromBuffer(pbRootFile, cbRootFile); - if(pTextFile != NULL) - { - // Get the initial line, containing variable names - nLength = ListFile_GetNextLine(pTextFile, szOneLine, _maxchars(szOneLine)); - - // Determine the index of the "FILENAME" variable - nError = GetRootVariableIndex(szOneLine, szOneLine + nLength, "FILENAME", &nFileNameIndex); - if(nError == ERROR_SUCCESS) - { - // Parse the next lines - while((nLength = ListFile_GetNextLine(pTextFile, szOneLine, _maxchars(szOneLine))) > 0) - { - // Parse the line - nError = ParseRootFileLine(szOneLine, szOneLine + nLength, nFileNameIndex, &EncodingKey, szFileName, _maxchars(szFileName)); - if(nError == ERROR_SUCCESS) - { - InsertFileEntry(pRootHandler, szFileName, KeyBuffer.Value); - } - } - } - - // Free the listfile - ListFile_Free(pTextFile); - } - - // Succeeded - return nError; -} diff --git a/dep/CascLib/src/CascRootFile_SC1.cpp b/dep/CascLib/src/CascRootFile_SC1.cpp deleted file mode 100644 index a795cfd8d96..00000000000 --- a/dep/CascLib/src/CascRootFile_SC1.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/*****************************************************************************/ -/* CascRootFile_SC1.cpp Copyright (c) Ladislav Zezula 2017 */ -/*---------------------------------------------------------------------------*/ -/* Support for loading Starcraft 1 ROOT file */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 28.10.15 1.00 Lad The first version of CascRootFile_SC1.cpp */ -/*****************************************************************************/ - -#define __CASCLIB_SELF__ -#include "CascLib.h" -#include "CascCommon.h" - -//----------------------------------------------------------------------------- -// Structure definitions for Overwatch root file - -typedef struct _CASC_FILE_ENTRY -{ - ENCODING_KEY EncodingKey; // Encoding key - ULONGLONG FileNameHash; // File name hash - DWORD dwFileName; // Offset of the file name in the name cache -} CASC_FILE_ENTRY, *PCASC_FILE_ENTRY; - -struct TRootHandler_SC1 : public TRootHandler -{ - // Linear global list of file entries - DYNAMIC_ARRAY FileTable; - - // Linear global list of names - DYNAMIC_ARRAY FileNames; - - // Global map of FileName -> FileEntry - PCASC_MAP pRootMap; -}; - -//----------------------------------------------------------------------------- -// Local functions - -static int InsertFileEntry( - TRootHandler_SC1 * pRootHandler, - const char * szFileName, - LPBYTE pbEncodingKey) -{ - PCASC_FILE_ENTRY pFileEntry; - size_t nLength = strlen(szFileName); - - // Attempt to insert the file name to the global buffer - szFileName = (char *)Array_Insert(&pRootHandler->FileNames, szFileName, nLength + 1); - if(szFileName == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Attempt to insert the entry to the array of file entries - pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1); - if(pFileEntry == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Fill the file entry - pFileEntry->EncodingKey = *(PENCODING_KEY)pbEncodingKey; - pFileEntry->FileNameHash = CalcFileNameHash(szFileName); - pFileEntry->dwFileName = (DWORD)Array_IndexOf(&pRootHandler->FileNames, szFileName); - - // Insert the file entry to the map - assert(Map_FindObject(pRootHandler->pRootMap, &pFileEntry->FileNameHash, NULL) == NULL); - Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash); - return ERROR_SUCCESS; -} - -//----------------------------------------------------------------------------- -// Implementation of Overwatch root file - -static int SC1Handler_Insert( - TRootHandler_SC1 * pRootHandler, - const char * szFileName, - LPBYTE pbEncodingKey) -{ - return InsertFileEntry(pRootHandler, szFileName, pbEncodingKey); -} - -static LPBYTE SC1Handler_Search(TRootHandler_SC1 * pRootHandler, TCascSearch * pSearch, PDWORD /* PtrFileSize */, PDWORD /* PtrLocaleFlags */, PDWORD /* PtrFileDataId */) -{ - PCASC_FILE_ENTRY pFileEntry; - - // Are we still inside the root directory range? - while(pSearch->IndexLevel1 < pRootHandler->FileTable.ItemCount) - { - // Retrieve the file item - pFileEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, pSearch->IndexLevel1); - - // Prepare the pointer to the next search - pSearch->IndexLevel1++; - - char *filename = (char *)Array_ItemAt(&pRootHandler->FileNames, pFileEntry->dwFileName); - if (CheckWildCard(filename, pSearch->szMask)) { - strcpy(pSearch->szFileName, filename); - return pFileEntry->EncodingKey.Value; - } - } - - // No more entries - return NULL; -} - -static void SC1Handler_EndSearch(TRootHandler_SC1 * /* pRootHandler */, TCascSearch * /* pSearch */) -{ - // Do nothing -} - -static LPBYTE SC1Handler_GetKey(TRootHandler_SC1 * pRootHandler, const char * szFileName) -{ - ULONGLONG FileNameHash = CalcFileNameHash(szFileName); - - return (LPBYTE)Map_FindObject(pRootHandler->pRootMap, &FileNameHash, NULL); -} - -static DWORD SC1Handler_GetFileId(TRootHandler_SC1 * /* pRootHandler */, const char * /* szFileName */) -{ - // Not implemented for Overwatch - return 0; -} - -static void SC1Handler_Close(TRootHandler_SC1 * pRootHandler) -{ - if(pRootHandler != NULL) - { - // Free the file map - if(pRootHandler->pRootMap) - Map_Free(pRootHandler->pRootMap); - pRootHandler->pRootMap = NULL; - - // Free the array of the file names and file items - Array_Free(&pRootHandler->FileTable); - Array_Free(&pRootHandler->FileNames); - - // Free the root file itself - CASC_FREE(pRootHandler); - } -} - -//----------------------------------------------------------------------------- -// Public functions - -int RootHandler_CreateSC1(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) -{ - TRootHandler_SC1 * pRootHandler; - ENCODING_KEY KeyBuffer; - QUERY_KEY EncodingKey = {KeyBuffer.Value, MD5_HASH_SIZE}; - void * pTextFile; - size_t nLength; - char szOneLine[0x200]; - DWORD dwFileCountMax = (DWORD)hs->pEncodingMap->TableSize; - int nError = ERROR_SUCCESS; - - // Allocate the root handler object - hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_SC1, 1); - if(pRootHandler == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Fill-in the handler functions - memset(pRootHandler, 0, sizeof(TRootHandler_SC1)); - pRootHandler->Insert = (ROOT_INSERT)SC1Handler_Insert; - pRootHandler->Search = (ROOT_SEARCH)SC1Handler_Search; - pRootHandler->EndSearch = (ROOT_ENDSEARCH)SC1Handler_EndSearch; - pRootHandler->GetKey = (ROOT_GETKEY)SC1Handler_GetKey; - pRootHandler->Close = (ROOT_CLOSE)SC1Handler_Close; - pRootHandler->GetFileId = (ROOT_GETFILEID)SC1Handler_GetFileId; - - // Fill-in the flags - pRootHandler->dwRootFlags |= ROOT_FLAG_HAS_NAMES; - - // Allocate the linear array of file entries - nError = Array_Create(&pRootHandler->FileTable, CASC_FILE_ENTRY, 0x10000); - if(nError != ERROR_SUCCESS) - return nError; - - // Allocate the buffer for the file names - nError = Array_Create(&pRootHandler->FileNames, char, 0x10000); - if(nError != ERROR_SUCCESS) - return nError; - - // Create map of ROOT_ENTRY -> FileEntry - pRootHandler->pRootMap = Map_Create(dwFileCountMax, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_ENTRY, FileNameHash)); - if(pRootHandler->pRootMap == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Parse the ROOT file - pTextFile = ListFile_FromBuffer(pbRootFile, cbRootFile); - if(pTextFile != NULL) - { - // Parse the next lines - while((nLength = ListFile_GetNextLine(pTextFile, szOneLine, _maxchars(szOneLine))) > 0) - { - LPSTR szEncodingKey; - BYTE EncodingKey[MD5_HASH_SIZE]; - - szEncodingKey = strchr(szOneLine, _T('|')); - if(szEncodingKey != NULL) - { - // Split the name and encoding key - *szEncodingKey++ = 0; - - // Insert the entry to the map - ConvertStringToBinary(szEncodingKey, MD5_STRING_SIZE, EncodingKey); - InsertFileEntry(pRootHandler, szOneLine, EncodingKey); - } - } - - // Free the listfile - ListFile_Free(pTextFile); - } - - // Succeeded - return nError; -} diff --git a/dep/CascLib/src/CascRootFile_TVFS.cpp b/dep/CascLib/src/CascRootFile_TVFS.cpp new file mode 100644 index 00000000000..b115a215037 --- /dev/null +++ b/dep/CascLib/src/CascRootFile_TVFS.cpp @@ -0,0 +1,634 @@ +/*****************************************************************************/ +/* CascRootFile_TVFS.cpp Copyright (c) Ladislav Zezula 2018 */ +/*---------------------------------------------------------------------------*/ +/* ROOT handler for TACT VFS manifest format (root) */ +/* Note: TACT = Trusted Application Content Transfer */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 24.05.18 1.00 Lad The first version of CascRootFile_TVFS.cpp */ +/*****************************************************************************/ + +#define __CASCLIB_SELF__ +#include "CascLib.h" +#include "CascCommon.h" + +//----------------------------------------------------------------------------- +// Local defines + +#define TVFS_FLAG_INCLUDE_CKEY 0x0001 // Include C-key in content file record +#define TVFS_FLAG_WRITE_SUPPORT 0x0002 // Write support. Include a table of encoding specifiers. This is required for writing files to the underlying storage. This bit is implied by the patch-support bit +#define TVFS_FLAG_PATCH_SUPPORT 0x0004 // Patch support. Include patch records in the content file records. +#define TVFS_FLAG_LOWERCASE_MANIFEST 0x0008 // Lowercase manifest. All paths in the path table have been converted to ASCII lowercase (i.e. [A-Z] converted to [a-z]) + +#define TVFS_PTE_PATH_SEPARATOR_PRE 0x0001 // There is path separator before the name +#define TVFS_PTE_PATH_SEPARATOR_POST 0x0002 // There is path separator after the name +#define TVFS_PTE_NODE_VALUE 0x0004 // The NodeValue in path table entry is valid + +#define TVFS_FOLDER_NODE 0x80000000 // Highest bit is set if a file node is a folder +#define TVFS_FOLDER_SIZE_MASK 0x7FFFFFFF // Mask to get length of the folder + +//----------------------------------------------------------------------------- +// Local structures + +// In-memory layout of the TVFS file header +typedef struct _TVFS_DIRECTORY_HEADER +{ + DWORD Signature; // Must be CASC_TVFS_ROOT_SIGNATURE + BYTE FormatVersion; // Version of the format. Should be 1. + BYTE HeaderSize; // Size of the header, in bytes + BYTE EKeySize; // Size of an E-Key. TACT uses 9-byte E-keys + BYTE PatchKeySize; // Size of a patch key. TACT uses 9-byte P-keys + DWORD Flags; // Flags. See TVFS_FLAG_XXX + + // Followed by the offset table (variable length) + DWORD PathTableOffset; // Offset of the path table + DWORD PathTableSize; // Size of the path table + DWORD VfsTableOffset; // Offset of the VFS table + DWORD VfsTableSize; // Size of the VFS table + DWORD CftTableOffset; // Offset of the container file table + DWORD CftTableSize; // Size of the container file table + USHORT MaxDepth; // The maximum depth of the path prefix tree stored in the path table + DWORD EstTableOffset; // The offset of the encoding specifier table. Only if the write-support bit is set in the header flag + DWORD EstTableSize; // The size of the encoding specifier table. Only if the write-support bit is set in the header flag + + DWORD CftOffsSize; // Byte length of the offset in the Content File Table entry + DWORD EstOffsSize; // Byte length of the offset in the Encoding Specifier Table entry + + LPBYTE pbDirectoryData; // Pointer to the begin of directory data + LPBYTE pbDirectoryEnd; // Pointer to the end of directory data + +// LPBYTE pbPathFileTable; // Begin and end of the path table +// LPBYTE pbPathTableEnd; + +// LPBYTE pbVfsFileTable; // Begin and end of the VFS file table +// LPBYTE pbVfsTableEnd; + +// LPBYTE pbCftFileTable; // Begin and end of the content file table +// LPBYTE pbCftTableEnd; + +} TVFS_DIRECTORY_HEADER, *PTVFS_DIRECTORY_HEADER; + +/* +// Minimum size of a valid path table entry. 1 byte + 1-byte name + 1 byte + DWORD +#define TVFS_HEADER_LENGTH FIELD_OFFSET(TVFS_DIRECTORY_HEADER, CftOffsSize) + +// Minimum size of a valid path table entry. 1 byte + 1-byte name + 1 byte + DWORD +#define TVFS_MIN_PATH_ENTRY (1 + 1 + 1 + sizeof(DWORD)) + +// Minimum size of the VFS entry (SpanCount + FileOffset + SpanLength + CftOffset) +#define TVFS_MIN_VFS_ENTRY (1 + sizeof(DWORD) + sizeof(DWORD) + 1) + +// Minimum size of the Content File Table entry (CASC_EKEY_SIZE + EncodedSize + ContentSize) +#define TVFS_MIN_CFT_ENTRY (CASC_EKEY_SIZE + sizeof(DWORD) + sizeof(DWORD)) + +// Minimum size of the TVFS folder data +#define TVFS_MIN_FILE_SIZE (TVFS_HEADER_LENGTH + TVFS_MIN_PATH_ENTRY + TVFS_MIN_VFS_ENTRY + TVFS_MIN_CFT_ENTRY) + +// Maximum estimated file table. Empirically set to 8 MB, increase if needed. +#define TVFS_MAX_FILE_SIZE 0x00800000 +*/ + +// In-memory layout of the path table entry +typedef struct _TVFS_PATH_TABLE_ENTRY +{ + LPBYTE pbNamePtr; // Pointer to the begin of the node name + LPBYTE pbNameEnd; // Pointer to the end of the file name + DWORD NodeFlags; // TVFS_PTE_XXX + DWORD NodeValue; // Node value +} TVFS_PATH_TABLE_ENTRY, *PTVFS_PATH_TABLE_ENTRY; + +//----------------------------------------------------------------------------- +// Handler definition for TVFS root file + +//static FILE * fp = NULL; + +// Structure for the root handler +struct TRootHandler_TVFS : public TFileTreeRoot +{ + public: + + TRootHandler_TVFS() : TFileTreeRoot(0) + { + // 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. + // - 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 + // - If the container file table is smaller than 0xff bytes, it's 1 byte + static DWORD GetOffsetFieldSize(DWORD dwTableSize) + { + if(dwTableSize > 0xffffff) + return 4; + if(dwTableSize > 0xffff) + return 3; + if(dwTableSize > 0xff) + return 2; + 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) + { + // Append the prefix separator, if needed + if (PathEntry.NodeFlags & TVFS_PTE_PATH_SEPARATOR_PRE) + PathBuffer_AddChar(PathBuffer, '/'); + + // Append the name fragment, if any + if (PathEntry.pbNameEnd > PathEntry.pbNamePtr) + PathBuffer_AddString(PathBuffer, PathEntry.pbNamePtr, PathEntry.pbNameEnd); + + // Append the postfix separator, if needed + if (PathEntry.NodeFlags & TVFS_PTE_PATH_SEPARATOR_POST) + PathBuffer_AddChar(PathBuffer, '/'); + + // Always end the buffer with zero + PathBuffer.szPtr[0] = 0; + return true; + } + + static int CaptureDirectoryHeader(TVFS_DIRECTORY_HEADER & DirHeader, LPBYTE pbDataPtr, LPBYTE pbDataEnd) + { + // Fill the header structure with zeros + memset(&DirHeader, 0, sizeof(TVFS_DIRECTORY_HEADER)); + DirHeader.pbDirectoryData = pbDataPtr; + DirHeader.pbDirectoryEnd = pbDataEnd; + + // Capture the signature + pbDataPtr = CaptureInteger32(pbDataPtr, pbDataEnd, &DirHeader.Signature); + if(pbDataPtr == NULL || DirHeader.Signature != CASC_TVFS_ROOT_SIGNATURE) + return ERROR_BAD_FORMAT; + + // Capture the other four integers + pbDataPtr = CaptureByteArray(pbDataPtr, pbDataEnd, 4, &DirHeader.FormatVersion); + if(pbDataPtr == NULL || DirHeader.FormatVersion != 1 || DirHeader.EKeySize != 9 || DirHeader.PatchKeySize != 9 || DirHeader.HeaderSize < 8) + return ERROR_BAD_FORMAT; + + // Capture the rest + pbDataPtr = CaptureByteArray(pbDataPtr, pbDataEnd, DirHeader.HeaderSize - FIELD_OFFSET(TVFS_DIRECTORY_HEADER, Flags), (LPBYTE)(&DirHeader.Flags)); + if(pbDataPtr == NULL) + return ERROR_BAD_FORMAT; + + // Swap the header values + DirHeader.Flags = ConvertBytesToInteger_4_LE((LPBYTE)(&DirHeader.Flags)); + + // Swap the offset table values + DirHeader.PathTableOffset = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.PathTableOffset)); + DirHeader.PathTableSize = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.PathTableSize)); + DirHeader.VfsTableOffset = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.VfsTableOffset)); + DirHeader.VfsTableSize = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.VfsTableSize)); + DirHeader.CftTableOffset = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.CftTableOffset)); + DirHeader.CftTableSize = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.CftTableSize)); + DirHeader.MaxDepth = (USHORT)ConvertBytesToInteger_2((LPBYTE)(&DirHeader.MaxDepth)); + DirHeader.EstTableOffset = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.EstTableOffset)); + DirHeader.EstTableSize = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.EstTableSize)); + + // Determine size of file table offsets + DirHeader.CftOffsSize = GetOffsetFieldSize(DirHeader.CftTableSize); + DirHeader.EstOffsSize = GetOffsetFieldSize(DirHeader.EstTableSize); + + // Capture the path table +// DirHeader.pbPathFileTable = pbDirectory + DirHeader.PathTableOffset; +// DirHeader.pbPathTableEnd = pbDirectory + DirHeader.PathTableOffset + DirHeader.PathTableSize; +// if(DirHeader.pbPathTableEnd > pbDataEnd) +// return ERROR_BAD_FORMAT; + + // Capture the VFS file table +// DirHeader.pbVfsFileTable = pbDirectory + DirHeader.VfsTableOffset; +// DirHeader.pbVfsTableEnd = pbDirectory + DirHeader.VfsTableOffset + DirHeader.VfsTableSize; +// if(DirHeader.pbVfsTableEnd > pbDataEnd) +// return ERROR_BAD_FORMAT; + + // Capture the container file table +// DirHeader.pbCftFileTable = pbDirectory + DirHeader.CftTableOffset; +// DirHeader.pbCftTableEnd = pbDirectory + DirHeader.CftTableOffset + DirHeader.CftTableSize; +// if(DirHeader.pbCftTableEnd > pbDataEnd) +// return ERROR_BAD_FORMAT; + + return ERROR_SUCCESS; + } + + int CaptureVfsSpanEntries(TVFS_DIRECTORY_HEADER & DirHeader, PENCODED_KEY pEKey, PDWORD PtrSpanSize, DWORD dwVfsOffset) + { + LPBYTE pbVfsFileTable = DirHeader.pbDirectoryData + DirHeader.VfsTableOffset; + LPBYTE pbVfsFileEntry = pbVfsFileTable + dwVfsOffset; + LPBYTE pbVfsFileEnd = pbVfsFileTable + DirHeader.VfsTableSize; + LPBYTE pbCftFileTable; + LPBYTE pbCftFileEntry; + LPBYTE pbCftFileEnd; + LPBYTE pbTemp = NULL; + size_t ItemSize = sizeof(DWORD) + sizeof(DWORD) + DirHeader.CftOffsSize; + DWORD dwCftOffset; + DWORD dwSpanCount; + DWORD dwSpanSize; + + // Get the number of span entries + if(!(pbVfsFileTable <= pbVfsFileEntry && pbVfsFileEntry <= pbVfsFileEnd)) + return ERROR_INVALID_PARAMETER; + dwSpanCount = *pbVfsFileEntry++; + + // 1 - 224 = valid file, 225-254 = other file, 255 = deleted file + // We will ignore all files with unsupported span count + if(dwSpanCount == 0 || dwSpanCount > 224) + return ERROR_BAD_FORMAT; + + // So far we've only saw entries with 1 span. + // Need to test files with multiple spans. Ignore such files for now. + assert(dwSpanCount == 1); + + // Capture the array of span items + if(CaptureArray_(pbVfsFileEntry, pbVfsFileEnd, &pbTemp, ItemSize, dwSpanCount) == NULL) + return ERROR_BAD_FORMAT; + + // + // 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 + // + + // Get the offset to the Container File Table + dwCftOffset = ConvertBytesToInteger_X(pbVfsFileEntry + sizeof(DWORD) + sizeof(DWORD), DirHeader.CftOffsSize); + dwSpanSize = ConvertBytesToInteger_4(pbVfsFileEntry + sizeof(DWORD)); + + // Go to the Container File Table and fetch the EKey from there + pbCftFileTable = DirHeader.pbDirectoryData + DirHeader.CftTableOffset; + pbCftFileEntry = pbCftFileTable + dwCftOffset; + pbCftFileEnd = pbCftFileTable + DirHeader.CftTableSize; + if((pbCftFileEntry + DirHeader.EKeySize) > pbCftFileEnd) + return ERROR_BAD_FORMAT; + + // Give the pointer to the EKey + memcpy(pEKey->Value, pbCftFileEntry, DirHeader.EKeySize); + PtrSpanSize[0] = dwSpanSize; + return ERROR_SUCCESS; + } + + // + // Structure of the path table entry: + // (1byte) 0x00 (optional) - means that there will be prefix path separator + // (1byte) File name length + // (?byte) File name + // (1byte) 0x00 (optional) - means that there will be postfix path separator + // (1byte) 0xFF (optional) - node value identifier + // (4byte) - node value + // + // Note: The path "data\archive\maps\file.bmp" could be cut into nodes like: + // data\0 (or data with subdirectory) + // arc + // hive\0 + // maps\0 (or folder data) + // file.bmp + // + + LPBYTE CapturePathEntry(TVFS_PATH_TABLE_ENTRY & PathEntry, LPBYTE pbPathTablePtr, LPBYTE pbPathTableEnd) + { + // Reset the path entry structure + PathEntry.pbNamePtr = pbPathTablePtr; + PathEntry.pbNameEnd = pbPathTablePtr; + PathEntry.NodeFlags = 0; + PathEntry.NodeValue = 0; + + // Zero before the name means prefix path separator + if (pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] == 0) + { + PathEntry.NodeFlags |= TVFS_PTE_PATH_SEPARATOR_PRE; + pbPathTablePtr++; + } + + // Capture the length of the name fragment + if (pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] != 0xFF) + { + // Capture length of the name fragment + size_t nLength = *pbPathTablePtr++; + + if ((pbPathTablePtr + nLength) > pbPathTableEnd) + return NULL; + PathEntry.pbNamePtr = pbPathTablePtr; + PathEntry.pbNameEnd = pbPathTablePtr + nLength; + pbPathTablePtr += nLength; + } + + // Zero after the name means postfix path separator + if (pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] == 0) + { + PathEntry.NodeFlags |= TVFS_PTE_PATH_SEPARATOR_POST; + pbPathTablePtr++; + } + + if (pbPathTablePtr < pbPathTableEnd) + { + // Check for node value + if (pbPathTablePtr[0] == 0xFF) + { + if ((pbPathTablePtr + 1 + sizeof(DWORD)) > pbPathTableEnd) + return NULL; + PathEntry.NodeValue = ConvertBytesToInteger_4(pbPathTablePtr + 1); + PathEntry.NodeFlags |= TVFS_PTE_NODE_VALUE; + pbPathTablePtr = pbPathTablePtr + 1 + sizeof(DWORD); + } + + // Non-0xFF after the name means path separator after + else + { + PathEntry.NodeFlags |= TVFS_PTE_PATH_SEPARATOR_POST; + assert(pbPathTablePtr[0] != 0); + } + } + + return pbPathTablePtr; + } + + bool IsVfsFileEKey(TCascStorage * hs, ENCODED_KEY & EKey, size_t EKeyLength) + { + PCASC_CKEY_ENTRY pCKeyEntry; + size_t ItemCount = hs->VfsRootList.ItemCount(); + + // Search the array + for (size_t i = 0; i < ItemCount; i++) + { + pCKeyEntry = (PCASC_CKEY_ENTRY)hs->VfsRootList.ItemAt(i); + if (pCKeyEntry != NULL) + { + if (!memcmp(pCKeyEntry->EKey, EKey.Value, EKeyLength)) + return true; + } + } + + // Not found in the VFS list + return false; + } + + // 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) + { + PCASC_CKEY_ENTRY pCKeyEntry; + LPBYTE pbVfsData = NULL; + DWORD cbVfsData = dwFileSize; + int nError = 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) + { + // 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; + + // Clear the captured header + memset(&SubHeader, 0, sizeof(TVFS_DIRECTORY_HEADER)); + CASC_FREE(pbVfsData); + } + } + } + + return nError; + } + + void InsertRootVfsEntry(TCascStorage * hs, LPBYTE pbCKey, const char * szFormat, size_t nIndex) + { + PCASC_CKEY_ENTRY pCKeyEntry; + char szFileName[0x20]; + + // The CKey entry must exist + if((pCKeyEntry = FindCKeyEntry_CKey(hs, pbCKey)) != NULL) + { + CascStrPrintf(szFileName, _countof(szFileName), szFormat, nIndex); + Insert(szFileName, pCKeyEntry); + } + } + + DWORD ParsePathFileTable(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, PATH_BUFFER & PathBuffer, LPBYTE pbPathTablePtr, LPBYTE pbPathTableEnd) + { + TVFS_DIRECTORY_HEADER SubHeader; + TVFS_PATH_TABLE_ENTRY PathEntry; + PCASC_CKEY_ENTRY pCKeyEntry; + ENCODED_KEY EKey; + char * szSavePathPtr = PathBuffer.szPtr; + DWORD dwSpanSize = 0; + int nError; + + // Prepare the EKey structure to be filled with zeros + memset(&EKey, 0, sizeof(ENCODED_KEY)); + + // Parse the file table + while(pbPathTablePtr < pbPathTableEnd) + { + // Capture the single path table entry + pbPathTablePtr = CapturePathEntry(PathEntry, pbPathTablePtr, pbPathTableEnd); + if(pbPathTablePtr == NULL) + return ERROR_BAD_FORMAT; + + // Append the node name to the total path. Also add backslash, if it's a folder + PathBuffer_AppendNode(PathBuffer, PathEntry); + + // Folder component + if (PathEntry.NodeFlags & TVFS_PTE_NODE_VALUE) + { + // If the TVFS_FOLDER_NODE is set, then the path node is a directory, + // with its data immediately following the path node. Lower 31 bits of NodeValue + // contain the length of the directory (including the NodeValue!) + if (PathEntry.NodeValue & TVFS_FOLDER_NODE) + { + LPBYTE pbDirectoryEnd = pbPathTablePtr + (PathEntry.NodeValue & TVFS_FOLDER_SIZE_MASK) - sizeof(DWORD); + + // Check the available data + 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; + + // Skip the directory data + pbPathTablePtr = pbDirectoryEnd; + } + else + { + // Capture the VFS and Container Table Entry in order to get the file EKey + nError = CaptureVfsSpanEntries(DirHeader, &EKey, &dwSpanSize, PathEntry.NodeValue); + if (nError != ERROR_SUCCESS) + return nError; + + // We need to check whether this is another TVFS directory file + if (IsVfsSubDirectory(hs, DirHeader, SubHeader, EKey, dwSpanSize) == ERROR_SUCCESS) + { + // Add colon (':') + PathBuffer_AddChar(PathBuffer, ':'); + + // Insert the file to the file tree + if((pCKeyEntry = FindCKeyEntry_EKey(hs, EKey.Value)) != NULL) + { + // The file content size should already be there + assert(pCKeyEntry->ContentSize == dwSpanSize); + FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin); + } + + ParseDirectoryData(hs, SubHeader, PathBuffer); + CASC_FREE(SubHeader.pbDirectoryData); + } + else + { +// if (fp != NULL) +// { +// fwrite(PathBuffer.szBegin, 1, (PathBuffer.szPtr - PathBuffer.szBegin), fp); +// fprintf(fp, "\n"); +// } + + // Insert the file to the file tree + if((pCKeyEntry = FindCKeyEntry_EKey(hs, EKey.Value)) != NULL) + { + // If the file content is not there, supply it now + if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE) + pCKeyEntry->ContentSize = dwSpanSize; + FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin); + } + } + } + + // Reset the position of the path buffer + PathBuffer.szPtr = szSavePathPtr; + PathBuffer.szPtr[0] = 0; + } + } + + // Return the total number of entries + return ERROR_SUCCESS; + } + + int ParseDirectoryData(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, PATH_BUFFER & PathBuffer) + { + LPBYTE pbRootDirectory = DirHeader.pbDirectoryData + DirHeader.PathTableOffset; + LPBYTE pbRootDirPtr = pbRootDirectory; + LPBYTE pbRootDirEnd = pbRootDirPtr + DirHeader.PathTableSize; + DWORD dwNodeValue = 0; + + // Most usually, there is a root directory in the folder + if((pbRootDirPtr + 1 + sizeof(DWORD)) < pbRootDirEnd) + { + // + // The structure of the root directory + // ----------------------------------- + // 1byte 0xFF + // 4bytes NodeValue (BigEndian). The most significant bit is set + // - Lower 31 bits contain length of the directory data, including NodeValue + // + + if(pbRootDirPtr[0] == 0xFF) + { + // Get the NodeValue and check its highest bit + if(CaptureInteger32_BE(pbRootDirPtr + 1, pbRootDirEnd, &dwNodeValue) == NULL || (dwNodeValue & TVFS_FOLDER_NODE) == 0) + return ERROR_BAD_FORMAT; + + // Get the range of the root directory + pbRootDirEnd = pbRootDirPtr + 1 + (dwNodeValue & TVFS_FOLDER_SIZE_MASK); + pbRootDirPtr = pbRootDirPtr + 1 + sizeof(DWORD); + + // Check the directory + if(pbRootDirEnd > (pbRootDirectory + DirHeader.PathTableSize)) + return ERROR_BAD_FORMAT; + } + } + + // Now go parse the path file table + return ParsePathFileTable(hs, DirHeader, PathBuffer, pbRootDirPtr, pbRootDirEnd); + } + + int 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; + + // Save the length of the key + FileTree.SetKeyLength(RootHeader.EKeySize); + + // Insert the main VFS root file as named entry + InsertRootVfsEntry(hs, hs->VfsRoot.CKey, "vfs-root", 0); + + // Insert all VFS roots folders as files + //for(size_t i = 0; i < hs->VfsRootList.ItemCount(); i++) + //{ + // pCKeyEntry = (PCASC_CKEY_ENTRY)hs->VfsRootList.ItemAt(i); + // InsertRootVfsEntry(hs, pCKeyEntry->CKey, "vfs-%u", i+1); + //} + + // Parse the entire directory data + return ParseDirectoryData(hs, RootHeader, PathBuffer); + } + + DWORD dwNestLevel; +}; + +//----------------------------------------------------------------------------- +// Public functions - TVFS root + +int RootHandler_CreateTVFS(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) +{ + TRootHandler_TVFS * pRootHandler = NULL; + TVFS_DIRECTORY_HEADER RootHeader; + int nError; + + // Capture the entire root directory + nError = TRootHandler_TVFS::CaptureDirectoryHeader(RootHeader, pbRootFile, pbRootFile + cbRootFile); + if(nError == 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) + { + delete pRootHandler; + pRootHandler = NULL; + } + } + } + + // Assign the root directory (or NULL) and return error + hs->pRootHandler = pRootHandler; + return nError; +} diff --git a/dep/CascLib/src/CascRootFile_Text.cpp b/dep/CascLib/src/CascRootFile_Text.cpp new file mode 100644 index 00000000000..e78b0bf086f --- /dev/null +++ b/dep/CascLib/src/CascRootFile_Text.cpp @@ -0,0 +1,121 @@ +/*****************************************************************************/ +/* CascRootFile_Text.cpp Copyright (c) Ladislav Zezula 2017 */ +/*---------------------------------------------------------------------------*/ +/* Support for generic ROOT handler with mapping of FileName -> CKey */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 28.10.15 1.00 Lad The first version of CascRootFile_Text.cpp */ +/*****************************************************************************/ + +#define __CASCLIB_SELF__ +#include "CascLib.h" +#include "CascCommon.h" + +//----------------------------------------------------------------------------- +// Handler definitions for Starcraft I root file + +struct TRootHandler_SC1 : public TFileTreeRoot +{ + public: + + TRootHandler_SC1() : TFileTreeRoot(0) + { + // We have file names and return CKey as result of search + dwFeatures |= (CASC_FEATURE_FILE_NAMES | CASC_FEATURE_ROOT_CKEY); + } + + static bool IsRootFile(LPBYTE pbRootFile, DWORD cbRootFile) + { + CASC_CSV Csv(1, false); + size_t nColumns; + bool bResult = false; + + // Get the first line from the listfile + if(Csv.Load(pbRootFile, cbRootFile) == ERROR_SUCCESS) + { + // There must be 2 or 3 elements + nColumns = Csv[CSV_ZERO].GetColumnCount(); + if (nColumns == 2 || nColumns == 3) + { + const CASC_CSV_COLUMN & FileName = Csv[CSV_ZERO][CSV_ZERO]; + const CASC_CSV_COLUMN & CKeyStr = Csv[CSV_ZERO][1]; + + bResult = (FileName.szValue && CKeyStr.szValue && CKeyStr.nLength == MD5_STRING_SIZE); + } + } + + // We need to reset the listfile to the begin position + return bResult; + } + + int Load(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) + { + PCASC_CKEY_ENTRY pCKeyEntry; + CASC_CSV Csv(0, false); + BYTE CKey[MD5_HASH_SIZE]; + int nError; + + // Parse the ROOT file first in order to see whether we have the correct format + nError = Csv.Load(pbRootFile, cbRootFile); + if(nError == ERROR_SUCCESS) + { + // Parse all lines + while(Csv.LoadNextLine()) + { + const CASC_CSV_COLUMN & FileName = Csv[CSV_ZERO][CSV_ZERO]; + const CASC_CSV_COLUMN & CKeyStr = Csv[CSV_ZERO][1]; + + // Convert the CKey to binary + if(ConvertStringToBinary(CKeyStr.szValue, MD5_STRING_SIZE, CKey) == ERROR_SUCCESS) + { + // Verify whether it is a known entry + if((pCKeyEntry = FindCKeyEntry_CKey(hs, CKey)) != NULL) + { + // Insert the FileName+CKey to the file tree + FileTree.InsertByName(pCKeyEntry, FileName.szValue); + } + } + } + } + + return nError; + } +}; + +//----------------------------------------------------------------------------- +// 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 +// + +int RootHandler_CreateStarcraft1(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) +{ + TRootHandler_SC1 * pRootHandler = NULL; + int nError = ERROR_BAD_FORMAT; + + // Verify whether this looks like a Starcraft I root file + if(TRootHandler_SC1::IsRootFile(pbRootFile, cbRootFile)) + { + // Allocate the root handler object + pRootHandler = new TRootHandler_SC1(); + if(pRootHandler != NULL) + { + // Load the root directory. If load failed, we free the object + nError = pRootHandler->Load(hs, pbRootFile, cbRootFile); + if(nError != ERROR_SUCCESS) + { + delete pRootHandler; + pRootHandler = NULL; + } + } + } + + // Assign the root directory (or NULL) and return error + hs->pRootHandler = pRootHandler; + return nError; +} diff --git a/dep/CascLib/src/CascRootFile_WoW.cpp b/dep/CascLib/src/CascRootFile_WoW.cpp new file mode 100644 index 00000000000..780220bbe40 --- /dev/null +++ b/dep/CascLib/src/CascRootFile_WoW.cpp @@ -0,0 +1,481 @@ +/*****************************************************************************/ +/* CascRootFile_WoW.cpp Copyright (c) Ladislav Zezula 2014 */ +/*---------------------------------------------------------------------------*/ +/* Storage functions for CASC */ +/* Note: WoW offsets refer to WoW.exe 6.0.3.19116 (32-bit) */ +/* SHA1: c10e9ffb7d040a37a356b96042657e1a0c95c0dd */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 29.04.14 1.00 Lad The first version of CascRootFile_WoW.cpp */ +/*****************************************************************************/ + +#define __CASCLIB_SELF__ +#include "CascLib.h" +#include "CascCommon.h" + +//----------------------------------------------------------------------------- +// Local structures + +#define ROOT_SEARCH_PHASE_INITIALIZING 0 +#define ROOT_SEARCH_PHASE_LISTFILE 1 +#define ROOT_SEARCH_PHASE_NAMELESS 2 +#define ROOT_SEARCH_PHASE_FINISHED 3 + +// Known dwRegion values returned from sub_661316 (7.0.3.22210 x86 win), also referred by lua GetCurrentRegion +#define WOW_REGION_US 0x01 +#define WOW_REGION_KR 0x02 +#define WOW_REGION_EU 0x03 +#define WOW_REGION_TW 0x04 +#define WOW_REGION_CN 0x05 + +typedef enum _ROOT_FORMAT +{ + RootFormatWoW6x, // WoW 6.x - 8.1.x + RootFormatWoW82 // WoW 8.2 or newer +} ROOT_FORMAT, *PROOT_FORMAT; + +// ROOT file header, since 8.2 +typedef struct _FILE_ROOT_HEADER_82 +{ + DWORD Signature; // Must be CASC_WOW82_ROOT_SIGNATURE + DWORD TotalFiles; + DWORD FilesWithNameHash; +} FILE_ROOT_HEADER_82, *PFILE_ROOT_HEADER_82; + +// On-disk version of root group. A root group contains a group of file +// with the same locale and file flags +typedef struct _FILE_ROOT_GROUP_HEADER +{ + DWORD NumberOfFiles; // Number of entries + DWORD ContentFlags; + DWORD LocaleFlags; // File locale mask (CASC_LOCALE_XXX) + + // Followed by a block of file data IDs (count: NumberOfFiles) + // Followed by the MD5 and file name hash (count: NumberOfFiles) + +} FILE_ROOT_GROUP_HEADER, *PFILE_ROOT_GROUP_HEADER; + +// On-disk version of root entry. Only present in versions 6.x - 8.1.xx +// Each root entry represents one file in the CASC storage +// In WoW 8.2 and newer, CKey and FileNameHash are split into separate arrays +// and FileNameHash is optional +typedef struct _FILE_ROOT_ENTRY +{ + CONTENT_KEY CKey; // MD5 of the file + ULONGLONG FileNameHash; // Jenkins hash of the file name + +} FILE_ROOT_ENTRY, *PFILE_ROOT_ENTRY; + +typedef struct _FILE_ROOT_GROUP +{ + FILE_ROOT_GROUP_HEADER Header; + PDWORD FileDataIds; // Pointer to the array of File Data IDs + + PFILE_ROOT_ENTRY pRootEntries; // Valid for WoW 6.x - 8.1.x + PCONTENT_KEY pCKeyEntries; // Valid for WoW 8.2 or newer + PULONGLONG pHashes; // Valid for WoW 8.2 or newer (optional) + +} FILE_ROOT_GROUP, *PFILE_ROOT_GROUP; + +//----------------------------------------------------------------------------- +// TRootHandler_WoW interface / implementation + +#define FTREE_FLAGS_WOW (FTREE_FLAG_USE_DATA_ID | FTREE_FLAG_USE_LOCALE_FLAGS | FTREE_FLAG_USE_CONTENT_FLAGS) + +struct TRootHandler_WoW : public TFileTreeRoot +{ + public: + + TRootHandler_WoW(ROOT_FORMAT RFormat, DWORD HashlessFileCount) : TFileTreeRoot(FTREE_FLAGS_WOW) + { + // Turn off the "we know file names" bit + FileCounterHashless = HashlessFileCount; + FileCounter = 0; + RootFormat = RFormat; + + // Update the flags based on format + switch(RootFormat) + { + case RootFormatWoW6x: + dwFeatures |= CASC_FEATURE_ROOT_CKEY | CASC_FEATURE_FNAME_HASHES | CASC_FEATURE_FILE_DATA_IDS | CASC_FEATURE_LOCALE_FLAGS | CASC_FEATURE_CONTENT_FLAGS; + break; + + case RootFormatWoW82: + dwFeatures |= CASC_FEATURE_ROOT_CKEY | CASC_FEATURE_FNAME_HASHES_OPTIONAL | CASC_FEATURE_FILE_DATA_IDS | CASC_FEATURE_LOCALE_FLAGS | CASC_FEATURE_CONTENT_FLAGS; + break; + } + } + + static LPBYTE CaptureRootHeader(FILE_ROOT_HEADER_82 & RootHeader, LPBYTE pbRootPtr, LPBYTE pbRootEnd) + { + // Validate the root file header + if((pbRootPtr + sizeof(FILE_ROOT_HEADER_82)) >= pbRootEnd) + return NULL; + memcpy(&RootHeader, pbRootPtr, sizeof(FILE_ROOT_HEADER_82)); + + // Verify the root file header + if(RootHeader.Signature != CASC_WOW82_ROOT_SIGNATURE) + return NULL; + if(RootHeader.FilesWithNameHash > RootHeader.TotalFiles) + return NULL; + + return pbRootPtr + sizeof(FILE_ROOT_HEADER_82); + } + + LPBYTE CaptureRootGroup(FILE_ROOT_GROUP & RootGroup, LPBYTE pbRootPtr, LPBYTE pbRootEnd) + { + // Reset the entire root group structure + memset(&RootGroup, 0, sizeof(FILE_ROOT_GROUP)); + + // Validate the locale block header + if((pbRootPtr + sizeof(FILE_ROOT_GROUP_HEADER)) >= pbRootEnd) + return NULL; + memcpy(&RootGroup.Header, pbRootPtr, sizeof(FILE_ROOT_GROUP_HEADER)); + pbRootPtr = pbRootPtr + sizeof(FILE_ROOT_GROUP_HEADER); + + // Validate the array of file data IDs + if((pbRootPtr + (sizeof(DWORD) * RootGroup.Header.NumberOfFiles)) >= pbRootEnd) + return NULL; + RootGroup.FileDataIds = (PDWORD)pbRootPtr; + pbRootPtr = pbRootPtr + (sizeof(DWORD) * RootGroup.Header.NumberOfFiles); + + // Add the number of files in this block to the number of files loaded + FileCounter += RootGroup.Header.NumberOfFiles; + + // Validate the array of root entries + switch(RootFormat) + { + case RootFormatWoW6x: + if((pbRootPtr + (sizeof(FILE_ROOT_ENTRY) * RootGroup.Header.NumberOfFiles)) > pbRootEnd) + return NULL; + RootGroup.pRootEntries = (PFILE_ROOT_ENTRY)pbRootPtr; + + // Return the position of the next block + return pbRootPtr + (sizeof(FILE_ROOT_ENTRY) * RootGroup.Header.NumberOfFiles); + + case RootFormatWoW82: + + // Verify the position of array of CONTENT_KEY + if((pbRootPtr + (sizeof(CONTENT_KEY) * RootGroup.Header.NumberOfFiles)) > pbRootEnd) + return NULL; + RootGroup.pCKeyEntries = (PCONTENT_KEY)pbRootPtr; + pbRootPtr = pbRootPtr + (sizeof(CONTENT_KEY) * RootGroup.Header.NumberOfFiles); + + // Also include array of file hashes + if(FileCounter > FileCounterHashless) + { + if((pbRootPtr + (sizeof(ULONGLONG) * RootGroup.Header.NumberOfFiles)) > pbRootEnd) + return NULL; + RootGroup.pHashes = (PULONGLONG)pbRootPtr; + pbRootPtr = pbRootPtr + (sizeof(ULONGLONG) * RootGroup.Header.NumberOfFiles); + } + + return pbRootPtr; + + default: + return NULL; + } + } + + int ParseWowRootFile_AddFiles_6x(TCascStorage * hs, FILE_ROOT_GROUP & RootGroup) + { + PFILE_ROOT_ENTRY pRootEntry = RootGroup.pRootEntries; + PCASC_CKEY_ENTRY pCKeyEntry; + DWORD FileDataId = 0; + + // Sanity check + assert(RootGroup.pRootEntries != NULL); + + // WoW.exe (build 19116): Blocks with zero files are skipped + for(DWORD i = 0; i < RootGroup.Header.NumberOfFiles; i++, pRootEntry++) + { + // Set the file data ID + FileDataId = FileDataId + RootGroup.FileDataIds[i]; +// BREAKIF(FileDataId == 2823765); + + // Find the item in the central storage. Insert it to the tree + if((pCKeyEntry = FindCKeyEntry_CKey(hs, pRootEntry->CKey.Value)) != NULL) + { + if(pRootEntry->FileNameHash != 0) + { + FileTree.InsertByHash(pCKeyEntry, pRootEntry->FileNameHash, FileDataId, RootGroup.Header.LocaleFlags, RootGroup.Header.ContentFlags); + } + else + { + FileTree.InsertById(pCKeyEntry, FileDataId, RootGroup.Header.LocaleFlags, RootGroup.Header.ContentFlags); + } + } + + // Update the file data ID + assert((FileDataId + 1) > FileDataId); + FileDataId++; + } + + return ERROR_SUCCESS; + } + + int ParseWowRootFile_AddFiles_82(TCascStorage * hs, FILE_ROOT_GROUP & RootGroup) + { + PCASC_CKEY_ENTRY pCKeyEntry; + PCONTENT_KEY pCKey = RootGroup.pCKeyEntries; + DWORD FileDataId = 0; + + // Sanity check + assert(RootGroup.pCKeyEntries != NULL); + + // WoW.exe (build 19116): Blocks with zero files are skipped + for(DWORD i = 0; i < RootGroup.Header.NumberOfFiles; i++, pCKey++) + { + // Set the file data ID + FileDataId = FileDataId + RootGroup.FileDataIds[i]; + + // Find the item in the central storage. Insert it to the tree + if((pCKeyEntry = FindCKeyEntry_CKey(hs, pCKey->Value)) != NULL) + { + // If we know the file name hash, we're gonna insert it by hash AND file data id. + // If we don't know the hash, we're gonna insert it just by file data id. + if(RootGroup.pHashes != NULL && RootGroup.pHashes[i] != 0) + { + FileTree.InsertByHash(pCKeyEntry, RootGroup.pHashes[i], FileDataId, RootGroup.Header.LocaleFlags, RootGroup.Header.ContentFlags); + } + else + { + FileTree.InsertById(pCKeyEntry, FileDataId, RootGroup.Header.LocaleFlags, RootGroup.Header.ContentFlags); + } + } + + // Update the file data ID + assert((FileDataId + 1) > FileDataId); + FileDataId++; + } + + return ERROR_SUCCESS; + } + + int ParseWowRootFile_Level2( + TCascStorage * hs, + LPBYTE pbRootPtr, + LPBYTE pbRootEnd, + DWORD dwLocaleMask, + BYTE bOverrideLowViolence, + BYTE bAudioLocale) + { + FILE_ROOT_GROUP RootBlock; + + // Reset the total file counter + FileCounter = 0; + + // Now parse the root file + while(pbRootPtr < pbRootEnd) + { + // Validate the file locale block + pbRootPtr = CaptureRootGroup(RootBlock, pbRootPtr, pbRootEnd); + if(pbRootPtr == NULL) + return ERROR_BAD_FORMAT; + + // WoW.exe (build 19116): Entries with flag 0x100 set are skipped + if(RootBlock.Header.ContentFlags & CASC_CFLAG_DONT_LOAD) + continue; + + // WoW.exe (build 19116): Entries with flag 0x80 set are skipped if overrideArchive CVAR is set to FALSE (which is by default in non-chinese clients) + if((RootBlock.Header.ContentFlags & CASC_CFLAG_LOW_VIOLENCE) && bOverrideLowViolence == 0) + continue; + + // WoW.exe (build 19116): Entries with (flags >> 0x1F) not equal to bAudioLocale are skipped + if((RootBlock.Header.ContentFlags >> 0x1F) != bAudioLocale) + continue; + + // WoW.exe (build 19116): Locales other than defined mask are skipped too + if((RootBlock.Header.LocaleFlags & dwLocaleMask) == 0) + continue; + + // Now call the custom function + switch(RootFormat) + { + case RootFormatWoW82: + ParseWowRootFile_AddFiles_82(hs, RootBlock); + break; + + case RootFormatWoW6x: + ParseWowRootFile_AddFiles_6x(hs, RootBlock); + break; + + default: + return ERROR_NOT_SUPPORTED; + } + } + + return ERROR_SUCCESS; + } + + /* + #define CASC_LOCALE_BIT_ENUS 0x01 + #define CASC_LOCALE_BIT_KOKR 0x02 + #define CASC_LOCALE_BIT_RESERVED 0x03 + #define CASC_LOCALE_BIT_FRFR 0x04 + #define CASC_LOCALE_BIT_DEDE 0x05 + #define CASC_LOCALE_BIT_ZHCN 0x06 + #define CASC_LOCALE_BIT_ESES 0x07 + #define CASC_LOCALE_BIT_ZHTW 0x08 + #define CASC_LOCALE_BIT_ENGB 0x09 + #define CASC_LOCALE_BIT_ENCN 0x0A + #define CASC_LOCALE_BIT_ENTW 0x0B + #define CASC_LOCALE_BIT_ESMX 0x0C + #define CASC_LOCALE_BIT_RURU 0x0D + #define CASC_LOCALE_BIT_PTBR 0x0E + #define CASC_LOCALE_BIT_ITIT 0x0F + #define CASC_LOCALE_BIT_PTPT 0x10 + + // dwLocale is obtained from a WOW_LOCALE_* to CASC_LOCALE_BIT_* mapping (sub_6615D0 in 7.0.3.22210 x86 win) + // because (ENUS, ENGB) and (PTBR, PTPT) pairs share the same value on WOW_LOCALE_* enum + // dwRegion is used to distinguish them + if(dwRegion == WOW_REGION_EU) + { + // Is this english version of WoW? + if(dwLocale == CASC_LOCALE_BIT_ENUS) + { + LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, CASC_LOCALE_ENGB, bOverrideArchive, bAudioLocale); + LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, CASC_LOCALE_ENUS, bOverrideArchive, bAudioLocale); + return ERROR_SUCCESS; + } + + // Is this portuguese version of WoW? + if(dwLocale == CASC_LOCALE_BIT_PTBR) + { + LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, CASC_LOCALE_PTPT, bOverrideArchive, bAudioLocale); + LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, CASC_LOCALE_PTBR, bOverrideArchive, bAudioLocale); + } + } + else + LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, (1 << dwLocale), bOverrideArchive, bAudioLocale); + */ + + int ParseWowRootFile_Level1( + TCascStorage * hs, + LPBYTE pbRootPtr, + LPBYTE pbRootEnd, + DWORD dwLocaleMask, + BYTE bAudioLocale) + { + int nError; + + // Load the locale as-is + nError = ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, dwLocaleMask, false, bAudioLocale); + if (nError != ERROR_SUCCESS) + return nError; + + // If we wanted enGB, we also load enUS for the missing files + if(dwLocaleMask == CASC_LOCALE_ENGB) + ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, CASC_LOCALE_ENUS, false, bAudioLocale); + + if(dwLocaleMask == CASC_LOCALE_PTPT) + ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, CASC_LOCALE_PTBR, false, bAudioLocale); + + return ERROR_SUCCESS; + } + + // WoW.exe: 004146C7 (BuildManifest::Load) + int Load(TCascStorage * hs, LPBYTE pbRootPtr, LPBYTE pbRootEnd, DWORD dwLocaleMask) + { + int nError; + + nError = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 0); + if (nError == ERROR_SUCCESS) + nError = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 1); + + return nError; + } + + // Search for files + PCASC_CKEY_ENTRY Search(TCascSearch * pSearch, PCASC_FIND_DATA pFindData) + { + // If we have a listfile, we'll feed the listfile entries to the file tree + if(pSearch->pCache != NULL && pSearch->bListFileUsed == false) + { + PCASC_FILE_NODE pFileNode; + ULONGLONG FileNameHash; + DWORD FileDataId = CASC_INVALID_ID; + char szFileName[MAX_PATH]; + + if(RootFormat == RootFormatWoW82) + { + // Keep going through the listfile + while(ListFile_GetNext(pSearch->pCache, szFileName, _countof(szFileName), &FileDataId)) + { + // Try to find the file node by file data id + pFileNode = FileTree.FindById(FileDataId); + if(pFileNode != NULL && pFileNode->NameLength == 0) + { + FileTree.SetNodeFileName(pFileNode, szFileName); + } + } + } + else + { + // Keep going through the listfile + while(ListFile_GetNextLine(pSearch->pCache, szFileName, MAX_PATH)) + { + // Calculate the hash of the file name + FileNameHash = CalcFileNameHash(szFileName); + + // Try to find the file node by file name hash + pFileNode = FileTree.Find(FileNameHash); + if(pFileNode != NULL && pFileNode->NameLength == 0) + { + FileTree.SetNodeFileName(pFileNode, szFileName); + } + } + } + pSearch->bListFileUsed = true; + } + + // Let the file tree root give us the file names + return TFileTreeRoot::Search(pSearch, pFindData); + } + + ROOT_FORMAT RootFormat; // Root file format + DWORD FileCounterHashless; // Number of files for which we don't have hash. Meaningless for WoW before 8.2 + DWORD FileCounter; // Counter of loaded files. Only used during loading of ROOT file +}; + +//----------------------------------------------------------------------------- +// Public functions + +int RootHandler_CreateWoW(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask) +{ + TRootHandler_WoW * pRootHandler = NULL; + FILE_ROOT_HEADER_82 RootHeader; + ROOT_FORMAT RootFormat = RootFormatWoW6x; + LPBYTE pbRootEnd = pbRootFile + cbRootFile; + LPBYTE pbRootPtr; + DWORD FileCounterHashless = 0; + int nError = ERROR_BAD_FORMAT; + + // Check for the new format (World of Warcraft 8.2, build 30170) + pbRootPtr = TRootHandler_WoW::CaptureRootHeader(RootHeader, pbRootFile, pbRootEnd); + if(pbRootPtr != NULL) + { + FileCounterHashless = RootHeader.TotalFiles - RootHeader.FilesWithNameHash; + RootFormat = RootFormatWoW82; + pbRootFile = pbRootPtr; + } + + // Create the WOW handler + pRootHandler = new TRootHandler_WoW(RootFormat, FileCounterHashless); + 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) + { + delete pRootHandler; + pRootHandler = NULL; + } + } + + // Assign the root directory (or NULL) and return error + hs->pRootHandler = pRootHandler; + return nError; +} + diff --git a/dep/CascLib/src/CascRootFile_WoW6.cpp b/dep/CascLib/src/CascRootFile_WoW6.cpp deleted file mode 100644 index 74805d07620..00000000000 --- a/dep/CascLib/src/CascRootFile_WoW6.cpp +++ /dev/null @@ -1,595 +0,0 @@ -/*****************************************************************************/ -/* CascOpenStorage.cpp Copyright (c) Ladislav Zezula 2014 */ -/*---------------------------------------------------------------------------*/ -/* Storage functions for CASC */ -/* Note: WoW6 offsets refer to WoW.exe 6.0.3.19116 (32-bit) */ -/* SHA1: c10e9ffb7d040a37a356b96042657e1a0c95c0dd */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 29.04.14 1.00 Lad The first version of CascOpenStorage.cpp */ -/*****************************************************************************/ - -#define __CASCLIB_SELF__ -#include "CascLib.h" -#include "CascCommon.h" - -//----------------------------------------------------------------------------- -// Local structures - -#define ROOT_SEARCH_PHASE_INITIALIZING 0 -#define ROOT_SEARCH_PHASE_LISTFILE 1 -#define ROOT_SEARCH_PHASE_NAMELESS 2 -#define ROOT_SEARCH_PHASE_FINISHED 2 - -// On-disk version of locale block -typedef struct _FILE_LOCALE_BLOCK -{ - DWORD NumberOfFiles; // Number of entries - DWORD Flags; - DWORD Locales; // File locale mask (CASC_LOCALE_XXX) - - // Followed by a block of 32-bit integers (count: NumberOfFiles) - // Followed by the MD5 and file name hash (count: NumberOfFiles) - -} FILE_LOCALE_BLOCK, *PFILE_LOCALE_BLOCK; - -// On-disk version of root entry -typedef struct _FILE_ROOT_ENTRY -{ - ENCODING_KEY EncodingKey; // MD5 of the file - ULONGLONG FileNameHash; // Jenkins hash of the file name - -} FILE_ROOT_ENTRY, *PFILE_ROOT_ENTRY; - - -typedef struct _CASC_ROOT_BLOCK -{ - PFILE_LOCALE_BLOCK pLocaleBlockHdr; // Pointer to the locale block - PDWORD FileDataIds; // Pointer to the array of File Data IDs - PFILE_ROOT_ENTRY pRootEntries; - -} CASC_ROOT_BLOCK, *PCASC_ROOT_BLOCK; - -// Root file entry for CASC storages without MNDX root file (World of Warcraft 6.0+) -// Does not match to the in-file structure of the root entry -typedef struct _CASC_FILE_ENTRY -{ - ENCODING_KEY EncodingKey; // File encoding key (MD5) - ULONGLONG FileNameHash; // Jenkins hash of the file name - DWORD FileDataId; // File Data Index - DWORD Locales; // Locale flags of the file - -} CASC_FILE_ENTRY, *PCASC_FILE_ENTRY; - -struct TRootHandler_WoW6 : public TRootHandler -{ - // Linear global list of file entries - DYNAMIC_ARRAY FileTable; - DYNAMIC_ARRAY FileDataIdLookupTable; - - // Global map of FileName -> FileEntry - PCASC_MAP pRootMap; - - // For counting files - DWORD dwTotalFileCount; -}; - -// Prototype for root file parsing routine -typedef int (*PARSE_ROOT)(TRootHandler_WoW6 * pRootHandler, PCASC_ROOT_BLOCK pBlockInfo); - -//----------------------------------------------------------------------------- -// Local functions - -static bool IsFileDataIdName(const char * szFileName) -{ - BYTE BinaryValue[4]; - - // File name must begin with "File", case insensitive - if(AsciiToUpperTable_BkSlash[szFileName[0]] == 'F' && - AsciiToUpperTable_BkSlash[szFileName[1]] == 'I' && - AsciiToUpperTable_BkSlash[szFileName[2]] == 'L' && - AsciiToUpperTable_BkSlash[szFileName[3]] == 'E') - { - // Then, 8 hexadecimal digits must follow - if(ConvertStringToBinary(szFileName + 4, 8, BinaryValue) == ERROR_SUCCESS) - { - // Must be followed by an extension or end-of-string - return (szFileName[0x0C] == 0 || szFileName[0x0C] == '.'); - } - } - - return false; -} - -static int FileDataIdCompare(const void *, const void * pvFile1, const void * pvFile2) -{ - return ((PCASC_FILE_ENTRY)pvFile1)->FileDataId - ((PCASC_FILE_ENTRY)pvFile2)->FileDataId; -} - -// Search by FileDataId -PCASC_FILE_ENTRY FindRootEntry(DYNAMIC_ARRAY & FileTable, DWORD FileDataId) -{ - PCASC_FILE_ENTRY* pStartEntry = (PCASC_FILE_ENTRY*)FileTable.ItemArray; - PCASC_FILE_ENTRY* pMidlEntry; - PCASC_FILE_ENTRY* pEndEntry = pStartEntry + FileTable.ItemCount - 1; - int nResult; - - // Perform binary search on the table - while(pStartEntry < pEndEntry) - { - // Calculate the middle of the interval - pMidlEntry = pStartEntry + ((pEndEntry - pStartEntry) / 2); - - // Did we find it? - nResult = (int)FileDataId - (int)(*pMidlEntry)->FileDataId; - if(nResult == 0) - return *pMidlEntry; - - // Move the interval to the left or right - (nResult < 0) ? pEndEntry = pMidlEntry : pStartEntry = pMidlEntry + 1; - } - - return NULL; -} - -// Search by file name hash -// Also used in CascSearchFile -PCASC_FILE_ENTRY FindRootEntry(PCASC_MAP pRootMap, const char * szFileName, DWORD * PtrTableIndex) -{ - // Calculate the HASH value of the normalized file name - ULONGLONG FileNameHash = CalcFileNameHash(szFileName); - - // Perform the hash search - return (PCASC_FILE_ENTRY)Map_FindObject(pRootMap, &FileNameHash, PtrTableIndex); -} - -LPBYTE VerifyLocaleBlock(PCASC_ROOT_BLOCK pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd) -{ - // Validate the file locale block - pBlockInfo->pLocaleBlockHdr = (PFILE_LOCALE_BLOCK)pbFilePointer; - pbFilePointer = (LPBYTE)(pBlockInfo->pLocaleBlockHdr + 1); - if(pbFilePointer > pbFileEnd) - return NULL; - - // Validate the array of 32-bit integers - pBlockInfo->FileDataIds = (PDWORD)pbFilePointer; - pbFilePointer = (LPBYTE)(pBlockInfo->FileDataIds + pBlockInfo->pLocaleBlockHdr->NumberOfFiles); - if(pbFilePointer > pbFileEnd) - return NULL; - - // Validate the array of root entries - pBlockInfo->pRootEntries = (PFILE_ROOT_ENTRY)pbFilePointer; - pbFilePointer = (LPBYTE)(pBlockInfo->pRootEntries + pBlockInfo->pLocaleBlockHdr->NumberOfFiles); - if(pbFilePointer > pbFileEnd) - return NULL; - - // Return the position of the next block - return pbFilePointer; -} - -static int ParseRoot_CountFiles( - TRootHandler_WoW6 * pRootHandler, - PCASC_ROOT_BLOCK pRootBlock) -{ - // Add the file count to the total file count - pRootHandler->dwTotalFileCount += pRootBlock->pLocaleBlockHdr->NumberOfFiles; - return ERROR_SUCCESS; -} - -static int ParseRoot_AddRootEntries( - TRootHandler_WoW6 * pRootHandler, - PCASC_ROOT_BLOCK pRootBlock) -{ - PCASC_FILE_ENTRY pFileEntry; - DWORD dwFileDataId = 0; - - // Sanity checks - assert(pRootHandler->FileTable.ItemArray != NULL); - assert(pRootHandler->FileTable.ItemCountMax != 0); - assert(pRootHandler->FileDataIdLookupTable.ItemArray != NULL); - assert(pRootHandler->FileDataIdLookupTable.ItemCountMax != 0); - - // WoW.exe (build 19116): Blocks with zero files are skipped - for(DWORD i = 0; i < pRootBlock->pLocaleBlockHdr->NumberOfFiles; i++) - { - // Create new entry, with overflow check - if(pRootHandler->FileTable.ItemCount >= pRootHandler->FileTable.ItemCountMax) - return ERROR_INSUFFICIENT_BUFFER; - pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1); - - if (pRootHandler->FileDataIdLookupTable.ItemCount >= pRootHandler->FileDataIdLookupTable.ItemCountMax) - return ERROR_INSUFFICIENT_BUFFER; - Array_Insert(&pRootHandler->FileDataIdLookupTable, &pFileEntry, 1); - - // (004147A3) Prepare the CASC_FILE_ENTRY structure - pFileEntry->FileNameHash = pRootBlock->pRootEntries[i].FileNameHash; - pFileEntry->FileDataId = dwFileDataId + pRootBlock->FileDataIds[i]; - pFileEntry->Locales = pRootBlock->pLocaleBlockHdr->Locales; - pFileEntry->EncodingKey = pRootBlock->pRootEntries[i].EncodingKey; - - // Also, insert the entry to the map - Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash); - - // Update the local File Data Id - assert((pFileEntry->FileDataId + 1) > pFileEntry->FileDataId); - dwFileDataId = pFileEntry->FileDataId + 1; - - // Move to the next root entry - pFileEntry++; - } - - return ERROR_SUCCESS; -} - -static int ParseWowRootFileInternal( - TRootHandler_WoW6 * pRootHandler, - PARSE_ROOT pfnParseRoot, - LPBYTE pbRootFile, - LPBYTE pbRootFileEnd, - DWORD dwLocaleMask, - BYTE bOverrideArchive, - BYTE bAudioLocale) -{ - CASC_ROOT_BLOCK RootBlock; - - // Now parse the root file - while(pbRootFile < pbRootFileEnd) - { - // Validate the file locale block - pbRootFile = VerifyLocaleBlock(&RootBlock, pbRootFile, pbRootFileEnd); - if(pbRootFile == NULL) - break; - - // WoW.exe (build 19116): Entries with flag 0x100 set are skipped - if(RootBlock.pLocaleBlockHdr->Flags & 0x100) - continue; - - // WoW.exe (build 19116): Entries with flag 0x80 set are skipped if overrideArchive CVAR is set to FALSE (which is by default in non-chinese clients) - if((RootBlock.pLocaleBlockHdr->Flags & 0x80) && bOverrideArchive == 0) - continue; - - // WoW.exe (build 19116): Entries with (flags >> 0x1F) not equal to bAudioLocale are skipped - if((RootBlock.pLocaleBlockHdr->Flags >> 0x1F) != bAudioLocale) - continue; - - // WoW.exe (build 19116): Locales other than defined mask are skipped too - if((RootBlock.pLocaleBlockHdr->Locales & dwLocaleMask) == 0) - continue; - - // Now call the custom function - pfnParseRoot(pRootHandler, &RootBlock); - } - - return ERROR_SUCCESS; -} - -/* -// known dwRegion values returned from sub_661316 (7.0.3.22210 x86 win), also referred by lua GetCurrentRegion -#define WOW_REGION_US 0x01 -#define WOW_REGION_KR 0x02 -#define WOW_REGION_EU 0x03 -#define WOW_REGION_TW 0x04 -#define WOW_REGION_CN 0x05 - -#define WOW_LOCALE_ENUS 0x00 -#define WOW_LOCALE_KOKR 0x01 -#define WOW_LOCALE_FRFR 0x02 -#define WOW_LOCALE_DEDE 0x03 -#define WOW_LOCALE_ZHCN 0x04 -#define WOW_LOCALE_ZHTW 0x05 -#define WOW_LOCALE_ESES 0x06 -#define WOW_LOCALE_ESMX 0x07 -#define WOW_LOCALE_RURU 0x08 -#define WOW_LOCALE_PTBR 0x0A -#define WOW_LOCALE_ITIT 0x0B - - // dwLocale is obtained from a WOW_LOCALE_* to CASC_LOCALE_BIT_* mapping (sub_6615D0 in 7.0.3.22210 x86 win) - // because (ENUS, ENGB) and (PTBR, PTPT) pairs share the same value on WOW_LOCALE_* enum - // dwRegion is used to distinguish them - if(dwRegion == WOW_REGION_EU) - { - // Is this english version of WoW? - if(dwLocale == CASC_LOCALE_BIT_ENUS) - { - LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENGB, bOverrideArchive, bAudioLocale); - LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENUS, bOverrideArchive, bAudioLocale); - return ERROR_SUCCESS; - } - - // Is this portuguese version of WoW? - if(dwLocale == CASC_LOCALE_BIT_PTBR) - { - LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTPT, bOverrideArchive, bAudioLocale); - LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTBR, bOverrideArchive, bAudioLocale); - } - } - else - LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, (1 << dwLocale), bOverrideArchive, bAudioLocale); -*/ - -static int ParseWowRootFile2( - TRootHandler_WoW6 * pRootHandler, - PARSE_ROOT pfnParseRoot, - LPBYTE pbRootFile, - LPBYTE pbRootFileEnd, - DWORD dwLocaleMask, - BYTE bAudioLocale) -{ - // Load the locale as-is - ParseWowRootFileInternal(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, dwLocaleMask, false, bAudioLocale); - - // If we wanted enGB, we also load enUS for the missing files - if(dwLocaleMask == CASC_LOCALE_ENGB) - ParseWowRootFileInternal(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, CASC_LOCALE_ENUS, false, bAudioLocale); - - if(dwLocaleMask == CASC_LOCALE_PTPT) - ParseWowRootFileInternal(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, CASC_LOCALE_PTBR, false, bAudioLocale); - - return ERROR_SUCCESS; -} - -// WoW.exe: 004146C7 (BuildManifest::Load) -static int ParseWowRootFile( - TRootHandler_WoW6 * pRootHandler, - PARSE_ROOT pfnParseRoot, - LPBYTE pbRootFile, - LPBYTE pbRootFileEnd, - DWORD dwLocaleMask) -{ - ParseWowRootFile2(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, dwLocaleMask, 0); - ParseWowRootFile2(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, dwLocaleMask, 1); - return ERROR_SUCCESS; -} - -//----------------------------------------------------------------------------- -// Implementation of WoW6 root file - -static int WowHandler_Insert( - TRootHandler_WoW6 * pRootHandler, - const char * szFileName, - LPBYTE pbEncodingKey) -{ - PCASC_FILE_ENTRY pFileEntry; - DWORD FileDataId = 0; - - // Don't let the number of items to overflow - if(pRootHandler->FileTable.ItemCount >= pRootHandler->FileTable.ItemCountMax) - return ERROR_NOT_ENOUGH_MEMORY; - - if (pRootHandler->FileDataIdLookupTable.ItemCount >= pRootHandler->FileDataIdLookupTable.ItemCountMax) - return ERROR_NOT_ENOUGH_MEMORY; - - // Insert the item to the linear file list - pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1); - if(pFileEntry != NULL) - { - Array_Insert(&pRootHandler->FileDataIdLookupTable, &pFileEntry, 1); - - // Get the file data ID of the previous item (0 if this is the first one) - if(pRootHandler->FileTable.ItemCount > 1) - FileDataId = pFileEntry[-1].FileDataId; - - // Fill-in the new entry - pFileEntry->EncodingKey = *(PENCODING_KEY)pbEncodingKey; - pFileEntry->FileNameHash = CalcFileNameHash(szFileName); - pFileEntry->FileDataId = FileDataId + 1; - pFileEntry->Locales = CASC_LOCALE_ALL; - - // Verify collisions (debug version only) - assert(Map_FindObject(pRootHandler->pRootMap, &pFileEntry->FileNameHash, NULL) == NULL); - - // Insert the entry to the map - Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash); - } - - return ERROR_SUCCESS; -} - -static LPBYTE WowHandler_Search( - TRootHandler_WoW6 * pRootHandler, - TCascSearch * pSearch, - PDWORD /* PtrFileSize */, - PDWORD PtrLocaleFlags, - PDWORD PtrFileDataId) -{ - PCASC_FILE_ENTRY pFileEntry; - - // Only if we have a listfile - if(pSearch->pCache != NULL) - { - // Keep going through the listfile - while(ListFile_GetNext(pSearch->pCache, pSearch->szMask, pSearch->szFileName, MAX_PATH)) - { - // Find the root entry - pFileEntry = FindRootEntry(pRootHandler->pRootMap, pSearch->szFileName, NULL); - if(pFileEntry != NULL) - { - // Give the caller the locale mask - if(PtrLocaleFlags != NULL) - PtrLocaleFlags[0] = pFileEntry->Locales; - if(PtrFileDataId != NULL) - PtrFileDataId[0] = pFileEntry->FileDataId; - return pFileEntry->EncodingKey.Value; - } - } - } - - // No more files - return NULL; -} - -static LPBYTE WowHandler_GetKey(TRootHandler_WoW6 * pRootHandler, const char * szFileName) -{ - PCASC_FILE_ENTRY pFileEntry; - DWORD FileDataId; - BYTE FileDataIdLE[4]; - - // Open by FileDataId. The file name must be as following: - // File########.unk, where '#' are hexa-decimal numbers (case insensitive). - // Extension is ignored in that case - if(IsFileDataIdName(szFileName)) - { - ConvertStringToBinary(szFileName + 4, 8, FileDataIdLE); - FileDataId = ConvertBytesToInteger_4(FileDataIdLE); - - pFileEntry = FindRootEntry(pRootHandler->FileDataIdLookupTable, FileDataId); - } - else - { - // Find by the file name hash - pFileEntry = FindRootEntry(pRootHandler->pRootMap, szFileName, NULL); - } - - return (pFileEntry != NULL) ? pFileEntry->EncodingKey.Value : NULL; -} - -static void WowHandler_EndSearch(TRootHandler_WoW6 * /* pRootHandler */, TCascSearch * pSearch) -{ - if(pSearch->pRootContext != NULL) - CASC_FREE(pSearch->pRootContext); - pSearch->pRootContext = NULL; -} - -static DWORD WowHandler_GetFileId(TRootHandler_WoW6 * pRootHandler, const char * szFileName) -{ - PCASC_FILE_ENTRY pFileEntry; - - // Find by the file name hash - pFileEntry = FindRootEntry(pRootHandler->pRootMap, szFileName, NULL); - return (pFileEntry != NULL) ? pFileEntry->FileDataId : 0; -} - -static void WowHandler_Close(TRootHandler_WoW6 * pRootHandler) -{ - if(pRootHandler != NULL) - { - Array_Free(&pRootHandler->FileTable); - Array_Free(&pRootHandler->FileDataIdLookupTable); - Map_Free(pRootHandler->pRootMap); - CASC_FREE(pRootHandler); - } -} - -#ifdef _DEBUG -static void TRootHandlerWoW6_Dump( - TCascStorage * hs, - TDumpContext * dc, // Pointer to an opened file - LPBYTE pbRootFile, - DWORD cbRootFile, - const TCHAR * szListFile, - int nDumpLevel) -{ - PCASC_ENCODING_ENTRY pEncodingEntry; - CASC_ROOT_BLOCK BlockInfo; - PLISTFILE_MAP pListMap; - QUERY_KEY EncodingKey; - LPBYTE pbRootFileEnd = pbRootFile + cbRootFile; - LPBYTE pbFilePointer; - char szOneLine[0x100]; - DWORD i; - - // Create the listfile map - pListMap = ListFile_CreateMap(szListFile); - - // Dump the root entries - for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; ) - { - // Validate the root block - pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd); - if(pbFilePointer == NULL) - break; - - // Dump the locale block - dump_print(dc, "Flags: %08X Locales: %08X NumberOfFiles: %u\n" - "=========================================================\n", - BlockInfo.pLocaleBlockHdr->Flags, - BlockInfo.pLocaleBlockHdr->Locales, - BlockInfo.pLocaleBlockHdr->NumberOfFiles); - - // Dump the hashes and encoding keys - for(i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++) - { - // Dump the entry - dump_print(dc, "%08X %08X-%08X %s %s\n", - (DWORD)(BlockInfo.FileDataIds[i]), - (DWORD)(BlockInfo.pRootEntries[i].FileNameHash >> 0x20), - (DWORD)(BlockInfo.pRootEntries[i].FileNameHash), - StringFromMD5(BlockInfo.pRootEntries[i].EncodingKey.Value, szOneLine), - ListFile_FindName(pListMap, BlockInfo.pRootEntries[i].FileNameHash)); - - // Find the encoding entry in the encoding table - if(nDumpLevel >= DUMP_LEVEL_ENCODING_FILE) - { - EncodingKey.pbData = BlockInfo.pRootEntries[i].EncodingKey.Value; - EncodingKey.cbData = MD5_HASH_SIZE; - pEncodingEntry = FindEncodingEntry(hs, &EncodingKey, NULL); - CascDumpEncodingEntry(hs, dc, pEncodingEntry, nDumpLevel); - } - } - - // Put extra newline - dump_print(dc, "\n"); - } - - ListFile_FreeMap(pListMap); -} -#endif - -//----------------------------------------------------------------------------- -// Public functions - -int RootHandler_CreateWoW6(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask) -{ - TRootHandler_WoW6 * pRootHandler; - LPBYTE pbRootFileEnd = pbRootFile + cbRootFile; - int nError; - - // Verify the size - if(pbRootFile == NULL || cbRootFile <= sizeof(PFILE_LOCALE_BLOCK)) - nError = ERROR_FILE_CORRUPT; - - // Allocate the root handler object - hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_WoW6, 1); - if(pRootHandler == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Fill-in the handler functions - memset(pRootHandler, 0, sizeof(TRootHandler_WoW6)); - pRootHandler->Insert = (ROOT_INSERT)WowHandler_Insert; - pRootHandler->Search = (ROOT_SEARCH)WowHandler_Search; - pRootHandler->EndSearch = (ROOT_ENDSEARCH)WowHandler_EndSearch; - pRootHandler->GetKey = (ROOT_GETKEY)WowHandler_GetKey; - pRootHandler->Close = (ROOT_CLOSE)WowHandler_Close; - pRootHandler->GetFileId = (ROOT_GETFILEID)WowHandler_GetFileId; - -#ifdef _DEBUG - pRootHandler->Dump = TRootHandlerWoW6_Dump; // Support for ROOT file dump -#endif // _DEBUG - - // Count the files that are going to be loaded - ParseWowRootFile(pRootHandler, ParseRoot_CountFiles, pbRootFile, pbRootFileEnd, dwLocaleMask); - pRootHandler->dwTotalFileCount += CASC_EXTRA_FILES; - - // Create linear table that will contain all root items - nError = Array_Create(&pRootHandler->FileTable, CASC_FILE_ENTRY, pRootHandler->dwTotalFileCount); - if(nError != ERROR_SUCCESS) - return nError; - - // Create sorted table that will contain all root items to lookup by FileDataId - nError = Array_Create(&pRootHandler->FileDataIdLookupTable, PCASC_FILE_ENTRY, pRootHandler->dwTotalFileCount); - if (nError != ERROR_SUCCESS) - return nError; - - // Create the map of FileHash ->FileEntry - pRootHandler->pRootMap = Map_Create(pRootHandler->dwTotalFileCount, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_ENTRY, FileNameHash)); - if(pRootHandler->pRootMap == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Parse the root file again and insert all files to the map - ParseWowRootFile(pRootHandler, ParseRoot_AddRootEntries, pbRootFile, pbRootFileEnd, dwLocaleMask); - - // Sort entries by FileDataId for searches - qsort_pointer_array((void**)pRootHandler->FileDataIdLookupTable.ItemArray, pRootHandler->FileDataIdLookupTable.ItemCount, &FileDataIdCompare, NULL); - return ERROR_SUCCESS; -} diff --git a/dep/CascLib/src/CascStructs.h b/dep/CascLib/src/CascStructs.h new file mode 100644 index 00000000000..f67da810c9e --- /dev/null +++ b/dep/CascLib/src/CascStructs.h @@ -0,0 +1,255 @@ +/*****************************************************************************/ +/* CascStructs.h Copyright (c) Ladislav Zezula 2019 */ +/*---------------------------------------------------------------------------*/ +/* On-disk structures for CASC storages */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 28.04.19 1.00 Lad The first version of CascStructs.h */ +/*****************************************************************************/ + +#ifndef __CASC_STRUCTS_H__ +#define __CASC_STRUCTS_H__ + +//----------------------------------------------------------------------------- +// Common definitions + +#define CASC_INDEX_COUNT 0x10 // Number of index files +#define CASC_CKEY_SIZE 0x10 // Size of the content key +#define CASC_EKEY_SIZE 0x09 // Size of the encoded key +#define CASC_MAX_DATA_FILES 0x100 // Maximum number of data files + +//----------------------------------------------------------------------------- +// The index files structures + +#define FILE_INDEX_PAGE_SIZE 0x200 // Number of bytes in one page of EKey items + +// Structure describing the 32-bit block size and 32-bit Jenkins hash of the block +typedef struct _BLOCK_SIZE_AND_HASH +{ + DWORD cbBlockSize; + DWORD dwBlockHash; + +} BLOCK_SIZE_AND_HASH, *PBLOCK_SIZE_AND_HASH; + +// Checksum describing a block in the index file (v2) +typedef struct _FILE_INDEX_GUARDED_BLOCK +{ + DWORD BlockSize; + DWORD BlockHash; + +} FILE_INDEX_GUARDED_BLOCK, *PFILE_INDEX_GUARDED_BLOCK; + +// Structure of the header of the index files version 1 +typedef struct _FILE_INDEX_HEADER_V1 +{ + USHORT IndexVersion; // Must be 0x05 + BYTE BucketIndex; // The bucket index of this file; should be the same as the first byte of the hex filename. + BYTE align_3; + DWORD field_4; + ULONGLONG field_8; + ULONGLONG SegmentSize; // Size of one data segment (aka data.### file) + BYTE EncodedSizeLength; // Length, in bytes, of the EncodedSize in the EKey entry + BYTE StorageOffsetLength; // Length, in bytes, of the StorageOffset field in the EKey entry + BYTE EKeyLength; // Length of the encoded key (bytes) + BYTE FileOffsetBits; // Number of bits of the archive file offset in StorageOffset field. Rest is data segment index + DWORD EKeyCount1; + DWORD EKeyCount2; + DWORD KeysHash1; + DWORD KeysHash2; + DWORD HeaderHash; +} FILE_INDEX_HEADER_V1, *PFILE_INDEX_HEADER_V1; + +typedef struct _FILE_INDEX_HEADER_V2 +{ + USHORT IndexVersion; // Must be 0x07 + BYTE BucketIndex; // The bucket index of this file; should be the same as the first byte of the hex filename. + BYTE ExtraBytes; // Unknown; must be 0 + BYTE EncodedSizeLength; // Length, in bytes, of the EncodedSize in the EKey entry + BYTE StorageOffsetLength; // Length, in bytes, of the StorageOffset field in the EKey entry + BYTE EKeyLength; // Length of the encoded key (bytes) + BYTE FileOffsetBits; // Number of bits of the archive file offset in StorageOffset field. Rest is data segment index + ULONGLONG SegmentSize; // Size of one data segment (aka data.### file) + +} FILE_INDEX_HEADER_V2, *PFILE_INDEX_HEADER_V2; + +// The EKey entry from the ".idx" files. Note that the lengths are optional and controlled by the FILE_INDEX_HEADER_Vx +typedef struct _FILE_EKEY_ENTRY +{ + BYTE EKey[CASC_EKEY_SIZE]; // The first 9 bytes of the encoded key + BYTE FileOffsetBE[5]; // Index of data file and offset within (big endian). + BYTE EncodedSize[4]; // Encoded size (big endian). This is the size of encoded header, all file frame headers and all file frames +} FILE_EKEY_ENTRY, *PFILE_EKEY_ENTRY; + +//----------------------------------------------------------------------------- +// The archive index (md5.index) files structures +// https://wowdev.wiki/TACT#CDN_File_Organization + +template <int CHKSUM_LENGTH> +struct FILE_INDEX_FOOTER +{ + BYTE TocHash[MD5_HASH_SIZE]; // Client tries to read with 0x10, then backs off in size when smaller + BYTE Version; // Version of the index header + BYTE Reserved[2]; // Length, in bytes, of the file offset field + 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 FooterHashBytes; // Normally 8, <= 0x10 + BYTE ElementCount[4]; // BigEndian in _old_ versions (e.g. 18179) + BYTE FooterHash[CHKSUM_LENGTH]; +}; + +//----------------------------------------------------------------------------- +// The ENCODING manifest structures +// +// The ENCODING file is in the form of: +// * File header. Fixed length. +// * Encoding Specification (ESpec) in the string form. Length is stored in FILE_ENCODING_HEADER::ESpecBlockSize +// https://wowdev.wiki/CASC#Encoding +// https://wowdev.wiki/TACT#Encoding_table +// + +#define FILE_MAGIC_ENCODING 0x4E45 // 'EN' + +// File header of the ENCODING manifest +typedef struct _FILE_ENCODING_HEADER +{ + USHORT Magic; // FILE_MAGIC_ENCODING ('EN') + BYTE Version; // Expected to be 1 by CascLib + BYTE CKeyLength; // The content key length in ENCODING file. Usually 0x10 + BYTE EKeyLength; // The encoded key length in ENCODING file. Usually 0x10 + BYTE CKeyPageSize[2]; // Size of the CKey page, in KB (big-endian) + BYTE EKeyPageSize[2]; // Size of the EKey page, in KB (big-endian) + BYTE CKeyPageCount[4]; // Number of CKey pages in the table (big endian) + BYTE EKeyPageCount[4]; // Number of EKey pages in the table (big endian) + BYTE field_11; // Asserted to be zero by the agent + BYTE ESpecBlockSize[4]; // Size of the ESpec string block + +} FILE_ENCODING_HEADER, *PFILE_ENCODING_HEADER; + +// Page header of the ENCODING manifest +typedef struct _FILE_CKEY_PAGE +{ + BYTE FirstKey[MD5_HASH_SIZE]; // The first CKey/EKey in the segment + BYTE SegmentHash[MD5_HASH_SIZE]; // MD5 hash of the entire segment + +} FILE_CKEY_PAGE, *PFILE_CKEY_PAGE; + +// Single entry in the page +typedef struct _FILE_CKEY_ENTRY +{ + USHORT EKeyCount; // Number of EKeys + BYTE ContentSize[4]; // Content file size (big endian) + BYTE CKey[CASC_CKEY_SIZE]; // Content key. This is MD5 of the file content + BYTE EKey[CASC_CKEY_SIZE]; // Encoded key. This is (trimmed) MD5 hash of the file header, containing MD5 hashes of all the logical blocks of the file + +} FILE_CKEY_ENTRY, *PFILE_CKEY_ENTRY; + +typedef struct _FILE_ESPEC_ENTRY +{ + BYTE ESpecKey[MD5_HASH_SIZE]; // The ESpec key of the file + BYTE ESpecIndexBE[4]; // Index of ESPEC entry, assuming zero-terminated strings (big endian) + BYTE FileSizeBE[5]; // Size of the encoded version of the file (big endian) + +} FILE_ESPEC_ENTRY, *PFILE_ESPEC_ENTRY; + +//----------------------------------------------------------------------------- +// The DOWNLOAD manifest structures +// +// See https://wowdev.wiki/TACT#Download_manifest +// + +#define FILE_MAGIC_DOWNLOAD 0x4C44 // 'DL' + +// File header of the DOWNLOAD manifest +typedef struct _FILE_DOWNLOAD_HEADER +{ + USHORT Magic; // FILE_MAGIC_DOWNLOAD ('DL') + BYTE Version; // Expected to be 1 by CascLib + BYTE EKeyLength; // The content key length in DOWNLOAD file. Expected to be 0x10 + BYTE EntryHasChecksum; // If nonzero, then the entry has checksum. + BYTE EntryCount[4]; // Number of entries (big-endian) + BYTE TagCount[2]; // Number of tag entries (big endian) + + // Version 2 or newer + BYTE FlagByteSize; // Number of flag bytes + + // Verion 3 or newer + BYTE BasePriority; + BYTE Unknown2[3]; + +} FILE_DOWNLOAD_HEADER, *PFILE_DOWNLOAD_HEADER; + +typedef struct _FILE_DOWNLOAD_ENTRY +{ + BYTE EKey[MD5_HASH_SIZE]; // Encoding key (variable length) + BYTE FileSize[5]; // File size + BYTE Priority; + + // DWORD Checksum [optional] + // BYTE Flags; + +} FILE_DOWNLOAD_ENTRY, *PFILE_DOWNLOAD_ENTRY; + +//----------------------------------------------------------------------------- +// The INSTALL manifest structures +// +// See https://wowdev.wiki/TACT#Install_manifest +// + +#define FILE_MAGIC_INSTALL 0x4E49 // 'IN' + +// File header of the INSTALL manifest +typedef struct _FILE_INSTALL_HEADER +{ + USHORT Magic; // FILE_MAGIC_INSTALL ('DL') + BYTE Version; // Expected to be 1 by CascLib + BYTE EKeyLength; // The content key length in INSTALL file. Expected to be 0x10 + BYTE TagCount[2]; // Number of tag entries (big endian) + BYTE EntryCount[4]; // Number of entries (big-endian) + +} FILE_INSTALL_HEADER, *PFILE_INSTALL_HEADER; + +//----------------------------------------------------------------------------- +// Data file structures + +#define BLTE_HEADER_SIGNATURE 0x45544C42 // 'BLTE' header in the data files +#define BLTE_HEADER_DELTA 0x1E // Distance of BLTE header from begin of the header area +#define MAX_ENCODED_HEADER 0x1000 // Starting size for the frame headers + +typedef struct _BLTE_HEADER +{ + BYTE Signature[4]; // Must be "BLTE" + BYTE HeaderSize[4]; // Header size in bytes (big endian) + BYTE MustBe0F; // Must be 0x0F. Optional, only if HeaderSize != 0 + BYTE FrameCount[3]; // Frame count (big endian). Optional, only if HeaderSize != 0 +} BLTE_HEADER, *PBLTE_HEADER; + +typedef struct _BLTE_ENCODED_HEADER +{ + // Header span. + ENCODED_KEY EKey; // Encoded key of the data beginning with "BLTE" (byte-reversed) + DWORD EncodedSize; // Encoded size of the data data beginning with "BLTE" (little endian) + BYTE field_14; // Seems to be 1 if the header span has no data + BYTE field_15; // Hardcoded to zero (Agent.exe 2.15.0.6296: 01370000->0148E2AA) + BYTE JenkinsHash[4]; // Jenkins hash (hashlittle2) of the preceding fields (EKey + EncodedSize + field_14 + field_15) (little endian) + BYTE Checksum[4]; // Checksum of the previous part. See "VerifyHeaderSpan()" for more information. + + // BLTE header. Always present. + BYTE Signature[4]; // Must be "BLTE" + BYTE HeaderSize[4]; // Header size in bytes (big endian) + BYTE MustBe0F; // Must be 0x0F. Optional, only if HeaderSize != 0 + BYTE FrameCount[3]; // Frame count (big endian). Optional, only if HeaderSize != 0 +} BLTE_ENCODED_HEADER, *PBLTE_ENCODED_HEADER; + +typedef struct _BLTE_FRAME +{ + BYTE EncodedSize[4]; // Encoded frame size (big endian) + BYTE ContentSize[4]; // Content frame size (big endian) + CONTENT_KEY FrameHash; // Hash of the encoded frame + +} BLTE_FRAME, *PBLTE_FRAME; + +#endif // __CASC_STRUCTS_H__ + diff --git a/dep/CascLib/src/DllMain.c b/dep/CascLib/src/DllMain.c new file mode 100644 index 00000000000..f7d0ce3063d --- /dev/null +++ b/dep/CascLib/src/DllMain.c @@ -0,0 +1,24 @@ +/*****************************************************************************/ +/* DllMain.c Copyright (c) Ladislav Zezula 2015 */ +/*---------------------------------------------------------------------------*/ +/* Description: DllMain for the CascLib.dll library */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 26.10.15 1.00 Lad The first version of DllMain.c */ +/*****************************************************************************/ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +//----------------------------------------------------------------------------- +// DllMain + +BOOL WINAPI DllMain(HINSTANCE hInst, DWORD dwReason, LPVOID lpReserved) +{ + UNREFERENCED_PARAMETER(hInst); + UNREFERENCED_PARAMETER(dwReason); + UNREFERENCED_PARAMETER(lpReserved); + + return TRUE; +} diff --git a/dep/CascLib/src/common/Array.h b/dep/CascLib/src/common/Array.h new file mode 100644 index 00000000000..1dc96b7be8e --- /dev/null +++ b/dep/CascLib/src/common/Array.h @@ -0,0 +1,208 @@ +/*****************************************************************************/ +/* Array.h Copyright (c) Ladislav Zezula 2015 */ +/*---------------------------------------------------------------------------*/ +/* Common array implementation */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 30.10.15 1.00 Lad The first version of DynamicArray.h */ +/* 10.08.18 1.00 Lad CLASS-ified, renamed to Array.h */ +/*****************************************************************************/ + +#ifndef __CASC_ARRAY_H__ +#define __CASC_ARRAY_H__ + +//----------------------------------------------------------------------------- +// Structures + +class CASC_ARRAY +{ + public: + + CASC_ARRAY() + { + m_pItemArray = NULL; + m_ItemCountMax = 0; + m_ItemCount = 0; + m_ItemSize = 0; + } + + ~CASC_ARRAY() + { + Free(); + } + + // Creates an array with a custom element type + template<typename TYPE> + int Create(size_t ItemCountMax) + { + return Create(sizeof(TYPE), ItemCountMax); + } + + // Creates an array with a custom element size + int Create(size_t ItemSize, size_t ItemCountMax) + { + // Create the array + if ((m_pItemArray = CASC_ALLOC(BYTE, ItemSize * ItemCountMax)) == NULL) + return ERROR_NOT_ENOUGH_MEMORY; + + m_ItemCountMax = ItemCountMax; + m_ItemCount = 0; + m_ItemSize = ItemSize; + return ERROR_SUCCESS; + } + + // Inserts one or more items; returns pointer to the first inserted item + void * Insert(size_t NewItemCount) + { + void * pNewItems; + + // Try to enlarge the buffer, if needed + if (!EnlargeArray(m_ItemCount + NewItemCount)) + return NULL; + pNewItems = m_pItemArray + (m_ItemCount * m_ItemSize); + + // Increment the size of the array + m_ItemCount += NewItemCount; + + // Return pointer to the new item + return pNewItems; + } + + // Inserts one or more items; returns pointer to the first inserted item + void * Insert(const void * NewItems, size_t NewItemCount) + { + void * pNewItem = Insert(NewItemCount); + + // Copy the item(s) to the array, if any + if (pNewItem && NewItems) + memcpy(pNewItem, NewItems, (NewItemCount * m_ItemSize)); + return pNewItem; + } + + // Returns an item at a given index + void * ItemAt(size_t ItemIndex) + { + return (ItemIndex < m_ItemCount) ? (m_pItemArray + (ItemIndex * m_ItemSize)) : NULL; + } + + void * LastItem() + { + return m_pItemArray + (m_ItemCount * m_ItemSize); + } + + // Inserts an item at a given index. If there is not enough items in the array, + // the array will be enlarged. Should any gaps to be created, the function will zero them + void * InsertAt(size_t ItemIndex) + { + LPBYTE pbLastItem; + LPBYTE pbNewItem; + + // Make sure we have array large enough + if(!EnlargeArray(ItemIndex + 1)) + return NULL; + + // Get the items range + pbLastItem = m_pItemArray + (m_ItemCount * m_ItemSize); + pbNewItem = m_pItemArray + (ItemIndex * m_ItemSize); + m_ItemCount = CASCLIB_MAX(m_ItemCount, ItemIndex+1); + + // If we inserted an item past the current end, we need to clear the items in-between + if (pbNewItem > pbLastItem) + { + memset(pbLastItem, 0, (pbNewItem - pbLastItem)); + m_ItemCount = ItemIndex + 1; + } + + return pbNewItem; + } + + // Returns index of an item + size_t IndexOf(const void * pItem) + { + LPBYTE pbItem = (LPBYTE)pItem; + + assert((m_pItemArray <= pbItem) && (pbItem <= m_pItemArray + (m_ItemCount * m_ItemSize))); + assert(((pbItem - m_pItemArray) % m_ItemSize) == 0); + + return ((pbItem - m_pItemArray) / m_ItemSize); + } + + void * ItemArray() + { + return m_pItemArray; + } + + size_t ItemCount() + { + return m_ItemCount; + } + + size_t ItemCountMax() + { + return m_ItemCountMax; + } + + size_t ItemSize() + { + return m_ItemSize; + } + + bool IsInitialized() + { + return (m_pItemArray && m_ItemCountMax); + } + + // Invalidates the entire array, but keeps memory allocated + void Reset() + { + memset(m_pItemArray, 0, m_ItemCountMax * m_ItemSize); + m_ItemCount = 0; + } + + // Frees the array + void Free() + { + CASC_FREE(m_pItemArray); + m_ItemCountMax = m_ItemCount = m_ItemSize = 0; + } + + protected: + + bool EnlargeArray(size_t NewItemCount) + { + LPBYTE NewItemArray; + size_t ItemCountMax; + + // We expect the array to be already allocated + assert(m_pItemArray != NULL); + assert(m_ItemCountMax != 0); + + // Shall we enlarge the table? + if (NewItemCount > m_ItemCountMax) + { + // Calculate new table size + ItemCountMax = m_ItemCountMax; + while (ItemCountMax < NewItemCount) + ItemCountMax = ItemCountMax << 1; + + // Allocate new table + NewItemArray = CASC_REALLOC(BYTE, m_pItemArray, (ItemCountMax * m_ItemSize)); + if (NewItemArray == NULL) + return false; + + // Set the new table size + m_ItemCountMax = ItemCountMax; + m_pItemArray = NewItemArray; + } + + return true; + } + + LPBYTE m_pItemArray; // Pointer to item array + size_t m_ItemCountMax; // Maximum item count + size_t m_ItemCount; // Current item count + size_t m_ItemSize; // Size of an item +}; + +#endif // __CASC_ARRAY__ diff --git a/dep/CascLib/src/common/Common.cpp b/dep/CascLib/src/common/Common.cpp index a0455b7c659..d545f52f84a 100644 --- a/dep/CascLib/src/common/Common.cpp +++ b/dep/CascLib/src/common/Common.cpp @@ -65,40 +65,230 @@ unsigned char IntToHexChar[] = "0123456789abcdef"; // GetLastError/SetLastError support for non-Windows platform #ifndef PLATFORM_WINDOWS -static int nLastError = ERROR_SUCCESS; +static DWORD dwLastError = ERROR_SUCCESS; -int GetLastError() +DWORD GetLastError() { - return nLastError; + return dwLastError; } -void SetLastError(int nError) +void SetLastError(DWORD dwErrCode) { - nLastError = nError; + dwLastError = dwErrCode; } #endif //----------------------------------------------------------------------------- -// String manipulation +// Overloaded "new" and "delete" operators -void CopyString(char * szTarget, const char * szSource, size_t cchLength) +void * operator new(size_t size) { - memcpy(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; + return CASC_ALLOC(BYTE, size); } -void CopyString(wchar_t * szTarget, const char * szSource, size_t cchLength) +void * operator new[](size_t size) { - mbstowcs(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; + return CASC_ALLOC(BYTE, size); } -void CopyString(char * szTarget, const wchar_t * szSource, size_t cchLength) +void operator delete(void * ptr) { - wcstombs(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; + CASC_FREE(ptr); } +void operator delete[](void * ptr) +{ + CASC_FREE(ptr); +} + +// For some reason, VS2015 needs this +void operator delete(void * ptr, size_t) +{ + CASC_FREE(ptr); +} + +//----------------------------------------------------------------------------- +// Linear data stream manipulation + +LPBYTE CaptureInteger32(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue) +{ + // Is there enough data? + if((pbDataPtr + sizeof(DWORD)) > pbDataEnd) + return NULL; + + // Give data + PtrValue[0] = *(PDWORD)pbDataPtr; + + // Return the pointer to data following after the integer + return pbDataPtr + sizeof(DWORD); +} + +LPBYTE CaptureInteger32_BE(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue) +{ + // Is there enough data? + if((pbDataPtr + sizeof(DWORD)) > pbDataEnd) + return NULL; + + // Convert data from Little endian to + PtrValue[0] = ConvertBytesToInteger_4(pbDataPtr); + + // Return the pointer to data following after the integer + return pbDataPtr + sizeof(DWORD); +} + +LPBYTE CaptureByteArray(LPBYTE pbDataPtr, LPBYTE pbDataEnd, size_t nLength, LPBYTE pbOutput) +{ + // Is there enough data? + if((pbDataPtr + nLength) > pbDataEnd) + return NULL; + + // Give data + memcpy(pbOutput, pbDataPtr, nLength); + + // Return the pointer to data following after the integer + return pbDataPtr + nLength; +} + +LPBYTE CaptureContentKey(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PCONTENT_KEY * PtrCKey) +{ + // Is there enough data? + if((pbDataPtr + sizeof(CONTENT_KEY)) > pbDataEnd) + return NULL; + + // Give data + PtrCKey[0] = (PCONTENT_KEY)pbDataPtr; + + // Return the pointer to data following after the integer + return pbDataPtr + sizeof(CONTENT_KEY); +} + +LPBYTE CaptureArray_(LPBYTE pbDataPtr, LPBYTE pbDataEnd, LPBYTE * PtrArray, size_t ItemSize, size_t ItemCount) +{ + size_t ArraySize = ItemSize * ItemCount; + + // Is there enough data? + if((pbDataPtr + ArraySize) > pbDataEnd) + return NULL; + + // Give data + PtrArray[0] = pbDataPtr; + + // Return the pointer to data following after the array + return pbDataPtr + ArraySize; +} + +//----------------------------------------------------------------------------- +// String copying and conversion + +void CascStrCopy(char * szTarget, size_t cchTarget, const char * szSource, size_t cchSource) +{ + size_t cchToCopy; + + if (cchTarget > 0) + { + // Make sure we know the length + if (cchSource == -1) + cchSource = strlen(szSource); + cchToCopy = CASCLIB_MIN((cchTarget - 1), cchSource); + + // Copy the string + memcpy(szTarget, szSource, cchToCopy); + szTarget[cchToCopy] = 0; + } +} + +void CascStrCopy(char * szTarget, size_t cchTarget, const wchar_t * szSource, size_t cchSource) +{ + size_t cchToCopy; + + if (cchTarget > 0) + { + // Make sure we know the length + if (cchSource == -1) + cchSource = wcslen(szSource); + cchToCopy = CASCLIB_MIN((cchTarget - 1), cchSource); + + wcstombs(szTarget, szSource, cchToCopy); + szTarget[cchToCopy] = 0; + } +} + +void CascStrCopy(wchar_t * szTarget, size_t cchTarget, const char * szSource, size_t cchSource) +{ + size_t cchToCopy; + + if (cchTarget > 0) + { + // Make sure we know the length + if (cchSource == -1) + cchSource = strlen(szSource); + cchToCopy = CASCLIB_MIN((cchTarget - 1), cchSource); + + mbstowcs(szTarget, szSource, cchToCopy); + szTarget[cchToCopy] = 0; + } +} + +void CascStrCopy(wchar_t * szTarget, size_t cchTarget, const wchar_t * szSource, size_t cchSource) +{ + size_t cchToCopy; + + if (cchTarget > 0) + { + // Make sure we know the length + if (cchSource == -1) + cchSource = wcslen(szSource); + cchToCopy = CASCLIB_MIN((cchTarget - 1), cchSource); + + memcpy(szTarget, szSource, cchToCopy * sizeof(wchar_t)); + szTarget[cchToCopy] = 0; + } +} + +//----------------------------------------------------------------------------- +// Safe version of s(w)printf + +size_t CascStrPrintf(char * buffer, size_t nCount, const char * format, ...) +{ + char * buffend; + va_list argList; + + // Start the argument list + va_start(argList, format); + +#ifdef PLATFORM_WINDOWS + StringCchVPrintfExA(buffer, nCount, &buffend, NULL, 0, format, argList); +#else + buffend = buffer + vsnprintf(buffer, nCount, format, argList); +#endif + + // End the argument list + va_end(argList); + return (buffend - buffer); +} + +size_t CascStrPrintf(wchar_t * buffer, size_t nCount, const wchar_t * format, ...) +{ + wchar_t * buffend; + va_list argList; + + // Start the argument list + va_start(argList, format); + +#ifdef PLATFORM_WINDOWS + StringCchVPrintfExW(buffer, nCount, &buffend, NULL, 0, format, argList); +#else + buffend = buffer + vswprintf(buffer, nCount, format, argList); +#endif + + // End the argument list + va_end(argList); + return (buffend - buffer); +} + +//----------------------------------------------------------------------------- +// String allocation + char * CascNewStr(const char * szString, size_t nCharsToReserve) { char * szNewString = NULL; @@ -137,102 +327,144 @@ wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve) return szNewString; } -TCHAR * CascNewStrFromAnsi(const char * szBegin, const char * szEnd) +template <typename XCHAR> +TCHAR * AppendPathFragment(TCHAR * szBuffer, TCHAR * szBufferEnd, const XCHAR * szPath, char chSeparator, bool bFirstFragment = false) { - TCHAR * szNewString = NULL; - - // Only if the entry is valid - if(szBegin != NULL && szEnd > szBegin) + // The "Path" must not be empty + if(szPath && szPath[0]) { - // Allocate and copy the string - szNewString = CASC_ALLOC(TCHAR, (szEnd - szBegin + 1)); - if(szNewString != NULL) - CopyString(szNewString, szBegin, (szEnd - szBegin)); + // 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 the string - return szNewString; + return szBuffer; } -TCHAR * CombinePath(const TCHAR * szDirectory, const TCHAR * szSubDir) +TCHAR * GetLastPathPart(TCHAR * szWorkPath) { - TCHAR * szFullPath = NULL; - TCHAR * szPathPtr; - size_t nLength1 = 0; - size_t nLength2 = 0; + size_t nLength = _tcslen(szWorkPath); - // Calculate lengths of each part - if(szDirectory != NULL) - { - // Get the length of the directory - nLength1 = _tcslen(szDirectory); + // Go one character back + if(nLength > 0) + nLength--; - // Cut all ending backslashes - while(nLength1 > 0 && (szDirectory[nLength1 - 1] == _T('\\') || szDirectory[nLength1 - 1] == _T('/'))) - nLength1--; - } + // Cut ending (back)slashes, if any + while(nLength > 0 && (szWorkPath[nLength] == _T('\\') || szWorkPath[nLength] == _T('/'))) + nLength--; - if(szSubDir != NULL) + // Cut the last path part + while(nLength > 0) { - // Cut all leading backslashes - while(szSubDir[0] == _T(PATH_SEPARATOR)) - szSubDir++; - - // Get the length of the subdir - nLength2 = _tcslen(szSubDir); - - // Cut all ending backslashes - while(nLength2 > 0 && szSubDir[nLength2 - 1] == _T(PATH_SEPARATOR)) - nLength2--; - } - - // Allocate space for the full path - szFullPath = szPathPtr = CASC_ALLOC(TCHAR, nLength1 + nLength2 + 2); - if(szFullPath != NULL) - { - // Copy the directory - if(szDirectory != NULL && nLength1 != 0) + // End of path? + if(szWorkPath[nLength] == _T('\\') || szWorkPath[nLength] == _T('/')) { - memcpy(szPathPtr, szDirectory, (nLength1 * sizeof(TCHAR))); - szPathPtr += nLength1; + return szWorkPath + nLength; } - // Copy the sub-directory - if(szSubDir != NULL && nLength2 != 0) - { - // Append backslash to the previous one - if(szPathPtr > szFullPath) - *szPathPtr++ = _T(PATH_SEPARATOR); + // Go one character back + nLength--; + } - // Copy the string - memcpy(szPathPtr, szSubDir, (nLength2 * sizeof(TCHAR))); - szPathPtr += nLength2; - } + return NULL; +} - // Terminate the string - szPathPtr[0] = 0; - } +bool CutLastPathPart(TCHAR * szWorkPath) +{ + // Get the last part of the path + szWorkPath = GetLastPathPart(szWorkPath); + if(szWorkPath == NULL) + return false; - return szFullPath; + szWorkPath[0] = 0; + return true; } -TCHAR * CombinePathAndString(const TCHAR * szPath, const char * szString, size_t nLength) +TCHAR * CombinePath(const TCHAR * szDirectory, const TCHAR * szSubDir) { + TCHAR * szFullPathEnd; TCHAR * szFullPath = NULL; - TCHAR * szSubDir; + TCHAR * szPathPtr; + size_t nLength1 = (szDirectory != NULL) ? _tcslen(szDirectory) : 0; + size_t nLength2 = (szSubDir != NULL) ? _tcslen(szSubDir) : 0; - // Create the subdir string - szSubDir = CASC_ALLOC(TCHAR, nLength + 1); - if(szSubDir != NULL) + // Allocate the entire buffer + szFullPath = szPathPtr = CASC_ALLOC(TCHAR, nLength1 + nLength2 + 2); + szFullPathEnd = szFullPath + nLength1 + nLength2 + 1; + if(szFullPath != NULL) { - CopyString(szSubDir, szString, nLength); - szFullPath = CombinePath(szPath, szSubDir); - CASC_FREE(szSubDir); + szPathPtr = AppendPathFragment(szPathPtr, szFullPathEnd, szDirectory, PATH_SEP_CHAR, true); + szPathPtr = AppendPathFragment(szPathPtr, szFullPathEnd, szSubDir, PATH_SEP_CHAR); } return szFullPath; } +size_t CombineFilePath(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szPath, const TCHAR * szSubPath1, const TCHAR * szSubPath2) +{ + TCHAR * szSaveBuffer = szBuffer; + TCHAR * szBufferEnd = szBuffer + nMaxChars - 1; + + // Append all three parts and return length + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szPath, PATH_SEP_CHAR, true); + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubPath1, PATH_SEP_CHAR); + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubPath2, PATH_SEP_CHAR); + return (szBuffer - szSaveBuffer); +} + +size_t CombineUrlPath(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szHost, const TCHAR * szSubPath1, const TCHAR * szSubPath2) +{ + TCHAR * szSaveBuffer = szBuffer; + TCHAR * szBufferEnd = szBuffer + nMaxChars - 1; + + // Append all three parts and return length + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szHost, '/', true); + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubPath1, '/'); + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubPath2, '/'); + return (szBuffer - szSaveBuffer); +} + +size_t CreateCascSubdirectoryName(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szSubDir, const TCHAR * 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); + + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubDir, '/', true); + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szHashSubPath, '/'); + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szExtension, '/', true); + return (szBuffer - szSaveBuffer); +} + size_t NormalizeFileName(const unsigned char * NormTable, char * szNormName, const char * szFileName, size_t cchMaxChars) { char * szNormNameEnd = szNormName + cchMaxChars; @@ -257,19 +489,26 @@ size_t NormalizeFileName_LowerSlash(char * szNormName, const char * szFileName, return NormalizeFileName(AsciiToLowerTable_Slash, szNormName, szFileName, cchMaxChars); } -ULONGLONG CalcFileNameHash(const char * szFileName) +ULONGLONG CalcNormNameHash(const char * szNormName, size_t nLength) { - char szNormName[MAX_PATH+1]; uint32_t dwHashHigh = 0; uint32_t dwHashLow = 0; + + // Calculate the HASH value of the normalized file name + hashlittle2(szNormName, nLength, &dwHashHigh, &dwHashLow); + return ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow; +} + +ULONGLONG CalcFileNameHash(const char * szFileName) +{ + char szNormName[MAX_PATH+1]; size_t nLength; // Normalize the file name - convert to uppercase, slashes to backslashes nLength = NormalizeFileName_UpperBkSlash(szNormName, szFileName, MAX_PATH); - // Calculate the HASH value of the normalized file name - hashlittle2(szNormName, nLength, &dwHashHigh, &dwHashLow); - return ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow; + // Calculate hash from the normalized name + return CalcNormNameHash(szNormName, nLength); } int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue) @@ -373,13 +612,16 @@ char * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, char * szBuffer) { char * szSaveBuffer = szBuffer; - // Convert the string to the array of MD5 - // Copy the blob data as text - for(size_t i = 0; i < cbBinary; i++) + // Verify the binary pointer + if(pbBinary && cbBinary) { - *szBuffer++ = IntToHexChar[pbBinary[0] >> 0x04]; - *szBuffer++ = IntToHexChar[pbBinary[0] & 0x0F]; - pbBinary++; + // 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 @@ -395,39 +637,51 @@ char * StringFromMD5(LPBYTE md5, char * szBuffer) //----------------------------------------------------------------------------- // File name utilities -const wchar_t * GetPlainFileName(const wchar_t * szFileName) +bool IsFileDataIdName(const char * szFileName, DWORD & FileDataId) { - const wchar_t * szPlainName = szFileName; + BYTE BinaryValue[4]; - while(*szFileName != 0) + // File name must begin with "File", case insensitive + if(AsciiToUpperTable_BkSlash[szFileName[0]] == 'F' && + AsciiToUpperTable_BkSlash[szFileName[1]] == 'I' && + AsciiToUpperTable_BkSlash[szFileName[2]] == 'L' && + AsciiToUpperTable_BkSlash[szFileName[3]] == 'E') { - if(*szFileName == '\\' || *szFileName == '/') - szPlainName = szFileName + 1; - szFileName++; + // Then, 8 hexadecimal digits must follow + if(ConvertStringToBinary(szFileName + 4, 8, BinaryValue) == ERROR_SUCCESS) + { + // Must be followed by an extension or end-of-string + if(szFileName[0x0C] == 0 || szFileName[0x0C] == '.') + { + FileDataId = ConvertBytesToInteger_4(BinaryValue); + return (FileDataId != CASC_INVALID_ID); + } + } } - return szPlainName; + return false; } -const char * GetPlainFileName(const char * szFileName) +bool IsFileCKeyEKeyName(const char * szFileName, LPBYTE PtrKeyBuffer) { - const char * szPlainName = szFileName; + size_t nLength = strlen(szFileName); - while(*szFileName != 0) + if(nLength == MD5_STRING_SIZE) { - if(*szFileName == '\\' || *szFileName == '/') - szPlainName = szFileName + 1; - szFileName++; + if(ConvertStringToBinary(szFileName, MD5_STRING_SIZE, PtrKeyBuffer) == ERROR_SUCCESS) + { + return true; + } } - return szPlainName; + return false; } -bool CheckWildCard(const char * szString, const char * szWildCard) +bool CascCheckWildCard(const char * szString, const char * szWildCard) { const char * szWildCardPtr; - for(;;) + while(szWildCard && szWildCard[0]) { // If there is '?' in the wildcard, we skip one char while(szWildCard[0] == '?') @@ -455,7 +709,7 @@ bool CheckWildCard(const char * szString, const char * szWildCard) if(AsciiToUpperTable_BkSlash[szWildCardPtr[0]] == AsciiToUpperTable_BkSlash[szString[0]]) { - if(CheckWildCard(szString, szWildCardPtr)) + if(CascCheckWildCard(szString, szWildCardPtr)) return true; } } @@ -476,244 +730,43 @@ bool CheckWildCard(const char * szString, const char * szWildCard) return (szString[0] == 0) ? true : false; } } + return true; } //----------------------------------------------------------------------------- // Hashing functions -bool IsValidMD5(LPBYTE pbMd5) +bool CascIsValidMD5(LPBYTE pbMd5) { - BYTE BitSummary = 0; + PDWORD Int32Array = (PDWORD)pbMd5; - // The MD5 is considered invalid of it is zeroed - BitSummary |= pbMd5[0x00] | pbMd5[0x01] | pbMd5[0x02] | pbMd5[0x03] | pbMd5[0x04] | pbMd5[0x05] | pbMd5[0x06] | pbMd5[0x07]; - BitSummary |= pbMd5[0x08] | pbMd5[0x09] | pbMd5[0x0A] | pbMd5[0x0B] | pbMd5[0x0C] | pbMd5[0x0D] | pbMd5[0x0E] | pbMd5[0x0F]; - return (BitSummary != 0); + // The MD5 is considered invalid if it is zeroed + return (Int32Array[0] | Int32Array[1] | Int32Array[2] | Int32Array[3]) ? true : false; } -bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5) +bool CascVerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5) { - hash_state md5_state; + MD5_CTX md5_ctx; BYTE md5_digest[MD5_HASH_SIZE]; // Don't verify the block if the MD5 is not valid. - if(!IsValidMD5(expected_md5)) + if(!CascIsValidMD5(expected_md5)) return true; // Calculate the MD5 of the data block - md5_init(&md5_state); - md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock); - md5_done(&md5_state, md5_digest); + MD5_Init(&md5_ctx); + MD5_Update(&md5_ctx, pvDataBlock, cbDataBlock); + MD5_Final(md5_digest, &md5_ctx); // Does the MD5's match? return (memcmp(md5_digest, expected_md5, MD5_HASH_SIZE) == 0); } -void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash) -{ - hash_state md5_state; - - md5_init(&md5_state); - md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock); - md5_done(&md5_state, md5_hash); -} - -//----------------------------------------------------------------------------- -// We have our own qsort implementation, optimized for using array of pointers - -#define STKSIZ (8*sizeof(void*) - 2) - -#define SWAP_ENTRIES(index1, index2) \ -{ \ - temp = base[index1]; \ - base[index1] = base[index2]; \ - base[index2] = temp; \ -} - -void qsort_pointer_array(void ** base, size_t num, int (*compare)(const void *, const void *, const void *), const void * context) +void CascCalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash) { - size_t lo, hi; /* ends of sub-array currently sorting */ - size_t mid; /* points to middle of subarray */ - size_t loguy, higuy; /* traveling pointers for partition step */ - size_t size; /* size of the sub-array */ - size_t lostk[STKSIZ], histk[STKSIZ]; - void * temp; - int stkptr; /* stack for saving sub-array to be processed */ - - /* validation section */ - assert(base != NULL); - assert(compare != NULL); - - if (num < 2) - return; /* nothing to do */ - - stkptr = 0; /* initialize stack */ - - lo = 0; - hi = (num-1); /* initialize limits */ - - /* this entry point is for pseudo-recursion calling: setting - lo and hi and jumping to here is like recursion, but stkptr is - preserved, locals aren't, so we preserve stuff on the stack */ -recurse: - - size = (hi - lo) + 1; /* number of el's to sort */ - - /* First we pick a partitioning element. The efficiency of the - algorithm demands that we find one that is approximately the median - of the values, but also that we select one fast. We choose the - median of the first, middle, and last elements, to avoid bad - performance in the face of already sorted data, or data that is made - up of multiple sorted runs appended together. Testing shows that a - median-of-three algorithm provides better performance than simply - picking the middle element for the latter case. */ - - mid = lo + (size / 2); /* find middle element */ - - /* Sort the first, middle, last elements into order */ - if (compare(context, base[lo], base[mid]) > 0) { - SWAP_ENTRIES(lo, mid); - } - if (compare(context, base[lo], base[hi]) > 0) { - SWAP_ENTRIES(lo, hi); - } - if (compare(context, base[mid], base[hi]) > 0) { - SWAP_ENTRIES(mid, hi); - } - - /* We now wish to partition the array into three pieces, one consisting - of elements <= partition element, one of elements equal to the - partition element, and one of elements > than it. This is done - below; comments indicate conditions established at every step. */ - - loguy = lo; - higuy = hi; - - /* Note that higuy decreases and loguy increases on every iteration, - so loop must terminate. */ - for (;;) { - /* lo <= loguy < hi, lo < higuy <= hi, - A[i] <= A[mid] for lo <= i <= loguy, - A[i] > A[mid] for higuy <= i < hi, - A[hi] >= A[mid] */ - - /* The doubled loop is to avoid calling comp(mid,mid), since some - existing comparison funcs don't work when passed the same - value for both pointers. */ - - if (mid > loguy) { - do { - loguy ++; - } while (loguy < mid && compare(context, base[loguy], base[mid]) <= 0); - } - if (mid <= loguy) { - do { - loguy ++; - } while (loguy <= hi && compare(context, base[loguy], base[mid]) <= 0); - } - - /* lo < loguy <= hi+1, A[i] <= A[mid] for lo <= i < loguy, - either loguy > hi or A[loguy] > A[mid] */ - - do { - higuy --; - } while (higuy > mid && compare(context, base[higuy], base[mid]) > 0); - - /* lo <= higuy < hi, A[i] > A[mid] for higuy < i < hi, - either higuy == lo or A[higuy] <= A[mid] */ - - if (higuy < loguy) - break; - - /* if loguy > hi or higuy == lo, then we would have exited, so - A[loguy] > A[mid], A[higuy] <= A[mid], - loguy <= hi, higuy > lo */ - - SWAP_ENTRIES(loguy, higuy); - - /* If the partition element was moved, follow it. Only need - to check for mid == higuy, since before the swap, - A[loguy] > A[mid] implies loguy != mid. */ - - if (mid == higuy) - mid = loguy; - - /* A[loguy] <= A[mid], A[higuy] > A[mid]; so condition at top - of loop is re-established */ - } + MD5_CTX md5_ctx; - /* A[i] <= A[mid] for lo <= i < loguy, - A[i] > A[mid] for higuy < i < hi, - A[hi] >= A[mid] - higuy < loguy - implying: - higuy == loguy-1 - or higuy == hi - 1, loguy == hi + 1, A[hi] == A[mid] */ - - /* Find adjacent elements equal to the partition element. The - doubled loop is to avoid calling comp(mid,mid), since some - existing comparison funcs don't work when passed the same value - for both pointers. */ - - higuy ++; - if (mid < higuy) { - do { - higuy --; - } while (higuy > mid && compare(context, base[higuy], base[mid]) == 0); - } - if (mid >= higuy) { - do { - higuy --; - } while (higuy > lo && compare(context, base[higuy], base[mid]) == 0); - } - - /* OK, now we have the following: - higuy < loguy - lo <= higuy <= hi - A[i] <= A[mid] for lo <= i <= higuy - A[i] == A[mid] for higuy < i < loguy - A[i] > A[mid] for loguy <= i < hi - A[hi] >= A[mid] */ - - /* We've finished the partition, now we want to sort the subarrays - [lo, higuy] and [loguy, hi]. - We do the smaller one first to minimize stack usage. - We only sort arrays of length 2 or more.*/ - - if ( higuy - lo >= hi - loguy ) { - if (lo < higuy) { - lostk[stkptr] = lo; - histk[stkptr] = higuy; - ++stkptr; - } /* save big recursion for later */ - - if (loguy < hi) { - lo = loguy; - goto recurse; /* do small recursion */ - } - } - else { - if (loguy < hi) { - lostk[stkptr] = loguy; - histk[stkptr] = hi; - ++stkptr; /* save big recursion for later */ - } - - if (lo < higuy) { - hi = higuy; - goto recurse; /* do small recursion */ - } - } - - /* We have sorted the array, except for any pending sorts on the stack. - Check if there are any, and do them. */ - - --stkptr; - if (stkptr >= 0) { - lo = lostk[stkptr]; - hi = histk[stkptr]; - goto recurse; /* pop subarray from stack */ - } - else - return; /* all subarrays done */ + MD5_Init(&md5_ctx); + MD5_Update(&md5_ctx, pvDataBlock, cbDataBlock); + MD5_Final(md5_hash, &md5_ctx); } diff --git a/dep/CascLib/src/common/Common.h b/dep/CascLib/src/common/Common.h index 484276a3bd7..c7f189b91ac 100644 --- a/dep/CascLib/src/common/Common.h +++ b/dep/CascLib/src/common/Common.h @@ -21,6 +21,76 @@ #define ALIGN_TO_SIZE(x, a) (((x) + (a)-1) & ~((a)-1)) #endif +// Prevent problems with CRT "min" and "max" functions, +// as they are not defined on all platforms +#define CASCLIB_MIN(a, b) ((a < b) ? a : b) +#define CASCLIB_MAX(a, b) ((a > b) ? a : b) +#define CASCLIB_UNUSED(p) ((void)(p)) + +//----------------------------------------------------------------------------- +// Common structures + +// Structure for static content key (CKey) and encoded key (EKey) +// The CKey is a MD5 hash of the file data. +// The EKey is (shortened) MD5 hash of the file header, which contains MD5 hashes of all the logical blocks of the file. +typedef struct _CONTENT_KEY +{ + BYTE Value[MD5_HASH_SIZE]; // MD5 of the file + +} CONTENT_KEY, *PCONTENT_KEY, ENCODED_KEY, *PENCODED_KEY; + +// Helper structure for merging file paths +typedef struct _PATH_BUFFER +{ + char * szBegin; + char * szPtr; + char * szEnd; +} PATH_BUFFER, *PPATH_BUFFER; + +//----------------------------------------------------------------------------- +// Basic structure used by all CascLib objects to describe a single entry +// in the CASC storage. Each entry represents one physical file +// in the storage. Note that the file may be present under several file names. + +// Flags for CASC_CKEY_ENTRY::Flags +#define CASC_CE_FILE_IS_LOCAL 0x00000001 // The file is available locally. Keep this flag to have value of 1 +#define CASC_CE_HAS_CKEY 0x00000002 // The CKey is present in the entry +#define CASC_CE_HAS_EKEY 0x00000004 // The EKey is present, at least partial one +#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 + +// In-memory representation of a single entry. +struct CASC_CKEY_ENTRY +{ + CASC_CKEY_ENTRY() + { + Init(); + } + + void Init(void) + { + memset(this, 0, sizeof(CASC_CKEY_ENTRY)); + StorageOffset = CASC_INVALID_OFFS64; + EncodedSize = CASC_INVALID_SIZE; + ContentSize = CASC_INVALID_SIZE; + } + + 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 Flags; // See CASC_CE_XXX + USHORT RefCount; // This is the number of file names referencing this entry + BYTE Priority; // Download priority + BYTE Alignment; + +}; +typedef CASC_CKEY_ENTRY *PCASC_CKEY_ENTRY; + //----------------------------------------------------------------------------- // Conversion tables @@ -29,23 +99,198 @@ extern unsigned char AsciiToUpperTable_BkSlash[256]; extern unsigned char IntToHexChar[]; //----------------------------------------------------------------------------- -// String manipulation +// Memory management +// +// We use our own macros for allocating/freeing memory. If you want +// to redefine them, please keep the following rules: +// +// - The memory allocation must return NULL if not enough memory +// (i.e not to throw exception) +// - The allocating function does not need to fill the allocated buffer with zeros +// - The reallocating function must support NULL as the previous block +// - Memory freeing function must check for NULL pointer and do nothing if so +// + +#define CASC_REALLOC(type, ptr, count) (type *)realloc(ptr, (count) * sizeof(type)) +#define CASC_ALLOC(type, count) (type *)malloc((count) * sizeof(type)) + +template <typename T> +void CASC_FREE(T *& ptr) +{ + if (ptr != NULL) + free(ptr); + ptr = NULL; +} + +//----------------------------------------------------------------------------- +// Overloaded "new" and "delete" operators + +void * operator new(size_t size); +void * operator new[](size_t size); +void operator delete(void * ptr); +void operator delete[](void * ptr); +void operator delete(void * ptr, size_t); // For some reason, VS2015 needs this + +//----------------------------------------------------------------------------- +// Big endian number manipulation + +inline DWORD ConvertBytesToInteger_2(LPBYTE ValueAsBytes) +{ + USHORT Value = 0; + + Value = (Value << 0x08) | ValueAsBytes[0]; + Value = (Value << 0x08) | ValueAsBytes[1]; + + return Value; +} + +inline DWORD ConvertBytesToInteger_3(LPBYTE ValueAsBytes) +{ + DWORD Value = 0; + + Value = (Value << 0x08) | ValueAsBytes[0]; + Value = (Value << 0x08) | ValueAsBytes[1]; + Value = (Value << 0x08) | ValueAsBytes[2]; + + return Value; +} + +inline DWORD ConvertBytesToInteger_4(LPBYTE ValueAsBytes) +{ + DWORD Value = 0; + + Value = (Value << 0x08) | ValueAsBytes[0]; + Value = (Value << 0x08) | ValueAsBytes[1]; + Value = (Value << 0x08) | ValueAsBytes[2]; + Value = (Value << 0x08) | ValueAsBytes[3]; + + return Value; +} + +// Converts the variable-size big-endian into integer +inline DWORD ConvertBytesToInteger_X(LPBYTE ValueAsBytes, DWORD dwByteSize) +{ + DWORD Value = 0; + + if(dwByteSize > 0) + Value = (Value << 0x08) | ValueAsBytes[0]; + if(dwByteSize > 1) + Value = (Value << 0x08) | ValueAsBytes[1]; + if(dwByteSize > 2) + Value = (Value << 0x08) | ValueAsBytes[2]; + if(dwByteSize > 3) + Value = (Value << 0x08) | ValueAsBytes[3]; + + return Value; +} + +inline DWORD ConvertBytesToInteger_4_LE(LPBYTE ValueAsBytes) +{ + DWORD Value = 0; + + Value = (Value << 0x08) | ValueAsBytes[3]; + Value = (Value << 0x08) | ValueAsBytes[2]; + Value = (Value << 0x08) | ValueAsBytes[1]; + Value = (Value << 0x08) | ValueAsBytes[0]; + + return Value; +} + +// Read the 40-bit big-endian offset into ULONGLONG +inline ULONGLONG ConvertBytesToInteger_5(LPBYTE ValueAsBytes) +{ + ULONGLONG Value = 0; + + Value = (Value << 0x08) | ValueAsBytes[0]; + Value = (Value << 0x08) | ValueAsBytes[1]; + Value = (Value << 0x08) | ValueAsBytes[2]; + Value = (Value << 0x08) | ValueAsBytes[3]; + Value = (Value << 0x08) | ValueAsBytes[4]; + + return Value; +} + +inline void ConvertIntegerToBytes_4(DWORD Value, LPBYTE ValueAsBytes) +{ + ValueAsBytes[0] = (Value >> 0x18) & 0xFF; + ValueAsBytes[1] = (Value >> 0x10) & 0xFF; + ValueAsBytes[2] = (Value >> 0x08) & 0xFF; + ValueAsBytes[3] = (Value >> 0x00) & 0xFF; +} + +inline void ConvertIntegerToBytes_4_LE(DWORD Value, LPBYTE ValueAsBytes) +{ + ValueAsBytes[0] = (Value >> 0x00) & 0xFF; + ValueAsBytes[1] = (Value >> 0x08) & 0xFF; + ValueAsBytes[2] = (Value >> 0x10) & 0xFF; + ValueAsBytes[3] = (Value >> 0x18) & 0xFF; +} + +// Faster than memset(Buffer, 0, 0x10) +inline void ZeroMemory16(void * Buffer) +{ + PDWORD PtrBuffer = (PDWORD)Buffer; + + PtrBuffer[0] = 0; + PtrBuffer[1] = 0; + PtrBuffer[2] = 0; + PtrBuffer[3] = 0; +} + +// Faster than memcpy(Target, Source, 0x10) +inline void CopyMemory16(void * Target, void * Source) +{ + PDWORD PtrTarget = (PDWORD)Target; + PDWORD PtrSource = (PDWORD)Source; + + PtrTarget[0] = PtrSource[0]; + PtrTarget[1] = PtrSource[1]; + PtrTarget[2] = PtrSource[2]; + PtrTarget[3] = PtrSource[3]; +} + +//----------------------------------------------------------------------------- +// Linear data stream manipulation -void CopyString(char * szTarget, const char * szSource, size_t cchLength); -void CopyString(wchar_t * szTarget, const char * szSource, size_t cchLength); -void CopyString(char * szTarget, const wchar_t * szSource, size_t cchLength); +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 CaptureArray_(LPBYTE pbDataPtr, LPBYTE pbDataEnd, LPBYTE * PtrArray, size_t ItemSize, size_t ItemCount); -char * CascNewStr(const char * szString, size_t nCharsToReserve); -wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve); +#define CaptureArray(pbDataPtr, pbDataEnd, PtrArray, type, count) CaptureArray_(pbDataPtr, pbDataEnd, PtrArray, sizeof(type), count) -TCHAR * CascNewStrFromAnsi(const char * szBegin, const char * szEnd); +//----------------------------------------------------------------------------- +// String copying and conversion + +void CascStrCopy(char * szTarget, size_t cchTarget, const char * szSource, size_t cchSource = -1); +void CascStrCopy(char * szTarget, size_t cchTarget, const wchar_t * szSource, size_t cchSource = -1); +void CascStrCopy(wchar_t * szTarget, size_t cchTarget, const char * szSource, size_t cchSource = -1); +void CascStrCopy(wchar_t * szTarget, size_t cchTarget, const wchar_t * szSource, size_t cchSource = -1); + +//----------------------------------------------------------------------------- +// Safe version of s(w)printf + +size_t CascStrPrintf(char * buffer, size_t nCount, const char * format, ...); +size_t CascStrPrintf(wchar_t * buffer, size_t nCount, const wchar_t * format, ...); + +//----------------------------------------------------------------------------- +// String allocation + +char * CascNewStr(const char * szString, size_t nCharsToReserve = 0); +wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve = 0); TCHAR * CombinePath(const TCHAR * szPath, const TCHAR * szSubDir); -TCHAR * CombinePathAndString(const TCHAR * szPath, const char * szString, size_t nLength); +size_t CombineFilePath(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szPath, const TCHAR * szSubPath1, const TCHAR * szSubPath2 = NULL); +size_t CombineUrlPath(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szHost, const TCHAR * szSubPath1, const TCHAR * szSubPath2 = NULL); +TCHAR * GetLastPathPart(TCHAR * 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); +ULONGLONG CalcNormNameHash(const char * szNormName, size_t nLength); ULONGLONG CalcFileNameHash(const char * szFileName); int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue); @@ -56,20 +301,81 @@ char * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, char * szBuffer); char * StringFromMD5(LPBYTE md5, char * szBuffer); //----------------------------------------------------------------------------- +// Structure query key + +struct QUERY_KEY +{ + QUERY_KEY() + { + pbData = NULL; + cbData = 0; + } + + ~QUERY_KEY() + { + CASC_FREE(pbData); + cbData = 0; + } + + LPBYTE pbData; + size_t cbData; +}; +typedef QUERY_KEY *PQUERY_KEY; + +//----------------------------------------------------------------------------- // File name utilities -bool CheckWildCard(const char * szString, const char * szWildCard); -const wchar_t * GetPlainFileName(const wchar_t * szFileName); -const char * GetPlainFileName(const char * szFileName); +// Retrieves the pointer to plain name +template <typename XCHAR> +const XCHAR * GetPlainFileName(const XCHAR * szFileName) +{ + const XCHAR * szPlainName = szFileName; + + while(*szFileName != 0) + { + if(*szFileName == '\\' || *szFileName == '/') + szPlainName = szFileName + 1; + szFileName++; + } + + return szPlainName; +} + +// Retrieves the pointer to file extension +template <typename XCHAR> +const XCHAR * GetFileExtension(const XCHAR * szFileName) +{ + const XCHAR * szExtension = NULL; + + // We need to start searching from the plain name + // Avoid: C:\$RECYCLE.BIN\File.ext + szFileName = GetPlainFileName(szFileName); + + // Find the last dot in the plain file name + while(szFileName[0] != 0) + { + if(szFileName[0] == '.') + szExtension = szFileName; + szFileName++; + } + + // If not found, return the end of the file name + return (XCHAR *)((szExtension != NULL) ? szExtension : szFileName); +} + +bool IsFileDataIdName(const char * szFileName, DWORD & FileDataId); +bool IsFileCKeyEKeyName(const char * szFileName, LPBYTE PtrKeyBuffer); + +bool CascCheckWildCard(const char * szString, const char * szWildCard); //----------------------------------------------------------------------------- // Hashing functions ULONGLONG HashStringJenkins(const char * szFileName); -bool IsValidMD5(LPBYTE pbMd5); -void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash); -bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5); +bool CascIsValidMD5(LPBYTE pbMd5); +void CascCalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash); +bool CascVerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5); //----------------------------------------------------------------------------- // Scanning a directory diff --git a/dep/CascLib/src/common/Csv.cpp b/dep/CascLib/src/common/Csv.cpp new file mode 100644 index 00000000000..24b654daa86 --- /dev/null +++ b/dep/CascLib/src/common/Csv.cpp @@ -0,0 +1,370 @@ +/*****************************************************************************/ +/* Csv.cpp Copyright (c) Ladislav Zezula 2019 */ +/*---------------------------------------------------------------------------*/ +/* Implementation for the CSV handler class */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 24.05.19 1.00 Lad Created */ +/*****************************************************************************/ + +#define __CASCLIB_SELF__ +#include "../CascLib.h" +#include "../CascCommon.h" + +//----------------------------------------------------------------------------- +// Local variables + +static const CASC_CSV_LINE NullLine; +#define NullColumn NullLine.Columns[0]; + +//----------------------------------------------------------------------------- +// Local functions + +static char * NextLine(char * szLine) +{ + // Find the end of the line + while(szLine[0] != 0 && szLine[0] != 0x0A && szLine[0] != 0x0D) + szLine++; + + // Terminate the line + while(szLine[0] == 0x0A || szLine[0] == 0x0D) + *szLine++ = 0; + + // If there's an end-of-string, it's over + return (szLine[0] != 0) ? szLine : NULL; +} + +static char * NextColumn(char * szColumn) +{ + // Find the end of the column + while(szColumn[0] != 0 && szColumn[0] != '|') + szColumn++; + + // Terminate the column + if (szColumn[0] == '|') + { + *szColumn++ = 0; + return szColumn; + } + + // If there's an end-of-string, it's over + return NULL; +} + +static size_t CalcHashValue(const char * szString) +{ + size_t dwHash = 0x7EEE7EEE; + + // Hash the string itself + while(szString[0] != 0) + { + dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ szString[0]; + szString++; + } + + // Return the hash limited by the table size + return dwHash; +} + +static BYTE HashToIndex(size_t dwHashValue) +{ + return (BYTE)(dwHashValue & (CSV_HASH_TABLE_SIZE - 1)); +} + +//----------------------------------------------------------------------------- +// Class for CSV line + +CASC_CSV_LINE::CASC_CSV_LINE() +{ + m_pParent = NULL; + m_nColumns = 0; +} + +CASC_CSV_LINE::~CASC_CSV_LINE() +{} + +bool CASC_CSV_LINE::SetLine(CASC_CSV * pParent, char * szCsvLine) +{ + char * szCsvColumn; + size_t nHdrColumns = 0; + size_t nColumns = 0; + + // Reset the number of column to zero + m_pParent = pParent; + m_nColumns = 0; + + // Parse each column + while(szCsvLine != NULL && nColumns < CSV_MAX_COLUMNS) + { + // Get current line and the next one + szCsvColumn = szCsvLine; + szCsvLine = NextColumn(szCsvLine); + + // Save the current line + Columns[nColumns].szValue = szCsvColumn; + Columns[nColumns].nLength = strlen(szCsvColumn); + nColumns++; + } + + // Columns overflow + if(nColumns >= CSV_MAX_COLUMNS) + { + assert(false); + return false; + } + + // If the parent has header lines, then the number of columns must match + // In the case of mismatched column count, ignore the line + nHdrColumns = pParent->GetHeaderColumns(); + if(nHdrColumns != 0 && nColumns != nHdrColumns) + return false; + + // All OK + m_nColumns = nColumns; + return true; +} + +const CASC_CSV_COLUMN & CASC_CSV_LINE::operator[](const char * szColumnName) const +{ + size_t nIndex; + + // The column must have a hash table + if(m_pParent != NULL) + { + nIndex = m_pParent->GetColumnIndex(szColumnName); + if(nIndex != CSV_INVALID_INDEX && nIndex < m_nColumns) + { + return Columns[nIndex]; + } + } + + return NullColumn; +} + +const CASC_CSV_COLUMN & CASC_CSV_LINE::operator[](size_t nIndex) const +{ + return (nIndex < m_nColumns) ? Columns[nIndex] : NullColumn; +} + +//----------------------------------------------------------------------------- +// Class for CSV object + +CASC_CSV::CASC_CSV(size_t nLinesMax, bool bHasHeader) +{ + // Initialize the class variables + memset(HashTable, 0xFF, sizeof(HashTable)); + m_szCsvFile = NULL; + m_szCsvPtr = NULL; + m_nCsvFile = 0; + m_nLines = 0; + m_bHasHeader = bHasHeader; + + // Nonzero number of lines means a finite CSV handler. The CSV will be loaded at once. + // Zero means that the user needs to call LoadNextLine() and then the line data + if(nLinesMax != 0) + { + m_pLines = new CASC_CSV_LINE[nLinesMax]; + m_nLinesMax = nLinesMax; + m_bHasAllLines = true; + } + else + { + m_pLines = new CASC_CSV_LINE[1]; + m_nLinesMax = 1; + m_bHasAllLines = false; + } +} + +CASC_CSV::~CASC_CSV() +{ + if(m_pLines != NULL) + delete[] m_pLines; + if(m_szCsvFile != NULL) + delete [] m_szCsvFile; + m_szCsvFile = NULL; +} + +int CASC_CSV::Load(const TCHAR * szFileName) +{ + DWORD cbFileData = 0; + int nError = ERROR_SUCCESS; + + m_szCsvFile = (char *)LoadFileToMemory(szFileName, &cbFileData); + if (m_szCsvFile != NULL) + { + // There is one extra byte reserved by LoadFileToMemory, so we can put zero there + m_szCsvFile[cbFileData] = 0; + m_szCsvPtr = m_szCsvFile; + m_nCsvFile = cbFileData; + + // Parse the data + nError = ParseCsvData() ? ERROR_SUCCESS : ERROR_BAD_FORMAT; + } + else + { + nError = GetLastError(); + } + + return nError; +} + +int CASC_CSV::Load(LPBYTE pbData, size_t cbData) +{ + int nError = ERROR_NOT_ENOUGH_MEMORY; + + m_szCsvFile = new char[cbData + 1]; + if (m_szCsvFile != NULL) + { + // Copy the entire data and terminate them with zero + memcpy(m_szCsvFile, pbData, cbData); + m_szCsvFile[cbData] = 0; + m_szCsvPtr = m_szCsvFile; + m_nCsvFile = cbData; + + // Parse the data + nError = ParseCsvData() ? ERROR_SUCCESS : ERROR_BAD_FORMAT; + } + + return nError; +} + +bool CASC_CSV::LoadNextLine() +{ + bool bResult = false; + + // Only endless CSV handler can do this + if(m_bHasAllLines == false) + { + // A few checks + assert(m_pLines != NULL); + assert(m_nLinesMax == 1); + + // Reset the current line and load it + bResult = LoadNextLine(m_pLines[0]); + m_nLines = (bResult) ? 1 : 0; + } + + return bResult; +} + +bool CASC_CSV::InitializeHashTable() +{ + // Create the hash table of HeaderText -> ColumnIndex + for(size_t i = 0; i < Header.GetColumnCount(); i++) + { + // Calculate the start slot and the current slot + size_t nStartIndex = HashToIndex(CalcHashValue(Header[i].szValue)); + size_t nHashIndex = nStartIndex; + + // Go as long as there is not a free space + while(HashTable[nHashIndex] != 0xFF) + { + nHashIndex = HashToIndex(nHashIndex + 1); + } + + // Set the slot + HashTable[nHashIndex] = (BYTE)i; + } + + return true; +} + +bool CASC_CSV::LoadNextLine(CASC_CSV_LINE & Line) +{ + // Overwatch ROOT's header begins with "#" + if(m_szCsvPtr == m_szCsvFile && m_szCsvPtr[0] == '#') + m_szCsvPtr++; + + // Parse the entire line + while(m_szCsvPtr != NULL && m_szCsvPtr[0] != 0) + { + char * szCsvLine = m_szCsvPtr; + + // Get the next line + m_szCsvPtr = NextLine(m_szCsvPtr); + + // Initialize the line + if (Line.SetLine(this, szCsvLine)) + return true; + } + + // End-of-file found + return false; +} + +bool CASC_CSV::ParseCsvData() +{ + // Sanity checks + assert(m_nLines == 0); + + // If we have header, then parse it and set the pointer to the next line + if(m_bHasHeader) + { + // Load the current line to the header + if (!LoadNextLine(Header)) + return false; + + // Initialize the hash table + if(!InitializeHashTable()) + return false; + } + + // Are we supposed to load all lines? + if(m_bHasAllLines) + { + // Parse each line + for(size_t i = 0; i < m_nLinesMax; i++) + { + if(!LoadNextLine(m_pLines[i])) + break; + m_nLines++; + } + } + + return true; +} + +const CASC_CSV_COLUMN & CASC_CSV::operator[](const char * szColumnName) const +{ + if (m_pLines == NULL || m_nLines == 0) + return NullColumn; + return m_pLines[0][GetColumnIndex(szColumnName)]; +} + +const CASC_CSV_LINE & CASC_CSV::operator[](size_t nIndex) const +{ + if (m_pLines == NULL || nIndex >= m_nLines) + return NullLine; + return m_pLines[nIndex]; +} + +size_t CASC_CSV::GetHeaderColumns() const +{ + return (m_bHasHeader) ? Header.GetColumnCount() : 0; +} + +size_t CASC_CSV::GetColumnIndex(const char * szColumnName) const +{ + if(m_bHasHeader) + { + // Calculate the start slot and the current slot + size_t nStartIndex = HashToIndex(CalcHashValue(szColumnName)); + size_t nHashIndex = nStartIndex; + size_t nIndex; + + // Go as long as there is not a free space + while((nIndex = HashTable[nHashIndex]) != 0xFF) + { + // Compare the string + if(!strcmp(Header[nIndex].szValue, szColumnName)) + return nIndex; + + // Move to the next column + nHashIndex = HashToIndex(nHashIndex + 1); + } + } + + return CSV_INVALID_INDEX; +} + diff --git a/dep/CascLib/src/common/Csv.h b/dep/CascLib/src/common/Csv.h new file mode 100644 index 00000000000..2138255e74e --- /dev/null +++ b/dep/CascLib/src/common/Csv.h @@ -0,0 +1,105 @@ +/*****************************************************************************/ +/* Csv.h Copyright (c) Ladislav Zezula 2019 */ +/*---------------------------------------------------------------------------*/ +/* Interface for the CSV handler class */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 24.05.19 1.00 Lad Created */ +/*****************************************************************************/ + +#ifndef __CSV_H__ +#define __CSV_H__ + +//----------------------------------------------------------------------------- +// Defines + +#define CSV_INVALID_INDEX ((size_t)(-1)) +#define CSV_ZERO ((size_t)(0)) // Use Csv[0][CSV_ZERO] instead of ambiguous Csv[0][0] +#define CSV_MAX_COLUMNS 0x10 +#define CSV_HASH_TABLE_SIZE 0x80 + +//----------------------------------------------------------------------------- +// Class for CSV line + +class CASC_CSV; + +struct CASC_CSV_COLUMN +{ + CASC_CSV_COLUMN() + { + szValue = NULL; + nLength = 0; + } + + const char * szValue; + size_t nLength; +}; + +class CASC_CSV_LINE +{ + public: + + CASC_CSV_LINE(); + ~CASC_CSV_LINE(); + + bool SetLine(CASC_CSV * pParent, char * szLine); + + const CASC_CSV_COLUMN & operator[](const char * szColumnName) const; + const CASC_CSV_COLUMN & operator[](size_t nIndex) const; + + size_t GetColumnCount() const + { + return m_nColumns; + } + + protected: + + friend class CASC_CSV; + + CASC_CSV * m_pParent; + CASC_CSV_COLUMN Columns[CSV_MAX_COLUMNS]; + size_t m_nColumns; +}; + +class CASC_CSV +{ + public: + + CASC_CSV(size_t nLinesMax, bool bHasHeader); + ~CASC_CSV(); + + int Load(LPBYTE pbData, size_t cbData); + int Load(const TCHAR * szFileName); + bool LoadNextLine(); + + const CASC_CSV_COLUMN & operator[](const char * szColumnName) const; + const CASC_CSV_LINE & operator[](size_t nIndex) const; + + size_t GetHeaderColumns() const; + size_t GetColumnIndex(const char * szColumnName) const; + + size_t GetLineCount() const + { + return m_nLines; + } + + protected: + + bool InitializeHashTable(); + bool LoadNextLine(CASC_CSV_LINE & Line); + bool ParseCsvData(); + + CASC_CSV_LINE * m_pLines; + CASC_CSV_LINE Header; + BYTE HashTable[CSV_HASH_TABLE_SIZE]; + char * m_szCsvFile; + char * m_szCsvPtr; + size_t m_nCsvFile; + size_t m_nLinesMax; + size_t m_nLines; + bool m_bHasHeader; + bool m_bHasAllLines; +}; + +#endif // __CSV_H__ diff --git a/dep/CascLib/src/common/Directory.cpp b/dep/CascLib/src/common/Directory.cpp index c4091095859..463881f3f9c 100644 --- a/dep/CascLib/src/common/Directory.cpp +++ b/dep/CascLib/src/common/Directory.cpp @@ -38,6 +38,20 @@ bool DirectoryExists(const TCHAR * szDirectory) return false; } +bool MakeDirectory(const TCHAR * szDirectory) +{ +#ifdef PLATFORM_WINDOWS + + BOOL bResult = CreateDirectory(szDirectory, NULL); + return (bResult) ? true : false; + +#else + + return (mkdir(szDirectory, 0755) == 0); + +#endif +} + int ScanIndexDirectory( const TCHAR * szIndexPath, INDEX_FILE_FOUND pfnOnFileFound, diff --git a/dep/CascLib/src/common/Directory.h b/dep/CascLib/src/common/Directory.h index ae97dca3d76..739f0612e54 100644 --- a/dep/CascLib/src/common/Directory.h +++ b/dep/CascLib/src/common/Directory.h @@ -18,6 +18,8 @@ typedef bool (*INDEX_FILE_FOUND)(const TCHAR * szFileName, PDWORD IndexArray, PD bool DirectoryExists(const TCHAR * szDirectory); +bool MakeDirectory(const TCHAR * szDirectory); + int ScanIndexDirectory( const TCHAR * szIndexPath, INDEX_FILE_FOUND pfnOnFileFound, diff --git a/dep/CascLib/src/common/DumpContext.cpp b/dep/CascLib/src/common/DumpContext.cpp deleted file mode 100644 index 8783ff201ba..00000000000 --- a/dep/CascLib/src/common/DumpContext.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/*****************************************************************************/ -/* DumpContext.cpp Copyright (c) Ladislav Zezula 2015 */ -/*---------------------------------------------------------------------------*/ -/* Implementation of dump context */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 16.03.15 1.00 Lad Created */ -/*****************************************************************************/ - -#define __CASCLIB_SELF__ -#include "../CascLib.h" -#include "../CascCommon.h" - -//----------------------------------------------------------------------------- -// Local functions - -static TCHAR * FormatFileName(TCascStorage * hs, const TCHAR * szNameFormat) -{ - TCHAR * szFileName; - TCHAR * szSrc; - TCHAR * szTrg; - - // Create copy of the file name - szFileName = szSrc = szTrg = CascNewStr(szNameFormat, 0); - if(szFileName != NULL) - { - // Format the file name - while(szSrc[0] != 0) - { - if(szSrc[0] == _T('%')) - { - // Replace "%build%" with a build number - if(!_tcsncmp(szSrc, _T("%build%"), 7)) - { - szTrg += _stprintf(szTrg, _T("%u"), hs->dwBuildNumber); - szSrc += 7; - continue; - } - } - - // Just copy the character - *szTrg++ = *szSrc++; - } - - // Terminate the target file name - szTrg[0] = 0; - } - - return szFileName; -} - -//----------------------------------------------------------------------------- -// Public functions - -TDumpContext * CreateDumpContext(TCascStorage * hs, const TCHAR * szNameFormat) -{ - TDumpContext * dc; - TCHAR * szFileName; - - // Validate the storage handle - if(hs != NULL) - { - // Calculate the number of bytes needed for dump context - dc = CASC_ALLOC(TDumpContext, 1); - if(dc != NULL) - { - // Initialize the dump context - memset(dc, 0, sizeof(TDumpContext)); - - // Format the real file name - szFileName = FormatFileName(hs, szNameFormat); - if(szFileName != NULL) - { - // Create the file - dc->pStream = FileStream_CreateFile(szFileName, 0); - if(dc->pStream != NULL) - { - // Initialize buffers - dc->pbBufferBegin = - dc->pbBufferPtr = dc->DumpBuffer; - dc->pbBufferEnd = dc->DumpBuffer + CASC_DUMP_BUFFER_SIZE; - - // Success - CASC_FREE(szFileName); - return dc; - } - - // File create failed --> delete the file name - CASC_FREE(szFileName); - } - - // Free the dump context - CASC_FREE(dc); - } - } - - return NULL; -} - -int dump_print(TDumpContext * dc, const char * szFormat, ...) -{ - va_list argList; - char szBuffer[0x200]; - int nLength = 0; - - // Only if the dump context is valid - if(dc != NULL) - { - // Print the buffer using sprintf - va_start(argList, szFormat); - nLength = vsprintf(szBuffer, szFormat, argList); - assert(nLength < 0x200); - va_end(argList); - - // Copy the string. Replace "\n" with "\n\r" - for(int i = 0; i < nLength; i++) - { - // Flush the buffer, if needed - if((dc->pbBufferPtr + 2) >= dc->pbBufferEnd) - { - FileStream_Write(dc->pStream, NULL, dc->pbBufferBegin, (DWORD)(dc->pbBufferPtr - dc->pbBufferBegin)); - dc->pbBufferPtr = dc->pbBufferBegin; - } - - // Copy the char - if(szBuffer[i] == 0x0A) - *dc->pbBufferPtr++ = 0x0D; - *dc->pbBufferPtr++ = szBuffer[i]; - } - } - - return nLength; -} - -int dump_close(TDumpContext * dc) -{ - // Only if the dump context is valid - if(dc != NULL) - { - // Flush the dump context if there are some data - if(dc->pbBufferPtr > dc->pbBufferBegin) - FileStream_Write(dc->pStream, NULL, dc->pbBufferBegin, (DWORD)(dc->pbBufferPtr - dc->pbBufferBegin)); - dc->pbBufferPtr = dc->pbBufferBegin; - - // Free the file stream and the entire context - if(dc->pStream != NULL) - FileStream_Close(dc->pStream); - CASC_FREE(dc); - } - - return 0; -} diff --git a/dep/CascLib/src/common/DumpContext.h b/dep/CascLib/src/common/DumpContext.h deleted file mode 100644 index 6f725f5b942..00000000000 --- a/dep/CascLib/src/common/DumpContext.h +++ /dev/null @@ -1,38 +0,0 @@ -/*****************************************************************************/ -/* DumpContext.h Copyright (c) Ladislav Zezula 2015 */ -/*---------------------------------------------------------------------------*/ -/* Interface for TDumpContext */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 16.03.15 1.00 Lad Created */ -/*****************************************************************************/ - -#ifndef __DUMP_CONTEXT_H__ -#define __DUMP_CONTEXT_H__ - -//----------------------------------------------------------------------------- -// Defines - -// Size of the buffer for the dump context -#define CASC_DUMP_BUFFER_SIZE 0x10000 - -// Structure for dump context -struct TDumpContext -{ - TFileStream * pStream; // Pointer to the open stream - LPBYTE pbBufferBegin; // Begin of the dump buffer - LPBYTE pbBufferPtr; // Current dump buffer pointer - LPBYTE pbBufferEnd; // End of the dump buffer - - BYTE DumpBuffer[CASC_DUMP_BUFFER_SIZE]; // Dump buffer -}; - -//----------------------------------------------------------------------------- -// Dump context functions - -TDumpContext * CreateDumpContext(struct _TCascStorage * hs, const TCHAR * szNameFormat); -int dump_print(TDumpContext * dc, const char * szFormat, ...); -int dump_close(TDumpContext * dc); - -#endif // __DUMP_CONTEXT_H__ diff --git a/dep/CascLib/src/common/DynamicArray.cpp b/dep/CascLib/src/common/DynamicArray.cpp deleted file mode 100644 index e744fbe3912..00000000000 --- a/dep/CascLib/src/common/DynamicArray.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/*****************************************************************************/ -/* DynamicArray.cpp Copyright (c) Ladislav Zezula 2015 */ -/*---------------------------------------------------------------------------*/ -/* Description: */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 30.10.15 1.00 Lad The first version of DynamicArray.cpp */ -/*****************************************************************************/ - -#define __CASCLIB_SELF__ -#include "../CascLib.h" -#include "../CascCommon.h" - -//----------------------------------------------------------------------------- -// Local functions - -static bool EnlargeArray(PDYNAMIC_ARRAY pArray, size_t NewItemCount) -{ - char * NewItemArray; - size_t ItemCountMax; - - // We expect the array to be already allocated - assert(pArray->ItemArray != NULL); - assert(pArray->ItemCountMax != 0); - - // Shall we enlarge the table? - if(NewItemCount > pArray->ItemCountMax) - { - // Calculate new table size - ItemCountMax = pArray->ItemCountMax; - while(ItemCountMax < NewItemCount) - ItemCountMax = ItemCountMax << 1; - - // Allocate new table - NewItemArray = CASC_REALLOC(char, pArray->ItemArray, pArray->ItemSize * ItemCountMax); - if(NewItemArray == NULL) - return false; - - // Set the new table size - pArray->ItemCountMax = ItemCountMax; - pArray->ItemArray = NewItemArray; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Public functions - -int Array_Create_(PDYNAMIC_ARRAY pArray, size_t ItemSize, size_t ItemCountMax) -{ - pArray->ItemArray = CASC_ALLOC(char, (ItemSize * ItemCountMax)); - if(pArray->ItemArray == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - pArray->ItemCountMax = ItemCountMax; - pArray->ItemCount = 0; - pArray->ItemSize = ItemSize; - return ERROR_SUCCESS; -} - -void * Array_Insert(PDYNAMIC_ARRAY pArray, const void * NewItems, size_t NewItemCount) -{ - char * NewItemPtr; - - // Try to enlarge the buffer, if needed - if(!EnlargeArray(pArray, pArray->ItemCount + NewItemCount)) - return NULL; - NewItemPtr = pArray->ItemArray + (pArray->ItemCount * pArray->ItemSize); - - // Copy the old item(s), if any - if(NewItems != NULL) - memcpy(NewItemPtr, NewItems, (NewItemCount * pArray->ItemSize)); - - // Increment the size of the array - pArray->ItemCount += NewItemCount; - return NewItemPtr; -} - -void * Array_ItemAt(PDYNAMIC_ARRAY pArray, size_t ItemIndex) -{ - assert(ItemIndex < pArray->ItemCount); - return pArray->ItemArray + (ItemIndex * pArray->ItemSize); -} - -size_t Array_IndexOf(PDYNAMIC_ARRAY pArray, const void * ArrayPtr) -{ - char * ArrayItem = (char *)ArrayPtr; - - assert(pArray->ItemArray <= ArrayItem && ArrayItem <= pArray->ItemArray + (pArray->ItemCount * pArray->ItemSize)); - return ((ArrayItem - pArray->ItemArray) / pArray->ItemSize); -} - -void Array_Free(PDYNAMIC_ARRAY pArray) -{ - if(pArray != NULL && pArray->ItemArray != NULL) - { - CASC_FREE(pArray->ItemArray); - } -} diff --git a/dep/CascLib/src/common/DynamicArray.h b/dep/CascLib/src/common/DynamicArray.h deleted file mode 100644 index 11cefacdcc4..00000000000 --- a/dep/CascLib/src/common/DynamicArray.h +++ /dev/null @@ -1,37 +0,0 @@ -/*****************************************************************************/ -/* DynamicArray.h Copyright (c) Ladislav Zezula 2015 */ -/*---------------------------------------------------------------------------*/ -/* Common array implementation */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 30.10.15 1.00 Lad The first version of DynamicArray.h */ -/*****************************************************************************/ - -#ifndef __DYNAMIC_ARRAY_H__ -#define __DYNAMIC_ARRAY_H__ - -//----------------------------------------------------------------------------- -// Structures - -typedef struct _DYNAMIC_ARRAY -{ - char * ItemArray; // Pointer to items - size_t ItemCountMax; // Current number of items - size_t ItemCount; // Total size of the buffer - size_t ItemSize; // Size of the single item - -} DYNAMIC_ARRAY, *PDYNAMIC_ARRAY; - -//----------------------------------------------------------------------------- -// Functions for managing the array - -int Array_Create_(PDYNAMIC_ARRAY pArray, size_t ItemSize, size_t ItemCountMax); -void * Array_Insert(PDYNAMIC_ARRAY pArray, const void * NewItems, size_t NewItemCount); -void * Array_ItemAt(PDYNAMIC_ARRAY pArray, size_t ItemIndex); -size_t Array_IndexOf(PDYNAMIC_ARRAY pArray, const void * ArrayPtr); -void Array_Free(PDYNAMIC_ARRAY pArray); - -#define Array_Create(pArray, type, ItemCountMax) Array_Create_(pArray, sizeof(type), ItemCountMax) - -#endif // __DYNAMIC_ARRAY__ diff --git a/dep/CascLib/src/common/FileStream.cpp b/dep/CascLib/src/common/FileStream.cpp index 22d7fceb4c2..845e793a6fa 100644 --- a/dep/CascLib/src/common/FileStream.cpp +++ b/dep/CascLib/src/common/FileStream.cpp @@ -219,10 +219,24 @@ static bool BaseFile_Read( #endif // Increment the current file position by number of bytes read - // If the number of bytes read doesn't match to required amount, return false pStream->Base.File.FilePos = ByteOffset + dwBytesRead; - if(dwBytesRead != dwBytesToRead) - SetLastError(ERROR_HANDLE_EOF); + + // If the number of bytes read doesn't match to required amount, return false + // However, Blizzard's CASC handlers read encoded data so that if less than expected + // was read, then they fill the rest with zeros + if(dwBytesRead < dwBytesToRead) + { + if(pStream->dwFlags & STREAM_FLAG_FILL_MISSING) + { + memset((LPBYTE)pvBuffer + dwBytesRead, 0, (dwBytesToRead - dwBytesRead)); + dwBytesRead = dwBytesToRead; + } + else + { + SetLastError(ERROR_HANDLE_EOF); + } + } + return (dwBytesRead == dwBytesToRead); } @@ -597,22 +611,26 @@ static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD { #ifdef PLATFORM_WINDOWS + INTERNET_PORT ServerPort = INTERNET_DEFAULT_HTTP_PORT; HINTERNET hRequest; DWORD dwTemp = 0; bool bFileAvailable = false; int nError = ERROR_SUCCESS; - // Keep compiler happy - dwStreamFlags = dwStreamFlags; + // Check alternate ports + if(dwStreamFlags & STREAM_FLAG_USE_PORT_1119) + { + ServerPort = 1119; + } - // Don't connect to the internet + // Don't download if we are not connected to the internet if(!InternetGetConnectedState(&dwTemp, 0)) nError = GetLastError(); // Initiate the connection to the internet if(nError == ERROR_SUCCESS) { - pStream->Base.Http.hInternet = InternetOpen(_T("CascLib HTTP archive reader"), + pStream->Base.Http.hInternet = InternetOpen(_T("agent/2.17.2.6700"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, @@ -631,7 +649,7 @@ static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD szFileName = BaseHttp_ExtractServerName(szFileName, szServerName); pStream->Base.Http.hConnect = InternetConnect(pStream->Base.Http.hInternet, szServerName, - INTERNET_DEFAULT_HTTP_PORT, + ServerPort, NULL, NULL, INTERNET_SERVICE_HTTP, @@ -651,26 +669,41 @@ static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD if(HttpSendRequest(hRequest, NULL, 0, NULL, 0)) { ULONGLONG FileTime = 0; + LPTSTR szEndPtr; + TCHAR szStatusCode[0x10]; + DWORD dwStatusCode = 404; DWORD dwFileSize = 0; - DWORD dwDataSize; + DWORD dwDataSize = sizeof(szStatusCode); DWORD dwIndex = 0; - // Check if the archive has Last Modified field - dwDataSize = sizeof(ULONGLONG); - if(HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME, &FileTime, &dwDataSize, &dwIndex)) - pStream->Base.Http.FileTime = FileTime; + // Check whether the file exists + if (HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE, szStatusCode, &dwDataSize, &dwIndex)) + dwStatusCode = _tcstoul(szStatusCode, &szEndPtr, 10); - // Verify if the server supports random access - dwDataSize = sizeof(DWORD); - if(HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwFileSize, &dwDataSize, &dwIndex)) + // Copare the statuc code + if (dwStatusCode == 200) { - if(dwFileSize != 0) + // Check if the archive has Last Modified field + dwDataSize = sizeof(ULONGLONG); + if (HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME, &FileTime, &dwDataSize, &dwIndex)) + pStream->Base.Http.FileTime = FileTime; + + // Verify if the server supports random access + dwDataSize = sizeof(DWORD); + if (HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwFileSize, &dwDataSize, &dwIndex)) { - pStream->Base.Http.FileSize = dwFileSize; - pStream->Base.Http.FilePos = 0; - bFileAvailable = true; + if (dwFileSize != 0) + { + pStream->Base.Http.FileSize = dwFileSize; + pStream->Base.Http.FilePos = 0; + bFileAvailable = true; + } } } + else + { + nError = ERROR_FILE_NOT_FOUND; + } } InternetCloseHandle(hRequest); } @@ -681,6 +714,7 @@ static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD if(bFileAvailable == false) { pStream->BaseClose(pStream); + SetLastError(nError); return false; } @@ -723,7 +757,7 @@ static bool BaseHttp_Read( { // Add range request to the HTTP headers // http://www.clevercomponents.com/articles/article015/resuming.asp - _stprintf(szRangeRequest, _T("Range: bytes=%u-%u"), (unsigned int)dwStartOffset, (unsigned int)dwEndOffset); + CascStrPrintf(szRangeRequest, _countof(szRangeRequest), _T("Range: bytes=%u-%u"), (unsigned int)dwStartOffset, (unsigned int)dwEndOffset); HttpAddRequestHeaders(hRequest, szRangeRequest, 0xFFFFFFFF, HTTP_ADDREQ_FLAG_ADD_IF_NEW); // Send the request to the server @@ -963,9 +997,7 @@ static bool BlockStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset) static void BlockStream_Close(TBlockStream * pStream) { // Free the data map, if any - if(pStream->FileBitmap != NULL) - CASC_FREE(pStream->FileBitmap); - pStream->FileBitmap = NULL; + CASC_FREE(pStream->FileBitmap); // Call the base class for closing the stream pStream->BaseClose(pStream); @@ -1026,7 +1058,7 @@ static TFileStream * AllocateFileStream( if(pStream != NULL) { // Zero the entire structure - memset(pStream, 0, StreamSize); + memset(pStream, 0, StreamSize + FileNameSize + sizeof(TCHAR)); pStream->pMaster = pMaster; pStream->dwFlags = dwStreamFlags; @@ -1344,7 +1376,10 @@ static TFileStream * FlatStream_Open(const TCHAR * szFileName, DWORD dwStreamFla // Create new empty stream pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags); if(pStream == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; + } // Do we have a master stream? if(pStream->pMaster != NULL) @@ -1661,7 +1696,7 @@ static void PartStream_Close(TBlockStream * pStream) // Make sure that the header is properly BSWAPed BSWAP_ARRAY32_UNSIGNED(&PartHeader, sizeof(PART_FILE_HEADER)); - sprintf(PartHeader.GameBuildNumber, "%u", (unsigned int)pStream->BuildNumber); + CascStrPrintf(PartHeader.GameBuildNumber, _countof(PartHeader.GameBuildNumber), "%u", (unsigned int)pStream->BuildNumber); // Write the part header pStream->BaseWrite(pStream, &ByteOffset, &PartHeader, sizeof(PART_FILE_HEADER)); @@ -1755,7 +1790,6 @@ static bool PartStream_CreateMirror(TBlockStream * pStream) return true; } - static TFileStream * PartStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags) { TBlockStream * pStream; @@ -2193,9 +2227,7 @@ static void Block4Stream_Close(TBlockStream * pStream) } // Free the data map, if any - if(pStream->FileBitmap != NULL) - CASC_FREE(pStream->FileBitmap); - pStream->FileBitmap = NULL; + CASC_FREE(pStream->FileBitmap); // Do not call the BaseClose function, // we closed all handles already @@ -2246,7 +2278,7 @@ static TFileStream * Block4Stream_Open(const TCHAR * szFileName, DWORD dwStreamF for(int nSuffix = 0; nSuffix < 30; nSuffix++) { // Open the n-th file - _stprintf(szNameBuff, _T("%s.%u"), pStream->szFileName, nSuffix); + CascStrPrintf(szNameBuff, (nNameLength + 4), _T("%s.%u"), pStream->szFileName, nSuffix); if(!pStream->BaseOpen(pStream, szNameBuff, dwBaseFlags)) break; @@ -2368,7 +2400,6 @@ TFileStream * FileStream_CreateFile( // File create failed, delete the stream CASC_FREE(pStream); - pStream = NULL; } // Return the stream diff --git a/dep/CascLib/src/common/FileStream.h b/dep/CascLib/src/common/FileStream.h index 1c72619e393..a2b0c5b9f7d 100644 --- a/dep/CascLib/src/common/FileStream.h +++ b/dep/CascLib/src/common/FileStream.h @@ -28,6 +28,8 @@ #define STREAM_FLAG_READ_ONLY 0x00000100 // Stream is read only #define STREAM_FLAG_WRITE_SHARE 0x00000200 // Allow write sharing when open for write #define STREAM_FLAG_USE_BITMAP 0x00000400 // If the file has a file bitmap, load it and use it +#define STREAM_FLAG_FILL_MISSING 0x00000800 // If less than expected was read from the file, fill the missing part with zeros +#define STREAM_FLAG_USE_PORT_1119 0x00001000 // For HTTP streams, use port 1119 #define STREAM_OPTIONS_MASK 0x0000FF00 // Mask for stream options #define STREAM_PROVIDERS_MASK 0x000000FF // Mask to get stream providers @@ -101,6 +103,12 @@ typedef void (*BLOCK_SAVEMAP)( struct TFileStream * pStream // Pointer to a block-oriented stream ); +typedef void (WINAPI * STREAM_DOWNLOAD_CALLBACK)( + void * pvUserData, + ULONGLONG ByteOffset, + DWORD dwTotalBytes + ); + //----------------------------------------------------------------------------- // Local structures - partial file structure and bitmap footer diff --git a/dep/CascLib/src/common/FileTree.cpp b/dep/CascLib/src/common/FileTree.cpp new file mode 100644 index 00000000000..f753c25f263 --- /dev/null +++ b/dep/CascLib/src/common/FileTree.cpp @@ -0,0 +1,684 @@ +/*****************************************************************************/ +/* FileTree.cpp Copyright (c) Ladislav Zezula 2018 */ +/*---------------------------------------------------------------------------*/ +/* Common implementation of a file tree object for various ROOt file formats */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 29.05.18 1.00 Lad The first version of FileTree.cpp */ +/*****************************************************************************/ + +#define __CASCLIB_SELF__ +#include "../CascLib.h" +#include "../CascCommon.h" + +//----------------------------------------------------------------------------- +// Local defines + +#define START_ITEM_COUNT 0x4000 + +inline DWORD GET_NODE_INT32(void * node, size_t offset) +{ + PDWORD PtrValue = (PDWORD)((LPBYTE)node + offset); + + return PtrValue[0]; +} + +inline void SET_NODE_INT32(void * node, size_t offset, DWORD value) +{ + PDWORD PtrValue = (PDWORD)((LPBYTE)node + offset); + + 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 + +// Inserts a new file node to the file tree. +// If the pointer to file node array changes, the function also rebuilds all maps +PCASC_FILE_NODE CASC_FILE_TREE::InsertNew(PCASC_CKEY_ENTRY pCKeyEntry) +{ + PCASC_FILE_NODE pFileNode; + + // Create a brand new node + pFileNode = InsertNew(); + if(pFileNode != NULL) + { + // Initialize the file node's CKeyEntry + pFileNode->pCKeyEntry = pCKeyEntry; + + // Don't insert the node into any of the arrays here. + // That is the caller's responsibility + } + + return pFileNode; +} + +PCASC_FILE_NODE CASC_FILE_TREE::InsertNew() +{ + PCASC_FILE_NODE pFileNode; + void * SaveItemArray = NodeTable.ItemArray(); // We need to save the array pointers. If it changes, we must rebuild both maps + + // Create a brand new node + pFileNode = (PCASC_FILE_NODE)NodeTable.Insert(1); + if(pFileNode != NULL) + { + // Initialize the file node + pFileNode->FileNameHash = 0; + pFileNode->pCKeyEntry = NULL; + pFileNode->Parent = 0; + pFileNode->NameIndex = 0; + pFileNode->NameLength = 0; + pFileNode->Flags = 0; + + // We need to supply a file data id for the new entry, otherwise the rebuilding function + // will use the uninitialized one + SetExtras(pFileNode, CASC_INVALID_ID, CASC_INVALID_ID, CASC_INVALID_ID); + + // If the array pointer changed or we are close to the size of the array, we need to rebuild the maps + if(NodeTable.ItemArray() != SaveItemArray || (NodeTable.ItemCount() * 3 / 2) > NameMap.HashTableSize()) + { + // Rebuild both maps. Note that rebuilding also inserts all items to the maps, so no need to insert them here + if(!RebuildNameMaps()) + { + pFileNode = NULL; + assert(false); + } + } + } + + return pFileNode; +} + +// Insert the node to the map of FileNameHash -> CASC_FILE_NODE +bool CASC_FILE_TREE::InsertToHashTable(PCASC_FILE_NODE pFileNode) +{ + bool bResult = false; + + // Insert the file node to the table + if(pFileNode->FileNameHash != 0) + bResult = NameMap.InsertObject(pFileNode, &pFileNode->FileNameHash); + return bResult; +} + +// Inserts the file node to the array of file data ids +bool CASC_FILE_TREE::InsertToIdTable(PCASC_FILE_NODE pFileNode) +{ + PCASC_FILE_NODE * RefElement; + DWORD FileDataId = CASC_INVALID_ID; + + if(FileDataIds.IsInitialized()) + { + // Retrieve the file data id + GetExtras(pFileNode, &FileDataId, NULL, NULL); + if(FileDataId != CASC_INVALID_ID) + { + // Sanity check + assert(FileDataId < 0x10000000); + + // Insert the element to the array + RefElement = (PCASC_FILE_NODE *)FileDataIds.InsertAt(FileDataId); + if(RefElement != NULL) + { + RefElement[0] = pFileNode; + return true; + } + } + } + + return false; +} + +bool CASC_FILE_TREE::SetNodePlainName(PCASC_FILE_NODE pFileNode, const char * szPlainName, const char * szPlainNameEnd) +{ + char * szNodeName; + size_t nLength = (szPlainNameEnd - szPlainName); + + // Insert all chars to the name array + szNodeName = (char *)NameTable.Insert(nLength); + if(szNodeName != NULL) + { + // Copy the plain name to the node. Do not include the string terminator + memcpy(szNodeName, szPlainName, nLength); + + // Supply the file name to the file node + pFileNode->NameIndex = (DWORD)NameTable.IndexOf(szNodeName); + pFileNode->NameLength = (USHORT)nLength; + return true; + } + + return false; +} + +bool CASC_FILE_TREE::SetKeyLength(DWORD aKeyLength) +{ + if(aKeyLength > MD5_HASH_SIZE) + return false; + KeyLength = aKeyLength; + return true; +} + +DWORD CASC_FILE_TREE::GetNextFileDataId() +{ + if(FileDataIds.IsInitialized()) + return (DWORD)(FileDataIds.ItemCount() + 1); + return CASC_INVALID_ID; +} + +bool CASC_FILE_TREE::RebuildNameMaps() +{ + PCASC_FILE_NODE pFileNode; + size_t nMaxItems = NodeTable.ItemCountMax(); + + // Free the map of "FullName -> CASC_FILE_NODE" + NameMap.Free(); + + // Create new map map "FullName -> CASC_FILE_NODE" + if(NameMap.Create(nMaxItems, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_NODE, FileNameHash)) != ERROR_SUCCESS) + return false; + + // Reset the entire array, but keep the buffer allocated + FileDataIds.Reset(); + + // Parse all items and insert them to the map + for(size_t i = 0; i < NodeTable.ItemCount(); i++) + { + // Retrieve the n-th object + pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(i); + if(pFileNode != NULL) + { + // Insert it to the map "FileNameHash -> CASC_FILE_NODE" + if(pFileNode->FileNameHash != 0) + InsertToHashTable(pFileNode); + + // Insert it to the array "FileDataId -> CASC_FILE_NODE" + if(FileDataIds.IsInitialized()) + InsertToIdTable(pFileNode); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Public functions + +int CASC_FILE_TREE::Create(DWORD Flags) +{ + PCASC_FILE_NODE pRootNode; + size_t FileNodeSize = FIELD_OFFSET(CASC_FILE_NODE, ExtraValues); + int nError; + + // Initialize the file tree + memset(this, 0, sizeof(CASC_FILE_TREE)); + KeyLength = MD5_HASH_SIZE; + + // Shall we use the data ID in the tree node? + if(Flags & FTREE_FLAG_USE_DATA_ID) + { + // Set the offset of the file data id in the entry + FileDataIdOffset = FileNodeSize; + 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; + } + + // Shall we use the locale ID in the tree node? + if(Flags & FTREE_FLAG_USE_LOCALE_FLAGS) + { + LocaleFlagsOffset = FileNodeSize; + FileNodeSize += sizeof(DWORD); + } + + if(Flags & FTREE_FLAG_USE_CONTENT_FLAGS) + { + ContentFlagsOffset = FileNodeSize; + FileNodeSize += sizeof(DWORD); + } + + // Align the file node size to 8 bytes + FileNodeSize = ALIGN_TO_SIZE(FileNodeSize, 8); + + // Initialize the dynamic array + nError = NodeTable.Create(FileNodeSize, START_ITEM_COUNT); + if(nError == ERROR_SUCCESS) + { + // Create the dynamic array that will hold the node names + nError = NameTable.Create<char>(START_ITEM_COUNT); + if(nError == ERROR_SUCCESS) + { + // Insert the first "root" node, without name + pRootNode = (PCASC_FILE_NODE)NodeTable.Insert(1); + if(pRootNode != NULL) + { + // Initialize the node + memset(pRootNode, 0, NodeTable.ItemSize()); + pRootNode->Parent = CASC_INVALID_INDEX; + pRootNode->NameIndex = CASC_INVALID_INDEX; + pRootNode->Flags = CFN_FLAG_FOLDER; + SetExtras(pRootNode, CASC_INVALID_ID, CASC_INVALID_ID, CASC_INVALID_ID); + } + } + } + + // Create both maps + if(!RebuildNameMaps()) + nError = ERROR_NOT_ENOUGH_MEMORY; + return nError; +} + +void CASC_FILE_TREE::Free() +{ + // Free both arrays + NodeTable.Free(); + NameTable.Free(); + FileDataIds.Free(); + + // Free the name map + NameMap.Free(); + + // Zero the object + memset(this, 0, sizeof(CASC_FILE_TREE)); +} + +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; + + // Sanity checks + assert(szFileName != NULL && szFileName[0] != 0); + assert(pCKeyEntry != NULL); + + // Calculate the file name hash + CmpCtx.FileNameHash = CalcFileNameHash(szFileName); +// CmpCtx.szFileName = szFileName; +// CmpCtx.pThis = this; + + // 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); + if(pFileNode == NULL) + { + // Insert new item + pFileNode = InsertNew(pCKeyEntry); + if(pFileNode != NULL) + { + // Supply the name hash + pFileNode->FileNameHash = CmpCtx.FileNameHash; + + // Set the file data id and the extra values + SetExtras(pFileNode, FileDataId, LocaleFlags, ContentFlags); + + // Insert the file node to the hash map + InsertToHashTable(pFileNode); + + // 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 + SetNodeFileName(pFileNode, szFileName); + + // If we created a new node, we need to increment the reference count + assert(pCKeyEntry->RefCount != 0xFFFF); + pCKeyEntry->RefCount++; + } + } + + return pFileNode; +} + +PCASC_FILE_NODE CASC_FILE_TREE::InsertByHash(PCASC_CKEY_ENTRY pCKeyEntry, ULONGLONG FileNameHash, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags) +{ + PCASC_FILE_NODE pFileNode; + + // Sanity checks + assert(FileDataIds.IsInitialized()); + assert(FileDataId != CASC_INVALID_ID); + assert(FileNameHash != 0); + assert(pCKeyEntry != NULL); + + // Insert the node to the tree by file data id + pFileNode = InsertById(pCKeyEntry, FileDataId, LocaleFlags, ContentFlags); + if(pFileNode != NULL) + { + // Supply the name hash + pFileNode->FileNameHash = FileNameHash; + + // Insert the file node to the hash map + InsertToHashTable(pFileNode); + } + + return pFileNode; +} + +PCASC_FILE_NODE CASC_FILE_TREE::InsertById(PCASC_CKEY_ENTRY pCKeyEntry, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags) +{ + PCASC_FILE_NODE pFileNode; + + // Sanity checks + assert(FileDataIds.IsInitialized()); + assert(FileDataId != CASC_INVALID_ID); + assert(pCKeyEntry != NULL); + + // Check whether the file data id exists in the array of file data ids + if((pFileNode = FindById(FileDataId)) == NULL) + { + // Insert the new file node + pFileNode = InsertNew(pCKeyEntry); + if(pFileNode != NULL) + { + // Set the file data id and the extra values + SetExtras(pFileNode, FileDataId, LocaleFlags, ContentFlags); + + // Insert the file node to the FileDataId array + InsertToIdTable(pFileNode); + + // Increment the number of references + pCKeyEntry->RefCount++; + } + } + + // Return the new or old node + return pFileNode; +} + +PCASC_FILE_NODE CASC_FILE_TREE::ItemAt(size_t nItemIndex) +{ + return (PCASC_FILE_NODE)NodeTable.ItemAt(nItemIndex); +} + +PCASC_FILE_NODE CASC_FILE_TREE::PathAt(char * szBuffer, size_t cchBuffer, size_t nItemIndex) +{ + PCASC_FILE_NODE pFileNode = NULL; + + // If we have FileDataId, then we need to enumerate the files by FileDataId + if(FileDataIds.IsInitialized()) + pFileNode = *(PCASC_FILE_NODE *)FileDataIds.ItemAt(nItemIndex); + else + pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(nItemIndex); + + // Construct the entire path + PathAt(szBuffer, cchBuffer, pFileNode); + return pFileNode; +} + +size_t CASC_FILE_TREE::PathAt(char * szBuffer, size_t cchBuffer, PCASC_FILE_NODE pFileNode) +{ + PCASC_FILE_NODE pParentNode; + const char * szNamePtr; + char * szSaveBuffer = szBuffer; + char * szBufferEnd = szBuffer + cchBuffer - 1; + + if(pFileNode != NULL && pFileNode->Parent != CASC_INVALID_INDEX) + { + // Copy all parents + pParentNode = (PCASC_FILE_NODE)NodeTable.ItemAt(pFileNode->Parent); + if(pParentNode != NULL) + { + // Query the parent and move the buffer + szBuffer = szBuffer + PathAt(szBuffer, cchBuffer, pParentNode); + } + + // Retrieve the node name + szNamePtr = (const char *)NameTable.ItemAt(pFileNode->NameIndex); + + // Check whether we have enough space + if((szBuffer + pFileNode->NameLength) < szBufferEnd) + { + // Copy the path part + memcpy(szBuffer, szNamePtr, pFileNode->NameLength); + szBuffer += pFileNode->NameLength; + + // Append backslash + if((pFileNode->Flags & CFN_FLAG_FOLDER) && ((szBuffer + 1) < szBufferEnd)) + { + *szBuffer++ = (pFileNode->Flags & CFN_FLAG_MOUNT_POINT) ? ':' : '\\'; + } + } + } + + // Terminate buffer with zero + szBuffer[0] = 0; + + // Return length of the copied string + return (szBuffer - szSaveBuffer); +} + +PCASC_FILE_NODE CASC_FILE_TREE::Find(const char * szFullPath, DWORD FileDataId, PCASC_FIND_DATA pFindData) +{ + PCASC_FILE_NODE pFileNode = NULL; + ULONGLONG FileNameHash; + + // Can we search by FileDataId? + if(FileDataIds.IsInitialized() && (FileDataId != CASC_INVALID_ID || IsFileDataIdName(szFullPath, FileDataId))) + { + pFileNode = FindById(FileDataId); + } + else + { + if(szFullPath != NULL && szFullPath[0] != 0) + { + FileNameHash = CalcFileNameHash(szFullPath); + pFileNode = (PCASC_FILE_NODE)NameMap.FindObject(&FileNameHash); + } + } + + // Did we find anything? + if(pFileNode != NULL && pFindData != NULL) + { + GetExtras(pFileNode, &pFindData->dwFileDataId, &pFindData->dwLocaleFlags, &pFindData->dwContentFlags); + pFindData->bCanOpenByName = (pFileNode->FileNameHash != 0); + pFindData->bCanOpenByDataId = (FileDataIdOffset != 0); + } + + return pFileNode; +} + +PCASC_FILE_NODE CASC_FILE_TREE::Find(PCASC_CKEY_ENTRY pCKeyEntry) +{ + PCASC_FILE_NODE pFileNode; + + for(size_t i = 0; i < NodeTable.ItemCount(); i++) + { + pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(i); + if((pFileNode->Flags & (CFN_FLAG_FOLDER | CFN_FLAG_MOUNT_POINT)) == 0) + { + if(pFileNode->pCKeyEntry == pCKeyEntry) + return pFileNode; + } + } + + return NULL; +} + +PCASC_FILE_NODE CASC_FILE_TREE::Find(ULONGLONG FileNameHash) +{ + return (PCASC_FILE_NODE)NameMap.FindObject(&FileNameHash); +} + +PCASC_FILE_NODE CASC_FILE_TREE::FindById(DWORD FileDataId) +{ + PCASC_FILE_NODE * RefElement; + PCASC_FILE_NODE pFileNode = NULL; + + if(FileDataId != CASC_INVALID_ID && FileDataIds.IsInitialized()) + { + // Insert the element to the array + RefElement = (PCASC_FILE_NODE *)FileDataIds.ItemAt(FileDataId); + if(RefElement != NULL) + { + pFileNode = RefElement[0]; + } + } + + return pFileNode; +} + +bool CASC_FILE_TREE::SetNodeFileName(PCASC_FILE_NODE pFileNode, const char * szFileName) +{ + ULONGLONG FileNameHash = 0; + PCASC_FILE_NODE pFolderNode = NULL; + const char * szNodeBegin = szFileName; + char szPathBuffer[MAX_PATH+1]; + size_t nFileNode = NodeTable.IndexOf(pFileNode); + size_t i; + DWORD Parent = 0; + + // 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++) + { + 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(szPathBuffer, 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_MOUNT_POINT : 0; + pFolderNode->Flags |= CFN_FLAG_FOLDER; + + // 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 + szPathBuffer[i] = 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); + + SetNodePlainName(pFileNode, szNodeBegin, szFileName + i); + pFileNode->Parent = Parent; + } + return true; +} + +size_t CASC_FILE_TREE::GetMaxFileIndex() +{ + if(FileDataIds.IsInitialized()) + { + return FileDataIds.ItemCount(); + } + else + { + return NodeTable.ItemCount(); + } +} + +size_t CASC_FILE_TREE::GetCount() +{ + return NodeTable.ItemCount(); +} + +size_t CASC_FILE_TREE::IndexOf(PCASC_FILE_NODE pFileNode) +{ + return NodeTable.IndexOf(pFileNode); +} + +void CASC_FILE_TREE::GetExtras(PCASC_FILE_NODE pFileNode, PDWORD PtrFileDataId, PDWORD PtrLocaleFlags, PDWORD PtrContentFlags) +{ + DWORD FileDataId = CASC_INVALID_ID; + DWORD LocaleFlags = CASC_INVALID_ID; + DWORD ContentFlags = CASC_INVALID_ID; + + // Retrieve the data ID, if supported + if(PtrFileDataId != NULL) + { + if(FileDataIdOffset != 0) + FileDataId = GET_NODE_INT32(pFileNode, FileDataIdOffset); + PtrFileDataId[0] = FileDataId; + } + + // Retrieve the locale ID, if supported + if(PtrLocaleFlags != NULL) + { + if(LocaleFlagsOffset != 0) + LocaleFlags = GET_NODE_INT32(pFileNode, LocaleFlagsOffset); + PtrLocaleFlags[0] = LocaleFlags; + } + + if(PtrContentFlags != NULL) + { + if(ContentFlagsOffset != 0) + ContentFlags = GET_NODE_INT32(pFileNode, ContentFlagsOffset); + PtrContentFlags[0] = ContentFlags; + } +} + +void CASC_FILE_TREE::SetExtras(PCASC_FILE_NODE pFileNode, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags) +{ + // Set the file data ID, if supported + if(FileDataIdOffset != 0) + { + SET_NODE_INT32(pFileNode, FileDataIdOffset, FileDataId); + } + + // Set the locale ID, if supported + if(LocaleFlagsOffset != 0) + { + SET_NODE_INT32(pFileNode, LocaleFlagsOffset, LocaleFlags); + } + + // Set the locale ID, if supported + if(ContentFlagsOffset != 0) + { + SET_NODE_INT32(pFileNode, ContentFlagsOffset, ContentFlags); + } +} diff --git a/dep/CascLib/src/common/FileTree.h b/dep/CascLib/src/common/FileTree.h new file mode 100644 index 00000000000..904da6c4c46 --- /dev/null +++ b/dep/CascLib/src/common/FileTree.h @@ -0,0 +1,116 @@ +/*****************************************************************************/ +/* FileTree.h Copyright (c) Ladislav Zezula 2018 */ +/*---------------------------------------------------------------------------*/ +/* Common implementation of a file tree object for various ROOt file formats */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 29.05.18 1.00 Lad The first version of FileTree.h */ +/*****************************************************************************/ + +#ifndef __FILETREE_H__ +#define __FILETREE_H__ + +//----------------------------------------------------------------------------- +// Structures + +#define FTREE_FLAG_USE_DATA_ID 0x0001 // The FILE_NODE also contains file data ID +#define FTREE_FLAG_USE_LOCALE_FLAGS 0x0002 // The FILE_NODE also contains file locale flags +#define FTREE_FLAG_USE_CONTENT_FLAGS 0x0004 // The FILE_NODE also contains content flags + +#define CFN_FLAG_FOLDER 0x0001 // This item is a folder +#define CFN_FLAG_MOUNT_POINT 0x0002 // This item is a mount point. + +// Common structure for holding a single folder/file node +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. + 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 + + 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); + void Free(); + + // Inserts a new node to the tree; either with name or nameless + PCASC_FILE_NODE InsertByName(PCASC_CKEY_ENTRY pCKeyEntry, const char * szFileName, DWORD FileDataId = CASC_INVALID_ID, DWORD LocaleFlags = CASC_INVALID_ID, DWORD ContentFlags = CASC_INVALID_ID); + PCASC_FILE_NODE InsertByHash(PCASC_CKEY_ENTRY pCKeyEntry, ULONGLONG FileNameHash, DWORD FileDataId, DWORD LocaleFlags = CASC_INVALID_ID, DWORD ContentFlags = CASC_INVALID_ID); + PCASC_FILE_NODE InsertById(PCASC_CKEY_ENTRY pCKeyEntry, DWORD FileDataId, DWORD LocaleFlags = CASC_INVALID_ID, DWORD ContentFlags = CASC_INVALID_ID); + + // Returns an item at the given index. The PathAt also builds the full path of the node + PCASC_FILE_NODE ItemAt(size_t nItemIndex); + PCASC_FILE_NODE PathAt(char * szBuffer, size_t cchBuffer, size_t nItemIndex); + size_t PathAt(char * szBuffer, size_t cchBuffer, PCASC_FILE_NODE pFileNode); + + // Finds a file using its full path, FileDataId or CKey/EKey + PCASC_FILE_NODE Find(const char * szFullPath, DWORD FileDataId, struct _CASC_FIND_DATA * pFindData); + PCASC_FILE_NODE Find(PCASC_CKEY_ENTRY pCKeyEntry); + PCASC_FILE_NODE Find(ULONGLONG FileNameHash); + PCASC_FILE_NODE FindById(DWORD FileDataId); + + // Assigns a file name to the node + bool SetNodeFileName(PCASC_FILE_NODE pFileNode, const char * szFileName); + + // Returns the number of items in the tree + size_t GetMaxFileIndex(); + size_t GetCount(); + + // Returns the index of an item in the tree + size_t IndexOf(PCASC_FILE_NODE pFileNode); + + // Retrieves the extra values from the node (if supported) + void GetExtras(PCASC_FILE_NODE pFileNode, PDWORD PtrFileDataId, PDWORD PtrLocaleFlags, PDWORD PtrContentFlags); + void SetExtras(PCASC_FILE_NODE pFileNode, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags); + + // Change the length of the key + bool SetKeyLength(DWORD KeyLength); + + // Retrieve the maximum FileDataId ever inserted + DWORD GetNextFileDataId(); + + protected: + + PCASC_FILE_NODE InsertNew(PCASC_CKEY_ENTRY pCKeyEntry); + PCASC_FILE_NODE InsertNew(); + bool InsertToHashTable(PCASC_FILE_NODE pFileNode); + bool InsertToIdTable(PCASC_FILE_NODE pFileNode); + + bool SetNodePlainName(PCASC_FILE_NODE pFileNode, const char * szPlainName, const char * szPlainNameEnd); + bool RebuildNameMaps(); + + CASC_ARRAY NodeTable; // Dynamic array that holds all CASC_FILE_NODEs + CASC_ARRAY NameTable; // Dynamic array that holds all node names + + CASC_ARRAY FileDataIds; // Dynamic array that maps FileDataId -> CASC_FILE_NODE + CASC_MAP NameMap; // Map of FileNameHash -> CASC_FILE_NODE + + 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 + DWORD KeyLength; // Actual length of the key supported by the root handler +}; + +typedef CASC_FILE_TREE * PCASC_FILE_TREE; + +#endif // __FILETREE_H__ diff --git a/dep/CascLib/src/common/ListFile.cpp b/dep/CascLib/src/common/ListFile.cpp index dc9a88bf44c..351ea226188 100644 --- a/dep/CascLib/src/common/ListFile.cpp +++ b/dep/CascLib/src/common/ListFile.cpp @@ -15,11 +15,14 @@ //----------------------------------------------------------------------------- // Listfile cache structure +#define LISTFILE_FLAG_USES_FILEDATAID 0x0001 // A new CSV format, containing FileDataId; FullFileName + typedef struct _LISTFILE_CACHE { - char * pBegin; // The begin of the listfile cache - char * pPos; // Current position in the cache - char * pEnd; // The last character in the file cache + char * pBegin; // The begin of the listfile cache + char * pPos; // Current position in the cache + char * pEnd; // The last character in the file cache + DWORD Flags; // Followed by the cache (variable length) @@ -28,7 +31,7 @@ typedef struct _LISTFILE_CACHE //----------------------------------------------------------------------------- // Creating the listfile cache for the given amount of data -static PLISTFILE_CACHE CreateListFileCache(DWORD dwFileSize) +static PLISTFILE_CACHE ListFile_CreateCache(DWORD dwFileSize) { PLISTFILE_CACHE pCache; @@ -40,12 +43,81 @@ static PLISTFILE_CACHE CreateListFileCache(DWORD dwFileSize) pCache->pBegin = pCache->pPos = (char *)(pCache + 1); pCache->pEnd = pCache->pBegin + dwFileSize; + pCache->Flags = 0; } // Return the cache return pCache; } +static char * ListFile_SkipSpaces(PLISTFILE_CACHE pCache) +{ + // Skip newlines, spaces, tabs and another non-printable stuff + while(pCache->pPos < pCache->pEnd && pCache->pPos[0] <= 0x20) + pCache->pPos++; + + // Remember the begin of the line + return pCache->pPos; +} + +static void ListFile_CheckFormat(PLISTFILE_CACHE pCache) +{ + // Only if the listfile is greatger than 2 MB + if((pCache->pEnd - pCache->pBegin) > 0x100000) + { + char * szPtr = pCache->pBegin; + size_t nDigitCount = 0; + + // Calculate the amount of digits + while(nDigitCount <= 20 && '0' <= szPtr[nDigitCount] && szPtr[nDigitCount] <= '9') + nDigitCount++; + + // There must be a semicolon after + if(nDigitCount <= 10 && szPtr[nDigitCount] == ';') + { + pCache->Flags |= LISTFILE_FLAG_USES_FILEDATAID; + } + } +} + +static int ListFile_GetFileDataId(PLISTFILE_CACHE pCache, PDWORD PtrFileDataId) +{ + char * szLineBegin = ListFile_SkipSpaces(pCache); + char * szLineEnd; + DWORD dwNewInt32 = 0; + DWORD dwInt32 = 0; + + // Set the limit for loading the number + szLineEnd = CASCLIB_MIN((szLineBegin + 20), pCache->pEnd); + + // Extract decimal digits from the string + while(szLineBegin < szLineEnd && '0' <= szLineBegin[0] && szLineBegin[0] <= '9') + { + // Check integer overflow + dwNewInt32 = (dwInt32 * 10) + (szLineBegin[0] - '0'); + if(dwNewInt32 < dwInt32) + return ERROR_BAD_FORMAT; + + dwInt32 = dwNewInt32; + szLineBegin++; + } + + // There must still be some space + if(szLineBegin < szLineEnd) + { + // There must be a semicolon after the decimal integer + // The decimal integer must be smaller than 10 MB (files) + if(szLineBegin[0] != ';' || dwInt32 >= 0xA00000) + return ERROR_BAD_FORMAT; + + pCache->pPos = szLineBegin + 1; + PtrFileDataId[0] = dwInt32; + return ERROR_SUCCESS; + } + + return ERROR_NO_MORE_FILES; +} + //----------------------------------------------------------------------------- // Functions for parsing an external listfile @@ -65,13 +137,16 @@ void * ListFile_OpenExternal(const TCHAR * szListFile) { // Create the in-memory cache for the entire listfile // The listfile does not have any data loaded yet - pCache = CreateListFileCache((DWORD)FileSize); + pCache = ListFile_CreateCache((DWORD)FileSize); if(pCache != NULL) { - if(!FileStream_Read(pStream, NULL, pCache->pBegin, (DWORD)FileSize)) + if(FileStream_Read(pStream, NULL, pCache->pBegin, (DWORD)FileSize)) { - ListFile_Free(pCache); - pCache = NULL; + ListFile_CheckFormat(pCache); + } + else + { + CASC_FREE(pCache); } } } @@ -89,7 +164,7 @@ void * ListFile_FromBuffer(LPBYTE pbBuffer, DWORD cbBuffer) // Create the in-memory cache for the entire listfile // The listfile does not have any data loaded yet - pCache = CreateListFileCache(cbBuffer); + pCache = ListFile_CreateCache(cbBuffer); if(pCache != NULL) memcpy(pCache->pBegin, pbBuffer, cbBuffer); @@ -105,7 +180,7 @@ bool ListFile_VerifyMD5(void * pvListFile, LPBYTE pbHashMD5) assert(pCache->pPos == pCache->pBegin); // Verify the MD5 hash for the entire block - return VerifyDataBlockHash(pCache->pBegin, (DWORD)(pCache->pEnd - pCache->pBegin), pbHashMD5); + return CascVerifyDataBlockHash(pCache->pBegin, (DWORD)(pCache->pEnd - pCache->pBegin), pbHashMD5); } size_t ListFile_GetNextLine(void * pvListFile, const char ** pszLineBegin, const char ** pszLineEnd) @@ -116,17 +191,15 @@ size_t ListFile_GetNextLine(void * pvListFile, const char ** pszLineBegin, const char * szLineEnd; // Skip newlines, spaces, tabs and another non-printable stuff - while(pCache->pPos < pCache->pEnd && pCache->pPos[0] <= 0x20) - pCache->pPos++; - // Remember the begin of the line - szLineBegin = pCache->pPos; + szLineBegin = ListFile_SkipSpaces(pCache); // Copy the remaining characters while(pCache->pPos < pCache->pEnd) { // If we have found a newline, stop loading - if(pCache->pPos[0] == 0x0D || pCache->pPos[0] == 0x0A) + // Note: the 0x85 char came from Overwatch build 24919 + if(pCache->pPos[0] == 0x0A || pCache->pPos[0] == 0x0D || pCache->pPos[0] == 0x85) break; // Blizzard listfiles can also contain information about patch: @@ -169,14 +242,28 @@ size_t ListFile_GetNextLine(void * pvListFile, char * szBuffer, size_t nMaxChars return nLength; } -size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer, size_t nMaxChars) +size_t ListFile_GetNext(void * pvListFile, char * szBuffer, size_t nMaxChars, PDWORD PtrFileDataId) { + PLISTFILE_CACHE pCache = (PLISTFILE_CACHE)pvListFile; size_t nLength = 0; int nError = ERROR_SUCCESS; // Check for parameters for(;;) { + DWORD FileDataId = CASC_INVALID_ID; + + // If this is a CSV-format listfile, we need to extract the FileDataId + // Lines that contain bogus data, invalid numbers or too big values will be skipped + if(pCache->Flags & LISTFILE_FLAG_USES_FILEDATAID) + { + nError = ListFile_GetFileDataId(pCache, &FileDataId); + if(nError == ERROR_NO_MORE_FILES) + break; + if(nError != ERROR_SUCCESS || FileDataId == CASC_INVALID_ID) + continue; + } + // Read the (next) line nLength = ListFile_GetNextLine(pvListFile, szBuffer, nMaxChars); if(nLength == 0) @@ -185,12 +272,9 @@ size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer, break; } - // If some mask entered, check it - if(CheckWildCard(szBuffer, szMask)) - { - nError = ERROR_SUCCESS; - break; - } + // Give the file data id and return true + PtrFileDataId[0] = FileDataId; + return nLength; } if(nError != ERROR_SUCCESS) @@ -198,163 +282,22 @@ size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer, return nLength; } -void ListFile_Free(void * pvListFile) +LPBYTE ListFile_GetData(void * pvListFile, PDWORD PtrDataSize) { - if(pvListFile != NULL) - { - CASC_FREE(pvListFile); - } -} - -//----------------------------------------------------------------------------- -// Functions for creating a listfile map - -#define LISTMAP_INITIAL 0x100000 - -static PLISTFILE_MAP ListMap_Create() -{ - PLISTFILE_MAP pListMap; - size_t cbToAllocate; - - // Create buffer for the listfile - // Note that because the listfile is quite big and CASC_REALLOC - // is a costly operation, we want to have as few reallocs as possible. - cbToAllocate = sizeof(LISTFILE_MAP) + LISTMAP_INITIAL; - pListMap = (PLISTFILE_MAP)CASC_ALLOC(BYTE, cbToAllocate); - if(pListMap != NULL) - { - // Fill the listfile buffer - memset(pListMap, 0, sizeof(LISTFILE_MAP)); - pListMap->cbBufferMax = LISTMAP_INITIAL; - } - - return pListMap; -} - -static PLISTFILE_MAP ListMap_InsertName(PLISTFILE_MAP pListMap, const char * szFileName, size_t nLength) -{ - PLISTFILE_ENTRY pListEntry; - size_t cbToAllocate; - size_t cbEntrySize; - - // Make sure there is enough space in the list map - cbEntrySize = sizeof(LISTFILE_ENTRY) + nLength; - cbEntrySize = ALIGN_TO_SIZE(cbEntrySize, 8); - if((pListMap->cbBuffer + cbEntrySize) > pListMap->cbBufferMax) - { - cbToAllocate = sizeof(LISTFILE_MAP) + (pListMap->cbBufferMax * 3) / 2; - pListMap = (PLISTFILE_MAP)CASC_REALLOC(BYTE, pListMap, cbToAllocate); - if(pListMap == NULL) - return NULL; - - pListMap->cbBufferMax = (pListMap->cbBufferMax * 3) / 2; - } - - // Get the pointer to the first entry - pListEntry = (PLISTFILE_ENTRY)((LPBYTE)(pListMap + 1) + pListMap->cbBuffer); - pListEntry->FileNameHash = CalcFileNameHash(szFileName); - pListEntry->cbEntrySize = (DWORD)cbEntrySize; - - // Copy the file name to the entry - memcpy(pListEntry->szFileName, szFileName, nLength); - pListEntry->szFileName[nLength] = 0; - - // Move the next entry - pListMap->cbBuffer += cbEntrySize; - pListMap->nEntries++; - return pListMap; -} - -static PLISTFILE_MAP ListMap_Finish(PLISTFILE_MAP pListMap) -{ - PLISTFILE_ENTRY pListEntry; - PCASC_MAP pMap; - LPBYTE pbEntry; - - // Sanity check - assert(pListMap->pNameMap == NULL); - - // Create the map - pListMap->pNameMap = pMap = Map_Create((DWORD)pListMap->nEntries, sizeof(ULONGLONG), 0); - if(pListMap->pNameMap == NULL) - { - ListFile_FreeMap(pListMap); - return NULL; - } - - // Fill the map - pbEntry = (LPBYTE)(pListMap + 1); - for(size_t i = 0; i < pListMap->nEntries; i++) - { - // Get the listfile entry - pListEntry = (PLISTFILE_ENTRY)pbEntry; - pbEntry += pListEntry->cbEntrySize; - - // Insert the entry to the map - Map_InsertObject(pMap, pListEntry, &pListEntry->FileNameHash); - } - - return pListMap; -} - -PLISTFILE_MAP ListFile_CreateMap(const TCHAR * szListFile) -{ - PLISTFILE_MAP pListMap = NULL; - void * pvListFile; - char szFileName[MAX_PATH+1]; - size_t nLength; + PLISTFILE_CACHE pCache = (PLISTFILE_CACHE)pvListFile; + LPBYTE pbData = NULL; + DWORD cbData = 0; - // Only if the listfile name has been given - if(szListFile != NULL) + // Get data from the list file cache + if (pvListFile != NULL) { - // Create map for the listfile - pListMap = ListMap_Create(); - if(pListMap != NULL) - { - // Open the external listfile - pvListFile = ListFile_OpenExternal(szListFile); - if(pvListFile != NULL) - { - // Go through the entire listfile and insert each name to the map - while((nLength = ListFile_GetNext(pvListFile, "*", szFileName, MAX_PATH)) != 0) - { - // Insert the file name to the map - pListMap = ListMap_InsertName(pListMap, szFileName, nLength); - if(pListMap == NULL) - break; - } - - if(pListMap == NULL) - { - // Finish the listfile map - pListMap = ListMap_Finish(pListMap); - } - - // Free the listfile - ListFile_Free(pvListFile); - } - } + pbData = (LPBYTE)pCache->pBegin; + cbData = (DWORD)(pCache->pEnd - pCache->pBegin); } - // Return the created map - return pListMap; -} - -const char * ListFile_FindName(PLISTFILE_MAP pListMap, ULONGLONG FileNameHash) -{ - PLISTFILE_ENTRY pListEntry = NULL; - - if(pListMap != NULL) - pListEntry = (PLISTFILE_ENTRY)Map_FindObject(pListMap->pNameMap, &FileNameHash, NULL); - return (pListEntry != NULL) ? pListEntry->szFileName : ""; + // Give the data to the caller + if (PtrDataSize != NULL) + PtrDataSize[0] = cbData; + return pbData; } -void ListFile_FreeMap(PLISTFILE_MAP pListMap) -{ - if(pListMap != NULL) - { - if(pListMap->pNameMap != NULL) - Map_Free(pListMap->pNameMap); - CASC_FREE(pListMap); - } -} diff --git a/dep/CascLib/src/common/ListFile.h b/dep/CascLib/src/common/ListFile.h index 84bec3ed751..1333b52c2b5 100644 --- a/dep/CascLib/src/common/ListFile.h +++ b/dep/CascLib/src/common/ListFile.h @@ -12,28 +12,6 @@ #define __LISTFILE_H__ //----------------------------------------------------------------------------- -// Structures - -typedef struct _LISTFILE_ENTRY -{ - ULONGLONG FileNameHash; // Hash of the file name - DWORD cbEntrySize; // Length of this entry, in bytes - char szFileName[1]; // File name, aligned to 8-byte boundary - -} LISTFILE_ENTRY, *PLISTFILE_ENTRY; - -typedef struct _LISTFILE_MAP -{ - PCASC_MAP pNameMap; // Map of hash-to-name - size_t cbBufferMax; // Total size of the buffer, in bytes - size_t cbBuffer; // Current size of the buffer, in bytes - size_t nEntries; // Number of entries - - // First LISTFILE_ENTRY starts here - -} LISTFILE_MAP, *PLISTFILE_MAP; - -//----------------------------------------------------------------------------- // Functions for parsing an external listfile void * ListFile_OpenExternal(const TCHAR * szListFile); @@ -41,14 +19,7 @@ void * ListFile_FromBuffer(LPBYTE pbBuffer, DWORD cbBuffer); bool ListFile_VerifyMD5(void * pvListFile, LPBYTE pbHashMD5); size_t ListFile_GetNextLine(void * pvListFile, const char ** pszLineBegin, const char ** pszLineEnd); size_t ListFile_GetNextLine(void * pvListFile, char * szBuffer, size_t nMaxChars); -size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer, size_t nMaxChars); -void ListFile_Free(void * pvListFile); - -//----------------------------------------------------------------------------- -// Functions for creating a listfile map - -PLISTFILE_MAP ListFile_CreateMap(const TCHAR * szListFile); -const char * ListFile_FindName(PLISTFILE_MAP pListMap, ULONGLONG FileNameHash); -void ListFile_FreeMap(PLISTFILE_MAP pListMap); +size_t ListFile_GetNext(void * pvListFile, char * szBuffer, size_t nMaxChars, PDWORD PtrFileDataId); +LPBYTE ListFile_GetData(void * pvListFile, PDWORD PtrDataSize); #endif // __LISTFILE_H__ diff --git a/dep/CascLib/src/common/Map.cpp b/dep/CascLib/src/common/Map.cpp deleted file mode 100644 index aa1e2a2ada1..00000000000 --- a/dep/CascLib/src/common/Map.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/*****************************************************************************/ -/* Map.cpp Copyright (c) Ladislav Zezula 2014 */ -/*---------------------------------------------------------------------------*/ -/* Implementation of map for CascLib */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 10.06.14 1.00 Lad The first version of Map.cpp */ -/*****************************************************************************/ - -#define __CASCLIB_SELF__ -#include "../CascLib.h" -#include "../CascCommon.h" - -//----------------------------------------------------------------------------- -// Local functions - -// Returns the extension, right after "." -static const char * String_GetExtension(const char * szString) -{ - const char * szExtension = strrchr(szString, '.'); - return (szExtension != NULL) ? szExtension + 1 : NULL; -} - -static DWORD CalcHashIndex_Key(PCASC_MAP pMap, void * pvKey) -{ - LPBYTE pbKey = (LPBYTE)pvKey; - DWORD dwHash = 0x7EEE7EEE; - - // Construct the hash from the first 8 digits - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[0]; - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[1]; - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[2]; - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[3]; - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[4]; - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[5]; - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[6]; - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[7]; - - // Return the hash limited by the table size - return (dwHash % pMap->TableSize); -} - -static DWORD CalcHashIndex_String(PCASC_MAP pMap, const char * szString, const char * szStringEnd) -{ - LPBYTE pbKeyEnd = (LPBYTE)szStringEnd; - LPBYTE pbKey = (LPBYTE)szString; - DWORD dwHash = 0x7EEE7EEE; - - // Hash the string itself - while(pbKey < pbKeyEnd) - { - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ AsciiToUpperTable_BkSlash[pbKey[0]]; - pbKey++; - } - - // Return the hash limited by the table size - return (dwHash % pMap->TableSize); -} - -static bool CompareObject_Key(PCASC_MAP pMap, void * pvObject, void * pvKey) -{ - LPBYTE pbObjectKey = (LPBYTE)pvObject + pMap->KeyOffset; - - return (memcmp(pbObjectKey, pvKey, pMap->KeyLength) == 0); -} - -static bool CompareObject_String( - PCASC_MAP pMap, - const char * szExistingString, - const char * szString, - const char * szStringEnd) -{ - // Keep compiler happy - CASCLIB_UNUSED(pMap); - - // Compare the whole part, case insensitive - while(szString < szStringEnd) - { - if(AsciiToUpperTable_BkSlash[*szExistingString] != AsciiToUpperTable_BkSlash[*szString]) - return false; - - szExistingString++; - szString++; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Public functions - -PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwKeyOffset) -{ - PCASC_MAP pMap; - size_t cbToAllocate; - size_t dwTableSize; - - // Calculate the size of the table - dwTableSize = (dwMaxItems * 3 / 2) | 0x01; - - // Allocate new map for the objects - cbToAllocate = sizeof(CASC_MAP) + (dwTableSize * sizeof(void *)); - pMap = (PCASC_MAP)CASC_ALLOC(LPBYTE, cbToAllocate); - if(pMap != NULL) - { - memset(pMap, 0, cbToAllocate); - pMap->KeyLength = dwKeyLength; - pMap->TableSize = dwTableSize; - pMap->KeyOffset = dwKeyOffset; - } - - // Return the allocated map - return pMap; -} - -size_t Map_EnumObjects(PCASC_MAP pMap, void **ppvArray) -{ - size_t nIndex = 0; - - // Verify pointer to the map - if(pMap != NULL && ppvArray != NULL) - { - // Enumerate all items in main table - for(size_t i = 0; i < pMap->TableSize; i++) - { - // Is that cell valid? - if(pMap->HashTable[i] != NULL) - { - ppvArray[nIndex++] = pMap->HashTable[i]; - } - } - - return pMap->ItemCount; - } - - return 0; -} - -void * Map_FindObject(PCASC_MAP pMap, void * pvKey, PDWORD PtrIndex) -{ - void * pvObject; - DWORD dwHashIndex; - - // Verify pointer to the map - if(pMap != NULL) - { - // Construct the main index - dwHashIndex = CalcHashIndex_Key(pMap, pvKey); - while(pMap->HashTable[dwHashIndex] != NULL) - { - // Get the pointer at that position - pvObject = pMap->HashTable[dwHashIndex]; - - // Compare the hash - if(CompareObject_Key(pMap, pvObject, pvKey)) - { - if(PtrIndex != NULL) - PtrIndex[0] = dwHashIndex; - return pvObject; - } - - // Move to the next entry - dwHashIndex = (dwHashIndex + 1) % pMap->TableSize; - } - } - - // Not found, sorry - return NULL; -} - -bool Map_InsertObject(PCASC_MAP pMap, void * pvNewObject, void * pvKey) -{ - void * pvExistingObject; - DWORD dwHashIndex; - - // Verify pointer to the map - if(pMap != NULL) - { - // Limit check - if((pMap->ItemCount + 1) >= pMap->TableSize) - return false; - - // Construct the hash index - dwHashIndex = CalcHashIndex_Key(pMap, pvKey); - while(pMap->HashTable[dwHashIndex] != NULL) - { - // Get the pointer at that position - pvExistingObject = pMap->HashTable[dwHashIndex]; - - // Check if hash being inserted conflicts with an existing hash - if(CompareObject_Key(pMap, pvExistingObject, pvKey)) - return false; - - // Move to the next entry - dwHashIndex = (dwHashIndex + 1) % pMap->TableSize; - } - - // Insert at that position - pMap->HashTable[dwHashIndex] = pvNewObject; - pMap->ItemCount++; - return true; - } - - // Failed - return false; -} - -bool Map_InsertString(PCASC_MAP pMap, const char * szString, bool bCutExtension) -{ - const char * szExistingString; - const char * szStringEnd = NULL; - DWORD dwHashIndex; - - // Verify pointer to the map - if(pMap != NULL) - { - // Limit check - if((pMap->ItemCount + 1) >= pMap->TableSize) - return false; - - // Retrieve the length of the string without extension - if(bCutExtension) - szStringEnd = String_GetExtension(szString); - if(szStringEnd == NULL) - szStringEnd = szString + strlen(szString); - - // Construct the hash index - dwHashIndex = CalcHashIndex_String(pMap, szString, szStringEnd); - while(pMap->HashTable[dwHashIndex] != NULL) - { - // Get the pointer at that position - szExistingString = (const char *)pMap->HashTable[dwHashIndex]; - - // Check if hash being inserted conflicts with an existing hash - if(CompareObject_String(pMap, szExistingString, szString, szStringEnd)) - return false; - - // Move to the next entry - dwHashIndex = (dwHashIndex + 1) % pMap->TableSize; - } - - // Insert at that position - pMap->HashTable[dwHashIndex] = (void *)szString; - pMap->ItemCount++; - return true; - } - - // Failed - return false; -} - -const char * Map_FindString(PCASC_MAP pMap, const char * szString, const char * szStringEnd) -{ - const char * szExistingString; - DWORD dwHashIndex; - - // Verify pointer to the map - if(pMap != NULL) - { - // Construct the main index - dwHashIndex = CalcHashIndex_String(pMap, szString, szStringEnd); - while(pMap->HashTable[dwHashIndex] != NULL) - { - // Get the pointer at that position - szExistingString = (const char *)pMap->HashTable[dwHashIndex]; - - // Compare the hash - if(CompareObject_String(pMap, szExistingString, szString, szStringEnd)) - return szExistingString; - - // Move to the next entry - dwHashIndex = (dwHashIndex + 1) % pMap->TableSize; - } - } - - // Not found, sorry - return NULL; -} - -void Map_Free(PCASC_MAP pMap) -{ - if(pMap != NULL) - { - CASC_FREE(pMap); - } -} diff --git a/dep/CascLib/src/common/Map.h b/dep/CascLib/src/common/Map.h index 2b590578105..13799eb0528 100644 --- a/dep/CascLib/src/common/Map.h +++ b/dep/CascLib/src/common/Map.h @@ -8,35 +8,355 @@ /* 10.06.14 1.00 Lad The first version of Map.h */ /*****************************************************************************/ -#ifndef __HASHTOPTR_H__ -#define __HASHTOPTR_H__ +#ifndef __CASC_MAP_H__ +#define __CASC_MAP_H__ //----------------------------------------------------------------------------- // Structures -#define KEY_LENGTH_STRING 0xFFFFFFFF // Pass this to Map_Create as dwKeyLength when you want map of string->object +#define MIN_HASH_TABLE_SIZE 0x00000100 // The smallest size of the hash table. +#define MAX_HASH_TABLE_SIZE 0x00800000 // The largest size of the hash table. Should be enough for any game. -typedef struct _CASC_MAP +typedef int (*PFNCOMPAREFUNC)(const void * pvObjectKey, const void * pvKey, size_t nKeyLength); +typedef DWORD (*PFNHASHFUNC)(void * pvKey, size_t nKeyLength); + +typedef enum _KEY_TYPE +{ + KeyIsHash, // Use when the key is already a hash. If specified, the map will not hash the key + KeyIsArbitrary, // The key is an arbitrary array of bytes. The map will hash it to get a map index + KeyIsString // Use when the key is a string +} KEY_TYPE, *PKEY_TYPE; + +#define KEY_LENGTH_STRING 0xFFFFFFFF + +//----------------------------------------------------------------------------- +// Hashing functions + +// Used when the key is a hash already - no need to hash it again +inline DWORD CalcHashValue_Hash(void * pvKey, size_t /* nKeyLength */) +{ + // Get the hash directly as value + return ConvertBytesToInteger_4((LPBYTE)pvKey); +} + +// Calculates hash value from a key +inline DWORD CalcHashValue_Key(void * pvKey, size_t nKeyLength) { - size_t TableSize; - size_t ItemCount; // Number of items in the map - size_t KeyOffset; // How far is the hash from the begin of the structure (in bytes) - size_t KeyLength; // Length of the hash key - void * HashTable[1]; // Pointer table + LPBYTE pbKey = (LPBYTE)pvKey; + DWORD dwHash = 0x7EEE7EEE; -} CASC_MAP, *PCASC_MAP; + // Construct the hash from the key + for(DWORD i = 0; i < nKeyLength; i++) + dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[i]; -typedef bool (*MAP_COMPARE)(PCASC_MAP pMap, void * pvObject, void * pvKey); + // Return the hash limited by the table size + return dwHash; +} + +// Calculates hash value from a string +inline DWORD CalcHashValue_String(const char * szString, const char * szStringEnd) +{ + LPBYTE pbKeyEnd = (LPBYTE)szStringEnd; + LPBYTE pbKey = (LPBYTE)szString; + DWORD dwHash = 0x7EEE7EEE; + + // Hash the string itself + while(pbKey < pbKeyEnd) + { + dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ AsciiToUpperTable_BkSlash[pbKey[0]]; + pbKey++; + } + + // Return the hash limited by the table size + return dwHash; +} //----------------------------------------------------------------------------- -// Functions +// Map implementation + +class CASC_MAP +{ + public: + + CASC_MAP() + { + PfnCalcHashValue = NULL; + m_HashTable = NULL; + m_HashTableSize = 0; + m_ItemCount = 0; + m_KeyOffset = 0; + m_KeyLength = 0; + m_bKeyIsHash = false; + } + + ~CASC_MAP() + { + Free(); + } + + int Create(size_t MaxItems, size_t KeyLength, size_t KeyOffset, KEY_TYPE KeyType = KeyIsHash) + { + // Set the class variables + m_KeyLength = CASCLIB_MAX(KeyLength, 8); + m_KeyOffset = KeyOffset; + m_ItemCount = 0; + + // Setup the hashing function + switch(KeyType) + { + case KeyIsHash: + PfnCalcHashValue = CalcHashValue_Hash; + break; + + case KeyIsArbitrary: + PfnCalcHashValue = CalcHashValue_Key; + break; + + case KeyIsString: + PfnCalcHashValue = NULL; + break; + + default: + assert(false); + return ERROR_NOT_SUPPORTED; + } + + // Calculate the hash table size. Take 133% of the item count and round it up to the next power of two + // This will make the hash table indexes somewhat more resilient against count changes and will make + // e.g. file order in the file tree more stable. + m_HashTableSize = GetNearestPowerOfTwo(MaxItems * 4 / 3); + if(m_HashTableSize == 0) + 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; + } + + void * FindObject(void * pvKey, PDWORD PtrIndex = NULL) + { + void * pvObject; + DWORD dwHashIndex; + + // Verify pointer to the map + if(m_HashTable != NULL) + { + // Construct the hash index + dwHashIndex = HashToIndex(PfnCalcHashValue(pvKey, m_KeyLength)); + + // Search the hash table + while((pvObject = m_HashTable[dwHashIndex]) != NULL) + { + // Compare the hash + if(CompareObject_Key(pvObject, pvKey)) + { + if(PtrIndex != NULL) + PtrIndex[0] = dwHashIndex; + return pvObject; + } + + // Move to the next entry + dwHashIndex = HashToIndex(dwHashIndex + 1); + } + } + + // Not found, sorry + return NULL; + } + + bool InsertObject(void * pvNewObject, void * pvKey) + { + void * pvExistingObject; + DWORD dwHashIndex; + + // Verify pointer to the map + if(m_HashTable != NULL) + { + // Limit check + if((m_ItemCount + 1) >= m_HashTableSize) + return false; + + // Construct the hash index + dwHashIndex = HashToIndex(PfnCalcHashValue(pvKey, m_KeyLength)); + + // Search the hash table + while((pvExistingObject = m_HashTable[dwHashIndex]) != NULL) + { + // Check if hash being inserted conflicts with an existing hash + if(CompareObject_Key(pvExistingObject, pvKey)) + return false; + + // Move to the next entry + dwHashIndex = HashToIndex(dwHashIndex + 1); + } + + // Insert at that position + m_HashTable[dwHashIndex] = pvNewObject; + m_ItemCount++; + return true; + } + + // Failed + return false; + } + + const char * FindString(const char * szString, const char * szStringEnd) + { + const char * szExistingString; + DWORD dwHashIndex; + + // Verify pointer to the map + if(m_HashTable != NULL) + { + // Construct the main index + dwHashIndex = HashToIndex(CalcHashValue_String(szString, szStringEnd)); + + // Search the hash table + while((szExistingString = (const char *)m_HashTable[dwHashIndex]) != NULL) + { + // Compare the hash + if(CompareObject_String(szExistingString, szString, szStringEnd)) + return szExistingString; + + // Move to the next entry + dwHashIndex = HashToIndex(dwHashIndex + 1); + } + } + + // Not found, sorry + return NULL; + } + + bool InsertString(const char * szString, bool bCutExtension) + { + const char * szExistingString; + const char * szStringEnd = NULL; + DWORD dwHashIndex; + + // Verify pointer to the map + if(m_HashTable != NULL) + { + // Limit check + if((m_ItemCount + 1) >= m_HashTableSize) + return false; + + // Retrieve the length of the string without extension + if(bCutExtension) + szStringEnd = GetFileExtension(szString); + else + szStringEnd = szString + strlen(szString); + + // Construct the hash index + dwHashIndex = HashToIndex(CalcHashValue_String(szString, szStringEnd)); + + // Search the hash table + while((szExistingString = (const char *)m_HashTable[dwHashIndex]) != NULL) + { + // Check if hash being inserted conflicts with an existing hash + if(CompareObject_String(szExistingString, szString, szStringEnd)) + return false; + + // Move to the next entry + dwHashIndex = HashToIndex(dwHashIndex + 1); + } + + // Insert at that position + m_HashTable[dwHashIndex] = (void *)szString; + m_ItemCount++; + return true; + } + + // Failed + return false; + } + + void * ItemAt(size_t nIndex) + { + assert(nIndex < m_HashTableSize); + return m_HashTable[nIndex]; + } + + size_t HashTableSize() + { + return m_HashTableSize; + } + + size_t ItemCount() + { + return m_ItemCount; + } + + bool IsInitialized() + { + return (m_HashTable && m_HashTableSize); + } + + void Free() + { + PfnCalcHashValue = NULL; + CASC_FREE(m_HashTable); + m_HashTableSize = 0; + } + + protected: + + DWORD HashToIndex(DWORD HashValue) + { + return HashValue & (m_HashTableSize - 1); + } + + bool CompareObject_Key(void * pvObject, void * pvKey) + { + LPBYTE pbObjectKey = (LPBYTE)pvObject + m_KeyOffset; + return (memcmp(pbObjectKey, pvKey, m_KeyLength) == 0); + } + + bool CompareObject_String(const char * szExistingString, const char * szString, const char * szStringEnd) + { + // Compare the whole part, case insensitive + while(szString < szStringEnd) + { + if(AsciiToUpperTable_BkSlash[*szExistingString] != AsciiToUpperTable_BkSlash[*szString]) + return false; + + szExistingString++; + szString++; + } + + return true; + } + + size_t GetNearestPowerOfTwo(size_t MaxItems) + { + size_t PowerOfTwo; + + // Round the hash table size up to the nearest power of two + for(PowerOfTwo = MIN_HASH_TABLE_SIZE; PowerOfTwo < MAX_HASH_TABLE_SIZE; PowerOfTwo <<= 1) + { + if(PowerOfTwo > MaxItems) + { + return PowerOfTwo; + } + } + + // If the hash table is too big, we cannot create the map + assert(false); + return 0; + } -PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwKeyOffset); -size_t Map_EnumObjects(PCASC_MAP pMap, void **ppvArray); -void * Map_FindObject(PCASC_MAP pMap, void * pvKey, PDWORD PtrIndex); -bool Map_InsertObject(PCASC_MAP pMap, void * pvNewObject, void * pvKey); -const char * Map_FindString(PCASC_MAP pMap, const char * szString, const char * szStringEnd); -bool Map_InsertString(PCASC_MAP pMap, const char * szString, bool bCutExtension); -void Map_Free(PCASC_MAP pMap); + PFNHASHFUNC PfnCalcHashValue; + void ** m_HashTable; // Hash table + size_t m_HashTableSize; // Size of the hash table, in entries. Always a power of two. + size_t m_ItemCount; // Number of objects in the map + size_t m_KeyOffset; // How far is the hash from the begin of the objects (in bytes) + size_t m_KeyLength; // Length of the hash key, in bytes + bool m_bKeyIsHash; // If set, then it means that the key is a hash of some sort. + // Will improve performance, as we will not hash a hash :-) +}; -#endif // __HASHTOPTR_H__ +#endif // __CASC_MAP_H__ diff --git a/dep/CascLib/src/common/RootHandler.cpp b/dep/CascLib/src/common/RootHandler.cpp index 4baeb41a421..89b693f55cb 100644 --- a/dep/CascLib/src/common/RootHandler.cpp +++ b/dep/CascLib/src/common/RootHandler.cpp @@ -13,76 +13,105 @@ #include "../CascCommon.h" //----------------------------------------------------------------------------- -// Common support +// Constructor and destructor - TFileTreeRoot -int RootHandler_Insert(TRootHandler * pRootHandler, const char * szFileName, LPBYTE pbEncodingKey) +TFileTreeRoot::TFileTreeRoot(DWORD FileTreeFlags) : TRootHandler() { - if(pRootHandler == NULL || pRootHandler->Insert == NULL || pbEncodingKey == NULL) - return ERROR_NOT_SUPPORTED; + // Initialize the file tree + FileTree.Create(FileTreeFlags); +} - return pRootHandler->Insert(pRootHandler, szFileName, pbEncodingKey); +TFileTreeRoot::~TFileTreeRoot() +{ + // Free the file tree + FileTree.Free(); + dwFeatures = 0; } -LPBYTE RootHandler_Search(TRootHandler * pRootHandler, struct _TCascSearch * pSearch, PDWORD PtrFileSize, PDWORD PtrLocaleFlags, PDWORD PtrFileDataId) +//----------------------------------------------------------------------------- +// Virtual functions - TFileTreeRoot + +int TFileTreeRoot::Insert( + const char * szFileName, + PCASC_CKEY_ENTRY pCKeyEntry) { - // Check if the root structure is valid at all - if(pRootHandler == NULL) - return NULL; + PCASC_FILE_NODE pFileNode; - return pRootHandler->Search(pRootHandler, pSearch, PtrFileSize, PtrLocaleFlags, PtrFileDataId); + pFileNode = FileTree.InsertByName(pCKeyEntry, szFileName, FileTree.GetNextFileDataId()); + return (pFileNode != NULL) ? ERROR_SUCCESS : ERROR_CAN_NOT_COMPLETE; } -void RootHandler_EndSearch(TRootHandler * pRootHandler, struct _TCascSearch * pSearch) +PCASC_CKEY_ENTRY TFileTreeRoot::GetFile(TCascStorage * /* hs */, const char * szFileName) { - // Check if the root structure is valid at all - if(pRootHandler != NULL) - { - pRootHandler->EndSearch(pRootHandler, pSearch); - } + PCASC_FILE_NODE pFileNode; + ULONGLONG FileNameHash = CalcFileNameHash(szFileName); + + pFileNode = FileTree.Find(FileNameHash); + return (pFileNode != NULL) ? pFileNode->pCKeyEntry : NULL; } -LPBYTE RootHandler_GetKey(TRootHandler * pRootHandler, const char * szFileName) +PCASC_CKEY_ENTRY TFileTreeRoot::GetFile(TCascStorage * /* hs */, DWORD FileDataId) { - // Check if the root structure is valid at all - if(pRootHandler == NULL) - return NULL; + PCASC_FILE_NODE pFileNode; - return pRootHandler->GetKey(pRootHandler, szFileName); + pFileNode = FileTree.FindById(FileDataId); + return (pFileNode != NULL) ? pFileNode->pCKeyEntry : NULL; } -void RootHandler_Dump(TCascStorage * hs, LPBYTE pbRootHandler, DWORD cbRootHandler, const TCHAR * szNameFormat, const TCHAR * szListFile, int nDumpLevel) +PCASC_CKEY_ENTRY TFileTreeRoot::Search(TCascSearch * pSearch, PCASC_FIND_DATA pFindData) { - TDumpContext * dc; + PCASC_FILE_NODE pFileNode; + size_t nMaxFileIndex = FileTree.GetMaxFileIndex(); - // Only if the ROOT provider suports the dump option - if(hs->pRootHandler != NULL && hs->pRootHandler->Dump != NULL) + // Are we still inside the root directory range? + while(pSearch->nFileIndex < nMaxFileIndex) { - // Create the dump file - dc = CreateDumpContext(hs, szNameFormat); - if(dc != NULL) + //BREAKIF(pSearch->nFileIndex >= 2823765); + + // Retrieve the file item + pFileNode = FileTree.PathAt(pFindData->szFileName, MAX_PATH, pSearch->nFileIndex++); + if(pFileNode != NULL) { - // Dump the content and close the file - hs->pRootHandler->Dump(hs, dc, pbRootHandler, cbRootHandler, szListFile, nDumpLevel); - dump_close(dc); + // Ignore folders and mount points + if(!(pFileNode->Flags & CFN_FLAG_FOLDER)) + { + // Check the wildcard + if (CascCheckWildCard(pFindData->szFileName, pSearch->szMask)) + { + // 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; + } + } } } + + // No more entries + return NULL; } -void RootHandler_Close(TRootHandler * pRootHandler) +bool TFileTreeRoot::GetInfo(PCASC_CKEY_ENTRY pCKeyEntry, PCASC_FILE_FULL_INFO pFileInfo) { - // Check if the root structure is allocated at all - if(pRootHandler != NULL) + PCASC_FILE_NODE pFileNode; + + // Can't do much if the root key is NULL + if(pCKeyEntry != NULL) { - pRootHandler->Close(pRootHandler); + pFileNode = FileTree.Find(pCKeyEntry); + if(pFileNode != NULL) + { + FileTree.GetExtras(pFileNode, &pFileInfo->FileDataId, &pFileInfo->LocaleFlags, &pFileInfo->ContentFlags); + pFileInfo->FileNameHash = pFileNode->FileNameHash; + return true; + } } -} - -DWORD RootHandler_GetFileId(TRootHandler * pRootHandler, const char * szFileName) -{ - // Check if the root structure is valid at all - if(pRootHandler == NULL) - return 0; - return pRootHandler->GetFileId(pRootHandler, szFileName); + return false; } diff --git a/dep/CascLib/src/common/RootHandler.h b/dep/CascLib/src/common/RootHandler.h index d1b66e501d8..4e56650b034 100644 --- a/dep/CascLib/src/common/RootHandler.h +++ b/dep/CascLib/src/common/RootHandler.h @@ -15,82 +15,97 @@ // Defines #define CASC_MNDX_ROOT_SIGNATURE 0x58444E4D // 'MNDX' +#define CASC_TVFS_ROOT_SIGNATURE 0x53465654 // 'TVFS' #define CASC_DIABLO3_ROOT_SIGNATURE 0x8007D0C4 -#define CASC_OVERWATCH_ROOT_SIGNATURE 0x35444D23 // '#MD5' +#define CASC_WOW82_ROOT_SIGNATURE 0x4D465354 // 'TSFM', WoW since 8.2 -#define ROOT_FLAG_HAS_NAMES 0x00000001 // The root file contains file names - -#define DUMP_LEVEL_ROOT_FILE 1 // Dump root file -#define DUMP_LEVEL_ENCODING_FILE 2 // Dump root file + encoding file -#define DUMP_LEVEL_INDEX_ENTRIES 3 // Dump root file + encoding file + index entries +#define DUMP_LEVEL_ROOT_FILE 1 // Dump root file +#define DUMP_LEVEL_ENCODING_FILE 2 // Dump root file + encoding file +#define DUMP_LEVEL_INDEX_ENTRIES 3 // Dump root file + encoding file + index entries //----------------------------------------------------------------------------- -// Root file function prototypes - -typedef int (*ROOT_INSERT)( - struct TRootHandler * pRootHandler, // Pointer to an initialized root handler - const char * szFileName, // Pointer to the file name - LPBYTE pbEncodingKey // Pointer to the encoding key of the file name - ); - -typedef LPBYTE (*ROOT_SEARCH)( - struct TRootHandler * pRootHandler, // Pointer to an initialized root handler - struct _TCascSearch * pSearch, // Pointer to the initialized search structure - PDWORD PtrFileSize, // Pointer to receive file size (optional) - PDWORD PtrLocaleFlags, // Pointer to receive locale flags (optional) - PDWORD PtrFileDataId // Pointer to FileDataID (optional) - ); - -typedef void (*ROOT_ENDSEARCH)( - struct TRootHandler * pRootHandler, // Pointer to an initialized root handler - struct _TCascSearch * pSearch // Pointer to the initialized search structure - ); - -typedef LPBYTE (*ROOT_GETKEY)( - struct TRootHandler * pRootHandler, // Pointer to an initialized root handler - const char * szFileName // Pointer to the name of a file - ); - -typedef void (*ROOT_DUMP)( - struct _TCascStorage * hs, // Pointer to the open storage - TDumpContext * dc, // Opened dump context - LPBYTE pbRootHandler, // Pointer to the loaded ROOT file - DWORD cbRootHandler, // Length of the loaded ROOT file - const TCHAR * szListFile, - int nDumpLevel - ); - -typedef void (*ROOT_CLOSE)( - struct TRootHandler * pRootHandler // Pointer to an initialized root handler - ); - -typedef DWORD(*ROOT_GETFILEID)( -struct TRootHandler * pRootHandler, // Pointer to an initialized root handler - const char * szFileName // Pointer to the name of a file - ); +// Class for generic root handler struct TRootHandler { - ROOT_INSERT Insert; // Inserts an existing file name - ROOT_SEARCH Search; // Performs the root file search - ROOT_ENDSEARCH EndSearch; // Performs cleanup after searching - ROOT_GETKEY GetKey; // Retrieves encoding key for a file name - ROOT_DUMP Dump; - ROOT_CLOSE Close; // Closing the root file - ROOT_GETFILEID GetFileId; // Returns File Id for a given Filename - - DWORD dwRootFlags; // Root flags - see the ROOT_FLAG_XXX + public: + + TRootHandler() + { + dwFeatures = 0; + } + + virtual ~TRootHandler() + {} + + // Inserts new file name to the root handler + // szFileName - Pointer to the file name + // pCKeyEntry - Pointer to the CASC_CKEY_ENTRY for the file + virtual int Insert(const char * /* szFileName */, PCASC_CKEY_ENTRY /* pCKeyEntry */) + { + return ERROR_NOT_SUPPORTED; + } + + // Searches the file by file name + // hs - Pointer to the storage structure + // szFileName - Pointer to the file name + virtual PCASC_CKEY_ENTRY GetFile(struct TCascStorage * /* hs */, const char * /* szFileName */) + { + return NULL; + } + + // Searches the file by file data id + // hs - Pointer to the storage structure + // FileDataId - File data id + virtual PCASC_CKEY_ENTRY GetFile(struct TCascStorage * /* hs */, DWORD /* FileDataId */) + { + return NULL; + } + + // Performs find-next-file operation + // pSearch - Pointer to the initialized search structure + // pFindData - Pointer to output structure that will contain the information + virtual PCASC_CKEY_ENTRY Search(struct TCascSearch * /* pSearch */, struct _CASC_FIND_DATA * /* pFindData */) + { + return NULL; + } + + // Returns advanced info from the root file entry. + // pCKeyEntry - CKey/EKey, depending on which type the root handler provides + // pFileInfo - Pointer to CASC_FILE_FULL_INFO structure + virtual bool GetInfo(PCASC_CKEY_ENTRY /* pCKeyEntry */, struct _CASC_FILE_FULL_INFO * /* pFileInfo */) + { + return false; + } + + DWORD GetFeatures() + { + return dwFeatures; + } + + protected: + + DWORD dwFeatures; // CASC features. See CASC_FEATURE_XXX }; //----------------------------------------------------------------------------- -// Public functions - -int RootHandler_Insert(TRootHandler * pRootHandler, const char * szFileName, LPBYTE pbEncodingKey); -LPBYTE RootHandler_Search(TRootHandler * pRootHandler, struct _TCascSearch * pSearch, PDWORD PtrFileSize, PDWORD PtrLocaleFlags, PDWORD PtrFileDataId); -void RootHandler_EndSearch(TRootHandler * pRootHandler, struct _TCascSearch * pSearch); -LPBYTE RootHandler_GetKey(TRootHandler * pRootHandler, const char * szFileName); -void RootHandler_Dump(struct _TCascStorage * hs, LPBYTE pbRootHandler, DWORD cbRootHandler, const TCHAR * szNameFormat, const TCHAR * szListFile, int nDumpLevel); -void RootHandler_Close(TRootHandler * pRootHandler); -DWORD RootHandler_GetFileId(TRootHandler * pRootHandler, const char * szFileName); +// Class for root handler that has basic mapping of FileName -> CASC_FILE_NODE + +struct TFileTreeRoot : public TRootHandler +{ + TFileTreeRoot(DWORD FileTreeFlags); + virtual ~TFileTreeRoot(); + + int Insert(const char * szFileName, PCASC_CKEY_ENTRY pCKeyEntry); + + PCASC_CKEY_ENTRY GetFile(struct TCascStorage * hs, const char * szFileName); + PCASC_CKEY_ENTRY GetFile(struct TCascStorage * hs, DWORD FileDataId); + PCASC_CKEY_ENTRY Search(struct TCascSearch * pSearch, struct _CASC_FIND_DATA * pFindData); + bool GetInfo(PCASC_CKEY_ENTRY pCKeyEntry, struct _CASC_FILE_FULL_INFO * pFileInfo); + + protected: + + CASC_FILE_TREE FileTree; +}; #endif // __ROOT_HANDLER_H__ diff --git a/dep/CascLib/src/libtomcrypt/src/hashes/hash_memory.c b/dep/CascLib/src/libtomcrypt/src/hashes/hash_memory.c deleted file mode 100644 index 1daf0bffa1b..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/hashes/hash_memory.c +++ /dev/null @@ -1,69 +0,0 @@ -/* LibTomCrypt, modular cryptographic library -- Tom St Denis - * - * LibTomCrypt is a library that provides various cryptographic - * algorithms in a highly modular and flexible manner. - * - * The library is free for all purposes without any express - * guarantee it works. - * - * Tom St Denis, tomstdenis@gmail.com, http://libtom.org - */ -#include "../headers/tomcrypt.h" - -/** - @file hash_memory.c - Hash memory helper, Tom St Denis -*/ - -/** - Hash a block of memory and store the digest. - @param hash The index of the hash you wish to use - @param in The data you wish to hash - @param inlen The length of the data to hash (octets) - @param out [out] Where to store the digest - @param outlen [in/out] Max size and resulting size of the digest - @return CRYPT_OK if successful -*/ -int hash_memory(int hash, const unsigned char *in, unsigned long inlen, unsigned char *out, unsigned long *outlen) -{ - hash_state *md; - int err; - - LTC_ARGCHK(in != NULL); - LTC_ARGCHK(out != NULL); - LTC_ARGCHK(outlen != NULL); - - if ((err = hash_is_valid(hash)) != CRYPT_OK) { - return err; - } - - if (*outlen < hash_descriptor[hash].hashsize) { - *outlen = hash_descriptor[hash].hashsize; - return CRYPT_BUFFER_OVERFLOW; - } - - md = XMALLOC(sizeof(hash_state)); - if (md == NULL) { - return CRYPT_MEM; - } - - if ((err = hash_descriptor[hash].init(md)) != CRYPT_OK) { - goto LBL_ERR; - } - if ((err = hash_descriptor[hash].process(md, in, inlen)) != CRYPT_OK) { - goto LBL_ERR; - } - err = hash_descriptor[hash].done(md, out); - *outlen = hash_descriptor[hash].hashsize; -LBL_ERR: -#ifdef LTC_CLEAN_STACK - zeromem(md, sizeof(hash_state)); -#endif - XFREE(md); - - return err; -} - -/* $Source: /cvs/libtom/libtomcrypt/src/hashes/helper/hash_memory.c,v $ */ -/* $Revision: 1.6 $ */ -/* $Date: 2006/12/28 01:27:23 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/hashes/md5.c b/dep/CascLib/src/libtomcrypt/src/hashes/md5.c deleted file mode 100644 index e92da256d56..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/hashes/md5.c +++ /dev/null @@ -1,368 +0,0 @@ -/* LibTomCrypt, modular cryptographic library -- Tom St Denis - * - * LibTomCrypt is a library that provides various cryptographic - * algorithms in a highly modular and flexible manner. - * - * The library is free for all purposes without any express - * guarantee it works. - * - * Tom St Denis, tomstdenis@gmail.com, http://libtom.org - */ -#include "../headers/tomcrypt.h" - - -/** - @file md5.c - LTC_MD5 hash function by Tom St Denis -*/ - -#ifdef LTC_MD5 - -const struct ltc_hash_descriptor md5_desc = -{ - "md5", - 3, - 16, - 64, - - /* OID */ - { 1, 2, 840, 113549, 2, 5, }, - 6, - - &md5_init, - &md5_process, - &md5_done, - &md5_test, - NULL -}; - -#define F(x,y,z) (z ^ (x & (y ^ z))) -#define G(x,y,z) (y ^ (z & (y ^ x))) -#define H(x,y,z) (x^y^z) -#define I(x,y,z) (y^(x|(~z))) - -#ifdef LTC_SMALL_CODE - -#define FF(a,b,c,d,M,s,t) \ - a = (a + F(b,c,d) + M + t); a = ROL(a, s) + b; - -#define GG(a,b,c,d,M,s,t) \ - a = (a + G(b,c,d) + M + t); a = ROL(a, s) + b; - -#define HH(a,b,c,d,M,s,t) \ - a = (a + H(b,c,d) + M + t); a = ROL(a, s) + b; - -#define II(a,b,c,d,M,s,t) \ - a = (a + I(b,c,d) + M + t); a = ROL(a, s) + b; - -static const unsigned char Worder[64] = { - 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, - 1,6,11,0,5,10,15,4,9,14,3,8,13,2,7,12, - 5,8,11,14,1,4,7,10,13,0,3,6,9,12,15,2, - 0,7,14,5,12,3,10,1,8,15,6,13,4,11,2,9 -}; - -static const unsigned char Rorder[64] = { - 7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22, - 5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20, - 4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23, - 6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21 -}; - -static const ulong32 Korder[64] = { -0xd76aa478UL, 0xe8c7b756UL, 0x242070dbUL, 0xc1bdceeeUL, 0xf57c0fafUL, 0x4787c62aUL, 0xa8304613UL, 0xfd469501UL, -0x698098d8UL, 0x8b44f7afUL, 0xffff5bb1UL, 0x895cd7beUL, 0x6b901122UL, 0xfd987193UL, 0xa679438eUL, 0x49b40821UL, -0xf61e2562UL, 0xc040b340UL, 0x265e5a51UL, 0xe9b6c7aaUL, 0xd62f105dUL, 0x02441453UL, 0xd8a1e681UL, 0xe7d3fbc8UL, -0x21e1cde6UL, 0xc33707d6UL, 0xf4d50d87UL, 0x455a14edUL, 0xa9e3e905UL, 0xfcefa3f8UL, 0x676f02d9UL, 0x8d2a4c8aUL, -0xfffa3942UL, 0x8771f681UL, 0x6d9d6122UL, 0xfde5380cUL, 0xa4beea44UL, 0x4bdecfa9UL, 0xf6bb4b60UL, 0xbebfbc70UL, -0x289b7ec6UL, 0xeaa127faUL, 0xd4ef3085UL, 0x04881d05UL, 0xd9d4d039UL, 0xe6db99e5UL, 0x1fa27cf8UL, 0xc4ac5665UL, -0xf4292244UL, 0x432aff97UL, 0xab9423a7UL, 0xfc93a039UL, 0x655b59c3UL, 0x8f0ccc92UL, 0xffeff47dUL, 0x85845dd1UL, -0x6fa87e4fUL, 0xfe2ce6e0UL, 0xa3014314UL, 0x4e0811a1UL, 0xf7537e82UL, 0xbd3af235UL, 0x2ad7d2bbUL, 0xeb86d391UL -}; - -#else - -#define FF(a,b,c,d,M,s,t) \ - a = (a + F(b,c,d) + M + t); a = ROLc(a, s) + b; - -#define GG(a,b,c,d,M,s,t) \ - a = (a + G(b,c,d) + M + t); a = ROLc(a, s) + b; - -#define HH(a,b,c,d,M,s,t) \ - a = (a + H(b,c,d) + M + t); a = ROLc(a, s) + b; - -#define II(a,b,c,d,M,s,t) \ - a = (a + I(b,c,d) + M + t); a = ROLc(a, s) + b; - - -#endif - -#ifdef LTC_CLEAN_STACK -static int _md5_compress(hash_state *md, unsigned char *buf) -#else -static int md5_compress(hash_state *md, unsigned char *buf) -#endif -{ - ulong32 i, W[16], a, b, c, d; -#ifdef LTC_SMALL_CODE - ulong32 t; -#endif - - /* copy the state into 512-bits into W[0..15] */ - for (i = 0; i < 16; i++) { - LOAD32L(W[i], buf + (4*i)); - } - - /* copy state */ - a = md->md5.state[0]; - b = md->md5.state[1]; - c = md->md5.state[2]; - d = md->md5.state[3]; - -#ifdef LTC_SMALL_CODE - for (i = 0; i < 16; ++i) { - FF(a,b,c,d,W[Worder[i]],Rorder[i],Korder[i]); - t = d; d = c; c = b; b = a; a = t; - } - - for (; i < 32; ++i) { - GG(a,b,c,d,W[Worder[i]],Rorder[i],Korder[i]); - t = d; d = c; c = b; b = a; a = t; - } - - for (; i < 48; ++i) { - HH(a,b,c,d,W[Worder[i]],Rorder[i],Korder[i]); - t = d; d = c; c = b; b = a; a = t; - } - - for (; i < 64; ++i) { - II(a,b,c,d,W[Worder[i]],Rorder[i],Korder[i]); - t = d; d = c; c = b; b = a; a = t; - } - -#else - FF(a,b,c,d,W[0],7,0xd76aa478UL) - FF(d,a,b,c,W[1],12,0xe8c7b756UL) - FF(c,d,a,b,W[2],17,0x242070dbUL) - FF(b,c,d,a,W[3],22,0xc1bdceeeUL) - FF(a,b,c,d,W[4],7,0xf57c0fafUL) - FF(d,a,b,c,W[5],12,0x4787c62aUL) - FF(c,d,a,b,W[6],17,0xa8304613UL) - FF(b,c,d,a,W[7],22,0xfd469501UL) - FF(a,b,c,d,W[8],7,0x698098d8UL) - FF(d,a,b,c,W[9],12,0x8b44f7afUL) - FF(c,d,a,b,W[10],17,0xffff5bb1UL) - FF(b,c,d,a,W[11],22,0x895cd7beUL) - FF(a,b,c,d,W[12],7,0x6b901122UL) - FF(d,a,b,c,W[13],12,0xfd987193UL) - FF(c,d,a,b,W[14],17,0xa679438eUL) - FF(b,c,d,a,W[15],22,0x49b40821UL) - GG(a,b,c,d,W[1],5,0xf61e2562UL) - GG(d,a,b,c,W[6],9,0xc040b340UL) - GG(c,d,a,b,W[11],14,0x265e5a51UL) - GG(b,c,d,a,W[0],20,0xe9b6c7aaUL) - GG(a,b,c,d,W[5],5,0xd62f105dUL) - GG(d,a,b,c,W[10],9,0x02441453UL) - GG(c,d,a,b,W[15],14,0xd8a1e681UL) - GG(b,c,d,a,W[4],20,0xe7d3fbc8UL) - GG(a,b,c,d,W[9],5,0x21e1cde6UL) - GG(d,a,b,c,W[14],9,0xc33707d6UL) - GG(c,d,a,b,W[3],14,0xf4d50d87UL) - GG(b,c,d,a,W[8],20,0x455a14edUL) - GG(a,b,c,d,W[13],5,0xa9e3e905UL) - GG(d,a,b,c,W[2],9,0xfcefa3f8UL) - GG(c,d,a,b,W[7],14,0x676f02d9UL) - GG(b,c,d,a,W[12],20,0x8d2a4c8aUL) - HH(a,b,c,d,W[5],4,0xfffa3942UL) - HH(d,a,b,c,W[8],11,0x8771f681UL) - HH(c,d,a,b,W[11],16,0x6d9d6122UL) - HH(b,c,d,a,W[14],23,0xfde5380cUL) - HH(a,b,c,d,W[1],4,0xa4beea44UL) - HH(d,a,b,c,W[4],11,0x4bdecfa9UL) - HH(c,d,a,b,W[7],16,0xf6bb4b60UL) - HH(b,c,d,a,W[10],23,0xbebfbc70UL) - HH(a,b,c,d,W[13],4,0x289b7ec6UL) - HH(d,a,b,c,W[0],11,0xeaa127faUL) - HH(c,d,a,b,W[3],16,0xd4ef3085UL) - HH(b,c,d,a,W[6],23,0x04881d05UL) - HH(a,b,c,d,W[9],4,0xd9d4d039UL) - HH(d,a,b,c,W[12],11,0xe6db99e5UL) - HH(c,d,a,b,W[15],16,0x1fa27cf8UL) - HH(b,c,d,a,W[2],23,0xc4ac5665UL) - II(a,b,c,d,W[0],6,0xf4292244UL) - II(d,a,b,c,W[7],10,0x432aff97UL) - II(c,d,a,b,W[14],15,0xab9423a7UL) - II(b,c,d,a,W[5],21,0xfc93a039UL) - II(a,b,c,d,W[12],6,0x655b59c3UL) - II(d,a,b,c,W[3],10,0x8f0ccc92UL) - II(c,d,a,b,W[10],15,0xffeff47dUL) - II(b,c,d,a,W[1],21,0x85845dd1UL) - II(a,b,c,d,W[8],6,0x6fa87e4fUL) - II(d,a,b,c,W[15],10,0xfe2ce6e0UL) - II(c,d,a,b,W[6],15,0xa3014314UL) - II(b,c,d,a,W[13],21,0x4e0811a1UL) - II(a,b,c,d,W[4],6,0xf7537e82UL) - II(d,a,b,c,W[11],10,0xbd3af235UL) - II(c,d,a,b,W[2],15,0x2ad7d2bbUL) - II(b,c,d,a,W[9],21,0xeb86d391UL) -#endif - - md->md5.state[0] = md->md5.state[0] + a; - md->md5.state[1] = md->md5.state[1] + b; - md->md5.state[2] = md->md5.state[2] + c; - md->md5.state[3] = md->md5.state[3] + d; - - return CRYPT_OK; -} - -#ifdef LTC_CLEAN_STACK -static int md5_compress(hash_state *md, unsigned char *buf) -{ - int err; - err = _md5_compress(md, buf); - burn_stack(sizeof(ulong32) * 21); - return err; -} -#endif - -/** - Initialize the hash state - @param md The hash state you wish to initialize - @return CRYPT_OK if successful -*/ -int md5_init(hash_state * md) -{ - LTC_ARGCHK(md != NULL); - md->md5.state[0] = 0x67452301UL; - md->md5.state[1] = 0xefcdab89UL; - md->md5.state[2] = 0x98badcfeUL; - md->md5.state[3] = 0x10325476UL; - md->md5.curlen = 0; - md->md5.length = 0; - return CRYPT_OK; -} - -/** - Process a block of memory though the hash - @param md The hash state - @param in The data to hash - @param inlen The length of the data (octets) - @return CRYPT_OK if successful -*/ -HASH_PROCESS(md5_process, md5_compress, md5, 64) - -/** - Terminate the hash to get the digest - @param md The hash state - @param out [out] The destination of the hash (16 bytes) - @return CRYPT_OK if successful -*/ -int md5_done(hash_state * md, unsigned char *out) -{ - int i; - - LTC_ARGCHK(md != NULL); - LTC_ARGCHK(out != NULL); - - if (md->md5.curlen >= sizeof(md->md5.buf)) { - return CRYPT_INVALID_ARG; - } - - - /* increase the length of the message */ - md->md5.length += md->md5.curlen * 8; - - /* append the '1' bit */ - md->md5.buf[md->md5.curlen++] = (unsigned char)0x80; - - /* if the length is currently above 56 bytes we append zeros - * then compress. Then we can fall back to padding zeros and length - * encoding like normal. - */ - if (md->md5.curlen > 56) { - while (md->md5.curlen < 64) { - md->md5.buf[md->md5.curlen++] = (unsigned char)0; - } - md5_compress(md, md->md5.buf); - md->md5.curlen = 0; - } - - /* pad upto 56 bytes of zeroes */ - while (md->md5.curlen < 56) { - md->md5.buf[md->md5.curlen++] = (unsigned char)0; - } - - /* store length */ - STORE64L(md->md5.length, md->md5.buf+56); - md5_compress(md, md->md5.buf); - - /* copy output */ - for (i = 0; i < 4; i++) { - STORE32L(md->md5.state[i], out+(4*i)); - } -#ifdef LTC_CLEAN_STACK - zeromem(md, sizeof(hash_state)); -#endif - return CRYPT_OK; -} - -/** - Self-test the hash - @return CRYPT_OK if successful, CRYPT_NOP if self-tests have been disabled -*/ -int md5_test(void) -{ - #ifndef LTC_TEST - return CRYPT_NOP; - #else - static const struct { - char *msg; - unsigned char hash[16]; - } tests[] = { - { "", - { 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, - 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e } }, - { "a", - {0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, - 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61 } }, - { "abc", - { 0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, - 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1, 0x7f, 0x72 } }, - { "message digest", - { 0xf9, 0x6b, 0x69, 0x7d, 0x7c, 0xb7, 0x93, 0x8d, - 0x52, 0x5a, 0x2f, 0x31, 0xaa, 0xf1, 0x61, 0xd0 } }, - { "abcdefghijklmnopqrstuvwxyz", - { 0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, - 0x7d, 0xfb, 0x49, 0x6c, 0xca, 0x67, 0xe1, 0x3b } }, - { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", - { 0xd1, 0x74, 0xab, 0x98, 0xd2, 0x77, 0xd9, 0xf5, - 0xa5, 0x61, 0x1c, 0x2c, 0x9f, 0x41, 0x9d, 0x9f } }, - { "12345678901234567890123456789012345678901234567890123456789012345678901234567890", - { 0x57, 0xed, 0xf4, 0xa2, 0x2b, 0xe3, 0xc9, 0x55, - 0xac, 0x49, 0xda, 0x2e, 0x21, 0x07, 0xb6, 0x7a } }, - { NULL, { 0 } } - }; - - int i; - unsigned char tmp[16]; - hash_state md; - - for (i = 0; tests[i].msg != NULL; i++) { - md5_init(&md); - md5_process(&md, (unsigned char *)tests[i].msg, (unsigned long)strlen(tests[i].msg)); - md5_done(&md, tmp); - if (XMEMCMP(tmp, tests[i].hash, 16) != 0) { - return CRYPT_FAIL_TESTVECTOR; - } - } - return CRYPT_OK; - #endif -} - -#endif - - - -/* $Source: /cvs/libtom/libtomcrypt/src/hashes/md5.c,v $ */ -/* $Revision: 1.10 $ */ -/* $Date: 2007/05/12 14:25:28 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt.h b/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt.h deleted file mode 100644 index 74cdff47549..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef TOMCRYPT_H_ -#define TOMCRYPT_H_ -#include <assert.h> -#include <stdio.h> -#include <string.h> -#include <stdlib.h> -#include <time.h> -#include <ctype.h> -#include <limits.h> - -/* use configuration data */ -#include "tomcrypt_custom.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* version */ -#define CRYPT 0x0117 -#define SCRYPT "1.17" - -/* max size of either a cipher/hash block or symmetric key [largest of the two] */ -#define MAXBLOCKSIZE 128 - -/* descriptor table size */ -#define TAB_SIZE 32 - -/* error codes [will be expanded in future releases] */ -enum { - CRYPT_OK=0, /* Result OK */ - CRYPT_ERROR, /* Generic Error */ - CRYPT_NOP, /* Not a failure but no operation was performed */ - - CRYPT_INVALID_KEYSIZE, /* Invalid key size given */ - CRYPT_INVALID_ROUNDS, /* Invalid number of rounds */ - CRYPT_FAIL_TESTVECTOR, /* Algorithm failed test vectors */ - - CRYPT_BUFFER_OVERFLOW, /* Not enough space for output */ - CRYPT_INVALID_PACKET, /* Invalid input packet given */ - - CRYPT_INVALID_PRNGSIZE, /* Invalid number of bits for a PRNG */ - CRYPT_ERROR_READPRNG, /* Could not read enough from PRNG */ - - CRYPT_INVALID_CIPHER, /* Invalid cipher specified */ - CRYPT_INVALID_HASH, /* Invalid hash specified */ - CRYPT_INVALID_PRNG, /* Invalid PRNG specified */ - - CRYPT_MEM, /* Out of memory */ - - CRYPT_PK_TYPE_MISMATCH, /* Not equivalent types of PK keys */ - CRYPT_PK_NOT_PRIVATE, /* Requires a private PK key */ - - CRYPT_INVALID_ARG, /* Generic invalid argument */ - CRYPT_FILE_NOTFOUND, /* File Not Found */ - - CRYPT_PK_INVALID_TYPE, /* Invalid type of PK key */ - CRYPT_PK_INVALID_SYSTEM,/* Invalid PK system specified */ - CRYPT_PK_DUP, /* Duplicate key already in key ring */ - CRYPT_PK_NOT_FOUND, /* Key not found in keyring */ - CRYPT_PK_INVALID_SIZE, /* Invalid size input for PK parameters */ - - CRYPT_INVALID_PRIME_SIZE,/* Invalid size of prime requested */ - CRYPT_PK_INVALID_PADDING /* Invalid padding on input */ -}; - -#include "tomcrypt_cfg.h" -#include "tomcrypt_macros.h" -#include "tomcrypt_cipher.h" -#include "tomcrypt_hash.h" -#include "tomcrypt_mac.h" -#include "tomcrypt_prng.h" -#include "tomcrypt_pk.h" -#include "tomcrypt_math.h" -#include "tomcrypt_misc.h" -#include "tomcrypt_argchk.h" -#include "tomcrypt_pkcs.h" - -#ifdef __cplusplus - } -#endif - -#endif /* TOMCRYPT_H_ */ - - -/* $Source: /cvs/libtom/libtomcrypt/src/headers/tomcrypt.h,v $ */ -/* $Revision: 1.21 $ */ -/* $Date: 2006/12/16 19:34:05 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_argchk.h b/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_argchk.h deleted file mode 100644 index cfc93ad7ea6..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_argchk.h +++ /dev/null @@ -1,38 +0,0 @@ -/* Defines the LTC_ARGCHK macro used within the library */ -/* ARGTYPE is defined in mycrypt_cfg.h */ -#if ARGTYPE == 0 - -#include <signal.h> - -/* this is the default LibTomCrypt macro */ -void crypt_argchk(char *v, char *s, int d); -#define LTC_ARGCHK(x) if (!(x)) { crypt_argchk(#x, __FILE__, __LINE__); } -#define LTC_ARGCHKVD(x) LTC_ARGCHK(x) - -#elif ARGTYPE == 1 - -/* fatal type of error */ -#define LTC_ARGCHK(x) assert((x)) -#define LTC_ARGCHKVD(x) LTC_ARGCHK(x) - -#elif ARGTYPE == 2 - -#define LTC_ARGCHK(x) if (!(x)) { fprintf(stderr, "\nwarning: ARGCHK failed at %s:%d\n", __FILE__, __LINE__); } -#define LTC_ARGCHKVD(x) LTC_ARGCHK(x) - -#elif ARGTYPE == 3 - -#define LTC_ARGCHK(x) -#define LTC_ARGCHKVD(x) LTC_ARGCHK(x) - -#elif ARGTYPE == 4 - -#define LTC_ARGCHK(x) if (!(x)) return CRYPT_INVALID_ARG; -#define LTC_ARGCHKVD(x) if (!(x)) return; - -#endif - - -/* $Source: /cvs/libtom/libtomcrypt/src/headers/tomcrypt_argchk.h,v $ */ -/* $Revision: 1.5 $ */ -/* $Date: 2006/08/27 20:50:21 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_cfg.h b/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_cfg.h deleted file mode 100644 index 7feae6e8bdc..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_cfg.h +++ /dev/null @@ -1,136 +0,0 @@ -/* This is the build config file. - * - * With this you can setup what to inlcude/exclude automatically during any build. Just comment - * out the line that #define's the word for the thing you want to remove. phew! - */ - -#ifndef TOMCRYPT_CFG_H -#define TOMCRYPT_CFG_H - -#if defined(_WIN32) || defined(_MSC_VER) -#define LTC_CALL __cdecl -#else -#ifndef LTC_CALL - #define LTC_CALL -#endif -#endif - -#ifndef LTC_EXPORT -#define LTC_EXPORT -#endif - -/* certain platforms use macros for these, making the prototypes broken */ -#ifndef LTC_NO_PROTOTYPES - -/* you can change how memory allocation works ... */ -LTC_EXPORT void * LTC_CALL XMALLOC(size_t n); -LTC_EXPORT void * LTC_CALL XREALLOC(void *p, size_t n); -LTC_EXPORT void * LTC_CALL XCALLOC(size_t n, size_t s); -LTC_EXPORT void LTC_CALL XFREE(void *p); - -LTC_EXPORT void LTC_CALL XQSORT(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *)); - - -/* change the clock function too */ -LTC_EXPORT clock_t LTC_CALL XCLOCK(void); - -/* various other functions */ -LTC_EXPORT void * LTC_CALL XMEMCPY(void *dest, const void *src, size_t n); -LTC_EXPORT int LTC_CALL XMEMCMP(const void *s1, const void *s2, size_t n); -LTC_EXPORT void * LTC_CALL XMEMSET(void *s, int c, size_t n); - -LTC_EXPORT int LTC_CALL XSTRCMP(const char *s1, const char *s2); - -#endif - -/* type of argument checking, 0=default, 1=fatal and 2=error+continue, 3=nothing */ -#ifndef ARGTYPE - #define ARGTYPE 0 -#endif - -/* Controls endianess and size of registers. Leave uncommented to get platform neutral [slower] code - * - * Note: in order to use the optimized macros your platform must support unaligned 32 and 64 bit read/writes. - * The x86 platforms allow this but some others [ARM for instance] do not. On those platforms you **MUST** - * use the portable [slower] macros. - */ - -/* detect x86-32 machines somewhat */ -#if !defined(__STRICT_ANSI__) && (defined(INTEL_CC) || (defined(_MSC_VER) && defined(WIN32)) || (defined(__GNUC__) && (defined(__DJGPP__) || defined(__CYGWIN__) || defined(__MINGW32__) || defined(__i386__)))) - #define ENDIAN_LITTLE - #define ENDIAN_32BITWORD - #define LTC_FAST - #define LTC_FAST_TYPE unsigned long -#endif - -/* detects MIPS R5900 processors (PS2) */ -#if (defined(__R5900) || defined(R5900) || defined(__R5900__)) && (defined(_mips) || defined(__mips__) || defined(mips)) - #define ENDIAN_LITTLE - #define ENDIAN_64BITWORD -#endif - -/* detect amd64 */ -#if !defined(__STRICT_ANSI__) && defined(__x86_64__) - #define ENDIAN_LITTLE - #define ENDIAN_64BITWORD - #define LTC_FAST - #define LTC_FAST_TYPE unsigned long -#endif - -/* detect PPC32 */ -#if !defined(__STRICT_ANSI__) && defined(LTC_PPC32) - #define ENDIAN_BIG - #define ENDIAN_32BITWORD - #define LTC_FAST - #define LTC_FAST_TYPE unsigned long -#endif - -/* detect sparc and sparc64 */ -#if defined(__sparc__) - #define ENDIAN_BIG - #if defined(__arch64__) - #define ENDIAN_64BITWORD - #else - #define ENDIAN_32BITWORD - #endif -#endif - - -#ifdef LTC_NO_FAST - #ifdef LTC_FAST - #undef LTC_FAST - #endif -#endif - -/* No asm is a quick way to disable anything "not portable" */ -#ifdef LTC_NO_ASM - #undef ENDIAN_LITTLE - #undef ENDIAN_BIG - #undef ENDIAN_32BITWORD - #undef ENDIAN_64BITWORD - #undef LTC_FAST - #undef LTC_FAST_TYPE - #define LTC_NO_ROLC - #define LTC_NO_BSWAP -#endif - -/* #define ENDIAN_LITTLE */ -/* #define ENDIAN_BIG */ - -/* #define ENDIAN_32BITWORD */ -/* #define ENDIAN_64BITWORD */ - -#if (defined(ENDIAN_BIG) || defined(ENDIAN_LITTLE)) && !(defined(ENDIAN_32BITWORD) || defined(ENDIAN_64BITWORD)) - #error You must specify a word size as well as endianess in tomcrypt_cfg.h -#endif - -#if !(defined(ENDIAN_BIG) || defined(ENDIAN_LITTLE)) - #define ENDIAN_NEUTRAL -#endif - -#endif - - -/* $Source: /cvs/libtom/libtomcrypt/src/headers/tomcrypt_cfg.h,v $ */ -/* $Revision: 1.19 $ */ -/* $Date: 2006/12/04 02:19:48 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_cipher.h b/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_cipher.h deleted file mode 100644 index bd740bf4a0c..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_cipher.h +++ /dev/null @@ -1,891 +0,0 @@ -/* ---- SYMMETRIC KEY STUFF ----- - * - * We put each of the ciphers scheduled keys in their own structs then we put all of - * the key formats in one union. This makes the function prototypes easier to use. - */ -#ifdef LTC_BLOWFISH -struct blowfish_key { - ulong32 S[4][256]; - ulong32 K[18]; -}; -#endif - -#ifdef LTC_RC5 -struct rc5_key { - int rounds; - ulong32 K[50]; -}; -#endif - -#ifdef LTC_RC6 -struct rc6_key { - ulong32 K[44]; -}; -#endif - -#ifdef LTC_SAFERP -struct saferp_key { - unsigned char K[33][16]; - long rounds; -}; -#endif - -#ifdef LTC_RIJNDAEL -struct rijndael_key { - ulong32 eK[60], dK[60]; - int Nr; -}; -#endif - -#ifdef LTC_KSEED -struct kseed_key { - ulong32 K[32], dK[32]; -}; -#endif - -#ifdef LTC_KASUMI -struct kasumi_key { - ulong32 KLi1[8], KLi2[8], - KOi1[8], KOi2[8], KOi3[8], - KIi1[8], KIi2[8], KIi3[8]; -}; -#endif - -#ifdef LTC_XTEA -struct xtea_key { - unsigned long A[32], B[32]; -}; -#endif - -#ifdef LTC_TWOFISH -#ifndef LTC_TWOFISH_SMALL - struct twofish_key { - ulong32 S[4][256], K[40]; - }; -#else - struct twofish_key { - ulong32 K[40]; - unsigned char S[32], start; - }; -#endif -#endif - -#ifdef LTC_SAFER -#define LTC_SAFER_K64_DEFAULT_NOF_ROUNDS 6 -#define LTC_SAFER_K128_DEFAULT_NOF_ROUNDS 10 -#define LTC_SAFER_SK64_DEFAULT_NOF_ROUNDS 8 -#define LTC_SAFER_SK128_DEFAULT_NOF_ROUNDS 10 -#define LTC_SAFER_MAX_NOF_ROUNDS 13 -#define LTC_SAFER_BLOCK_LEN 8 -#define LTC_SAFER_KEY_LEN (1 + LTC_SAFER_BLOCK_LEN * (1 + 2 * LTC_SAFER_MAX_NOF_ROUNDS)) -typedef unsigned char safer_block_t[LTC_SAFER_BLOCK_LEN]; -typedef unsigned char safer_key_t[LTC_SAFER_KEY_LEN]; -struct safer_key { safer_key_t key; }; -#endif - -#ifdef LTC_RC2 -struct rc2_key { unsigned xkey[64]; }; -#endif - -#ifdef LTC_DES -struct des_key { - ulong32 ek[32], dk[32]; -}; - -struct des3_key { - ulong32 ek[3][32], dk[3][32]; -}; -#endif - -#ifdef LTC_CAST5 -struct cast5_key { - ulong32 K[32], keylen; -}; -#endif - -#ifdef LTC_NOEKEON -struct noekeon_key { - ulong32 K[4], dK[4]; -}; -#endif - -#ifdef LTC_SKIPJACK -struct skipjack_key { - unsigned char key[10]; -}; -#endif - -#ifdef LTC_KHAZAD -struct khazad_key { - ulong64 roundKeyEnc[8 + 1]; - ulong64 roundKeyDec[8 + 1]; -}; -#endif - -#ifdef LTC_ANUBIS -struct anubis_key { - int keyBits; - int R; - ulong32 roundKeyEnc[18 + 1][4]; - ulong32 roundKeyDec[18 + 1][4]; -}; -#endif - -#ifdef LTC_MULTI2 -struct multi2_key { - int N; - ulong32 uk[8]; -}; -#endif - -typedef union Symmetric_key { -#ifdef LTC_DES - struct des_key des; - struct des3_key des3; -#endif -#ifdef LTC_RC2 - struct rc2_key rc2; -#endif -#ifdef LTC_SAFER - struct safer_key safer; -#endif -#ifdef LTC_TWOFISH - struct twofish_key twofish; -#endif -#ifdef LTC_BLOWFISH - struct blowfish_key blowfish; -#endif -#ifdef LTC_RC5 - struct rc5_key rc5; -#endif -#ifdef LTC_RC6 - struct rc6_key rc6; -#endif -#ifdef LTC_SAFERP - struct saferp_key saferp; -#endif -#ifdef LTC_RIJNDAEL - struct rijndael_key rijndael; -#endif -#ifdef LTC_XTEA - struct xtea_key xtea; -#endif -#ifdef LTC_CAST5 - struct cast5_key cast5; -#endif -#ifdef LTC_NOEKEON - struct noekeon_key noekeon; -#endif -#ifdef LTC_SKIPJACK - struct skipjack_key skipjack; -#endif -#ifdef LTC_KHAZAD - struct khazad_key khazad; -#endif -#ifdef LTC_ANUBIS - struct anubis_key anubis; -#endif -#ifdef LTC_KSEED - struct kseed_key kseed; -#endif -#ifdef LTC_KASUMI - struct kasumi_key kasumi; -#endif -#ifdef LTC_MULTI2 - struct multi2_key multi2; -#endif - void *data; -} symmetric_key; - -#ifdef LTC_ECB_MODE -/** A block cipher ECB structure */ -typedef struct { - /** The index of the cipher chosen */ - int cipher, - /** The block size of the given cipher */ - blocklen; - /** The scheduled key */ - symmetric_key key; -} symmetric_ECB; -#endif - -#ifdef LTC_CFB_MODE -/** A block cipher CFB structure */ -typedef struct { - /** The index of the cipher chosen */ - int cipher, - /** The block size of the given cipher */ - blocklen, - /** The padding offset */ - padlen; - /** The current IV */ - unsigned char IV[MAXBLOCKSIZE], - /** The pad used to encrypt/decrypt */ - pad[MAXBLOCKSIZE]; - /** The scheduled key */ - symmetric_key key; -} symmetric_CFB; -#endif - -#ifdef LTC_OFB_MODE -/** A block cipher OFB structure */ -typedef struct { - /** The index of the cipher chosen */ - int cipher, - /** The block size of the given cipher */ - blocklen, - /** The padding offset */ - padlen; - /** The current IV */ - unsigned char IV[MAXBLOCKSIZE]; - /** The scheduled key */ - symmetric_key key; -} symmetric_OFB; -#endif - -#ifdef LTC_CBC_MODE -/** A block cipher CBC structure */ -typedef struct { - /** The index of the cipher chosen */ - int cipher, - /** The block size of the given cipher */ - blocklen; - /** The current IV */ - unsigned char IV[MAXBLOCKSIZE]; - /** The scheduled key */ - symmetric_key key; -} symmetric_CBC; -#endif - - -#ifdef LTC_CTR_MODE -/** A block cipher CTR structure */ -typedef struct { - /** The index of the cipher chosen */ - int cipher, - /** The block size of the given cipher */ - blocklen, - /** The padding offset */ - padlen, - /** The mode (endianess) of the CTR, 0==little, 1==big */ - mode, - /** counter width */ - ctrlen; - - /** The counter */ - unsigned char ctr[MAXBLOCKSIZE], - /** The pad used to encrypt/decrypt */ - pad[MAXBLOCKSIZE]; - /** The scheduled key */ - symmetric_key key; -} symmetric_CTR; -#endif - - -#ifdef LTC_LRW_MODE -/** A LRW structure */ -typedef struct { - /** The index of the cipher chosen (must be a 128-bit block cipher) */ - int cipher; - - /** The current IV */ - unsigned char IV[16], - - /** the tweak key */ - tweak[16], - - /** The current pad, it's the product of the first 15 bytes against the tweak key */ - pad[16]; - - /** The scheduled symmetric key */ - symmetric_key key; - -#ifdef LRW_TABLES - /** The pre-computed multiplication table */ - unsigned char PC[16][256][16]; -#endif -} symmetric_LRW; -#endif - -#ifdef LTC_F8_MODE -/** A block cipher F8 structure */ -typedef struct { - /** The index of the cipher chosen */ - int cipher, - /** The block size of the given cipher */ - blocklen, - /** The padding offset */ - padlen; - /** The current IV */ - unsigned char IV[MAXBLOCKSIZE], - MIV[MAXBLOCKSIZE]; - /** Current block count */ - ulong32 blockcnt; - /** The scheduled key */ - symmetric_key key; -} symmetric_F8; -#endif - - -/** cipher descriptor table, last entry has "name == NULL" to mark the end of table */ -extern struct ltc_cipher_descriptor { - /** name of cipher */ - char *name; - /** internal ID */ - unsigned char ID; - /** min keysize (octets) */ - int min_key_length, - /** max keysize (octets) */ - max_key_length, - /** block size (octets) */ - block_length, - /** default number of rounds */ - default_rounds; - /** Setup the cipher - @param key The input symmetric key - @param keylen The length of the input key (octets) - @param num_rounds The requested number of rounds (0==default) - @param skey [out] The destination of the scheduled key - @return CRYPT_OK if successful - */ - int (*setup)(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); - /** Encrypt a block - @param pt The plaintext - @param ct [out] The ciphertext - @param skey The scheduled key - @return CRYPT_OK if successful - */ - int (*ecb_encrypt)(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); - /** Decrypt a block - @param ct The ciphertext - @param pt [out] The plaintext - @param skey The scheduled key - @return CRYPT_OK if successful - */ - int (*ecb_decrypt)(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); - /** Test the block cipher - @return CRYPT_OK if successful, CRYPT_NOP if self-testing has been disabled - */ - int (*test)(void); - - /** Terminate the context - @param skey The scheduled key - */ - void (*done)(symmetric_key *skey); - - /** Determine a key size - @param keysize [in/out] The size of the key desired and the suggested size - @return CRYPT_OK if successful - */ - int (*keysize)(int *keysize); - -/** Accelerators **/ - /** Accelerated ECB encryption - @param pt Plaintext - @param ct Ciphertext - @param blocks The number of complete blocks to process - @param skey The scheduled key context - @return CRYPT_OK if successful - */ - int (*accel_ecb_encrypt)(const unsigned char *pt, unsigned char *ct, unsigned long blocks, symmetric_key *skey); - - /** Accelerated ECB decryption - @param pt Plaintext - @param ct Ciphertext - @param blocks The number of complete blocks to process - @param skey The scheduled key context - @return CRYPT_OK if successful - */ - int (*accel_ecb_decrypt)(const unsigned char *ct, unsigned char *pt, unsigned long blocks, symmetric_key *skey); - - /** Accelerated CBC encryption - @param pt Plaintext - @param ct Ciphertext - @param blocks The number of complete blocks to process - @param IV The initial value (input/output) - @param skey The scheduled key context - @return CRYPT_OK if successful - */ - int (*accel_cbc_encrypt)(const unsigned char *pt, unsigned char *ct, unsigned long blocks, unsigned char *IV, symmetric_key *skey); - - /** Accelerated CBC decryption - @param pt Plaintext - @param ct Ciphertext - @param blocks The number of complete blocks to process - @param IV The initial value (input/output) - @param skey The scheduled key context - @return CRYPT_OK if successful - */ - int (*accel_cbc_decrypt)(const unsigned char *ct, unsigned char *pt, unsigned long blocks, unsigned char *IV, symmetric_key *skey); - - /** Accelerated CTR encryption - @param pt Plaintext - @param ct Ciphertext - @param blocks The number of complete blocks to process - @param IV The initial value (input/output) - @param mode little or big endian counter (mode=0 or mode=1) - @param skey The scheduled key context - @return CRYPT_OK if successful - */ - int (*accel_ctr_encrypt)(const unsigned char *pt, unsigned char *ct, unsigned long blocks, unsigned char *IV, int mode, symmetric_key *skey); - - /** Accelerated LRW - @param pt Plaintext - @param ct Ciphertext - @param blocks The number of complete blocks to process - @param IV The initial value (input/output) - @param tweak The LRW tweak - @param skey The scheduled key context - @return CRYPT_OK if successful - */ - int (*accel_lrw_encrypt)(const unsigned char *pt, unsigned char *ct, unsigned long blocks, unsigned char *IV, const unsigned char *tweak, symmetric_key *skey); - - /** Accelerated LRW - @param ct Ciphertext - @param pt Plaintext - @param blocks The number of complete blocks to process - @param IV The initial value (input/output) - @param tweak The LRW tweak - @param skey The scheduled key context - @return CRYPT_OK if successful - */ - int (*accel_lrw_decrypt)(const unsigned char *ct, unsigned char *pt, unsigned long blocks, unsigned char *IV, const unsigned char *tweak, symmetric_key *skey); - - /** Accelerated CCM packet (one-shot) - @param key The secret key to use - @param keylen The length of the secret key (octets) - @param uskey A previously scheduled key [optional can be NULL] - @param nonce The session nonce [use once] - @param noncelen The length of the nonce - @param header The header for the session - @param headerlen The length of the header (octets) - @param pt [out] The plaintext - @param ptlen The length of the plaintext (octets) - @param ct [out] The ciphertext - @param tag [out] The destination tag - @param taglen [in/out] The max size and resulting size of the authentication tag - @param direction Encrypt or Decrypt direction (0 or 1) - @return CRYPT_OK if successful - */ - int (*accel_ccm_memory)( - const unsigned char *key, unsigned long keylen, - symmetric_key *uskey, - const unsigned char *nonce, unsigned long noncelen, - const unsigned char *header, unsigned long headerlen, - unsigned char *pt, unsigned long ptlen, - unsigned char *ct, - unsigned char *tag, unsigned long *taglen, - int direction); - - /** Accelerated GCM packet (one shot) - @param key The secret key - @param keylen The length of the secret key - @param IV The initial vector - @param IVlen The length of the initial vector - @param adata The additional authentication data (header) - @param adatalen The length of the adata - @param pt The plaintext - @param ptlen The length of the plaintext (ciphertext length is the same) - @param ct The ciphertext - @param tag [out] The MAC tag - @param taglen [in/out] The MAC tag length - @param direction Encrypt or Decrypt mode (GCM_ENCRYPT or GCM_DECRYPT) - @return CRYPT_OK on success - */ - int (*accel_gcm_memory)( - const unsigned char *key, unsigned long keylen, - const unsigned char *IV, unsigned long IVlen, - const unsigned char *adata, unsigned long adatalen, - unsigned char *pt, unsigned long ptlen, - unsigned char *ct, - unsigned char *tag, unsigned long *taglen, - int direction); - - /** Accelerated one shot LTC_OMAC - @param key The secret key - @param keylen The key length (octets) - @param in The message - @param inlen Length of message (octets) - @param out [out] Destination for tag - @param outlen [in/out] Initial and final size of out - @return CRYPT_OK on success - */ - int (*omac_memory)( - const unsigned char *key, unsigned long keylen, - const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); - - /** Accelerated one shot XCBC - @param key The secret key - @param keylen The key length (octets) - @param in The message - @param inlen Length of message (octets) - @param out [out] Destination for tag - @param outlen [in/out] Initial and final size of out - @return CRYPT_OK on success - */ - int (*xcbc_memory)( - const unsigned char *key, unsigned long keylen, - const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); - - /** Accelerated one shot F9 - @param key The secret key - @param keylen The key length (octets) - @param in The message - @param inlen Length of message (octets) - @param out [out] Destination for tag - @param outlen [in/out] Initial and final size of out - @return CRYPT_OK on success - @remark Requires manual padding - */ - int (*f9_memory)( - const unsigned char *key, unsigned long keylen, - const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); -} cipher_descriptor[]; - -#ifdef LTC_BLOWFISH -int blowfish_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int blowfish_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int blowfish_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int blowfish_test(void); -void blowfish_done(symmetric_key *skey); -int blowfish_keysize(int *keysize); -extern const struct ltc_cipher_descriptor blowfish_desc; -#endif - -#ifdef LTC_RC5 -int rc5_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int rc5_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int rc5_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int rc5_test(void); -void rc5_done(symmetric_key *skey); -int rc5_keysize(int *keysize); -extern const struct ltc_cipher_descriptor rc5_desc; -#endif - -#ifdef LTC_RC6 -int rc6_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int rc6_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int rc6_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int rc6_test(void); -void rc6_done(symmetric_key *skey); -int rc6_keysize(int *keysize); -extern const struct ltc_cipher_descriptor rc6_desc; -#endif - -#ifdef LTC_RC2 -int rc2_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int rc2_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int rc2_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int rc2_test(void); -void rc2_done(symmetric_key *skey); -int rc2_keysize(int *keysize); -extern const struct ltc_cipher_descriptor rc2_desc; -#endif - -#ifdef LTC_SAFERP -int saferp_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int saferp_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int saferp_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int saferp_test(void); -void saferp_done(symmetric_key *skey); -int saferp_keysize(int *keysize); -extern const struct ltc_cipher_descriptor saferp_desc; -#endif - -#ifdef LTC_SAFER -int safer_k64_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int safer_sk64_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int safer_k128_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int safer_sk128_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int safer_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *key); -int safer_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *key); -int safer_k64_test(void); -int safer_sk64_test(void); -int safer_sk128_test(void); -void safer_done(symmetric_key *skey); -int safer_64_keysize(int *keysize); -int safer_128_keysize(int *keysize); -extern const struct ltc_cipher_descriptor safer_k64_desc, safer_k128_desc, safer_sk64_desc, safer_sk128_desc; -#endif - -#ifdef LTC_RIJNDAEL - -/* make aes an alias */ -#define aes_setup rijndael_setup -#define aes_ecb_encrypt rijndael_ecb_encrypt -#define aes_ecb_decrypt rijndael_ecb_decrypt -#define aes_test rijndael_test -#define aes_done rijndael_done -#define aes_keysize rijndael_keysize - -#define aes_enc_setup rijndael_enc_setup -#define aes_enc_ecb_encrypt rijndael_enc_ecb_encrypt -#define aes_enc_keysize rijndael_enc_keysize - -int rijndael_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int rijndael_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int rijndael_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int rijndael_test(void); -void rijndael_done(symmetric_key *skey); -int rijndael_keysize(int *keysize); -int rijndael_enc_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int rijndael_enc_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -void rijndael_enc_done(symmetric_key *skey); -int rijndael_enc_keysize(int *keysize); -extern const struct ltc_cipher_descriptor rijndael_desc, aes_desc; -extern const struct ltc_cipher_descriptor rijndael_enc_desc, aes_enc_desc; -#endif - -#ifdef LTC_XTEA -int xtea_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int xtea_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int xtea_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int xtea_test(void); -void xtea_done(symmetric_key *skey); -int xtea_keysize(int *keysize); -extern const struct ltc_cipher_descriptor xtea_desc; -#endif - -#ifdef LTC_TWOFISH -int twofish_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int twofish_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int twofish_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int twofish_test(void); -void twofish_done(symmetric_key *skey); -int twofish_keysize(int *keysize); -extern const struct ltc_cipher_descriptor twofish_desc; -#endif - -#ifdef LTC_DES -int des_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int des_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int des_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int des_test(void); -void des_done(symmetric_key *skey); -int des_keysize(int *keysize); -int des3_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int des3_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int des3_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int des3_test(void); -void des3_done(symmetric_key *skey); -int des3_keysize(int *keysize); -extern const struct ltc_cipher_descriptor des_desc, des3_desc; -#endif - -#ifdef LTC_CAST5 -int cast5_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int cast5_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int cast5_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int cast5_test(void); -void cast5_done(symmetric_key *skey); -int cast5_keysize(int *keysize); -extern const struct ltc_cipher_descriptor cast5_desc; -#endif - -#ifdef LTC_NOEKEON -int noekeon_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int noekeon_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int noekeon_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int noekeon_test(void); -void noekeon_done(symmetric_key *skey); -int noekeon_keysize(int *keysize); -extern const struct ltc_cipher_descriptor noekeon_desc; -#endif - -#ifdef LTC_SKIPJACK -int skipjack_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int skipjack_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int skipjack_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int skipjack_test(void); -void skipjack_done(symmetric_key *skey); -int skipjack_keysize(int *keysize); -extern const struct ltc_cipher_descriptor skipjack_desc; -#endif - -#ifdef LTC_KHAZAD -int khazad_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int khazad_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int khazad_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int khazad_test(void); -void khazad_done(symmetric_key *skey); -int khazad_keysize(int *keysize); -extern const struct ltc_cipher_descriptor khazad_desc; -#endif - -#ifdef LTC_ANUBIS -int anubis_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int anubis_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int anubis_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int anubis_test(void); -void anubis_done(symmetric_key *skey); -int anubis_keysize(int *keysize); -extern const struct ltc_cipher_descriptor anubis_desc; -#endif - -#ifdef LTC_KSEED -int kseed_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int kseed_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int kseed_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int kseed_test(void); -void kseed_done(symmetric_key *skey); -int kseed_keysize(int *keysize); -extern const struct ltc_cipher_descriptor kseed_desc; -#endif - -#ifdef LTC_KASUMI -int kasumi_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int kasumi_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int kasumi_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int kasumi_test(void); -void kasumi_done(symmetric_key *skey); -int kasumi_keysize(int *keysize); -extern const struct ltc_cipher_descriptor kasumi_desc; -#endif - - -#ifdef LTC_MULTI2 -int multi2_setup(const unsigned char *key, int keylen, int num_rounds, symmetric_key *skey); -int multi2_ecb_encrypt(const unsigned char *pt, unsigned char *ct, symmetric_key *skey); -int multi2_ecb_decrypt(const unsigned char *ct, unsigned char *pt, symmetric_key *skey); -int multi2_test(void); -void multi2_done(symmetric_key *skey); -int multi2_keysize(int *keysize); -extern const struct ltc_cipher_descriptor multi2_desc; -#endif - -#ifdef LTC_ECB_MODE -int ecb_start(int cipher, const unsigned char *key, - int keylen, int num_rounds, symmetric_ECB *ecb); -int ecb_encrypt(const unsigned char *pt, unsigned char *ct, unsigned long len, symmetric_ECB *ecb); -int ecb_decrypt(const unsigned char *ct, unsigned char *pt, unsigned long len, symmetric_ECB *ecb); -int ecb_done(symmetric_ECB *ecb); -#endif - -#ifdef LTC_CFB_MODE -int cfb_start(int cipher, const unsigned char *IV, const unsigned char *key, - int keylen, int num_rounds, symmetric_CFB *cfb); -int cfb_encrypt(const unsigned char *pt, unsigned char *ct, unsigned long len, symmetric_CFB *cfb); -int cfb_decrypt(const unsigned char *ct, unsigned char *pt, unsigned long len, symmetric_CFB *cfb); -int cfb_getiv(unsigned char *IV, unsigned long *len, symmetric_CFB *cfb); -int cfb_setiv(const unsigned char *IV, unsigned long len, symmetric_CFB *cfb); -int cfb_done(symmetric_CFB *cfb); -#endif - -#ifdef LTC_OFB_MODE -int ofb_start(int cipher, const unsigned char *IV, const unsigned char *key, - int keylen, int num_rounds, symmetric_OFB *ofb); -int ofb_encrypt(const unsigned char *pt, unsigned char *ct, unsigned long len, symmetric_OFB *ofb); -int ofb_decrypt(const unsigned char *ct, unsigned char *pt, unsigned long len, symmetric_OFB *ofb); -int ofb_getiv(unsigned char *IV, unsigned long *len, symmetric_OFB *ofb); -int ofb_setiv(const unsigned char *IV, unsigned long len, symmetric_OFB *ofb); -int ofb_done(symmetric_OFB *ofb); -#endif - -#ifdef LTC_CBC_MODE -int cbc_start(int cipher, const unsigned char *IV, const unsigned char *key, - int keylen, int num_rounds, symmetric_CBC *cbc); -int cbc_encrypt(const unsigned char *pt, unsigned char *ct, unsigned long len, symmetric_CBC *cbc); -int cbc_decrypt(const unsigned char *ct, unsigned char *pt, unsigned long len, symmetric_CBC *cbc); -int cbc_getiv(unsigned char *IV, unsigned long *len, symmetric_CBC *cbc); -int cbc_setiv(const unsigned char *IV, unsigned long len, symmetric_CBC *cbc); -int cbc_done(symmetric_CBC *cbc); -#endif - -#ifdef LTC_CTR_MODE - -#define CTR_COUNTER_LITTLE_ENDIAN 0x0000 -#define CTR_COUNTER_BIG_ENDIAN 0x1000 -#define LTC_CTR_RFC3686 0x2000 - -int ctr_start( int cipher, - const unsigned char *IV, - const unsigned char *key, int keylen, - int num_rounds, int ctr_mode, - symmetric_CTR *ctr); -int ctr_encrypt(const unsigned char *pt, unsigned char *ct, unsigned long len, symmetric_CTR *ctr); -int ctr_decrypt(const unsigned char *ct, unsigned char *pt, unsigned long len, symmetric_CTR *ctr); -int ctr_getiv(unsigned char *IV, unsigned long *len, symmetric_CTR *ctr); -int ctr_setiv(const unsigned char *IV, unsigned long len, symmetric_CTR *ctr); -int ctr_done(symmetric_CTR *ctr); -int ctr_test(void); -#endif - -#ifdef LTC_LRW_MODE - -#define LRW_ENCRYPT 0 -#define LRW_DECRYPT 1 - -int lrw_start( int cipher, - const unsigned char *IV, - const unsigned char *key, int keylen, - const unsigned char *tweak, - int num_rounds, - symmetric_LRW *lrw); -int lrw_encrypt(const unsigned char *pt, unsigned char *ct, unsigned long len, symmetric_LRW *lrw); -int lrw_decrypt(const unsigned char *ct, unsigned char *pt, unsigned long len, symmetric_LRW *lrw); -int lrw_getiv(unsigned char *IV, unsigned long *len, symmetric_LRW *lrw); -int lrw_setiv(const unsigned char *IV, unsigned long len, symmetric_LRW *lrw); -int lrw_done(symmetric_LRW *lrw); -int lrw_test(void); - -/* don't call */ -int lrw_process(const unsigned char *pt, unsigned char *ct, unsigned long len, int mode, symmetric_LRW *lrw); -#endif - -#ifdef LTC_F8_MODE -int f8_start( int cipher, const unsigned char *IV, - const unsigned char *key, int keylen, - const unsigned char *salt_key, int skeylen, - int num_rounds, symmetric_F8 *f8); -int f8_encrypt(const unsigned char *pt, unsigned char *ct, unsigned long len, symmetric_F8 *f8); -int f8_decrypt(const unsigned char *ct, unsigned char *pt, unsigned long len, symmetric_F8 *f8); -int f8_getiv(unsigned char *IV, unsigned long *len, symmetric_F8 *f8); -int f8_setiv(const unsigned char *IV, unsigned long len, symmetric_F8 *f8); -int f8_done(symmetric_F8 *f8); -int f8_test_mode(void); -#endif - -#ifdef LTC_XTS_MODE -typedef struct { - symmetric_key key1, key2; - int cipher; -} symmetric_xts; - -int xts_start( int cipher, - const unsigned char *key1, - const unsigned char *key2, - unsigned long keylen, - int num_rounds, - symmetric_xts *xts); - -int xts_encrypt( - const unsigned char *pt, unsigned long ptlen, - unsigned char *ct, - const unsigned char *tweak, - symmetric_xts *xts); -int xts_decrypt( - const unsigned char *ct, unsigned long ptlen, - unsigned char *pt, - const unsigned char *tweak, - symmetric_xts *xts); - -void xts_done(symmetric_xts *xts); -int xts_test(void); -void xts_mult_x(unsigned char *I); -#endif - -int find_cipher(const char *name); -int find_cipher_any(const char *name, int blocklen, int keylen); -int find_cipher_id(unsigned char ID); -int register_cipher(const struct ltc_cipher_descriptor *cipher); -int unregister_cipher(const struct ltc_cipher_descriptor *cipher); -int cipher_is_valid(int idx); - -LTC_MUTEX_PROTO(ltc_cipher_mutex) - -/* $Source: /cvs/libtom/libtomcrypt/src/headers/tomcrypt_cipher.h,v $ */ -/* $Revision: 1.54 $ */ -/* $Date: 2007/05/12 14:37:41 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_custom.h b/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_custom.h deleted file mode 100644 index 88ec8f984ab..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_custom.h +++ /dev/null @@ -1,424 +0,0 @@ -#ifndef TOMCRYPT_CUSTOM_H_ -#define TOMCRYPT_CUSTOM_H_ - -#define LTC_NO_CIPHERS -#define LTC_NO_HASHES -#define LTC_NO_MACS -#define LTC_NO_PRNGS -#define LTC_NO_CURVES -#define LTC_NO_MODES -#define LTC_NO_PKCS -#define LTC_NO_ROLC - -#define LTC_SOURCE -#define LTC_SHA1 -#define LTC_MD5 -#define LTC_DER -#define LTC_RC4 - -#define USE_LTM -#define LTM_DESC - -/* macros for various libc functions you can change for embedded targets */ -#ifndef XMALLOC - #ifdef malloc - #define LTC_NO_PROTOTYPES - #endif -#define XMALLOC LibTomMalloc -#endif -#ifndef XREALLOC - #ifdef realloc - #define LTC_NO_PROTOTYPES - #endif -#define XREALLOC LibTomRealloc -#endif -#ifndef XCALLOC - #ifdef calloc - #define LTC_NO_PROTOTYPES - #endif -#define XCALLOC LibTomCalloc -#endif -#ifndef XFREE - #ifdef free - #define LTC_NO_PROTOTYPES - #endif -#define XFREE LibTomFree -#endif - -#ifndef XMEMSET - #ifdef memset - #define LTC_NO_PROTOTYPES - #endif -#define XMEMSET memset -#endif -#ifndef XMEMCPY - #ifdef memcpy - #define LTC_NO_PROTOTYPES - #endif -#define XMEMCPY memcpy -#endif -#ifndef XMEMCMP - #ifdef memcmp - #define LTC_NO_PROTOTYPES - #endif -#define XMEMCMP memcmp -#endif -#ifndef XSTRCMP - #ifdef strcmp - #define LTC_NO_PROTOTYPES - #endif -#define XSTRCMP strcmp -#endif - -#ifndef XCLOCK -#define XCLOCK LibTomClock -#endif -#ifndef XCLOCKS_PER_SEC -#define XCLOCKS_PER_SEC CLOCKS_PER_SEC -#endif - -#ifndef XQSORT - #ifdef qsort - #define LTC_NO_PROTOTYPES - #endif -#define XQSORT LibTomQsort -#endif - -/* Easy button? */ -#ifdef LTC_EASY - #define LTC_NO_CIPHERS - #define LTC_RIJNDAEL - #define LTC_BLOWFISH - #define LTC_DES - #define LTC_CAST5 - - #define LTC_NO_MODES - #define LTC_ECB_MODE - #define LTC_CBC_MODE - #define LTC_CTR_MODE - - #define LTC_NO_HASHES - #define LTC_SHA1 - #define LTC_SHA512 - #define LTC_SHA384 - #define LTC_SHA256 - #define LTC_SHA224 - - #define LTC_NO_MACS - #define LTC_HMAC - #define LTC_OMAC - #define LTC_CCM_MODE - - #define LTC_NO_PRNGS - #define LTC_SPRNG - #define LTC_YARROW - #define LTC_DEVRANDOM - #define TRY_URANDOM_FIRST - - #define LTC_NO_PK - #define LTC_MRSA - #define LTC_MECC -#endif - -/* Use small code where possible */ -/* #define LTC_SMALL_CODE */ - -/* Enable self-test test vector checking */ -#ifndef LTC_NO_TEST - #define LTC_TEST -#endif - -/* clean the stack of functions which put private information on stack */ -/* #define LTC_CLEAN_STACK */ - -/* disable all file related functions */ -/* #define LTC_NO_FILE */ - -/* disable all forms of ASM */ -/* #define LTC_NO_ASM */ - -/* disable FAST mode */ -/* #define LTC_NO_FAST */ - -/* disable BSWAP on x86 */ -/* #define LTC_NO_BSWAP */ - -/* ---> Symmetric Block Ciphers <--- */ -#ifndef LTC_NO_CIPHERS - -#define LTC_BLOWFISH -#define LTC_RC2 -#define LTC_RC5 -#define LTC_RC6 -#define LTC_SAFERP -#define LTC_RIJNDAEL -#define LTC_XTEA -/* _TABLES tells it to use tables during setup, _SMALL means to use the smaller scheduled key format - * (saves 4KB of ram), _ALL_TABLES enables all tables during setup */ -#define LTC_TWOFISH -#ifndef LTC_NO_TABLES - #define LTC_TWOFISH_TABLES - /* #define LTC_TWOFISH_ALL_TABLES */ -#else - #define LTC_TWOFISH_SMALL -#endif -/* #define LTC_TWOFISH_SMALL */ -/* LTC_DES includes EDE triple-LTC_DES */ -#define LTC_DES -#define LTC_CAST5 -#define LTC_NOEKEON -#define LTC_SKIPJACK -#define LTC_SAFER -#define LTC_KHAZAD -#define LTC_ANUBIS -#define LTC_ANUBIS_TWEAK -#define LTC_KSEED -#define LTC_KASUMI - -#endif /* LTC_NO_CIPHERS */ - - -/* ---> Block Cipher Modes of Operation <--- */ -#ifndef LTC_NO_MODES - -#define LTC_CFB_MODE -#define LTC_OFB_MODE -#define LTC_ECB_MODE -#define LTC_CBC_MODE -#define LTC_CTR_MODE - -/* F8 chaining mode */ -#define LTC_F8_MODE - -/* LRW mode */ -#define LTC_LRW_MODE -#ifndef LTC_NO_TABLES - /* like GCM mode this will enable 16 8x128 tables [64KB] that make - * seeking very fast. - */ - #define LRW_TABLES -#endif - -/* XTS mode */ -#define LTC_XTS_MODE - -#endif /* LTC_NO_MODES */ - -/* ---> One-Way Hash Functions <--- */ -#ifndef LTC_NO_HASHES - -#define LTC_CHC_HASH -#define LTC_WHIRLPOOL -#define LTC_SHA512 -#define LTC_SHA384 -#define LTC_SHA256 -#define LTC_SHA224 -#define LTC_TIGER -#define LTC_SHA1 -#define LTC_MD5 -#define LTC_MD4 -#define LTC_MD2 -#define LTC_RIPEMD128 -#define LTC_RIPEMD160 -#define LTC_RIPEMD256 -#define LTC_RIPEMD320 - -#endif /* LTC_NO_HASHES */ - -/* ---> MAC functions <--- */ -#ifndef LTC_NO_MACS - -#define LTC_HMAC -#define LTC_OMAC -#define LTC_PMAC -#define LTC_XCBC -#define LTC_F9_MODE -#define LTC_PELICAN - -#if defined(LTC_PELICAN) && !defined(LTC_RIJNDAEL) - #error Pelican-MAC requires LTC_RIJNDAEL -#endif - -/* ---> Encrypt + Authenticate Modes <--- */ - -#define LTC_EAX_MODE -#if defined(LTC_EAX_MODE) && !(defined(LTC_CTR_MODE) && defined(LTC_OMAC)) - #error LTC_EAX_MODE requires CTR and LTC_OMAC mode -#endif - -#define LTC_OCB_MODE -#define LTC_CCM_MODE -#define LTC_GCM_MODE - -/* Use 64KiB tables */ -#ifndef LTC_NO_TABLES - #define LTC_GCM_TABLES -#endif - -/* USE SSE2? requires GCC works on x86_32 and x86_64*/ -#ifdef LTC_GCM_TABLES -/* #define LTC_GCM_TABLES_SSE2 */ -#endif - -#endif /* LTC_NO_MACS */ - -/* Various tidbits of modern neatoness */ -#define LTC_BASE64 - -/* --> Pseudo Random Number Generators <--- */ -#ifndef LTC_NO_PRNGS - -/* Yarrow */ -#define LTC_YARROW -/* which descriptor of AES to use? */ -/* 0 = rijndael_enc 1 = aes_enc, 2 = rijndael [full], 3 = aes [full] */ -#define LTC_YARROW_AES 0 - -#if defined(LTC_YARROW) && !defined(LTC_CTR_MODE) - #error LTC_YARROW requires LTC_CTR_MODE chaining mode to be defined! -#endif - -/* a PRNG that simply reads from an available system source */ -#define LTC_SPRNG - -/* The LTC_RC4 stream cipher */ -#define LTC_RC4 - -/* Fortuna PRNG */ -#define LTC_FORTUNA -/* reseed every N calls to the read function */ -#define LTC_FORTUNA_WD 10 -/* number of pools (4..32) can save a bit of ram by lowering the count */ -#define LTC_FORTUNA_POOLS 32 - -/* Greg's LTC_SOBER128 PRNG ;-0 */ -#define LTC_SOBER128 - -/* the *nix style /dev/random device */ -#define LTC_DEVRANDOM -/* try /dev/urandom before trying /dev/random */ -#define TRY_URANDOM_FIRST - -#endif /* LTC_NO_PRNGS */ - -/* ---> math provider? <--- */ -#ifndef LTC_NO_MATH - -/* LibTomMath */ -#define LTM_LTC_DESC - -/* TomsFastMath */ -//#define TFM_LTC_DESC - -#endif /* LTC_NO_MATH */ - -/* ---> Public Key Crypto <--- */ -#ifndef LTC_NO_PK - -/* Include RSA support */ -#define LTC_MRSA - -/* Include Katja (a Rabin variant like RSA) */ -/* #define MKAT */ - -/* Digital Signature Algorithm */ -#define LTC_MDSA - -/* ECC */ -#define LTC_MECC - -/* use Shamir's trick for point mul (speeds up signature verification) */ -#define LTC_ECC_SHAMIR - -#if defined(TFM_LTC_DESC) && defined(LTC_MECC) - #define LTC_MECC_ACCEL -#endif - -/* do we want fixed point ECC */ -/* #define LTC_MECC_FP */ - -/* Timing Resistant? */ -/* #define LTC_ECC_TIMING_RESISTANT */ - -#endif /* LTC_NO_PK */ - -/* LTC_PKCS #1 (RSA) and #5 (Password Handling) stuff */ -#ifndef LTC_NO_PKCS - -#define LTC_PKCS_1 -#define LTC_PKCS_5 - -/* Include ASN.1 DER (required by DSA/RSA) */ -#define LTC_DER - -#endif /* LTC_NO_PKCS */ - -/* cleanup */ - -#ifdef LTC_MECC -/* Supported ECC Key Sizes */ -#ifndef LTC_NO_CURVES - #define ECC112 - #define ECC128 - #define ECC160 - #define ECC192 - #define ECC224 - #define ECC256 - #define ECC384 - #define ECC521 -#endif -#endif - -#if defined(LTC_MECC) || defined(LTC_MRSA) || defined(LTC_MDSA) || defined(MKATJA) - /* Include the MPI functionality? (required by the PK algorithms) */ - #define MPI -#endif - -#ifdef LTC_MRSA - #define LTC_PKCS_1 -#endif - -#if defined(LTC_DER) && !defined(MPI) - #error ASN.1 DER requires MPI functionality -#endif - -#if (defined(LTC_MDSA) || defined(LTC_MRSA) || defined(LTC_MECC) || defined(MKATJA)) && !defined(LTC_DER) - #error PK requires ASN.1 DER functionality, make sure LTC_DER is enabled -#endif - -/* THREAD management */ -#ifdef LTC_PTHREAD - -#include <pthread.h> - -#define LTC_MUTEX_GLOBAL(x) pthread_mutex_t x = PTHREAD_MUTEX_INITIALIZER; -#define LTC_MUTEX_PROTO(x) extern pthread_mutex_t x; -#define LTC_MUTEX_TYPE(x) pthread_mutex_t x; -#define LTC_MUTEX_INIT(x) pthread_mutex_init(x, NULL); -#define LTC_MUTEX_LOCK(x) pthread_mutex_lock(x); -#define LTC_MUTEX_UNLOCK(x) pthread_mutex_unlock(x); - -#else - -/* default no functions */ -#define LTC_MUTEX_GLOBAL(x) -#define LTC_MUTEX_PROTO(x) -#define LTC_MUTEX_TYPE(x) -#define LTC_MUTEX_INIT(x) -#define LTC_MUTEX_LOCK(x) -#define LTC_MUTEX_UNLOCK(x) - -#endif - -/* Debuggers */ - -/* define this if you use Valgrind, note: it CHANGES the way SOBER-128 and LTC_RC4 work (see the code) */ -/* #define LTC_VALGRIND */ - -#endif - - - -/* $Source: /cvs/libtom/libtomcrypt/src/headers/tomcrypt_custom.h,v $ */ -/* $Revision: 1.73 $ */ -/* $Date: 2007/05/12 14:37:41 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_hash.h b/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_hash.h deleted file mode 100644 index 18553ebf9da..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_hash.h +++ /dev/null @@ -1,378 +0,0 @@ -/* ---- HASH FUNCTIONS ---- */ -#ifdef LTC_SHA512 -struct sha512_state { - ulong64 length, state[8]; - unsigned long curlen; - unsigned char buf[128]; -}; -#endif - -#ifdef LTC_SHA256 -struct sha256_state { - ulong64 length; - ulong32 state[8], curlen; - unsigned char buf[64]; -}; -#endif - -#ifdef LTC_SHA1 -struct sha1_state { - ulong64 length; - ulong32 state[5], curlen; - unsigned char buf[64]; -}; -#endif - -#ifdef LTC_MD5 -struct md5_state { - ulong64 length; - ulong32 state[4], curlen; - unsigned char buf[64]; -}; -#endif - -#ifdef LTC_MD4 -struct md4_state { - ulong64 length; - ulong32 state[4], curlen; - unsigned char buf[64]; -}; -#endif - -#ifdef LTC_TIGER -struct tiger_state { - ulong64 state[3], length; - unsigned long curlen; - unsigned char buf[64]; -}; -#endif - -#ifdef LTC_MD2 -struct md2_state { - unsigned char chksum[16], X[48], buf[16]; - unsigned long curlen; -}; -#endif - -#ifdef LTC_RIPEMD128 -struct rmd128_state { - ulong64 length; - unsigned char buf[64]; - ulong32 curlen, state[4]; -}; -#endif - -#ifdef LTC_RIPEMD160 -struct rmd160_state { - ulong64 length; - unsigned char buf[64]; - ulong32 curlen, state[5]; -}; -#endif - -#ifdef LTC_RIPEMD256 -struct rmd256_state { - ulong64 length; - unsigned char buf[64]; - ulong32 curlen, state[8]; -}; -#endif - -#ifdef LTC_RIPEMD320 -struct rmd320_state { - ulong64 length; - unsigned char buf[64]; - ulong32 curlen, state[10]; -}; -#endif - -#ifdef LTC_WHIRLPOOL -struct whirlpool_state { - ulong64 length, state[8]; - unsigned char buf[64]; - ulong32 curlen; -}; -#endif - -#ifdef LTC_CHC_HASH -struct chc_state { - ulong64 length; - unsigned char state[MAXBLOCKSIZE], buf[MAXBLOCKSIZE]; - ulong32 curlen; -}; -#endif - -typedef union Hash_state { -#ifdef LTC_CHC_HASH - struct chc_state chc; -#endif -#ifdef LTC_WHIRLPOOL - struct whirlpool_state whirlpool; -#endif -#ifdef LTC_SHA512 - struct sha512_state sha512; -#endif -#ifdef LTC_SHA256 - struct sha256_state sha256; -#endif -#ifdef LTC_SHA1 - struct sha1_state sha1; -#endif -#ifdef LTC_MD5 - struct md5_state md5; -#endif -#ifdef LTC_MD4 - struct md4_state md4; -#endif -#ifdef LTC_MD2 - struct md2_state md2; -#endif -#ifdef LTC_TIGER - struct tiger_state tiger; -#endif -#ifdef LTC_RIPEMD128 - struct rmd128_state rmd128; -#endif -#ifdef LTC_RIPEMD160 - struct rmd160_state rmd160; -#endif -#ifdef LTC_RIPEMD256 - struct rmd256_state rmd256; -#endif -#ifdef LTC_RIPEMD320 - struct rmd320_state rmd320; -#endif - void *data; -} hash_state; - -/** hash descriptor */ -extern struct ltc_hash_descriptor { - /** name of hash */ - char *name; - /** internal ID */ - unsigned char ID; - /** Size of digest in octets */ - unsigned long hashsize; - /** Input block size in octets */ - unsigned long blocksize; - /** ASN.1 OID */ - unsigned long OID[16]; - /** Length of DER encoding */ - unsigned long OIDlen; - - /** Init a hash state - @param hash The hash to initialize - @return CRYPT_OK if successful - */ - int (*init)(hash_state *hash); - /** Process a block of data - @param hash The hash state - @param in The data to hash - @param inlen The length of the data (octets) - @return CRYPT_OK if successful - */ - int (*process)(hash_state *hash, const unsigned char *in, unsigned long inlen); - /** Produce the digest and store it - @param hash The hash state - @param out [out] The destination of the digest - @return CRYPT_OK if successful - */ - int (*done)(hash_state *hash, unsigned char *out); - /** Self-test - @return CRYPT_OK if successful, CRYPT_NOP if self-tests have been disabled - */ - int (*test)(void); - - /* accelerated hmac callback: if you need to-do multiple packets just use the generic hmac_memory and provide a hash callback */ - int (*hmac_block)(const unsigned char *key, unsigned long keylen, - const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); - -} hash_descriptor[]; - -#ifdef LTC_CHC_HASH -int chc_register(int cipher); -int chc_init(hash_state * md); -int chc_process(hash_state * md, const unsigned char *in, unsigned long inlen); -int chc_done(hash_state * md, unsigned char *hash); -int chc_test(void); -extern const struct ltc_hash_descriptor chc_desc; -#endif - -#ifdef LTC_WHIRLPOOL -int whirlpool_init(hash_state * md); -int whirlpool_process(hash_state * md, const unsigned char *in, unsigned long inlen); -int whirlpool_done(hash_state * md, unsigned char *hash); -int whirlpool_test(void); -extern const struct ltc_hash_descriptor whirlpool_desc; -#endif - -#ifdef LTC_SHA512 -int sha512_init(hash_state * md); -int sha512_process(hash_state * md, const unsigned char *in, unsigned long inlen); -int sha512_done(hash_state * md, unsigned char *hash); -int sha512_test(void); -extern const struct ltc_hash_descriptor sha512_desc; -#endif - -#ifdef LTC_SHA384 -#ifndef LTC_SHA512 - #error LTC_SHA512 is required for LTC_SHA384 -#endif -int sha384_init(hash_state * md); -#define sha384_process sha512_process -int sha384_done(hash_state * md, unsigned char *hash); -int sha384_test(void); -extern const struct ltc_hash_descriptor sha384_desc; -#endif - -#ifdef LTC_SHA256 -int sha256_init(hash_state * md); -int sha256_process(hash_state * md, const unsigned char *in, unsigned long inlen); -int sha256_done(hash_state * md, unsigned char *hash); -int sha256_test(void); -extern const struct ltc_hash_descriptor sha256_desc; - -#ifdef LTC_SHA224 -#ifndef LTC_SHA256 - #error LTC_SHA256 is required for LTC_SHA224 -#endif -int sha224_init(hash_state * md); -#define sha224_process sha256_process -int sha224_done(hash_state * md, unsigned char *hash); -int sha224_test(void); -extern const struct ltc_hash_descriptor sha224_desc; -#endif -#endif - -#ifdef LTC_SHA1 -int sha1_init(hash_state * md); -int sha1_process(hash_state * md, const unsigned char *in, unsigned long inlen); -int sha1_done(hash_state * md, unsigned char *hash); -int sha1_test(void); -extern const struct ltc_hash_descriptor sha1_desc; -#endif - -#ifdef LTC_MD5 -int md5_init(hash_state * md); -int md5_process(hash_state * md, const unsigned char *in, unsigned long inlen); -int md5_done(hash_state * md, unsigned char *hash); -int md5_test(void); -extern const struct ltc_hash_descriptor md5_desc; -#endif - -#ifdef LTC_MD4 -int md4_init(hash_state * md); -int md4_process(hash_state * md, const unsigned char *in, unsigned long inlen); -int md4_done(hash_state * md, unsigned char *hash); -int md4_test(void); -extern const struct ltc_hash_descriptor md4_desc; -#endif - -#ifdef LTC_MD2 -int md2_init(hash_state * md); -int md2_process(hash_state * md, const unsigned char *in, unsigned long inlen); -int md2_done(hash_state * md, unsigned char *hash); -int md2_test(void); -extern const struct ltc_hash_descriptor md2_desc; -#endif - -#ifdef LTC_TIGER -int tiger_init(hash_state * md); -int tiger_process(hash_state * md, const unsigned char *in, unsigned long inlen); -int tiger_done(hash_state * md, unsigned char *hash); -int tiger_test(void); -extern const struct ltc_hash_descriptor tiger_desc; -#endif - -#ifdef LTC_RIPEMD128 -int rmd128_init(hash_state * md); -int rmd128_process(hash_state * md, const unsigned char *in, unsigned long inlen); -int rmd128_done(hash_state * md, unsigned char *hash); -int rmd128_test(void); -extern const struct ltc_hash_descriptor rmd128_desc; -#endif - -#ifdef LTC_RIPEMD160 -int rmd160_init(hash_state * md); -int rmd160_process(hash_state * md, const unsigned char *in, unsigned long inlen); -int rmd160_done(hash_state * md, unsigned char *hash); -int rmd160_test(void); -extern const struct ltc_hash_descriptor rmd160_desc; -#endif - -#ifdef LTC_RIPEMD256 -int rmd256_init(hash_state * md); -int rmd256_process(hash_state * md, const unsigned char *in, unsigned long inlen); -int rmd256_done(hash_state * md, unsigned char *hash); -int rmd256_test(void); -extern const struct ltc_hash_descriptor rmd256_desc; -#endif - -#ifdef LTC_RIPEMD320 -int rmd320_init(hash_state * md); -int rmd320_process(hash_state * md, const unsigned char *in, unsigned long inlen); -int rmd320_done(hash_state * md, unsigned char *hash); -int rmd320_test(void); -extern const struct ltc_hash_descriptor rmd320_desc; -#endif - - -int find_hash(const char *name); -int find_hash_id(unsigned char ID); -int find_hash_oid(const unsigned long *ID, unsigned long IDlen); -int find_hash_any(const char *name, int digestlen); -int register_hash(const struct ltc_hash_descriptor *hash); -int unregister_hash(const struct ltc_hash_descriptor *hash); -int hash_is_valid(int idx); - -LTC_MUTEX_PROTO(ltc_hash_mutex) - -int hash_memory(int hash, - const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); -int hash_memory_multi(int hash, unsigned char *out, unsigned long *outlen, - const unsigned char *in, unsigned long inlen, ...); -int hash_filehandle(int hash, FILE *in, unsigned char *out, unsigned long *outlen); -int hash_file(int hash, const char *fname, unsigned char *out, unsigned long *outlen); - -/* a simple macro for making hash "process" functions */ -#define HASH_PROCESS(func_name, compress_name, state_var, block_size) \ -int func_name (hash_state * md, const unsigned char *in, unsigned long inlen) \ -{ \ - unsigned long n; \ - int err; \ - LTC_ARGCHK(md != NULL); \ - LTC_ARGCHK(in != NULL); \ - if (md-> state_var .curlen > sizeof(md-> state_var .buf)) { \ - return CRYPT_INVALID_ARG; \ - } \ - while (inlen > 0) { \ - if (md-> state_var .curlen == 0 && inlen >= block_size) { \ - if ((err = compress_name (md, (unsigned char *)in)) != CRYPT_OK) { \ - return err; \ - } \ - md-> state_var .length += block_size * 8; \ - in += block_size; \ - inlen -= block_size; \ - } else { \ - n = MIN(inlen, (block_size - md-> state_var .curlen)); \ - memcpy(md-> state_var .buf + md-> state_var.curlen, in, (size_t)n); \ - md-> state_var .curlen += n; \ - in += n; \ - inlen -= n; \ - if (md-> state_var .curlen == block_size) { \ - if ((err = compress_name (md, md-> state_var .buf)) != CRYPT_OK) { \ - return err; \ - } \ - md-> state_var .length += 8*block_size; \ - md-> state_var .curlen = 0; \ - } \ - } \ - } \ - return CRYPT_OK; \ -} - -/* $Source: /cvs/libtom/libtomcrypt/src/headers/tomcrypt_hash.h,v $ */ -/* $Revision: 1.22 $ */ -/* $Date: 2007/05/12 14:32:35 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_mac.h b/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_mac.h deleted file mode 100644 index 7ad9516bd29..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_mac.h +++ /dev/null @@ -1,384 +0,0 @@ -#ifdef LTC_HMAC -typedef struct Hmac_state { - hash_state md; - int hash; - hash_state hashstate; - unsigned char *key; -} hmac_state; - -int hmac_init(hmac_state *hmac, int hash, const unsigned char *key, unsigned long keylen); -int hmac_process(hmac_state *hmac, const unsigned char *in, unsigned long inlen); -int hmac_done(hmac_state *hmac, unsigned char *out, unsigned long *outlen); -int hmac_test(void); -int hmac_memory(int hash, - const unsigned char *key, unsigned long keylen, - const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); -int hmac_memory_multi(int hash, - const unsigned char *key, unsigned long keylen, - unsigned char *out, unsigned long *outlen, - const unsigned char *in, unsigned long inlen, ...); -int hmac_file(int hash, const char *fname, const unsigned char *key, - unsigned long keylen, - unsigned char *dst, unsigned long *dstlen); -#endif - -#ifdef LTC_OMAC - -typedef struct { - int cipher_idx, - buflen, - blklen; - unsigned char block[MAXBLOCKSIZE], - prev[MAXBLOCKSIZE], - Lu[2][MAXBLOCKSIZE]; - symmetric_key key; -} omac_state; - -int omac_init(omac_state *omac, int cipher, const unsigned char *key, unsigned long keylen); -int omac_process(omac_state *omac, const unsigned char *in, unsigned long inlen); -int omac_done(omac_state *omac, unsigned char *out, unsigned long *outlen); -int omac_memory(int cipher, - const unsigned char *key, unsigned long keylen, - const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); -int omac_memory_multi(int cipher, - const unsigned char *key, unsigned long keylen, - unsigned char *out, unsigned long *outlen, - const unsigned char *in, unsigned long inlen, ...); -int omac_file(int cipher, - const unsigned char *key, unsigned long keylen, - const char *filename, - unsigned char *out, unsigned long *outlen); -int omac_test(void); -#endif /* LTC_OMAC */ - -#ifdef LTC_PMAC - -typedef struct { - unsigned char Ls[32][MAXBLOCKSIZE], /* L shifted by i bits to the left */ - Li[MAXBLOCKSIZE], /* value of Li [current value, we calc from previous recall] */ - Lr[MAXBLOCKSIZE], /* L * x^-1 */ - block[MAXBLOCKSIZE], /* currently accumulated block */ - checksum[MAXBLOCKSIZE]; /* current checksum */ - - symmetric_key key; /* scheduled key for cipher */ - unsigned long block_index; /* index # for current block */ - int cipher_idx, /* cipher idx */ - block_len, /* length of block */ - buflen; /* number of bytes in the buffer */ -} pmac_state; - -int pmac_init(pmac_state *pmac, int cipher, const unsigned char *key, unsigned long keylen); -int pmac_process(pmac_state *pmac, const unsigned char *in, unsigned long inlen); -int pmac_done(pmac_state *pmac, unsigned char *out, unsigned long *outlen); - -int pmac_memory(int cipher, - const unsigned char *key, unsigned long keylen, - const unsigned char *msg, unsigned long msglen, - unsigned char *out, unsigned long *outlen); - -int pmac_memory_multi(int cipher, - const unsigned char *key, unsigned long keylen, - unsigned char *out, unsigned long *outlen, - const unsigned char *in, unsigned long inlen, ...); - -int pmac_file(int cipher, - const unsigned char *key, unsigned long keylen, - const char *filename, - unsigned char *out, unsigned long *outlen); - -int pmac_test(void); - -/* internal functions */ -int pmac_ntz(unsigned long x); -void pmac_shift_xor(pmac_state *pmac); - -#endif /* PMAC */ - -#ifdef LTC_EAX_MODE - -#if !(defined(LTC_OMAC) && defined(LTC_CTR_MODE)) - #error LTC_EAX_MODE requires LTC_OMAC and CTR -#endif - -typedef struct { - unsigned char N[MAXBLOCKSIZE]; - symmetric_CTR ctr; - omac_state headeromac, ctomac; -} eax_state; - -int eax_init(eax_state *eax, int cipher, const unsigned char *key, unsigned long keylen, - const unsigned char *nonce, unsigned long noncelen, - const unsigned char *header, unsigned long headerlen); - -int eax_encrypt(eax_state *eax, const unsigned char *pt, unsigned char *ct, unsigned long length); -int eax_decrypt(eax_state *eax, const unsigned char *ct, unsigned char *pt, unsigned long length); -int eax_addheader(eax_state *eax, const unsigned char *header, unsigned long length); -int eax_done(eax_state *eax, unsigned char *tag, unsigned long *taglen); - -int eax_encrypt_authenticate_memory(int cipher, - const unsigned char *key, unsigned long keylen, - const unsigned char *nonce, unsigned long noncelen, - const unsigned char *header, unsigned long headerlen, - const unsigned char *pt, unsigned long ptlen, - unsigned char *ct, - unsigned char *tag, unsigned long *taglen); - -int eax_decrypt_verify_memory(int cipher, - const unsigned char *key, unsigned long keylen, - const unsigned char *nonce, unsigned long noncelen, - const unsigned char *header, unsigned long headerlen, - const unsigned char *ct, unsigned long ctlen, - unsigned char *pt, - unsigned char *tag, unsigned long taglen, - int *stat); - - int eax_test(void); -#endif /* EAX MODE */ - -#ifdef LTC_OCB_MODE -typedef struct { - unsigned char L[MAXBLOCKSIZE], /* L value */ - Ls[32][MAXBLOCKSIZE], /* L shifted by i bits to the left */ - Li[MAXBLOCKSIZE], /* value of Li [current value, we calc from previous recall] */ - Lr[MAXBLOCKSIZE], /* L * x^-1 */ - R[MAXBLOCKSIZE], /* R value */ - checksum[MAXBLOCKSIZE]; /* current checksum */ - - symmetric_key key; /* scheduled key for cipher */ - unsigned long block_index; /* index # for current block */ - int cipher, /* cipher idx */ - block_len; /* length of block */ -} ocb_state; - -int ocb_init(ocb_state *ocb, int cipher, - const unsigned char *key, unsigned long keylen, const unsigned char *nonce); - -int ocb_encrypt(ocb_state *ocb, const unsigned char *pt, unsigned char *ct); -int ocb_decrypt(ocb_state *ocb, const unsigned char *ct, unsigned char *pt); - -int ocb_done_encrypt(ocb_state *ocb, - const unsigned char *pt, unsigned long ptlen, - unsigned char *ct, - unsigned char *tag, unsigned long *taglen); - -int ocb_done_decrypt(ocb_state *ocb, - const unsigned char *ct, unsigned long ctlen, - unsigned char *pt, - const unsigned char *tag, unsigned long taglen, int *stat); - -int ocb_encrypt_authenticate_memory(int cipher, - const unsigned char *key, unsigned long keylen, - const unsigned char *nonce, - const unsigned char *pt, unsigned long ptlen, - unsigned char *ct, - unsigned char *tag, unsigned long *taglen); - -int ocb_decrypt_verify_memory(int cipher, - const unsigned char *key, unsigned long keylen, - const unsigned char *nonce, - const unsigned char *ct, unsigned long ctlen, - unsigned char *pt, - const unsigned char *tag, unsigned long taglen, - int *stat); - -int ocb_test(void); - -/* internal functions */ -void ocb_shift_xor(ocb_state *ocb, unsigned char *Z); -int ocb_ntz(unsigned long x); -int s_ocb_done(ocb_state *ocb, const unsigned char *pt, unsigned long ptlen, - unsigned char *ct, unsigned char *tag, unsigned long *taglen, int mode); - -#endif /* LTC_OCB_MODE */ - -#ifdef LTC_CCM_MODE - -#define CCM_ENCRYPT 0 -#define CCM_DECRYPT 1 - -int ccm_memory(int cipher, - const unsigned char *key, unsigned long keylen, - symmetric_key *uskey, - const unsigned char *nonce, unsigned long noncelen, - const unsigned char *header, unsigned long headerlen, - unsigned char *pt, unsigned long ptlen, - unsigned char *ct, - unsigned char *tag, unsigned long *taglen, - int direction); - -int ccm_test(void); - -#endif /* LTC_CCM_MODE */ - -#if defined(LRW_MODE) || defined(LTC_GCM_MODE) -void gcm_gf_mult(const unsigned char *a, const unsigned char *b, unsigned char *c); -#endif - - -/* table shared between GCM and LRW */ -#if defined(LTC_GCM_TABLES) || defined(LRW_TABLES) || ((defined(LTC_GCM_MODE) || defined(LTC_GCM_MODE)) && defined(LTC_FAST)) -extern const unsigned char gcm_shift_table[]; -#endif - -#ifdef LTC_GCM_MODE - -#define GCM_ENCRYPT 0 -#define GCM_DECRYPT 1 - -#define LTC_GCM_MODE_IV 0 -#define LTC_GCM_MODE_AAD 1 -#define LTC_GCM_MODE_TEXT 2 - -typedef struct { - symmetric_key K; - unsigned char H[16], /* multiplier */ - X[16], /* accumulator */ - Y[16], /* counter */ - Y_0[16], /* initial counter */ - buf[16]; /* buffer for stuff */ - - int cipher, /* which cipher */ - ivmode, /* Which mode is the IV in? */ - mode, /* mode the GCM code is in */ - buflen; /* length of data in buf */ - - ulong64 totlen, /* 64-bit counter used for IV and AAD */ - pttotlen; /* 64-bit counter for the PT */ - -#ifdef LTC_GCM_TABLES - unsigned char PC[16][256][16] /* 16 tables of 8x128 */ -#ifdef LTC_GCM_TABLES_SSE2 -__attribute__ ((aligned (16))) -#endif -; -#endif -} gcm_state; - -void gcm_mult_h(gcm_state *gcm, unsigned char *I); - -int gcm_init(gcm_state *gcm, int cipher, - const unsigned char *key, int keylen); - -int gcm_reset(gcm_state *gcm); - -int gcm_add_iv(gcm_state *gcm, - const unsigned char *IV, unsigned long IVlen); - -int gcm_add_aad(gcm_state *gcm, - const unsigned char *adata, unsigned long adatalen); - -int gcm_process(gcm_state *gcm, - unsigned char *pt, unsigned long ptlen, - unsigned char *ct, - int direction); - -int gcm_done(gcm_state *gcm, - unsigned char *tag, unsigned long *taglen); - -int gcm_memory( int cipher, - const unsigned char *key, unsigned long keylen, - const unsigned char *IV, unsigned long IVlen, - const unsigned char *adata, unsigned long adatalen, - unsigned char *pt, unsigned long ptlen, - unsigned char *ct, - unsigned char *tag, unsigned long *taglen, - int direction); -int gcm_test(void); - -#endif /* LTC_GCM_MODE */ - -#ifdef LTC_PELICAN - -typedef struct pelican_state -{ - symmetric_key K; - unsigned char state[16]; - int buflen; -} pelican_state; - -int pelican_init(pelican_state *pelmac, const unsigned char *key, unsigned long keylen); -int pelican_process(pelican_state *pelmac, const unsigned char *in, unsigned long inlen); -int pelican_done(pelican_state *pelmac, unsigned char *out); -int pelican_test(void); - -int pelican_memory(const unsigned char *key, unsigned long keylen, - const unsigned char *in, unsigned long inlen, - unsigned char *out); - -#endif - -#ifdef LTC_XCBC - -/* add this to "keylen" to xcbc_init to use a pure three-key XCBC MAC */ -#define LTC_XCBC_PURE 0x8000UL - -typedef struct { - unsigned char K[3][MAXBLOCKSIZE], - IV[MAXBLOCKSIZE]; - - symmetric_key key; - - int cipher, - buflen, - blocksize; -} xcbc_state; - -int xcbc_init(xcbc_state *xcbc, int cipher, const unsigned char *key, unsigned long keylen); -int xcbc_process(xcbc_state *xcbc, const unsigned char *in, unsigned long inlen); -int xcbc_done(xcbc_state *xcbc, unsigned char *out, unsigned long *outlen); -int xcbc_memory(int cipher, - const unsigned char *key, unsigned long keylen, - const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); -int xcbc_memory_multi(int cipher, - const unsigned char *key, unsigned long keylen, - unsigned char *out, unsigned long *outlen, - const unsigned char *in, unsigned long inlen, ...); -int xcbc_file(int cipher, - const unsigned char *key, unsigned long keylen, - const char *filename, - unsigned char *out, unsigned long *outlen); -int xcbc_test(void); - -#endif - -#ifdef LTC_F9_MODE - -typedef struct { - unsigned char akey[MAXBLOCKSIZE], - ACC[MAXBLOCKSIZE], - IV[MAXBLOCKSIZE]; - - symmetric_key key; - - int cipher, - buflen, - keylen, - blocksize; -} f9_state; - -int f9_init(f9_state *f9, int cipher, const unsigned char *key, unsigned long keylen); -int f9_process(f9_state *f9, const unsigned char *in, unsigned long inlen); -int f9_done(f9_state *f9, unsigned char *out, unsigned long *outlen); -int f9_memory(int cipher, - const unsigned char *key, unsigned long keylen, - const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); -int f9_memory_multi(int cipher, - const unsigned char *key, unsigned long keylen, - unsigned char *out, unsigned long *outlen, - const unsigned char *in, unsigned long inlen, ...); -int f9_file(int cipher, - const unsigned char *key, unsigned long keylen, - const char *filename, - unsigned char *out, unsigned long *outlen); -int f9_test(void); - -#endif - - -/* $Source: /cvs/libtom/libtomcrypt/src/headers/tomcrypt_mac.h,v $ */ -/* $Revision: 1.23 $ */ -/* $Date: 2007/05/12 14:37:41 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_macros.h b/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_macros.h deleted file mode 100644 index 53bda9bb4ba..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_macros.h +++ /dev/null @@ -1,424 +0,0 @@ -/* fix for MSVC ...evil! */ -#ifdef _MSC_VER - #define CONST64(n) n ## ui64 - typedef unsigned __int64 ulong64; -#else - #define CONST64(n) n ## ULL - typedef unsigned long long ulong64; -#endif - -/* this is the "32-bit at least" data type - * Re-define it to suit your platform but it must be at least 32-bits - */ -#if defined(__x86_64__) || (defined(__sparc__) && defined(__arch64__)) - typedef unsigned ulong32; -#else - typedef unsigned long ulong32; -#endif - -/* ---- HELPER MACROS ---- */ -#ifdef ENDIAN_NEUTRAL - -#define STORE32L(x, y) \ - { (y)[3] = (unsigned char)(((x)>>24)&255); (y)[2] = (unsigned char)(((x)>>16)&255); \ - (y)[1] = (unsigned char)(((x)>>8)&255); (y)[0] = (unsigned char)((x)&255); } - -#define LOAD32L(x, y) \ - { x = ((unsigned long)((y)[3] & 255)<<24) | \ - ((unsigned long)((y)[2] & 255)<<16) | \ - ((unsigned long)((y)[1] & 255)<<8) | \ - ((unsigned long)((y)[0] & 255)); } - -#define STORE64L(x, y) \ - { (y)[7] = (unsigned char)(((x)>>56)&255); (y)[6] = (unsigned char)(((x)>>48)&255); \ - (y)[5] = (unsigned char)(((x)>>40)&255); (y)[4] = (unsigned char)(((x)>>32)&255); \ - (y)[3] = (unsigned char)(((x)>>24)&255); (y)[2] = (unsigned char)(((x)>>16)&255); \ - (y)[1] = (unsigned char)(((x)>>8)&255); (y)[0] = (unsigned char)((x)&255); } - -#define LOAD64L(x, y) \ - { x = (((ulong64)((y)[7] & 255))<<56)|(((ulong64)((y)[6] & 255))<<48)| \ - (((ulong64)((y)[5] & 255))<<40)|(((ulong64)((y)[4] & 255))<<32)| \ - (((ulong64)((y)[3] & 255))<<24)|(((ulong64)((y)[2] & 255))<<16)| \ - (((ulong64)((y)[1] & 255))<<8)|(((ulong64)((y)[0] & 255))); } - -#define STORE32H(x, y) \ - { (y)[0] = (unsigned char)(((x)>>24)&255); (y)[1] = (unsigned char)(((x)>>16)&255); \ - (y)[2] = (unsigned char)(((x)>>8)&255); (y)[3] = (unsigned char)((x)&255); } - -#define LOAD32H(x, y) \ - { x = ((unsigned long)((y)[0] & 255)<<24) | \ - ((unsigned long)((y)[1] & 255)<<16) | \ - ((unsigned long)((y)[2] & 255)<<8) | \ - ((unsigned long)((y)[3] & 255)); } - -#define STORE64H(x, y) \ - { (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255); \ - (y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255); \ - (y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255); \ - (y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); } - -#define LOAD64H(x, y) \ - { x = (((ulong64)((y)[0] & 255))<<56)|(((ulong64)((y)[1] & 255))<<48) | \ - (((ulong64)((y)[2] & 255))<<40)|(((ulong64)((y)[3] & 255))<<32) | \ - (((ulong64)((y)[4] & 255))<<24)|(((ulong64)((y)[5] & 255))<<16) | \ - (((ulong64)((y)[6] & 255))<<8)|(((ulong64)((y)[7] & 255))); } - -#endif /* ENDIAN_NEUTRAL */ - -#ifdef ENDIAN_LITTLE - -#if !defined(LTC_NO_BSWAP) && (defined(INTEL_CC) || (defined(__GNUC__) && (defined(__DJGPP__) || defined(__CYGWIN__) || defined(__MINGW32__) || defined(__i386__) || defined(__x86_64__)))) - -#define STORE32H(x, y) \ -asm __volatile__ ( \ - "bswapl %0 \n\t" \ - "movl %0,(%1)\n\t" \ - "bswapl %0 \n\t" \ - ::"r"(x), "r"(y)); - -#define LOAD32H(x, y) \ -asm __volatile__ ( \ - "movl (%1),%0\n\t" \ - "bswapl %0\n\t" \ - :"=r"(x): "r"(y)); - -#else - -#define STORE32H(x, y) \ - { (y)[0] = (unsigned char)(((x)>>24)&255); (y)[1] = (unsigned char)(((x)>>16)&255); \ - (y)[2] = (unsigned char)(((x)>>8)&255); (y)[3] = (unsigned char)((x)&255); } - -#define LOAD32H(x, y) \ - { x = ((unsigned long)((y)[0] & 255)<<24) | \ - ((unsigned long)((y)[1] & 255)<<16) | \ - ((unsigned long)((y)[2] & 255)<<8) | \ - ((unsigned long)((y)[3] & 255)); } - -#endif - - -/* x86_64 processor */ -#if !defined(LTC_NO_BSWAP) && (defined(__GNUC__) && defined(__x86_64__)) - -#define STORE64H(x, y) \ -asm __volatile__ ( \ - "bswapq %0 \n\t" \ - "movq %0,(%1)\n\t" \ - "bswapq %0 \n\t" \ - ::"r"(x), "r"(y)); - -#define LOAD64H(x, y) \ -asm __volatile__ ( \ - "movq (%1),%0\n\t" \ - "bswapq %0\n\t" \ - :"=r"(x): "r"(y)); - -#else - -#define STORE64H(x, y) \ - { (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255); \ - (y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255); \ - (y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255); \ - (y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); } - -#define LOAD64H(x, y) \ - { x = (((ulong64)((y)[0] & 255))<<56)|(((ulong64)((y)[1] & 255))<<48) | \ - (((ulong64)((y)[2] & 255))<<40)|(((ulong64)((y)[3] & 255))<<32) | \ - (((ulong64)((y)[4] & 255))<<24)|(((ulong64)((y)[5] & 255))<<16) | \ - (((ulong64)((y)[6] & 255))<<8)|(((ulong64)((y)[7] & 255))); } - -#endif - -#ifdef ENDIAN_32BITWORD - -#define STORE32L(x, y) \ - { ulong32 __t = (x); XMEMCPY(y, &__t, 4); } - -#define LOAD32L(x, y) \ - XMEMCPY(&(x), y, 4); - -#define STORE64L(x, y) \ - { (y)[7] = (unsigned char)(((x)>>56)&255); (y)[6] = (unsigned char)(((x)>>48)&255); \ - (y)[5] = (unsigned char)(((x)>>40)&255); (y)[4] = (unsigned char)(((x)>>32)&255); \ - (y)[3] = (unsigned char)(((x)>>24)&255); (y)[2] = (unsigned char)(((x)>>16)&255); \ - (y)[1] = (unsigned char)(((x)>>8)&255); (y)[0] = (unsigned char)((x)&255); } - -#define LOAD64L(x, y) \ - { x = (((ulong64)((y)[7] & 255))<<56)|(((ulong64)((y)[6] & 255))<<48)| \ - (((ulong64)((y)[5] & 255))<<40)|(((ulong64)((y)[4] & 255))<<32)| \ - (((ulong64)((y)[3] & 255))<<24)|(((ulong64)((y)[2] & 255))<<16)| \ - (((ulong64)((y)[1] & 255))<<8)|(((ulong64)((y)[0] & 255))); } - -#else /* 64-bit words then */ - -#define STORE32L(x, y) \ - { ulong32 __t = (x); XMEMCPY(y, &__t, 4); } - -#define LOAD32L(x, y) \ - { XMEMCPY(&(x), y, 4); x &= 0xFFFFFFFF; } - -#define STORE64L(x, y) \ - { ulong64 __t = (x); XMEMCPY(y, &__t, 8); } - -#define LOAD64L(x, y) \ - { XMEMCPY(&(x), y, 8); } - -#endif /* ENDIAN_64BITWORD */ - -#endif /* ENDIAN_LITTLE */ - -#ifdef ENDIAN_BIG -#define STORE32L(x, y) \ - { (y)[3] = (unsigned char)(((x)>>24)&255); (y)[2] = (unsigned char)(((x)>>16)&255); \ - (y)[1] = (unsigned char)(((x)>>8)&255); (y)[0] = (unsigned char)((x)&255); } - -#define LOAD32L(x, y) \ - { x = ((unsigned long)((y)[3] & 255)<<24) | \ - ((unsigned long)((y)[2] & 255)<<16) | \ - ((unsigned long)((y)[1] & 255)<<8) | \ - ((unsigned long)((y)[0] & 255)); } - -#define STORE64L(x, y) \ - { (y)[7] = (unsigned char)(((x)>>56)&255); (y)[6] = (unsigned char)(((x)>>48)&255); \ - (y)[5] = (unsigned char)(((x)>>40)&255); (y)[4] = (unsigned char)(((x)>>32)&255); \ - (y)[3] = (unsigned char)(((x)>>24)&255); (y)[2] = (unsigned char)(((x)>>16)&255); \ - (y)[1] = (unsigned char)(((x)>>8)&255); (y)[0] = (unsigned char)((x)&255); } - -#define LOAD64L(x, y) \ - { x = (((ulong64)((y)[7] & 255))<<56)|(((ulong64)((y)[6] & 255))<<48) | \ - (((ulong64)((y)[5] & 255))<<40)|(((ulong64)((y)[4] & 255))<<32) | \ - (((ulong64)((y)[3] & 255))<<24)|(((ulong64)((y)[2] & 255))<<16) | \ - (((ulong64)((y)[1] & 255))<<8)|(((ulong64)((y)[0] & 255))); } - -#ifdef ENDIAN_32BITWORD - -#define STORE32H(x, y) \ - { ulong32 __t = (x); XMEMCPY(y, &__t, 4); } - -#define LOAD32H(x, y) \ - XMEMCPY(&(x), y, 4); - -#define STORE64H(x, y) \ - { (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255); \ - (y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255); \ - (y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255); \ - (y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); } - -#define LOAD64H(x, y) \ - { x = (((ulong64)((y)[0] & 255))<<56)|(((ulong64)((y)[1] & 255))<<48)| \ - (((ulong64)((y)[2] & 255))<<40)|(((ulong64)((y)[3] & 255))<<32)| \ - (((ulong64)((y)[4] & 255))<<24)|(((ulong64)((y)[5] & 255))<<16)| \ - (((ulong64)((y)[6] & 255))<<8)| (((ulong64)((y)[7] & 255))); } - -#else /* 64-bit words then */ - -#define STORE32H(x, y) \ - { ulong32 __t = (x); XMEMCPY(y, &__t, 4); } - -#define LOAD32H(x, y) \ - { XMEMCPY(&(x), y, 4); x &= 0xFFFFFFFF; } - -#define STORE64H(x, y) \ - { ulong64 __t = (x); XMEMCPY(y, &__t, 8); } - -#define LOAD64H(x, y) \ - { XMEMCPY(&(x), y, 8); } - -#endif /* ENDIAN_64BITWORD */ -#endif /* ENDIAN_BIG */ - -#define BSWAP(x) ( ((x>>24)&0x000000FFUL) | ((x<<24)&0xFF000000UL) | \ - ((x>>8)&0x0000FF00UL) | ((x<<8)&0x00FF0000UL) ) - - -/* 32-bit Rotates */ -#if defined(_MSC_VER) - -/* instrinsic rotate */ -#include <stdlib.h> -#pragma intrinsic(_lrotr,_lrotl) -#define ROR(x,n) _lrotr(x,n) -#define ROL(x,n) _lrotl(x,n) -#define RORc(x,n) _lrotr(x,n) -#define ROLc(x,n) _lrotl(x,n) - -#elif !defined(__STRICT_ANSI__) && defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) && !defined(INTEL_CC) && !defined(LTC_NO_ASM) - -static inline unsigned ROL(unsigned word, int i) -{ - asm ("roll %%cl,%0" - :"=r" (word) - :"0" (word),"c" (i)); - return word; -} - -static inline unsigned ROR(unsigned word, int i) -{ - asm ("rorl %%cl,%0" - :"=r" (word) - :"0" (word),"c" (i)); - return word; -} - -#ifndef LTC_NO_ROLC - -static inline unsigned ROLc(unsigned word, const int i) -{ - asm ("roll %2,%0" - :"=r" (word) - :"0" (word),"I" (i)); - return word; -} - -static inline unsigned RORc(unsigned word, const int i) -{ - asm ("rorl %2,%0" - :"=r" (word) - :"0" (word),"I" (i)); - return word; -} - -#else - -#define ROLc ROL -#define RORc ROR - -#endif - -#elif !defined(__STRICT_ANSI__) && defined(LTC_PPC32) - -static inline unsigned ROL(unsigned word, int i) -{ - asm ("rotlw %0,%0,%2" - :"=r" (word) - :"0" (word),"r" (i)); - return word; -} - -static inline unsigned ROR(unsigned word, int i) -{ - asm ("rotlw %0,%0,%2" - :"=r" (word) - :"0" (word),"r" (32-i)); - return word; -} - -#ifndef LTC_NO_ROLC - -static inline unsigned ROLc(unsigned word, const int i) -{ - asm ("rotlwi %0,%0,%2" - :"=r" (word) - :"0" (word),"I" (i)); - return word; -} - -static inline unsigned RORc(unsigned word, const int i) -{ - asm ("rotrwi %0,%0,%2" - :"=r" (word) - :"0" (word),"I" (i)); - return word; -} - -#else - -#define ROLc ROL -#define RORc ROR - -#endif - - -#else - -/* rotates the hard way */ -#define ROL(x, y) ( (((unsigned long)(x)<<(unsigned long)((y)&31)) | (((unsigned long)(x)&0xFFFFFFFFUL)>>(unsigned long)(32-((y)&31)))) & 0xFFFFFFFFUL) -#define ROR(x, y) ( ((((unsigned long)(x)&0xFFFFFFFFUL)>>(unsigned long)((y)&31)) | ((unsigned long)(x)<<(unsigned long)(32-((y)&31)))) & 0xFFFFFFFFUL) -#define ROLc(x, y) ( (((unsigned long)(x)<<(unsigned long)((y)&31)) | (((unsigned long)(x)&0xFFFFFFFFUL)>>(unsigned long)(32-((y)&31)))) & 0xFFFFFFFFUL) -#define RORc(x, y) ( ((((unsigned long)(x)&0xFFFFFFFFUL)>>(unsigned long)((y)&31)) | ((unsigned long)(x)<<(unsigned long)(32-((y)&31)))) & 0xFFFFFFFFUL) - -#endif - - -/* 64-bit Rotates */ -#if !defined(__STRICT_ANSI__) && defined(__GNUC__) && defined(__x86_64__) && !defined(LTC_NO_ASM) - -static inline unsigned long ROL64(unsigned long word, int i) -{ - asm("rolq %%cl,%0" - :"=r" (word) - :"0" (word),"c" (i)); - return word; -} - -static inline unsigned long ROR64(unsigned long word, int i) -{ - asm("rorq %%cl,%0" - :"=r" (word) - :"0" (word),"c" (i)); - return word; -} - -#ifndef LTC_NO_ROLC - -static inline unsigned long ROL64c(unsigned long word, const int i) -{ - asm("rolq %2,%0" - :"=r" (word) - :"0" (word),"J" (i)); - return word; -} - -static inline unsigned long ROR64c(unsigned long word, const int i) -{ - asm("rorq %2,%0" - :"=r" (word) - :"0" (word),"J" (i)); - return word; -} - -#else /* LTC_NO_ROLC */ - -#define ROL64c ROL64 -#define ROR64c ROR64 - -#endif - -#else /* Not x86_64 */ - -#define ROL64(x, y) \ - ( (((x)<<((ulong64)(y)&63)) | \ - (((x)&CONST64(0xFFFFFFFFFFFFFFFF))>>((ulong64)64-((y)&63)))) & CONST64(0xFFFFFFFFFFFFFFFF)) - -#define ROR64(x, y) \ - ( ((((x)&CONST64(0xFFFFFFFFFFFFFFFF))>>((ulong64)(y)&CONST64(63))) | \ - ((x)<<((ulong64)(64-((y)&CONST64(63)))))) & CONST64(0xFFFFFFFFFFFFFFFF)) - -#define ROL64c(x, y) \ - ( (((x)<<((ulong64)(y)&63)) | \ - (((x)&CONST64(0xFFFFFFFFFFFFFFFF))>>((ulong64)64-((y)&63)))) & CONST64(0xFFFFFFFFFFFFFFFF)) - -#define ROR64c(x, y) \ - ( ((((x)&CONST64(0xFFFFFFFFFFFFFFFF))>>((ulong64)(y)&CONST64(63))) | \ - ((x)<<((ulong64)(64-((y)&CONST64(63)))))) & CONST64(0xFFFFFFFFFFFFFFFF)) - -#endif - -#ifndef MAX - #define MAX(x, y) ( ((x)>(y))?(x):(y) ) -#endif - -#ifndef MIN - #define MIN(x, y) ( ((x)<(y))?(x):(y) ) -#endif - -/* extract a byte portably */ -#ifdef _MSC_VER - #define byte(x, n) ((unsigned char)((x) >> (8 * (n)))) -#else - #define byte(x, n) (((x) >> (8 * (n))) & 255) -#endif - -/* $Source: /cvs/libtom/libtomcrypt/src/headers/tomcrypt_macros.h,v $ */ -/* $Revision: 1.15 $ */ -/* $Date: 2006/11/29 23:43:57 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_math.h b/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_math.h deleted file mode 100644 index a05d7fff942..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_math.h +++ /dev/null @@ -1,500 +0,0 @@ -/** math functions **/ - -#define LTC_MP_LT -1 -#define LTC_MP_EQ 0 -#define LTC_MP_GT 1 - -#define LTC_MP_NO 0 -#define LTC_MP_YES 1 - -#ifndef LTC_MECC - typedef void ecc_point; -#endif - -#ifndef LTC_MRSA - typedef void rsa_key; -#endif - -/** math descriptor */ -typedef struct { - /** Name of the math provider */ - char *name; - - /** Bits per digit, amount of bits must fit in an unsigned long */ - int bits_per_digit; - -/* ---- init/deinit functions ---- */ - - /** initialize a bignum - @param a The number to initialize - @return CRYPT_OK on success - */ - int (*init)(void **a); - - /** init copy - @param dst The number to initialize and write to - @param src The number to copy from - @return CRYPT_OK on success - */ - int (*init_copy)(void **dst, void *src); - - /** deinit - @param a The number to free - @return CRYPT_OK on success - */ - void (*deinit)(void *a); - -/* ---- data movement ---- */ - - /** negate - @param src The number to negate - @param dst The destination - @return CRYPT_OK on success - */ - int (*neg)(void *src, void *dst); - - /** copy - @param src The number to copy from - @param dst The number to write to - @return CRYPT_OK on success - */ - int (*copy)(void *src, void *dst); - -/* ---- trivial low level functions ---- */ - - /** set small constant - @param a Number to write to - @param n Source upto bits_per_digit (actually meant for very small constants) - @return CRYPT_OK on succcess - */ - int (*set_int)(void *a, unsigned long n); - - /** get small constant - @param a Number to read, only fetches upto bits_per_digit from the number - @return The lower bits_per_digit of the integer (unsigned) - */ - unsigned long (*get_int)(void *a); - - /** get digit n - @param a The number to read from - @param n The number of the digit to fetch - @return The bits_per_digit sized n'th digit of a - */ - unsigned long (*get_digit)(void *a, int n); - - /** Get the number of digits that represent the number - @param a The number to count - @return The number of digits used to represent the number - */ - int (*get_digit_count)(void *a); - - /** compare two integers - @param a The left side integer - @param b The right side integer - @return LTC_MP_LT if a < b, LTC_MP_GT if a > b and LTC_MP_EQ otherwise. (signed comparison) - */ - int (*compare)(void *a, void *b); - - /** compare against int - @param a The left side integer - @param b The right side integer (upto bits_per_digit) - @return LTC_MP_LT if a < b, LTC_MP_GT if a > b and LTC_MP_EQ otherwise. (signed comparison) - */ - int (*compare_d)(void *a, unsigned long n); - - /** Count the number of bits used to represent the integer - @param a The integer to count - @return The number of bits required to represent the integer - */ - int (*count_bits)(void * a); - - /** Count the number of LSB bits which are zero - @param a The integer to count - @return The number of contiguous zero LSB bits - */ - int (*count_lsb_bits)(void *a); - - /** Compute a power of two - @param a The integer to store the power in - @param n The power of two you want to store (a = 2^n) - @return CRYPT_OK on success - */ - int (*twoexpt)(void *a , int n); - -/* ---- radix conversions ---- */ - - /** read ascii string - @param a The integer to store into - @param str The string to read - @param radix The radix the integer has been represented in (2-64) - @return CRYPT_OK on success - */ - int (*read_radix)(void *a, const char *str, int radix); - - /** write number to string - @param a The integer to store - @param str The destination for the string - @param radix The radix the integer is to be represented in (2-64) - @return CRYPT_OK on success - */ - int (*write_radix)(void *a, char *str, int radix); - - /** get size as unsigned char string - @param a The integer to get the size (when stored in array of octets) - @return The length of the integer - */ - unsigned long (*unsigned_size)(void *a); - - /** store an integer as an array of octets - @param src The integer to store - @param dst The buffer to store the integer in - @return CRYPT_OK on success - */ - int (*unsigned_write)(void *src, unsigned char *dst); - - /** read an array of octets and store as integer - @param dst The integer to load - @param src The array of octets - @param len The number of octets - @return CRYPT_OK on success - */ - int (*unsigned_read)(void *dst, unsigned char *src, unsigned long len); - -/* ---- basic math ---- */ - - /** add two integers - @param a The first source integer - @param b The second source integer - @param c The destination of "a + b" - @return CRYPT_OK on success - */ - int (*add)(void *a, void *b, void *c); - - - /** add two integers - @param a The first source integer - @param b The second source integer (single digit of upto bits_per_digit in length) - @param c The destination of "a + b" - @return CRYPT_OK on success - */ - int (*addi)(void *a, unsigned long b, void *c); - - /** subtract two integers - @param a The first source integer - @param b The second source integer - @param c The destination of "a - b" - @return CRYPT_OK on success - */ - int (*sub)(void *a, void *b, void *c); - - /** subtract two integers - @param a The first source integer - @param b The second source integer (single digit of upto bits_per_digit in length) - @param c The destination of "a - b" - @return CRYPT_OK on success - */ - int (*subi)(void *a, unsigned long b, void *c); - - /** multiply two integers - @param a The first source integer - @param b The second source integer (single digit of upto bits_per_digit in length) - @param c The destination of "a * b" - @return CRYPT_OK on success - */ - int (*mul)(void *a, void *b, void *c); - - /** multiply two integers - @param a The first source integer - @param b The second source integer (single digit of upto bits_per_digit in length) - @param c The destination of "a * b" - @return CRYPT_OK on success - */ - int (*muli)(void *a, unsigned long b, void *c); - - /** Square an integer - @param a The integer to square - @param b The destination - @return CRYPT_OK on success - */ - int (*sqr)(void *a, void *b); - - /** Divide an integer - @param a The dividend - @param b The divisor - @param c The quotient (can be NULL to signify don't care) - @param d The remainder (can be NULL to signify don't care) - @return CRYPT_OK on success - */ - int (*mpdiv)(void *a, void *b, void *c, void *d); - - /** divide by two - @param a The integer to divide (shift right) - @param b The destination - @return CRYPT_OK on success - */ - int (*div_2)(void *a, void *b); - - /** Get remainder (small value) - @param a The integer to reduce - @param b The modulus (upto bits_per_digit in length) - @param c The destination for the residue - @return CRYPT_OK on success - */ - int (*modi)(void *a, unsigned long b, unsigned long *c); - - /** gcd - @param a The first integer - @param b The second integer - @param c The destination for (a, b) - @return CRYPT_OK on success - */ - int (*gcd)(void *a, void *b, void *c); - - /** lcm - @param a The first integer - @param b The second integer - @param c The destination for [a, b] - @return CRYPT_OK on success - */ - int (*lcm)(void *a, void *b, void *c); - - /** Modular multiplication - @param a The first source - @param b The second source - @param c The modulus - @param d The destination (a*b mod c) - @return CRYPT_OK on success - */ - int (*mulmod)(void *a, void *b, void *c, void *d); - - /** Modular squaring - @param a The first source - @param b The modulus - @param c The destination (a*a mod b) - @return CRYPT_OK on success - */ - int (*sqrmod)(void *a, void *b, void *c); - - /** Modular inversion - @param a The value to invert - @param b The modulus - @param c The destination (1/a mod b) - @return CRYPT_OK on success - */ - int (*invmod)(void *, void *, void *); - -/* ---- reduction ---- */ - - /** setup montgomery - @param a The modulus - @param b The destination for the reduction digit - @return CRYPT_OK on success - */ - int (*montgomery_setup)(void *a, void **b); - - /** get normalization value - @param a The destination for the normalization value - @param b The modulus - @return CRYPT_OK on success - */ - int (*montgomery_normalization)(void *a, void *b); - - /** reduce a number - @param a The number [and dest] to reduce - @param b The modulus - @param c The value "b" from montgomery_setup() - @return CRYPT_OK on success - */ - int (*montgomery_reduce)(void *a, void *b, void *c); - - /** clean up (frees memory) - @param a The value "b" from montgomery_setup() - @return CRYPT_OK on success - */ - void (*montgomery_deinit)(void *a); - -/* ---- exponentiation ---- */ - - /** Modular exponentiation - @param a The base integer - @param b The power (can be negative) integer - @param c The modulus integer - @param d The destination - @return CRYPT_OK on success - */ - int (*exptmod)(void *a, void *b, void *c, void *d); - - /** Primality testing - @param a The integer to test - @param b The destination of the result (FP_YES if prime) - @return CRYPT_OK on success - */ - int (*isprime)(void *a, int *b); - -/* ---- (optional) ecc point math ---- */ - - /** ECC GF(p) point multiplication (from the NIST curves) - @param k The integer to multiply the point by - @param G The point to multiply - @param R The destination for kG - @param modulus The modulus for the field - @param map Boolean indicated whether to map back to affine or not (can be ignored if you work in affine only) - @return CRYPT_OK on success - */ - int (*ecc_ptmul)(void *k, ecc_point *G, ecc_point *R, void *modulus, int map); - - /** ECC GF(p) point addition - @param P The first point - @param Q The second point - @param R The destination of P + Q - @param modulus The modulus - @param mp The "b" value from montgomery_setup() - @return CRYPT_OK on success - */ - int (*ecc_ptadd)(ecc_point *P, ecc_point *Q, ecc_point *R, void *modulus, void *mp); - - /** ECC GF(p) point double - @param P The first point - @param R The destination of 2P - @param modulus The modulus - @param mp The "b" value from montgomery_setup() - @return CRYPT_OK on success - */ - int (*ecc_ptdbl)(ecc_point *P, ecc_point *R, void *modulus, void *mp); - - /** ECC mapping from projective to affine, currently uses (x,y,z) => (x/z^2, y/z^3, 1) - @param P The point to map - @param modulus The modulus - @param mp The "b" value from montgomery_setup() - @return CRYPT_OK on success - @remark The mapping can be different but keep in mind a ecc_point only has three - integers (x,y,z) so if you use a different mapping you have to make it fit. - */ - int (*ecc_map)(ecc_point *P, void *modulus, void *mp); - - /** Computes kA*A + kB*B = C using Shamir's Trick - @param A First point to multiply - @param kA What to multiple A by - @param B Second point to multiply - @param kB What to multiple B by - @param C [out] Destination point (can overlap with A or B - @param modulus Modulus for curve - @return CRYPT_OK on success - */ - int (*ecc_mul2add)(ecc_point *A, void *kA, - ecc_point *B, void *kB, - ecc_point *C, - void *modulus); - -/* ---- (optional) rsa optimized math (for internal CRT) ---- */ - - /** RSA Key Generation - @param prng An active PRNG state - @param wprng The index of the PRNG desired - @param size The size of the modulus (key size) desired (octets) - @param e The "e" value (public key). e==65537 is a good choice - @param key [out] Destination of a newly created private key pair - @return CRYPT_OK if successful, upon error all allocated ram is freed - */ - int (*rsa_keygen)(prng_state *prng, int wprng, int size, long e, rsa_key *key); - - - /** RSA exponentiation - @param in The octet array representing the base - @param inlen The length of the input - @param out The destination (to be stored in an octet array format) - @param outlen The length of the output buffer and the resulting size (zero padded to the size of the modulus) - @param which PK_PUBLIC for public RSA and PK_PRIVATE for private RSA - @param key The RSA key to use - @return CRYPT_OK on success - */ - int (*rsa_me)(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen, int which, - rsa_key *key); -} ltc_math_descriptor; - -extern ltc_math_descriptor ltc_mp; - -int ltc_init_multi(void **a, ...); -void ltc_deinit_multi(void *a, ...); - -#ifdef LTM_DESC -extern const ltc_math_descriptor ltm_desc; -#endif - -#ifdef TFM_DESC -extern const ltc_math_descriptor tfm_desc; -#endif - -#ifdef GMP_DESC -extern const ltc_math_descriptor gmp_desc; -#endif - -#if !defined(DESC_DEF_ONLY) && defined(LTC_SOURCE) - -#define MP_DIGIT_BIT ltc_mp.bits_per_digit - -/* some handy macros */ -#define mp_init(a) ltc_mp.init(a) -#define mp_init_multi ltc_init_multi -#define mp_clear(a) ltc_mp.deinit(a) -#define mp_clear_multi ltc_deinit_multi -#define mp_init_copy(a, b) ltc_mp.init_copy(a, b) - -#define mp_neg(a, b) ltc_mp.neg(a, b) -#define mp_copy(a, b) ltc_mp.copy(a, b) - -#define mp_set(a, b) ltc_mp.set_int(a, b) -#define mp_set_int(a, b) ltc_mp.set_int(a, b) -#define mp_get_int(a) ltc_mp.get_int(a) -#define mp_get_digit(a, n) ltc_mp.get_digit(a, n) -#define mp_get_digit_count(a) ltc_mp.get_digit_count(a) -#define mp_cmp(a, b) ltc_mp.compare(a, b) -#define mp_cmp_d(a, b) ltc_mp.compare_d(a, b) -#define mp_count_bits(a) ltc_mp.count_bits(a) -#define mp_cnt_lsb(a) ltc_mp.count_lsb_bits(a) -#define mp_2expt(a, b) ltc_mp.twoexpt(a, b) - -#define mp_read_radix(a, b, c) ltc_mp.read_radix(a, b, c) -#define mp_toradix(a, b, c) ltc_mp.write_radix(a, b, c) -#define mp_unsigned_bin_size(a) ltc_mp.unsigned_size(a) -#define mp_to_unsigned_bin(a, b) ltc_mp.unsigned_write(a, b) -#define mp_read_unsigned_bin(a, b, c) ltc_mp.unsigned_read(a, b, c) - -#define mp_add(a, b, c) ltc_mp.add(a, b, c) -#define mp_add_d(a, b, c) ltc_mp.addi(a, b, c) -#define mp_sub(a, b, c) ltc_mp.sub(a, b, c) -#define mp_sub_d(a, b, c) ltc_mp.subi(a, b, c) -#define mp_mul(a, b, c) ltc_mp.mul(a, b, c) -#define mp_mul_d(a, b, c) ltc_mp.muli(a, b, c) -#define mp_sqr(a, b) ltc_mp.sqr(a, b) -#define mp_div(a, b, c, d) ltc_mp.mpdiv(a, b, c, d) -#define mp_div_2(a, b) ltc_mp.div_2(a, b) -#define mp_mod(a, b, c) ltc_mp.mpdiv(a, b, NULL, c) -#define mp_mod_d(a, b, c) ltc_mp.modi(a, b, c) -#define mp_gcd(a, b, c) ltc_mp.gcd(a, b, c) -#define mp_lcm(a, b, c) ltc_mp.lcm(a, b, c) - -#define mp_mulmod(a, b, c, d) ltc_mp.mulmod(a, b, c, d) -#define mp_sqrmod(a, b, c) ltc_mp.sqrmod(a, b, c) -#define mp_invmod(a, b, c) ltc_mp.invmod(a, b, c) - -#define mp_montgomery_setup(a, b) ltc_mp.montgomery_setup(a, b) -#define mp_montgomery_normalization(a, b) ltc_mp.montgomery_normalization(a, b) -#define mp_montgomery_reduce(a, b, c) ltc_mp.montgomery_reduce(a, b, c) -#define mp_montgomery_free(a) ltc_mp.montgomery_deinit(a) - -#define mp_exptmod(a,b,c,d) ltc_mp.exptmod(a,b,c,d) -#define mp_prime_is_prime(a, b, c) ltc_mp.isprime(a, c) - -#define mp_iszero(a) (mp_cmp_d(a, 0) == LTC_MP_EQ ? LTC_MP_YES : LTC_MP_NO) -#define mp_isodd(a) (mp_get_digit_count(a) > 0 ? (mp_get_digit(a, 0) & 1 ? LTC_MP_YES : LTC_MP_NO) : LTC_MP_NO) -#define mp_exch(a, b) do { void *ABC__tmp = a; a = b; b = ABC__tmp; } while(0); - -#define mp_tohex(a, b) mp_toradix(a, b, 16) - -#endif - -/* $Source: /cvs/libtom/libtomcrypt/src/headers/tomcrypt_math.h,v $ */ -/* $Revision: 1.44 $ */ -/* $Date: 2007/05/12 14:32:35 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_misc.h b/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_misc.h deleted file mode 100644 index f5384cacc51..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_misc.h +++ /dev/null @@ -1,23 +0,0 @@ -/* ---- LTC_BASE64 Routines ---- */ -#ifdef LTC_BASE64 -int base64_encode(const unsigned char *in, unsigned long len, - unsigned char *out, unsigned long *outlen); - -int base64_decode(const unsigned char *in, unsigned long len, - unsigned char *out, unsigned long *outlen); -#endif - -/* ---- MEM routines ---- */ -void zeromem(void *dst, size_t len); -void burn_stack(unsigned long len); - -const char *error_to_string(int err); - -extern const char *crypt_build_settings; - -/* ---- HMM ---- */ -int crypt_fsa(void *mp, ...); - -/* $Source: /cvs/libtom/libtomcrypt/src/headers/tomcrypt_misc.h,v $ */ -/* $Revision: 1.5 $ */ -/* $Date: 2007/05/12 14:32:35 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_pk.h b/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_pk.h deleted file mode 100644 index b5f277a8848..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_pk.h +++ /dev/null @@ -1,558 +0,0 @@ -/* ---- NUMBER THEORY ---- */ - -enum { - PK_PUBLIC=0, - PK_PRIVATE=1 -}; - -int rand_prime(void *N, long len, prng_state *prng, int wprng); - -/* ---- RSA ---- */ -#ifdef LTC_MRSA - -/* Min and Max RSA key sizes (in bits) */ -#define MIN_RSA_SIZE 1024 -#define MAX_RSA_SIZE 4096 - -/** RSA LTC_PKCS style key */ -typedef struct Rsa_key { - /** Type of key, PK_PRIVATE or PK_PUBLIC */ - int type; - /** The public exponent */ - void *e; - /** The private exponent */ - void *d; - /** The modulus */ - void *N; - /** The p factor of N */ - void *p; - /** The q factor of N */ - void *q; - /** The 1/q mod p CRT param */ - void *qP; - /** The d mod (p - 1) CRT param */ - void *dP; - /** The d mod (q - 1) CRT param */ - void *dQ; -} rsa_key; - -int rsa_make_key(prng_state *prng, int wprng, int size, long e, rsa_key *key); - -int rsa_exptmod(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen, int which, - rsa_key *key); - -void rsa_free(rsa_key *key); - -/* These use LTC_PKCS #1 v2.0 padding */ -#define rsa_encrypt_key(_in, _inlen, _out, _outlen, _lparam, _lparamlen, _prng, _prng_idx, _hash_idx, _key) \ - rsa_encrypt_key_ex(_in, _inlen, _out, _outlen, _lparam, _lparamlen, _prng, _prng_idx, _hash_idx, LTC_LTC_PKCS_1_OAEP, _key) - -#define rsa_decrypt_key(_in, _inlen, _out, _outlen, _lparam, _lparamlen, _hash_idx, _stat, _key) \ - rsa_decrypt_key_ex(_in, _inlen, _out, _outlen, _lparam, _lparamlen, _hash_idx, LTC_LTC_PKCS_1_OAEP, _stat, _key) - -#define rsa_sign_hash(_in, _inlen, _out, _outlen, _prng, _prng_idx, _hash_idx, _saltlen, _key) \ - rsa_sign_hash_ex(_in, _inlen, _out, _outlen, LTC_LTC_PKCS_1_PSS, _prng, _prng_idx, _hash_idx, _saltlen, _key) - -#define rsa_verify_hash(_sig, _siglen, _hash, _hashlen, _hash_idx, _saltlen, _stat, _key) \ - rsa_verify_hash_ex(_sig, _siglen, _hash, _hashlen, LTC_LTC_PKCS_1_PSS, _hash_idx, _saltlen, _stat, _key) - -/* These can be switched between LTC_PKCS #1 v2.x and LTC_PKCS #1 v1.5 paddings */ -int rsa_encrypt_key_ex(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen, - const unsigned char *lparam, unsigned long lparamlen, - prng_state *prng, int prng_idx, int hash_idx, int padding, rsa_key *key); - -int rsa_decrypt_key_ex(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen, - const unsigned char *lparam, unsigned long lparamlen, - int hash_idx, int padding, - int *stat, rsa_key *key); - -int rsa_sign_hash_ex(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen, - int padding, - prng_state *prng, int prng_idx, - int hash_idx, unsigned long saltlen, - rsa_key *key); - -int rsa_verify_hash_ex(const unsigned char *sig, unsigned long siglen, - const unsigned char *hash, unsigned long hashlen, - int padding, - int hash_idx, unsigned long saltlen, - int *stat, rsa_key *key); - -/* LTC_PKCS #1 import/export */ -int rsa_export(unsigned char *out, unsigned long *outlen, int type, rsa_key *key); -int rsa_import(const unsigned char *in, unsigned long inlen, rsa_key *key); - -/* Ladik: Added for verifying Blizzard strong signature verification */ -int rsa_verify_simple(const unsigned char *sig, unsigned long siglen, - const unsigned char *hash, unsigned long hashlen, - int *stat, - rsa_key *key); - -#endif - -/* ---- Katja ---- */ -#ifdef MKAT - -/* Min and Max KAT key sizes (in bits) */ -#define MIN_KAT_SIZE 1024 -#define MAX_KAT_SIZE 4096 - -/** Katja LTC_PKCS style key */ -typedef struct KAT_key { - /** Type of key, PK_PRIVATE or PK_PUBLIC */ - int type; - /** The private exponent */ - void *d; - /** The modulus */ - void *N; - /** The p factor of N */ - void *p; - /** The q factor of N */ - void *q; - /** The 1/q mod p CRT param */ - void *qP; - /** The d mod (p - 1) CRT param */ - void *dP; - /** The d mod (q - 1) CRT param */ - void *dQ; - /** The pq param */ - void *pq; -} katja_key; - -int katja_make_key(prng_state *prng, int wprng, int size, katja_key *key); - -int katja_exptmod(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen, int which, - katja_key *key); - -void katja_free(katja_key *key); - -/* These use LTC_PKCS #1 v2.0 padding */ -int katja_encrypt_key(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen, - const unsigned char *lparam, unsigned long lparamlen, - prng_state *prng, int prng_idx, int hash_idx, katja_key *key); - -int katja_decrypt_key(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen, - const unsigned char *lparam, unsigned long lparamlen, - int hash_idx, int *stat, - katja_key *key); - -/* LTC_PKCS #1 import/export */ -int katja_export(unsigned char *out, unsigned long *outlen, int type, katja_key *key); -int katja_import(const unsigned char *in, unsigned long inlen, katja_key *key); - -#endif - -/* ---- ECC Routines ---- */ -#ifdef LTC_MECC - -/* size of our temp buffers for exported keys */ -#define ECC_BUF_SIZE 256 - -/* max private key size */ -#define ECC_MAXSIZE 66 - -/** Structure defines a NIST GF(p) curve */ -typedef struct { - /** The size of the curve in octets */ - int size; - - /** name of curve */ - char *name; - - /** The prime that defines the field the curve is in (encoded in hex) */ - char *prime; - - /** The fields B param (hex) */ - char *B; - - /** The order of the curve (hex) */ - char *order; - - /** The x co-ordinate of the base point on the curve (hex) */ - char *Gx; - - /** The y co-ordinate of the base point on the curve (hex) */ - char *Gy; -} ltc_ecc_set_type; - -/** A point on a ECC curve, stored in Jacbobian format such that (x,y,z) => (x/z^2, y/z^3, 1) when interpretted as affine */ -typedef struct { - /** The x co-ordinate */ - void *x; - - /** The y co-ordinate */ - void *y; - - /** The z co-ordinate */ - void *z; -} ecc_point; - -/** An ECC key */ -typedef struct { - /** Type of key, PK_PRIVATE or PK_PUBLIC */ - int type; - - /** Index into the ltc_ecc_sets[] for the parameters of this curve; if -1, then this key is using user supplied curve in dp */ - int idx; - - /** pointer to domain parameters; either points to NIST curves (identified by idx >= 0) or user supplied curve */ - const ltc_ecc_set_type *dp; - - /** The public key */ - ecc_point pubkey; - - /** The private key */ - void *k; -} ecc_key; - -/** the ECC params provided */ -extern const ltc_ecc_set_type ltc_ecc_sets[]; - -int ecc_test(void); -void ecc_sizes(int *low, int *high); -int ecc_get_size(ecc_key *key); - -int ecc_make_key(prng_state *prng, int wprng, int keysize, ecc_key *key); -int ecc_make_key_ex(prng_state *prng, int wprng, ecc_key *key, const ltc_ecc_set_type *dp); -void ecc_free(ecc_key *key); - -int ecc_export(unsigned char *out, unsigned long *outlen, int type, ecc_key *key); -int ecc_import(const unsigned char *in, unsigned long inlen, ecc_key *key); -int ecc_import_ex(const unsigned char *in, unsigned long inlen, ecc_key *key, const ltc_ecc_set_type *dp); - -int ecc_ansi_x963_export(ecc_key *key, unsigned char *out, unsigned long *outlen); -int ecc_ansi_x963_import(const unsigned char *in, unsigned long inlen, ecc_key *key); -int ecc_ansi_x963_import_ex(const unsigned char *in, unsigned long inlen, ecc_key *key, ltc_ecc_set_type *dp); - -int ecc_shared_secret(ecc_key *private_key, ecc_key *public_key, - unsigned char *out, unsigned long *outlen); - -int ecc_encrypt_key(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen, - prng_state *prng, int wprng, int hash, - ecc_key *key); - -int ecc_decrypt_key(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen, - ecc_key *key); - -int ecc_sign_hash(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen, - prng_state *prng, int wprng, ecc_key *key); - -int ecc_verify_hash(const unsigned char *sig, unsigned long siglen, - const unsigned char *hash, unsigned long hashlen, - int *stat, ecc_key *key); - -/* low level functions */ -ecc_point *ltc_ecc_new_point(void); -void ltc_ecc_del_point(ecc_point *p); -int ltc_ecc_is_valid_idx(int n); - -/* point ops (mp == montgomery digit) */ -#if !defined(LTC_MECC_ACCEL) || defined(LTM_LTC_DESC) || defined(GMP_LTC_DESC) -/* R = 2P */ -int ltc_ecc_projective_dbl_point(ecc_point *P, ecc_point *R, void *modulus, void *mp); - -/* R = P + Q */ -int ltc_ecc_projective_add_point(ecc_point *P, ecc_point *Q, ecc_point *R, void *modulus, void *mp); -#endif - -#if defined(LTC_MECC_FP) -/* optimized point multiplication using fixed point cache (HAC algorithm 14.117) */ -int ltc_ecc_fp_mulmod(void *k, ecc_point *G, ecc_point *R, void *modulus, int map); - -/* functions for saving/loading/freeing/adding to fixed point cache */ -int ltc_ecc_fp_save_state(unsigned char **out, unsigned long *outlen); -int ltc_ecc_fp_restore_state(unsigned char *in, unsigned long inlen); -void ltc_ecc_fp_free(void); -int ltc_ecc_fp_add_point(ecc_point *g, void *modulus, int lock); - -/* lock/unlock all points currently in fixed point cache */ -void ltc_ecc_fp_tablelock(int lock); -#endif - -/* R = kG */ -int ltc_ecc_mulmod(void *k, ecc_point *G, ecc_point *R, void *modulus, int map); - -#ifdef LTC_ECC_SHAMIR -/* kA*A + kB*B = C */ -int ltc_ecc_mul2add(ecc_point *A, void *kA, - ecc_point *B, void *kB, - ecc_point *C, - void *modulus); - -#ifdef LTC_MECC_FP -/* Shamir's trick with optimized point multiplication using fixed point cache */ -int ltc_ecc_fp_mul2add(ecc_point *A, void *kA, - ecc_point *B, void *kB, - ecc_point *C, void *modulus); -#endif - -#endif - - -/* map P to affine from projective */ -int ltc_ecc_map(ecc_point *P, void *modulus, void *mp); - -#endif - -#ifdef LTC_MDSA - -/* Max diff between group and modulus size in bytes */ -#define LTC_MDSA_DELTA 512 - -/* Max DSA group size in bytes (default allows 4k-bit groups) */ -#define LTC_MDSA_MAX_GROUP 512 - -/** DSA key structure */ -typedef struct { - /** The key type, PK_PRIVATE or PK_PUBLIC */ - int type; - - /** The order of the sub-group used in octets */ - int qord; - - /** The generator */ - void *g; - - /** The prime used to generate the sub-group */ - void *q; - - /** The large prime that generats the field the contains the sub-group */ - void *p; - - /** The private key */ - void *x; - - /** The public key */ - void *y; -} dsa_key; - -int dsa_make_key(prng_state *prng, int wprng, int group_size, int modulus_size, dsa_key *key); -void dsa_free(dsa_key *key); - -int dsa_sign_hash_raw(const unsigned char *in, unsigned long inlen, - void *r, void *s, - prng_state *prng, int wprng, dsa_key *key); - -int dsa_sign_hash(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen, - prng_state *prng, int wprng, dsa_key *key); - -int dsa_verify_hash_raw( void *r, void *s, - const unsigned char *hash, unsigned long hashlen, - int *stat, dsa_key *key); - -int dsa_verify_hash(const unsigned char *sig, unsigned long siglen, - const unsigned char *hash, unsigned long hashlen, - int *stat, dsa_key *key); - -int dsa_encrypt_key(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen, - prng_state *prng, int wprng, int hash, - dsa_key *key); - -int dsa_decrypt_key(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen, - dsa_key *key); - -int dsa_import(const unsigned char *in, unsigned long inlen, dsa_key *key); -int dsa_export(unsigned char *out, unsigned long *outlen, int type, dsa_key *key); -int dsa_verify_key(dsa_key *key, int *stat); - -int dsa_shared_secret(void *private_key, void *base, - dsa_key *public_key, - unsigned char *out, unsigned long *outlen); -#endif - -#ifdef LTC_DER -/* DER handling */ - -enum { - LTC_ASN1_EOL, - LTC_ASN1_BOOLEAN, - LTC_ASN1_INTEGER, - LTC_ASN1_SHORT_INTEGER, - LTC_ASN1_BIT_STRING, - LTC_ASN1_OCTET_STRING, - LTC_ASN1_NULL, - LTC_ASN1_OBJECT_IDENTIFIER, - LTC_ASN1_IA5_STRING, - LTC_ASN1_PRINTABLE_STRING, - LTC_ASN1_UTF8_STRING, - LTC_ASN1_UTCTIME, - LTC_ASN1_CHOICE, - LTC_ASN1_SEQUENCE, - LTC_ASN1_SET, - LTC_ASN1_SETOF -}; - -/** A LTC ASN.1 list type */ -typedef struct ltc_asn1_list_ { - /** The LTC ASN.1 enumerated type identifier */ - int type; - /** The data to encode or place for decoding */ - void *data; - /** The size of the input or resulting output */ - unsigned long size; - /** The used flag, this is used by the CHOICE ASN.1 type to indicate which choice was made */ - int used; - /** prev/next entry in the list */ - struct ltc_asn1_list_ *prev, *next, *child, *parent; -} ltc_asn1_list; - -#define LTC_SET_ASN1(list, index, Type, Data, Size) \ - do { \ - int LTC_MACRO_temp = (index); \ - ltc_asn1_list *LTC_MACRO_list = (list); \ - LTC_MACRO_list[LTC_MACRO_temp].type = (Type); \ - LTC_MACRO_list[LTC_MACRO_temp].data = (void*)(Data); \ - LTC_MACRO_list[LTC_MACRO_temp].size = (Size); \ - LTC_MACRO_list[LTC_MACRO_temp].used = 0; \ - } while (0); - -/* SEQUENCE */ -int der_encode_sequence_ex(ltc_asn1_list *list, unsigned long inlen, - unsigned char *out, unsigned long *outlen, int type_of); - -#define der_encode_sequence(list, inlen, out, outlen) der_encode_sequence_ex(list, inlen, out, outlen, LTC_ASN1_SEQUENCE) - -int der_decode_sequence_ex(const unsigned char *in, unsigned long inlen, - ltc_asn1_list *list, unsigned long outlen, int ordered); - -#define der_decode_sequence(in, inlen, list, outlen) der_decode_sequence_ex(in, inlen, list, outlen, 1) - -int der_length_sequence(ltc_asn1_list *list, unsigned long inlen, - unsigned long *outlen); - -/* SET */ -#define der_decode_set(in, inlen, list, outlen) der_decode_sequence_ex(in, inlen, list, outlen, 0) -#define der_length_set der_length_sequence -int der_encode_set(ltc_asn1_list *list, unsigned long inlen, - unsigned char *out, unsigned long *outlen); - -int der_encode_setof(ltc_asn1_list *list, unsigned long inlen, - unsigned char *out, unsigned long *outlen); - -/* VA list handy helpers with triplets of <type, size, data> */ -int der_encode_sequence_multi(unsigned char *out, unsigned long *outlen, ...); -int der_decode_sequence_multi(const unsigned char *in, unsigned long inlen, ...); - -/* FLEXI DECODER handle unknown list decoder */ -int der_decode_sequence_flexi(const unsigned char *in, unsigned long *inlen, ltc_asn1_list **out); -void der_free_sequence_flexi(ltc_asn1_list *list); -void der_sequence_free(ltc_asn1_list *in); - -/* BOOLEAN */ -int der_length_boolean(unsigned long *outlen); -int der_encode_boolean(int in, - unsigned char *out, unsigned long *outlen); -int der_decode_boolean(const unsigned char *in, unsigned long inlen, - int *out); -/* INTEGER */ -int der_encode_integer(void *num, unsigned char *out, unsigned long *outlen); -int der_decode_integer(const unsigned char *in, unsigned long inlen, void *num); -int der_length_integer(void *num, unsigned long *len); - -/* INTEGER -- handy for 0..2^32-1 values */ -int der_decode_short_integer(const unsigned char *in, unsigned long inlen, unsigned long *num); -int der_encode_short_integer(unsigned long num, unsigned char *out, unsigned long *outlen); -int der_length_short_integer(unsigned long num, unsigned long *outlen); - -/* BIT STRING */ -int der_encode_bit_string(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); -int der_decode_bit_string(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); -int der_length_bit_string(unsigned long nbits, unsigned long *outlen); - -/* OCTET STRING */ -int der_encode_octet_string(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); -int der_decode_octet_string(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); -int der_length_octet_string(unsigned long noctets, unsigned long *outlen); - -/* OBJECT IDENTIFIER */ -int der_encode_object_identifier(unsigned long *words, unsigned long nwords, - unsigned char *out, unsigned long *outlen); -int der_decode_object_identifier(const unsigned char *in, unsigned long inlen, - unsigned long *words, unsigned long *outlen); -int der_length_object_identifier(unsigned long *words, unsigned long nwords, unsigned long *outlen); -unsigned long der_object_identifier_bits(unsigned long x); - -/* IA5 STRING */ -int der_encode_ia5_string(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); -int der_decode_ia5_string(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); -int der_length_ia5_string(const unsigned char *octets, unsigned long noctets, unsigned long *outlen); - -int der_ia5_char_encode(int c); -int der_ia5_value_decode(int v); - -/* Printable STRING */ -int der_encode_printable_string(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); -int der_decode_printable_string(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); -int der_length_printable_string(const unsigned char *octets, unsigned long noctets, unsigned long *outlen); - -int der_printable_char_encode(int c); -int der_printable_value_decode(int v); - -/* UTF-8 */ -#if (defined(SIZE_MAX) || __STDC_VERSION__ >= 199901L || defined(WCHAR_MAX) || defined(_WCHAR_T) || defined(_WCHAR_T_DEFINED) || defined (__WCHAR_TYPE__)) && !defined(LTC_NO_WCHAR) -#include <wchar.h> -#else -typedef ulong32 wchar_t; -#endif - -int der_encode_utf8_string(const wchar_t *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen); - -int der_decode_utf8_string(const unsigned char *in, unsigned long inlen, - wchar_t *out, unsigned long *outlen); -unsigned long der_utf8_charsize(const wchar_t c); -int der_length_utf8_string(const wchar_t *in, unsigned long noctets, unsigned long *outlen); - - -/* CHOICE */ -int der_decode_choice(const unsigned char *in, unsigned long *inlen, - ltc_asn1_list *list, unsigned long outlen); - -/* UTCTime */ -typedef struct { - unsigned YY, /* year */ - MM, /* month */ - DD, /* day */ - hh, /* hour */ - mm, /* minute */ - ss, /* second */ - off_dir, /* timezone offset direction 0 == +, 1 == - */ - off_hh, /* timezone offset hours */ - off_mm; /* timezone offset minutes */ -} ltc_utctime; - -int der_encode_utctime(ltc_utctime *utctime, - unsigned char *out, unsigned long *outlen); - -int der_decode_utctime(const unsigned char *in, unsigned long *inlen, - ltc_utctime *out); - -int der_length_utctime(ltc_utctime *utctime, unsigned long *outlen); - - -#endif - -/* $Source: /cvs/libtom/libtomcrypt/src/headers/tomcrypt_pk.h,v $ */ -/* $Revision: 1.81 $ */ -/* $Date: 2007/05/12 14:32:35 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_pkcs.h b/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_pkcs.h deleted file mode 100644 index 84fb82a6229..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_pkcs.h +++ /dev/null @@ -1,89 +0,0 @@ -/* LTC_PKCS Header Info */ - -/* ===> LTC_PKCS #1 -- RSA Cryptography <=== */ -#ifdef LTC_PKCS_1 - -enum ltc_pkcs_1_v1_5_blocks -{ - LTC_LTC_PKCS_1_EMSA = 1, /* Block type 1 (LTC_PKCS #1 v1.5 signature padding) */ - LTC_LTC_PKCS_1_EME = 2 /* Block type 2 (LTC_PKCS #1 v1.5 encryption padding) */ -}; - -enum ltc_pkcs_1_paddings -{ - LTC_LTC_PKCS_1_V1_5 = 1, /* LTC_PKCS #1 v1.5 padding (\sa ltc_pkcs_1_v1_5_blocks) */ - LTC_LTC_PKCS_1_OAEP = 2, /* LTC_PKCS #1 v2.0 encryption padding */ - LTC_LTC_PKCS_1_PSS = 3 /* LTC_PKCS #1 v2.1 signature padding */ -}; - -int pkcs_1_mgf1( int hash_idx, - const unsigned char *seed, unsigned long seedlen, - unsigned char *mask, unsigned long masklen); - -int pkcs_1_i2osp(void *n, unsigned long modulus_len, unsigned char *out); -int pkcs_1_os2ip(void *n, unsigned char *in, unsigned long inlen); - -/* *** v1.5 padding */ -int pkcs_1_v1_5_encode(const unsigned char *msg, - unsigned long msglen, - int block_type, - unsigned long modulus_bitlen, - prng_state *prng, - int prng_idx, - unsigned char *out, - unsigned long *outlen); - -int pkcs_1_v1_5_decode(const unsigned char *msg, - unsigned long msglen, - int block_type, - unsigned long modulus_bitlen, - unsigned char *out, - unsigned long *outlen, - int *is_valid); - -/* *** v2.1 padding */ -int pkcs_1_oaep_encode(const unsigned char *msg, unsigned long msglen, - const unsigned char *lparam, unsigned long lparamlen, - unsigned long modulus_bitlen, prng_state *prng, - int prng_idx, int hash_idx, - unsigned char *out, unsigned long *outlen); - -int pkcs_1_oaep_decode(const unsigned char *msg, unsigned long msglen, - const unsigned char *lparam, unsigned long lparamlen, - unsigned long modulus_bitlen, int hash_idx, - unsigned char *out, unsigned long *outlen, - int *res); - -int pkcs_1_pss_encode(const unsigned char *msghash, unsigned long msghashlen, - unsigned long saltlen, prng_state *prng, - int prng_idx, int hash_idx, - unsigned long modulus_bitlen, - unsigned char *out, unsigned long *outlen); - -int pkcs_1_pss_decode(const unsigned char *msghash, unsigned long msghashlen, - const unsigned char *sig, unsigned long siglen, - unsigned long saltlen, int hash_idx, - unsigned long modulus_bitlen, int *res); - -#endif /* LTC_PKCS_1 */ - -/* ===> LTC_PKCS #5 -- Password Based Cryptography <=== */ -#ifdef LTC_PKCS_5 - -/* Algorithm #1 (old) */ -int pkcs_5_alg1(const unsigned char *password, unsigned long password_len, - const unsigned char *salt, - int iteration_count, int hash_idx, - unsigned char *out, unsigned long *outlen); - -/* Algorithm #2 (new) */ -int pkcs_5_alg2(const unsigned char *password, unsigned long password_len, - const unsigned char *salt, unsigned long salt_len, - int iteration_count, int hash_idx, - unsigned char *out, unsigned long *outlen); - -#endif /* LTC_PKCS_5 */ - -/* $Source: /cvs/libtom/libtomcrypt/src/headers/tomcrypt_pkcs.h,v $ */ -/* $Revision: 1.8 $ */ -/* $Date: 2007/05/12 14:32:35 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_prng.h b/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_prng.h deleted file mode 100644 index f3e3e550e88..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/headers/tomcrypt_prng.h +++ /dev/null @@ -1,199 +0,0 @@ -/* ---- PRNG Stuff ---- */ -#ifdef LTC_YARROW -struct yarrow_prng { - int cipher, hash; - unsigned char pool[MAXBLOCKSIZE]; - symmetric_CTR ctr; - LTC_MUTEX_TYPE(prng_lock) -}; -#endif - -#ifdef LTC_RC4 -struct rc4_prng { - int x, y; - unsigned char buf[256]; -}; -#endif - -#ifdef LTC_FORTUNA -struct fortuna_prng { - hash_state pool[LTC_FORTUNA_POOLS]; /* the pools */ - - symmetric_key skey; - - unsigned char K[32], /* the current key */ - IV[16]; /* IV for CTR mode */ - - unsigned long pool_idx, /* current pool we will add to */ - pool0_len, /* length of 0'th pool */ - wd; - - ulong64 reset_cnt; /* number of times we have reset */ - LTC_MUTEX_TYPE(prng_lock) -}; -#endif - -#ifdef LTC_SOBER128 -struct sober128_prng { - ulong32 R[17], /* Working storage for the shift register */ - initR[17], /* saved register contents */ - konst, /* key dependent constant */ - sbuf; /* partial word encryption buffer */ - - int nbuf, /* number of part-word stream bits buffered */ - flag, /* first add_entropy call or not? */ - set; /* did we call add_entropy to set key? */ - -}; -#endif - -typedef union Prng_state { - char dummy[1]; -#ifdef LTC_YARROW - struct yarrow_prng yarrow; -#endif -#ifdef LTC_RC4 - struct rc4_prng rc4; -#endif -#ifdef LTC_FORTUNA - struct fortuna_prng fortuna; -#endif -#ifdef LTC_SOBER128 - struct sober128_prng sober128; -#endif -} prng_state; - -/** PRNG descriptor */ -extern struct ltc_prng_descriptor { - /** Name of the PRNG */ - char *name; - /** size in bytes of exported state */ - int export_size; - /** Start a PRNG state - @param prng [out] The state to initialize - @return CRYPT_OK if successful - */ - int (*start)(prng_state *prng); - /** Add entropy to the PRNG - @param in The entropy - @param inlen Length of the entropy (octets)\ - @param prng The PRNG state - @return CRYPT_OK if successful - */ - int (*add_entropy)(const unsigned char *in, unsigned long inlen, prng_state *prng); - /** Ready a PRNG state to read from - @param prng The PRNG state to ready - @return CRYPT_OK if successful - */ - int (*ready)(prng_state *prng); - /** Read from the PRNG - @param out [out] Where to store the data - @param outlen Length of data desired (octets) - @param prng The PRNG state to read from - @return Number of octets read - */ - unsigned long (*read)(unsigned char *out, unsigned long outlen, prng_state *prng); - /** Terminate a PRNG state - @param prng The PRNG state to terminate - @return CRYPT_OK if successful - */ - int (*done)(prng_state *prng); - /** Export a PRNG state - @param out [out] The destination for the state - @param outlen [in/out] The max size and resulting size of the PRNG state - @param prng The PRNG to export - @return CRYPT_OK if successful - */ - int (*pexport)(unsigned char *out, unsigned long *outlen, prng_state *prng); - /** Import a PRNG state - @param in The data to import - @param inlen The length of the data to import (octets) - @param prng The PRNG to initialize/import - @return CRYPT_OK if successful - */ - int (*pimport)(const unsigned char *in, unsigned long inlen, prng_state *prng); - /** Self-test the PRNG - @return CRYPT_OK if successful, CRYPT_NOP if self-testing has been disabled - */ - int (*test)(void); -} prng_descriptor[]; - -#ifdef LTC_YARROW -int yarrow_start(prng_state *prng); -int yarrow_add_entropy(const unsigned char *in, unsigned long inlen, prng_state *prng); -int yarrow_ready(prng_state *prng); -unsigned long yarrow_read(unsigned char *out, unsigned long outlen, prng_state *prng); -int yarrow_done(prng_state *prng); -int yarrow_export(unsigned char *out, unsigned long *outlen, prng_state *prng); -int yarrow_import(const unsigned char *in, unsigned long inlen, prng_state *prng); -int yarrow_test(void); -extern const struct ltc_prng_descriptor yarrow_desc; -#endif - -#ifdef LTC_FORTUNA -int fortuna_start(prng_state *prng); -int fortuna_add_entropy(const unsigned char *in, unsigned long inlen, prng_state *prng); -int fortuna_ready(prng_state *prng); -unsigned long fortuna_read(unsigned char *out, unsigned long outlen, prng_state *prng); -int fortuna_done(prng_state *prng); -int fortuna_export(unsigned char *out, unsigned long *outlen, prng_state *prng); -int fortuna_import(const unsigned char *in, unsigned long inlen, prng_state *prng); -int fortuna_test(void); -extern const struct ltc_prng_descriptor fortuna_desc; -#endif - -#ifdef LTC_RC4 -int rc4_start(prng_state *prng); -int rc4_add_entropy(const unsigned char *in, unsigned long inlen, prng_state *prng); -int rc4_ready(prng_state *prng); -unsigned long rc4_read(unsigned char *out, unsigned long outlen, prng_state *prng); -int rc4_done(prng_state *prng); -int rc4_export(unsigned char *out, unsigned long *outlen, prng_state *prng); -int rc4_import(const unsigned char *in, unsigned long inlen, prng_state *prng); -int rc4_test(void); -extern const struct ltc_prng_descriptor rc4_desc; -#endif - -#ifdef LTC_SPRNG -int sprng_start(prng_state *prng); -int sprng_add_entropy(const unsigned char *in, unsigned long inlen, prng_state *prng); -int sprng_ready(prng_state *prng); -unsigned long sprng_read(unsigned char *out, unsigned long outlen, prng_state *prng); -int sprng_done(prng_state *prng); -int sprng_export(unsigned char *out, unsigned long *outlen, prng_state *prng); -int sprng_import(const unsigned char *in, unsigned long inlen, prng_state *prng); -int sprng_test(void); -extern const struct ltc_prng_descriptor sprng_desc; -#endif - -#ifdef LTC_SOBER128 -int sober128_start(prng_state *prng); -int sober128_add_entropy(const unsigned char *in, unsigned long inlen, prng_state *prng); -int sober128_ready(prng_state *prng); -unsigned long sober128_read(unsigned char *out, unsigned long outlen, prng_state *prng); -int sober128_done(prng_state *prng); -int sober128_export(unsigned char *out, unsigned long *outlen, prng_state *prng); -int sober128_import(const unsigned char *in, unsigned long inlen, prng_state *prng); -int sober128_test(void); -extern const struct ltc_prng_descriptor sober128_desc; -#endif - -int find_prng(const char *name); -int register_prng(const struct ltc_prng_descriptor *prng); -int unregister_prng(const struct ltc_prng_descriptor *prng); -int prng_is_valid(int idx); -LTC_MUTEX_PROTO(ltc_prng_mutex) - -/* Slow RNG you **might** be able to use to seed a PRNG with. Be careful as this - * might not work on all platforms as planned - */ -unsigned long rng_get_bytes(unsigned char *out, - unsigned long outlen, - void (*callback)(void)); - -int rng_make_prng(int bits, int wprng, prng_state *prng, void (*callback)(void)); - - -/* $Source: /cvs/libtom/libtomcrypt/src/headers/tomcrypt_prng.h,v $ */ -/* $Revision: 1.9 $ */ -/* $Date: 2007/05/12 14:32:35 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/misc/crypt_argchk.c b/dep/CascLib/src/libtomcrypt/src/misc/crypt_argchk.c deleted file mode 100644 index 39c686a5d46..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/misc/crypt_argchk.c +++ /dev/null @@ -1,30 +0,0 @@ -/* LibTomCrypt, modular cryptographic library -- Tom St Denis - * - * LibTomCrypt is a library that provides various cryptographic - * algorithms in a highly modular and flexible manner. - * - * The library is free for all purposes without any express - * guarantee it works. - * - * Tom St Denis, tomstdenis@gmail.com, http://libtom.org - */ -#include "../headers/tomcrypt.h" -#include <signal.h> - -/** - @file crypt_argchk.c - Perform argument checking, Tom St Denis -*/ - -#if (ARGTYPE == 0) -void crypt_argchk(char *v, char *s, int d) -{ - fprintf(stderr, "LTC_ARGCHK '%s' failure on line %d of file %s\n", - v, d, s); - (void)raise(SIGABRT); -} -#endif - -/* $Source: /cvs/libtom/libtomcrypt/src/misc/crypt/crypt_argchk.c,v $ */ -/* $Revision: 1.5 $ */ -/* $Date: 2006/12/28 01:27:24 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/misc/crypt_hash_descriptor.c b/dep/CascLib/src/libtomcrypt/src/misc/crypt_hash_descriptor.c deleted file mode 100644 index 9953551a8b7..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/misc/crypt_hash_descriptor.c +++ /dev/null @@ -1,27 +0,0 @@ -/* LibTomCrypt, modular cryptographic library -- Tom St Denis - * - * LibTomCrypt is a library that provides various cryptographic - * algorithms in a highly modular and flexible manner. - * - * The library is free for all purposes without any express - * guarantee it works. - * - * Tom St Denis, tomstdenis@gmail.com, http://libtom.org - */ -#include "../headers/tomcrypt.h" - -/** - @file crypt_hash_descriptor.c - Stores the hash descriptor table, Tom St Denis -*/ - -struct ltc_hash_descriptor hash_descriptor[TAB_SIZE] = { -{ NULL, 0, 0, 0, { 0 }, 0, NULL, NULL, NULL, NULL, NULL } -}; - -LTC_MUTEX_GLOBAL(ltc_hash_mutex) - - -/* $Source: /cvs/libtom/libtomcrypt/src/misc/crypt/crypt_hash_descriptor.c,v $ */ -/* $Revision: 1.10 $ */ -/* $Date: 2006/12/28 01:27:24 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/misc/crypt_hash_is_valid.c b/dep/CascLib/src/libtomcrypt/src/misc/crypt_hash_is_valid.c deleted file mode 100644 index 20cc30cb0e1..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/misc/crypt_hash_is_valid.c +++ /dev/null @@ -1,36 +0,0 @@ -/* LibTomCrypt, modular cryptographic library -- Tom St Denis - * - * LibTomCrypt is a library that provides various cryptographic - * algorithms in a highly modular and flexible manner. - * - * The library is free for all purposes without any express - * guarantee it works. - * - * Tom St Denis, tomstdenis@gmail.com, http://libtom.org - */ -#include "../headers/tomcrypt.h" - -/** - @file crypt_hash_is_valid.c - Determine if hash is valid, Tom St Denis -*/ - -/* - Test if a hash index is valid - @param idx The index of the hash to search for - @return CRYPT_OK if valid -*/ -int hash_is_valid(int idx) -{ - LTC_MUTEX_LOCK(<c_hash_mutex); - if (idx < 0 || idx >= TAB_SIZE || hash_descriptor[idx].name == NULL) { - LTC_MUTEX_UNLOCK(<c_hash_mutex); - return CRYPT_INVALID_HASH; - } - LTC_MUTEX_UNLOCK(<c_hash_mutex); - return CRYPT_OK; -} - -/* $Source: /cvs/libtom/libtomcrypt/src/misc/crypt/crypt_hash_is_valid.c,v $ */ -/* $Revision: 1.6 $ */ -/* $Date: 2006/12/28 01:27:24 $ */ diff --git a/dep/CascLib/src/libtomcrypt/src/misc/crypt_libc.c b/dep/CascLib/src/libtomcrypt/src/misc/crypt_libc.c deleted file mode 100644 index bcc89f4f94d..00000000000 --- a/dep/CascLib/src/libtomcrypt/src/misc/crypt_libc.c +++ /dev/null @@ -1,43 +0,0 @@ -/*****************************************************************************/ -/* crypt_libc.c Copyright (c) Ladislav Zezula 2010 */ -/*---------------------------------------------------------------------------*/ -/* Description: */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 05.05.10 1.00 Lad The first version of crypt_libc.c */ -/*****************************************************************************/ - -// LibTomCrypt header -#include <stdlib.h> -#include "../headers/tomcrypt.h" - -void * LibTomMalloc(size_t n) -{ - return malloc(n); -} - -void * LibTomCalloc(size_t n, size_t s) -{ - return calloc(n, s); -} - -void * LibTomRealloc(void *p, size_t n) -{ - return realloc(p, n); -} - -void LibTomFree(void * p) -{ - free(p); -} - -clock_t LibTomClock(void) -{ - return clock(); -} - -void LibTomQsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *)) -{ - qsort(base, nmemb, size, compar); -} diff --git a/dep/CascLib/src/md5/md5.cpp b/dep/CascLib/src/md5/md5.cpp new file mode 100644 index 00000000000..b6cc49db42b --- /dev/null +++ b/dep/CascLib/src/md5/md5.cpp @@ -0,0 +1,291 @@ +/* + * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. + * MD5 Message-Digest Algorithm (RFC 1321). + * + * Homepage: + * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 + * + * Author: + * Alexander Peslyak, better known as Solar Designer <solar at openwall.com> + * + * This software was written by Alexander Peslyak in 2001. No copyright is + * claimed, and the software is hereby placed in the public domain. + * In case this attempt to disclaim copyright and place the software in the + * public domain is deemed null and void, then the software is + * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * (This is a heavily cut-down "BSD license".) + * + * This differs from Colin Plumb's older public domain implementation in that + * no exactly 32-bit integer data type is required (any 32-bit or wider + * unsigned integer data type will do), there's no compile-time endianness + * configuration, and the function prototypes match OpenSSL's. No code from + * Colin Plumb's implementation has been reused; this comment merely compares + * the properties of the two independent implementations. + * + * The primary goals of this implementation are portability and ease of use. + * It is meant to be fast, but not as fast as possible. Some known + * optimizations are not included to reduce source code size and avoid + * compile-time configuration. + */ + +#ifndef HAVE_OPENSSL + +#include <string.h> + +#include "md5.h" + +/* + * The basic MD5 functions. + * + * F and G are optimized compared to their RFC 1321 definitions for + * architectures that lack an AND-NOT instruction, just like in Colin Plumb's + * implementation. + */ +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) +#define H(x, y, z) (((x) ^ (y)) ^ (z)) +#define H2(x, y, z) ((x) ^ ((y) ^ (z))) +#define I(x, y, z) ((y) ^ ((x) | ~(z))) + +/* + * The MD5 transformation for all four rounds. + */ +#define STEP(f, a, b, c, d, x, t, s) \ + (a) += f((b), (c), (d)) + (x) + (t); \ + (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ + (a) += (b); + +/* + * SET reads 4 input bytes in little-endian byte order and stores them in a + * properly aligned word in host byte order. + * + * The check for little-endian architectures that tolerate unaligned memory + * accesses is just an optimization. Nothing will break if it fails to detect + * a suitable architecture. + * + * Unfortunately, this optimization may be a C strict aliasing rules violation + * if the caller's data buffer has effective type that cannot be aliased by + * MD5_u32plus. In practice, this problem may occur if these MD5 routines are + * inlined into a calling function, or with future and dangerously advanced + * link-time optimizations. For the time being, keeping these MD5 routines in + * their own translation unit avoids the problem. + */ +#if defined(__i386__) || defined(__x86_64__) || defined(__vax__) +#define SET(n) \ + (*(MD5_u32plus *)&ptr[(n) * 4]) +#define GET(n) \ + SET(n) +#else +#define SET(n) \ + (ctx->block[(n)] = \ + (MD5_u32plus)ptr[(n) * 4] | \ + ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ + ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ + ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) +#define GET(n) \ + (ctx->block[(n)]) +#endif + +/* + * This processes one or more 64-byte data blocks, but does NOT update the bit + * counters. There are no alignment requirements. + */ +static const void *body(MD5_CTX *ctx, const void *data, unsigned long size) +{ + const unsigned char *ptr; + MD5_u32plus a, b, c, d; + MD5_u32plus saved_a, saved_b, saved_c, saved_d; + + ptr = (const unsigned char *)data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + + do { + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + +/* Round 1 */ + STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) + STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) + STEP(F, c, d, a, b, SET(2), 0x242070db, 17) + STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) + STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) + STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) + STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) + STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) + STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) + STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) + STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) + STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) + STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) + STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) + STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) + STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) + +/* Round 2 */ + STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) + STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) + STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) + STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) + STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) + STEP(G, d, a, b, c, GET(10), 0x02441453, 9) + STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) + STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) + STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) + STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) + STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) + STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) + STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) + STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) + STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) + STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) + +/* Round 3 */ + STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) + STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11) + STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) + STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23) + STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) + STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11) + STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) + STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23) + STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) + STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11) + STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) + STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23) + STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) + STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11) + STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) + STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23) + +/* Round 4 */ + STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) + STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) + STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) + STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) + STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) + STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) + STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) + STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) + STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) + STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) + STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) + STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) + STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) + STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) + STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) + STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + + ptr += 64; + } while (size -= 64); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + + return ptr; +} + +void MD5_Init(MD5_CTX *ctx) +{ + ctx->a = 0x67452301; + ctx->b = 0xefcdab89; + ctx->c = 0x98badcfe; + ctx->d = 0x10325476; + + ctx->lo = 0; + ctx->hi = 0; +} + +void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size) +{ + MD5_u32plus saved_lo; + unsigned long used, available; + + saved_lo = ctx->lo; + if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) + ctx->hi++; + ctx->hi += size >> 29; + + used = saved_lo & 0x3f; + + if (used) { + available = 64 - used; + + if (size < available) { + memcpy(&ctx->buffer[used], data, size); + return; + } + + memcpy(&ctx->buffer[used], data, available); + data = (const unsigned char *)data + available; + size -= available; + body(ctx, ctx->buffer, 64); + } + + if (size >= 64) { + data = body(ctx, data, size & ~(unsigned long)0x3f); + size &= 0x3f; + } + + memcpy(ctx->buffer, data, size); +} + +#define OUT(dst, src) \ + (dst)[0] = (unsigned char)(src); \ + (dst)[1] = (unsigned char)((src) >> 8); \ + (dst)[2] = (unsigned char)((src) >> 16); \ + (dst)[3] = (unsigned char)((src) >> 24); + +void MD5_Final(unsigned char *result, MD5_CTX *ctx) +{ + unsigned long used, available; + + used = ctx->lo & 0x3f; + + ctx->buffer[used++] = 0x80; + + available = 64 - used; + + if (available < 8) { + memset(&ctx->buffer[used], 0, available); + body(ctx, ctx->buffer, 64); + used = 0; + available = 64; + } + + memset(&ctx->buffer[used], 0, available - 8); + + ctx->lo <<= 3; + OUT(&ctx->buffer[56], ctx->lo) + OUT(&ctx->buffer[60], ctx->hi) + + body(ctx, ctx->buffer, 64); + + OUT(&result[0], ctx->a) + OUT(&result[4], ctx->b) + OUT(&result[8], ctx->c) + OUT(&result[12], ctx->d) + + memset(ctx, 0, sizeof(*ctx)); +} + +#endif diff --git a/dep/CascLib/src/md5/md5.h b/dep/CascLib/src/md5/md5.h new file mode 100644 index 00000000000..4216c0204c8 --- /dev/null +++ b/dep/CascLib/src/md5/md5.h @@ -0,0 +1,45 @@ +/* + * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. + * MD5 Message-Digest Algorithm (RFC 1321). + * + * Homepage: + * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 + * + * Author: + * Alexander Peslyak, better known as Solar Designer <solar at openwall.com> + * + * This software was written by Alexander Peslyak in 2001. No copyright is + * claimed, and the software is hereby placed in the public domain. + * In case this attempt to disclaim copyright and place the software in the + * public domain is deemed null and void, then the software is + * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See md5.c for more information. + */ + +#ifdef HAVE_OPENSSL +#include <openssl/md5.h> +#elif !defined(_MD5_H) +#define _MD5_H + +/* Any 32-bit or wider unsigned integer data type will do */ +typedef unsigned int MD5_u32plus; + +typedef struct { + MD5_u32plus lo, hi; + MD5_u32plus a, b, c, d; + unsigned char buffer[64]; + MD5_u32plus block[16]; +} MD5_CTX; + +void MD5_Init(MD5_CTX *ctx); +void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size); +void MD5_Final(unsigned char *result, MD5_CTX *ctx); + +#endif diff --git a/dep/PackageList.txt b/dep/PackageList.txt index e928f137448..e539385e789 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: c63818ecf8d998fae811a2d6db23045f4f6e36a1 + Version: a1197edf0b3bd4d52c3f39be7fa7b44bb0b98012 rapidjson (A fast JSON parser/generator for C++ with both SAX/DOM style API http://rapidjson.org/) https://github.com/miloyip/rapidjson diff --git a/src/tools/extractor_common/CascHandles.cpp b/src/tools/extractor_common/CascHandles.cpp index 44805fdccd1..9487b3de093 100644 --- a/src/tools/extractor_common/CascHandles.cpp +++ b/src/tools/extractor_common/CascHandles.cpp @@ -72,32 +72,30 @@ CASC::StorageHandle CASC::OpenStorage(boost::filesystem::path const& path, DWORD namespace CASC { - static DWORD GetStorageInfo(StorageHandle const& storage, CASC_STORAGE_INFO_CLASS storageInfoClass) + template<typename T> + static bool GetStorageInfo(StorageHandle const& storage, CASC_STORAGE_INFO_CLASS storageInfoClass, T* value) { - DWORD value = 0; size_t infoDataSizeNeeded = 0; - if (!::CascGetStorageInfo(storage.get(), storageInfoClass, &value, sizeof(value), &infoDataSizeNeeded)) - { - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) - { - std::unique_ptr<char> buf(new char[infoDataSizeNeeded]); - ::CascGetStorageInfo(storage.get(), storageInfoClass, buf.get(), infoDataSizeNeeded, &infoDataSizeNeeded); - return *reinterpret_cast<DWORD*>(buf.get()); - } - } - - return value; + return ::CascGetStorageInfo(storage.get(), storageInfoClass, value, sizeof(T), &infoDataSizeNeeded); } } DWORD CASC::GetBuildNumber(StorageHandle const& storage) { - return GetStorageInfo(storage, CascStorageGameBuild); + CASC_STORAGE_PRODUCT product; + if (GetStorageInfo(storage, CascStorageProduct, &product)) + return product.dwBuildNumber; + + return 0; } DWORD CASC::GetInstalledLocalesMask(StorageHandle const& storage) { - return GetStorageInfo(storage, CascStorageInstalledLocales); + DWORD locales; + if (GetStorageInfo(storage, CascStorageInstalledLocales, &locales)) + return locales; + + return 0; } CASC::FileHandle CASC::OpenFile(StorageHandle const& storage, char const* fileName, DWORD localeMask, bool printErrors /*= false*/) |