aboutsummaryrefslogtreecommitdiff
path: root/dep/CascLib/src/overwatch/apm.cpp
blob: e4a09506d0d58deb94f5b6907713f59d747168d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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;
}