aboutsummaryrefslogtreecommitdiff
path: root/dep/CascLib/src/overwatch/apm.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dep/CascLib/src/overwatch/apm.cpp')
-rw-r--r--dep/CascLib/src/overwatch/apm.cpp154
1 files changed, 154 insertions, 0 deletions
diff --git a/dep/CascLib/src/overwatch/apm.cpp b/dep/CascLib/src/overwatch/apm.cpp
new file mode 100644
index 00000000000..e4a09506d0d
--- /dev/null
+++ b/dep/CascLib/src/overwatch/apm.cpp
@@ -0,0 +1,154 @@
+/*****************************************************************************/
+/* apm.cpp Copyright (c) Ladislav Zezula 2023 */
+/*---------------------------------------------------------------------------*/
+/* Support for Application Package Manifest (.apm) */
+/* Know-how from https://github.com/overtools/TACTLib */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 02.08.23 1.00 Lad Created */
+/*****************************************************************************/
+
+#define __CASCLIB_SELF__
+#include "../CascLib.h"
+#include "../CascCommon.h"
+
+#include "overwatch.h"
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+template <typename APM_ENTRY>
+static bool IsContinuousArray(APM_ENTRY * pEntry, size_t nCount)
+{
+ // Must be at last 5 entries
+ if(nCount-- >= 5)
+ {
+ for(size_t i = 0; i < nCount; i++, pEntry++)
+ {
+ // n+1-th index must be greater than n-th index
+ if(pEntry[1].Index > pEntry[0].Index + 10)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+static LPBYTE CaptureApmHeader(TCascStorage * hs, LPBYTE pbDataPtr, LPBYTE pbDataEnd, CASC_APM_HEADER & ApmHeader)
+{
+ // Build 47161+
+ PCASC_APM_HEADER_V2 pHeader_V3 = NULL;
+ if(CaptureStructure<CASC_APM_HEADER_V2>(pbDataPtr, pbDataEnd, &pHeader_V3) != NULL)
+ {
+ if(pHeader_V3->BuildNumber == hs->dwBuildNumber &&
+ pHeader_V3->ZeroValue1 == 0 &&
+ pHeader_V3->ZeroValue2 == 0 &&
+ pHeader_V3->ZeroValue3 == 0)
+ {
+ ApmHeader.BuildNumber = pHeader_V3->BuildNumber;
+ ApmHeader.PackageCount = pHeader_V3->PackageCount;
+ ApmHeader.PackageCount = pHeader_V3->PackageCount;
+ ApmHeader.EntryCount = pHeader_V3->EntryCount;
+ ApmHeader.HeaderMagic = pHeader_V3->HeaderMagic;
+ return pbDataPtr + sizeof(CASC_APM_HEADER_V2);
+ }
+ }
+
+ // Build 24919
+ PCASC_APM_HEADER_V1 pHeader_V1 = NULL;
+ if(CaptureStructure<CASC_APM_HEADER_V1>(pbDataPtr, pbDataEnd, &pHeader_V1) != NULL)
+ {
+ if((pHeader_V1->HeaderMagic & 0x00FFFFFF) == CASC_APM_HEADER_MAGIC)
+ {
+ ApmHeader.BuildNumber = pHeader_V1->BuildNumber;
+ ApmHeader.PackageCount = pHeader_V1->PackageCount;
+ ApmHeader.PackageCount = pHeader_V1->PackageCount;
+ ApmHeader.EntryCount = pHeader_V1->EntryCount;
+ ApmHeader.HeaderMagic = pHeader_V1->HeaderMagic;
+ return pbDataPtr + sizeof(CASC_APM_HEADER_V1);
+ }
+ }
+ return NULL;
+}
+
+static LPBYTE SkipApmEntries(TCascStorage * hs, LPBYTE pbDataPtr, LPBYTE pbDataEnd, size_t nCount)
+{
+ PCASC_APM_ENTRY_V2 pApmEntries_V2 = NULL;
+ PCASC_APM_ENTRY_V1 pApmEntries_V1 = NULL;
+ LPBYTE pbSavePtr = pbDataPtr;
+
+ // Keep compiler happy
+ CASCLIB_UNUSED(hs);
+
+ // Try APM entries v2 (example: Overwatch build 47161)
+ if((pbDataPtr = CaptureArray(pbSavePtr, pbDataEnd, &pApmEntries_V2, nCount)) != NULL)
+ {
+ if(IsContinuousArray(pApmEntries_V2, nCount))
+ {
+ return pbDataPtr;
+ }
+ }
+
+ // Try APM entries v1 (example: Overwatch build 24919)
+ if((pbDataPtr = CaptureArray(pbSavePtr, pbDataEnd, &pApmEntries_V1, nCount)) != NULL)
+ {
+ if(IsContinuousArray(pApmEntries_V1, nCount))
+ {
+ return pbDataPtr;
+ }
+ }
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+DWORD LoadApplicationPackageManifestFile(TCascStorage * hs, CASC_FILE_TREE & FileTree, PCASC_CKEY_ENTRY pCKeyEntry, const char * szApmFileName)
+{
+ CASC_BLOB ApmFile;
+ const char * szApmPlainName = GetPlainFileName(szApmFileName);
+ DWORD dwErrCode;
+
+ // Load the entire internal file to memory
+ if((dwErrCode = LoadInternalFileToMemory(hs, pCKeyEntry, ApmFile)) == ERROR_SUCCESS)
+ {
+ PCASC_APM_PACKAGE_ENTRY_V1 pEntries;
+ CASC_APM_HEADER ApmHeader = {0};
+ LPBYTE pbDataEnd = ApmFile.pbData + ApmFile.cbData;
+ LPBYTE pbDataPtr = ApmFile.pbData;
+ size_t nPlainName;
+ char szFileName[MAX_PATH];
+
+ // Capture the header
+ if((pbDataPtr = CaptureApmHeader(hs, pbDataPtr, pbDataEnd, ApmHeader)) == NULL)
+ return ERROR_BAD_FORMAT;
+
+ // Skip the array of APM_ENTRYs. We use heuristics to determine which APM entry is there
+ if((pbDataPtr = SkipApmEntries(hs, pbDataPtr, pbDataEnd, ApmHeader.EntryCount)) == NULL)
+ return ERROR_BAD_FORMAT;
+
+ // Get the array of APM package entries. Only take those with CKey
+ if((pbDataPtr = CaptureArray(pbDataPtr, pbDataEnd, &pEntries, ApmHeader.PackageCount)) != NULL)
+ {
+ // The array must fill the whole file without any leftover
+ if(pbDataPtr == pbDataEnd && ApmHeader.PackageCount > 1)
+ {
+ // Check the first two entries - if their CKey is OK, then we consider them valid
+ if(FindCKeyEntry_CKey(hs, pEntries[0].CKey) != NULL && FindCKeyEntry_CKey(hs, pEntries[1].CKey) != NULL)
+ {
+ // Create the name template of the assets
+ nPlainName = BuildAssetFileNameTemplate(szFileName,
+ _countof(szFileName),
+ "AppPackageManifests",
+ szApmPlainName);
+
+ dwErrCode = InsertAssetFiles(hs, FileTree, szFileName, nPlainName, pEntries, ApmHeader.PackageCount);
+ }
+ }
+ }
+ }
+ return dwErrCode;
+}