aboutsummaryrefslogtreecommitdiff
path: root/dep/CascLib/src/overwatch/cmf.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dep/CascLib/src/overwatch/cmf.cpp')
-rw-r--r--dep/CascLib/src/overwatch/cmf.cpp221
1 files changed, 221 insertions, 0 deletions
diff --git a/dep/CascLib/src/overwatch/cmf.cpp b/dep/CascLib/src/overwatch/cmf.cpp
new file mode 100644
index 00000000000..77672108bde
--- /dev/null
+++ b/dep/CascLib/src/overwatch/cmf.cpp
@@ -0,0 +1,221 @@
+/*****************************************************************************/
+/* cmf.cpp Copyright (c) Ladislav Zezula 2023 */
+/*---------------------------------------------------------------------------*/
+/* Support for Content Manifest Files (.cmf) */
+/* Know-how from https://github.com/overtools/TACTLib */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 29.07.23 1.00 Lad Created */
+/*****************************************************************************/
+
+#define __CASCLIB_SELF__
+#include "../CascLib.h"
+#include "../CascCommon.h"
+
+#include "aes.h"
+#include "overwatch.h"
+
+//-----------------------------------------------------------------------------
+// Encryption key providers for CMF files. These are taken from TACTLib
+// with the kind permission of the TACTLib authors
+// (https://github.com/overtools/TACTLib)
+
+// Key and IV provider functions
+typedef LPBYTE(*GET_KEY)(const CASC_CMF_HEADER & Header, LPBYTE pbKey, int nLength);
+typedef LPBYTE(*GET_IV)(const CASC_CMF_HEADER & Header, LPBYTE nameSha1, LPBYTE pbKey, int nLength);
+
+// Structure for the single provider
+typedef struct _CASC_CMF_KEY_PROVIDER
+{
+ DWORD dwBuildNumber;
+ GET_KEY PfnGetKey;
+ GET_IV PfnGetIV;
+} CASC_CMF_KEY_PROVIDER;
+typedef const CASC_CMF_KEY_PROVIDER *PCASC_CMF_KEY_PROVIDER;
+
+// Needed by various providers in the cmf-key.cpp file
+struct TMath
+{
+ template <typename TYPE>
+ TYPE Max(TYPE value1, TYPE value2)
+ {
+ return (value1 > value2) ? value1 : value2;
+ }
+ DWORD dwDummy;
+} Math;
+
+// Needed by various providers in the cmf-key.cpp file
+static uint Constrain(LONGLONG value)
+{
+ return (uint)(value % 0xFFFFFFFFULL);
+}
+
+// Needed by various providers in the cmf-key.cpp file
+static int SignedMod(LONGLONG p1, LONGLONG p2)
+{
+ int a = (int)p1;
+ int b = (int)p2;
+ return (a % b) < 0 ? (a % b + b) : (a % b);
+}
+
+// Include the CMF key provider functions and the table of providers
+// This file is created by the "cmf-update.py" script, DO NOT EDIT.
+#include "cmf-key.cpp"
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static PCASC_CMF_KEY_PROVIDER FindCmfKeyProvider(DWORD dwBuildNumber)
+{
+ PCASC_CMF_KEY_PROVIDER pStartEntry = CmfKeyProviders;
+ PCASC_CMF_KEY_PROVIDER pMidleEntry = NULL;
+ PCASC_CMF_KEY_PROVIDER pFinalEntry = &CmfKeyProviders[_countof(CmfKeyProviders)];
+
+ // Perform binary search on the table
+ while(pStartEntry < pFinalEntry)
+ {
+ // Calculate the middle of the interval
+ pMidleEntry = pStartEntry + ((pFinalEntry - pStartEntry) / 2);
+
+ // Did we find it?
+ if(dwBuildNumber == pMidleEntry->dwBuildNumber)
+ return pMidleEntry;
+
+ // Move the interval to the left or right
+ if(dwBuildNumber > pMidleEntry->dwBuildNumber)
+ pStartEntry = pMidleEntry + 1;
+ else
+ pFinalEntry = pMidleEntry;
+ }
+/*
+ for(size_t i = 0; i < _countof(CmfKeyProviders); i++)
+ {
+ if(CmfKeyProviders[i].dwBuildNumber == dwBuildNumber)
+ {
+ return &CmfKeyProviders[i];
+ }
+ }
+*/
+ return NULL;
+}
+
+static DWORD DecryptCmfStream(const CASC_CMF_HEADER & Header, const char * szPlainName, LPBYTE pbDataPtr, LPBYTE pbDataEnd)
+{
+ PCASC_CMF_KEY_PROVIDER pKeyProvider;
+ AES_KEY AesKey;
+ BYTE RawKey[CASC_AES_KEY_LENGTH];
+ BYTE RawIV[CASC_AES_IV_LENGTH];
+ BYTE nameDigest[SHA1_HASH_SIZE];
+
+ // Find the provider for that Overwatch build
+ if((pKeyProvider = FindCmfKeyProvider(Header.m_buildVersion)) == NULL)
+ return ERROR_FILE_ENCRYPTED;
+
+ // Create SHA1 from the file name
+ CascHash_SHA1(szPlainName, strlen(szPlainName), nameDigest);
+
+ // Retrieve key and IV
+ pKeyProvider->PfnGetKey(Header, RawKey, sizeof(RawKey));
+ pKeyProvider->PfnGetIV(Header, nameDigest, RawIV, sizeof(RawIV));
+
+ // Decrypt the stream using AES
+ AES_set_decrypt_key(RawKey, 256, &AesKey);
+ AES_cbc_decrypt(pbDataPtr, pbDataPtr, (pbDataEnd - pbDataPtr), &AesKey, RawIV);
+ return ERROR_SUCCESS;
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+DWORD LoadContentManifestFile(TCascStorage * hs, CASC_FILE_TREE & FileTree, PCASC_CKEY_ENTRY pCKeyEntry, const char * szCmfFileName)
+{
+ CASC_BLOB CmfFile;
+ const char * szCmfPlainName = GetPlainFileName(szCmfFileName);
+ DWORD dwErrCode;
+
+ // Load the entire internal file to memory
+ if((dwErrCode = LoadInternalFileToMemory(hs, pCKeyEntry, CmfFile)) == ERROR_SUCCESS)
+ {
+ PCASC_APM_ENTRY_V2 pApmEntries = NULL;
+ CASC_CMF_HEADER CmfHeader = {0};
+ LPBYTE pbDataEnd = CmfFile.pbData + CmfFile.cbData;
+ LPBYTE pbDataPtr = CmfFile.pbData;
+ size_t nPlainName;
+ DWORD dwBuildVersion;
+ char szFileName[MAX_PATH];
+
+ // Get the build version
+ if((pbDataPtr = CaptureInteger32(pbDataPtr, pbDataEnd, &dwBuildVersion)) == NULL)
+ return ERROR_BAD_FORMAT;
+ pbDataPtr = CmfFile.pbData;
+
+ // Parse headers of various versions
+ if(dwBuildVersion > CASC_OVERWATCH_VERSION_148_PTR)
+ {
+ CASC_CMF_HEADER_148 * pHeader148;
+
+ if((pbDataPtr = CaptureStructure(pbDataPtr, pbDataEnd, &pHeader148)) == NULL)
+ return ERROR_BAD_FORMAT;
+ CmfHeader = *pHeader148;
+ }
+ else if(dwBuildVersion > CASC_OVERWATCH_VERSION_122_PTR)
+ {
+ CASC_CMF_HEADER_122 * pHeader122;
+
+ if((pbDataPtr = CaptureStructure(pbDataPtr, pbDataEnd, &pHeader122)) == NULL)
+ return ERROR_BAD_FORMAT;
+ CmfHeader = *pHeader122;
+ }
+ else
+ {
+ CASC_CMF_HEADER_100 * pHeader100;
+
+ if((pbDataPtr = CaptureStructure(pbDataPtr, pbDataEnd, &pHeader100)) == NULL)
+ return ERROR_BAD_FORMAT;
+ CmfHeader = *pHeader100;
+ }
+
+ // Decrypt the stream, if needed
+ if(CmfHeader.IsEncrypted())
+ {
+ if((dwErrCode = DecryptCmfStream(CmfHeader, szCmfPlainName, pbDataPtr, pbDataEnd)) != ERROR_SUCCESS)
+ {
+ return dwErrCode;
+ }
+ }
+
+ // Skip APM entries. We don't need them for anything, really
+ if((pbDataPtr = CaptureArray(pbDataPtr, pbDataEnd, &pApmEntries, CmfHeader.m_entryCount)) == NULL)
+ {
+ return ERROR_BAD_FORMAT;
+ }
+
+ // Create the name template of the assets
+ nPlainName = BuildAssetFileNameTemplate(szFileName,
+ _countof(szFileName),
+ "ContentManifestFiles",
+ szCmfPlainName);
+
+ // Load the hash list This is the list of Asset ID -> CKey
+ if(CmfHeader.m_buildVersion >= 57230)
+ {
+ PCASC_CMF_HASH_ENTRY_135 pHashList;
+
+ if((pbDataPtr = CaptureArray(pbDataPtr, pbDataEnd, &pHashList, CmfHeader.m_dataCount)) == NULL)
+ return ERROR_BAD_FORMAT;
+
+ dwErrCode = InsertAssetFiles(hs, FileTree, szFileName, nPlainName, pHashList, CmfHeader.m_dataCount);
+ }
+ else
+ {
+ PCASC_CMF_HASH_ENTRY_100 pHashList;
+
+ if((pbDataPtr = CaptureArray(pbDataPtr, pbDataEnd, &pHashList, CmfHeader.m_dataCount)) == NULL)
+ return ERROR_BAD_FORMAT;
+
+ dwErrCode = InsertAssetFiles(hs, FileTree, szFileName, nPlainName, pHashList, CmfHeader.m_dataCount);
+ }
+ }
+ return dwErrCode;
+}