Files
TrinityCore/dep/CascLib/src/CascRootFile_OW.cpp

602 lines
19 KiB
C++

/*****************************************************************************/
/* CascRootFile_Text.cpp Copyright (c) Ladislav Zezula 2017 */
/*---------------------------------------------------------------------------*/
/* Support for loading ROOT files in plain text */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 28.10.15 1.00 Lad The first version of CascRootFile_Text.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Structure definitions for CMF files
#define MAX_LINE_ELEMENTS 8
typedef struct _CMF_HEADER_V3
{
DWORD BuildVersion;
DWORD Unknown0;
DWORD Unknown1;
DWORD Unknown2;
DWORD Unknown3;
DWORD DataCount;
DWORD Unknown4;
DWORD EntryCount;
DWORD Magic;
} CMF_HEADER_V3, *PCMF_HEADER_V3;
typedef struct _CMF_HEADER_V2
{
DWORD BuildVersion;
DWORD Unknown0;
DWORD Unknown1;
DWORD Unknown2;
DWORD DataCount;
DWORD Unknown3;
DWORD EntryCount;
DWORD Magic;
} CMF_HEADER_V2, *PCMF_HEADER_V2;
typedef struct _CMF_HEADER_V1
{
DWORD BuildVersion;
DWORD Unknown0;
DWORD DataCount;
DWORD Unknown1;
DWORD EntryCount;
DWORD Magic;
} CMF_HEADER_V1, *PCMF_HEADER_V1;
//-----------------------------------------------------------------------------
// Structure definitions for APM files
// In-memory format
typedef struct _APM_ENTRY
{
DWORD Index;
ULONGLONG HashA;
ULONGLONG HashB;
} APM_ENTRY, *PAPM_ENTRY;
// On-disk format, size = 0x14
typedef struct _APM_ENTRY_V2
{
DWORD Index;
DWORD HashA_Lo; // Must split the hashes in order to make this structure properly aligned
DWORD HashA_Hi;
DWORD HashB_Lo;
DWORD HashB_Hi;
} APM_ENTRY_V2, *PAPM_ENTRY_V2;
// On-disk format, size = 0x0C
typedef struct _APM_ENTRY_V1
{
DWORD Index;
DWORD HashA_Lo; // Must split the hashes in order to make this structure properly aligned
DWORD HashA_Hi;
} APM_ENTRY_V1, *PAPM_ENTRY_V1;
// In-memory format
typedef struct _APM_PACKAGE_ENTRY
{
ULONGLONG PackageGUID; // 077 file
ULONGLONG Unknown1;
DWORD Unknown2;
DWORD Unknown3;
ULONGLONG Unknown4;
} APM_PACKAGE_ENTRY, *PAPM_PACKAGE_ENTRY;
// On-disk format
typedef struct _APM_PACKAGE_ENTRY_V2
{
ULONGLONG PackageGUID; // 077 file
ULONGLONG Unknown1;
DWORD Unknown2;
DWORD Unknown3;
ULONGLONG Unknown4;
} APM_PACKAGE_ENTRY_V2, *PAPM_PACKAGE_ENTRY_V2;
// On-disk format
typedef struct _APM_PACKAGE_ENTRY_V1
{
ULONGLONG EntryPointGUID; // virtual most likely
ULONGLONG PrimaryGUID; // real
ULONGLONG SecondaryGUID; // real
ULONGLONG Key; // encryption
ULONGLONG PackageGUID; // 077 file
ULONGLONG Unknown1;
DWORD Unknown2;
} APM_PACKAGE_ENTRY_V1, *PAPM_PACKAGE_ENTRY_V1;
typedef struct _APM_HEADER_V3
{
ULONGLONG BuildNumber; // Build number of the game
ULONGLONG ZeroValue1;
DWORD ZeroValue2;
DWORD PackageCount;
DWORD ZeroValue3;
DWORD EntryCount;
DWORD Checksum;
// Followed by the array of APM_ENTRY (count is in "EntryCount")
// Followed by the array of APM_PACKAGE (count is in "PackageCount")
} APM_HEADER_V3, *PAPM_HEADER_V3;
typedef struct _APM_HEADER_V2
{
ULONGLONG BuildNumber; // Build number of the game
ULONGLONG ZeroValue1;
DWORD PackageCount;
DWORD ZeroValue2;
DWORD EntryCount;
DWORD Checksum;
// Followed by the array of APM_ENTRY (count is in "EntryCount")
// Followed by the array of APM_PACKAGE (count is in "PackageCount")
} APM_HEADER_V2, *PAPM_HEADER_V2;
typedef struct _APM_HEADER_V1
{
ULONGLONG BuildNumber; // Build number of the game
DWORD BuildVersion;
DWORD PackageCount;
DWORD EntryCount;
DWORD Checksum;
// Followed by the array of APM_ENTRY (count is in "EntryCount")
// Followed by the array of APM_PACKAGE (count is in "PackageCount")
} APM_HEADER_V1, *PAPM_HEADER_V1;
//-----------------------------------------------------------------------------
// Handler classes
/*
struct TCmfFile
{
TCmfFile()
{
memset(this, 0, sizeof(TCmfFile));
}
LPBYTE CaptureHeader(LPBYTE pbCmfData, LPBYTE pbCmfEnd)
{
DWORD BuildNumber = *(PDWORD)pbCmfData;
// Check the newest header version
if(BuildNumber >= 45104 && BuildNumber != 45214)
{
PCMF_HEADER_V3 pHeader3 = (PCMF_HEADER_V3)pbCmfData;
if ((LPBYTE)(pHeader3 + 1) > pbCmfEnd)
return NULL;
BuildVersion = pHeader3->BuildVersion;
DataCount = pHeader3->DataCount;
EntryCount = pHeader3->EntryCount;
Magic = pHeader3->Magic;
return (LPBYTE)(pHeader3 + 1);
}
else if(BuildNumber >= 39028)
{
// TODO
assert(false);
return NULL;
}
else
{
// TODO
assert(false);
return NULL;
}
}
DWORD BuildVersion;
DWORD DataCount;
DWORD EntryCount;
DWORD Magic;
};
struct TApmFile
{
TApmFile()
{
memset(this, 0, sizeof(TApmFile));
}
~TApmFile()
{
CASC_FREE(pApmPackages);
CASC_FREE(pApmEntries);
}
LPBYTE CaptureHeader(LPBYTE pbApmData, LPBYTE pbApmEnd)
{
// Check the data size for the largest possible header size
if((pbApmData + sizeof(APM_HEADER_V3)) < pbApmEnd)
{
// Try the version 3
PAPM_HEADER_V3 pApmFile3 = (PAPM_HEADER_V3)(pbApmData);
if(pApmFile3->ZeroValue1 == 0 && pApmFile3->ZeroValue2 == 0 && pApmFile3->PackageCount && pApmFile3->EntryCount && pApmFile3->Checksum)
{
BuildNumber = pApmFile3->BuildNumber;
PackageCount = pApmFile3->PackageCount;
EntryCount = pApmFile3->EntryCount;
Checksum = pApmFile3->Checksum;
return pbApmData + 0x24;
}
// Try the version 2
PAPM_HEADER_V2 pApmFile2 = (PAPM_HEADER_V2)(pbApmData);
if(pApmFile2->ZeroValue1 == 0 && pApmFile2->PackageCount && pApmFile2->EntryCount && pApmFile2->Checksum)
{
BuildNumber = pApmFile2->BuildNumber;
PackageCount = pApmFile2->PackageCount;
EntryCount = pApmFile2->EntryCount;
Checksum = pApmFile2->Checksum;
return pbApmData + 0x20;
}
// Try the version 1 (build 24919)
PAPM_HEADER_V1 pApmHeader1 = (PAPM_HEADER_V1)(pbApmData);
if(pApmHeader1->BuildVersion != 0 && pApmHeader1->PackageCount && pApmHeader1->EntryCount && pApmHeader1->Checksum)
{
BuildNumber = pApmHeader1->BuildNumber;
PackageCount = pApmHeader1->PackageCount;
EntryCount = pApmHeader1->EntryCount;
Checksum = pApmHeader1->Checksum;
return pbApmData + 0x18;
}
}
return NULL;
}
LPBYTE CaptureArrayOfEntries(LPBYTE pbArrayOfEntries, LPBYTE pbApmEnd)
{
// Allocate array of entries
pApmEntries = CASC_ALLOC<APM_ENTRY>(EntryCount);
if(pApmEntries != NULL)
{
// The newest format
if(BuildNumber > 45104 && BuildNumber != 45214)
{
PAPM_ENTRY_V2 pEntry2 = (PAPM_ENTRY_V2)pbArrayOfEntries;
LPBYTE pbEntriesEnd = (LPBYTE)(pEntry2 + EntryCount);
if(pbEntriesEnd <= pbApmEnd)
{
for(DWORD i = 0; i < EntryCount; i++)
{
pApmEntries[i].Index = pEntry2->Index;
pApmEntries[i].HashA = MAKE_OFFSET64(pEntry2->HashA_Hi, pEntry2->HashA_Lo);
pApmEntries[i].HashB = MAKE_OFFSET64(pEntry2->HashB_Hi, pEntry2->HashB_Lo);
}
return pbEntriesEnd;
}
}
else
{
PAPM_ENTRY_V1 pEntry1 = (PAPM_ENTRY_V1)pbArrayOfEntries;
LPBYTE pbEntriesEnd = (LPBYTE)(pEntry1 + EntryCount);
if(pbEntriesEnd <= pbApmEnd)
{
for(DWORD i = 0; i < EntryCount; i++)
{
pApmEntries[i].Index = pEntry1->Index;
pApmEntries[i].HashA = MAKE_OFFSET64(pEntry1->HashA_Hi, pEntry1->HashA_Lo);
pApmEntries[i].HashB = 0;
}
return pbEntriesEnd;
}
}
}
return NULL;
}
LPBYTE CapturePackageEntries(LPBYTE pbArrayOfEntries, LPBYTE pbApmEnd)
{
// Allocate array of entries
pApmPackages = CASC_ALLOC_ZERO<APM_PACKAGE_ENTRY>(PackageCount);
if(pApmPackages != NULL)
{
// The newest format
if(BuildNumber > 45104 && BuildNumber != 45214)
{
PAPM_PACKAGE_ENTRY_V2 pEntry2 = (PAPM_PACKAGE_ENTRY_V2)pbArrayOfEntries;
LPBYTE pbEntriesEnd = (LPBYTE)(pEntry2 + PackageCount);
if(pbEntriesEnd <= pbApmEnd)
{
for(DWORD i = 0; i < PackageCount; i++)
{
pApmPackages[i].PackageGUID = pEntry2[i].PackageGUID;
pApmPackages[i].Unknown1 = pEntry2[i].Unknown1;
pApmPackages[i].Unknown2 = pEntry2[i].Unknown2;
pApmPackages[i].Unknown3 = pEntry2[i].Unknown3;
pApmPackages[i].Unknown4 = pEntry2[i].Unknown4;
}
return pbEntriesEnd;
}
}
else
{
PAPM_PACKAGE_ENTRY_V1 pEntry1 = (PAPM_PACKAGE_ENTRY_V1)pbArrayOfEntries;
LPBYTE pbEntriesEnd = (LPBYTE)(pEntry1 + PackageCount);
if(pbEntriesEnd <= pbApmEnd)
{
for(DWORD i = 0; i < PackageCount; i++)
{
// TODO!!!
pApmPackages[i].PackageGUID = pEntry1->PackageGUID;
}
return pbEntriesEnd;
}
}
}
return NULL;
}
PAPM_ENTRY pApmEntries;
PAPM_PACKAGE_ENTRY pApmPackages;
ULONGLONG BuildNumber;
DWORD PackageCount;
DWORD EntryCount;
DWORD Checksum;
size_t HeaderSize;
// Followed by the array of APM_ENTRY (count is in "EntryCount")
// Followed by the array of APM_PACKAGE (count is in "PackageCount")
};
*/
//-----------------------------------------------------------------------------
// Handler definition for OVERWATCH root file
//
// -------------------------------------
// Overwatch ROOT file (build 24919):
// -------------------------------------
// #MD5|CHUNK_ID|FILENAME|INSTALLPATH
// FE3AD8A77EEF77B383DF4929AED816FD|0|RetailClient/GameClientApp.exe|GameClientApp.exe
// 5EDDEFECA544B6472C5CD52BE63BC02F|0|RetailClient/Overwatch Launcher.exe|Overwatch Launcher.exe
// 6DE09F0A67F33F874F2DD8E2AA3B7AAC|0|RetailClient/ca-bundle.crt|ca-bundle.crt
// 99FE9EB6A4BB20209202F8C7884859D9|0|RetailClient/ortp_x64.dll|ortp_x64.dll
//
// -------------------------------------
// Overwatch ROOT file (build 47161):
// -------------------------------------
// #FILEID|MD5|CHUNK_ID|PRIORITY|MPRIORITY|FILENAME|INSTALLPATH
// RetailClient/Overwatch.exe|807F96661280C07E762A8C129FEBDA6F|0|0|255|RetailClient/Overwatch.exe|Overwatch.exe
// RetailClient/Overwatch Launcher.exe|5EDDEFECA544B6472C5CD52BE63BC02F|0|0|255|RetailClient/Overwatch Launcher.exe|Overwatch Launcher.exe
// RetailClient/ortp_x64.dll|7D1B5DEC267480F3E8DAD6B95143A59C|0|0|255|RetailClient/ortp_x64.dll|ortp_x64.dll
//
struct TRootHandler_OW : public TFileTreeRoot
{
TRootHandler_OW() : TFileTreeRoot(0)
{
// We have file names and return CKey as result of search
dwFeatures |= (CASC_FEATURE_FILE_NAMES | CASC_FEATURE_ROOT_CKEY);
}
/*
bool IsManifestFolderName(const char * szFileName, const char * szManifestFolder, size_t nLength)
{
if(!_strnicmp(szFileName, szManifestFolder, nLength))
{
return (szFileName[nLength] == '\\' || szFileName[nLength] == '/');
}
return false;
}
bool IsApmFileName(const char * szFileName)
{
const char * szExtension;
if(IsManifestFolderName(szFileName, "Manifest", 8) || IsManifestFolderName(szFileName, "TactManifest", 12))
{
szExtension = GetFileExtension(szFileName);
if(!_stricmp(szExtension, ".apm"))
{
return true;
}
}
return false;
}
DWORD LoadApmFile(TCascStorage * hs, CONTENT_KEY & CKey, const char * szFileName)
{
TApmFile ApmFile;
LPBYTE pbApmData;
DWORD cbApmData = 0;
DWORD dwErrCode = ERROR_BAD_FORMAT;
pbApmData = LoadInternalFileToMemory(hs, CKey.Value, CASC_OPEN_BY_CKEY, &cbApmData);
if(pbApmData != NULL)
{
LPBYTE pbApmEnd = pbApmData + cbApmData;
LPBYTE pbApmPtr = pbApmData;
pbApmPtr = ApmFile.CaptureHeader(pbApmPtr, pbApmEnd);
if(pbApmPtr == NULL)
return ERROR_BAD_FORMAT;
// Read the array of entries
pbApmPtr = ApmFile.CaptureArrayOfEntries(pbApmPtr, pbApmEnd);
if(pbApmPtr == NULL)
return ERROR_BAD_FORMAT;
// Read the array of package entries
pbApmPtr = ApmFile.CapturePackageEntries(pbApmPtr, pbApmEnd);
if(pbApmPtr == NULL)
return ERROR_BAD_FORMAT;
CASC_FREE(pbApmData);
}
return dwErrCode;
}
static DWORD LoadCmfFile(TCascStorage * hs, CONTENT_KEY & CKey, const char * szFileName)
{
TCmfFile CmfFile;
LPBYTE pbCmfData;
DWORD cbCmfData = 0;
DWORD dwErrCode = ERROR_BAD_FORMAT;
pbCmfData = LoadInternalFileToMemory(hs, CKey.Value, CASC_OPEN_BY_CKEY, &cbCmfData);
if(pbCmfData != NULL)
{
LPBYTE pbCmfEnd = pbCmfData + cbCmfData;
LPBYTE pbCmfPtr = pbCmfData;
// Capture the CMF header
pbCmfPtr = CmfFile.CaptureHeader(pbCmfPtr, pbCmfEnd);
if(pbCmfPtr == NULL)
return ERROR_BAD_FORMAT;
// if(CmfFile.Magic >= 0x636D6614)
// DecryptCmfFile(
CASC_FREE(pbCmfData);
}
return dwErrCode;
}
*/
int Load(TCascStorage * hs, CASC_CSV & Csv, size_t nFileNameIndex, size_t nCKeyIndex)
{
PCASC_CKEY_ENTRY pCKeyEntry;
// size_t ApmFiles[0x80];
// size_t nApmFiles = 0;
BYTE CKey[MD5_HASH_SIZE];
CASCLIB_UNUSED(hs);
// Keep loading every line until there is something
while(Csv.LoadNextLine())
{
const CASC_CSV_COLUMN & FileName = Csv[CSV_ZERO][nFileNameIndex];
const CASC_CSV_COLUMN & CKeyStr = Csv[CSV_ZERO][nCKeyIndex];
// Retrieve the file name and the content key
if(FileName.szValue && CKeyStr.szValue && CKeyStr.nLength == MD5_STRING_SIZE)
{
// Convert the string CKey to binary
if(BinaryFromString(CKeyStr.szValue, MD5_STRING_SIZE, CKey) == ERROR_SUCCESS)
{
// Find the item in the tree
if((pCKeyEntry = FindCKeyEntry_CKey(hs, CKey)) != NULL)
{
// Insert the file name and the CKey into the tree
FileTree.InsertByName(pCKeyEntry, FileName.szValue);
// If the file name is actually an asset, we need to parse that asset and load files in it
// if(IsApmFileName(szFileName))
// {
// ApmFiles[nApmFiles++] = FileTree_IndexOf(&pRootHandler->FileTree, pFileNode1);
// }
}
}
}
}
/*
// Load all CMF+APM files
if(dwErrCode == ERROR_SUCCESS)
{
for(size_t i = 0; i < nApmFiles; i++)
{
char szApmFile[MAX_PATH + 1];
char szCmfFile[MAX_PATH + 1];
// Get the n-th item and its name
pFileNode1 = (PCASC_FILE_NODE)FileTree_PathAt(&pRootHandler->FileTree, szApmFile, MAX_PATH, ApmFiles[i]);
if(pFileNode1 == NULL)
break;
if(strcmp(szApmFile, "TactManifest\\Win_SPWin_RDEV_LenUS_EExt.apm"))
continue;
// Get the name of thew CMF file
CascStrCopy(szCmfFile, _countof(szCmfFile), szApmFile);
CascStrCopy((char *)GetFileExtension(szCmfFile), 5, ".cmf");
pFileNode2 = (PCASC_FILE_NODE)FileTree_Find(&pRootHandler->FileTree, szCmfFile);
if(pFileNode2 == NULL)
break;
// Create the map of CMF entries
dwErrCode = LoadCmfFile(hs, pFileNode2->CKey, szCmfFile);
if(dwErrCode != ERROR_SUCCESS)
break;
}
}
*/
return ERROR_SUCCESS;
}
};
//-----------------------------------------------------------------------------
// Public functions
// TODO: There is way more files in the Overwatch CASC storage than present in the ROOT file.
DWORD RootHandler_CreateOverwatch(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
{
TRootHandler_OW * pRootHandler = NULL;
CASC_CSV Csv(0, true);
size_t Indices[2];
DWORD dwErrCode;
// Load the ROOT file
dwErrCode = Csv.Load(pbRootFile, cbRootFile);
if(dwErrCode == ERROR_SUCCESS)
{
// Retrieve the indices of the file name and MD5 columns
Indices[0] = Csv.GetColumnIndex("FILENAME");
Indices[1] = Csv.GetColumnIndex("MD5");
// If both indices were found OK, then load the root file
if(Indices[0] != CSV_INVALID_INDEX && Indices[1] != CSV_INVALID_INDEX)
{
pRootHandler = new TRootHandler_OW();
if (pRootHandler != NULL)
{
// Load the root directory. If load failed, we free the object
dwErrCode = pRootHandler->Load(hs, Csv, Indices[0], Indices[1]);
if (dwErrCode != ERROR_SUCCESS)
{
delete pRootHandler;
pRootHandler = NULL;
}
}
}
else
{
dwErrCode = ERROR_BAD_FORMAT;
}
}
// Assign the root directory (or NULL) and return error
hs->pRootHandler = pRootHandler;
return dwErrCode;
}