aboutsummaryrefslogtreecommitdiff
path: root/libs/installer_fomod_plus/share/FOMODData/PluginReader.h
blob: dade7e19d45c5c17b6d6b4d30d272685beae16f6 (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
#pragma once

#include <fstream>
#include <string>
#include <vector>
#include <cstdint>
#include <unordered_set>

static const std::unordered_set<std::string> VANILLA_MASTERS = {
    "Skyrim.esm",
    "Update.esm",
    "Dawnguard.esm",
    "HearthFires.esm",
    "Dragonborn.esm"
};

class PluginReader {
public:

    /**
     * Reads the master files from a Bethesda plugin file (ESP/ESM/ESL)
     * @param filePath Path to the plugin file
     * @param trimVanilla Exclude the vanilla game masters or not. Mostly to save DB space.
     * @return Vector of master filenames
     */
    static std::vector<std::string> readMasters(const std::string& filePath, const bool trimVanilla = false)
    {
        std::vector<std::string> masters;
        std::ifstream file(filePath, std::ios::binary);

        if (!file) {
            return masters;
        }

        // Check TES4 record signature
        char signature[4];
        file.read(signature, 4);
        if (strncmp(signature, "TES4", 4) != 0) {
            return masters;
        }

        // Read record size
        uint32_t recordSize;
        file.read(reinterpret_cast<char*>(&recordSize), 4);

        constexpr uint32_t skipSize = sizeof(uint32_t) // flags
        + sizeof(uint32_t) // formId
        + sizeof(uint16_t) // timestamp
        + sizeof(uint16_t) // version control
        + sizeof(uint16_t) // internal version
        + sizeof(uint16_t); // unknown

        // Skip header flags, formID, etc. (total 8 bytes)
        file.seekg(skipSize, std::ios::cur);

        // Calculate where the TES4 record ends
        std::streampos recordEnd = file.tellg() + static_cast<std::streampos>(recordSize);

        // Read subrecords until we reach the end of the TES4 record
        while (file && file.tellg() < recordEnd) {
            char subRecordType[4];
            uint16_t subRecordSize;

            // Read subrecord type and size
            file.read(subRecordType, 4);
            file.read(reinterpret_cast<char*>(&subRecordSize), 2);

            if (strncmp(subRecordType, "MAST", 4) == 0) {
                // Read master filename (null-terminated string)
                std::string masterName;
                masterName.resize(subRecordSize);
                file.read(masterName.data(), subRecordSize);

                // Remove null terminator if present
                if (!masterName.empty() && masterName.back() == '\0') {
                    masterName.pop_back();
                }

                // Only add if it's not a vanilla master or if we're not trimming
                if (!trimVanilla || !VANILLA_MASTERS.contains(masterName)) {
                    masters.push_back(masterName);
                }

                // Each MAST is followed by a DATA subrecord
                char dataType[4];
                uint16_t dataSize;
                file.read(dataType, 4);
                file.read(reinterpret_cast<char*>(&dataSize), 2);

                // Skip DATA content (usually an 8-byte value)
                file.seekg(dataSize, std::ios::cur);
            } else {
                // Skip other subrecord types
                file.seekg(subRecordSize, std::ios::cur);
            }
        }

        return masters;
    }

    /**
     * Checks if a file is a valid Bethesda plugin (ESP/ESM/ESL)
     * @param filePath Path to the file
     * @return True if the file is a valid plugin
     */
    static bool isValidPlugin(const std::string& filePath)
    {
        std::ifstream file(filePath, std::ios::binary);

        if (!file) {
            return false;
        }

        char signature[4];
        file.read(signature, 4);

        return strncmp(signature, "TES4", 4) == 0;
    }
};