Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions launcher/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,9 @@ set(API_SOURCES
modplatform/EnsureMetadataTask.h
modplatform/EnsureMetadataTask.cpp

modplatform/ModpackResumeTask.h
modplatform/ModpackResumeTask.cpp

modplatform/CheckUpdateTask.h

modplatform/flame/FlameAPI.h
Expand All @@ -542,6 +545,8 @@ set(API_SOURCES
modplatform/helpers/HashUtils.cpp
modplatform/helpers/OverrideUtils.h
modplatform/helpers/OverrideUtils.cpp
modplatform/helpers/PendingModpackManifest.h
modplatform/helpers/PendingModpackManifest.cpp

modplatform/helpers/ExportToModList.h
modplatform/helpers/ExportToModList.cpp
Expand Down Expand Up @@ -580,6 +585,9 @@ set(FLAME_SOURCES
modplatform/flame/FlameCheckUpdate.h
modplatform/flame/FlameInstanceCreationTask.h
modplatform/flame/FlameInstanceCreationTask.cpp
modplatform/flame/FlamePackInstallTask.h
modplatform/flame/FlamePackInstallTask.cpp
modplatform/flame/ContentDownloadJob.h
modplatform/flame/FlamePackExportTask.h
modplatform/flame/FlamePackExportTask.cpp
)
Expand All @@ -591,6 +599,8 @@ set(MODRINTH_SOURCES
modplatform/modrinth/ModrinthCheckUpdate.h
modplatform/modrinth/ModrinthInstanceCreationTask.cpp
modplatform/modrinth/ModrinthInstanceCreationTask.h
modplatform/modrinth/ModrinthPackInstallTask.h
modplatform/modrinth/ModrinthPackInstallTask.cpp
modplatform/modrinth/ModrinthPackExportTask.cpp
modplatform/modrinth/ModrinthPackExportTask.h
)
Expand Down
119 changes: 113 additions & 6 deletions launcher/InstanceCreationTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,24 @@

#include <QDebug>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSettings>

#include "FileSystem.h"
#include "InstanceTask.h"
#include "minecraft/MinecraftLoadAndCheck.h"
#include "minecraft/PackProfile.h"
#include "minecraft/update/AssetUpdateTask.h"
#include "minecraft/update/LegacyFMLLibrariesTask.h"
#include "minecraft/update/LibrariesTask.h"
#include "settings/Setting.h"
#include "tasks/SequentialTask.h"

bool InstanceCreationTask::abort()
{
if (!canAbort()) {
if (!canAbort() || !isRunning()) {
return false;
}

Expand All @@ -18,15 +28,29 @@ bool InstanceCreationTask::abort()
return m_gameFilesTask->abort();
}

return InstanceTask::abort();
// createInstance() may be running a nested event loop. Let it stop its content
// task and decide whether to keep the instance before staging can be removed.
return true;
}

void InstanceCreationTask::executeTask()
{
m_abort = false;
m_contentIncomplete = false;
m_gameDownloadFailed = false;
m_error_message.clear();
m_gameFilesTask.reset();
m_instance.reset();
setAbortable(true);

if (updateInstance()) {
emitSucceeded();
auto updated = updateInstance();
if (!isRunning())
return;
if (updated) {
if (m_abort)
emitAborted();
else
emitSucceeded();
return;
}

Expand All @@ -37,9 +61,13 @@ void InstanceCreationTask::executeTask()
}

m_instance = createInstance();
if (!isRunning())
return;
if (!m_instance) {
if (m_abort)
if (m_abort && m_error_message.isEmpty()) {
emitAborted();
return;
}

qWarning() << "Instance creation failed!";
if (!m_error_message.isEmpty()) {
Expand All @@ -52,6 +80,19 @@ void InstanceCreationTask::executeTask()
return;
}

if (m_contentIncomplete && !shouldOverride()) {
finishIncompleteInstance(
tr("Modpack installation is incomplete. Remaining files must be downloaded before playing.")
);
return;
}
if (m_abort) {
// Destroy the profile before InstanceStaging removes its directory.
m_instance.reset();
emitAborted();
return;
}

// If this is set, it means we're updating an instance. So, we now need to remove the
// files scheduled to, and we'd better not let the user abort in the middle of it, since it'd
// put the instance in an invalid state.
Expand Down Expand Up @@ -91,13 +132,31 @@ void InstanceCreationTask::executeTask()
return;
}
auto task = makeShared<SequentialTask>();
task->addTask(makeShared<MinecraftLoadAndCheck>(m_instance.get(), Net::Mode::Online));
auto loadTask = makeShared<MinecraftLoadAndCheck>(m_instance.get(), Net::Mode::Online);
connect(loadTask.get(), &Task::failed, this, [this, source = loadTask.get()] { m_gameDownloadFailed = source->downloadFailed(); });
task->addTask(loadTask);
for (const auto& t : updateTasks) {
// These connections must run before SequentialTask forwards the failure.
if (auto librariesTask = qobject_cast<LibrariesTask*>(t.get())) {
connect(librariesTask, &Task::failed, this,
[this, librariesTask] { m_gameDownloadFailed = librariesTask->downloadFailed(); });
} else if (auto legacyLibrariesTask = qobject_cast<LegacyFMLLibrariesTask*>(t.get())) {
connect(legacyLibrariesTask, &Task::failed, this,
[this, legacyLibrariesTask] { m_gameDownloadFailed = legacyLibrariesTask->downloadFailed(); });
} else if (auto assetTask = qobject_cast<AssetUpdateTask*>(t.get())) {
connect(assetTask, &Task::failed, this, [this, assetTask] { m_gameDownloadFailed = assetTask->downloadFailed(); });
}
task->addTask(t);
}
connect(task.get(), &Task::finished, this, [this, task] {
if (!isRunning())
return;
if (task->wasSuccessful() || m_abort) {
emitSucceeded();
} else if (!shouldOverride() && keepOnGameDownloadFailure() && m_gameDownloadFailed) {
finishIncompleteInstance(tr("Game file downloads did not finish. The modpack content is complete, but game files must be "
"downloaded before playing.\n%1")
.arg(task->failReason()));
} else {
emitFailed(tr("Could not download game files: %1").arg(task->failReason()));
}
Expand All @@ -110,6 +169,54 @@ void InstanceCreationTask::executeTask()
}
}

void InstanceCreationTask::finishIncompleteInstance(const QString& warning)
{
setAbortable(false);
auto settings = m_instance->settings();
m_instance->saveNow();

// Instance settings are saved synchronously by INISettingsObject. Check the
// persisted identity as well: its setters do not return disk write errors.
QSettings config(FS::PathCombine(m_instance->instanceRoot(), "instance.cfg"), QSettings::IniFormat);
config.setFallbacksEnabled(false);
config.sync();
bool configSaved = QFile::exists(config.fileName()) && config.status() == QSettings::NoError;
for (const auto& key : { "name", "InstanceType", "iconKey", "ManagedPack", "ManagedPackType", "ManagedPackID", "ManagedPackName",
"ManagedPackVersionID", "ManagedPackVersionName" }) {
auto setting = settings->getSetting(key);
if (config.value(key, setting->defValue()).toString() != setting->get().toString())
configSaved = false;
}
// saveNow() has no result API. Check the saved component configuration using
// public profile accessors, without reloading or starting metadata downloads.
auto profile = m_instance->getPackProfile();
QFile profileFile(FS::PathCombine(m_instance->instanceRoot(), "mmc-pack.json"));
QJsonObject savedProfile;
if (profileFile.open(QIODevice::ReadOnly)) {
savedProfile = QJsonDocument::fromJson(profileFile.readAll()).object();
profileFile.close();
}
auto savedComponents = savedProfile.value("components").toArray();
bool profileSaved = savedProfile.value("formatVersion").toInt() == 1 && savedProfile.value("components").isArray() &&
savedComponents.size() == profile->rowCount();
for (int i = 0; profileSaved && i < profile->rowCount(); ++i) {
auto component = profile->getComponent(static_cast<size_t>(i));
auto saved = savedComponents[i].toObject();
profileSaved = saved.value("uid").toString() == component->m_uid && saved.value("version").toString() == component->m_version &&
saved.value("dependencyOnly").toBool() == component->m_dependencyOnly &&
saved.value("important").toBool() == component->m_important &&
saved.value("disabled").toBool() == component->m_disabled;
}
if (!configSaved || !profileSaved) {
m_instance.reset();
emitFailed(tr("Could not save the incomplete instance configuration."));
return;
}

logWarning(warning);
emitSucceeded();
}

void InstanceCreationTask::scheduleToDelete(QWidget* parent, QDir dir, QString path, bool checkDisabled)
{
if (path.isEmpty()) {
Expand Down
7 changes: 7 additions & 0 deletions launcher/InstanceCreationTask.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ class InstanceCreationTask : public InstanceTask {

protected:
void setError(const QString& message) { m_error_message = message; };
// Set only after the content task has stopped and the pending manifest has been saved.
void setContentIncomplete(bool incomplete) { m_contentIncomplete = incomplete; }
virtual bool keepOnGameDownloadFailure() const { return false; }
void scheduleToDelete(QWidget* parent, QDir dir, QString path, bool checkDisabled = false);

protected:
Expand All @@ -47,6 +50,10 @@ class InstanceCreationTask : public InstanceTask {
ShouldDeleteSaves m_shouldDeleteSaves;

private:
void finishIncompleteInstance(const QString& warning);

bool m_contentIncomplete = false;
bool m_gameDownloadFailed = false;
QString m_error_message;
std::unique_ptr<MinecraftInstance> m_instance;
Task::Ptr m_gameFilesTask;
Expand Down
9 changes: 9 additions & 0 deletions launcher/LaunchController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "launch/steps/PrintServers.h"
#include "minecraft/auth/AccountData.h"
#include "minecraft/auth/AccountList.h"
#include "modplatform/ModpackResumeTask.h"

#include "net/NetUtils.h"
#include "ui/InstanceWindow.h"
Expand Down Expand Up @@ -70,6 +71,13 @@ void LaunchController::executeTask()
return;
}

if (m_wantedLaunchMode == LaunchMode::Offline && ModpackResumeTask::hasPendingManifest(m_instance->instanceRoot())) {
const auto reason = tr("This modpack installation is incomplete. Launch online to finish installing it before playing.");
QMessageBox::warning(m_parentWidget, tr("Incomplete modpack installation"), reason);
emitFailed(reason);
return;
}

if (!JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget)) {
emitFailed(tr("Invalid Java arguments specified. Please fix this first."));
return;
Expand Down Expand Up @@ -370,6 +378,7 @@ void LaunchController::launchInstance()
return;
}

m_session->explicitlyOffline = m_wantedLaunchMode == LaunchMode::Offline;
m_launcher = m_instance->createLaunchTask(m_session, m_targetToJoin);
if (!m_launcher) {
emitFailed(tr("Couldn't instantiate a launcher."));
Expand Down
3 changes: 2 additions & 1 deletion launcher/launch/TaskStepWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ void TaskStepWrapper::executeTask()

void TaskStepWrapper::proceed()
{
m_task->start();
// Let the progress dialog connect and become visible before tasks enter nested event loops.
QMetaObject::invokeMethod(m_task.get(), &Task::start, Qt::QueuedConnection);
}

void TaskStepWrapper::updateFinished()
Expand Down
6 changes: 5 additions & 1 deletion launcher/meta/BaseEntity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode, bool

void BaseEntityLoadTask::executeTask()
{
m_downloadFailed = false;
const QString fname = QDir("meta").absoluteFilePath(m_entity->localFilename());
auto hashMatches = false;
// the file exists on disk try to load it
Expand Down Expand Up @@ -176,7 +177,10 @@ void BaseEntityLoadTask::executeTask()
dl->addValidator(new ParsingValidator(m_entity));
m_task->addNetAction(dl);
m_task->setAskRetry(false);
connect(m_task.get(), &Task::failed, this, &BaseEntityLoadTask::emitFailed);
connect(m_task.get(), &Task::failed, this, [this](const QString& reason) {
m_downloadFailed = m_task->hasNetworkFailure();
emitFailed(reason);
});
connect(m_task.get(), &Task::succeeded, this, [this]() {
m_entity->m_load_status = BaseEntity::LoadStatus::Remote;
m_entity->m_file_sha256 = m_entity->m_sha256;
Expand Down
3 changes: 3 additions & 0 deletions launcher/meta/BaseEntity.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class BaseEntity {

virtual void parse(const QJsonObject& obj) = 0;
[[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online, bool forceReload = false);
Task::Ptr currentLoadTask() const { return m_task; }

protected:
QString m_sha256; // the expected sha256
Expand All @@ -64,11 +65,13 @@ class BaseEntityLoadTask : public Task {
virtual void executeTask() override;
virtual bool canAbort() const override;
virtual bool abort() override;
bool downloadFailed() const { return m_downloadFailed; }

private:
BaseEntity* m_entity;
Net::Mode m_mode;
bool m_force_reload = false;
bool m_downloadFailed = false;
NetJob::Ptr m_task;
};
} // namespace Meta
Loading