blob: 65cd0ae829f9959b020523b1bf1d48b766fbc62b (
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
155
156
157
158
159
160
|
#pragma once
#include "stringutil.h"
#include <QDir>
#include <QFileInfo>
#include <QString>
#include <QTemporaryDir>
#include <archive.h>
#include <filesystem>
#include <functional>
#include <iostream>
#include <memory>
#include <optional>
struct ExtractionResult {
bool success = false;
QString moduleConfigPath;
std::vector<QString> pluginPaths;
QString errorMessage;
std::unique_ptr<QTemporaryDir> tempDir; // Owns the temp directory lifetime
};
/**
* Utility class to extract FOMOD data from archives without being in an installer context.
* Used by the Patch Wizard's rescan functionality.
*/
class ArchiveExtractor {
public:
using ProgressCallback = std::function<void(const QString& fileName)>;
/**
* Extract ModuleConfig.xml and plugin files from an archive.
* @param archiveFilePath Full path to the archive file
* @param progressCallback Optional callback for progress updates
* @return ExtractionResult containing paths to extracted files
*/
static ExtractionResult extractFomodData(
const QString& archiveFilePath,
const ProgressCallback& progressCallback = nullptr)
{
ExtractionResult result;
result.tempDir = std::make_unique<QTemporaryDir>();
if (!result.tempDir->isValid()) {
result.errorMessage = "Failed to create temporary directory";
return result;
}
const auto archive = CreateArchive();
if (!archive->isValid()) {
result.errorMessage = "Failed to load archive module";
return result;
}
if (!archive->open(archiveFilePath.toStdWString(), nullptr)) {
result.errorMessage = QString("Failed to open archive (error %1)")
.arg(static_cast<int>(archive->getLastError()));
return result;
}
// Get file list and mark files for extraction
const auto& fileList = archive->getFileList();
QString moduleConfigInArchive;
for (auto* fileData : fileList) {
const auto entryPath = QString::fromStdWString(fileData->getArchiveFilePath());
// Check for ModuleConfig.xml
if (entryPath.toLower().endsWith("fomod/moduleconfig.xml") ||
entryPath.toLower().endsWith("fomod\\moduleconfig.xml")) {
moduleConfigInArchive = entryPath;
// Set output path relative to output directory for extract()
fileData->addOutputFilePath(L"ModuleConfig.xml");
result.moduleConfigPath = result.tempDir->filePath("ModuleConfig.xml");
}
// Check for plugin files
else if (isPluginFile(entryPath)) {
const auto fileName = QFileInfo(entryPath).fileName();
const auto relativePath = QString("plugins/") + fileName;
fileData->addOutputFilePath(relativePath.toStdWString());
result.pluginPaths.push_back(result.tempDir->filePath(relativePath));
}
}
if (moduleConfigInArchive.isEmpty()) {
result.errorMessage = "No ModuleConfig.xml found in archive";
return result;
}
// Create plugins subdirectory
QDir(result.tempDir->path()).mkpath("plugins");
// Extract the files
Archive::FileChangeCallback fileChangeCallback = [&progressCallback](
Archive::FileChangeType, const std::wstring& fileName) {
if (progressCallback) {
progressCallback(QString::fromStdWString(fileName));
}
};
Archive::ErrorCallback errorCallback = [&result](const std::wstring& error) {
result.errorMessage = QString::fromStdWString(error);
};
const bool extractSuccess = archive->extract(
result.tempDir->path().toStdWString(),
Archive::ProgressCallback{}, // progress callback
fileChangeCallback,
errorCallback
);
if (!extractSuccess) {
if (result.errorMessage.isEmpty()) {
result.errorMessage = "Extraction failed";
}
return result;
}
// Verify ModuleConfig.xml was extracted
if (!QFile::exists(result.moduleConfigPath)) {
result.errorMessage = "ModuleConfig.xml extraction failed";
return result;
}
// Filter to only existing plugin files
std::vector<QString> existingPlugins;
for (const auto& path : result.pluginPaths) {
if (QFile::exists(path)) {
existingPlugins.push_back(path);
}
}
result.pluginPaths = std::move(existingPlugins);
result.success = true;
return result;
}
/**
* Check if an archive contains FOMOD files without extracting.
* @param archiveFilePath Full path to the archive file
* @return true if the archive contains fomod/ModuleConfig.xml
*/
static bool hasFomodFiles(const QString& archiveFilePath)
{
const auto archive = CreateArchive();
if (!archive->isValid() || !archive->open(archiveFilePath.toStdWString(), nullptr)) {
return false;
}
for (const auto* fileData : archive->getFileList()) {
const auto path = QString::fromStdWString(fileData->getArchiveFilePath());
if (path.toLower().endsWith("fomod/moduleconfig.xml") ||
path.toLower().endsWith("fomod\\moduleconfig.xml")) {
return true;
}
}
return false;
}
};
|