diff options
author | Nayd <dnpd.dd@gmail.com> | 2015-01-11 22:12:49 +0000 |
---|---|---|
committer | Nayd <dnpd.dd@gmail.com> | 2015-01-11 23:02:19 +0000 |
commit | 31e3dc2e75459f681480ae09641f1087096dfd69 (patch) | |
tree | 70685fc84efd01d450f8bc06a4cb266e6478b694 /dep | |
parent | 8bbd9133d57fc34b77544cbdd59526ed9ccaa607 (diff) |
Dep/CascLib: Update to https://github.com/ladislav-zezula/CascLib/commit/5d3789af3435534c288c2145e158d422651c7fe1
Closes #13866
Diffstat (limited to 'dep')
-rw-r--r-- | dep/CascLib/doc/history.txt | 8 | ||||
-rw-r--r-- | dep/CascLib/src/CascBuildCfg.cpp | 6 | ||||
-rw-r--r-- | dep/CascLib/src/CascCommon.h | 141 | ||||
-rw-r--r-- | dep/CascLib/src/CascDumpData.cpp | 361 | ||||
-rw-r--r-- | dep/CascLib/src/CascFindFile.cpp | 66 | ||||
-rw-r--r-- | dep/CascLib/src/CascLib.h | 24 | ||||
-rw-r--r-- | dep/CascLib/src/CascMndxRoot.cpp | 53 | ||||
-rw-r--r-- | dep/CascLib/src/CascMndxRoot.h | 2 | ||||
-rw-r--r-- | dep/CascLib/src/CascOpenFile.cpp | 191 | ||||
-rw-r--r-- | dep/CascLib/src/CascOpenStorage.cpp | 605 | ||||
-rw-r--r-- | dep/CascLib/src/CascReadFile.cpp | 224 | ||||
-rw-r--r-- | dep/CascLib/src/common/Common.cpp | 23 | ||||
-rw-r--r-- | dep/CascLib/src/common/Common.h | 9 | ||||
-rw-r--r-- | dep/CascLib/src/common/FileStream.cpp | 8 | ||||
-rw-r--r-- | dep/CascLib/src/common/ListFile.cpp | 163 | ||||
-rw-r--r-- | dep/CascLib/src/common/ListFile.h | 32 | ||||
-rw-r--r-- | dep/CascLib/src/common/Map.h | 2 | ||||
-rw-r--r-- | dep/PackageList.txt | 2 |
18 files changed, 1131 insertions, 789 deletions
diff --git a/dep/CascLib/doc/history.txt b/dep/CascLib/doc/history.txt index 45d8354379f..a1fc51d6677 100644 --- a/dep/CascLib/doc/history.txt +++ b/dep/CascLib/doc/history.txt @@ -1,10 +1,6 @@ -StormLib history - -================ - +CascLib history +=============== Version 1.00 - - - Created diff --git a/dep/CascLib/src/CascBuildCfg.cpp b/dep/CascLib/src/CascBuildCfg.cpp index effb74552c5..b1d6584771c 100644 --- a/dep/CascLib/src/CascBuildCfg.cpp +++ b/dep/CascLib/src/CascBuildCfg.cpp @@ -99,7 +99,7 @@ static TCHAR * CheckForIndexDirectory(TCascStorage * hs, const TCHAR * szSubDir) return hs->szIndexPath; } - delete [] szIndexPath; + CASC_FREE(szIndexPath); return NULL; } @@ -270,7 +270,7 @@ static int LoadInfoVariable(PQUERY_KEY pVarBlob, const char * szLineBegin, const // Initialize the blob pVarBlob->pbData = CASC_ALLOC(BYTE, (szLinePtr - szLineBegin) + 1); - pVarBlob->cbData = (size_t)(szLinePtr - szLineBegin); + pVarBlob->cbData = (DWORD)(szLinePtr - szLineBegin); // Check for success if(pVarBlob->pbData == NULL) @@ -826,7 +826,7 @@ static int LoadCdnBuildFile(TCascStorage * hs, PQUERY_KEY pFileBlob) //----------------------------------------------------------------------------- // Public functions -int LoadBuildConfiguration(TCascStorage * hs) +int LoadBuildInfo(TCascStorage * hs) { QUERY_KEY InfoFile = {NULL, 0}; QUERY_KEY FileData = {NULL, 0}; diff --git a/dep/CascLib/src/CascCommon.h b/dep/CascLib/src/CascCommon.h index b2f6a156e39..51b6689ec16 100644 --- a/dep/CascLib/src/CascCommon.h +++ b/dep/CascLib/src/CascCommon.h @@ -23,9 +23,9 @@ #include "CascPort.h" #include "common/Common.h" +#include "common/Map.h" #include "common/FileStream.h" #include "common/ListFile.h" -#include "common/Map.h" // Headers from LibTomCrypt #include "libtomcrypt/src/headers/tomcrypt.h" @@ -48,6 +48,20 @@ #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.xxx: +// 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) @@ -57,16 +71,46 @@ #define CASC_PACKAGE_BUFFER 0x1000 //----------------------------------------------------------------------------- -// Structures +// On-disk structures + +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; + +typedef struct _FILE_ROOT_ENTRY +{ + DWORD EncodingKey[4]; // MD5 of the file + ULONGLONG FileNameHash; // Jenkins hash of the file name + +} FILE_ROOT_ENTRY, *PFILE_ROOT_ENTRY; + +typedef struct _ROOT_BLOCK_INFO +{ + PFILE_LOCALE_BLOCK pLocaleBlockHdr; // Pointer to the locale block + PDWORD pInt32Array; // Pointer to the array of 32-bit integers + PFILE_ROOT_ENTRY pRootEntries; + +} ROOT_BLOCK_INFO, *PROOT_BLOCK_INFO; + +//----------------------------------------------------------------------------- +// In-memory structures +class TMndxFindResult; struct TFileStream; struct _MAR_FILE; typedef struct _CASC_INDEX_ENTRY { BYTE IndexKey[CASC_FILE_KEY_SIZE]; // The first 9 bytes of the encoding key - BYTE FileOffset[5]; // - BYTE FileSize[4]; + 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 @@ -113,7 +157,7 @@ typedef struct _CASC_ENCODING_HEADER typedef struct _CASC_ENCODING_ENTRY { USHORT KeyCount; // Number of subitems - BYTE FileSizeBytes[4]; // File size as bytes (big-endian) + BYTE FileSizeBE[4]; // Compressed file size (header area + frame headers + compressed frames), in bytes BYTE EncodingKey[MD5_HASH_SIZE]; // File encoding key // Followed by the index keys @@ -132,29 +176,35 @@ typedef struct _CASC_ROOT_LOCALE_BLOCK } CASC_ROOT_LOCALE_BLOCK, *PCASC_ROOT_LOCALE_BLOCK; +// 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 +{ + 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 + +} CASC_ROOT_ENTRY_MNDX, *PCASC_ROOT_ENTRY_MNDX; + +// 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_ROOT_ENTRY { - BYTE EncodingKey[MD5_HASH_SIZE]; // File encoding key (MD5) ULONGLONG FileNameHash; // Jenkins hash of the file name - DWORD Locales; // File locales (see CASC_LOCALE_XXX) - DWORD Flags; // File flags + DWORD SumValue; // Sum value + DWORD Locales; // Locale flags of the file + DWORD EncodingKey[4]; // File encoding key (MD5) } CASC_ROOT_ENTRY, *PCASC_ROOT_ENTRY; -typedef struct _CASC_ROOT_KEY_INFO +// Definition of the hash table for CASC root items +typedef struct _CASC_ROOT_HASH_TABLE { - BYTE EncodingKey[MD5_HASH_SIZE]; // Encoding key for the file, obtained from root info - ULONGLONG FileSize; // Size of the file, in bytes - BYTE Flags; // File flags -} CASC_ROOT_KEY_INFO, *PCASC_ROOT_KEY_INFO; + PCASC_ROOT_ENTRY TablePtr; // Pointer to the CASC root table + DWORD TableSize; // Total size of the root table + DWORD ItemCount; // Number of items currently in the table -typedef struct _CASC_MNDX_ENTRY -{ - DWORD Flags; // High 8 bits: Flags, low 24 bits: package index - BYTE EncodingKey[MD5_HASH_SIZE]; // Encoding key for the file - DWORD FileSize; // Size of the file, in bytes - -} CASC_MNDX_ENTRY, *PCASC_MNDX_ENTRY; +} CASC_ROOT_HASH_TABLE, *PCASC_ROOT_HASH_TABLE; typedef struct _CASC_MNDX_INFO { @@ -174,8 +224,8 @@ typedef struct _CASC_MNDX_INFO 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_MNDX_ENTRY pMndxEntries; - PCASC_MNDX_ENTRY * ppValidEntries; + PCASC_ROOT_ENTRY_MNDX pMndxEntries; + PCASC_ROOT_ENTRY_MNDX * ppValidEntries; } CASC_MNDX_INFO, *PCASC_MNDX_INFO; @@ -208,6 +258,7 @@ typedef struct _TCascStorage 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 QUERY_KEY CdnConfigKey; QUERY_KEY CdnBuildKey; @@ -239,9 +290,7 @@ typedef struct _TCascStorage PCASC_ENCODING_ENTRY * ppEncodingEntries; // Map of encoding entries size_t nEncodingEntries; - PCASC_ROOT_ENTRY * ppRootEntries; // Sorted array of root entries - PCASC_ROOT_ENTRY pRootEntries; // Linear array of root entries - size_t nRootEntries; // Number of root entries + CASC_ROOT_HASH_TABLE RootTable; // Hash table for the root entries PCASC_MNDX_INFO pMndxInfo; // Used for storages which have MNDX/MAR file PCASC_PACKAGES pPackages; // Linear list of present packages @@ -258,9 +307,11 @@ typedef struct _TCascFile 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_FILE_FRAME pFrames; // Array of file frames DWORD FrameCount; // Number of the file frames @@ -270,9 +321,15 @@ typedef struct _TCascFile DWORD CacheStart; // Starting offset in the cache DWORD CacheEnd; // Ending offset in the cache -} TCascFile; +#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 -class TMndxFindResult; +} TCascFile; typedef struct _TCascSearch { @@ -284,8 +341,7 @@ typedef struct _TCascSearch char * szMask; char szFileName[MAX_PATH]; // Buffer for the file name char szNormName[MAX_PATH]; // Buffer for normalized file name - ULONGLONG FileNameHash; // Name hash being searched right now - size_t RootIndex; // Root index of the previously found item + DWORD RootIndex; // Root index of the previously found item DWORD dwState; // Pointer to the state (0 = listfile, 1 = nameless, 2 = done) BYTE BitArray[1]; // Bit array of already-reported files @@ -305,17 +361,10 @@ typedef struct _TCascSearch // #if defined(_MSC_VER) && defined(_DEBUG) -/* -void * DbgRealloc(void * ptr, size_t nSize); -#define CASC_REALLOC(type, ptr, count) (type *)DbgRealloc(ptr, ((count) * sizeof(type))) +#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) -*/ - -#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) #else @@ -336,7 +385,7 @@ ULONGLONG ConvertBytesToInteger_5(LPBYTE ValueAsBytes); //----------------------------------------------------------------------------- // Build configuration reading -int LoadBuildConfiguration(TCascStorage * hs); +int LoadBuildInfo(TCascStorage * hs); //----------------------------------------------------------------------------- // Internal file functions @@ -344,31 +393,23 @@ int LoadBuildConfiguration(TCascStorage * hs); TCascStorage * IsValidStorageHandle(HANDLE hStorage); TCascFile * IsValidFileHandle(HANDLE hFile); -PCASC_ROOT_ENTRY FindFirstRootEntry(TCascStorage * hs, const char * szFileName, size_t * PtrIndex); +PCASC_ROOT_ENTRY FindRootEntry(TCascStorage * hs, const char * szFileName, DWORD * PtrTableIndex); PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKey, size_t * PtrIndex); PCASC_INDEX_ENTRY FindIndexEntry(TCascStorage * hs, PQUERY_KEY pIndexKey); int CascDecompress(void * pvOutBuffer, PDWORD pcbOutBuffer, void * pvInBuffer, DWORD cbInBuffer); //----------------------------------------------------------------------------- -// Dump data +// Dumping CASC data structures #ifdef _DEBUG void CascDumpSparseArray(const char * szFileName, void * pvSparseArray); void CascDumpNameFragTable(const char * szFileName, void * pvMarFile); void CascDumpFileNames(const char * szFileName, void * pvMarFile); -void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo); void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs); -void CascDumpStorage(const char * szFileName, TCascStorage * hs, const TCHAR * szListFile); +void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo); +void CascDumpRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, const char * szFormat, const TCHAR * szListFile, int nDumpLevel); void CascDumpFile(const char * szFileName, HANDLE hFile); -#else // _DEBUG -#define CascDumpSparseArray(n,a) /* */ -#define CascDumpNameFragTable(n, m) /* */ -#define CascDumpFileNames(n, m) /* */ -#define CascDumpMndxRoot(n,i) /* */ -#define CascDumpIndexEntries(n,h) /* */ -#define CascDumpStorage(n,h) /* */ -#define CascDumpFile(n,h) /* */ #endif // _DEBUG #endif // __CASCCOMMON_H__ diff --git a/dep/CascLib/src/CascDumpData.cpp b/dep/CascLib/src/CascDumpData.cpp index d3871709fce..5dc9110b6cd 100644 --- a/dep/CascLib/src/CascDumpData.cpp +++ b/dep/CascLib/src/CascDumpData.cpp @@ -16,24 +16,19 @@ #ifdef _DEBUG // The entire file is only valid for debug purposes //----------------------------------------------------------------------------- -// Local functions +// Forward definitions -static char * StringFromIndexKey(LPBYTE md5, char * szBuffer) -{ - return StringFromBinary(md5, 9, szBuffer); -} +LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd); -static char * StringFromMD5(LPBYTE md5, char * szBuffer) -{ - return StringFromBinary(md5, MD5_HASH_SIZE, szBuffer); -} +//----------------------------------------------------------------------------- +// Sort compare functions static int CompareIndexEntries_FilePos(const void *, const void * pvIndexEntry1, const void * pvIndexEntry2) { PCASC_INDEX_ENTRY pIndexEntry1 = (PCASC_INDEX_ENTRY)pvIndexEntry1; PCASC_INDEX_ENTRY pIndexEntry2 = (PCASC_INDEX_ENTRY)pvIndexEntry2; - ULONGLONG FileOffset1 = ConvertBytesToInteger_5(pIndexEntry1->FileOffset); - ULONGLONG FileOffset2 = ConvertBytesToInteger_5(pIndexEntry2->FileOffset); + ULONGLONG FileOffset1 = ConvertBytesToInteger_5(pIndexEntry1->FileOffsetBE); + ULONGLONG FileOffset2 = ConvertBytesToInteger_5(pIndexEntry2->FileOffsetBE); DWORD ArchIndex1 = (DWORD)(FileOffset1 >> 0x1E); DWORD ArchIndex2 = (DWORD)(FileOffset2 >> 0x1E); @@ -54,98 +49,121 @@ static int CompareIndexEntries_FilePos(const void *, const void * pvIndexEntry1, return 0; } +//----------------------------------------------------------------------------- +// Local functions -static char ** CreateFileNameArray(TCascStorage * hs, const TCHAR * szListFile) +static char * StringFromMD5(LPBYTE md5, char * szBuffer) { - PCASC_ROOT_ENTRY pRootEntry; - char ** FileNameArray = NULL; - void * pvListFile; - size_t nRootIndex; - char szFileName1[MAX_PATH+1]; - char szFileName2[MAX_PATH+1]; - - // Open the listfile stream and initialize the listfile cache - pvListFile = ListFile_OpenExternal(szListFile); - if(pvListFile != NULL) + return StringFromBinary(md5, MD5_HASH_SIZE, szBuffer); +} + +static char * FormatFileName(const char * szFormat, TCascStorage * hs) +{ + char * szFileName; + char * szSrc; + char * szTrg; + + // Create copy of the file name + szFileName = szSrc = szTrg = NewStr(szFormat, 0); + if(szFileName != NULL) { - // Allocate the array of file names - FileNameArray = CASC_ALLOC(char*, hs->nRootEntries); - if(FileNameArray != NULL) + // Format the file name + while(szSrc[0] != 0) { - // Zero the name array - memset(FileNameArray, 0, hs->nRootEntries * sizeof(char *)); - - // Perform search - while(ListFile_GetNext(pvListFile, "*", szFileName1, MAX_PATH)) + if(szSrc[0] == '%') { - // Create normalized name - strcpy(szFileName2, szFileName1); - NormalizeFileName_UpperBkSlash(szFileName2); - - // Try to find the root entry - pRootEntry = FindFirstRootEntry(hs, szFileName2, &nRootIndex); - if(pRootEntry != NULL) + // Replace "%build%" with a build number + if(!strncmp(szSrc, "%build%", 7)) { - assert(nRootIndex < hs->nRootEntries); - if(FileNameArray[nRootIndex] == NULL) - FileNameArray[nRootIndex] = NewStr(szFileName1, 0); + szTrg += sprintf(szTrg, "%u", hs->dwBuildNumber); + szSrc += 7; + continue; } } + + // Just copy the character + *szTrg++ = *szSrc++; } - // Close the listfile cache - ListFile_Free(pvListFile); + // Terminate the target file name + szTrg[0] = 0; } - return FileNameArray; + return szFileName; } -static void FreeFileNameArray(TCascStorage * hs, char ** FileNameArray) +FILE * CreateDumpFile(const char * szFormat, TCascStorage * hs) { - if(FileNameArray != NULL) + FILE * fp = NULL; + char * szFileName; + + // Validate the storage handle + if(hs != NULL) { - // Free all sub-entries - for(size_t i = 0; i < hs->nRootEntries; i++) + // Format the real file name + szFileName = FormatFileName(szFormat, hs); + if(szFileName != NULL) { - if(FileNameArray[i] != NULL) - CASC_FREE(FileNameArray[i]); + // Create the dump file + fp = fopen(szFileName, "wt"); + CASC_FREE(szFileName); } - - // Free the array itself - CASC_FREE(FileNameArray); } + + return fp; } static void DumpIndexKey( FILE * fp, TCascStorage * hs, - LPBYTE pbEncodingKey, - PCASC_INDEX_ENTRY * ppIndexEntries) + LPBYTE pbIndexKey, + int nDumpLevel) { PCASC_INDEX_ENTRY pIndexEntry; + TCascFile * hf; QUERY_KEY QueryKey; - size_t EntryIndex = 0; - char szMd5[MD5_STRING_SIZE]; + HANDLE hFile; + BYTE HeaderArea[MAX_HEADER_AREA_SIZE]; + char szBuffer[0x20]; - QueryKey.pbData = pbEncodingKey; + QueryKey.pbData = pbIndexKey; QueryKey.cbData = MD5_HASH_SIZE; pIndexEntry = FindIndexEntry(hs, &QueryKey); if(pIndexEntry != NULL) { - ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffset); + ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE); DWORD ArchIndex = (DWORD)(FileOffset >> 0x1E); - DWORD FileSize = *(PDWORD)pIndexEntry->FileSize; - - // Mark the index entry as dumped - ppIndexEntries[EntryIndex] = NULL; + DWORD FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE); // Mask the file offset FileOffset &= 0x3FFFFFFF; - fprintf(fp, " Index: %s, ArchIdx: %02x FileOffset %08x FileSize: %lx\n", - StringFromIndexKey(pIndexEntry->IndexKey, szMd5), + fprintf(fp, " 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 + fprintf(fp, " FileSize: %X Rest: %s\n", + ConvertBytesToInteger_4_LE(&HeaderArea[0x10]), + StringFromBinary(&HeaderArea[0x14], 10, szBuffer)); + } + } } else { @@ -157,122 +175,31 @@ static void DumpEncodingEntry( FILE * fp, TCascStorage * hs, PCASC_ENCODING_ENTRY pEncodingEntry, - PCASC_INDEX_ENTRY * ppIndexEntries) + int nDumpLevel) { - LPBYTE pbEncodingKey; + LPBYTE pbIndexKey; char szMd5[MD5_STRING_SIZE]; - fprintf(fp, " Encoding Key: %s Key Count: %u Size: %lx\n", - StringFromMD5(pEncodingEntry->EncodingKey, szMd5), - pEncodingEntry->KeyCount, - ConvertBytesToInteger_4(pEncodingEntry->FileSizeBytes)); - - // If there is a file key - if(pEncodingEntry->KeyCount != 0) - { - // Get the first encoding key - pbEncodingKey = pEncodingEntry->EncodingKey + MD5_HASH_SIZE; + // If the encoding key exists + if(pEncodingEntry != NULL) + { + fprintf(fp, " Size %lx Key Count: %u\n", + ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE), + pEncodingEntry->KeyCount); - // Dump all encoding keys + // Dump all index keys + pbIndexKey = pEncodingEntry->EncodingKey + MD5_HASH_SIZE; for(DWORD j = 0; j < pEncodingEntry->KeyCount; j++) { - fprintf(fp, " Index Key: %s\n", StringFromMD5(pbEncodingKey, szMd5)); - DumpIndexKey(fp, hs, pbEncodingKey, ppIndexEntries); - pbEncodingKey += MD5_HASH_SIZE; + fprintf(fp, " %s\n", StringFromMD5(pbIndexKey, szMd5)); + DumpIndexKey(fp, hs, pbIndexKey, nDumpLevel); + pbIndexKey += MD5_HASH_SIZE; } - } - else - { - fprintf(fp, " ZERO FILE KEYS\n"); - return; - } -} -static void DumpEncodingEntry( - FILE * fp, - TCascStorage * hs, - PCASC_ROOT_ENTRY pRootEntry, - PCASC_ENCODING_ENTRY * ppEncodingKeys, - PCASC_INDEX_ENTRY * ppIndexEntries) -{ - PCASC_ENCODING_ENTRY pEncodingKey; - QUERY_KEY QueryKey; - size_t EntryIndex = 0; - - // Find the encoding key - QueryKey.pbData = pRootEntry->EncodingKey; - QueryKey.cbData = MD5_HASH_SIZE; - pEncodingKey = FindEncodingEntry(hs, &QueryKey, &EntryIndex); - if(pEncodingKey == NULL) - { - fprintf(fp, " NO ENCODING KEY\n"); - return; } - - // Get the file key, clear the encoding key - ppEncodingKeys[EntryIndex] = NULL; - DumpEncodingEntry(fp, hs, pEncodingKey, ppIndexEntries); -} - -static void DumpRootEntries(FILE * fp, TCascStorage * hs, char ** FileNameArray) -{ - PCASC_ENCODING_ENTRY * ppEncodingEntries; // Array of encoding entries - PCASC_INDEX_ENTRY * ppIndexEntries; // Complete list of key entries for all files - const char * szFileName = NULL; - ULONGLONG PrevNameHash = (ULONGLONG)-1; - char szMd5[MD5_STRING_SIZE]; - - // Create copy of the encoding keys and file keys - ppEncodingEntries = CASC_ALLOC(PCASC_ENCODING_ENTRY, hs->nEncodingEntries); - ppIndexEntries = CASC_ALLOC(PCASC_INDEX_ENTRY, hs->pIndexEntryMap->ItemCount); - if(ppEncodingEntries && ppIndexEntries) + else { - // Copy all pointers - memcpy(ppEncodingEntries, hs->ppEncodingEntries, hs->nEncodingEntries * sizeof(PCASC_ENCODING_ENTRY)); - Map_EnumObjects(hs->pIndexEntryMap, (void **)ppIndexEntries); - - // Parse all entries - for(size_t i = 0; i < hs->nRootEntries; i++) - { - PCASC_ROOT_ENTRY pRootEntry = hs->ppRootEntries[i]; - const char * szDuplicate = ""; - - // Check duplicates - if(pRootEntry->FileNameHash != PrevNameHash) - szFileName = FileNameArray[i]; - else - szDuplicate = "(DUPLICATE) "; - - // Dump the root entry - fprintf(fp, "NameHash: %016llx Locales: %08lx MD5: %s %sFileName: %s\n", - pRootEntry->FileNameHash, - pRootEntry->Locales, - StringFromMD5(pRootEntry->EncodingKey, szMd5), - szDuplicate, - szFileName); - - DumpEncodingEntry(fp, hs, pRootEntry, ppEncodingEntries, ppIndexEntries); - PrevNameHash = pRootEntry->FileNameHash; - fprintf(fp, "\n"); - } - - // Dump all orphaned encoding keys - for(size_t i = 0; i < hs->nEncodingEntries; i++) - { - if(ppEncodingEntries[i] != NULL) - { - fprintf(fp, "[NO ROOT KEY]\n"); - - DumpEncodingEntry(fp, hs, ppEncodingEntries[i], ppIndexEntries); - ppEncodingEntries[i] = NULL; - - fprintf(fp, "\n"); - } - } - - - CASC_FREE(ppIndexEntries); - CASC_FREE(ppEncodingEntries); + fprintf(fp, " NO ENCODING KEYS\n"); } } @@ -398,7 +325,7 @@ void CascDumpFileNames(const char * szFileName, void * pvMarFile) void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo) { - PCASC_MNDX_ENTRY pMndxEntry; + PCASC_ROOT_ENTRY_MNDX pRootEntry; FILE * fp; char szMd5[MD5_STRING_SIZE]; @@ -409,12 +336,12 @@ void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo) fprintf(fp, "Indx Fl+Asset EncodingKey FileSize\n==== ======== ================================ ========\n"); for(DWORD i = 0; i < pMndxInfo->MndxEntriesValid; i++) { - pMndxEntry = pMndxInfo->ppValidEntries[i]; + pRootEntry = pMndxInfo->ppValidEntries[i]; fprintf(fp, "%04X %08X %s %08X\n", i, - pMndxEntry->Flags, - StringFromMD5(pMndxEntry->EncodingKey, szMd5), - pMndxEntry->FileSize); + pRootEntry->Flags, + StringFromMD5(pRootEntry->EncodingKey, szMd5), + pRootEntry->FileSize); } fclose(fp); } @@ -446,11 +373,11 @@ void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs) for(size_t i = 0; i < nIndexEntries; i++) { PCASC_INDEX_ENTRY pIndexEntry = ppIndexEntries[i]; - ULONGLONG ArchOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffset); + ULONGLONG ArchOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE); DWORD ArchIndex = (DWORD)(ArchOffset >> 0x1E); DWORD FileSize; - FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSize); + FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE); ArchOffset &= 0x3FFFFFFF; fprintf(fp, " %02X %08X %08X %s\n", ArchIndex, ArchOffset, FileSize, StringFromBinary(pIndexEntry->IndexKey, CASC_FILE_KEY_SIZE, szIndexKey)); @@ -463,33 +390,81 @@ void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs) } } -void CascDumpStorage(const char * szFileName, TCascStorage * hs, const TCHAR * szListFile) +void CascDumpRootFile( + TCascStorage * hs, + LPBYTE pbRootFile, + DWORD cbRootFile, + const char * szFormat, + const TCHAR * szListFile, + int nDumpLevel) { - char ** FileNameArray = NULL; + PCASC_ENCODING_ENTRY pEncodingEntry; + ROOT_BLOCK_INFO BlockInfo; + PLISTFILE_MAP pListMap; + QUERY_KEY EncodingKey; + LPBYTE pbRootFileEnd = pbRootFile + cbRootFile; + LPBYTE pbFilePointer; FILE * fp; + char szOneLine[0x100]; + DWORD i; - // Validate the storage handle - if(hs != NULL) + // This function only dumps WoW-style root file + assert(*(PDWORD)pbRootFile != CASC_MNDX_SIGNATURE); + + // Create the dump file + fp = CreateDumpFile(szFormat, hs); + if(fp != NULL) { - // Create the dump file - fp = fopen(szFileName, "wt"); - if(fp != NULL) + // Create the listfile map +// DWORD dwTickCount = GetTickCount(); + pListMap = ListFile_CreateMap(szListFile); +// dwTickCount = GetTickCount() - dwTickCount; + + // Dump the root entries as-is + for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; ) { - // If we also have listfile, open it - if(szListFile != NULL) - FileNameArray = CreateFileNameArray(hs, szListFile); + // Validate the root block + pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd); + if(pbFilePointer == NULL) + break; + + // Dump the locale block + fprintf(fp, "Flags: %08X Locales: %08X NumberOfFiles: %u\n" + "=========================================================\n", + BlockInfo.pLocaleBlockHdr->Flags, + BlockInfo.pLocaleBlockHdr->Locales, + BlockInfo.pLocaleBlockHdr->NumberOfFiles); - // Dump all root keys - fprintf(fp, "Root Entries\n=========\n\n"); - DumpRootEntries(fp, hs, FileNameArray); + // Dump the hashes and encoding keys + for(i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++) + { + // Dump the entry + fprintf(fp, "%08X %08X-%08X %s %s\n", + (DWORD)(BlockInfo.pInt32Array[i]), + (DWORD)(BlockInfo.pRootEntries[i].FileNameHash >> 0x20), + (DWORD)(BlockInfo.pRootEntries[i].FileNameHash), + StringFromMD5((LPBYTE)BlockInfo.pRootEntries[i].EncodingKey, szOneLine), + ListFile_FindName(pListMap, BlockInfo.pRootEntries[i].FileNameHash)); + + // Find the encoding entry in the encoding table + if(nDumpLevel > 1) + { + EncodingKey.pbData = (LPBYTE)BlockInfo.pRootEntries[i].EncodingKey; + EncodingKey.cbData = MD5_HASH_SIZE; + pEncodingEntry = FindEncodingEntry(hs, &EncodingKey, NULL); + DumpEncodingEntry(fp, hs, pEncodingEntry, nDumpLevel); + } + } - FreeFileNameArray(hs, FileNameArray); - fclose(fp); + // Put extra newline + fprintf(fp, "\n"); } + + ListFile_FreeMap(pListMap); + fclose(fp); } } - void CascDumpFile(const char * szFileName, HANDLE hFile) { FILE * fp; diff --git a/dep/CascLib/src/CascFindFile.cpp b/dep/CascLib/src/CascFindFile.cpp index f1b14560bed..753e00a90a2 100644 --- a/dep/CascLib/src/CascFindFile.cpp +++ b/dep/CascLib/src/CascFindFile.cpp @@ -72,7 +72,7 @@ static bool VerifyRootEntry(TCascSearch * pSearch, PCASC_ROOT_ENTRY pRootEntry, // dwRootEntries++; // Now try to find that encoding key in the array of encoding keys - QueryKey.pbData = pRootEntry->EncodingKey; + QueryKey.pbData = (LPBYTE)pRootEntry->EncodingKey; QueryKey.cbData = MD5_HASH_SIZE; pEncodingEntry = FindEncodingEntry(hs, &QueryKey, NULL); if(pEncodingEntry == NULL) @@ -94,7 +94,9 @@ static bool VerifyRootEntry(TCascSearch * pSearch, PCASC_ROOT_ENTRY pRootEntry, pFindData->FileNameHash = pRootEntry->FileNameHash; pFindData->dwPackageIndex = 0; pFindData->dwLocaleFlags = pRootEntry->Locales; - pFindData->dwFileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBytes); + + // Fill-in the file size + pFindData->dwFileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE); return true; } @@ -104,7 +106,7 @@ static TCascSearch * AllocateSearchHandle(TCascStorage * hs, const TCHAR * szLis size_t cbToAllocate; // When using the MNDX info, do not allocate the extra bit array - cbToAllocate = sizeof(TCascSearch) + ((hs->pMndxInfo == NULL) ? (hs->nRootEntries / 8) : 0); + cbToAllocate = sizeof(TCascSearch) + ((hs->pMndxInfo == NULL) ? (hs->RootTable.TableSize / 8) : 0); pSearch = (TCascSearch *)CASC_ALLOC(BYTE, cbToAllocate); if(pSearch != NULL) { @@ -147,53 +149,28 @@ static bool DoStorageSearch_ListFile(TCascSearch * pSearch, PCASC_FIND_DATA pFin { PCASC_ROOT_ENTRY pRootEntry; TCascStorage * hs = pSearch->hs; - size_t RootIndex; + DWORD TableIndex = 0; - for(;;) + // Get next file from the listfile + while(ListFile_GetNext(pSearch->pCache, pSearch->szMask, pSearch->szFileName, MAX_PATH)) { - // Shall we get a new file name from the listfile? - if(pSearch->FileNameHash == 0) +#ifdef _DEBUG + //if(!_stricmp(pSearch->szFileName, "Character\\NightElf\\Female\\NightElf_FemaleFacialLowerHair01_02_HD.blp")) + // DebugBreak(); +#endif + + // Find the root entry + pRootEntry = FindRootEntry(hs, pSearch->szFileName, &TableIndex); + if(pRootEntry != NULL) { - // Try to get next file from the listfile - if(!ListFile_GetNext(pSearch->pCache, pSearch->szMask, pSearch->szFileName, MAX_PATH)) - break; - -// if(!_stricmp(pSearch->szFileName, "Interface\\Glues\\MODELS\\UI_MainMenu_Warlords\\Caustic_MedFreq.blp")) -// DebugBreak(); - - // Normalize the file name - strcpy(pSearch->szNormName, pSearch->szFileName); - NormalizeFileName_UpperBkSlash(pSearch->szNormName); - - // Find the first root entry belonging to this file name - pRootEntry = FindFirstRootEntry(pSearch->hs, pSearch->szNormName, &RootIndex); - if(pRootEntry == NULL) - continue; - - // We have the name now, search all locales - pSearch->FileNameHash = pRootEntry->FileNameHash; - pSearch->RootIndex = RootIndex; - } - - // Is the root index in range? - while(pSearch->RootIndex < hs->nRootEntries) - { - // Get the next root entry. If name mismatches, stop searching - pRootEntry = hs->ppRootEntries[pSearch->RootIndex]; - if(pRootEntry->FileNameHash != pSearch->FileNameHash) - break; - // Verify whether the file exists in the storage - if(VerifyRootEntry(pSearch, pRootEntry, pFindData, pSearch->RootIndex++)) + if(VerifyRootEntry(pSearch, pRootEntry, pFindData, TableIndex)) { strcpy(pFindData->szFileName, pSearch->szFileName); pFindData->szPlainName = (char *)GetPlainFileName(pFindData->szFileName); return true; } } - - // Reset the name hash and root index and retry search - pSearch->FileNameHash = 0; } // Listfile search ended @@ -206,13 +183,13 @@ static bool DoStorageSearch_Hash(TCascSearch * pSearch, PCASC_FIND_DATA pFindDat TCascStorage * hs = pSearch->hs; // Check if there is more files with the same name hash - while(pSearch->RootIndex < hs->nRootEntries) + while(pSearch->RootIndex < hs->RootTable.TableSize) { // Get the pointer to the root entry - pRootEntry = hs->ppRootEntries[pSearch->RootIndex]; + pRootEntry = hs->RootTable.TablePtr + pSearch->RootIndex; // Verify if that root entry exists in the CASC storage - // Note that the file name will be there from the previous search + // and was not found before if(VerifyRootEntry(pSearch, pRootEntry, pFindData, pSearch->RootIndex)) { pFindData->szFileName[0] = 0; @@ -242,7 +219,6 @@ static bool DoStorageSearch(TCascSearch * pSearch, PCASC_FIND_DATA pFindData) pSearch->pCache = ListFile_OpenExternal(pSearch->szListFile); // Move the search phase to the listfile searching - pSearch->FileNameHash = 0; pSearch->RootIndex = 0; pSearch->dwState++; @@ -259,7 +235,7 @@ static bool DoStorageSearch(TCascSearch * pSearch, PCASC_FIND_DATA pFindData) return true; // Move to the nameless search state - pSearch->RootIndex = 0; + assert(pSearch->RootIndex == 0); pSearch->dwState++; } diff --git a/dep/CascLib/src/CascLib.h b/dep/CascLib/src/CascLib.h index a3960a558c2..330a4b2bb49 100644 --- a/dep/CascLib/src/CascLib.h +++ b/dep/CascLib/src/CascLib.h @@ -66,7 +66,7 @@ extern "C" { #define CASC_LOCALE_UNKNOWN1 0x00000001 #define CASC_LOCALE_ENUS 0x00000002 #define CASC_LOCALE_KOKR 0x00000004 -#define CASC_LOCALE_UNKNOWN8 0x00000008 +#define CASC_LOCALE_RESERVED 0x00000008 #define CASC_LOCALE_FRFR 0x00000010 #define CASC_LOCALE_DEDE 0x00000020 #define CASC_LOCALE_ZHCN 0x00000040 @@ -81,6 +81,24 @@ 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_DUAL_LANG 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) #ifndef MD5_HASH_SIZE @@ -110,6 +128,8 @@ typedef enum _CASC_STORAGE_INFO_CLASS { CascStorageFileCount, CascStorageFeatures, + CascStorageGameInfo, + CascStorageGameBuild, CascStorageInfoClassMax } CASC_STORAGE_INFO_CLASS, *PCASC_STORAGE_INFO_CLASS; @@ -148,7 +168,7 @@ void qsort_pointer_array(void ** base, size_t num, int (*compare)(const void *, //----------------------------------------------------------------------------- // Functions for storage manipulation -bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * phStorage); +bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwLocaleMask, HANDLE * phStorage); bool WINAPI CascGetStorageInfo(HANDLE hStorage, CASC_STORAGE_INFO_CLASS InfoClass, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded); bool WINAPI CascCloseStorage(HANDLE hStorage); diff --git a/dep/CascLib/src/CascMndxRoot.cpp b/dep/CascLib/src/CascMndxRoot.cpp index a788b652573..db29fba1662 100644 --- a/dep/CascLib/src/CascMndxRoot.cpp +++ b/dep/CascLib/src/CascMndxRoot.cpp @@ -202,6 +202,11 @@ DWORD GetNumberOfSetBits(DWORD Value32) 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; @@ -2887,7 +2892,7 @@ PCASC_PACKAGE FindMndxPackage(TCascStorage * hs, const char * szFileName) static bool FillFindData(TCascSearch * pSearch, PCASC_FIND_DATA pFindData, TMndxFindResult * pStruct1C) { - CASC_ROOT_KEY_INFO RootKeyInfo; + PCASC_ROOT_ENTRY_MNDX pRootEntry = NULL; TCascStorage * hs = pSearch->hs; PCASC_PACKAGE pPackage; char * szStrippedName; @@ -2899,6 +2904,7 @@ static bool FillFindData(TCascSearch * pSearch, PCASC_FIND_DATA pFindData, TMndx // Fill the file name memcpy(pFindData->szFileName, pStruct1C->szFoundPath, pStruct1C->cchFoundPath); pFindData->szFileName[pStruct1C->cchFoundPath] = 0; + pFindData->szPlainName = (char *)GetPlainFileName(pFindData->szFileName); pFindData->dwFileSize = CASC_INVALID_SIZE; // Fill the file size @@ -2910,10 +2916,10 @@ static bool FillFindData(TCascSearch * pSearch, PCASC_FIND_DATA pFindData, TMndx while(szStrippedName[0] == '/') szStrippedName++; - nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &RootKeyInfo); + nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &pRootEntry); if(nError == ERROR_SUCCESS) { - pFindData->dwFileSize = (DWORD)RootKeyInfo.FileSize; + pFindData->dwFileSize = pRootEntry->FileSize; } } return true; @@ -3014,7 +3020,7 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) { if(pMndxInfo->pMarFile1 == NULL || pMndxInfo->pMarFile2 == NULL || pMndxInfo->pMarFile3 == NULL) nError = ERROR_BAD_FORMAT; - if(pMndxInfo->MndxEntrySize != sizeof(CASC_MNDX_ENTRY)) + if(pMndxInfo->MndxEntrySize != sizeof(CASC_ROOT_ENTRY_MNDX)) nError = ERROR_BAD_FORMAT; } @@ -3028,7 +3034,7 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) if(nError == ERROR_SUCCESS && FileNameCount == pMndxInfo->MndxEntriesValid) { cbToAllocate = pMndxInfo->MndxEntriesTotal * pMndxInfo->MndxEntrySize; - pMndxInfo->pMndxEntries = (PCASC_MNDX_ENTRY)CASC_ALLOC(BYTE, cbToAllocate); + pMndxInfo->pMndxEntries = (PCASC_ROOT_ENTRY_MNDX)CASC_ALLOC(BYTE, cbToAllocate); if(pMndxInfo->pMndxEntries != NULL) { if(!RootFileRead(pbRootFile + pMndxInfo->MndxEntriesOffset, pbRootFileEnd, pMndxInfo->pMndxEntries, cbToAllocate)) @@ -3045,10 +3051,10 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) if(nError == ERROR_SUCCESS) { assert(pMndxInfo->MndxEntriesValid <= pMndxInfo->MndxEntriesTotal); - pMndxInfo->ppValidEntries = CASC_ALLOC(PCASC_MNDX_ENTRY, pMndxInfo->MndxEntriesValid + 1); + pMndxInfo->ppValidEntries = CASC_ALLOC(PCASC_ROOT_ENTRY_MNDX, pMndxInfo->MndxEntriesValid + 1); if(pMndxInfo->ppValidEntries != NULL) { - PCASC_MNDX_ENTRY pMndxEntry = pMndxInfo->pMndxEntries; + PCASC_ROOT_ENTRY_MNDX pRootEntry = pMndxInfo->pMndxEntries; DWORD ValidEntryCount = 1; // edx DWORD nIndex1 = 0; @@ -3056,14 +3062,14 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) pMndxInfo->ppValidEntries[nIndex1++] = pMndxInfo->pMndxEntries; // Put the remaining entries - for(i = 0; i < pMndxInfo->MndxEntriesTotal; i++, pMndxEntry++) + for(i = 0; i < pMndxInfo->MndxEntriesTotal; i++, pRootEntry++) { if(ValidEntryCount > pMndxInfo->MndxEntriesValid) break; - if(pMndxEntry->Flags & 0x80000000) + if(pRootEntry->Flags & 0x80000000) { - pMndxInfo->ppValidEntries[nIndex1++] = pMndxEntry + 1; + pMndxInfo->ppValidEntries[nIndex1++] = pRootEntry + 1; ValidEntryCount++; } } @@ -3106,9 +3112,9 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) return nError; } -int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_KEY_INFO pFoundInfo) +int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_ENTRY_MNDX * ppRootEntry) { - PCASC_MNDX_ENTRY pMndxEntry; + PCASC_ROOT_ENTRY_MNDX pRootEntry; TMndxFindResult Struct1C; // Search the database for the file name @@ -3124,20 +3130,19 @@ int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwP if(Struct1C.FileNameIndex < pMndxInfo->MndxEntriesValid) { // HOTS: E945F4 - pMndxEntry = pMndxInfo->ppValidEntries[Struct1C.FileNameIndex]; - while((pMndxEntry->Flags & 0x00FFFFFF) != dwPackage) + pRootEntry = pMndxInfo->ppValidEntries[Struct1C.FileNameIndex]; + while((pRootEntry->Flags & 0x00FFFFFF) != dwPackage) { // The highest bit serves as a terminator if set - if(pMndxEntry->Flags & 0x80000000) + if(pRootEntry->Flags & 0x80000000) return ERROR_FILE_NOT_FOUND; - pMndxEntry++; + pRootEntry++; } - // Fill the root info - memcpy(pFoundInfo->EncodingKey, pMndxEntry->EncodingKey, MD5_HASH_SIZE); - pFoundInfo->FileSize = pMndxEntry->FileSize; - pFoundInfo->Flags = (BYTE)((pMndxEntry->Flags >> 0x18) & 0x3F); + // Give the root entry pointer to the caller + if(ppRootEntry != NULL) + ppRootEntry[0] = pRootEntry; return ERROR_SUCCESS; } } @@ -3213,14 +3218,18 @@ extern "C" { extern "C" void * allocate_zeroed_memory_x86(size_t bytes) { - return calloc(bytes, 1); + void * ptr = CASC_ALLOC(BYTE, bytes); + + if(ptr != NULL) + memset(ptr, 0, bytes); + return ptr; } extern "C" void free_memory_x86(void * ptr) { if(ptr != NULL) { - free(ptr); + CASC_FREE(ptr); } } diff --git a/dep/CascLib/src/CascMndxRoot.h b/dep/CascLib/src/CascMndxRoot.h index 23017642ff9..bd93f230845 100644 --- a/dep/CascLib/src/CascMndxRoot.h +++ b/dep/CascLib/src/CascMndxRoot.h @@ -358,7 +358,7 @@ inline bool IS_SINGLE_CHAR_MATCH(TGenericArray & Table, DWORD ItemIndex) int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); PCASC_PACKAGE FindMndxPackage(TCascStorage * hs, const char * szFileName); -int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_KEY_INFO pFoundInfo); +int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_ENTRY_MNDX * ppFoundInfo); bool DoStorageSearch_MNDX(TCascSearch * pSearch, PCASC_FIND_DATA pFindData); void FreeMndxInfo(PCASC_MNDX_INFO pMndxInfo); diff --git a/dep/CascLib/src/CascOpenFile.cpp b/dep/CascLib/src/CascOpenFile.cpp index e7cdb9aa107..e5a9855f97f 100644 --- a/dep/CascLib/src/CascOpenFile.cpp +++ b/dep/CascLib/src/CascOpenFile.cpp @@ -69,115 +69,47 @@ PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKe } // Also used in CascSearchFile -PCASC_ROOT_ENTRY FindFirstRootEntry(TCascStorage * hs, const char * szFileName, size_t * PtrIndex) +PCASC_ROOT_ENTRY FindRootEntry(TCascStorage * hs, const char * szFileName, DWORD * PtrTableIndex) { - PCASC_ROOT_ENTRY pFoundEntry = NULL; PCASC_ROOT_ENTRY pRootEntry; ULONGLONG FileNameHash; + DWORD TableIndex; uint32_t dwHashHigh = 0; uint32_t dwHashLow = 0; - size_t StartEntry = 0; - size_t MidlEntry = 0; - size_t EndEntry = hs->nRootEntries; // Calculate the HASH value of the normalized file name hashlittle2(szFileName, strlen(szFileName), &dwHashHigh, &dwHashLow); FileNameHash = ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow; - // Perform binary search - while(StartEntry < EndEntry) - { - // Calculate the middle of the interval - MidlEntry = StartEntry + ((EndEntry - StartEntry) / 2); - pRootEntry = hs->ppRootEntries[MidlEntry]; + // Get the first table index + TableIndex = (DWORD)(FileNameHash & (hs->RootTable.TableSize - 1)); + assert(hs->RootTable.ItemCount < hs->RootTable.TableSize); - // Did we find it? + // Search the proper entry + for(;;) + { + // Does the has match? + pRootEntry = hs->RootTable.TablePtr + TableIndex; if(pRootEntry->FileNameHash == FileNameHash) { - pFoundEntry = pRootEntry; - break; + if(PtrTableIndex != NULL) + PtrTableIndex[0] = TableIndex; + return pRootEntry; } - // Move the interval to the left or right - (FileNameHash < pRootEntry->FileNameHash) ? EndEntry = MidlEntry : StartEntry = MidlEntry + 1; - } + // If the entry is free, the file is not there + if(pRootEntry->FileNameHash == 0 && pRootEntry->SumValue == 0) + return NULL; - // Move the pointer back to the first entry with that hash - if(pFoundEntry != NULL) - { - while(MidlEntry > 0 && hs->ppRootEntries[MidlEntry - 1]->FileNameHash == FileNameHash) - { - pFoundEntry = hs->ppRootEntries[MidlEntry - 1]; - MidlEntry--; - } + // Move to the next entry + TableIndex = (DWORD)((TableIndex + 1) & (hs->RootTable.TableSize - 1)); } - - // Return what we found - if(PtrIndex != NULL) - *PtrIndex = MidlEntry; - return pFoundEntry; -} - -// Check the root directory for that hash -PCASC_ROOT_ENTRY FindRootEntryLocale(TCascStorage * hs, char * szFileName, DWORD Locale) -{ - PCASC_ROOT_ENTRY pThatEntry = NULL; - PCASC_ROOT_ENTRY pENUSEntry = NULL; - PCASC_ROOT_ENTRY pENGBEntry = NULL; - PCASC_ROOT_ENTRY pAnyEntry = NULL; - PCASC_ROOT_ENTRY pEndEntry = NULL; - PCASC_ROOT_ENTRY pRootEntry = NULL; - ULONGLONG FileNameHash; - size_t EntryIndex = 0; - size_t EndEntry = hs->nRootEntries; - - // Find a root entry with the given name hash - pRootEntry = FindFirstRootEntry(hs, szFileName, &EntryIndex); - if(pRootEntry != NULL) - { - // Rememeber the file name hash - pEndEntry = hs->pRootEntries + hs->nRootEntries; - FileNameHash = pRootEntry->FileNameHash; - - // Find all suitable root entries - while(EntryIndex < EndEntry) - { - // Get the root entry - pRootEntry = hs->ppRootEntries[EntryIndex++]; - if(pRootEntry->FileNameHash != FileNameHash) - break; - - // If a locale has been given, check it - if(pThatEntry == NULL && Locale != 0 && (Locale & pRootEntry->Locales)) - pThatEntry = pRootEntry; - if(pENUSEntry == NULL && (pRootEntry->Locales & CASC_LOCALE_ENUS)) - pENUSEntry = pRootEntry; - if(pENGBEntry == NULL && (pRootEntry->Locales & CASC_LOCALE_ENGB)) - pENGBEntry = pRootEntry; - if(pAnyEntry == NULL) - pAnyEntry = pRootEntry; - - // Move to the next one - pRootEntry++; - } - - // Return the key by priority - if(pThatEntry != NULL) - return pThatEntry; - if(pENGBEntry != NULL) - return pENGBEntry; - if(pENUSEntry != NULL) - return pENUSEntry; - } - - // Return whatever we got - return pAnyEntry; } static TCascFile * CreateFileHandle(TCascStorage * hs, PCASC_INDEX_ENTRY pIndexEntry) { ULONGLONG FileOffsMask = ((ULONGLONG)1 << hs->KeyMapping[0].SegmentBits) - 1; - ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffset); + ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE); TCascFile * hf; // Allocate the CASC file structure @@ -190,12 +122,13 @@ static TCascFile * CreateFileHandle(TCascStorage * hs, PCASC_INDEX_ENTRY pIndexE hf->HeaderOffset = (DWORD)(FileOffset & FileOffsMask); hf->szClassName = "TCascFile"; - // Copy the compressed file size - hf->CompressedSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSize) - 0x1E; + // 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 itself + // 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 @@ -206,10 +139,9 @@ static TCascFile * CreateFileHandle(TCascStorage * hs, PCASC_INDEX_ENTRY pIndexE return hf; } -static bool OpenFileByIndexKey(TCascStorage * hs, PQUERY_KEY pIndexKey, DWORD dwFlags, HANDLE * phFile) +static bool OpenFileByIndexKey(TCascStorage * hs, PQUERY_KEY pIndexKey, DWORD dwFlags, TCascFile ** ppCascFile) { PCASC_INDEX_ENTRY pIndexEntry; - TCascFile * hf = NULL; int nError = ERROR_SUCCESS; CASCLIB_UNUSED(dwFlags); @@ -222,43 +154,53 @@ static bool OpenFileByIndexKey(TCascStorage * hs, PQUERY_KEY pIndexKey, DWORD dw // Create the file handle structure if(nError == ERROR_SUCCESS) { - hf = CreateFileHandle(hs, pIndexEntry); - *phFile = (HANDLE)hf; - if(hf == NULL) + ppCascFile[0] = CreateFileHandle(hs, pIndexEntry); + if(ppCascFile[0] == NULL) nError = ERROR_FILE_NOT_FOUND; } +#ifdef CASCLIB_TEST + if(nError == ERROR_SUCCESS && ppCascFile[0] != NULL) + { + ppCascFile[0]->FileSize_IdxEntry = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE); + } +#endif + if(nError != ERROR_SUCCESS) SetLastError(nError); return (nError == ERROR_SUCCESS); } -static bool OpenFileByEncodingKey(TCascStorage * hs, PQUERY_KEY pEncodingKey, DWORD dwFlags, HANDLE * phFile) +static bool OpenFileByEncodingKey(TCascStorage * hs, PQUERY_KEY pEncodingKey, DWORD dwFlags, TCascFile ** ppCascFile) { PCASC_ENCODING_ENTRY pEncodingEntry; QUERY_KEY IndexKey; - TCascFile * hf = NULL; - int nError = ERROR_SUCCESS; // Find the encoding entry pEncodingEntry = FindEncodingEntry(hs, pEncodingKey, NULL); if(pEncodingEntry == NULL) - nError = ERROR_FILE_NOT_FOUND; + { + 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 = pEncodingEntry->EncodingKey + (MD5_HASH_SIZE * pEncodingEntry->KeyCount); -// assert(pEncodingEntry->KeyCount == 1); IndexKey.pbData = pEncodingEntry->EncodingKey + MD5_HASH_SIZE; IndexKey.cbData = MD5_HASH_SIZE; - if(OpenFileByIndexKey(hs, &IndexKey, dwFlags, phFile)) + if(OpenFileByIndexKey(hs, &IndexKey, dwFlags, ppCascFile)) { - // Fix the file size from the encoding key - hf = IsValidFileHandle(*phFile); - if(hf != NULL) + // Check if the file handle was created + if(ppCascFile[0] != NULL) { - hf->FileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBytes); + // 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; } } @@ -289,7 +231,7 @@ bool WINAPI CascOpenFileByIndexKey(HANDLE hStorage, PQUERY_KEY pIndexKey, DWORD } // Use the internal function to open the file - return OpenFileByIndexKey(hs, pIndexKey, dwFlags, phFile); + return OpenFileByIndexKey(hs, pIndexKey, dwFlags, (TCascFile **)phFile); } bool WINAPI CascOpenFileByEncodingKey(HANDLE hStorage, PQUERY_KEY pEncodingKey, DWORD dwFlags, HANDLE * phFile) @@ -312,12 +254,12 @@ bool WINAPI CascOpenFileByEncodingKey(HANDLE hStorage, PQUERY_KEY pEncodingKey, } // Use the internal function fo open the file - return OpenFileByEncodingKey(hs, pEncodingKey, dwFlags, phFile); + return OpenFileByEncodingKey(hs, pEncodingKey, dwFlags, (TCascFile **)phFile); } bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocale, DWORD dwFlags, HANDLE * phFile) { - CASC_ROOT_KEY_INFO EncodingKeyInfo; + PCASC_ROOT_ENTRY_MNDX pRootEntryMndx = NULL; PCASC_ROOT_ENTRY pRootEntry; PCASC_PACKAGE pPackage; TCascStorage * hs; @@ -326,6 +268,8 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal char * szFileName2; int nError = ERROR_SUCCESS; + CASCLIB_UNUSED(dwLocale); + // Validate the storage handle hs = IsValidStorageHandle(hStorage); if(hs == NULL) @@ -360,11 +304,11 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal while(szStrippedName[0] == '/') szStrippedName++; - nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &EncodingKeyInfo); + nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &pRootEntryMndx); if(nError == ERROR_SUCCESS) { // Prepare the encoding key - EncodingKey.pbData = EncodingKeyInfo.EncodingKey; + EncodingKey.pbData = pRootEntryMndx->EncodingKey; EncodingKey.cbData = MD5_HASH_SIZE; } } @@ -376,23 +320,27 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal else { // Convert the file name to lowercase + slashes - NormalizeFileName_UpperBkSlash(szFileName2); + NormalizeFileName_UpperBkSlash(szFileName2, szFileName2); // Check the root directory for that hash - pRootEntry = FindRootEntryLocale(hs, szFileName2, dwLocale); - nError = (pRootEntry != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND; + pRootEntry = FindRootEntry(hs, szFileName2, NULL); if(pRootEntry != NULL) { // Prepare the root key - EncodingKey.pbData = pRootEntry->EncodingKey; + EncodingKey.pbData = (LPBYTE)pRootEntry->EncodingKey; EncodingKey.cbData = MD5_HASH_SIZE; + nError = ERROR_SUCCESS; + } + else + { + nError = ERROR_FILE_NOT_FOUND; } } // Use the root key to find the file in the encoding table entry if(nError == ERROR_SUCCESS) { - if(!OpenFileByEncodingKey(hs, &EncodingKey, dwFlags, phFile)) + if(!OpenFileByEncodingKey(hs, &EncodingKey, dwFlags, (TCascFile **)phFile)) { assert(GetLastError() != ERROR_SUCCESS); nError = GetLastError(); @@ -400,11 +348,18 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal } // Delete the file name copy - delete [] szFileName2; + CASC_FREE(szFileName2); } else nError = ERROR_NOT_ENOUGH_MEMORY; +#ifdef CASCLIB_TEST + if(phFile[0] != NULL && pRootEntryMndx != NULL) + { + ((TCascFile *)(phFile[0]))->FileSize_RootEntry = pRootEntryMndx->FileSize; + } +#endif + if(nError != ERROR_SUCCESS) SetLastError(nError); return (nError == ERROR_SUCCESS); diff --git a/dep/CascLib/src/CascOpenStorage.cpp b/dep/CascLib/src/CascOpenStorage.cpp index f9e6ba6f4d4..490d889742f 100644 --- a/dep/CascLib/src/CascOpenStorage.cpp +++ b/dep/CascLib/src/CascOpenStorage.cpp @@ -2,6 +2,8 @@ /* 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 */ /* -------- ---- --- ------- */ @@ -14,9 +16,17 @@ #include "CascMndxRoot.h" //----------------------------------------------------------------------------- +// Dumping options + +#ifdef _DEBUG +#define CASC_DUMP_ROOT_FILE 2 // The root file will be dumped (level 2) +#endif + +//----------------------------------------------------------------------------- // Local structures -#define CASC_ENCODING_SEGMENT_SIZE 0x1000 +#define CASC_INITIAL_ROOT_TABLE_SIZE 0x00100000 +#define CASC_ENCODING_SEGMENT_SIZE 0x1000 typedef struct _BLOCK_SIZE_AND_HASH { @@ -79,32 +89,6 @@ typedef struct _FILE_ENCODING_SEGMENT } FILE_ENCODING_SEGMENT, *PFILE_ENCODING_SEGMENT; -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; - -typedef struct _FILE_ROOT_ENTRY -{ - BYTE EncodingKey[MD5_HASH_SIZE]; // MD5 of the file - ULONGLONG FileNameHash; // Jenkins hash of the file name - -} FILE_ROOT_ENTRY, *PFILE_ROOT_ENTRY; - -typedef struct _ROOT_BLOCK_INFO -{ - PFILE_LOCALE_BLOCK pLocaleBlockHdr; // Pointer to the locale block - PDWORD pInt32Array; // Pointer to the array of 32-bit integers - PFILE_ROOT_ENTRY pRootEntries; - -} ROOT_BLOCK_INFO, *PROOT_BLOCK_INFO; - //----------------------------------------------------------------------------- // Local variables @@ -181,19 +165,6 @@ static void QUERY_KEY_FreeArray(PQUERY_KEY pBlobArray) CASC_FREE(pBlobArray); } -static int CompareRootEntries(const void *, const void * pvKeyEntry1, const void * pvKeyEntry2) -{ - PCASC_ROOT_ENTRY pRootEntry1 = (PCASC_ROOT_ENTRY)pvKeyEntry1; - PCASC_ROOT_ENTRY pRootEntry2 = (PCASC_ROOT_ENTRY)pvKeyEntry2; - - // Compare name hash first - if(pRootEntry1->FileNameHash < pRootEntry2->FileNameHash) - return -1; - if(pRootEntry1->FileNameHash > pRootEntry2->FileNameHash) - return +1; - return 0; -} - static bool IsCascIndexHeader_V1(LPBYTE pbFileData, DWORD cbFileData) { PFILE_INDEX_HEADER_V1 pIndexHeader = (PFILE_INDEX_HEADER_V1)pbFileData; @@ -235,18 +206,18 @@ static bool IsCascIndexHeader_V2(LPBYTE pbFileData, DWORD cbFileData) return (HashHigh == pSizeAndHash->dwBlockHash); } -static LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd) +LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd) { - // Validate the locale header + // Validate the file locale block pBlockInfo->pLocaleBlockHdr = (PFILE_LOCALE_BLOCK)pbFilePointer; - pbFilePointer += sizeof(FILE_LOCALE_BLOCK); - if(pbFilePointer >= pbFileEnd) + pbFilePointer = (LPBYTE)(pBlockInfo->pLocaleBlockHdr + 1); + if(pbFilePointer > pbFileEnd) return NULL; // Validate the array of 32-bit integers pBlockInfo->pInt32Array = (PDWORD)pbFilePointer; pbFilePointer = (LPBYTE)(pBlockInfo->pInt32Array + pBlockInfo->pLocaleBlockHdr->NumberOfFiles); - if(pbFilePointer >= pbFileEnd) + if(pbFilePointer > pbFileEnd) return NULL; // Validate the array of root entries @@ -488,7 +459,7 @@ static int VerifyAndParseKeyMapping_V2(PCASC_MAPPING_TABLE pKeyMapping, DWORD Ke if(PtrLastPart[0] == 0) return ERROR_SUCCESS; - HashLow = hashlittle(PtrLastPart + 1, 0x13, 0) | 0x80000000; + HashLow = hashlittle(PtrLastPart + i, 0x13, 0) | 0x80000000; if(HashLow != PtrLastPart[0]) return ERROR_BAD_FORMAT; } @@ -533,7 +504,7 @@ static int LoadKeyMapping(PCASC_MAPPING_TABLE pKeyMapping, DWORD KeyIndex) { // Retrieve the file size FileStream_GetSize(pStream, &FileSize); - if((0 < FileSize && FileSize <= 0x90000) || 1) + if(0 < FileSize && FileSize <= 0x100000) { // WoW6 actually reads THE ENTIRE file to memory // Verified on Mac build (x64) @@ -664,112 +635,146 @@ static int CreateMapOfEncodingKeys(TCascStorage * hs, PFILE_ENCODING_SEGMENT pEn return nError; } -static DWORD GetSizeOfEncodingFile(HANDLE hFile) +static int LoadIndexFiles(TCascStorage * hs) +{ + DWORD IndexArray[CASC_INDEX_COUNT]; + DWORD OldIndexArray[CASC_INDEX_COUNT]; + int nError; + int i; + + // 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) + { + // 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; + } + } + } + + // Now we need to build the map of the index entries + if(nError == ERROR_SUCCESS) + { + nError = CreateArrayOfIndexEntries(hs); + } + + return nError; +} + +static LPBYTE LoadEncodingFileToMemory(HANDLE hFile, DWORD * pcbEncodingFile) { CASC_ENCODING_HEADER EncodingHeader; + LPBYTE pbEncodingFile = NULL; DWORD cbEncodingFile = 0; - DWORD dwSegmentPos; - DWORD dwNumSegments; + DWORD dwSegmentPos = 0; + DWORD dwNumSegments = 0; DWORD dwBytesRead; + int nError = ERROR_BAD_FORMAT; - // Read the endoding header + // Read the encoding header CascReadFile(hFile, &EncodingHeader, sizeof(CASC_ENCODING_HEADER), &dwBytesRead); if(dwBytesRead == sizeof(CASC_ENCODING_HEADER)) { dwNumSegments = ConvertBytesToInteger_4(EncodingHeader.NumSegments); dwSegmentPos = ConvertBytesToInteger_4(EncodingHeader.SegmentsPos); + 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); - } - - // Reset the position back - CascSetFilePointer(hFile, 0, NULL, FILE_BEGIN); - return cbEncodingFile; -} - -static LPBYTE LoadCascFile(HANDLE hFile, DWORD cbMaxSize, PDWORD pcbFileData) -{ - LPBYTE pbFileData = NULL; - DWORD cbFileData; - DWORD dwBytesRead = 0; - int nError = ERROR_SUCCESS; - - // Retrieve the size of the file - cbFileData = CascGetFileSize(hFile, NULL); - if(cbFileData != 0 && cbFileData != CASC_INVALID_SIZE) - { - // Trim the size to the maximum - cbFileData = CASCLIB_MIN(cbMaxSize, cbFileData); - - // Allocate the buffer that will hold the entire 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 + pbEncodingFile = CASC_ALLOC(BYTE, cbEncodingFile); + if(pbEncodingFile == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } - else - nError = ERROR_FILE_CORRUPT; - // If something failed, clean-up the buffers - if(nError != ERROR_SUCCESS) + // If all went OK, we load the entire file to memory + if(nError == ERROR_SUCCESS) { - // Clear the file data - if(pbFileData != NULL) - CASC_FREE(pbFileData); - pbFileData = NULL; - cbFileData = 0; + // Copy the header itself + memcpy(pbEncodingFile, &EncodingHeader, sizeof(CASC_ENCODING_HEADER)); - // Set the last error value - SetLastError(nError); + // 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; } - // Return what we got - if(pcbFileData != NULL) - *pcbFileData = cbFileData; - return pbFileData; + // Give the loaded file length + if(pcbEncodingFile != NULL) + *pcbEncodingFile = cbEncodingFile; + return pbEncodingFile; } -static int LoadIndexFiles(TCascStorage * hs) +static LPBYTE LoadRootFileToMemory(HANDLE hFile, DWORD * pcbRootFile) { - DWORD IndexArray[CASC_INDEX_COUNT]; - DWORD OldIndexArray[CASC_INDEX_COUNT]; - int nError; - int i; + TCascFile * hf; + LPBYTE pbRootFile = NULL; + DWORD cbRootFile = 0; + DWORD dwBytesRead; + BYTE StartOfFile[0x10]; + 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); + // Dummy read the first 16 bytes + CascReadFile(hFile, &StartOfFile, sizeof(StartOfFile), &dwBytesRead); + if(dwBytesRead != sizeof(StartOfFile)) + nError = ERROR_BAD_FORMAT; + + // Calculate and allocate space for the entire file if(nError == ERROR_SUCCESS) { - // Load each index file - for(i = 0; i < CASC_INDEX_COUNT; i++) + // Convert the file handle to pointer to TCascFile + hf = IsValidFileHandle(hFile); + if(hf != NULL) { - hs->KeyMapping[i].szFileName = CreateIndexFileName(hs, i, IndexArray[i]); - if(hs->KeyMapping[i].szFileName != NULL) + // Parse the frames to get the file size + for(DWORD i = 0; i < hf->FrameCount; i++) { - nError = LoadKeyMapping(&hs->KeyMapping[i], i); - if(nError != ERROR_SUCCESS) - break; + cbRootFile += hf->pFrames[i].FrameSize; } } + + // Evaluate the error + nError = (cbRootFile != 0) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; } - // Now we need to build the map of the index entries + // Allocate space for the entire file if(nError == ERROR_SUCCESS) { - nError = CreateArrayOfIndexEntries(hs); + pbRootFile = CASC_ALLOC(BYTE, cbRootFile); + if(pbRootFile == NULL) + nError = ERROR_NOT_ENOUGH_MEMORY; } - return nError; + // If all went OK, we load the entire file to memory + if(nError == ERROR_SUCCESS) + { + // Copy the header itself + memcpy(pbRootFile, StartOfFile, sizeof(StartOfFile)); + + // Read the rest of the data + CascReadFile(hFile, pbRootFile + sizeof(StartOfFile), cbRootFile - sizeof(StartOfFile), &dwBytesRead); + if(dwBytesRead != (cbRootFile - sizeof(StartOfFile))) + nError = ERROR_FILE_CORRUPT; + } + + // Give the loaded file length + if(pcbRootFile != NULL) + *pcbRootFile = cbRootFile; + return pbRootFile; } static int LoadEncodingFile(TCascStorage * hs) @@ -788,18 +793,15 @@ static int LoadEncodingFile(TCascStorage * hs) if(!CascOpenFileByIndexKey((HANDLE)hs, &hs->EncodingEKey, 0, &hFile)) nError = GetLastError(); - // Load the encoding file to memory + // Load the entire ENCODING file to memory if(nError == ERROR_SUCCESS) { - // Retrieve the CASC header. We do not usually need to load - // the entire file, but we need to know how big part of it we need - cbEncodingFile = GetSizeOfEncodingFile(hFile); - - // Load the entire file to memory - pbEncodingFile = LoadCascFile(hFile, cbEncodingFile, &cbEncodingFile); + // 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; + // Close the encoding file CascCloseFile(hFile); } @@ -859,91 +861,237 @@ static int LoadEncodingFile(TCascStorage * hs) return nError; } -static int LoadRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) +typedef struct _CHECK_ROOT_ENTRY_INPUT +{ + ULONGLONG FileNameHash; + DWORD SumValue; + DWORD EncodingKey[4]; + +} CHECK_ROOT_ENTRY_INPUT, *PCHECK_ROOT_ENTRY_INPUT; + +typedef struct _CHECK_ROOT_ENTRY_OUTPUT +{ + DWORD field_0; + DWORD field_4; + DWORD field_8; + bool field_C; + +} CHECK_ROOT_ENTRY_OUTPUT, *PCHECK_ROOT_ENTRY_OUTPUT; + + +// WoW6: 00413F61 +static bool EnlargeHashTableIfMoreThan75PercentUsed(PCASC_ROOT_HASH_TABLE pRootTable, DWORD NewItemCount) +{ + // Don't relocate anything, just check + assert((double)NewItemCount / (double)pRootTable->TableSize < .75); + return true; +} + +// WOW6: 00414402 +// Finds an existing root table entry or a free one +PCASC_ROOT_ENTRY CascRootTable_FindFreeEntryWithEnlarge( + PCASC_ROOT_HASH_TABLE pRootTable, + PCASC_ROOT_ENTRY pNewEntry) { - PFILE_ROOT_ENTRY pSrcEntry; - PCASC_ROOT_ENTRY pTrgEntry; + PCASC_ROOT_ENTRY pEntry; + DWORD TableIndex; + + // The table size must be a power of two + assert((pRootTable->TableSize & (pRootTable->TableSize - 1)) == 0); + + // Make sure that number of occupied items is never bigger + // than 75% of the table size + if(!EnlargeHashTableIfMoreThan75PercentUsed(pRootTable, pRootTable->ItemCount + 1)) + return NULL; + + // Get the start index of the table + TableIndex = (DWORD)(pNewEntry->FileNameHash) & (pRootTable->TableSize - 1); + + // If that entry is already occupied, move to a next entry + for(;;) + { + // Check that entry if it's free or not + pEntry = pRootTable->TablePtr + TableIndex; + if(pEntry->SumValue == 0) + break; + + // Is the found entry equal to the existing one? + if(pEntry->FileNameHash == pNewEntry->FileNameHash) + break; + + // Move to the next entry + TableIndex = (TableIndex + 1) & (pRootTable->TableSize - 1); + } + + // Either return a free entry or an existing one + return pEntry; +} + +// WOW6: 004145D1 +static void CascRootTable_InsertTableEntry( + PCASC_ROOT_HASH_TABLE pRootTable, + PCASC_ROOT_ENTRY pNewEntry) +{ + PCASC_ROOT_ENTRY pEntry; + + // Find an existing entry or an empty one + pEntry = CascRootTable_FindFreeEntryWithEnlarge(pRootTable, pNewEntry); + assert(pEntry != NULL); + + // If that entry is not used yet, fill it in + if(pEntry->FileNameHash == 0) + { + *pEntry = *pNewEntry; + pRootTable->ItemCount++; + } +} + +static int LoadWowRootFileLocales( + TCascStorage * hs, + LPBYTE pbRootFile, + DWORD cbRootFile, + DWORD dwLocaleMask, + bool bLoadBlocksWithFlags80, + BYTE HighestBitValue) +{ + CASC_ROOT_ENTRY NewRootEntry; ROOT_BLOCK_INFO BlockInfo; LPBYTE pbRootFileEnd = pbRootFile + cbRootFile; LPBYTE pbFilePointer; - size_t nRootEntries = 0; - size_t nRootIndex = 0; - int nError = ERROR_NOT_ENOUGH_MEMORY; - // Calculate the root entries + // Now parse the root file for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; ) { - // Validate the root block + // Validate the file locale block pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd); if(pbFilePointer == NULL) break; - // Add the number of entries - nRootEntries = nRootEntries + BlockInfo.pLocaleBlockHdr->NumberOfFiles; - } - - // Create a linear array of the root entries and sort it - hs->pRootEntries = pTrgEntry = CASC_ALLOC(CASC_ROOT_ENTRY, nRootEntries); - hs->ppRootEntries = CASC_ALLOC(PCASC_ROOT_ENTRY, nRootEntries); - if(hs->ppRootEntries && hs->pRootEntries) - { - // Convert each entry from FILE_ROOT_ENTRY to CASC_ROOT_ENTRY - for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; ) - { - // Validate the root block - pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd); - if(pbFilePointer == NULL) - break; + // WoW.exe (build 19116): Entries with flag 0x100 set are skipped + if(BlockInfo.pLocaleBlockHdr->Flags & 0x100) + continue; - // Get the pointer to the first root entry - pSrcEntry = (PFILE_ROOT_ENTRY)BlockInfo.pRootEntries; + // WoW.exe (build 19116): Entries with flag 0x80 set are skipped if arg_4 is set to FALSE (which is by default) + if(bLoadBlocksWithFlags80 == 0 && (BlockInfo.pLocaleBlockHdr->Flags & 0x80)) + continue; - // Convert all entries - for(DWORD i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++) - { - // Copy the root entry - CopyFileKey(pTrgEntry->EncodingKey, pSrcEntry->EncodingKey); - pTrgEntry->FileNameHash = pSrcEntry->FileNameHash; - pTrgEntry->Locales = BlockInfo.pLocaleBlockHdr->Locales; - pTrgEntry->Flags = BlockInfo.pLocaleBlockHdr->Flags; + // WoW.exe (build 19116): Entries with (flags >> 0x1F) not equal to arg_8 are skipped + if((BYTE)(BlockInfo.pLocaleBlockHdr->Flags >> 0x1F) != HighestBitValue) + continue; -// if(pTrgEntry->FileNameHash == 0x5ddb88608673f698ULL) -// DebugBreak(); + // WoW.exe (build 19116): Locales other than defined mask are skipped too + if((BlockInfo.pLocaleBlockHdr->Locales & dwLocaleMask) == 0) + continue; - // Insert the CASC root entry to the linear array of pointers - hs->ppRootEntries[nRootIndex++] = pTrgEntry; + // Reset the sum value + NewRootEntry.SumValue = 0; - // Move to the next root entry - pSrcEntry++; - pTrgEntry++; - } + // WoW.exe (build 19116): Blocks with zero files are skipped + for(DWORD i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++) + { + // (004147A3) Prepare the CASC_ROOT_ENTRY structure + NewRootEntry.FileNameHash = BlockInfo.pRootEntries[i].FileNameHash; + NewRootEntry.SumValue = NewRootEntry.SumValue + BlockInfo.pInt32Array[i]; + NewRootEntry.Locales = BlockInfo.pLocaleBlockHdr->Locales; + NewRootEntry.EncodingKey[0] = BlockInfo.pRootEntries[i].EncodingKey[0]; + NewRootEntry.EncodingKey[1] = BlockInfo.pRootEntries[i].EncodingKey[1]; + NewRootEntry.EncodingKey[2] = BlockInfo.pRootEntries[i].EncodingKey[2]; + NewRootEntry.EncodingKey[3] = BlockInfo.pRootEntries[i].EncodingKey[3]; + + // Insert the root table item to the hash table + CascRootTable_InsertTableEntry(&hs->RootTable, &NewRootEntry); + NewRootEntry.SumValue++; } + } - // Save the number of entries - assert(nRootIndex == nRootEntries); - hs->nRootEntries = nRootIndex; + return 1; +} - // Now sort the array - qsort_pointer_array((void **)hs->ppRootEntries, hs->nRootEntries, CompareRootEntries, NULL); - nError = ERROR_SUCCESS; - } +// WoW.exe: 004146C7 (BuildManifest::Load) +static int LoadWowRootFileWithParams( + TCascStorage * hs, + LPBYTE pbRootFile, + DWORD cbRootFile, + DWORD dwLocaleBits, + BYTE HighestBitValue) +{ + // Load the locale as-is + LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, dwLocaleBits, false, HighestBitValue); - return nError; + // If we wanted enGB, we also load enUS for the missing files + if(dwLocaleBits == CASC_LOCALE_ENGB) + LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENUS, false, HighestBitValue); + + if(dwLocaleBits == CASC_LOCALE_PTPT) + LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTBR, false, HighestBitValue); + + return ERROR_SUCCESS; +} /* - FILE * fp = fopen("E:\\root_entries.txt", "wt"); - if(fp != NULL) + // Code from WoW.exe + if(dwLocaleBits == CASC_LOCALE_DUAL_LANG) { - for(size_t i = 0; i < nRootEntries; i++) + // Is this english version of WoW? + if(arg_4 == CASC_LOCALE_BIT_ENUS) + { + LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENGB, false, HighestBitValue); + LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENUS, false, HighestBitValue); + return ERROR_SUCCESS; + } + + // Is this portuguese version of WoW? + if(arg_4 == CASC_LOCALE_BIT_PTBR) { - fprintf(fp, "%08X: %016I64lX\n", i, hs->ppRootEntries[i]->FileNameHash); + LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTPT, false, HighestBitValue); + LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTBR, false, HighestBitValue); } - fclose(fp); } + + LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, (1 << arg_4), false, HighestBitValue); */ + +static int LoadWowRootFile( + TCascStorage * hs, + LPBYTE pbRootFile, + DWORD cbRootFile, + DWORD dwLocaleMask) +{ + int nError; + + // Dump the root file, if needed +#ifdef CASC_DUMP_ROOT_FILE + //CascDumpRootFile(hs, + // pbRootFile, + // cbRootFile, + // "\\casc_root_%build%.txt", + // _T("\\Ladik\\Appdir\\CascLib\\listfile\\listfile-wow6.txt"), + // CASC_DUMP_ROOT_FILE); +#endif + + // Allocate root table entries. Note that the initial size + // of the root table is set to 0x00200000 by World of Warcraft 6.x + hs->RootTable.TablePtr = CASC_ALLOC(CASC_ROOT_ENTRY, CASC_INITIAL_ROOT_TABLE_SIZE); + hs->RootTable.TableSize = CASC_INITIAL_ROOT_TABLE_SIZE; + if(hs->RootTable.TablePtr == NULL) + return ERROR_NOT_ENOUGH_MEMORY; + + // Clear the entire table + memset(hs->RootTable.TablePtr, 0, CASC_INITIAL_ROOT_TABLE_SIZE * sizeof(CASC_ROOT_ENTRY)); + + // Load the root file + nError = LoadWowRootFileWithParams(hs, pbRootFile, cbRootFile, dwLocaleMask, 0); + if(nError != ERROR_SUCCESS) + return nError; + + nError = LoadWowRootFileWithParams(hs, pbRootFile, cbRootFile, dwLocaleMask, 1); + if(nError != ERROR_SUCCESS) + return nError; + + return ERROR_SUCCESS; } -static int LoadRootFile(TCascStorage * hs) +static int LoadRootFile(TCascStorage * hs, DWORD dwLocaleMask) { PDWORD FileSignature; HANDLE hFile = NULL; @@ -952,9 +1100,14 @@ static int LoadRootFile(TCascStorage * hs) int nError = ERROR_SUCCESS; // Sanity checks + assert(hs->RootTable.TablePtr == NULL); + assert(hs->RootTable.ItemCount == 0); assert(hs->ppEncodingEntries != NULL); - assert(hs->pRootEntries == NULL); - assert(hs->nRootEntries == 0); + + // Locale: The default parameter is 0 - in that case, + // we load enUS+enGB + if(dwLocaleMask == 0) + dwLocaleMask = CASC_LOCALE_ENUS | CASC_LOCALE_ENGB; // The root file is either MNDX file (Heroes of the Storm) // or a file containing an array of root entries (World of Warcraft 6.0+) @@ -963,15 +1116,15 @@ static int LoadRootFile(TCascStorage * hs) if(!CascOpenFileByEncodingKey((HANDLE)hs, &hs->RootKey, 0, &hFile)) nError = GetLastError(); - // Load ther entire root file to memory + // Load the entire ROOT file to memory if(nError == ERROR_SUCCESS) { - // Load the entire root file to memory - pbRootFile = LoadCascFile(hFile, 0xFFFFFFFF, &cbRootFile); - if(pbRootFile == NULL || cbRootFile == 0) + // Load the necessary part of the ENCODING file to memory + pbRootFile = LoadRootFileToMemory(hFile, &cbRootFile); + if(pbRootFile == NULL || cbRootFile <= sizeof(PFILE_LOCALE_BLOCK)) nError = ERROR_FILE_CORRUPT; - // Close the root file + // Close the encoding file CascCloseFile(hFile); } @@ -985,7 +1138,8 @@ static int LoadRootFile(TCascStorage * hs) } else { - nError = LoadRootFile(hs, pbRootFile, cbRootFile); + // WOW6: 00415000 + nError = LoadWowRootFile(hs, pbRootFile, cbRootFile, dwLocaleMask); } } @@ -1007,10 +1161,8 @@ static TCascStorage * FreeCascStorage(TCascStorage * hs) FreeMndxInfo(hs->pMndxInfo); // Free the pointers to file entries - if(hs->ppRootEntries != NULL) - CASC_FREE(hs->ppRootEntries); - if(hs->pRootEntries != NULL) - CASC_FREE(hs->pRootEntries); + if(hs->RootTable.TablePtr != NULL) + CASC_FREE(hs->RootTable.TablePtr); if(hs->ppEncodingEntries != NULL) CASC_FREE(hs->ppEncodingEntries); if(hs->pEncodingHeader != NULL) @@ -1074,13 +1226,11 @@ static TCascStorage * FreeCascStorage(TCascStorage * hs) //----------------------------------------------------------------------------- // Public functions -bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * phStorage) +bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwLocaleMask, HANDLE * phStorage) { TCascStorage * hs; int nError = ERROR_SUCCESS; - CASCLIB_UNUSED(dwFlags); - // Allocate the storage structure hs = (TCascStorage *)CASC_ALLOC(TCascStorage, 1); if(hs == NULL) @@ -1092,6 +1242,7 @@ bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * ph // Prepare the base storage parameters memset(hs, 0, sizeof(TCascStorage)); hs->szClassName = "TCascStorage"; + hs->dwFileBeginDelta = 0xFFFFFFFF; hs->dwRefCount = 1; nError = InitializeCascDirectories(hs, szDataPath); } @@ -1099,7 +1250,7 @@ bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * ph // Now we need to load the root file so we know the config files if(nError == ERROR_SUCCESS) { - nError = LoadBuildConfiguration(hs); + nError = LoadBuildInfo(hs); } // Load the index files @@ -1117,17 +1268,9 @@ bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * ph // Load the index files if(nError == ERROR_SUCCESS) { - nError = LoadRootFile(hs); + nError = LoadRootFile(hs, dwLocaleMask); } -#ifdef _DEBUG -// if(nError == ERROR_SUCCESS) -// { -// CascDumpStorage("E:\\casc_dump.txt", hs, _T("e:\\Ladik\\Appdir\\CascLib\\listfile\\listfile-wow6.txt")); -// CascDumpIndexEntries("E:\\casc_index.txt", hs); -// } -#endif - // If something failed, free the storage and return if(nError != ERROR_SUCCESS) { @@ -1147,7 +1290,7 @@ bool WINAPI CascGetStorageInfo( size_t * pcbLengthNeeded) { TCascStorage * hs; - DWORD dwCascFeatures = 0; + DWORD dwInfoValue = 0; // Verify the storage handle hs = IsValidStorageHandle(hStorage); @@ -1161,41 +1304,41 @@ bool WINAPI CascGetStorageInfo( switch(InfoClass) { case CascStorageFileCount: - - // Check the buffer size - if(cbStorageInfo < sizeof(DWORD)) - { - *pcbLengthNeeded = sizeof(DWORD); - SetLastError(ERROR_INSUFFICIENT_BUFFER); - return false; - } - - // Give the number of files - *(PDWORD)pvStorageInfo = (DWORD)hs->pIndexEntryMap->ItemCount; - return true; + dwInfoValue = (DWORD)hs->pIndexEntryMap->ItemCount; + break; case CascStorageFeatures: - - // Check the buffer size - if(cbStorageInfo < sizeof(DWORD)) - { - *pcbLengthNeeded = sizeof(DWORD); - SetLastError(ERROR_INSUFFICIENT_BUFFER); - return false; - } - - // Construct the features if(hs->pMndxInfo != NULL) - dwCascFeatures |= CASC_FEATURE_LISTFILE; + dwInfoValue |= CASC_FEATURE_LISTFILE; + break; + + case CascStorageGameInfo: + dwInfoValue = hs->dwGameInfo; + break; - // Give the number of files - *(PDWORD)pvStorageInfo = dwCascFeatures; - return true; + case CascStorageGameBuild: + dwInfoValue = hs->dwBuildNumber; + break; default: SetLastError(ERROR_INVALID_PARAMETER); return false; } + + // + // Return the required DWORD value + // + + if(cbStorageInfo < sizeof(DWORD)) + { + *pcbLengthNeeded = sizeof(DWORD); + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return false; + } + + // Give the number of files + *(PDWORD)pvStorageInfo = dwInfoValue; + return true; } diff --git a/dep/CascLib/src/CascReadFile.cpp b/dep/CascLib/src/CascReadFile.cpp index 64dd66ef88a..d35da531ec4 100644 --- a/dep/CascLib/src/CascReadFile.cpp +++ b/dep/CascLib/src/CascReadFile.cpp @@ -15,28 +15,11 @@ //----------------------------------------------------------------------------- // Local structures -#define BLTE_HEADER_SIGNATURE 0x45544C42 - -// Data file begin: -// BYTE HeaderHash[MD5_HASH_SIZE]; // MD5 of the frame array -// DWORD dwFileSize; // Size of the file -// BYTE SomeSize[4]; // Some size (big endian) -// BYTE Padding[6]; // Padding (?) - -typedef struct _BLTE_HEADER -{ - DWORD dwSignature; // Must be "BLTE" - BYTE HeaderSizeAsBytes[4]; // Header size in bytes (big endian) - BYTE MustBe0F; // Must be 0x0F - BYTE FrameCount[3]; // Number of frames (big endian) - -} BLTE_HEADER, *PBLTE_HEADER; - 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 frame + BYTE md5[MD5_HASH_SIZE]; // Hash of the compressed frame } BLTE_FRAME, *PBLTE_FRAME; @@ -65,11 +48,6 @@ static int EnsureDataStreamIsOpen(TCascFile * hf) // Open the stream pStream = FileStream_OpenFile(szDataFile, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE); hs->DataFileArray[hf->ArchiveIndex] = pStream; - - // TODO: There is 0x1E bytes at the beginning of the file stream - // Ignore them for now, but we will want to know what they mean - // Offs0000: MD5 of something - // Offs0010: 2 bytes CASC_FREE(szDataFile); } } @@ -79,7 +57,7 @@ static int EnsureDataStreamIsOpen(TCascFile * hf) return (hf->pStream != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND; } -static int LoadFileFrames(TCascFile * hf, DWORD FrameCount) +static int LoadFileFrames(TCascFile * hf) { PBLTE_FRAME pFileFrames; PBLTE_FRAME pFileFrame; @@ -93,40 +71,41 @@ static int LoadFileFrames(TCascFile * hf, DWORD FrameCount) assert(hf->pFrames != NULL); // Allocate frame array - pFileFrames = pFileFrame = CASC_ALLOC(BLTE_FRAME, FrameCount); + pFileFrames = pFileFrame = CASC_ALLOC(BLTE_FRAME, hf->FrameCount); if(pFileFrames != NULL) { // Load the frame array ArchiveFileOffset = hf->FramesOffset; - if(FileStream_Read(hf->pStream, &ArchiveFileOffset, pFileFrames, FrameCount * sizeof(BLTE_FRAME))) + if(FileStream_Read(hf->pStream, &ArchiveFileOffset, pFileFrames, hf->FrameCount * sizeof(BLTE_FRAME))) { // Move the raw archive offset ArchiveFileOffset += (hf->FrameCount * sizeof(BLTE_FRAME)); // Copy the frames to the file structure - for(DWORD i = 0; i < FrameCount; i++, pFileFrame++) + for(DWORD i = 0; i < hf->FrameCount; i++, pFileFrame++) { 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); + 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; } - - // Fill-in the frame count - hf->FrameCount = FrameCount; } else nError = GetLastError(); - // Verify the file size -// assert(FileSize == hf->FileSize); + // Note: Do not take the FileSize from the sum of frames. + // This value is invalid when loading the ENCODING file. // hf->FileSize = FileSize; +#ifdef CASCLIB_TEST + hf->FileSize_FrameSum = FileSize; +#endif + // Free the array CASC_FREE(pFileFrames); } @@ -136,66 +115,115 @@ static int LoadFileFrames(TCascFile * hf, DWORD FrameCount) return nError; } -static int EnsureFrameHeadersLoaded(TCascFile * hf) +static int EnsureHeaderAreaIsLoaded(TCascFile * hf) { - PBLTE_HEADER pBlteHeader; + TCascStorage * hs = hf->hs; ULONGLONG FileOffset = hf->HeaderOffset; - DWORD dwHeaderOffsetFixup = 0; - DWORD dwFrameHeaderSize; - DWORD dwFrameCount; - BYTE HeaderBuffer[sizeof(BLTE_HEADER) + 0x20]; - int nError = ERROR_SUCCESS; - - // Sanity check - assert(hf->pStream != NULL); + 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; - // If the frame headers are not loaded yet, do it - if(hf->pFrames == NULL) + hs->dwFileBeginDelta = (FileSignature == BLTE_HEADER_SIGNATURE) ? BLTE_HEADER_DELTA : 0; + } + + // If the file size is not loaded yet, do it + if(hf->FrameCount == 0) { - // Note that older builds of Heroes of the Storm have entries pointing - // to the begin of the BLTE header, which is MD5 + some junk. - // Newer versions of HOTS have encoding entries pointing directly to - // the BLTE header - FileStream_Read(hf->pStream, &FileOffset, HeaderBuffer, sizeof(HeaderBuffer)); - pBlteHeader = (PBLTE_HEADER)HeaderBuffer; - - // If we don't have the BLTE header right there, - // just get the block that is 0x1E bytes later - if(pBlteHeader->dwSignature != BLTE_HEADER_SIGNATURE) + // 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); + + // Load the size of the frame headers + hf->HeaderSize = ConvertBytesToInteger_4(pbHeaderArea); + if(hf->HeaderSize & 0x80000000) + return ERROR_BAD_FORMAT; + pbHeaderArea += sizeof(DWORD); + + // Read the header size + assert(hs->dwFileBeginDelta <= BLTE_HEADER_DELTA); + hf->HeaderOffset += (BLTE_HEADER_DELTA - hs->dwFileBeginDelta); + hf->FrameCount = 1; + + // Retrieve the frame count, if different from 1 + if(hf->HeaderSize != 0) { - memcpy(&HeaderBuffer[0x00], &HeaderBuffer[0x1E], sizeof(BLTE_HEADER)); - dwHeaderOffsetFixup = 0x1E; + // The next byte must be 0x0F + if(pbHeaderArea[0] != 0x0F) + return ERROR_BAD_FORMAT; + pbHeaderArea++; + + // Next three bytes form number of frames + hf->FrameCount = ConvertBytesToInteger_3(pbHeaderArea); } - // Check for the BLTE header signature - if(pBlteHeader->dwSignature != BLTE_HEADER_SIGNATURE) - return ERROR_BAD_FORMAT; - hf->HeaderOffset += dwHeaderOffsetFixup; +#ifdef CASCLIB_TEST + hf->FileSize_HdrArea = FileSize; +#endif + } + + return ERROR_SUCCESS; +} + +static int EnsureFrameHeadersLoaded(TCascFile * hf) +{ + int nError; - // Check for a single unit file - dwFrameHeaderSize = ConvertBytesToInteger_4(pBlteHeader->HeaderSizeAsBytes); - dwFrameCount = (dwFrameHeaderSize != 0) ? ConvertBytesToInteger_3(pBlteHeader->FrameCount) : 1; + // Make sure we have header area loaded + nError = EnsureHeaderAreaIsLoaded(hf); + if(nError != ERROR_SUCCESS) + return nError; + // If the frame headers are not loaded yet, do it + if(hf->pFrames == NULL) + { // Allocate the frame array - hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, dwFrameCount); + hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, hf->FrameCount); if(hf->pFrames != NULL) { - // Save the number of frames - hf->FrameCount = dwFrameCount; - // Either load the frames from the file or supply them on our own - if(dwFrameHeaderSize != 0) + if(hf->HeaderSize != 0) { - if(pBlteHeader->MustBe0F != 0x0F) - return ERROR_FILE_CORRUPT; - - hf->FramesOffset = hf->HeaderOffset + sizeof(BLTE_HEADER); - nError = LoadFileFrames(hf, dwFrameCount); + 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(pBlteHeader->dwSignature) + sizeof(pBlteHeader->HeaderSizeAsBytes); + hf->FramesOffset = hf->HeaderOffset + sizeof(DWORD) + sizeof(DWORD); hf->pFrames[0].FrameArchiveOffset = hf->FramesOffset; hf->pFrames[0].FrameFileOffset = 0; @@ -239,9 +267,27 @@ static PCASC_FILE_FRAME FindFileFrame(TCascFile * hf, DWORD FilePointer) //----------------------------------------------------------------------------- // Public functions +// +// 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 +// + DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh) { TCascFile * hf; + int nError; CASCLIB_UNUSED(pdwFileSizeHigh); @@ -252,6 +298,14 @@ DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh) return CASC_INVALID_SIZE; } + // Make sure that the file header area is loaded + nError = EnsureHeaderAreaIsLoaded(hf); + if(nError != ERROR_SUCCESS) + { + SetLastError(nError); + return CASC_INVALID_SIZE; + } + // Give the file size to the caller if(pdwFileSizeHigh != NULL) *pdwFileSizeHigh = 0; @@ -348,23 +402,17 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW return false; } - // If the file position is at or beyond end of file, do nothing - if(hf->FilePointer >= hf->FileSize) - { - *pdwBytesRead = 0; - return ERROR_SUCCESS; - } - - // Make sure we have that data file open + // If the file frames are not loaded yet, do it now if(nError == ERROR_SUCCESS) { - nError = EnsureDataStreamIsOpen(hf); + nError = EnsureFrameHeadersLoaded(hf); } - // If the file frames are not loaded yet, do it now - if(nError == ERROR_SUCCESS) + // If the file position is at or beyond end of file, do nothing + if(nError == ERROR_SUCCESS && hf->FilePointer >= hf->FileSize) { - nError = EnsureFrameHeadersLoaded(hf); + *pdwBytesRead = 0; + return ERROR_SUCCESS; } // Find the file frame where to read from @@ -423,7 +471,7 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW } // Verify the block MD5 - if(IsValidMD5(pFrame->md5) && !VerifyDataBlockHash(pbRawData, pFrame->CompressedSize, pFrame->md5)) + if(!VerifyDataBlockHash(pbRawData, pFrame->CompressedSize, pFrame->md5)) { CASC_FREE(pbRawData); nError = ERROR_FILE_CORRUPT; diff --git a/dep/CascLib/src/common/Common.cpp b/dep/CascLib/src/common/Common.cpp index d81c1b6fd89..74f86d44853 100644 --- a/dep/CascLib/src/common/Common.cpp +++ b/dep/CascLib/src/common/Common.cpp @@ -62,20 +62,6 @@ unsigned char AsciiToUpperTable[256] = unsigned char IntToHexChar[] = "0123456789abcdef"; //----------------------------------------------------------------------------- -// Support for memory reallocation - -#if defined(_MSC_VER) && defined(_DEBUG) -void * DbgRealloc(void * ptr, size_t nSize) -{ - // HeapReAlloc does not support NULL as previous block - if(ptr == NULL) - return HeapAlloc(GetProcessHeap, 0, nSize); - - return HeapReAlloc(GetProcessHeap(), 0, ptr, nSize); -} -#endif - -//----------------------------------------------------------------------------- // GetLastError/SetLastError support for non-Windows platform #ifndef PLATFORM_WINDOWS @@ -232,11 +218,14 @@ TCHAR * CombinePath(const TCHAR * szDirectory, const TCHAR * szSubDir) return szFullPath; } -void NormalizeFileName_UpperBkSlash(char * szFileName) +void NormalizeFileName_UpperBkSlash(const char * szSrcFileName, char * szTrgFileName) { + size_t i; + // Normalize the file name: ToLower + BackSlashToSlash - for(size_t i = 0; szFileName[i] != 0; i++) - szFileName[i] = AsciiToUpperTable[szFileName[i]]; + for(i = 0; szSrcFileName[i] != 0; i++) + szTrgFileName[i] = AsciiToUpperTable[szSrcFileName[i]]; + szTrgFileName[i] = 0; } void NormalizeFileName_LowerSlash(char * szFileName) diff --git a/dep/CascLib/src/common/Common.h b/dep/CascLib/src/common/Common.h index eb192fd50ca..40712b99eed 100644 --- a/dep/CascLib/src/common/Common.h +++ b/dep/CascLib/src/common/Common.h @@ -29,13 +29,6 @@ extern unsigned char AsciiToUpperTable[256]; extern unsigned char IntToHexChar[]; //----------------------------------------------------------------------------- -// Memory management helper - -#if defined(_MSC_VER) && defined(_DEBUG) -void * DbgRealloc(void * ptr, size_t nSize); -#endif - -//----------------------------------------------------------------------------- // GetLastError/SetLastError support for non-Windows platform #ifndef PLATFORM_WINDOWS @@ -57,7 +50,7 @@ TCHAR * NewStrFromAnsi(LPBYTE pbStringBegin, LPBYTE pbStringEnd); TCHAR * CombinePath(const TCHAR * szPath, const TCHAR * szSubDir); -void NormalizeFileName_UpperBkSlash(char * szFileName); +void NormalizeFileName_UpperBkSlash(const char * szSrcFileName, char * szTrgFileName); void NormalizeFileName_LowerSlash(char * szFileName); int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue); diff --git a/dep/CascLib/src/common/FileStream.cpp b/dep/CascLib/src/common/FileStream.cpp index 09ac47bd095..8e541b44fca 100644 --- a/dep/CascLib/src/common/FileStream.cpp +++ b/dep/CascLib/src/common/FileStream.cpp @@ -2551,6 +2551,14 @@ bool FileStream_SetCallback(TFileStream * pStream, STREAM_DOWNLOAD_CALLBACK pfnC */ bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead) { + //FILE * fp = fopen("E:\\Loading.txt", "at"); + //if(fp != NULL) + //{ + // ULONGLONG ByteOffset = (pByteOffset != NULL) ? pByteOffset[0] : 0; + // fprintf(fp, "%-32ws\t%08X\t%08X\n", GetPlainFileName(pStream->szFileName), (ULONG)ByteOffset, dwBytesToRead); + // fclose(fp); + //} + assert(pStream->StreamRead != NULL); return pStream->StreamRead(pStream, pByteOffset, pvBuffer, dwBytesToRead); } diff --git a/dep/CascLib/src/common/ListFile.cpp b/dep/CascLib/src/common/ListFile.cpp index 4b742b1d07c..8e0b1374241 100644 --- a/dep/CascLib/src/common/ListFile.cpp +++ b/dep/CascLib/src/common/ListFile.cpp @@ -125,12 +125,12 @@ static size_t ReadListFileLine(TListFileCache * pCache, char * szLine, size_t nM } // If we have found a newline, stop loading - if(*pCache->pPos == 0x0D || *pCache->pPos == 0x0A) + if(pCache->pPos[0] == 0x0D || pCache->pPos[0] == 0x0A) break; // Blizzard listfiles can also contain information about patch: // Pass1\Files\MacOS\unconditional\user\Background Downloader.app\Contents\Info.plist~Patch(Data#frFR#base-frFR,1326) - if(*pCache->pPos == '~') + if(pCache->pPos[0] == '~') szExtraString = szLine; // Copy the character @@ -190,7 +190,7 @@ static TListFileCache * CreateListFileCache(RELOAD_CACHE pfnReloadCache, CLOSE_S } //----------------------------------------------------------------------------- -// Listfile functions +// Functions for parsing an external listfile void * ListFile_OpenExternal(const TCHAR * szListFile) { @@ -264,3 +264,160 @@ void ListFile_Free(void * pvListFile) CASC_FREE(pCache); } } + +//----------------------------------------------------------------------------- +// 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; + char szFileName2[MAX_PATH+1]; + size_t cbToAllocate; + size_t cbEntrySize; + uint32_t dwHashHigh = 0; + uint32_t dwHashLow = 0; + + // 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); + + // Get the name hash + NormalizeFileName_UpperBkSlash(szFileName, szFileName2); + hashlittle2(szFileName2, nLength, &dwHashHigh, &dwHashLow); + + // Calculate the HASH value of the normalized file name + pListEntry->FileNameHash = ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow; + pListEntry->cbEntrySize = (DWORD)cbEntrySize; + 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); + } + + return pListMap; +} + +PLISTFILE_MAP ListFile_CreateMap(const TCHAR * szListFile) +{ + PLISTFILE_MAP pListMap = NULL; + void * pvListFile; + char szFileName[MAX_PATH+1]; + size_t nLength; + + // Only if the listfile name has been given + if(szListFile != 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; + } + + // Finish the listfile map + pListMap = ListMap_Finish(pListMap); + + // Free the listfile + ListFile_Free(pvListFile); + } + } + } + + // 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); + return (pListEntry != NULL) ? pListEntry->szFileName : ""; +} + +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 1c603af3766..9815160e1ea 100644 --- a/dep/CascLib/src/common/ListFile.h +++ b/dep/CascLib/src/common/ListFile.h @@ -11,8 +11,40 @@ #ifndef __LISTFILE_H__ #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); 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); + #endif // __LISTFILE_H__ diff --git a/dep/CascLib/src/common/Map.h b/dep/CascLib/src/common/Map.h index b4b9c918df3..40ea4238b81 100644 --- a/dep/CascLib/src/common/Map.h +++ b/dep/CascLib/src/common/Map.h @@ -14,7 +14,7 @@ //----------------------------------------------------------------------------- // Structures -#define KEY_LENGTH_STRING 0xFFFFFFFF // Pass this to Map_Create as dwKeyLength when you want map of string->object +#define KEY_LENGTH_STRING 0xFFFFFFFF // Pass this to Map_Create as dwKeyLength when you want map of string->object typedef struct _CASC_MAP { diff --git a/dep/PackageList.txt b/dep/PackageList.txt index 637988142f2..074274a621e 100644 --- a/dep/PackageList.txt +++ b/dep/PackageList.txt @@ -38,7 +38,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: 3e3f4f443cb7b2893cbbe60b0a6efafebf48bc40 + Version: 5d3789af3435534c288c2145e158d422651c7fe1 zmqpp (C++ binding for 0mq/zmq is a 'high-level' library that hides most of the c-style interface core 0mq provides.) https://github.com/zeromq/zmqpp |