summaryrefslogtreecommitdiff
path: root/meshmc/launcher/launch
diff options
context:
space:
mode:
Diffstat (limited to 'meshmc/launcher/launch')
-rw-r--r--meshmc/launcher/launch/LaunchStep.cpp52
-rw-r--r--meshmc/launcher/launch/LaunchStep.h74
-rw-r--r--meshmc/launcher/launch/LaunchTask.cpp278
-rw-r--r--meshmc/launcher/launch/LaunchTask.h138
-rw-r--r--meshmc/launcher/launch/LogModel.cpp174
-rw-r--r--meshmc/launcher/launch/LogModel.h75
-rw-r--r--meshmc/launcher/launch/steps/CheckJava.cpp173
-rw-r--r--meshmc/launcher/launch/steps/CheckJava.h69
-rw-r--r--meshmc/launcher/launch/steps/LookupServerAddress.cpp125
-rw-r--r--meshmc/launcher/launch/steps/LookupServerAddress.h73
-rw-r--r--meshmc/launcher/launch/steps/PostLaunchCommand.cpp104
-rw-r--r--meshmc/launcher/launch/steps/PostLaunchCommand.h64
-rw-r--r--meshmc/launcher/launch/steps/PreLaunchCommand.cpp104
-rw-r--r--meshmc/launcher/launch/steps/PreLaunchCommand.h64
-rw-r--r--meshmc/launcher/launch/steps/TextPrint.cpp54
-rw-r--r--meshmc/launcher/launch/steps/TextPrint.h66
-rw-r--r--meshmc/launcher/launch/steps/Update.cpp96
-rw-r--r--meshmc/launcher/launch/steps/Update.h70
18 files changed, 1853 insertions, 0 deletions
diff --git a/meshmc/launcher/launch/LaunchStep.cpp b/meshmc/launcher/launch/LaunchStep.cpp
new file mode 100644
index 0000000000..3d1ad09a89
--- /dev/null
+++ b/meshmc/launcher/launch/LaunchStep.cpp
@@ -0,0 +1,52 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "LaunchStep.h"
+#include "LaunchTask.h"
+
+void LaunchStep::bind(LaunchTask* parent)
+{
+ m_parent = parent;
+ connect(this, &LaunchStep::readyForLaunch, parent,
+ &LaunchTask::onReadyForLaunch);
+ connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);
+ connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines);
+ connect(this, &LaunchStep::finished, parent, &LaunchTask::onStepFinished);
+ connect(this, &LaunchStep::progressReportingRequest, parent,
+ &LaunchTask::onProgressReportingRequested);
+}
diff --git a/meshmc/launcher/launch/LaunchStep.h b/meshmc/launcher/launch/LaunchStep.h
new file mode 100644
index 0000000000..91ef5ea201
--- /dev/null
+++ b/meshmc/launcher/launch/LaunchStep.h
@@ -0,0 +1,74 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "tasks/Task.h"
+#include "MessageLevel.h"
+
+#include <QStringList>
+
+class LaunchTask;
+class LaunchStep : public Task
+{
+ Q_OBJECT
+ public: /* methods */
+ explicit LaunchStep(LaunchTask* parent) : Task(nullptr), m_parent(parent)
+ {
+ bind(parent);
+ };
+ virtual ~LaunchStep() {};
+
+ private: /* methods */
+ void bind(LaunchTask* parent);
+
+ signals:
+ void logLines(QStringList lines, MessageLevel::Enum level);
+ void logLine(QString line, MessageLevel::Enum level);
+ void readyForLaunch();
+ void progressReportingRequest();
+
+ public slots:
+ virtual void proceed() {};
+ // called in the opposite order than the Task launch(), used to clean up or
+ // otherwise undo things after the launch ends
+ virtual void finalize() {};
+
+ protected: /* data */
+ LaunchTask* m_parent;
+};
diff --git a/meshmc/launcher/launch/LaunchTask.cpp b/meshmc/launcher/launch/LaunchTask.cpp
new file mode 100644
index 0000000000..86f2c11bf4
--- /dev/null
+++ b/meshmc/launcher/launch/LaunchTask.cpp
@@ -0,0 +1,278 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "launch/LaunchTask.h"
+#include "MessageLevel.h"
+#include "MMCStrings.h"
+#include "java/JavaChecker.h"
+#include "tasks/Task.h"
+#include <QDebug>
+#include <QDir>
+#include <QEventLoop>
+#include <QRegularExpression>
+#include <QCoreApplication>
+#include <QStandardPaths>
+#include <assert.h>
+
+void LaunchTask::init()
+{
+ m_instance->setRunning(true);
+}
+
+shared_qobject_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
+{
+ shared_qobject_ptr<LaunchTask> proc(new LaunchTask(inst));
+ proc->init();
+ return proc;
+}
+
+LaunchTask::LaunchTask(InstancePtr instance) : m_instance(instance) {}
+
+void LaunchTask::appendStep(shared_qobject_ptr<LaunchStep> step)
+{
+ m_steps.append(step);
+}
+
+void LaunchTask::prependStep(shared_qobject_ptr<LaunchStep> step)
+{
+ m_steps.prepend(step);
+}
+
+void LaunchTask::executeTask()
+{
+ m_instance->setCrashed(false);
+ if (!m_steps.size()) {
+ state = LaunchTask::Finished;
+ emitSucceeded();
+ }
+ state = LaunchTask::Running;
+ onStepFinished();
+}
+
+void LaunchTask::onReadyForLaunch()
+{
+ state = LaunchTask::Waiting;
+ emit readyForLaunch();
+}
+
+void LaunchTask::onStepFinished()
+{
+ // initial -> just start the first step
+ if (currentStep == -1) {
+ currentStep++;
+ m_steps[currentStep]->start();
+ return;
+ }
+
+ auto step = m_steps[currentStep];
+ if (step->wasSuccessful()) {
+ // end?
+ if (currentStep == m_steps.size() - 1) {
+ finalizeSteps(true, QString());
+ } else {
+ currentStep++;
+ step = m_steps[currentStep];
+ step->start();
+ }
+ } else {
+ finalizeSteps(false, step->failReason());
+ }
+}
+
+void LaunchTask::finalizeSteps(bool successful, const QString& error)
+{
+ for (auto step = currentStep; step >= 0; step--) {
+ m_steps[step]->finalize();
+ }
+ if (successful) {
+ emitSucceeded();
+ } else {
+ emitFailed(error);
+ }
+}
+
+void LaunchTask::onProgressReportingRequested()
+{
+ state = LaunchTask::Waiting;
+ emit requestProgress(m_steps[currentStep].get());
+}
+
+void LaunchTask::setCensorFilter(QMap<QString, QString> filter)
+{
+ m_censorFilter = filter;
+}
+
+QString LaunchTask::censorPrivateInfo(QString in)
+{
+ auto iter = m_censorFilter.begin();
+ while (iter != m_censorFilter.end()) {
+ in.replace(iter.key(), iter.value());
+ iter++;
+ }
+ return in;
+}
+
+void LaunchTask::proceed()
+{
+ if (state != LaunchTask::Waiting) {
+ return;
+ }
+ m_steps[currentStep]->proceed();
+}
+
+bool LaunchTask::canAbort() const
+{
+ switch (state) {
+ case LaunchTask::Aborted:
+ case LaunchTask::Failed:
+ case LaunchTask::Finished:
+ return false;
+ case LaunchTask::NotStarted:
+ return true;
+ case LaunchTask::Running:
+ case LaunchTask::Waiting: {
+ auto step = m_steps[currentStep];
+ return step->canAbort();
+ }
+ }
+ return false;
+}
+
+bool LaunchTask::abort()
+{
+ switch (state) {
+ case LaunchTask::Aborted:
+ case LaunchTask::Failed:
+ case LaunchTask::Finished:
+ return true;
+ case LaunchTask::NotStarted: {
+ state = LaunchTask::Aborted;
+ emitFailed("Aborted");
+ return true;
+ }
+ case LaunchTask::Running:
+ case LaunchTask::Waiting: {
+ auto step = m_steps[currentStep];
+ if (!step->canAbort()) {
+ return false;
+ }
+ if (step->abort()) {
+ state = LaunchTask::Aborted;
+ return true;
+ }
+ }
+ default:
+ break;
+ }
+ return false;
+}
+
+shared_qobject_ptr<LogModel> LaunchTask::getLogModel()
+{
+ if (!m_logModel) {
+ m_logModel.reset(new LogModel());
+ m_logModel->setMaxLines(m_instance->getConsoleMaxLines());
+ m_logModel->setStopOnOverflow(
+ m_instance->shouldStopOnConsoleOverflow());
+ // FIXME: should this really be here?
+ m_logModel->setOverflowMessage(
+ tr("MeshMC stopped watching the game log because the log length "
+ "surpassed %1 lines.\n"
+ "You may have to fix your mods because the game is still "
+ "logging to files and"
+ " likely wasting harddrive space at an alarming rate!")
+ .arg(m_logModel->getMaxLines()));
+ }
+ return m_logModel;
+}
+
+void LaunchTask::onLogLines(const QStringList& lines,
+ MessageLevel::Enum defaultLevel)
+{
+ for (auto& line : lines) {
+ onLogLine(line, defaultLevel);
+ }
+}
+
+void LaunchTask::onLogLine(QString line, MessageLevel::Enum level)
+{
+ // if MeshMC part set a log level, use it
+ auto innerLevel = MessageLevel::fromLine(line);
+ if (innerLevel != MessageLevel::Unknown) {
+ level = innerLevel;
+ }
+
+ // If the level is still undetermined, guess level
+ if (level == MessageLevel::StdErr || level == MessageLevel::StdOut ||
+ level == MessageLevel::Unknown) {
+ level = m_instance->guessLevel(line, level);
+ }
+
+ // censor private user info
+ line = censorPrivateInfo(line);
+
+ auto& model = *getLogModel();
+ model.append(level, line);
+}
+
+void LaunchTask::emitSucceeded()
+{
+ m_instance->setRunning(false);
+ Task::emitSucceeded();
+}
+
+void LaunchTask::emitFailed(QString reason)
+{
+ m_instance->setRunning(false);
+ m_instance->setCrashed(true);
+ Task::emitFailed(reason);
+}
+
+QString LaunchTask::substituteVariables(const QString& cmd) const
+{
+ QString out = cmd;
+ auto variables = m_instance->getVariables();
+ for (auto it = variables.begin(); it != variables.end(); ++it) {
+ out.replace("$" + it.key(), it.value());
+ }
+ auto env = QProcessEnvironment::systemEnvironment();
+ for (auto var : env.keys()) {
+ out.replace("$" + var, env.value(var));
+ }
+ return out;
+}
diff --git a/meshmc/launcher/launch/LaunchTask.h b/meshmc/launcher/launch/LaunchTask.h
new file mode 100644
index 0000000000..12a5c068c3
--- /dev/null
+++ b/meshmc/launcher/launch/LaunchTask.h
@@ -0,0 +1,138 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#include <QProcess>
+#include <QObjectPtr.h>
+#include "LogModel.h"
+#include "BaseInstance.h"
+#include "MessageLevel.h"
+#include "LoggedProcess.h"
+#include "LaunchStep.h"
+
+class LaunchTask : public Task
+{
+ Q_OBJECT
+ protected:
+ explicit LaunchTask(InstancePtr instance);
+ void init();
+
+ public:
+ enum State { NotStarted, Running, Waiting, Failed, Aborted, Finished };
+
+ public: /* methods */
+ static shared_qobject_ptr<LaunchTask> create(InstancePtr inst);
+ virtual ~LaunchTask() {};
+
+ void appendStep(shared_qobject_ptr<LaunchStep> step);
+ void prependStep(shared_qobject_ptr<LaunchStep> step);
+ void setCensorFilter(QMap<QString, QString> filter);
+
+ InstancePtr instance()
+ {
+ return m_instance;
+ }
+
+ void setPid(qint64 pid)
+ {
+ m_pid = pid;
+ }
+
+ qint64 pid()
+ {
+ return m_pid;
+ }
+
+ /**
+ * @brief prepare the process for launch (for multi-stage launch)
+ */
+ virtual void executeTask() override;
+
+ /**
+ * @brief launch the armed instance
+ */
+ void proceed();
+
+ /**
+ * @brief abort launch
+ */
+ bool abort() override;
+
+ bool canAbort() const override;
+
+ shared_qobject_ptr<LogModel> getLogModel();
+
+ public:
+ QString substituteVariables(const QString& cmd) const;
+ QString censorPrivateInfo(QString in);
+
+ protected: /* methods */
+ virtual void emitFailed(QString reason) override;
+ virtual void emitSucceeded() override;
+
+ signals:
+ /**
+ * @brief emitted when the launch preparations are done
+ */
+ void readyForLaunch();
+
+ void requestProgress(Task* task);
+
+ void requestLogging();
+
+ public slots:
+ void onLogLines(const QStringList& lines,
+ MessageLevel::Enum defaultLevel = MessageLevel::MeshMC);
+ void onLogLine(QString line,
+ MessageLevel::Enum defaultLevel = MessageLevel::MeshMC);
+ void onReadyForLaunch();
+ void onStepFinished();
+ void onProgressReportingRequested();
+
+ private: /*methods */
+ void finalizeSteps(bool successful, const QString& error);
+
+ protected: /* data */
+ InstancePtr m_instance;
+ shared_qobject_ptr<LogModel> m_logModel;
+ QList<shared_qobject_ptr<LaunchStep>> m_steps;
+ QMap<QString, QString> m_censorFilter;
+ int currentStep = -1;
+ State state = NotStarted;
+ qint64 m_pid = -1;
+};
diff --git a/meshmc/launcher/launch/LogModel.cpp b/meshmc/launcher/launch/LogModel.cpp
new file mode 100644
index 0000000000..fd21c48f73
--- /dev/null
+++ b/meshmc/launcher/launch/LogModel.cpp
@@ -0,0 +1,174 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "LogModel.h"
+
+LogModel::LogModel(QObject* parent) : QAbstractListModel(parent)
+{
+ m_content.resize(m_maxLines);
+}
+
+int LogModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return m_numLines;
+}
+
+QVariant LogModel::data(const QModelIndex& index, int role) const
+{
+ if (index.row() < 0 || index.row() >= m_numLines)
+ return QVariant();
+
+ auto row = index.row();
+ auto realRow = (row + m_firstLine) % m_maxLines;
+ if (role == Qt::DisplayRole || role == Qt::EditRole) {
+ return m_content[realRow].line;
+ }
+ if (role == LevelRole) {
+ return m_content[realRow].level;
+ }
+
+ return QVariant();
+}
+
+void LogModel::append(MessageLevel::Enum level, QString line)
+{
+ if (m_suspended) {
+ return;
+ }
+ int lineNum = (m_firstLine + m_numLines) % m_maxLines;
+ // overflow
+ if (m_numLines == m_maxLines) {
+ if (m_stopOnOverflow) {
+ // nothing more to do, the buffer is full
+ return;
+ }
+ beginRemoveRows(QModelIndex(), 0, 0);
+ m_firstLine = (m_firstLine + 1) % m_maxLines;
+ m_numLines--;
+ endRemoveRows();
+ } else if (m_numLines == m_maxLines - 1 && m_stopOnOverflow) {
+ level = MessageLevel::Fatal;
+ line = m_overflowMessage;
+ }
+ beginInsertRows(QModelIndex(), m_numLines, m_numLines);
+ m_numLines++;
+ m_content[lineNum].level = level;
+ m_content[lineNum].line = line;
+ endInsertRows();
+}
+
+void LogModel::suspend(bool suspend)
+{
+ m_suspended = suspend;
+}
+
+bool LogModel::suspended()
+{
+ return m_suspended;
+}
+
+void LogModel::clear()
+{
+ beginResetModel();
+ m_firstLine = 0;
+ m_numLines = 0;
+ endResetModel();
+}
+
+QString LogModel::toPlainText()
+{
+ QString out;
+ out.reserve(m_numLines * 80);
+ for (int i = 0; i < m_numLines; i++) {
+ QString& line = m_content[(m_firstLine + i) % m_maxLines].line;
+ out.append(line + '\n');
+ }
+ out.squeeze();
+ return out;
+}
+
+void LogModel::setMaxLines(int maxLines)
+{
+ // no-op
+ if (maxLines == m_maxLines) {
+ return;
+ }
+ // if it all still fits in the buffer, just resize it
+ if (m_firstLine + m_numLines < m_maxLines) {
+ m_maxLines = maxLines;
+ m_content.resize(maxLines);
+ return;
+ }
+ // otherwise, we need to reorganize the data because it crosses the wrap
+ // boundary
+ QVector<entry> newContent;
+ newContent.resize(maxLines);
+ if (m_numLines <= maxLines) {
+ // if it all fits in the new buffer, just copy it over
+ for (int i = 0; i < m_numLines; i++) {
+ newContent[i] = m_content[(m_firstLine + i) % m_maxLines];
+ }
+ m_content.swap(newContent);
+ } else {
+ // if it doesn't fit, part of the data needs to be thrown away (the
+ // oldest log messages)
+ int lead = m_numLines - maxLines;
+ beginRemoveRows(QModelIndex(), 0, lead - 1);
+ for (int i = 0; i < maxLines; i++) {
+ newContent[i] = m_content[(m_firstLine + lead + i) % m_maxLines];
+ }
+ m_numLines = m_maxLines;
+ m_content.swap(newContent);
+ endRemoveRows();
+ }
+ m_firstLine = 0;
+ m_maxLines = maxLines;
+}
+
+int LogModel::getMaxLines()
+{
+ return m_maxLines;
+}
+
+void LogModel::setStopOnOverflow(bool stop)
+{
+ m_stopOnOverflow = stop;
+}
+
+void LogModel::setOverflowMessage(const QString& overflowMessage)
+{
+ m_overflowMessage = overflowMessage;
+}
+
+void LogModel::setLineWrap(bool state)
+{
+ if (m_lineWrap != state) {
+ m_lineWrap = state;
+ }
+}
+
+bool LogModel::wrapLines() const
+{
+ return m_lineWrap;
+}
diff --git a/meshmc/launcher/launch/LogModel.h b/meshmc/launcher/launch/LogModel.h
new file mode 100644
index 0000000000..7d85585f0a
--- /dev/null
+++ b/meshmc/launcher/launch/LogModel.h
@@ -0,0 +1,75 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <QAbstractListModel>
+#include <QString>
+#include "MessageLevel.h"
+
+class LogModel : public QAbstractListModel
+{
+ Q_OBJECT
+ public:
+ explicit LogModel(QObject* parent = 0);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant data(const QModelIndex& index, int role) const;
+
+ void append(MessageLevel::Enum, QString line);
+ void clear();
+
+ void suspend(bool suspend);
+ bool suspended();
+
+ QString toPlainText();
+
+ int getMaxLines();
+ void setMaxLines(int maxLines);
+ void setStopOnOverflow(bool stop);
+ void setOverflowMessage(const QString& overflowMessage);
+
+ void setLineWrap(bool state);
+ bool wrapLines() const;
+
+ enum Roles { LevelRole = Qt::UserRole };
+
+ private /* types */:
+ struct entry {
+ MessageLevel::Enum level;
+ QString line;
+ };
+
+ private: /* data */
+ QVector<entry> m_content;
+ int m_maxLines = 1000;
+ // first line in the circular buffer
+ int m_firstLine = 0;
+ // number of lines occupied in the circular buffer
+ int m_numLines = 0;
+ bool m_stopOnOverflow = false;
+ QString m_overflowMessage = "OVERFLOW";
+ bool m_suspended = false;
+ bool m_lineWrap = true;
+
+ private:
+ Q_DISABLE_COPY(LogModel)
+};
diff --git a/meshmc/launcher/launch/steps/CheckJava.cpp b/meshmc/launcher/launch/steps/CheckJava.cpp
new file mode 100644
index 0000000000..715e721b4c
--- /dev/null
+++ b/meshmc/launcher/launch/steps/CheckJava.cpp
@@ -0,0 +1,173 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "CheckJava.h"
+#include <launch/LaunchTask.h>
+#include <FileSystem.h>
+#include <QStandardPaths>
+#include <QFileInfo>
+#include <sys.h>
+
+void CheckJava::executeTask()
+{
+ auto instance = m_parent->instance();
+ auto settings = instance->settings();
+ m_javaPath = FS::ResolveExecutable(settings->get("JavaPath").toString());
+ bool perInstance = settings->get("OverrideJava").toBool() ||
+ settings->get("OverrideJavaLocation").toBool();
+
+ auto realJavaPath = QStandardPaths::findExecutable(m_javaPath);
+ if (realJavaPath.isEmpty()) {
+ if (perInstance) {
+ emit logLine(
+ QString("The java binary \"%1\" couldn't be found. Please fix "
+ "the java path "
+ "override in the instance's settings or disable it.")
+ .arg(m_javaPath),
+ MessageLevel::Warning);
+ } else {
+ emit logLine(QString("The java binary \"%1\" couldn't be found. "
+ "Please set up java in "
+ "the settings.")
+ .arg(m_javaPath),
+ MessageLevel::Warning);
+ }
+ emitFailed(QString("Java path is not valid."));
+ return;
+ } else {
+ emit logLine("Java path is:\n" + m_javaPath + "\n\n",
+ MessageLevel::MeshMC);
+ }
+
+ QFileInfo javaInfo(realJavaPath);
+ qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
+ auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
+ auto storedArchitecture = settings->get("JavaArchitecture").toString();
+ auto storedVersion = settings->get("JavaVersion").toString();
+ auto storedVendor = settings->get("JavaVendor").toString();
+ m_javaUnixTime = javaUnixTime;
+ // if timestamps are not the same, or something is missing, check!
+ if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 ||
+ storedArchitecture.size() == 0 || storedVendor.size() == 0) {
+ m_JavaChecker = new JavaChecker();
+ emit logLine(QString("Checking Java version..."), MessageLevel::MeshMC);
+ connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this,
+ &CheckJava::checkJavaFinished);
+ m_JavaChecker->m_path = realJavaPath;
+ m_JavaChecker->performCheck();
+ return;
+ } else {
+ auto verString = instance->settings()->get("JavaVersion").toString();
+ auto archString =
+ instance->settings()->get("JavaArchitecture").toString();
+ auto vendorString = instance->settings()->get("JavaVendor").toString();
+ printJavaInfo(verString, archString, vendorString);
+ }
+ emitSucceeded();
+}
+
+void CheckJava::checkJavaFinished(JavaCheckResult result)
+{
+ switch (result.validity) {
+ case JavaCheckResult::Validity::Errored: {
+ // Error message displayed if java can't start
+ emit logLine(QString("Could not start java:"), MessageLevel::Error);
+ emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
+ emit logLine("\nCheck your MeshMC Java settings.",
+ MessageLevel::MeshMC);
+ printSystemInfo(false, false);
+ emitFailed(QString("Could not start java!"));
+ return;
+ }
+ case JavaCheckResult::Validity::ReturnedInvalidData: {
+ emit logLine(QString("Java checker returned some invalid data "
+ "MeshMC doesn't understand:"),
+ MessageLevel::Error);
+ emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
+ emit logLine("\nMinecraft might not start properly.",
+ MessageLevel::MeshMC);
+ printSystemInfo(false, false);
+ emitSucceeded();
+ return;
+ }
+ case JavaCheckResult::Validity::Valid: {
+ auto instance = m_parent->instance();
+ printJavaInfo(result.javaVersion.toString(), result.mojangPlatform,
+ result.javaVendor);
+ instance->settings()->set("JavaVersion",
+ result.javaVersion.toString());
+ instance->settings()->set("JavaArchitecture",
+ result.mojangPlatform);
+ instance->settings()->set("JavaVendor", result.javaVendor);
+ instance->settings()->set("JavaTimestamp", m_javaUnixTime);
+ emitSucceeded();
+ return;
+ }
+ }
+}
+
+void CheckJava::printJavaInfo(const QString& version,
+ const QString& architecture,
+ const QString& vendor)
+{
+ emit logLine(
+ QString("Java is version %1, using %2-bit architecture, from %3.\n\n")
+ .arg(version, architecture, vendor),
+ MessageLevel::MeshMC);
+ printSystemInfo(true, architecture == "64");
+}
+
+void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit)
+{
+ auto cpu64 = Sys::isCPU64bit();
+ auto system64 = Sys::isSystem64bit();
+ if (cpu64 != system64) {
+ emit logLine(QString("Your CPU architecture is not matching your "
+ "system architecture. You might want to install a "
+ "64bit Operating System.\n\n"),
+ MessageLevel::Error);
+ }
+ if (javaIsKnown) {
+ if (javaIs64bit != system64) {
+ emit logLine(QString("Your Java architecture is not matching your "
+ "system architecture. You might want to "
+ "install a 64bit Java version.\n\n"),
+ MessageLevel::Error);
+ }
+ }
+}
diff --git a/meshmc/launcher/launch/steps/CheckJava.h b/meshmc/launcher/launch/steps/CheckJava.h
new file mode 100644
index 0000000000..8de2d7da5a
--- /dev/null
+++ b/meshmc/launcher/launch/steps/CheckJava.h
@@ -0,0 +1,69 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <launch/LaunchStep.h>
+#include <LoggedProcess.h>
+#include <java/JavaChecker.h>
+
+class CheckJava : public LaunchStep
+{
+ Q_OBJECT
+ public:
+ explicit CheckJava(LaunchTask* parent) : LaunchStep(parent) {};
+ virtual ~CheckJava() {};
+
+ virtual void executeTask();
+ virtual bool canAbort() const
+ {
+ return false;
+ }
+ private slots:
+ void checkJavaFinished(JavaCheckResult result);
+
+ private:
+ void printJavaInfo(const QString& version, const QString& architecture,
+ const QString& vendor);
+ void printSystemInfo(bool javaIsKnown, bool javaIs64bit);
+
+ private:
+ QString m_javaPath;
+ qlonglong m_javaUnixTime;
+ JavaCheckerPtr m_JavaChecker;
+};
diff --git a/meshmc/launcher/launch/steps/LookupServerAddress.cpp b/meshmc/launcher/launch/steps/LookupServerAddress.cpp
new file mode 100644
index 0000000000..f88b8f8812
--- /dev/null
+++ b/meshmc/launcher/launch/steps/LookupServerAddress.cpp
@@ -0,0 +1,125 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "LookupServerAddress.h"
+
+#include <launch/LaunchTask.h>
+
+LookupServerAddress::LookupServerAddress(LaunchTask* parent)
+ : LaunchStep(parent), m_dnsLookup(new QDnsLookup(this))
+{
+ connect(m_dnsLookup, &QDnsLookup::finished, this,
+ &LookupServerAddress::on_dnsLookupFinished);
+
+ m_dnsLookup->setType(QDnsLookup::SRV);
+}
+
+void LookupServerAddress::setLookupAddress(const QString& lookupAddress)
+{
+ m_lookupAddress = lookupAddress;
+ m_dnsLookup->setName(QString("_minecraft._tcp.%1").arg(lookupAddress));
+}
+
+void LookupServerAddress::setOutputAddressPtr(MinecraftServerTargetPtr output)
+{
+ m_output = std::move(output);
+}
+
+bool LookupServerAddress::abort()
+{
+ m_dnsLookup->abort();
+ emitFailed("Aborted");
+ return true;
+}
+
+void LookupServerAddress::executeTask()
+{
+ m_dnsLookup->lookup();
+}
+
+void LookupServerAddress::on_dnsLookupFinished()
+{
+ if (isFinished()) {
+ // Aborted
+ return;
+ }
+
+ if (m_dnsLookup->error() != QDnsLookup::NoError) {
+ emit logLine(QString("Failed to resolve server address (this is NOT an "
+ "error!) %1: %2\n")
+ .arg(m_dnsLookup->name(), m_dnsLookup->errorString()),
+ MessageLevel::MeshMC);
+ resolve(m_lookupAddress,
+ 25565); // Technically the task failed, however, we don't abort
+ // the launch and leave it up to minecraft to fail (or
+ // maybe not) when connecting
+ return;
+ }
+
+ const auto records = m_dnsLookup->serviceRecords();
+ if (records.empty()) {
+ emit logLine(
+ QString("Failed to resolve server address %1: the DNS lookup "
+ "succeeded, but no records were returned.\n")
+ .arg(m_dnsLookup->name()),
+ MessageLevel::Warning);
+ resolve(m_lookupAddress,
+ 25565); // Technically the task failed, however, we don't abort
+ // the launch and leave it up to minecraft to fail (or
+ // maybe not) when connecting
+ return;
+ }
+
+ const auto& firstRecord = records.at(0);
+ quint16 port = firstRecord.port();
+
+ emit logLine(QString("Resolved server address %1 to %2 with port %3\n")
+ .arg(m_dnsLookup->name(), firstRecord.target(),
+ QString::number(port)),
+ MessageLevel::MeshMC);
+ resolve(firstRecord.target(), port);
+}
+
+void LookupServerAddress::resolve(const QString& address, quint16 port)
+{
+ m_output->address = address;
+ m_output->port = port;
+
+ emitSucceeded();
+ m_dnsLookup->deleteLater();
+}
diff --git a/meshmc/launcher/launch/steps/LookupServerAddress.h b/meshmc/launcher/launch/steps/LookupServerAddress.h
new file mode 100644
index 0000000000..1625875c69
--- /dev/null
+++ b/meshmc/launcher/launch/steps/LookupServerAddress.h
@@ -0,0 +1,73 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <launch/LaunchStep.h>
+#include <QObjectPtr.h>
+#include <QDnsLookup>
+
+#include "minecraft/launch/MinecraftServerTarget.h"
+
+class LookupServerAddress : public LaunchStep
+{
+ Q_OBJECT
+ public:
+ explicit LookupServerAddress(LaunchTask* parent);
+ virtual ~LookupServerAddress() {};
+
+ virtual void executeTask();
+ virtual bool abort();
+ virtual bool canAbort() const
+ {
+ return true;
+ }
+
+ void setLookupAddress(const QString& lookupAddress);
+ void setOutputAddressPtr(MinecraftServerTargetPtr output);
+
+ private slots:
+ void on_dnsLookupFinished();
+
+ private:
+ void resolve(const QString& address, quint16 port);
+
+ QDnsLookup* m_dnsLookup;
+ QString m_lookupAddress;
+ MinecraftServerTargetPtr m_output;
+};
diff --git a/meshmc/launcher/launch/steps/PostLaunchCommand.cpp b/meshmc/launcher/launch/steps/PostLaunchCommand.cpp
new file mode 100644
index 0000000000..db1df3bbb4
--- /dev/null
+++ b/meshmc/launcher/launch/steps/PostLaunchCommand.cpp
@@ -0,0 +1,104 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PostLaunchCommand.h"
+#include <launch/LaunchTask.h>
+
+PostLaunchCommand::PostLaunchCommand(LaunchTask* parent) : LaunchStep(parent)
+{
+ auto instance = m_parent->instance();
+ m_command = instance->getPostExitCommand();
+ m_process.setProcessEnvironment(instance->createEnvironment());
+ connect(&m_process, &LoggedProcess::log, this,
+ &PostLaunchCommand::logLines);
+ connect(&m_process, &LoggedProcess::stateChanged, this,
+ &PostLaunchCommand::on_state);
+}
+
+void PostLaunchCommand::executeTask()
+{
+ QString postlaunch_cmd = m_parent->substituteVariables(m_command);
+ emit logLine(tr("Running Post-Launch command: %1").arg(postlaunch_cmd),
+ MessageLevel::MeshMC);
+ m_process.start(postlaunch_cmd);
+}
+
+void PostLaunchCommand::on_state(LoggedProcess::State state)
+{
+ auto getError = [&]() {
+ return tr("Post-Launch command failed with code %1.\n\n")
+ .arg(m_process.exitCode());
+ };
+ switch (state) {
+ case LoggedProcess::Aborted:
+ case LoggedProcess::Crashed:
+ case LoggedProcess::FailedToStart: {
+ auto error = getError();
+ emit logLine(error, MessageLevel::Fatal);
+ emitFailed(error);
+ return;
+ }
+ case LoggedProcess::Finished: {
+ if (m_process.exitCode() != 0) {
+ auto error = getError();
+ emit logLine(error, MessageLevel::Fatal);
+ emitFailed(error);
+ } else {
+ emit logLine(tr("Post-Launch command ran successfully.\n\n"),
+ MessageLevel::MeshMC);
+ emitSucceeded();
+ }
+ }
+ default:
+ break;
+ }
+}
+
+void PostLaunchCommand::setWorkingDirectory(const QString& wd)
+{
+ m_process.setWorkingDirectory(wd);
+}
+
+bool PostLaunchCommand::abort()
+{
+ auto state = m_process.state();
+ if (state == LoggedProcess::Running || state == LoggedProcess::Starting) {
+ m_process.kill();
+ }
+ return true;
+}
diff --git a/meshmc/launcher/launch/steps/PostLaunchCommand.h b/meshmc/launcher/launch/steps/PostLaunchCommand.h
new file mode 100644
index 0000000000..3e87dd4dfd
--- /dev/null
+++ b/meshmc/launcher/launch/steps/PostLaunchCommand.h
@@ -0,0 +1,64 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <launch/LaunchStep.h>
+#include <LoggedProcess.h>
+
+class PostLaunchCommand : public LaunchStep
+{
+ Q_OBJECT
+ public:
+ explicit PostLaunchCommand(LaunchTask* parent);
+ virtual ~PostLaunchCommand() {};
+
+ virtual void executeTask();
+ virtual bool abort();
+ virtual bool canAbort() const
+ {
+ return true;
+ }
+ void setWorkingDirectory(const QString& wd);
+ private slots:
+ void on_state(LoggedProcess::State state);
+
+ private:
+ LoggedProcess m_process;
+ QString m_command;
+};
diff --git a/meshmc/launcher/launch/steps/PreLaunchCommand.cpp b/meshmc/launcher/launch/steps/PreLaunchCommand.cpp
new file mode 100644
index 0000000000..fdaf2c0fbf
--- /dev/null
+++ b/meshmc/launcher/launch/steps/PreLaunchCommand.cpp
@@ -0,0 +1,104 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PreLaunchCommand.h"
+#include <launch/LaunchTask.h>
+
+PreLaunchCommand::PreLaunchCommand(LaunchTask* parent) : LaunchStep(parent)
+{
+ auto instance = m_parent->instance();
+ m_command = instance->getPreLaunchCommand();
+ m_process.setProcessEnvironment(instance->createEnvironment());
+ connect(&m_process, &LoggedProcess::log, this, &PreLaunchCommand::logLines);
+ connect(&m_process, &LoggedProcess::stateChanged, this,
+ &PreLaunchCommand::on_state);
+}
+
+void PreLaunchCommand::executeTask()
+{
+ // FIXME: where to put this?
+ QString prelaunch_cmd = m_parent->substituteVariables(m_command);
+ emit logLine(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd),
+ MessageLevel::MeshMC);
+ m_process.start(prelaunch_cmd);
+}
+
+void PreLaunchCommand::on_state(LoggedProcess::State state)
+{
+ auto getError = [&]() {
+ return tr("Pre-Launch command failed with code %1.\n\n")
+ .arg(m_process.exitCode());
+ };
+ switch (state) {
+ case LoggedProcess::Aborted:
+ case LoggedProcess::Crashed:
+ case LoggedProcess::FailedToStart: {
+ auto error = getError();
+ emit logLine(error, MessageLevel::Fatal);
+ emitFailed(error);
+ return;
+ }
+ case LoggedProcess::Finished: {
+ if (m_process.exitCode() != 0) {
+ auto error = getError();
+ emit logLine(error, MessageLevel::Fatal);
+ emitFailed(error);
+ } else {
+ emit logLine(tr("Pre-Launch command ran successfully.\n\n"),
+ MessageLevel::MeshMC);
+ emitSucceeded();
+ }
+ }
+ default:
+ break;
+ }
+}
+
+void PreLaunchCommand::setWorkingDirectory(const QString& wd)
+{
+ m_process.setWorkingDirectory(wd);
+}
+
+bool PreLaunchCommand::abort()
+{
+ auto state = m_process.state();
+ if (state == LoggedProcess::Running || state == LoggedProcess::Starting) {
+ m_process.kill();
+ }
+ return true;
+}
diff --git a/meshmc/launcher/launch/steps/PreLaunchCommand.h b/meshmc/launcher/launch/steps/PreLaunchCommand.h
new file mode 100644
index 0000000000..22c489d2d5
--- /dev/null
+++ b/meshmc/launcher/launch/steps/PreLaunchCommand.h
@@ -0,0 +1,64 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "launch/LaunchStep.h"
+#include "LoggedProcess.h"
+
+class PreLaunchCommand : public LaunchStep
+{
+ Q_OBJECT
+ public:
+ explicit PreLaunchCommand(LaunchTask* parent);
+ virtual ~PreLaunchCommand() {};
+
+ virtual void executeTask();
+ virtual bool abort();
+ virtual bool canAbort() const
+ {
+ return true;
+ }
+ void setWorkingDirectory(const QString& wd);
+ private slots:
+ void on_state(LoggedProcess::State state);
+
+ private:
+ LoggedProcess m_process;
+ QString m_command;
+};
diff --git a/meshmc/launcher/launch/steps/TextPrint.cpp b/meshmc/launcher/launch/steps/TextPrint.cpp
new file mode 100644
index 0000000000..0110bc7908
--- /dev/null
+++ b/meshmc/launcher/launch/steps/TextPrint.cpp
@@ -0,0 +1,54 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "TextPrint.h"
+
+TextPrint::TextPrint(LaunchTask* parent, const QStringList& lines,
+ MessageLevel::Enum level)
+ : LaunchStep(parent)
+{
+ m_lines = lines;
+ m_level = level;
+}
+TextPrint::TextPrint(LaunchTask* parent, const QString& line,
+ MessageLevel::Enum level)
+ : LaunchStep(parent)
+{
+ m_lines.append(line);
+ m_level = level;
+}
+
+void TextPrint::executeTask()
+{
+ emit logLines(m_lines, m_level);
+ emitSucceeded();
+}
+
+bool TextPrint::canAbort() const
+{
+ return true;
+}
+
+bool TextPrint::abort()
+{
+ emitFailed("Aborted.");
+ return true;
+}
diff --git a/meshmc/launcher/launch/steps/TextPrint.h b/meshmc/launcher/launch/steps/TextPrint.h
new file mode 100644
index 0000000000..4bf8ca5579
--- /dev/null
+++ b/meshmc/launcher/launch/steps/TextPrint.h
@@ -0,0 +1,66 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <launch/LaunchStep.h>
+#include <LoggedProcess.h>
+#include <java/JavaChecker.h>
+
+/*
+ * FIXME: maybe do not export
+ */
+
+class TextPrint : public LaunchStep
+{
+ Q_OBJECT
+ public:
+ explicit TextPrint(LaunchTask* parent, const QStringList& lines,
+ MessageLevel::Enum level);
+ explicit TextPrint(LaunchTask* parent, const QString& line,
+ MessageLevel::Enum level);
+ virtual ~TextPrint() {};
+
+ virtual void executeTask();
+ virtual bool canAbort() const;
+ virtual bool abort();
+
+ private:
+ QStringList m_lines;
+ MessageLevel::Enum m_level;
+};
diff --git a/meshmc/launcher/launch/steps/Update.cpp b/meshmc/launcher/launch/steps/Update.cpp
new file mode 100644
index 0000000000..f1efc8db51
--- /dev/null
+++ b/meshmc/launcher/launch/steps/Update.cpp
@@ -0,0 +1,96 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Update.h"
+#include <launch/LaunchTask.h>
+
+void Update::executeTask()
+{
+ if (m_aborted) {
+ emitFailed(tr("Task aborted."));
+ return;
+ }
+ m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode));
+ if (m_updateTask) {
+ connect(m_updateTask.get(), SIGNAL(finished()), this,
+ SLOT(updateFinished()));
+ connect(m_updateTask.get(), &Task::progress, this, &Task::setProgress);
+ connect(m_updateTask.get(), &Task::status, this, &Task::setStatus);
+ emit progressReportingRequest();
+ return;
+ }
+ emitSucceeded();
+}
+
+void Update::proceed()
+{
+ m_updateTask->start();
+}
+
+void Update::updateFinished()
+{
+ if (m_updateTask->wasSuccessful()) {
+ emitSucceeded();
+ m_updateTask.reset();
+ } else {
+ QString reason = tr("Instance update failed because: %1\n\n")
+ .arg(m_updateTask->failReason());
+ emit logLine(reason, MessageLevel::Fatal);
+ emitFailed(reason);
+ m_updateTask.reset();
+ }
+}
+
+bool Update::canAbort() const
+{
+ if (m_updateTask) {
+ return m_updateTask->canAbort();
+ }
+ return true;
+}
+
+bool Update::abort()
+{
+ m_aborted = true;
+ if (m_updateTask) {
+ if (m_updateTask->canAbort()) {
+ return m_updateTask->abort();
+ }
+ }
+ return true;
+}
diff --git a/meshmc/launcher/launch/steps/Update.h b/meshmc/launcher/launch/steps/Update.h
new file mode 100644
index 0000000000..4d6b2ac000
--- /dev/null
+++ b/meshmc/launcher/launch/steps/Update.h
@@ -0,0 +1,70 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <launch/LaunchStep.h>
+#include <QObjectPtr.h>
+#include <LoggedProcess.h>
+#include <java/JavaChecker.h>
+#include <net/Mode.h>
+
+// FIXME: stupid. should be defined by the instance type? or even completely
+// abstracted away...
+class Update : public LaunchStep
+{
+ Q_OBJECT
+ public:
+ explicit Update(LaunchTask* parent, Net::Mode mode)
+ : LaunchStep(parent), m_mode(mode) {};
+ virtual ~Update() {};
+
+ void executeTask() override;
+ bool canAbort() const override;
+ void proceed() override;
+ public slots:
+ bool abort() override;
+
+ private slots:
+ void updateFinished();
+
+ private:
+ Task::Ptr m_updateTask;
+ bool m_aborted = false;
+ Net::Mode m_mode = Net::Mode::Offline;
+};