This commit is contained in:
Shauren
2025-02-28 17:41:28 +01:00
parent 438d0c3089
commit 464d8e39e0
14 changed files with 237 additions and 100 deletions

View File

@@ -23,6 +23,7 @@
#if defined(_DEBUG) && !defined(CASCLIB_NODEBUG)
#define CASCLIB_DEBUG
//#define CASCLIB_WRITE_VERIFIED_FILENAMES // If defined, TRootHandler_WoW will save all files whose hashes are confirmed
#endif
#include "CascPort.h"
@@ -109,14 +110,13 @@ typedef struct _CASC_BUILD_FILE
} CASC_BUILD_FILE, *PCASC_BUILD_FILE;
// Information about index file
struct CASC_INDEX
typedef struct _CASC_INDEX
{
CASC_BLOB FileData;
LPTSTR szFileName; // Full name of the index file
DWORD NewSubIndex; // New subindex
DWORD OldSubIndex; // Old subindex
};
typedef CASC_INDEX * PCASC_INDEX;
} CASC_INDEX, *PCASC_INDEX;
// Normalized header of the index files.
// Both version 1 and version 2 are converted to this structure
@@ -277,6 +277,22 @@ struct TCascStorage
hs->ClassName == CASC_MAGIC_STORAGE) ? hs : NULL;
}
DWORD SetProductCodeName(LPCSTR szNewCodeName, size_t nLength = 0)
{
if(szCodeName == NULL && szNewCodeName != NULL)
{
// Make sure we have the length
if(nLength == 0)
nLength = strlen(szNewCodeName);
// Allocate the code name buffer and copy from ANSI string
if((szCodeName = CASC_ALLOC<TCHAR>(nLength + 1)) == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
CascStrCopy(szCodeName, nLength + 1, szNewCodeName, nLength);
}
return ERROR_SUCCESS;
}
// Class recognizer. Has constant value of 'CASCSTOR' (CASC_MAGIC_STORAGE)
ULONGLONG ClassName;
@@ -456,7 +472,7 @@ inline void FreeCascBlob(PCASC_BLOB pBlob)
//-----------------------------------------------------------------------------
// Text file parsing (CascFiles.cpp)
bool InvokeProgressCallback(TCascStorage * hs, LPCSTR szMessage, LPCSTR szObject, DWORD CurrentValue, DWORD TotalValue);
bool InvokeProgressCallback(TCascStorage * hs, CASC_PROGRESS_MSG Message, LPCSTR szObject, DWORD CurrentValue, DWORD TotalValue);
DWORD GetFileSpanInfo(PCASC_CKEY_ENTRY pCKeyEntry, PULONGLONG PtrContentSize, PULONGLONG PtrEncodedSize = NULL);
DWORD FetchCascFile(TCascStorage * hs, CPATH_TYPE PathType, LPBYTE pbEKey, LPCTSTR szExtension, CASC_PATH<TCHAR> & LocalPath, PCASC_ARCHIVE_INFO pArchiveInfo = NULL);
DWORD CheckCascBuildFileExact(CASC_BUILD_FILE & BuildFile, LPCTSTR szLocalPath);

View File

@@ -496,16 +496,7 @@ static DWORD LoadVfsRootEntry(TCascStorage * hs, const char * szVariableName, co
static DWORD LoadBuildProductId(TCascStorage * hs, const char * /* szVariableName */, const char * szDataBegin, const char * szDataEnd, void * /* pvParam */)
{
size_t nLength = (szDataEnd - szDataBegin);
if(hs->szCodeName == NULL)
{
if((hs->szCodeName = CASC_ALLOC<TCHAR>(nLength + 1)) != NULL)
{
CascStrCopy(hs->szCodeName, nLength + 1, szDataBegin, nLength);
}
}
hs->SetProductCodeName(szDataBegin, (szDataEnd - szDataBegin));
return ERROR_SUCCESS;
}
@@ -559,14 +550,6 @@ static int LoadQueryKey(const CASC_CSV_COLUMN & Column, CASC_BLOB & Key)
return LoadHashArray(&Key, Column.szValue, Column.szValue + Column.nLength, 1);
}
static void SetProductCodeName(TCascStorage * hs, LPCSTR szCodeName)
{
if(hs->szCodeName == NULL && szCodeName != NULL)
{
hs->szCodeName = CascNewStrA2T(szCodeName);
}
}
static DWORD GetDefaultCdnServers(TCascStorage * hs, const CASC_CSV_COLUMN & Column)
{
if(hs->szCdnServers == NULL && Column.nLength != 0)
@@ -661,12 +644,12 @@ static DWORD ParseFile_BuildInfo(TCascStorage * hs, CASC_CSV & Csv)
return ERROR_CANCELLED;
// We now have preferred product to open
SetProductCodeName(hs, ProductsList[nChoiceIndex]);
hs->SetProductCodeName(ProductsList[nChoiceIndex]);
}
else if(nProductCount == 1)
{
// We now have preferred product to open
SetProductCodeName(hs, ProductsList[nDefault]);
hs->SetProductCodeName(ProductsList[nDefault]);
}
else
{
@@ -692,7 +675,7 @@ static DWORD ParseFile_BuildInfo(TCascStorage * hs, CASC_CSV & Csv)
continue;
// Save the code name of the selected product
SetProductCodeName(hs, Csv[i]["Product!STRING:0"].szValue);
hs->SetProductCodeName(Csv[i]["Product!STRING:0"].szValue);
nSelected = i;
break;
}
@@ -949,7 +932,7 @@ static DWORD LoadCsvFile(TCascStorage * hs, PARSE_REGION_LINE PfnParseRegionLine
{
// Inform the user that we are downloading something
CascStrCopy(szFileNameA, _countof(szFileNameA), szFileName);
if(InvokeProgressCallback(hs, "Downloading the \"%s\" file", szFileNameA, 0, 0))
if(InvokeProgressCallback(hs, CascProgressDownloadingFile, szFileNameA, 0, 0))
return ERROR_CANCELLED;
// Download the file using Ribbit/HTTP protocol
@@ -1275,6 +1258,23 @@ static DWORD HttpDownloadFile(
return dwErrCode;
}
DWORD SetProductCodeName(TCascStorage * hs, LPCSTR szCodeName, size_t nLength)
{
if(hs->szCodeName == NULL && szCodeName != NULL)
{
// Make sure we have the length
if(nLength == 0)
{
nLength = strlen(szCodeName);
}
if((hs->szCodeName = CASC_ALLOC<TCHAR>(nLength + 1)) == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
CascStrCopy(hs->szCodeName, nLength + 1, szCodeName, nLength);
}
return ERROR_SUCCESS;
}
DWORD FetchCascFile(
TCascStorage * hs,
LPCTSTR szRootPath,
@@ -1450,13 +1450,13 @@ static LPTSTR CheckForDirectories(LPCTSTR szParentFolder, ...)
//-----------------------------------------------------------------------------
// Public functions
bool InvokeProgressCallback(TCascStorage * hs, LPCSTR szMessage, LPCSTR szObject, DWORD CurrentValue, DWORD TotalValue)
bool InvokeProgressCallback(TCascStorage * hs, CASC_PROGRESS_MSG Message, LPCSTR szObject, DWORD CurrentValue, DWORD TotalValue)
{
PCASC_OPEN_STORAGE_ARGS pArgs = hs->pArgs;
bool bResult = false;
if(pArgs && pArgs->PfnProgressCallback)
bResult = pArgs->PfnProgressCallback(pArgs->PtrProgressParam, szMessage, szObject, CurrentValue, TotalValue);
bResult = pArgs->PfnProgressCallback(pArgs->PtrProgressParam, Message, szObject, CurrentValue, TotalValue);
return bResult;
}
@@ -1522,14 +1522,14 @@ DWORD CheckCascBuildFileExact(CASC_BUILD_FILE & BuildFile, LPCTSTR szLocalPath)
}
// Unrecognized file name
return ERROR_BAD_FORMAT;
return ERROR_FILE_NOT_FOUND;
}
DWORD CheckCascBuildFileDirs(CASC_BUILD_FILE & BuildFile, LPCTSTR szLocalPath)
{
CASC_PATH<TCHAR> WorkPath(szLocalPath, NULL);
DWORD dwErrCode = ERROR_FILE_NOT_FOUND;
DWORD dwLevelCount = 0;
// Clear the build file structure
memset(&BuildFile, 0, sizeof(CASC_BUILD_FILE));
@@ -1547,16 +1547,15 @@ DWORD CheckCascBuildFileDirs(CASC_BUILD_FILE & BuildFile, LPCTSTR szLocalPath)
}
}
// Try to cut off one path path
if(!WorkPath.CutLastPart())
// Try to cut off one path path. Don't go indefinitely.
if((dwLevelCount > 5) || !WorkPath.CutLastPart())
{
dwErrCode = ERROR_PATH_NOT_FOUND;
break;
}
}
// Unrecognized file name
return dwErrCode;
// None of the supported file names was found
return ERROR_FILE_NOT_FOUND;
}
DWORD CheckOnlineStorage(PCASC_OPEN_STORAGE_ARGS pArgs, CASC_BUILD_FILE & BuildFile, bool bOnlineStorage)
@@ -1616,6 +1615,7 @@ DWORD CheckArchiveFilesDirectories(TCascStorage * hs)
DWORD CheckDataFilesDirectory(TCascStorage * hs)
{
CASC_PATH<TCHAR> DataPath(hs->szRootPath, _T("data"), NULL);
DWORD dwErrCode;
bool bTwoDigitFolderFound = false;
// When CASC_FEATURE_ONLINE is not set, then the folder must exist
@@ -1623,17 +1623,15 @@ DWORD CheckDataFilesDirectory(TCascStorage * hs)
{
// Check if there are subfolders at all. If not, do not bother
// the file system with open requests into data files folder
if(ScanDirectory(DataPath, CheckForTwoDigitFolder, NULL, &bTwoDigitFolderFound) != ERROR_SUCCESS)
return ERROR_PATH_NOT_FOUND;
if((dwErrCode = ScanDirectory(DataPath, CheckForTwoDigitFolder, NULL, &bTwoDigitFolderFound)) != ERROR_SUCCESS)
return dwErrCode;
if(bTwoDigitFolderFound == false)
return ERROR_PATH_NOT_FOUND;
}
// Create the path for raw files
if((hs->szFilesPath = DataPath.New()) == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
return ERROR_SUCCESS;
return ((hs->szFilesPath = DataPath.New()) == NULL) ? ERROR_NOT_ENOUGH_MEMORY : ERROR_SUCCESS;
}
DWORD LoadBuildFile_Versions_Cdns(TCascStorage * hs)
@@ -1705,7 +1703,7 @@ DWORD LoadCdnConfigFile(TCascStorage * hs)
assert(hs->CdnConfigKey.pbData != NULL && hs->CdnConfigKey.cbData == MD5_HASH_SIZE);
// Inform the user about what we are doing
if(InvokeProgressCallback(hs, "Loading CDN config file", NULL, 0, 0))
if(InvokeProgressCallback(hs, CascProgressLoadingFile, "CDN config", 0, 0))
return ERROR_CANCELLED;
// Load the CDN config file
@@ -1718,7 +1716,7 @@ DWORD LoadCdnBuildFile(TCascStorage * hs)
assert(hs->CdnBuildKey.pbData != NULL && hs->CdnBuildKey.cbData == MD5_HASH_SIZE);
// Inform the user about what we are doing
if(InvokeProgressCallback(hs, "Loading CDN build file", NULL, 0, 0))
if(InvokeProgressCallback(hs, CascProgressLoadingFile, "CDN build", 0, 0))
return ERROR_CANCELLED;
// Load the CDN config file. Note that we don't

View File

@@ -276,7 +276,7 @@ static DWORD LoadIndexItems(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, EKE
while((pbEKeyEntry + EntryLength) <= pbEKeyEnd)
{
// ENCODING for Starcraft II Beta
BREAK_ON_XKEY3(pbEKeyEntry, 0x8b, 0x0d, 0x9a);
// BREAK_ON_XKEY3(pbEKeyEntry, 0x8b, 0x0d, 0x9a);
if(!PfnEKeyEntry(hs, InHeader, pbEKeyEntry))
return ERROR_INDEX_PARSING_DONE;
@@ -487,7 +487,7 @@ static DWORD ProcessLocalIndexFiles(TCascStorage * hs, EKEY_ENTRY_CALLBACK PfnEK
CASC_INDEX & IndexFile = hs->IndexFiles[i];
// Inform the user about what we are doing
if(InvokeProgressCallback(hs, "Loading index files", NULL, i, dwIndexCount))
if(InvokeProgressCallback(hs, CascProgressLoadingIndexes, NULL, i, dwIndexCount))
{
dwErrCode = ERROR_CANCELLED;
break;
@@ -514,7 +514,7 @@ static DWORD LoadLocalIndexFiles(TCascStorage * hs)
DWORD dwErrCode;
// Inform the user about what we are doing
if(InvokeProgressCallback(hs, "Loading index files", NULL, 0, 0))
if(InvokeProgressCallback(hs, CascProgressLoadingIndexes, NULL, 0, 0))
return ERROR_CANCELLED;
// Perform the directory scan
@@ -749,7 +749,7 @@ static DWORD LoadArchiveIndexFiles(TCascStorage * hs)
LPBYTE pbIndexHash = hs->ArchivesKey.pbData + (i * MD5_HASH_SIZE);
// Inform the user about what we are doing
if(InvokeProgressCallback(hs, "Downloading archive indexes", NULL, (DWORD)(i), (DWORD)(nArchiveCount)))
if(InvokeProgressCallback(hs, CascProgressDownloadingArchiveIndexes, NULL, (DWORD)(i), (DWORD)(nArchiveCount)))
{
dwErrCode = ERROR_CANCELLED;
break;

View File

@@ -72,6 +72,12 @@ extern "C" {
#endif
#endif
#if defined(CASCLIB_DETECT_UNICODE_MISMATCHES)
#if defined(_UNICODE) != defined(CASCLIB_UNICODE)
#error CascLib was not built with the same UNICODE setting as your project
#endif
#endif
//-----------------------------------------------------------------------------
// Defines
@@ -318,12 +324,21 @@ typedef struct _CASC_FILE_SPAN_INFO
//-----------------------------------------------------------------------------
// Extended version of CascOpenStorage
typedef enum _CASC_PROGRESS_MSG
{
CascProgressLoadingFile, // "Loading file: %s"
CascProgressLoadingManifest, // "Loading manifest: %s"
CascProgressDownloadingFile, // "Downloading file: %s"
CascProgressLoadingIndexes, // "Loading index files"
CascProgressDownloadingArchiveIndexes, // "Downloading archive indexes"
} CASC_PROGRESS_MSG, *PCASC_PROGRESS_MSG;
// Some operations (e.g. opening an online storage) may take long time.
// This callback allows an application to be notified about loading progress
// and even cancel the storage loading process
typedef bool (WINAPI * PFNPROGRESSCALLBACK)( // Return 'true' to cancel the loading process
void * PtrUserParam, // User-specific parameter passed to the callback
LPCSTR szWork, // Text for the current activity (example: "Loading "ENCODING" file")
CASC_PROGRESS_MSG ProgressMsg, // Text for the current activity. The target callback needs to translate it into language-specific message
LPCSTR szObject, // (optional) name of the object tied to the activity (example: index file name)
DWORD CurrentValue, // (optional) current object being processed
DWORD TotalValue // (optional) If non-zero, this is the total number of objects to process

View File

@@ -442,7 +442,7 @@ static DWORD LoadEncodingManifest(TCascStorage * hs)
DWORD dwErrCode = ERROR_SUCCESS;
// Inform the user about what we are doing
if(InvokeProgressCallback(hs, "Loading ENCODING manifest", NULL, 0, 0))
if(InvokeProgressCallback(hs, CascProgressLoadingManifest, "ENCODING", 0, 0))
return ERROR_CANCELLED;
// Fill-in the information from the index entry and insert it to the file tree
@@ -753,7 +753,7 @@ static int LoadDownloadManifest(TCascStorage * hs)
DWORD dwErrCode = ERROR_SUCCESS;
// Inform the user about what we are doing
if(InvokeProgressCallback(hs, "Loading DOWNLOAD manifest", NULL, 0, 0))
if(InvokeProgressCallback(hs, CascProgressLoadingManifest, "DOWNLOAD", 0, 0))
return ERROR_CANCELLED;
// Load the entire DOWNLOAD file to memory
@@ -786,7 +786,7 @@ static int LoadInstallManifest(TCascStorage * hs)
DWORD dwErrCode = ERROR_SUCCESS;
// Inform the user about what we are doing
if(InvokeProgressCallback(hs, "Loading INSTALL manifest", NULL, 0, 0))
if(InvokeProgressCallback(hs, CascProgressLoadingManifest, "INSTALL", 0, 0))
return ERROR_CANCELLED;
// Load the entire DOWNLOAD file to memory
@@ -854,7 +854,7 @@ static int LoadBuildManifest(TCascStorage * hs, DWORD dwLocaleMask)
assert(hs->pRootHandler == NULL);
// Inform the user about what we are doing
if(InvokeProgressCallback(hs, "Loading ROOT manifest", NULL, 0, 0))
if(InvokeProgressCallback(hs, CascProgressLoadingManifest, "ROOT", 0, 0))
return ERROR_CANCELLED;
// Locale: The default parameter is 0 - in that case, we load all locales
@@ -913,6 +913,10 @@ __LoadRootFile:
break;
}
}
else
{
dwErrCode = ERROR_BAD_FORMAT;
}
}
else
{
@@ -922,7 +926,7 @@ __LoadRootFile:
// Handle reparsing of the root file
if(dwErrCode == ERROR_REPARSE_ROOT && pCKeyEntry != &hs->RootFile)
{
if(InvokeProgressCallback(hs, "Loading ROOT manifest (reparsed)", NULL, 0, 0))
if(InvokeProgressCallback(hs, CascProgressLoadingManifest, "ROOT (reparsed)", 0, 0))
return ERROR_CANCELLED;
// Replace the root handler
@@ -1182,6 +1186,12 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs, L
hs->dwBuildNumber = 21742 + hs->InstallCKey.ContentSize;
}
// Make sure we have a code name. Not a case of WoW build 22267
if(hs->szCodeName == NULL && hs->dwBuildNumber == 22267)
{
hs->SetProductCodeName("wow", 3);
}
// Create the array of CKey entries. Each entry represents a file in the storage
if(dwErrCode == ERROR_SUCCESS)
{
@@ -1337,6 +1347,13 @@ static DWORD ParseOpenParams(LPTSTR szParams, PCASC_OPEN_STORAGE_ARGS pArgs)
pArgs->szRegion = szParamsPtr;
}
// There could be region appended at the end
if((szParamsPtr = GetNextParam(szParamsPtr)) != NULL)
{
if(pArgs->szBuildKey && pArgs->szBuildKey[0])
return ERROR_INVALID_PARAMETER;
pArgs->szBuildKey = szParamsPtr;
}
return ERROR_SUCCESS;
}

View File

@@ -100,10 +100,6 @@
#define PKEXPORT
#ifndef __SYS_ZLIB
#define __SYS_ZLIB
#endif
#ifndef __BIG_ENDIAN__
#define CASCLIB_PLATFORM_LITTLE_ENDIAN
#endif
@@ -210,6 +206,7 @@
#define _tremove remove
#define _taccess access
#define _access access
#define _tfopen fopen
#define _stricmp strcasecmp
#define _strnicmp strncasecmp

View File

@@ -1290,6 +1290,9 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
case CascCacheLastFrame:
dwBytesRead2 = ReadFile_FrameCached(hf, pbBuffer, StartOffset, EndOffset);
break;
default:
break;
}
// If the second-stage-read failed, we invalidate the entire operation and return 0 bytes read

View File

@@ -34,7 +34,7 @@ typedef struct _APM_HEADER_V3
// Followed by the array of APM_ENTRY (count is in "EntryCount")
// Followed by the array of APM_PACKAGE (count is in "PackageCount")
} APM_HEADER_V3, * PAPM_HEADER_V3;
} APM_HEADER_V3, *PAPM_HEADER_V3;
typedef struct _APM_HEADER_V2
{
@@ -48,7 +48,7 @@ typedef struct _APM_HEADER_V2
// Followed by the array of APM_ENTRY (count is in "EntryCount")
// Followed by the array of APM_PACKAGE (count is in "PackageCount")
} APM_HEADER_V2, * PAPM_HEADER_V2;
} APM_HEADER_V2, *PAPM_HEADER_V2;
typedef struct _APM_HEADER_V1
{
@@ -61,7 +61,7 @@ typedef struct _APM_HEADER_V1
// Followed by the array of APM_ENTRY (count is in "EntryCount")
// Followed by the array of APM_PACKAGE (count is in "PackageCount")
} APM_HEADER_V1, * PAPM_HEADER_V1;
} APM_HEADER_V1, *PAPM_HEADER_V1;
// On-disk format, size = 0x0C
typedef struct _APM_ENTRY_V1
@@ -69,7 +69,7 @@ typedef struct _APM_ENTRY_V1
DWORD Index;
DWORD HashA_Lo; // Must split the hashes in order to make this structure properly aligned
DWORD HashA_Hi;
} APM_ENTRY_V1, * PAPM_ENTRY_V1;
} APM_ENTRY_V1, *PAPM_ENTRY_V1;
// On-disk format, size = 0x14
typedef struct _APM_ENTRY_V2
@@ -91,7 +91,7 @@ typedef struct _APM_PACKAGE_ENTRY_V1
ULONGLONG PackageGUID; // 077 file
ULONGLONG Unknown1;
DWORD Unknown2;
} APM_PACKAGE_ENTRY_V1, * PAPM_PACKAGE_ENTRY_V1;
} APM_PACKAGE_ENTRY_V1, *PAPM_PACKAGE_ENTRY_V1;
// On-disk format
typedef struct _APM_PACKAGE_ENTRY_V2

View File

@@ -712,7 +712,7 @@ struct TRootHandler_TVFS : public TFileTreeRoot
FileTree.SetKeyLength(RootHeader.EKeySize);
// Initialize the array of span entries
dwErrCode = SpanArray.Create(sizeof(CASC_CKEY_ENTRY), 0x100);
dwErrCode = SpanArray.Create(sizeof(CASC_CKEY_ENTRY), 0x10000);
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;

View File

@@ -35,6 +35,19 @@ typedef enum _ROOT_FORMAT
RootFormatWoW_v2, // Since build 30080 (WoW 8.2.0)
} ROOT_FORMAT, *PROOT_FORMAT;
// The last byte of the structure causes wrong alignment with default compiler options
#pragma pack(push, 1)
typedef struct _FILE_ROOT_GROUP_HEADER_58221 // Since build 58221 (11.1.0.58221)
{
DWORD NumberOfFiles; // Number of entries
DWORD LocaleFlags; // File locale mask (CASC_LOCALE_XXX)
DWORD ContentFlags1;
DWORD ContentFlags2;
BYTE ContentFlags3;
} FILE_ROOT_GROUPHEADER_58221, *PFILE_ROOT_GROUPHEADER_58221;
#pragma pack(pop)
// ROOT file header since build 50893 (10.1.7)
typedef struct _FILE_ROOT_HEADER_50893
{
@@ -43,7 +56,7 @@ typedef struct _FILE_ROOT_HEADER_50893
DWORD Version; // Must be 1
DWORD TotalFiles;
DWORD FilesWithNameHash;
} FILE_ROOT_HEADER_50893, * PFILE_ROOT_HEADER_50893;
} FILE_ROOT_HEADER_50893, *PFILE_ROOT_HEADER_50893;
// ROOT file header since build 30080 (8.2.0)
typedef struct _FILE_ROOT_HEADER_30080
@@ -97,14 +110,15 @@ struct TRootHandler_WoW : public TFileTreeRoot
{
public:
typedef LPBYTE (*CAPTURE_ROOT_HEADER)(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless);
typedef LPBYTE (*CAPTURE_ROOT_HEADER)(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless, PDWORD Version);
TRootHandler_WoW(ROOT_FORMAT RFormat, DWORD HashlessFileCount) : TFileTreeRoot(FTREE_FLAGS_WOW)
TRootHandler_WoW(ROOT_FORMAT aRootFormat, DWORD aFileCounterHashless, LPCTSTR szDumpFile = NULL) : TFileTreeRoot(FTREE_FLAGS_WOW)
{
// Turn off the "we know file names" bit
FileCounterHashless = HashlessFileCount;
FileCounterHashless = aFileCounterHashless;
FileCounter = 0;
RootFormat = RFormat;
RootFormat = aRootFormat;
fp = NULL;
// Update the flags based on format
switch(RootFormat)
@@ -117,10 +131,44 @@ struct TRootHandler_WoW : public TFileTreeRoot
dwFeatures |= CASC_FEATURE_ROOT_CKEY | CASC_FEATURE_LOCALE_FLAGS | CASC_FEATURE_CONTENT_FLAGS | CASC_FEATURE_FNAME_HASHES;
break;
}
// Create the file for dumping listfile
if(szDumpFile && szDumpFile[0])
{
fp = _tfopen(szDumpFile, _T("wt"));
}
}
~TRootHandler_WoW()
{
if(fp != NULL)
fclose(fp);
fp = NULL;
}
#ifdef CASCLIB_WRITE_VERIFIED_FILENAMES
void VerifyAndLogFileName(LPCSTR szFileName, ULONG FileDataId)
{
PCASC_FILE_NODE pFileNode;
ULONGLONG FileNameHash = CalcFileNameHash(szFileName);
if((pFileNode = FileTree.Find(FileNameHash)) != NULL)
{
if(pFileNode->FileNameHash == FileNameHash)
{
if(FileDataId != 0)
fprintf(fp, "%u;%s\n", FileDataId, szFileName);
else
fprintf(fp, "%s\n", szFileName);
}
}
}
#else
#define VerifyAndLogFileName(szFileName, FileDataId) /* */
#endif
// Check for the new format (World of Warcraft 10.1.7, build 50893)
static LPBYTE CaptureRootHeader_50893(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless)
static LPBYTE CaptureRootHeader_50893(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless, PDWORD Version)
{
FILE_ROOT_HEADER_50893 RootHeader;
@@ -132,7 +180,7 @@ struct TRootHandler_WoW : public TFileTreeRoot
// Verify the root file header
if(RootHeader.Signature != CASC_WOW_ROOT_SIGNATURE)
return NULL;
if(RootHeader.Version != 1)
if(RootHeader.Version != 1 && RootHeader.Version != 2)
return NULL;
if(RootHeader.FilesWithNameHash > RootHeader.TotalFiles)
return NULL;
@@ -142,11 +190,12 @@ struct TRootHandler_WoW : public TFileTreeRoot
*RootFormat = RootFormatWoW_v2;
*FileCounterHashless = RootHeader.TotalFiles - RootHeader.FilesWithNameHash;
*Version = RootHeader.Version;
return pbRootPtr + RootHeader.SizeOfHeader;
}
// Check for the root format for build 30080+ (WoW 8.2.0)
static LPBYTE CaptureRootHeader_30080(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless)
static LPBYTE CaptureRootHeader_30080(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless, PDWORD Version)
{
FILE_ROOT_HEADER_30080 RootHeader;
@@ -163,11 +212,12 @@ struct TRootHandler_WoW : public TFileTreeRoot
*RootFormat = RootFormatWoW_v2;
*FileCounterHashless = RootHeader.TotalFiles - RootHeader.FilesWithNameHash;
*Version = 0;
return pbRootPtr + sizeof(FILE_ROOT_HEADER_30080);
}
// Check for the root format for build 18125+ (WoW 6.0.1)
static LPBYTE CaptureRootHeader_18125(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless)
static LPBYTE CaptureRootHeader_18125(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless, PDWORD Version)
{
size_t DataLength;
@@ -183,10 +233,11 @@ struct TRootHandler_WoW : public TFileTreeRoot
*RootFormat = RootFormatWoW_v1;
*FileCounterHashless = 0;
*Version = 0;
return pbRootPtr;
}
static LPBYTE CaptureRootHeader(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless)
static LPBYTE CaptureRootHeader(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless, PDWORD Version)
{
CAPTURE_ROOT_HEADER PfnCaptureRootHeader[] =
{
@@ -199,7 +250,7 @@ struct TRootHandler_WoW : public TFileTreeRoot
{
LPBYTE pbCapturedPtr;
if((pbCapturedPtr = PfnCaptureRootHeader[i](pbRootPtr, pbRootEnd, RootFormat, FileCounterHashless)) != NULL)
if((pbCapturedPtr = PfnCaptureRootHeader[i](pbRootPtr, pbRootEnd, RootFormat, FileCounterHashless, Version)) != NULL)
{
return pbCapturedPtr;
}
@@ -207,16 +258,34 @@ struct TRootHandler_WoW : public TFileTreeRoot
return NULL;
}
LPBYTE CaptureRootGroup(FILE_ROOT_GROUP & RootGroup, LPBYTE pbRootPtr, LPBYTE pbRootEnd)
LPBYTE CaptureRootGroup(FILE_ROOT_GROUP & RootGroup, LPBYTE pbRootPtr, LPBYTE pbRootEnd, DWORD dwRootVersion)
{
// 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);
if(dwRootVersion == 0 || dwRootVersion == 1)
{
// 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);
}
else if(dwRootVersion == 2)
{
PFILE_ROOT_GROUPHEADER_58221 pRootGroupHeader;
// Get pointer to the root group header
if((pbRootPtr + sizeof(FILE_ROOT_GROUPHEADER_58221)) >= pbRootEnd)
return NULL;
pRootGroupHeader = (PFILE_ROOT_GROUPHEADER_58221)pbRootPtr;
pbRootPtr = pbRootPtr + sizeof(FILE_ROOT_GROUPHEADER_58221);
// Convert to old ContentFlags for now...
RootGroup.Header.NumberOfFiles = pRootGroupHeader->NumberOfFiles;
RootGroup.Header.ContentFlags = pRootGroupHeader->ContentFlags1 | pRootGroupHeader->ContentFlags2 | (DWORD)(pRootGroupHeader->ContentFlags3 << 17);
RootGroup.Header.LocaleFlags = pRootGroupHeader->LocaleFlags;
}
// Validate the array of file data IDs
if((pbRootPtr + (sizeof(DWORD) * RootGroup.Header.NumberOfFiles)) >= pbRootEnd)
@@ -345,7 +414,8 @@ struct TRootHandler_WoW : public TFileTreeRoot
LPBYTE pbRootEnd,
DWORD dwLocaleMask,
BYTE bOverrideLowViolence,
BYTE bAudioLocale)
BYTE bAudioLocale,
DWORD dwRootVersion)
{
FILE_ROOT_GROUP RootBlock;
@@ -360,7 +430,7 @@ struct TRootHandler_WoW : public TFileTreeRoot
//OutputDebugStringA(szMessage);
// Validate the file locale block
pbRootPtr = CaptureRootGroup(RootBlock, pbRootPtr, pbRootEnd);
pbRootPtr = CaptureRootGroup(RootBlock, pbRootPtr, pbRootEnd, dwRootVersion);
if(pbRootPtr == NULL)
return ERROR_BAD_FORMAT;
@@ -446,33 +516,34 @@ struct TRootHandler_WoW : public TFileTreeRoot
LPBYTE pbRootPtr,
LPBYTE pbRootEnd,
DWORD dwLocaleMask,
BYTE bAudioLocale)
BYTE bAudioLocale,
DWORD dwRootVersion)
{
DWORD dwErrCode;
// Load the locale as-is
dwErrCode = ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, dwLocaleMask, false, bAudioLocale);
dwErrCode = ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, dwLocaleMask, false, bAudioLocale, dwRootVersion);
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
// 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);
ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, CASC_LOCALE_ENUS, false, bAudioLocale, dwRootVersion);
if(dwLocaleMask == CASC_LOCALE_PTPT)
ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, CASC_LOCALE_PTBR, false, bAudioLocale);
ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, CASC_LOCALE_PTBR, false, bAudioLocale, dwRootVersion);
return ERROR_SUCCESS;
}
// WoW.exe: 004146C7 (BuildManifest::Load)
DWORD Load(TCascStorage * hs, LPBYTE pbRootPtr, LPBYTE pbRootEnd, DWORD dwLocaleMask)
DWORD Load(TCascStorage * hs, LPBYTE pbRootPtr, LPBYTE pbRootEnd, DWORD dwLocaleMask, DWORD dwRootVersion)
{
DWORD dwErrCode;
dwErrCode = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 0);
dwErrCode = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 0, dwRootVersion);
if(dwErrCode == ERROR_SUCCESS)
dwErrCode = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 1);
dwErrCode = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 1, dwRootVersion);
#ifdef CASCLIB_DEBUG
// Dump the array of the file data IDs
@@ -507,6 +578,9 @@ struct TRootHandler_WoW : public TFileTreeRoot
break;
}
// Try to verify the file name by hash
VerifyAndLogFileName(szFileName, FileDataId);
//
// Several files were renamed around WoW build 50893 (10.1.7). Example:
//
@@ -543,10 +617,11 @@ struct TRootHandler_WoW : public TFileTreeRoot
break;
}
// Calculate the hash of the file name
FileNameHash = CalcFileNameHash(szFileName);
// Try to verify the file name by hash
VerifyAndLogFileName(szFileName, 0);
// Try to find the file node by file name hash
// Calculate the hash of the file name and lookup in tree
FileNameHash = CalcFileNameHash(szFileName);
pFileNode = FileTree.Find(FileNameHash);
if(pFileNode != NULL && pFileNode->NameLength == 0)
{
@@ -562,6 +637,7 @@ struct TRootHandler_WoW : public TFileTreeRoot
}
ROOT_FORMAT RootFormat; // Root file format
FILE * fp; // Handle to the dump file
DWORD FileCounterHashless; // Number of files for which we don't have hash. Meaningless for WoW before 8.2.0
DWORD FileCounter; // Counter of loaded files. Only used during loading of ROOT file
};
@@ -573,24 +649,34 @@ DWORD RootHandler_CreateWoW(TCascStorage * hs, CASC_BLOB & RootFile, DWORD dwLoc
{
TRootHandler_WoW * pRootHandler = NULL;
ROOT_FORMAT RootFormat = RootFormatWoW_v1;
LPCTSTR szDumpFile = NULL;
LPBYTE pbRootFile = RootFile.pbData;
LPBYTE pbRootEnd = RootFile.End();
LPBYTE pbRootPtr;
DWORD FileCounterHashless = 0;
DWORD RootVersion = 0;
DWORD dwErrCode = ERROR_BAD_FORMAT;
// Verify the root header
if((pbRootPtr = TRootHandler_WoW::CaptureRootHeader(pbRootFile, pbRootEnd, &RootFormat, &FileCounterHashless)) == NULL)
if((pbRootPtr = TRootHandler_WoW::CaptureRootHeader(pbRootFile, pbRootEnd, &RootFormat, &FileCounterHashless, &RootVersion)) == NULL)
return ERROR_BAD_FORMAT;
// Create the WOW handler
pRootHandler = new TRootHandler_WoW(RootFormat, FileCounterHashless);
#ifdef CASCLIB_WRITE_VERIFIED_FILENAMES
LPCTSTR szExtension = (RootFormat == RootFormatWoW_v1) ? _T("txt") : _T("csv");
TCHAR szBuffer[MAX_PATH];
CascStrPrintf(szBuffer, _countof(szBuffer), _T("\\listfile_wow_%u_%s.%s"), hs->dwBuildNumber, hs->szCodeName, szExtension);
szDumpFile = szBuffer;
#endif
// Create the root handler
pRootHandler = new TRootHandler_WoW(RootFormat, FileCounterHashless, szDumpFile);
if(pRootHandler != NULL)
{
//fp = fopen("E:\\file-data-ids2.txt", "wt");
// Load the root directory. If load failed, we free the object
dwErrCode = pRootHandler->Load(hs, pbRootPtr, pbRootEnd, dwLocaleMask);
dwErrCode = pRootHandler->Load(hs, pbRootPtr, pbRootEnd, dwLocaleMask, RootVersion);
if(dwErrCode != ERROR_SUCCESS)
{
delete pRootHandler;

View File

@@ -17,7 +17,7 @@
#define CASC_INDEX_COUNT 0x10 // Number of index files
#define CASC_CKEY_SIZE 0x10 // Size of the content key
#define CASC_EKEY_SIZE 0x09 // Size of the encoded key
#define CASC_MAX_DATA_FILES 0x100 // Maximum number of data files
#define CASC_MAX_DATA_FILES 0x1000 // Maximum number of data files
//-----------------------------------------------------------------------------
// The index files structures

View File

@@ -31,6 +31,8 @@ EXPORTS
CascAddEncryptionKey
CascAddStringEncryptionKey
CascImportKeysFromString
CascImportKeysFromFile
CascFindEncryptionKey
CascGetNotFoundEncryptionKey

View File

@@ -77,7 +77,9 @@ unsigned char IntToHexChar[] = "0123456789abcdef";
//-----------------------------------------------------------------------------
// GetCascError/SetCascError support for non-Windows platform
static DWORD dwLastError = ERROR_SUCCESS;
#ifndef CASCLIB_PLATFORM_WINDOWS
static __thread DWORD dwLastError = ERROR_SUCCESS;
#endif
DWORD GetCascError()
{
@@ -92,8 +94,9 @@ void SetCascError(DWORD dwErrCode)
{
#ifdef CASCLIB_PLATFORM_WINDOWS
SetLastError(dwErrCode);
#endif
#else
dwLastError = dwErrCode;
#endif
}
//-----------------------------------------------------------------------------

View File

@@ -63,7 +63,7 @@ catch2
CascLib (An open-source implementation of library for reading CASC storage from Blizzard games since 2014)
https://github.com/ladislav-zezula/CascLib
Version: 5c60050770767f2e606f6fec0c35beb8b9b00c60
Version: 07ab5f37ad282cc101d5c17793c550a0a6d4637f
rapidjson (A fast JSON parser/generator for C++ with both SAX/DOM style API http://rapidjson.org/)
https://github.com/Tencent/rapidjson