aboutsummaryrefslogtreecommitdiff
path: root/dep/CascLib/src/CascRootFile_WoW.cpp
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2019-06-06 16:48:21 +0200
committerShauren <shauren.trinity@gmail.com>2019-06-08 17:09:24 +0200
commitfc330fd8ff0115804d9c4b53a1f810c00dd63de9 (patch)
treecfa10998fed66779834bf0b7a9b8b799d33d91d4 /dep/CascLib/src/CascRootFile_WoW.cpp
parent82c7b6c5688495d90c4ee5995a4ff74039348296 (diff)
Dep/CascLib: Update to ladislav-zezula/CascLib@a1197edf0b3bd4d52c3f39be7fa7b44bb0b98012
Diffstat (limited to 'dep/CascLib/src/CascRootFile_WoW.cpp')
-rw-r--r--dep/CascLib/src/CascRootFile_WoW.cpp481
1 files changed, 481 insertions, 0 deletions
diff --git a/dep/CascLib/src/CascRootFile_WoW.cpp b/dep/CascLib/src/CascRootFile_WoW.cpp
new file mode 100644
index 00000000000..780220bbe40
--- /dev/null
+++ b/dep/CascLib/src/CascRootFile_WoW.cpp
@@ -0,0 +1,481 @@
+/*****************************************************************************/
+/* CascRootFile_WoW.cpp Copyright (c) Ladislav Zezula 2014 */
+/*---------------------------------------------------------------------------*/
+/* Storage functions for CASC */
+/* Note: WoW offsets refer to WoW.exe 6.0.3.19116 (32-bit) */
+/* SHA1: c10e9ffb7d040a37a356b96042657e1a0c95c0dd */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 29.04.14 1.00 Lad The first version of CascRootFile_WoW.cpp */
+/*****************************************************************************/
+
+#define __CASCLIB_SELF__
+#include "CascLib.h"
+#include "CascCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local structures
+
+#define ROOT_SEARCH_PHASE_INITIALIZING 0
+#define ROOT_SEARCH_PHASE_LISTFILE 1
+#define ROOT_SEARCH_PHASE_NAMELESS 2
+#define ROOT_SEARCH_PHASE_FINISHED 3
+
+// Known dwRegion values returned from sub_661316 (7.0.3.22210 x86 win), also referred by lua GetCurrentRegion
+#define WOW_REGION_US 0x01
+#define WOW_REGION_KR 0x02
+#define WOW_REGION_EU 0x03
+#define WOW_REGION_TW 0x04
+#define WOW_REGION_CN 0x05
+
+typedef enum _ROOT_FORMAT
+{
+ RootFormatWoW6x, // WoW 6.x - 8.1.x
+ RootFormatWoW82 // WoW 8.2 or newer
+} ROOT_FORMAT, *PROOT_FORMAT;
+
+// ROOT file header, since 8.2
+typedef struct _FILE_ROOT_HEADER_82
+{
+ DWORD Signature; // Must be CASC_WOW82_ROOT_SIGNATURE
+ DWORD TotalFiles;
+ DWORD FilesWithNameHash;
+} FILE_ROOT_HEADER_82, *PFILE_ROOT_HEADER_82;
+
+// On-disk version of root group. A root group contains a group of file
+// with the same locale and file flags
+typedef struct _FILE_ROOT_GROUP_HEADER
+{
+ DWORD NumberOfFiles; // Number of entries
+ DWORD ContentFlags;
+ DWORD LocaleFlags; // File locale mask (CASC_LOCALE_XXX)
+
+ // Followed by a block of file data IDs (count: NumberOfFiles)
+ // Followed by the MD5 and file name hash (count: NumberOfFiles)
+
+} FILE_ROOT_GROUP_HEADER, *PFILE_ROOT_GROUP_HEADER;
+
+// On-disk version of root entry. Only present in versions 6.x - 8.1.xx
+// Each root entry represents one file in the CASC storage
+// In WoW 8.2 and newer, CKey and FileNameHash are split into separate arrays
+// and FileNameHash is optional
+typedef struct _FILE_ROOT_ENTRY
+{
+ CONTENT_KEY CKey; // MD5 of the file
+ ULONGLONG FileNameHash; // Jenkins hash of the file name
+
+} FILE_ROOT_ENTRY, *PFILE_ROOT_ENTRY;
+
+typedef struct _FILE_ROOT_GROUP
+{
+ FILE_ROOT_GROUP_HEADER Header;
+ PDWORD FileDataIds; // Pointer to the array of File Data IDs
+
+ PFILE_ROOT_ENTRY pRootEntries; // Valid for WoW 6.x - 8.1.x
+ PCONTENT_KEY pCKeyEntries; // Valid for WoW 8.2 or newer
+ PULONGLONG pHashes; // Valid for WoW 8.2 or newer (optional)
+
+} FILE_ROOT_GROUP, *PFILE_ROOT_GROUP;
+
+//-----------------------------------------------------------------------------
+// TRootHandler_WoW interface / implementation
+
+#define FTREE_FLAGS_WOW (FTREE_FLAG_USE_DATA_ID | FTREE_FLAG_USE_LOCALE_FLAGS | FTREE_FLAG_USE_CONTENT_FLAGS)
+
+struct TRootHandler_WoW : public TFileTreeRoot
+{
+ public:
+
+ TRootHandler_WoW(ROOT_FORMAT RFormat, DWORD HashlessFileCount) : TFileTreeRoot(FTREE_FLAGS_WOW)
+ {
+ // Turn off the "we know file names" bit
+ FileCounterHashless = HashlessFileCount;
+ FileCounter = 0;
+ RootFormat = RFormat;
+
+ // Update the flags based on format
+ switch(RootFormat)
+ {
+ case RootFormatWoW6x:
+ dwFeatures |= CASC_FEATURE_ROOT_CKEY | CASC_FEATURE_FNAME_HASHES | CASC_FEATURE_FILE_DATA_IDS | CASC_FEATURE_LOCALE_FLAGS | CASC_FEATURE_CONTENT_FLAGS;
+ break;
+
+ case RootFormatWoW82:
+ dwFeatures |= CASC_FEATURE_ROOT_CKEY | CASC_FEATURE_FNAME_HASHES_OPTIONAL | CASC_FEATURE_FILE_DATA_IDS | CASC_FEATURE_LOCALE_FLAGS | CASC_FEATURE_CONTENT_FLAGS;
+ break;
+ }
+ }
+
+ static LPBYTE CaptureRootHeader(FILE_ROOT_HEADER_82 & RootHeader, LPBYTE pbRootPtr, LPBYTE pbRootEnd)
+ {
+ // Validate the root file header
+ if((pbRootPtr + sizeof(FILE_ROOT_HEADER_82)) >= pbRootEnd)
+ return NULL;
+ memcpy(&RootHeader, pbRootPtr, sizeof(FILE_ROOT_HEADER_82));
+
+ // Verify the root file header
+ if(RootHeader.Signature != CASC_WOW82_ROOT_SIGNATURE)
+ return NULL;
+ if(RootHeader.FilesWithNameHash > RootHeader.TotalFiles)
+ return NULL;
+
+ return pbRootPtr + sizeof(FILE_ROOT_HEADER_82);
+ }
+
+ LPBYTE CaptureRootGroup(FILE_ROOT_GROUP & RootGroup, LPBYTE pbRootPtr, LPBYTE pbRootEnd)
+ {
+ // Reset the entire root group structure
+ memset(&RootGroup, 0, sizeof(FILE_ROOT_GROUP));
+
+ // Validate the locale block header
+ if((pbRootPtr + sizeof(FILE_ROOT_GROUP_HEADER)) >= pbRootEnd)
+ return NULL;
+ memcpy(&RootGroup.Header, pbRootPtr, sizeof(FILE_ROOT_GROUP_HEADER));
+ pbRootPtr = pbRootPtr + sizeof(FILE_ROOT_GROUP_HEADER);
+
+ // Validate the array of file data IDs
+ if((pbRootPtr + (sizeof(DWORD) * RootGroup.Header.NumberOfFiles)) >= pbRootEnd)
+ return NULL;
+ RootGroup.FileDataIds = (PDWORD)pbRootPtr;
+ pbRootPtr = pbRootPtr + (sizeof(DWORD) * RootGroup.Header.NumberOfFiles);
+
+ // Add the number of files in this block to the number of files loaded
+ FileCounter += RootGroup.Header.NumberOfFiles;
+
+ // Validate the array of root entries
+ switch(RootFormat)
+ {
+ case RootFormatWoW6x:
+ if((pbRootPtr + (sizeof(FILE_ROOT_ENTRY) * RootGroup.Header.NumberOfFiles)) > pbRootEnd)
+ return NULL;
+ RootGroup.pRootEntries = (PFILE_ROOT_ENTRY)pbRootPtr;
+
+ // Return the position of the next block
+ return pbRootPtr + (sizeof(FILE_ROOT_ENTRY) * RootGroup.Header.NumberOfFiles);
+
+ case RootFormatWoW82:
+
+ // Verify the position of array of CONTENT_KEY
+ if((pbRootPtr + (sizeof(CONTENT_KEY) * RootGroup.Header.NumberOfFiles)) > pbRootEnd)
+ return NULL;
+ RootGroup.pCKeyEntries = (PCONTENT_KEY)pbRootPtr;
+ pbRootPtr = pbRootPtr + (sizeof(CONTENT_KEY) * RootGroup.Header.NumberOfFiles);
+
+ // Also include array of file hashes
+ if(FileCounter > FileCounterHashless)
+ {
+ if((pbRootPtr + (sizeof(ULONGLONG) * RootGroup.Header.NumberOfFiles)) > pbRootEnd)
+ return NULL;
+ RootGroup.pHashes = (PULONGLONG)pbRootPtr;
+ pbRootPtr = pbRootPtr + (sizeof(ULONGLONG) * RootGroup.Header.NumberOfFiles);
+ }
+
+ return pbRootPtr;
+
+ default:
+ return NULL;
+ }
+ }
+
+ int ParseWowRootFile_AddFiles_6x(TCascStorage * hs, FILE_ROOT_GROUP & RootGroup)
+ {
+ PFILE_ROOT_ENTRY pRootEntry = RootGroup.pRootEntries;
+ PCASC_CKEY_ENTRY pCKeyEntry;
+ DWORD FileDataId = 0;
+
+ // Sanity check
+ assert(RootGroup.pRootEntries != NULL);
+
+ // WoW.exe (build 19116): Blocks with zero files are skipped
+ for(DWORD i = 0; i < RootGroup.Header.NumberOfFiles; i++, pRootEntry++)
+ {
+ // Set the file data ID
+ FileDataId = FileDataId + RootGroup.FileDataIds[i];
+// BREAKIF(FileDataId == 2823765);
+
+ // Find the item in the central storage. Insert it to the tree
+ if((pCKeyEntry = FindCKeyEntry_CKey(hs, pRootEntry->CKey.Value)) != NULL)
+ {
+ if(pRootEntry->FileNameHash != 0)
+ {
+ FileTree.InsertByHash(pCKeyEntry, pRootEntry->FileNameHash, FileDataId, RootGroup.Header.LocaleFlags, RootGroup.Header.ContentFlags);
+ }
+ else
+ {
+ FileTree.InsertById(pCKeyEntry, FileDataId, RootGroup.Header.LocaleFlags, RootGroup.Header.ContentFlags);
+ }
+ }
+
+ // Update the file data ID
+ assert((FileDataId + 1) > FileDataId);
+ FileDataId++;
+ }
+
+ return ERROR_SUCCESS;
+ }
+
+ int ParseWowRootFile_AddFiles_82(TCascStorage * hs, FILE_ROOT_GROUP & RootGroup)
+ {
+ PCASC_CKEY_ENTRY pCKeyEntry;
+ PCONTENT_KEY pCKey = RootGroup.pCKeyEntries;
+ DWORD FileDataId = 0;
+
+ // Sanity check
+ assert(RootGroup.pCKeyEntries != NULL);
+
+ // WoW.exe (build 19116): Blocks with zero files are skipped
+ for(DWORD i = 0; i < RootGroup.Header.NumberOfFiles; i++, pCKey++)
+ {
+ // Set the file data ID
+ FileDataId = FileDataId + RootGroup.FileDataIds[i];
+
+ // Find the item in the central storage. Insert it to the tree
+ if((pCKeyEntry = FindCKeyEntry_CKey(hs, pCKey->Value)) != NULL)
+ {
+ // If we know the file name hash, we're gonna insert it by hash AND file data id.
+ // If we don't know the hash, we're gonna insert it just by file data id.
+ if(RootGroup.pHashes != NULL && RootGroup.pHashes[i] != 0)
+ {
+ FileTree.InsertByHash(pCKeyEntry, RootGroup.pHashes[i], FileDataId, RootGroup.Header.LocaleFlags, RootGroup.Header.ContentFlags);
+ }
+ else
+ {
+ FileTree.InsertById(pCKeyEntry, FileDataId, RootGroup.Header.LocaleFlags, RootGroup.Header.ContentFlags);
+ }
+ }
+
+ // Update the file data ID
+ assert((FileDataId + 1) > FileDataId);
+ FileDataId++;
+ }
+
+ return ERROR_SUCCESS;
+ }
+
+ int ParseWowRootFile_Level2(
+ TCascStorage * hs,
+ LPBYTE pbRootPtr,
+ LPBYTE pbRootEnd,
+ DWORD dwLocaleMask,
+ BYTE bOverrideLowViolence,
+ BYTE bAudioLocale)
+ {
+ FILE_ROOT_GROUP RootBlock;
+
+ // Reset the total file counter
+ FileCounter = 0;
+
+ // Now parse the root file
+ while(pbRootPtr < pbRootEnd)
+ {
+ // Validate the file locale block
+ pbRootPtr = CaptureRootGroup(RootBlock, pbRootPtr, pbRootEnd);
+ if(pbRootPtr == NULL)
+ return ERROR_BAD_FORMAT;
+
+ // WoW.exe (build 19116): Entries with flag 0x100 set are skipped
+ if(RootBlock.Header.ContentFlags & CASC_CFLAG_DONT_LOAD)
+ continue;
+
+ // WoW.exe (build 19116): Entries with flag 0x80 set are skipped if overrideArchive CVAR is set to FALSE (which is by default in non-chinese clients)
+ if((RootBlock.Header.ContentFlags & CASC_CFLAG_LOW_VIOLENCE) && bOverrideLowViolence == 0)
+ continue;
+
+ // WoW.exe (build 19116): Entries with (flags >> 0x1F) not equal to bAudioLocale are skipped
+ if((RootBlock.Header.ContentFlags >> 0x1F) != bAudioLocale)
+ continue;
+
+ // WoW.exe (build 19116): Locales other than defined mask are skipped too
+ if((RootBlock.Header.LocaleFlags & dwLocaleMask) == 0)
+ continue;
+
+ // Now call the custom function
+ switch(RootFormat)
+ {
+ case RootFormatWoW82:
+ ParseWowRootFile_AddFiles_82(hs, RootBlock);
+ break;
+
+ case RootFormatWoW6x:
+ ParseWowRootFile_AddFiles_6x(hs, RootBlock);
+ break;
+
+ default:
+ return ERROR_NOT_SUPPORTED;
+ }
+ }
+
+ return ERROR_SUCCESS;
+ }
+
+ /*
+ #define CASC_LOCALE_BIT_ENUS 0x01
+ #define CASC_LOCALE_BIT_KOKR 0x02
+ #define CASC_LOCALE_BIT_RESERVED 0x03
+ #define CASC_LOCALE_BIT_FRFR 0x04
+ #define CASC_LOCALE_BIT_DEDE 0x05
+ #define CASC_LOCALE_BIT_ZHCN 0x06
+ #define CASC_LOCALE_BIT_ESES 0x07
+ #define CASC_LOCALE_BIT_ZHTW 0x08
+ #define CASC_LOCALE_BIT_ENGB 0x09
+ #define CASC_LOCALE_BIT_ENCN 0x0A
+ #define CASC_LOCALE_BIT_ENTW 0x0B
+ #define CASC_LOCALE_BIT_ESMX 0x0C
+ #define CASC_LOCALE_BIT_RURU 0x0D
+ #define CASC_LOCALE_BIT_PTBR 0x0E
+ #define CASC_LOCALE_BIT_ITIT 0x0F
+ #define CASC_LOCALE_BIT_PTPT 0x10
+
+ // dwLocale is obtained from a WOW_LOCALE_* to CASC_LOCALE_BIT_* mapping (sub_6615D0 in 7.0.3.22210 x86 win)
+ // because (ENUS, ENGB) and (PTBR, PTPT) pairs share the same value on WOW_LOCALE_* enum
+ // dwRegion is used to distinguish them
+ if(dwRegion == WOW_REGION_EU)
+ {
+ // Is this english version of WoW?
+ if(dwLocale == CASC_LOCALE_BIT_ENUS)
+ {
+ LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, CASC_LOCALE_ENGB, bOverrideArchive, bAudioLocale);
+ LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, CASC_LOCALE_ENUS, bOverrideArchive, bAudioLocale);
+ return ERROR_SUCCESS;
+ }
+
+ // Is this portuguese version of WoW?
+ if(dwLocale == CASC_LOCALE_BIT_PTBR)
+ {
+ LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, CASC_LOCALE_PTPT, bOverrideArchive, bAudioLocale);
+ LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, CASC_LOCALE_PTBR, bOverrideArchive, bAudioLocale);
+ }
+ }
+ else
+ LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, (1 << dwLocale), bOverrideArchive, bAudioLocale);
+ */
+
+ int ParseWowRootFile_Level1(
+ TCascStorage * hs,
+ LPBYTE pbRootPtr,
+ LPBYTE pbRootEnd,
+ DWORD dwLocaleMask,
+ BYTE bAudioLocale)
+ {
+ int nError;
+
+ // Load the locale as-is
+ nError = ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, dwLocaleMask, false, bAudioLocale);
+ if (nError != ERROR_SUCCESS)
+ return nError;
+
+ // If we wanted enGB, we also load enUS for the missing files
+ if(dwLocaleMask == CASC_LOCALE_ENGB)
+ ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, CASC_LOCALE_ENUS, false, bAudioLocale);
+
+ if(dwLocaleMask == CASC_LOCALE_PTPT)
+ ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, CASC_LOCALE_PTBR, false, bAudioLocale);
+
+ return ERROR_SUCCESS;
+ }
+
+ // WoW.exe: 004146C7 (BuildManifest::Load)
+ int Load(TCascStorage * hs, LPBYTE pbRootPtr, LPBYTE pbRootEnd, DWORD dwLocaleMask)
+ {
+ int nError;
+
+ nError = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 0);
+ if (nError == ERROR_SUCCESS)
+ nError = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 1);
+
+ return nError;
+ }
+
+ // Search for files
+ PCASC_CKEY_ENTRY Search(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
+ {
+ // If we have a listfile, we'll feed the listfile entries to the file tree
+ if(pSearch->pCache != NULL && pSearch->bListFileUsed == false)
+ {
+ PCASC_FILE_NODE pFileNode;
+ ULONGLONG FileNameHash;
+ DWORD FileDataId = CASC_INVALID_ID;
+ char szFileName[MAX_PATH];
+
+ if(RootFormat == RootFormatWoW82)
+ {
+ // Keep going through the listfile
+ while(ListFile_GetNext(pSearch->pCache, szFileName, _countof(szFileName), &FileDataId))
+ {
+ // Try to find the file node by file data id
+ pFileNode = FileTree.FindById(FileDataId);
+ if(pFileNode != NULL && pFileNode->NameLength == 0)
+ {
+ FileTree.SetNodeFileName(pFileNode, szFileName);
+ }
+ }
+ }
+ else
+ {
+ // Keep going through the listfile
+ while(ListFile_GetNextLine(pSearch->pCache, szFileName, MAX_PATH))
+ {
+ // Calculate the hash of the file name
+ FileNameHash = CalcFileNameHash(szFileName);
+
+ // Try to find the file node by file name hash
+ pFileNode = FileTree.Find(FileNameHash);
+ if(pFileNode != NULL && pFileNode->NameLength == 0)
+ {
+ FileTree.SetNodeFileName(pFileNode, szFileName);
+ }
+ }
+ }
+ pSearch->bListFileUsed = true;
+ }
+
+ // Let the file tree root give us the file names
+ return TFileTreeRoot::Search(pSearch, pFindData);
+ }
+
+ ROOT_FORMAT RootFormat; // Root file format
+ DWORD FileCounterHashless; // Number of files for which we don't have hash. Meaningless for WoW before 8.2
+ DWORD FileCounter; // Counter of loaded files. Only used during loading of ROOT file
+};
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+int RootHandler_CreateWoW(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask)
+{
+ TRootHandler_WoW * pRootHandler = NULL;
+ FILE_ROOT_HEADER_82 RootHeader;
+ ROOT_FORMAT RootFormat = RootFormatWoW6x;
+ LPBYTE pbRootEnd = pbRootFile + cbRootFile;
+ LPBYTE pbRootPtr;
+ DWORD FileCounterHashless = 0;
+ int nError = ERROR_BAD_FORMAT;
+
+ // Check for the new format (World of Warcraft 8.2, build 30170)
+ pbRootPtr = TRootHandler_WoW::CaptureRootHeader(RootHeader, pbRootFile, pbRootEnd);
+ if(pbRootPtr != NULL)
+ {
+ FileCounterHashless = RootHeader.TotalFiles - RootHeader.FilesWithNameHash;
+ RootFormat = RootFormatWoW82;
+ pbRootFile = pbRootPtr;
+ }
+
+ // Create the WOW handler
+ pRootHandler = new TRootHandler_WoW(RootFormat, FileCounterHashless);
+ if(pRootHandler != NULL)
+ {
+ // Load the root directory. If load failed, we free the object
+ nError = pRootHandler->Load(hs, pbRootFile, pbRootEnd, dwLocaleMask);
+ if(nError != ERROR_SUCCESS)
+ {
+ delete pRootHandler;
+ pRootHandler = NULL;
+ }
+ }
+
+ // Assign the root directory (or NULL) and return error
+ hs->pRootHandler = pRootHandler;
+ return nError;
+}
+