640 lines
22 KiB
C++
640 lines
22 KiB
C++
#include "modloaderinstaller.h"
|
|
#include "localization.h"
|
|
|
|
#include "gamelauncher.h"
|
|
#include "javalocator.h"
|
|
#include "launcherpaths.h"
|
|
#include "minecraftversion.h"
|
|
#include "modloaderversionservice.h"
|
|
#include "versioninstaller.h"
|
|
#include "zlibreference.h"
|
|
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QFile>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QNetworkAccessManager>
|
|
#include <QNetworkReply>
|
|
#include <QNetworkRequest>
|
|
#include <QProcess>
|
|
#include <QProcessEnvironment>
|
|
#include <QSaveFile>
|
|
#include <QTimer>
|
|
|
|
#include <algorithm>
|
|
|
|
namespace {
|
|
|
|
constexpr int kTransferTimeoutMs = 30000;
|
|
constexpr int kInstallerTimeoutMs = 15 * 60 * 1000;
|
|
constexpr int kLogTailLines = 12;
|
|
|
|
const QString kFabricProfile = QStringLiteral("https://meta.fabricmc.net/v2/versions/loader/");
|
|
const QString kQuiltProfile = QStringLiteral("https://meta.quiltmc.org/v3/versions/loader/");
|
|
|
|
bool isSafeSegment(const QString &value)
|
|
{
|
|
if (value.isEmpty() || value.contains(u'/') || value.contains(u'\\') || value.contains(u':'))
|
|
return false;
|
|
return value != QLatin1String(".") && value != QLatin1String("..");
|
|
}
|
|
|
|
QString versionJsonPath(const QString &gameDir, const QString &id)
|
|
{
|
|
return gameDir + QStringLiteral("/versions/") + id + u'/' + id + QStringLiteral(".json");
|
|
}
|
|
|
|
}
|
|
|
|
ModLoaderInstaller::ModLoaderInstaller(ModLoaderVersionService *meta,
|
|
VersionInstaller *versionInstaller,
|
|
QObject *parent)
|
|
: QObject(parent)
|
|
, m_meta(meta)
|
|
, m_versionInstaller(versionInstaller)
|
|
, m_network(new QNetworkAccessManager(this))
|
|
{
|
|
connect(m_versionInstaller, &VersionInstaller::finished, this, [this](const QString &id) {
|
|
if (m_awaitingVersionId.isEmpty() || id != m_awaitingVersionId)
|
|
return;
|
|
const QString versionId = m_awaitingVersionId;
|
|
const QString loader = loaderKey(m_loader);
|
|
const QString game = m_gameVersion;
|
|
const QString version = m_loaderVersion;
|
|
reset();
|
|
emit finished(loader, game, version, versionId);
|
|
});
|
|
connect(m_versionInstaller, &VersionInstaller::failed, this,
|
|
[this](const QString &id, const QString &message) {
|
|
if (m_awaitingVersionId.isEmpty() || id != m_awaitingVersionId)
|
|
return;
|
|
fail(message);
|
|
});
|
|
connect(m_versionInstaller, &VersionInstaller::canceled, this, [this](const QString &id) {
|
|
if (m_awaitingVersionId.isEmpty() || id != m_awaitingVersionId)
|
|
return;
|
|
const QString label = m_label;
|
|
reset();
|
|
emit canceled(label);
|
|
});
|
|
}
|
|
|
|
ModLoaderInstaller::~ModLoaderInstaller()
|
|
{
|
|
if (m_process && m_process->state() != QProcess::NotRunning) {
|
|
m_process->kill();
|
|
m_process->waitForFinished(2000);
|
|
}
|
|
}
|
|
|
|
double ModLoaderInstaller::fraction() const
|
|
{
|
|
if (m_bytesTotal <= 0)
|
|
return -1.0;
|
|
return double(m_bytesDone) / double(m_bytesTotal);
|
|
}
|
|
|
|
void ModLoaderInstaller::install(const QString &gameDir,
|
|
ModLoader loader,
|
|
const QString &gameVersion,
|
|
const QString &loaderVersion,
|
|
const QString &javaPreference)
|
|
{
|
|
if (m_running) {
|
|
emit failed(loaderTitle(loader), Loc::text("loader.error.installInProgress"));
|
|
return;
|
|
}
|
|
if (gameVersion.trimmed().isEmpty() || loaderVersion.trimmed().isEmpty()) {
|
|
emit failed(loaderTitle(loader), Loc::text("loader.error.noVersionSelected").arg(loaderTitle(loader)));
|
|
return;
|
|
}
|
|
|
|
m_gameDir = gameDir;
|
|
m_loader = loader;
|
|
m_gameVersion = gameVersion.trimmed();
|
|
m_loaderVersion = loaderVersion.trimmed();
|
|
m_javaPreference = javaPreference;
|
|
m_label = QStringLiteral("%1 %2").arg(loaderTitle(loader), m_loaderVersion);
|
|
m_installerLog.clear();
|
|
m_bytesDone = 0;
|
|
m_bytesTotal = 0;
|
|
m_currentFile.clear();
|
|
m_canceling = false;
|
|
m_running = true;
|
|
|
|
emit started(m_label);
|
|
setStage(Loc::text("loader.progress.preparing").arg(loaderTitle(loader)));
|
|
|
|
m_meta->ensureLoaded(loader, m_gameVersion, [this, loader](bool ok, const QString &warning) {
|
|
if (!m_running || m_canceling)
|
|
return;
|
|
if (!ok) {
|
|
fail(warning.isEmpty() ? Loc::text("loader.error.listUnavailable").arg(loaderTitle(loader))
|
|
: warning);
|
|
return;
|
|
}
|
|
ensureBaseVersion([this, loader] {
|
|
if (loader == ModLoader::Fabric || loader == ModLoader::Quilt)
|
|
beginFabricLike();
|
|
else
|
|
beginInstallerJar();
|
|
});
|
|
});
|
|
}
|
|
|
|
void ModLoaderInstaller::cancel()
|
|
{
|
|
if (!m_running)
|
|
return;
|
|
m_canceling = true;
|
|
|
|
if (m_reply) {
|
|
m_reply->abort();
|
|
m_reply = nullptr;
|
|
}
|
|
if (m_process && m_process->state() != QProcess::NotRunning) {
|
|
m_process->kill();
|
|
m_process->waitForFinished(2000);
|
|
}
|
|
if (m_installerStarted)
|
|
removeVersionsAddedByInstaller();
|
|
if (!m_awaitingVersionId.isEmpty()) {
|
|
m_versionInstaller->cancel();
|
|
return;
|
|
}
|
|
|
|
const QString label = m_label;
|
|
reset();
|
|
emit canceled(label);
|
|
}
|
|
|
|
void ModLoaderInstaller::beginFabricLike()
|
|
{
|
|
const auto entry = m_meta->find(m_loader, m_gameVersion, m_loaderVersion);
|
|
const QString expectedId =
|
|
entry ? entry->versionId
|
|
: (m_loader == ModLoader::Fabric ? QStringLiteral("fabric-loader-")
|
|
: QStringLiteral("quilt-loader-"))
|
|
+ m_loaderVersion + u'-' + m_gameVersion;
|
|
|
|
if (!isSafeSegment(expectedId)) {
|
|
fail(Loc::text("loader.error.invalidVersionId").arg(expectedId));
|
|
return;
|
|
}
|
|
|
|
const QString base = m_loader == ModLoader::Fabric ? kFabricProfile : kQuiltProfile;
|
|
const QUrl url(base + m_gameVersion + u'/' + m_loaderVersion + QStringLiteral("/profile/json"));
|
|
|
|
setStage(Loc::text("loader.progress.description").arg(loaderTitle(m_loader)));
|
|
m_currentFile = expectedId + QStringLiteral(".json");
|
|
emit progressChanged();
|
|
|
|
QNetworkRequest request{url};
|
|
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
|
|
QNetworkRequest::NoLessSafeRedirectPolicy);
|
|
request.setTransferTimeout(kTransferTimeoutMs);
|
|
|
|
m_reply = m_network->get(request);
|
|
connect(m_reply, &QNetworkReply::finished, this, [this, expectedId] {
|
|
QNetworkReply *reply = m_reply;
|
|
m_reply = nullptr;
|
|
reply->deleteLater();
|
|
if (!m_running || m_canceling)
|
|
return;
|
|
|
|
if (reply->error() != QNetworkReply::NoError) {
|
|
fail(Loc::text("loader.error.descriptionFetchFailed")
|
|
.arg(loaderTitle(m_loader), reply->errorString()));
|
|
return;
|
|
}
|
|
|
|
const QByteArray payload = reply->readAll();
|
|
const QJsonObject root = QJsonDocument::fromJson(payload).object();
|
|
const QString id = root.value(QStringLiteral("id")).toString();
|
|
const QString inherits = root.value(QStringLiteral("inheritsFrom")).toString();
|
|
|
|
if (id.isEmpty() || !isSafeSegment(id)) {
|
|
fail(Loc::text("loader.error.descriptionBadId")
|
|
.arg(loaderTitle(m_loader)));
|
|
return;
|
|
}
|
|
if (inherits != m_gameVersion) {
|
|
fail(Loc::text("loader.error.descriptionWrongMinecraft")
|
|
.arg(loaderTitle(m_loader), inherits, m_gameVersion));
|
|
return;
|
|
}
|
|
if (id != expectedId)
|
|
emit log(Loc::text("loader.status.versionId").arg(id));
|
|
|
|
const QString directory = m_gameDir + QStringLiteral("/versions/") + id;
|
|
if (!QDir().mkpath(directory)) {
|
|
fail(Loc::text("common.error.createFailed").arg(QDir::toNativeSeparators(directory)));
|
|
return;
|
|
}
|
|
|
|
QSaveFile file(versionJsonPath(m_gameDir, id));
|
|
if (!file.open(QIODevice::WriteOnly) || file.write(payload) != payload.size()
|
|
|| !file.commit()) {
|
|
file.cancelWriting();
|
|
fail(Loc::text("common.error.versionSaveFailed").arg(id));
|
|
return;
|
|
}
|
|
|
|
handOffToVersionInstaller(id);
|
|
});
|
|
}
|
|
|
|
void ModLoaderInstaller::beginInstallerJar()
|
|
{
|
|
const auto entry = m_meta->find(m_loader, m_gameVersion, m_loaderVersion);
|
|
if (!entry || !entry->installerUrl.isValid()) {
|
|
fail(Loc::text("loader.error.incompatible")
|
|
.arg(loaderTitle(m_loader), m_loaderVersion, m_gameVersion));
|
|
return;
|
|
}
|
|
if (entry->installerUrl.scheme() != QLatin1String("https")) {
|
|
fail(Loc::text("loader.error.installerBadUrl").arg(loaderTitle(m_loader)));
|
|
return;
|
|
}
|
|
downloadInstaller(entry->installerUrl, [this](const QString &path) { runInstaller(path); });
|
|
}
|
|
|
|
void ModLoaderInstaller::ensureBaseVersion(std::function<void()> next)
|
|
{
|
|
QString loadError;
|
|
const MinecraftVersion installed =
|
|
VersionLoader::load(m_gameDir, m_gameVersion, {}, &loadError);
|
|
if (installed.isValid()) {
|
|
LaunchOptions probe;
|
|
probe.gameDir = m_gameDir;
|
|
probe.versionId = m_gameVersion;
|
|
if (GameLauncher::missingFiles(probe, installed, 1).isEmpty()) {
|
|
next();
|
|
return;
|
|
}
|
|
}
|
|
|
|
setStage(Loc::text("loader.progress.installingMinecraft").arg(m_gameVersion));
|
|
m_currentFile = m_gameVersion;
|
|
emit progressChanged();
|
|
|
|
const QString base = m_gameVersion;
|
|
auto guard = std::make_shared<QMetaObject::Connection>();
|
|
auto failGuard = std::make_shared<QMetaObject::Connection>();
|
|
|
|
*guard = connect(m_versionInstaller, &VersionInstaller::finished, this,
|
|
[this, base, guard, failGuard, next](const QString &id) {
|
|
if (id != base)
|
|
return;
|
|
disconnect(*guard);
|
|
disconnect(*failGuard);
|
|
if (!m_running || m_canceling)
|
|
return;
|
|
next();
|
|
});
|
|
*failGuard = connect(m_versionInstaller, &VersionInstaller::failed, this,
|
|
[this, base, guard, failGuard](const QString &id, const QString &message) {
|
|
if (id != base)
|
|
return;
|
|
disconnect(*guard);
|
|
disconnect(*failGuard);
|
|
fail(message);
|
|
});
|
|
|
|
m_versionInstaller->install(m_gameDir, base);
|
|
}
|
|
|
|
void ModLoaderInstaller::downloadInstaller(const QUrl &url, std::function<void(const QString &)> next)
|
|
{
|
|
if (!LauncherPaths::ensureRootExists()) {
|
|
fail(Loc::text("loader.error.launcherDirUnavailable"));
|
|
return;
|
|
}
|
|
|
|
const QString target = LauncherPaths::loaderDownloadDir() + u'/' + loaderKey(m_loader) + u'-'
|
|
+ QString(m_loaderVersion).replace(u'/', u'_')
|
|
+ QStringLiteral("-installer.jar");
|
|
|
|
setStage(Loc::text("loader.progress.downloadingInstaller").arg(loaderTitle(m_loader)));
|
|
m_currentFile = QFileInfo(target).fileName();
|
|
emit progressChanged();
|
|
|
|
QNetworkRequest request{url};
|
|
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
|
|
QNetworkRequest::NoLessSafeRedirectPolicy);
|
|
request.setTransferTimeout(kTransferTimeoutMs);
|
|
|
|
m_reply = m_network->get(request);
|
|
connect(m_reply, &QNetworkReply::downloadProgress, this,
|
|
[this](qint64 received, qint64 total) {
|
|
m_bytesDone = received;
|
|
m_bytesTotal = total;
|
|
emit progressChanged();
|
|
});
|
|
connect(m_reply, &QNetworkReply::finished, this, [this, target, next] {
|
|
QNetworkReply *reply = m_reply;
|
|
m_reply = nullptr;
|
|
reply->deleteLater();
|
|
if (!m_running || m_canceling)
|
|
return;
|
|
|
|
if (reply->error() != QNetworkReply::NoError) {
|
|
fail(Loc::text("loader.error.installerDownloadFailed")
|
|
.arg(loaderTitle(m_loader), reply->errorString()));
|
|
return;
|
|
}
|
|
|
|
const QByteArray payload = reply->readAll();
|
|
QSaveFile file(target);
|
|
if (!file.open(QIODevice::WriteOnly) || file.write(payload) != payload.size()
|
|
|| !file.commit()) {
|
|
file.cancelWriting();
|
|
fail(Loc::text("loader.error.installerSaveFailed").arg(loaderTitle(m_loader)));
|
|
return;
|
|
}
|
|
next(target);
|
|
});
|
|
}
|
|
|
|
bool ModLoaderInstaller::writeLauncherProfilesStub()
|
|
{
|
|
const QString path = m_gameDir + QStringLiteral("/launcher_profiles.json");
|
|
if (QFile::exists(path))
|
|
return true;
|
|
if (!QDir().mkpath(m_gameDir))
|
|
return false;
|
|
|
|
const QJsonObject stub{
|
|
{QStringLiteral("profiles"), QJsonObject{}},
|
|
{QStringLiteral("selectedProfile"), QString()},
|
|
{QStringLiteral("clientToken"), QString()},
|
|
{QStringLiteral("authenticationDatabase"), QJsonObject{}},
|
|
{QStringLiteral("launcherVersion"),
|
|
QJsonObject{{QStringLiteral("name"), QString()},
|
|
{QStringLiteral("format"), 21},
|
|
{QStringLiteral("profilesFormat"), 2}}}};
|
|
|
|
QSaveFile file(path);
|
|
if (!file.open(QIODevice::WriteOnly))
|
|
return false;
|
|
const QByteArray payload = QJsonDocument(stub).toJson(QJsonDocument::Indented);
|
|
if (file.write(payload) != payload.size()) {
|
|
file.cancelWriting();
|
|
return false;
|
|
}
|
|
return file.commit();
|
|
}
|
|
|
|
void ModLoaderInstaller::runInstaller(const QString &installerPath)
|
|
{
|
|
if (!writeLauncherProfilesStub()) {
|
|
fail(Loc::text("loader.error.noLauncherProfiles")
|
|
.arg(loaderTitle(m_loader)));
|
|
return;
|
|
}
|
|
|
|
QString loadError;
|
|
const MinecraftVersion base = VersionLoader::load(m_gameDir, m_gameVersion, {}, &loadError);
|
|
const int requiredMajor = base.isValid() ? base.javaMajor : 8;
|
|
|
|
QString javaError;
|
|
const QString java = JavaLocator::select(m_gameDir, requiredMajor, m_javaPreference, &javaError);
|
|
if (java.isEmpty()) {
|
|
fail(javaError.isEmpty() ? Loc::text("loader.error.noJava")
|
|
.arg(loaderTitle(m_loader))
|
|
: javaError);
|
|
return;
|
|
}
|
|
|
|
m_versionsBefore = VersionLoader::installedVersions(m_gameDir);
|
|
m_installerStarted = true;
|
|
m_installerLogPath = LauncherPaths::loaderDownloadDir() + u'/' + loaderKey(m_loader) + u'-'
|
|
+ QString(m_loaderVersion).replace(u'/', u'_')
|
|
+ QStringLiteral("-install.log");
|
|
m_installerLogFile = new QFile(m_installerLogPath, this);
|
|
if (!m_installerLogFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
|
delete m_installerLogFile;
|
|
m_installerLogFile = nullptr;
|
|
m_installerLogPath.clear();
|
|
}
|
|
|
|
setStage(Loc::text("loader.progress.installing").arg(loaderTitle(m_loader)));
|
|
m_currentFile = Loc::text("loader.progress.installerRunning");
|
|
m_bytesDone = 0;
|
|
m_bytesTotal = 0;
|
|
emit progressChanged();
|
|
|
|
m_process = new QProcess(this);
|
|
m_process->setProgram(java);
|
|
m_process->setArguments({QStringLiteral("-jar"), installerPath,
|
|
QStringLiteral("--installClient"), QDir::toNativeSeparators(m_gameDir)});
|
|
m_process->setWorkingDirectory(LauncherPaths::loaderDownloadDir());
|
|
m_process->setProcessChannelMode(QProcess::MergedChannels);
|
|
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
|
|
QString zlibNote;
|
|
m_zlibNgUnpatched = !ZlibReference::applyTo(environment, &zlibNote);
|
|
if (!zlibNote.isEmpty()) {
|
|
appendInstallerLog(zlibNote);
|
|
emit log(zlibNote);
|
|
}
|
|
m_process->setProcessEnvironment(environment);
|
|
|
|
connect(m_process, &QProcess::readyReadStandardOutput, this, [this] {
|
|
const QStringList lines = QString::fromLocal8Bit(m_process->readAllStandardOutput())
|
|
.split(u'\n', Qt::SkipEmptyParts);
|
|
for (const QString &line : lines) {
|
|
const QString text = line.trimmed();
|
|
m_installerLog << text;
|
|
appendInstallerLog(text);
|
|
emit log(text);
|
|
}
|
|
while (m_installerLog.size() > kLogTailLines)
|
|
m_installerLog.removeFirst();
|
|
});
|
|
connect(m_process, &QProcess::finished, this,
|
|
[this](int exitCode, QProcess::ExitStatus status) {
|
|
handleInstallerFinished(exitCode, status == QProcess::CrashExit);
|
|
});
|
|
connect(m_process, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
|
|
if (m_canceling || error == QProcess::Crashed)
|
|
return;
|
|
fail(Loc::text("loader.error.installerStartFailed")
|
|
.arg(loaderTitle(m_loader), m_process->errorString()));
|
|
});
|
|
|
|
m_process->start();
|
|
if (!m_process->waitForStarted(kTransferTimeoutMs)) {
|
|
fail(Loc::text("loader.error.installerFailedToStart")
|
|
.arg(loaderTitle(m_loader), m_process->errorString()));
|
|
return;
|
|
}
|
|
|
|
QTimer::singleShot(kInstallerTimeoutMs, this, [this] {
|
|
if (m_process && m_process->state() != QProcess::NotRunning) {
|
|
m_process->kill();
|
|
fail(Loc::text("loader.error.installerTimeout").arg(loaderTitle(m_loader)));
|
|
}
|
|
});
|
|
}
|
|
|
|
void ModLoaderInstaller::handleInstallerFinished(int exitCode, bool crashed)
|
|
{
|
|
if (m_process) {
|
|
m_process->deleteLater();
|
|
m_process = nullptr;
|
|
}
|
|
closeInstallerLog();
|
|
if (!m_running || m_canceling)
|
|
return;
|
|
|
|
if (crashed || exitCode != 0) {
|
|
removeVersionsAddedByInstaller();
|
|
QString message = Loc::text("loader.error.installerExitCode")
|
|
.arg(loaderTitle(m_loader))
|
|
.arg(exitCode);
|
|
const QString hint = installerFailureHint();
|
|
if (!hint.isEmpty())
|
|
message += u'\n' + hint;
|
|
if (!m_installerLog.isEmpty())
|
|
message += u'\n' + m_installerLog.join(u'\n');
|
|
if (!m_installerLogPath.isEmpty())
|
|
message += u'\n'
|
|
+ Loc::text("loader.error.fullLog").arg(QDir::toNativeSeparators(m_installerLogPath));
|
|
fail(message);
|
|
return;
|
|
}
|
|
|
|
QStringList added = versionsAddedByInstaller();
|
|
if (added.isEmpty()) {
|
|
const QStringList dependents = VersionLoader::dependentsOf(m_gameDir, m_gameVersion);
|
|
for (const QString &id : dependents) {
|
|
if (id.contains(m_loaderVersion))
|
|
added << id;
|
|
}
|
|
}
|
|
|
|
if (added.isEmpty()) {
|
|
QString message = Loc::text("loader.error.noVersionProduced")
|
|
.arg(loaderTitle(m_loader),
|
|
QDir::toNativeSeparators(m_gameDir
|
|
+ QStringLiteral("/versions")));
|
|
if (!m_installerLog.isEmpty())
|
|
message += u'\n' + m_installerLog.join(u'\n');
|
|
fail(message);
|
|
return;
|
|
}
|
|
if (added.size() > 1) {
|
|
const auto match = std::find_if(added.cbegin(), added.cend(), [this](const QString &id) {
|
|
return id.contains(m_loaderVersion);
|
|
});
|
|
added = {match != added.cend() ? *match : added.first()};
|
|
}
|
|
|
|
m_installerStarted = false;
|
|
m_versionsBefore.clear();
|
|
handOffToVersionInstaller(added.first());
|
|
}
|
|
|
|
QStringList ModLoaderInstaller::versionsAddedByInstaller() const
|
|
{
|
|
if (!m_installerStarted)
|
|
return {};
|
|
|
|
QStringList added;
|
|
const QStringList after = VersionLoader::installedVersions(m_gameDir);
|
|
for (const QString &id : after) {
|
|
if (!m_versionsBefore.contains(id))
|
|
added << id;
|
|
}
|
|
return added;
|
|
}
|
|
|
|
void ModLoaderInstaller::removeVersionsAddedByInstaller()
|
|
{
|
|
const QStringList added = versionsAddedByInstaller();
|
|
for (const QString &id : added) {
|
|
QString error;
|
|
if (!VersionLoader::remove(m_gameDir, id, &error))
|
|
emit log(Loc::text("loader.error.cleanupFailed").arg(id, error));
|
|
}
|
|
m_versionsBefore.clear();
|
|
m_installerStarted = false;
|
|
}
|
|
|
|
QString ModLoaderInstaller::installerFailureHint() const
|
|
{
|
|
if (!m_zlibNgUnpatched)
|
|
return {};
|
|
|
|
const bool checksumFailure =
|
|
std::any_of(m_installerLog.cbegin(), m_installerLog.cend(), [](const QString &line) {
|
|
return line.contains(QLatin1String("invalid outputs"))
|
|
|| line.contains(QLatin1String("Processor failed"));
|
|
});
|
|
if (!checksumFailure)
|
|
return {};
|
|
|
|
return Loc::text("loader.error.zlibNg");
|
|
}
|
|
|
|
void ModLoaderInstaller::appendInstallerLog(const QString &line)
|
|
{
|
|
if (!m_installerLogFile)
|
|
return;
|
|
m_installerLogFile->write(line.toUtf8());
|
|
m_installerLogFile->write("\n");
|
|
}
|
|
|
|
void ModLoaderInstaller::closeInstallerLog()
|
|
{
|
|
if (!m_installerLogFile)
|
|
return;
|
|
m_installerLogFile->close();
|
|
delete m_installerLogFile;
|
|
m_installerLogFile = nullptr;
|
|
}
|
|
|
|
void ModLoaderInstaller::handOffToVersionInstaller(const QString &versionId)
|
|
{
|
|
m_awaitingVersionId = versionId;
|
|
setStage(Loc::text("loader.progress.downloadingVersionFiles"));
|
|
m_currentFile = versionId;
|
|
emit progressChanged();
|
|
m_versionInstaller->install(m_gameDir, versionId);
|
|
}
|
|
|
|
void ModLoaderInstaller::setStage(const QString &stage)
|
|
{
|
|
m_stage = stage;
|
|
emit progressChanged();
|
|
}
|
|
|
|
void ModLoaderInstaller::reset()
|
|
{
|
|
m_running = false;
|
|
m_canceling = false;
|
|
m_awaitingVersionId.clear();
|
|
m_stage.clear();
|
|
m_currentFile.clear();
|
|
m_bytesDone = 0;
|
|
m_bytesTotal = 0;
|
|
m_versionsBefore.clear();
|
|
m_installerStarted = false;
|
|
m_zlibNgUnpatched = false;
|
|
closeInstallerLog();
|
|
m_installerLogPath.clear();
|
|
if (m_process) {
|
|
m_process->deleteLater();
|
|
m_process = nullptr;
|
|
}
|
|
emit progressChanged();
|
|
}
|
|
|
|
void ModLoaderInstaller::fail(const QString &message)
|
|
{
|
|
const QString label = m_label;
|
|
reset();
|
|
emit failed(label, message);
|
|
}
|