#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
#define ROOT_SEARCH_PHASE_INITIALIZING 0
#define ROOT_SEARCH_PHASE_LISTFILE 1
#define ROOT_SEARCH_PHASE_NAMELESS 2
#define ROOT_SEARCH_PHASE_FINISHED 3
#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
{
RootFormatWoW_v1, RootFormatWoW_v2, } ROOT_FORMAT, *PROOT_FORMAT;
#pragma pack(push, 1)
typedef struct _FILE_ROOT_GROUP_HEADER_58221 {
DWORD NumberOfFiles; DWORD LocaleFlags; DWORD ContentFlags1;
DWORD ContentFlags2;
BYTE ContentFlags3;
} FILE_ROOT_GROUPHEADER_58221, *PFILE_ROOT_GROUPHEADER_58221;
#pragma pack(pop)
typedef struct _FILE_ROOT_HEADER_50893
{
DWORD Signature; DWORD SizeOfHeader;
DWORD Version; DWORD TotalFiles;
DWORD FilesWithNameHash;
} FILE_ROOT_HEADER_50893, *PFILE_ROOT_HEADER_50893;
typedef struct _FILE_ROOT_HEADER_30080
{
DWORD Signature; DWORD TotalFiles;
DWORD FilesWithNameHash;
} FILE_ROOT_HEADER_30080, *PFILE_ROOT_HEADER_30080;
typedef struct _FILE_ROOT_GROUP_HEADER
{
DWORD NumberOfFiles; DWORD ContentFlags;
DWORD LocaleFlags;
} FILE_ROOT_GROUP_HEADER, *PFILE_ROOT_GROUP_HEADER;
typedef struct _FILE_ROOT_ENTRY
{
CONTENT_KEY CKey; ULONGLONG FileNameHash;
} FILE_ROOT_ENTRY, *PFILE_ROOT_ENTRY;
typedef struct _FILE_ROOT_GROUP
{
FILE_ROOT_GROUP_HEADER Header;
PDWORD FileDataIds;
PFILE_ROOT_ENTRY pRootEntries; PCONTENT_KEY pCKeyEntries; PULONGLONG pHashes;
} FILE_ROOT_GROUP, *PFILE_ROOT_GROUP;
#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:
typedef LPBYTE (*CAPTURE_ROOT_HEADER)(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless, PDWORD Version);
TRootHandler_WoW(ROOT_FORMAT aRootFormat, DWORD aFileCounterHashless, LPCTSTR szDumpFile = NULL) : TFileTreeRoot(FTREE_FLAGS_WOW)
{
FileCounterHashless = aFileCounterHashless;
FileCounter = 0;
RootFormat = aRootFormat;
fp = NULL;
switch(RootFormat)
{
case RootFormatWoW_v2:
dwFeatures |= CASC_FEATURE_ROOT_CKEY | CASC_FEATURE_LOCALE_FLAGS | CASC_FEATURE_CONTENT_FLAGS | CASC_FEATURE_FILE_DATA_IDS | CASC_FEATURE_FNAME_HASHES_OPTIONAL;
break;
case RootFormatWoW_v1:
dwFeatures |= CASC_FEATURE_ROOT_CKEY | CASC_FEATURE_LOCALE_FLAGS | CASC_FEATURE_CONTENT_FLAGS | CASC_FEATURE_FNAME_HASHES;
break;
}
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
static LPBYTE CaptureRootHeader_50893(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless, PDWORD Version)
{
FILE_ROOT_HEADER_50893 RootHeader;
if((pbRootPtr + sizeof(FILE_ROOT_HEADER_50893)) >= pbRootEnd)
return NULL;
memcpy(&RootHeader, pbRootPtr, sizeof(FILE_ROOT_HEADER_50893));
if(RootHeader.Signature != CASC_WOW_ROOT_SIGNATURE)
return NULL;
if(RootHeader.Version != 1 && RootHeader.Version != 2)
return NULL;
if(RootHeader.FilesWithNameHash > RootHeader.TotalFiles)
return NULL;
if(RootHeader.SizeOfHeader < 4)
RootHeader.SizeOfHeader = 4;
*RootFormat = RootFormatWoW_v2;
*FileCounterHashless = RootHeader.TotalFiles - RootHeader.FilesWithNameHash;
*Version = RootHeader.Version;
return pbRootPtr + RootHeader.SizeOfHeader;
}
static LPBYTE CaptureRootHeader_30080(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless, PDWORD Version)
{
FILE_ROOT_HEADER_30080 RootHeader;
if((pbRootPtr + sizeof(FILE_ROOT_HEADER_30080)) >= pbRootEnd)
return NULL;
memcpy(&RootHeader, pbRootPtr, sizeof(FILE_ROOT_HEADER_30080));
if(RootHeader.Signature != CASC_WOW_ROOT_SIGNATURE)
return NULL;
if(RootHeader.FilesWithNameHash > RootHeader.TotalFiles)
return NULL;
*RootFormat = RootFormatWoW_v2;
*FileCounterHashless = RootHeader.TotalFiles - RootHeader.FilesWithNameHash;
*Version = 0;
return pbRootPtr + sizeof(FILE_ROOT_HEADER_30080);
}
static LPBYTE CaptureRootHeader_18125(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless, PDWORD Version)
{
size_t DataLength;
if((pbRootPtr + sizeof(FILE_ROOT_GROUP_HEADER)) >= pbRootEnd)
return NULL;
DataLength = ((PFILE_ROOT_GROUP_HEADER)(pbRootPtr))->NumberOfFiles * (sizeof(DWORD) + sizeof(FILE_ROOT_ENTRY));
if((pbRootPtr + sizeof(FILE_ROOT_GROUP_HEADER) + DataLength) >= pbRootEnd)
return NULL;
*RootFormat = RootFormatWoW_v1;
*FileCounterHashless = 0;
*Version = 0;
return pbRootPtr;
}
static LPBYTE CaptureRootHeader(LPBYTE pbRootPtr, LPBYTE pbRootEnd, PROOT_FORMAT RootFormat, PDWORD FileCounterHashless, PDWORD Version)
{
CAPTURE_ROOT_HEADER PfnCaptureRootHeader[] =
{
&CaptureRootHeader_50893,
&CaptureRootHeader_30080,
&CaptureRootHeader_18125,
};
for(size_t i = 0; i < _countof(PfnCaptureRootHeader); i++)
{
LPBYTE pbCapturedPtr;
if((pbCapturedPtr = PfnCaptureRootHeader[i](pbRootPtr, pbRootEnd, RootFormat, FileCounterHashless, Version)) != NULL)
{
return pbCapturedPtr;
}
}
return NULL;
}
LPBYTE CaptureRootGroup(FILE_ROOT_GROUP & RootGroup, LPBYTE pbRootPtr, LPBYTE pbRootEnd, DWORD dwRootVersion)
{
memset(&RootGroup, 0, sizeof(FILE_ROOT_GROUP));
if(dwRootVersion == 0 || dwRootVersion == 1)
{
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;
if((pbRootPtr + sizeof(FILE_ROOT_GROUPHEADER_58221)) >= pbRootEnd)
return NULL;
pRootGroupHeader = (PFILE_ROOT_GROUPHEADER_58221)pbRootPtr;
pbRootPtr = pbRootPtr + sizeof(FILE_ROOT_GROUPHEADER_58221);
RootGroup.Header.NumberOfFiles = pRootGroupHeader->NumberOfFiles;
RootGroup.Header.ContentFlags = pRootGroupHeader->ContentFlags1 | pRootGroupHeader->ContentFlags2 | (DWORD)(pRootGroupHeader->ContentFlags3 << 17);
RootGroup.Header.LocaleFlags = pRootGroupHeader->LocaleFlags;
}
if((pbRootPtr + (sizeof(DWORD) * RootGroup.Header.NumberOfFiles)) >= pbRootEnd)
return NULL;
RootGroup.FileDataIds = (PDWORD)pbRootPtr;
pbRootPtr = pbRootPtr + (sizeof(DWORD) * RootGroup.Header.NumberOfFiles);
FileCounter += RootGroup.Header.NumberOfFiles;
switch(RootFormat)
{
case RootFormatWoW_v2:
if((pbRootPtr + (sizeof(CONTENT_KEY) * RootGroup.Header.NumberOfFiles)) > pbRootEnd)
return NULL;
RootGroup.pCKeyEntries = (PCONTENT_KEY)pbRootPtr;
pbRootPtr = pbRootPtr + (sizeof(CONTENT_KEY) * RootGroup.Header.NumberOfFiles);
if(!(RootGroup.Header.ContentFlags & CASC_CFLAG_NO_NAME_HASH))
{
if((pbRootPtr + (sizeof(ULONGLONG) * RootGroup.Header.NumberOfFiles)) > pbRootEnd)
return NULL;
RootGroup.pHashes = (PULONGLONG)pbRootPtr;
pbRootPtr = pbRootPtr + (sizeof(ULONGLONG) * RootGroup.Header.NumberOfFiles);
}
return pbRootPtr;
case RootFormatWoW_v1:
if((pbRootPtr + (sizeof(FILE_ROOT_ENTRY) * RootGroup.Header.NumberOfFiles)) > pbRootEnd)
return NULL;
RootGroup.pRootEntries = (PFILE_ROOT_ENTRY)pbRootPtr;
return pbRootPtr + (sizeof(FILE_ROOT_ENTRY) * RootGroup.Header.NumberOfFiles);
default:
return NULL;
}
}
DWORD ParseWowRootFile_AddFiles_v2(TCascStorage * hs, FILE_ROOT_GROUP & RootGroup)
{
PCASC_CKEY_ENTRY pCKeyEntry;
PCONTENT_KEY pCKey = RootGroup.pCKeyEntries;
DWORD FileDataId = 0;
assert(RootGroup.pCKeyEntries != NULL);
for(DWORD i = 0; i < RootGroup.Header.NumberOfFiles; i++, pCKey++)
{
FileDataId = FileDataId + RootGroup.FileDataIds[i];
if((pCKeyEntry = FindCKeyEntry_CKey(hs, pCKey->Value)) != NULL)
{
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);
}
}
assert((FileDataId + 1) > FileDataId);
FileDataId++;
}
return ERROR_SUCCESS;
}
DWORD ParseWowRootFile_AddFiles_v1(TCascStorage * hs, FILE_ROOT_GROUP & RootGroup)
{
PFILE_ROOT_ENTRY pRootEntry = RootGroup.pRootEntries;
PCASC_CKEY_ENTRY pCKeyEntry;
DWORD FileDataId = 0;
assert(RootGroup.pRootEntries != NULL);
for(DWORD i = 0; i < RootGroup.Header.NumberOfFiles; i++, pRootEntry++)
{
FileDataId = FileDataId + RootGroup.FileDataIds[i];
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);
}
}
assert((FileDataId + 1) > FileDataId);
FileDataId++;
}
return ERROR_SUCCESS;
}
DWORD ParseWowRootFile_Level2(
TCascStorage * hs,
LPBYTE pbRootPtr,
LPBYTE pbRootEnd,
DWORD dwLocaleMask,
BYTE bOverrideLowViolence,
BYTE bAudioLocale,
DWORD dwRootVersion)
{
FILE_ROOT_GROUP RootBlock;
FileCounter = 0;
while(pbRootPtr < pbRootEnd)
{
pbRootPtr = CaptureRootGroup(RootBlock, pbRootPtr, pbRootEnd, dwRootVersion);
if(pbRootPtr == NULL)
return ERROR_BAD_FORMAT;
if(RootBlock.Header.ContentFlags & CASC_CFLAG_DONT_LOAD)
continue;
if((RootBlock.Header.ContentFlags & CASC_CFLAG_LOW_VIOLENCE) && bOverrideLowViolence == 0)
continue;
if((RootBlock.Header.ContentFlags >> 0x1F) != bAudioLocale)
continue;
if(RootBlock.Header.LocaleFlags != 0 && (RootBlock.Header.LocaleFlags & dwLocaleMask) == 0)
continue;
switch(RootFormat)
{
case RootFormatWoW_v2:
ParseWowRootFile_AddFiles_v2(hs, RootBlock);
break;
case RootFormatWoW_v1:
ParseWowRootFile_AddFiles_v1(hs, RootBlock);
break;
default:
return ERROR_NOT_SUPPORTED;
}
}
return ERROR_SUCCESS;
}
DWORD ParseWowRootFile_Level1(
TCascStorage * hs,
LPBYTE pbRootPtr,
LPBYTE pbRootEnd,
DWORD dwLocaleMask,
BYTE bAudioLocale,
DWORD dwRootVersion)
{
DWORD dwErrCode;
dwErrCode = ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, dwLocaleMask, false, bAudioLocale, dwRootVersion);
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
if(dwLocaleMask == CASC_LOCALE_ENGB)
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, dwRootVersion);
return ERROR_SUCCESS;
}
DWORD Load(TCascStorage * hs, LPBYTE pbRootPtr, LPBYTE pbRootEnd, DWORD dwLocaleMask, DWORD dwRootVersion)
{
DWORD dwErrCode;
dwErrCode = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 0, dwRootVersion);
if(dwErrCode == ERROR_SUCCESS)
dwErrCode = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 1, dwRootVersion);
#ifdef CASCLIB_DEBUG
#endif
return dwErrCode;
}
PCASC_CKEY_ENTRY Search(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
if(pSearch->pCache != NULL && pSearch->bListFileUsed == false)
{
PCASC_FILE_NODE pFileNode;
ULONGLONG FileNameHash;
size_t nLength;
DWORD FileDataId = CASC_INVALID_ID;
char szFileName[MAX_PATH];
if(RootFormat == RootFormatWoW_v2)
{
for(;;)
{
nLength = ListFile_GetNext(pSearch->pCache, szFileName, _countof(szFileName), &FileDataId);
if(nLength == 0)
{
if(GetCascError() == ERROR_INSUFFICIENT_BUFFER)
continue;
break;
}
VerifyAndLogFileName(szFileName, FileDataId);
if((pFileNode = FileTree.FindById(FileDataId)) != NULL)
{
if(pFileNode->NameLength == 0)
{
if(pFileNode->FileNameHash && pFileNode->FileNameHash != CalcFileNameHash(szFileName))
continue;
FileTree.SetNodeFileName(pFileNode, szFileName);
}
}
}
}
else
{
for(;;)
{
nLength = ListFile_GetNextLine(pSearch->pCache, szFileName, _countof(szFileName));
if(nLength == 0)
{
if(GetCascError() == ERROR_INSUFFICIENT_BUFFER)
continue;
break;
}
VerifyAndLogFileName(szFileName, 0);
FileNameHash = CalcFileNameHash(szFileName);
pFileNode = FileTree.Find(FileNameHash);
if(pFileNode != NULL && pFileNode->NameLength == 0)
{
FileTree.SetNodeFileName(pFileNode, szFileName);
}
}
}
pSearch->bListFileUsed = true;
}
return TFileTreeRoot::Search(pSearch, pFindData);
}
ROOT_FORMAT RootFormat; FILE * fp; DWORD FileCounterHashless; DWORD FileCounter; };
DWORD RootHandler_CreateWoW(TCascStorage * hs, CASC_BLOB & RootFile, DWORD dwLocaleMask)
{
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;
if((pbRootPtr = TRootHandler_WoW::CaptureRootHeader(pbRootFile, pbRootEnd, &RootFormat, &FileCounterHashless, &RootVersion)) == NULL)
return ERROR_BAD_FORMAT;
#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
pRootHandler = new TRootHandler_WoW(RootFormat, FileCounterHashless, szDumpFile);
if(pRootHandler != NULL)
{
dwErrCode = pRootHandler->Load(hs, pbRootPtr, pbRootEnd, dwLocaleMask, RootVersion);
if(dwErrCode != ERROR_SUCCESS)
{
delete pRootHandler;
pRootHandler = NULL;
}
}
hs->pRootHandler = pRootHandler;
return dwErrCode;
}