diff options
author | Petr Mrázek <peterix@gmail.com> | 2015-05-11 22:21:37 +0200 |
---|---|---|
committer | Petr Mrázek <peterix@gmail.com> | 2015-06-30 07:16:20 +0200 |
commit | 34ddfc7ecc2b0450b3d501e65cb4203ac747ed42 (patch) | |
tree | c53d32ac89aa996347901ea64f25909231a75895 | |
parent | d14a61b0df38d150e1449b19b7eee411e91e5211 (diff) | |
download | MultiMC-34ddfc7ecc2b0450b3d501e65cb4203ac747ed42.tar MultiMC-34ddfc7ecc2b0450b3d501e65cb4203ac747ed42.tar.gz MultiMC-34ddfc7ecc2b0450b3d501e65cb4203ac747ed42.tar.lz MultiMC-34ddfc7ecc2b0450b3d501e65cb4203ac747ed42.tar.xz MultiMC-34ddfc7ecc2b0450b3d501e65cb4203ac747ed42.zip |
GH-1053 base process and launch refactor, part 1
29 files changed, 741 insertions, 601 deletions
diff --git a/application/ConsoleWindow.cpp b/application/ConsoleWindow.cpp index a96d84a1..6a3cd6b2 100644 --- a/application/ConsoleWindow.cpp +++ b/application/ConsoleWindow.cpp @@ -53,7 +53,7 @@ private: BasePage * m_log_page; }; -ConsoleWindow::ConsoleWindow(BaseProcess *process, QWidget *parent) +ConsoleWindow::ConsoleWindow(BaseLauncher *process, QWidget *parent) : QMainWindow(parent), m_proc(process) { MultiMCPlatform::fixWM_CLASS(this); diff --git a/application/ConsoleWindow.h b/application/ConsoleWindow.h index 6e3849c5..a5e9c3a4 100644 --- a/application/ConsoleWindow.h +++ b/application/ConsoleWindow.h @@ -17,7 +17,7 @@ #include <QMainWindow> #include <QSystemTrayIcon> -#include "BaseProcess.h" +#include "BaseLauncher.h" class QPushButton; class PageContainer; @@ -26,7 +26,7 @@ class ConsoleWindow : public QMainWindow Q_OBJECT public: - explicit ConsoleWindow(BaseProcess *proc, QWidget *parent = 0); + explicit ConsoleWindow(BaseLauncher *proc, QWidget *parent = 0); virtual ~ConsoleWindow(); /** @@ -47,7 +47,7 @@ slots: void onEnded(InstancePtr instance, int code, QProcess::ExitStatus status); void onLaunchFailed(InstancePtr instance); - // FIXME: add handlers for the other MinecraftProcess signals (pre/post launch command + // FIXME: add handlers for the other MinecraftLauncher signals (pre/post launch command // failures) void iconActivated(QSystemTrayIcon::ActivationReason); @@ -56,7 +56,7 @@ protected: void closeEvent(QCloseEvent *); private: - BaseProcess *m_proc = nullptr; + BaseLauncher *m_proc = nullptr; bool m_mayclose = true; QSystemTrayIcon *m_trayIcon = nullptr; PageContainer *m_container = nullptr; diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp index c595f95f..8e43ff0e 100644 --- a/application/MainWindow.cpp +++ b/application/MainWindow.cpp @@ -378,7 +378,7 @@ namespace Ui { #include "Env.h" #include "BaseInstance.h" -#include "BaseProcess.h" +#include "BaseLauncher.h" #include "java/JavaUtils.h" #include "JavaCommon.h" #include "InstancePageProvider.h" @@ -1744,7 +1744,7 @@ void MainWindow::launchInstance(InstancePtr instance, AuthSessionPtr session, return; } - BaseProcess *proc = instance->prepareForLaunch(session); + BaseLauncher *proc = instance->prepareForLaunch(session); if (!proc) return; diff --git a/application/MainWindow.h b/application/MainWindow.h index 33a5a4ed..010db55c 100644 --- a/application/MainWindow.h +++ b/application/MainWindow.h @@ -30,7 +30,7 @@ class QToolButton; class InstanceProxyModel; class LabeledToolButton; class QLabel; -class MinecraftProcess; +class MinecraftLauncher; class ConsoleWindow; class BaseProfilerFactory; class GenericPageProvider; @@ -196,7 +196,7 @@ private: class GroupView *view; InstanceProxyModel *proxymodel; NetJobPtr skin_download_job; - MinecraftProcess *proc; + MinecraftLauncher *proc; ConsoleWindow *console; LabeledToolButton *renameButton; QToolButton *changeIconButton; diff --git a/application/pages/LogPage.cpp b/application/pages/LogPage.cpp index 3fade26f..0b88193c 100644 --- a/application/pages/LogPage.cpp +++ b/application/pages/LogPage.cpp @@ -7,11 +7,11 @@ #include <QScrollBar> #include <QShortcut> -#include "BaseProcess.h" +#include "BaseLauncher.h" #include <settings/Setting.h> #include "GuiUtil.h" -LogPage::LogPage(BaseProcess *proc, QWidget *parent) +LogPage::LogPage(BaseLauncher *proc, QWidget *parent) : QWidget(parent), ui(new Ui::LogPage), m_process(proc) { ui->setupUi(this); @@ -148,14 +148,14 @@ void LogPage::write(QString data, MessageLevel::Enum mode) { if (!m_write_active) { - if (mode != MessageLevel::PrePost && mode != MessageLevel::MultiMC) + if (mode != MessageLevel::MultiMC) { return; } } if(m_stopOnOverflow && m_write_active) { - if(mode != MessageLevel::PrePost && mode != MessageLevel::MultiMC) + if(mode != MessageLevel::MultiMC) { if(ui->text->blockCount() >= ui->text->maximumBlockCount()) { @@ -231,11 +231,6 @@ void LogPage::write(QString data, MessageLevel::Enum mode) format.setBackground(QColor("black")); break; } - case MessageLevel::PrePost: - { - format.setForeground(QColor("grey")); - break; - } case MessageLevel::Info: case MessageLevel::Message: default: diff --git a/application/pages/LogPage.h b/application/pages/LogPage.h index 0cbf3d23..a420a75f 100644 --- a/application/pages/LogPage.h +++ b/application/pages/LogPage.h @@ -18,7 +18,7 @@ #include <QWidget> #include "BaseInstance.h" -#include "BaseProcess.h" +#include "BaseLauncher.h" #include "BasePage.h" #include <MultiMC.h> @@ -33,7 +33,7 @@ class LogPage : public QWidget, public BasePage Q_OBJECT public: - explicit LogPage(BaseProcess *proc, QWidget *parent = 0); + explicit LogPage(BaseLauncher *proc, QWidget *parent = 0); virtual ~LogPage(); virtual QString displayName() const override { @@ -77,7 +77,7 @@ private slots: private: Ui::LogPage *ui; - BaseProcess *m_process; + BaseLauncher *m_process; int m_last_scroll_value = 0; bool m_scroll_active = true; int m_saved_offset = 0; diff --git a/depends/launcher/org/multimc/Utils.java b/depends/launcher/org/multimc/Utils.java index cbe11fb1..c850f96d 100644 --- a/depends/launcher/org/multimc/Utils.java +++ b/depends/launcher/org/multimc/Utils.java @@ -64,7 +64,7 @@ public class Utils * Log to the MultiMC console * * @param message A String containing the message - * @param level A String containing the level name. See MinecraftProcess::getLevel() + * @param level A String containing the level name. See MinecraftLauncher::getLevel() */ public static void log(String message, String level) { diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index 4c547a77..91d46df1 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -27,7 +27,7 @@ class QDir; class Task; -class BaseProcess; +class BaseLauncher; class BaseInstance; // pointer for lazy people @@ -138,7 +138,7 @@ public: virtual std::shared_ptr<Task> doUpdate() = 0; /// returns a valid process, ready for launch with the given account. - virtual BaseProcess *prepareForLaunch(AuthSessionPtr account) = 0; + virtual BaseLauncher *prepareForLaunch(AuthSessionPtr account) = 0; /// do any necessary cleanups after the instance finishes. also runs before /// 'prepareForLaunch' diff --git a/logic/BaseLauncher.cpp b/logic/BaseLauncher.cpp new file mode 100644 index 00000000..96cb2b93 --- /dev/null +++ b/logic/BaseLauncher.cpp @@ -0,0 +1,311 @@ +/* Copyright 2013-2015 MultiMC Contributors + * + * Authors: Orochimarufan <orochimarufan.x3@gmail.com> + * + * 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 "BaseLauncher.h" +#include "MessageLevel.h" +#include <QDebug> +#include <QDir> +#include <QEventLoop> + +BaseLauncher::BaseLauncher(InstancePtr instance): m_instance(instance) +{ +} + +void BaseLauncher::initializeEnvironment() +{ + // prepare the process environment + QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment(); + + QStringList ignored = + { + "JAVA_ARGS", + "CLASSPATH", + "CONFIGPATH", + "JAVA_HOME", + "JRE_HOME", + "_JAVA_OPTIONS", + "JAVA_OPTIONS", + "JAVA_TOOL_OPTIONS" + }; + for(auto key: rawenv.keys()) + { + auto value = rawenv.value(key); + // filter out dangerous java crap + if(ignored.contains(key)) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } + // filter MultiMC-related things + if(key.startsWith("QT_")) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } +#ifdef LINUX + // Do not pass LD_* variables to java. They were intended for MultiMC + if(key.startsWith("LD_")) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } + // Strip IBus + // IBus is a Linux IME framework. For some reason, it breaks MC? + if (key == "XMODIFIERS" && value.contains(IBUS)) + { + QString save = value; + value.replace(IBUS, ""); + qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value; + } +#endif + qDebug() << "Env: " << key << value; + m_env.insert(key, value); + } +#ifdef LINUX + // HACK: Workaround for QTBUG-42500 + m_env.insert("LD_LIBRARY_PATH", ""); +#endif + + // export some infos + auto variables = getVariables(); + for (auto it = variables.begin(); it != variables.end(); ++it) + { + m_env.insert(it.key(), it.value()); + } +} + +void BaseLauncher::init() +{ + initializeEnvironment(); + + m_process.setProcessEnvironment(m_env); + connect(&m_process, &LoggedProcess::log, this, &BaseLauncher::on_log); + connect(&m_process, &LoggedProcess::stateChanged, this, &BaseLauncher::on_state); + + m_prelaunchprocess.setProcessEnvironment(m_env); + connect(&m_prelaunchprocess, &LoggedProcess::log, this, &BaseLauncher::on_log); + connect(&m_prelaunchprocess, &LoggedProcess::stateChanged, this, &BaseLauncher::on_pre_state); + + m_postlaunchprocess.setProcessEnvironment(m_env); + connect(&m_postlaunchprocess, &LoggedProcess::log, this, &BaseLauncher::on_log); + connect(&m_postlaunchprocess, &LoggedProcess::stateChanged, this, &BaseLauncher::on_post_state); + + // a process has been constructed for the instance. It is running from MultiMC POV + m_instance->setRunning(true); +} + + +void BaseLauncher::setWorkdir(QString path) +{ + QDir mcDir(path); + m_process.setWorkingDirectory(mcDir.absolutePath()); + m_prelaunchprocess.setWorkingDirectory(mcDir.absolutePath()); + m_postlaunchprocess.setWorkingDirectory(mcDir.absolutePath()); +} + +void BaseLauncher::printHeader() +{ + emit log(m_header); +} + +void BaseLauncher::on_log(QStringList lines, MessageLevel::Enum level) +{ + logOutput(lines, level); +} + +void BaseLauncher::logOutput(const QStringList &lines, MessageLevel::Enum defaultLevel) +{ + for (auto & line: lines) + { + logOutput(line, defaultLevel); + } +} + +void BaseLauncher::logOutput(QString line, MessageLevel::Enum level) +{ + // if the launcher 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 = this->guessLevel(line, level); + } + + // censor private user info + line = censorPrivateInfo(line); + + emit log(line, level); +} + +void BaseLauncher::preLaunch() +{ + QString prelaunch_cmd = m_instance->settings()->get("PreLaunchCommand").toString(); + if (!prelaunch_cmd.isEmpty()) + { + prelaunch_cmd = substituteVariables(prelaunch_cmd); + // Launch + emit log(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd)); + m_prelaunchprocess.start(prelaunch_cmd); + } + else + { + on_pre_state(LoggedProcess::Skipped); + } +} + +void BaseLauncher::on_pre_state(LoggedProcess::State state) +{ + switch(state) + { + case LoggedProcess::Aborted: + case LoggedProcess::Crashed: + case LoggedProcess::FailedToStart: + { + emit log(tr("Pre-Launch command failed with code %1.\n\n") + .arg(m_prelaunchprocess.exitCode()), + MessageLevel::Fatal); + m_instance->cleanupAfterRun(); + emit prelaunch_failed(m_instance, m_prelaunchprocess.exitCode(), m_prelaunchprocess.exitStatus()); + // not running, failed + m_instance->setRunning(false); + } + case LoggedProcess::Finished: + { + emit log(tr("Pre-Launch command ran successfully.\n\n")); + m_instance->reload(); + } + case LoggedProcess::Skipped: + default: + break; + } +} + +void BaseLauncher::on_state(LoggedProcess::State state) +{ + QProcess::ExitStatus estat = QProcess::NormalExit; + switch(state) + { + case LoggedProcess::Aborted: + case LoggedProcess::Crashed: + case LoggedProcess::FailedToStart: + estat = QProcess::CrashExit; + case LoggedProcess::Finished: + { + auto exitCode = m_process.exitCode(); + m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); + + // run post-exit + postLaunch(); + m_instance->cleanupAfterRun(); + // no longer running... + m_instance->setRunning(false); + emit ended(m_instance, exitCode, estat); + } + case LoggedProcess::Skipped: + qWarning() << "Illegal game state: Skipped"; + default: + break; + } +} + +void BaseLauncher::on_post_state(LoggedProcess::State state) +{ + switch(state) + { + case LoggedProcess::Aborted: + case LoggedProcess::Crashed: + case LoggedProcess::FailedToStart: + case LoggedProcess::Finished: + case LoggedProcess::Skipped: + { + + } + default: + break; + } +} + +void BaseLauncher::killProcess() +{ + killed = true; + if (m_prelaunchprocess.state() == LoggedProcess::Running) + { + m_prelaunchprocess.kill(); + } + else if(m_process.state() == LoggedProcess::Running) + { + m_process.kill(); + } + else if(m_postlaunchprocess.state() == LoggedProcess::Running) + { + m_postlaunchprocess.kill(); + } +} + +void BaseLauncher::postLaunch() +{ + QString postlaunch_cmd = m_instance->settings()->get("PostExitCommand").toString(); + if (!postlaunch_cmd.isEmpty()) + { + postlaunch_cmd = substituteVariables(postlaunch_cmd); + emit log(tr("Running Post-Launch command: %1").arg(postlaunch_cmd)); + m_postlaunchprocess.start(postlaunch_cmd); + if (m_postlaunchprocess.exitStatus() != QProcess::NormalExit) + { + emit log(tr("Post-Launch command failed with code %1.\n\n") + .arg(m_postlaunchprocess.exitCode()), + MessageLevel::Error); + emit postlaunch_failed(m_instance, m_postlaunchprocess.exitCode(), + m_postlaunchprocess.exitStatus()); + // not running, failed + m_instance->setRunning(false); + } + else + emit log(tr("Post-Launch command ran successfully.\n\n")); + } +} + +QString BaseLauncher::substituteVariables(const QString &cmd) const +{ + QString out = cmd; + auto variables = 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; +} + +qint64 BaseLauncher::pid() +{ +#ifdef Q_OS_WIN + struct _PROCESS_INFORMATION *procinfo = m_process.pid(); + return procinfo->dwProcessId; +#else + return m_process.pid(); +#endif +} diff --git a/logic/BaseProcess.h b/logic/BaseLauncher.h index 75bf0217..f47d3351 100644 --- a/logic/BaseProcess.h +++ b/logic/BaseLauncher.h @@ -18,36 +18,18 @@ #pragma once #include <QProcess> #include "BaseInstance.h" +#include "MessageLevel.h" +#include "LoggedProcess.h" -/** - * @brief the MessageLevel Enum - * defines what level a message is - */ -namespace MessageLevel -{ -enum Enum -{ - MultiMC, /**< MultiMC Messages */ - Debug, /**< Debug Messages */ - Info, /**< Info Messages */ - Message, /**< Standard Messages */ - Warning, /**< Warnings */ - Error, /**< Errors */ - Fatal, /**< Fatal Errors */ - PrePost, /**< Pre/Post Launch command output */ -}; -MessageLevel::Enum getLevel(const QString &levelName); -} - -class BaseProcess: public QProcess +class BaseLauncher: public QObject { Q_OBJECT protected: - explicit BaseProcess(InstancePtr instance); + explicit BaseLauncher(InstancePtr instance); void init(); public: /* methods */ - virtual ~BaseProcess() {}; + virtual ~BaseLauncher() {}; InstancePtr instance() { @@ -64,6 +46,8 @@ public: /* methods */ void killProcess(); + qint64 pid(); + /** * @brief prepare the process for launch (for multi-stage launch) */ @@ -80,10 +64,10 @@ public: /* methods */ virtual void abort() = 0; protected: /* methods */ - bool preLaunch(); - bool postLaunch(); - bool waitForPrePost(); + void preLaunch(); + void postLaunch(); QString substituteVariables(const QString &cmd) const; + void initializeEnvironment(); void printHeader(); @@ -120,23 +104,22 @@ signals: void log(QString text, MessageLevel::Enum level = MessageLevel::MultiMC); protected slots: - void finish(int, QProcess::ExitStatus status); - void on_stdErr(); - void on_stdOut(); - void on_prepost_stdOut(); - void on_prepost_stdErr(); - void logOutput(const QStringList &lines, - MessageLevel::Enum defaultLevel = MessageLevel::Message, - bool guessLevel = true, bool censor = true); - void logOutput(QString line, - MessageLevel::Enum defaultLevel = MessageLevel::Message, - bool guessLevel = true, bool censor = true); + void on_log(QStringList lines, MessageLevel::Enum level); + void logOutput(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::Message); + void logOutput(QString line, MessageLevel::Enum defaultLevel = MessageLevel::Message); + + void on_pre_state(LoggedProcess::State state); + void on_state(LoggedProcess::State state); + void on_post_state(LoggedProcess::State state); protected: InstancePtr m_instance; - QString m_err_leftover; - QString m_out_leftover; - QProcess m_prepostlaunchprocess; + + LoggedProcess m_prelaunchprocess; + LoggedProcess m_postlaunchprocess; + LoggedProcess m_process; + QProcessEnvironment m_env; + bool killed = false; QString m_header; }; diff --git a/logic/BaseProcess.cpp b/logic/BaseProcess.cpp deleted file mode 100644 index 852bec4c..00000000 --- a/logic/BaseProcess.cpp +++ /dev/null @@ -1,420 +0,0 @@ -/* Copyright 2013-2015 MultiMC Contributors - * - * Authors: Orochimarufan <orochimarufan.x3@gmail.com> - * - * 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 "BaseProcess.h" -#include <QDebug> -#include <QDir> -#include <QEventLoop> - -#define IBUS "@im=ibus" - -MessageLevel::Enum MessageLevel::getLevel(const QString& levelName) -{ - if (levelName == "MultiMC") - return MessageLevel::MultiMC; - else if (levelName == "Debug") - return MessageLevel::Debug; - else if (levelName == "Info") - return MessageLevel::Info; - else if (levelName == "Message") - return MessageLevel::Message; - else if (levelName == "Warning") - return MessageLevel::Warning; - else if (levelName == "Error") - return MessageLevel::Error; - else if (levelName == "Fatal") - return MessageLevel::Fatal; - // Skip PrePost, it's not exposed to !![]! - else - return MessageLevel::Message; -} - -BaseProcess::BaseProcess(InstancePtr instance): QProcess(), m_instance(instance) -{ -} - -void BaseProcess::init() -{ - connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), - SLOT(finish(int, QProcess::ExitStatus))); - - // prepare the process environment - QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment(); - - QProcessEnvironment env; - - QStringList ignored = - { - "JAVA_ARGS", - "CLASSPATH", - "CONFIGPATH", - "JAVA_HOME", - "JRE_HOME", - "_JAVA_OPTIONS", - "JAVA_OPTIONS", - "JAVA_TOOL_OPTIONS", - "CDPATH" - }; - for(auto key: rawenv.keys()) - { - auto value = rawenv.value(key); - // filter out dangerous java crap - if(ignored.contains(key)) - { - qDebug() << "Env: ignoring" << key << value; - continue; - } - // filter MultiMC-related things - if(key.startsWith("QT_")) - { - qDebug() << "Env: ignoring" << key << value; - continue; - } -#ifdef Q_OS_LINUX - // Do not pass LD_* variables to java. They were intended for MultiMC - if(key.startsWith("LD_")) - { - qDebug() << "Env: ignoring" << key << value; - continue; - } - // Strip IBus - // IBus is a Linux IME framework. For some reason, it breaks MC? - if (key == "XMODIFIERS" && value.contains(IBUS)) - { - QString save = value; - value.replace(IBUS, ""); - qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value; - } -#endif - if(key == "GAME_PRELOAD") - { - env.insert("LD_PRELOAD", value); - continue; - } - if(key == "GAME_LIBRARY_PATH") - { - env.insert("LD_LIBRARY_PATH", value); - continue; - } - qDebug() << "Env: " << key << value; - env.insert(key, value); - } -#ifdef Q_OS_LINUX - // HACK: Workaround for QTBUG-42500 - if(!env.contains("LD_LIBRARY_PATH")) - { - env.insert("LD_LIBRARY_PATH", ""); - } -#endif - - // export some infos - auto variables = getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) - { - env.insert(it.key(), it.value()); - } - - this->setProcessEnvironment(env); - m_prepostlaunchprocess.setProcessEnvironment(env); - - // std channels - connect(this, SIGNAL(readyReadStandardError()), SLOT(on_stdErr())); - connect(this, SIGNAL(readyReadStandardOutput()), SLOT(on_stdOut())); - - // Log prepost launch command output (can be disabled.) - if (m_instance->settings()->get("LogPrePostOutput").toBool()) - { - connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardError, this, - &BaseProcess::on_prepost_stdErr); - connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardOutput, this, - &BaseProcess::on_prepost_stdOut); - } - - // a process has been constructed for the instance. It is running from MultiMC POV - m_instance->setRunning(true); -} - - -void BaseProcess::setWorkdir(QString path) -{ - QDir mcDir(path); - this->setWorkingDirectory(mcDir.absolutePath()); - m_prepostlaunchprocess.setWorkingDirectory(mcDir.absolutePath()); -} - -void BaseProcess::printHeader() -{ - emit log(m_header); -} - - -void BaseProcess::logOutput(const QStringList &lines, MessageLevel::Enum defaultLevel, - bool guessLevel, bool censor) -{ - for (int i = 0; i < lines.size(); ++i) - logOutput(lines[i], defaultLevel, guessLevel, censor); -} - -void BaseProcess::logOutput(QString line, MessageLevel::Enum defaultLevel, bool guessLevel, - bool censor) -{ - MessageLevel::Enum level = defaultLevel; - - // Level prefix - int endmark = line.indexOf("]!"); - if (line.startsWith("!![") && endmark != -1) - { - level = MessageLevel::getLevel(line.left(endmark).mid(3)); - line = line.mid(endmark + 2); - } - // Guess level - else if (guessLevel) - level = this->guessLevel(line, defaultLevel); - - if (censor) - line = censorPrivateInfo(line); - - emit log(line, level); -} - -void BaseProcess::on_stdErr() -{ - QByteArray data = readAllStandardError(); - QString str = m_err_leftover + QString::fromLocal8Bit(data); - - str.remove('\r'); - QStringList lines = str.split("\n"); - m_err_leftover = lines.takeLast(); - - logOutput(lines, MessageLevel::Error); -} - -void BaseProcess::on_stdOut() -{ - QByteArray data = readAllStandardOutput(); - QString str = m_out_leftover + QString::fromLocal8Bit(data); - - str.remove('\r'); - QStringList lines = str.split("\n"); - m_out_leftover = lines.takeLast(); - - logOutput(lines); -} - -void BaseProcess::on_prepost_stdErr() -{ - QByteArray data = m_prepostlaunchprocess.readAllStandardError(); - QString str = m_err_leftover + QString::fromLocal8Bit(data); - - str.remove('\r'); - QStringList lines = str.split("\n"); - m_err_leftover = lines.takeLast(); - - logOutput(lines, MessageLevel::PrePost, false, false); -} - -void BaseProcess::on_prepost_stdOut() -{ - QByteArray data = m_prepostlaunchprocess.readAllStandardOutput(); - QString str = m_out_leftover + QString::fromLocal8Bit(data); - - str.remove('\r'); - QStringList lines = str.split("\n"); - m_out_leftover = lines.takeLast(); - - logOutput(lines, MessageLevel::PrePost, false, false); -} - -// exit handler -void BaseProcess::finish(int code, ExitStatus status) -{ - // Flush console window - if (!m_err_leftover.isEmpty()) - { - logOutput(m_err_leftover, MessageLevel::Error); - m_err_leftover.clear(); - } - if (!m_out_leftover.isEmpty()) - { - logOutput(m_out_leftover); - m_out_leftover.clear(); - } - - if (!killed) - { - if (status == NormalExit) - { - //: Message displayed on instance exit - emit log(tr("Game exited with exitcode %1.").arg(code)); - } - else - { - //: Message displayed on instance crashed - if(code == -1) - emit log(tr("Game crashed.").arg(code)); - else - emit log(tr("Game crashed with exitcode %1.").arg(code)); - } - } - else - { - //: Message displayed after the instance exits due to kill request - emit log(tr("Game was killed by user."), MessageLevel::Error); - } - - m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code)); - - // run post-exit - postLaunch(); - m_instance->cleanupAfterRun(); - // no longer running... - m_instance->setRunning(false); - emit ended(m_instance, code, status); -} - -void BaseProcess::killProcess() -{ - killed = true; - if (m_prepostlaunchprocess.state() == QProcess::Running) - { - m_prepostlaunchprocess.kill(); - } - else - { - kill(); - } -} - -bool BaseProcess::preLaunch() -{ - QString prelaunch_cmd = m_instance->settings()->get("PreLaunchCommand").toString(); - if (!prelaunch_cmd.isEmpty()) - { - prelaunch_cmd = substituteVariables(prelaunch_cmd); - // Launch - emit log(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd)); - m_prepostlaunchprocess.start(prelaunch_cmd); - if (!waitForPrePost()) - { - emit log(tr("The command failed to start"), MessageLevel::Fatal); - return false; - } - // Flush console window - if (!m_err_leftover.isEmpty()) - { - logOutput(m_err_leftover, MessageLevel::PrePost); - m_err_leftover.clear(); - } - if (!m_out_leftover.isEmpty()) - { - logOutput(m_out_leftover, MessageLevel::PrePost); - m_out_leftover.clear(); - } - // Process return values - if (m_prepostlaunchprocess.exitStatus() != NormalExit) - { - emit log(tr("Pre-Launch command failed with code %1.\n\n") - .arg(m_prepostlaunchprocess.exitCode()), - MessageLevel::Fatal); - m_instance->cleanupAfterRun(); - emit prelaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(), - m_prepostlaunchprocess.exitStatus()); - // not running, failed - m_instance->setRunning(false); - return false; - } - else - emit log(tr("Pre-Launch command ran successfully.\n\n")); - - return m_instance->reload(); - } - return true; -} -bool BaseProcess::postLaunch() -{ - QString postlaunch_cmd = m_instance->settings()->get("PostExitCommand").toString(); - if (!postlaunch_cmd.isEmpty()) - { - postlaunch_cmd = substituteVariables(postlaunch_cmd); - emit log(tr("Running Post-Launch command: %1").arg(postlaunch_cmd)); - m_prepostlaunchprocess.start(postlaunch_cmd); - if (!waitForPrePost()) - { - return false; - } - // Flush console window - if (!m_err_leftover.isEmpty()) - { - logOutput(m_err_leftover, MessageLevel::PrePost); - m_err_leftover.clear(); - } - if (!m_out_leftover.isEmpty()) - { - logOutput(m_out_leftover, MessageLevel::PrePost); - m_out_leftover.clear(); - } - if (m_prepostlaunchprocess.exitStatus() != NormalExit) - { - emit log(tr("Post-Launch command failed with code %1.\n\n") - .arg(m_prepostlaunchprocess.exitCode()), - MessageLevel::Error); - emit postlaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(), - m_prepostlaunchprocess.exitStatus()); - // not running, failed - m_instance->setRunning(false); - } - else - emit log(tr("Post-Launch command ran successfully.\n\n")); - - return m_instance->reload(); - } - return true; -} - -bool BaseProcess::waitForPrePost() -{ - if (!m_prepostlaunchprocess.waitForStarted()) - return false; - QEventLoop eventLoop; - auto finisher = [this, &eventLoop](QProcess::ProcessState state) - { - if (state == QProcess::NotRunning) - { - eventLoop.quit(); - } - }; - auto connection = connect(&m_prepostlaunchprocess, &QProcess::stateChanged, finisher); - int ret = eventLoop.exec(); - disconnect(connection); - return ret == 0; -} - -QString BaseProcess::substituteVariables(const QString &cmd) const -{ - QString out = cmd; - auto variables = 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/logic/CMakeLists.txt b/logic/CMakeLists.txt index e6e36225..19686aa0 100644 --- a/logic/CMakeLists.txt +++ b/logic/CMakeLists.txt @@ -8,9 +8,13 @@ set(LOGIC_SOURCES BaseVersionList.cpp InstanceList.h InstanceList.cpp + LoggedProcess.h + LoggedProcess.cpp + MessageLevel.cpp + MessageLevel.h BaseVersion.h - BaseProcess.h - BaseProcess.cpp + BaseLauncher.h + BaseLauncher.cpp BaseInstance.h BaseInstance.cpp NullInstance.h @@ -136,8 +140,8 @@ set(LOGIC_SOURCES minecraft/JarMod.h minecraft/MinecraftInstance.cpp minecraft/MinecraftInstance.h - minecraft/MinecraftProcess.cpp - minecraft/MinecraftProcess.h + minecraft/MinecraftLauncher.cpp + minecraft/MinecraftLauncher.h minecraft/MinecraftVersion.cpp minecraft/MinecraftVersion.h minecraft/MinecraftVersionList.cpp diff --git a/logic/LoggedProcess.cpp b/logic/LoggedProcess.cpp new file mode 100644 index 00000000..53840621 --- /dev/null +++ b/logic/LoggedProcess.cpp @@ -0,0 +1,147 @@ +#include "LoggedProcess.h" +#include "MessageLevel.h" +#include <QDebug> + +LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent) +{ + // QProcess has a strange interface... let's map a lot of those into a few. + connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut); + connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr); + connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus))); + connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError))); + connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange); +} + +QStringList reprocess(const QByteArray & data, QString & leftover) +{ + QString str = leftover + QString::fromLocal8Bit(data); + + str.remove('\r'); + QStringList lines = str.split("\n"); + leftover = lines.takeLast(); + return lines; +} + +void LoggedProcess::on_stdErr() +{ + auto lines = reprocess(readAllStandardError(), m_err_leftover); + emit log(lines, MessageLevel::StdErr); +} + +void LoggedProcess::on_stdOut() +{ + auto lines = reprocess(readAllStandardOutput(), m_out_leftover); + emit log(lines, MessageLevel::StdOut); +} + +void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status) +{ + // Flush console window + if (!m_err_leftover.isEmpty()) + { + emit log({m_err_leftover}, MessageLevel::StdErr); + m_err_leftover.clear(); + } + if (!m_out_leftover.isEmpty()) + { + emit log({m_err_leftover}, MessageLevel::StdOut); + m_out_leftover.clear(); + } + + // based on state, send signals + if (!m_is_aborting) + { + if (status == QProcess::NormalExit) + { + //: Message displayed on instance exit + emit log({tr("Process exited with code %1.").arg(exit_code)}, MessageLevel::MultiMC); + changeState(LoggedProcess::Finished); + } + else + { + //: Message displayed on instance crashed + if(exit_code == -1) + emit log({tr("Process crashed.").arg(exit_code)}, MessageLevel::MultiMC); + else + emit log({tr("Process crashed with exitcode %1.").arg(exit_code)}, MessageLevel::MultiMC); + changeState(LoggedProcess::Crashed); + } + } + else + { + //: Message displayed after the instance exits due to kill request + emit log({tr("Process was killed by user.")}, MessageLevel::Error); + changeState(LoggedProcess::Aborted); + } +} + +void LoggedProcess::on_error(QProcess::ProcessError error) +{ + switch(error) + { + case QProcess::FailedToStart: + { + emit log({tr("The process failed to start.")}, MessageLevel::Fatal); + changeState(LoggedProcess::FailedToStart); + break; + } + // we'll just ignore those... never needed them + case QProcess::Crashed: + case QProcess::ReadError: + case QProcess::Timedout: + case QProcess::UnknownError: + case QProcess::WriteError: + break; + } +} + +void LoggedProcess::kill() +{ + m_is_aborting = true; + QProcess::kill(); +} + +int LoggedProcess::exitCode() const +{ + return m_exit_code; +} + +void LoggedProcess::changeState(LoggedProcess::State state) +{ + if(state == m_state) + return; + m_state = state; + emit stateChanged(m_state); +} + +LoggedProcess::State LoggedProcess::state() const +{ + return m_state; +} + +void LoggedProcess::on_stateChange(QProcess::ProcessState state) +{ + switch(state) + { + case QProcess::NotRunning: + break; // let's not - there are too many that handle this already. + case QProcess::Starting: + { + if(m_state != LoggedProcess::NotRunning) + { + qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Starting; + } + changeState(LoggedProcess::Starting); + return; + } + case QProcess::Running: + { + if(m_state != LoggedProcess::Starting) + { + qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Running; + } + changeState(LoggedProcess::Running); + return; + } + } +} diff --git a/logic/LoggedProcess.h b/logic/LoggedProcess.h new file mode 100644 index 00000000..253be2c1 --- /dev/null +++ b/logic/LoggedProcess.h @@ -0,0 +1,60 @@ +#pragma once +#include <QProcess> +#include "MessageLevel.h" + +/* + * This is a basic process. + * It has line-based logging support and hides some of the nasty bits. + */ +class LoggedProcess : public QProcess +{ +Q_OBJECT +public: + enum State + { + NotRunning, + Starting, + FailedToStart, + Running, + Finished, + Crashed, + Aborted, + Skipped + }; + +public: + explicit LoggedProcess(QObject* parent = 0); + virtual ~LoggedProcess() {}; + + State state() const; + int exitCode() const; + +signals: + void log(QStringList lines, MessageLevel::Enum level); + void stateChanged(LoggedProcess::State state); + +public slots: + /** + * @brief kill the process - equivalent to kill -9 + */ + void kill(); + + +private slots: + void on_stdErr(); + void on_stdOut(); + void on_exit(int exit_code, QProcess::ExitStatus status); + void on_error(QProcess::ProcessError error); + void on_stateChange(QProcess::ProcessState); + +private: + void changeState(LoggedProcess::State state); + +private: + QString m_err_leftover; + QString m_out_leftover; + bool m_killed = false; + State m_state = NotRunning; + int m_exit_code = 0; + bool m_is_aborting = false; +}; diff --git a/logic/MessageLevel.cpp b/logic/MessageLevel.cpp new file mode 100644 index 00000000..a5191290 --- /dev/null +++ b/logic/MessageLevel.cpp @@ -0,0 +1,36 @@ +#include "MessageLevel.h" + +MessageLevel::Enum MessageLevel::getLevel(const QString& levelName) +{ + if (levelName == "MultiMC") + return MessageLevel::MultiMC; + else if (levelName == "Debug") + return MessageLevel::Debug; + else if (levelName == "Info") + return MessageLevel::Info; + else if (levelName == "Message") + return MessageLevel::Message; + else if (levelName == "Warning") + return MessageLevel::Warning; + else if (levelName == "Error") + return MessageLevel::Error; + else if (levelName == "Fatal") + return MessageLevel::Fatal; + // Skip PrePost, it's not exposed to !![]! + // Also skip StdErr and StdOut + else + return MessageLevel::Unknown; +} + +MessageLevel::Enum MessageLevel::fromLine(QString &line) +{ + // Level prefix + int endmark = line.indexOf("]!"); + if (line.startsWith("!![") && endmark != -1) + { + auto level = MessageLevel::getLevel(line.left(endmark).mid(3)); + line = line.mid(endmark + 2); + return level; + } + return MessageLevel::Unknown; +} diff --git a/logic/MessageLevel.h b/logic/MessageLevel.h new file mode 100644 index 00000000..0128148d --- /dev/null +++ b/logic/MessageLevel.h @@ -0,0 +1,28 @@ +#pragma once + +#include <QString> + +/** + * @brief the MessageLevel Enum + * defines what level a log message is + */ +namespace MessageLevel +{ +enum Enum +{ + Unknown, /**< No idea what this is or where it came from */ + StdOut, /**< Undetermined stderr messages */ + StdErr, /**< Undetermined stdout messages */ + MultiMC, /**< MultiMC Messages */ + Debug, /**< Debug Messages */ + Info, /**< Info Messages */ + Message, /**< Standard Messages */ + Warning, /**< Warnings */ + Error, /**< Errors */ + Fatal, /**< Fatal Errors */ +}; +MessageLevel::Enum getLevel(const QString &levelName); + +/* Get message level from a line. Line is modified if it was successful. */ +MessageLevel::Enum fromLine(QString &line); +} diff --git a/logic/NullInstance.h b/logic/NullInstance.h index cf8c7fce..5e81b2e4 100644 --- a/logic/NullInstance.h +++ b/logic/NullInstance.h @@ -43,7 +43,7 @@ public: { return instanceRoot(); }; - virtual BaseProcess* prepareForLaunch(AuthSessionPtr) + virtual BaseLauncher* prepareForLaunch(AuthSessionPtr) { return nullptr; } diff --git a/logic/minecraft/LegacyInstance.cpp b/logic/minecraft/LegacyInstance.cpp index 387975fa..a2b813e2 100644 --- a/logic/minecraft/LegacyInstance.cpp +++ b/logic/minecraft/LegacyInstance.cpp @@ -24,7 +24,7 @@ #include "minecraft/LegacyUpdate.h" #include "icons/IconList.h" -#include "minecraft/MinecraftProcess.h" +#include "minecraft/MinecraftLauncher.h" #include "minecraft/ModList.h" LegacyInstance::LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) @@ -95,7 +95,7 @@ std::shared_ptr<Task> LegacyInstance::doUpdate() return std::shared_ptr<Task>(new LegacyUpdate(this, this)); } -BaseProcess *LegacyInstance::prepareForLaunch(AuthSessionPtr account) +BaseLauncher *LegacyInstance::prepareForLaunch(AuthSessionPtr account) { QString launchScript; QIcon icon = ENV.icons()->getIcon(iconKey()); @@ -122,7 +122,7 @@ BaseProcess *LegacyInstance::prepareForLaunch(AuthSessionPtr account) launchScript += "lwjgl " + lwjgl + "\n"; launchScript += "launcher legacy\n"; } - auto process = MinecraftProcess::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr())); + auto process = MinecraftLauncher::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr())); process->setLaunchScript(launchScript); process->setWorkdir(minecraftRoot()); process->setLogin(account); diff --git a/logic/minecraft/LegacyInstance.h b/logic/minecraft/LegacyInstance.h index 64bcb08b..236771f4 100644 --- a/logic/minecraft/LegacyInstance.h +++ b/logic/minecraft/LegacyInstance.h @@ -111,7 +111,7 @@ public: virtual void setShouldUpdate(bool val) override; virtual std::shared_ptr<Task> doUpdate() override; - virtual BaseProcess *prepareForLaunch(AuthSessionPtr account) override; + virtual BaseLauncher *prepareForLaunch(AuthSessionPtr account) override; virtual void cleanupAfterRun() override; virtual QString getStatusbarDescription() override; diff --git a/logic/minecraft/MinecraftProcess.cpp b/logic/minecraft/MinecraftLauncher.cpp index d0f2a6cc..60fc935f 100644 --- a/logic/minecraft/MinecraftProcess.cpp +++ b/logic/minecraft/MinecraftLauncher.cpp @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "minecraft/MinecraftProcess.h" +#include "minecraft/MinecraftLauncher.h" #include "BaseInstance.h" #include <java/JavaChecker.h> #include <MMCStrings.h> @@ -32,19 +32,19 @@ #include "cmdutils.h" // constructor -MinecraftProcess::MinecraftProcess(MinecraftInstancePtr inst) : BaseProcess(inst) +MinecraftLauncher::MinecraftLauncher(MinecraftInstancePtr inst) : BaseLauncher(inst) { } -MinecraftProcess* MinecraftProcess::create(MinecraftInstancePtr inst) +MinecraftLauncher* MinecraftLauncher::create(MinecraftInstancePtr inst) { - auto proc = new MinecraftProcess(inst); + auto proc = new MinecraftLauncher(inst); proc->init(); return proc; } -QString MinecraftProcess::censorPrivateInfo(QString in) +QString MinecraftLauncher::censorPrivateInfo(QString in) { if (!m_session) return in; @@ -67,7 +67,7 @@ QString MinecraftProcess::censorPrivateInfo(QString in) } // console window -MessageLevel::Enum MinecraftProcess::guessLevel(const QString &line, MessageLevel::Enum level) +MessageLevel::Enum MinecraftLauncher::guessLevel(const QString &line, MessageLevel::Enum level) { QRegularExpression re("\\[(?<timestamp>[0-9:]+)\\] \\[[^/]+/(?<level>[^\\]]+)\\]"); auto match = re.match(line); @@ -107,7 +107,7 @@ MessageLevel::Enum MinecraftProcess::guessLevel(const QString &line, MessageLeve return level; } -QMap<QString, QString> MinecraftProcess::getVariables() const +QMap<QString, QString> MinecraftLauncher::getVariables() const { auto mcInstance = std::dynamic_pointer_cast<MinecraftInstance>(m_instance); QMap<QString, QString> out; @@ -120,7 +120,7 @@ QMap<QString, QString> MinecraftProcess::getVariables() const return out; } -QStringList MinecraftProcess::javaArguments() const +QStringList MinecraftLauncher::javaArguments() const { QStringList args; @@ -161,75 +161,83 @@ QStringList MinecraftProcess::javaArguments() const return args; } -void MinecraftProcess::arm() +bool MinecraftLauncher::checkJava(QString JavaPath) +{ + auto realJavaPath = QStandardPaths::findExecutable(JavaPath); + if (realJavaPath.isEmpty()) + { + emit log(tr("The java binary \"%1\" couldn't be found. You may have to set up java " + "if Minecraft fails to launch.").arg(JavaPath), + MessageLevel::Warning); + } + + QFileInfo javaInfo(realJavaPath); + qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch(); + auto storedUnixTime = m_instance->settings()->get("JavaTimestamp").toLongLong(); + // if they are not the same, check! + if(javaUnixTime != storedUnixTime) + { + QEventLoop ev; + auto checker = std::make_shared<JavaChecker>(); + bool successful = false; + QString errorLog; + QString version; + emit log(tr("Checking Java version..."), MessageLevel::MultiMC); + connect(checker.get(), &JavaChecker::checkFinished, + [&](JavaCheckResult result) + { + successful = result.valid; + errorLog = result.errorLog; + version = result.javaVersion; + ev.exit(); + }); + checker->m_path = realJavaPath; + checker->performCheck(); + ev.exec(); + if(!successful) + { + // Error message displayed if java can't start + emit log(tr("Could not start java:"), MessageLevel::Error); + auto lines = errorLog.split('\n'); + for(auto line: lines) + { + emit log(line, MessageLevel::Error); + } + emit log("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC); + m_instance->cleanupAfterRun(); + emit launch_failed(m_instance); + // not running, failed + m_instance->setRunning(false); + return false; + } + emit log(tr("Java version is %1!\n").arg(version), MessageLevel::MultiMC); + m_instance->settings()->set("JavaVersion", version); + m_instance->settings()->set("JavaTimestamp", javaUnixTime); + } + return true; +} + +void MinecraftLauncher::arm() { printHeader(); - emit log("Minecraft folder is:\n" + workingDirectory() + "\n\n"); + emit log("Minecraft folder is:\n" + m_process.workingDirectory() + "\n\n"); + /* if (!preLaunch()) { emit ended(m_instance, 1, QProcess::CrashExit); return; } + */ m_instance->setLastLaunch(); QString JavaPath = m_instance->settings()->get("JavaPath").toString(); emit log("Java path is:\n" + JavaPath + "\n\n"); - auto realJavaPath = QStandardPaths::findExecutable(JavaPath); - if (realJavaPath.isEmpty()) + if(!checkJava(JavaPath)) { - emit log(tr("The java binary \"%1\" couldn't be found. You may have to set up java " - "if Minecraft fails to launch.").arg(JavaPath), - MessageLevel::Warning); - } - - // check java version here. - { - QFileInfo javaInfo(realJavaPath); - qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch(); - auto storedUnixTime = m_instance->settings()->get("JavaTimestamp").toLongLong(); - // if they are not the same, check! - if(javaUnixTime != storedUnixTime) - { - QEventLoop ev; - auto checker = std::make_shared<JavaChecker>(); - bool successful = false; - QString errorLog; - QString version; - emit log(tr("Checking Java version..."), MessageLevel::MultiMC); - connect(checker.get(), &JavaChecker::checkFinished, - [&](JavaCheckResult result) - { - successful = result.valid; - errorLog = result.errorLog; - version = result.javaVersion; - ev.exit(); - }); - checker->m_path = realJavaPath; - checker->performCheck(); - ev.exec(); - if(!successful) - { - // Error message displayed if java can't start - emit log(tr("Could not start java:"), MessageLevel::Error); - auto lines = errorLog.split('\n'); - for(auto line: lines) - { - emit log(line, MessageLevel::Error); - } - emit log("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC); - m_instance->cleanupAfterRun(); - emit launch_failed(m_instance); - // not running, failed - m_instance->setRunning(false); - return; - } - emit log(tr("Java version is %1!\n").arg(version), MessageLevel::MultiMC); - m_instance->settings()->set("JavaVersion", version); - m_instance->settings()->set("JavaTimestamp", javaUnixTime); - } + return; } QStringList args = javaArguments(); @@ -250,14 +258,15 @@ void MinecraftProcess::arm() } emit log("Wrapper command is:\n" + wrapperCommand + "\n\n"); args.prepend(JavaPath); - start(wrapperCommand, args); + m_process.start(wrapperCommand, args); } else { - start(JavaPath, args); + m_process.start(JavaPath, args); } - if (!waitForStarted()) + // instantiate the launcher part + if (!m_process.waitForStarted()) { //: Error message displayed if instace can't start emit log(tr("Could not launch minecraft!"), MessageLevel::Error); @@ -268,23 +277,20 @@ void MinecraftProcess::arm() return; } - emit log(tr("Minecraft process ID: %1\n\n").arg(processId()), MessageLevel::MultiMC); + emit log(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); // send the launch script to the launcher part - QByteArray bytes = launchScript.toUtf8(); - writeData(bytes.constData(), bytes.length()); + m_process.write(launchScript.toUtf8()); } -void MinecraftProcess::launch() +void MinecraftLauncher::launch() { QString launchString("launch\n"); - QByteArray bytes = launchString.toUtf8(); - writeData(bytes.constData(), bytes.length()); + m_process.write(launchString.toUtf8()); } -void MinecraftProcess::abort() +void MinecraftLauncher::abort() { QString launchString("abort\n"); - QByteArray bytes = launchString.toUtf8(); - writeData(bytes.constData(), bytes.length()); + m_process.write(launchString.toUtf8()); } diff --git a/logic/minecraft/MinecraftProcess.h b/logic/minecraft/MinecraftLauncher.h index 34c02b77..1c870e84 100644 --- a/logic/minecraft/MinecraftProcess.h +++ b/logic/minecraft/MinecraftLauncher.h @@ -19,20 +19,20 @@ #include <QString> #include "minecraft/MinecraftInstance.h" -#include "BaseProcess.h" +#include "BaseLauncher.h" /** - * The MinecraftProcess class + * The MinecraftLauncher class */ -class MinecraftProcess : public BaseProcess +class MinecraftLauncher : public BaseLauncher { Q_OBJECT protected: - MinecraftProcess(MinecraftInstancePtr inst); + MinecraftLauncher(MinecraftInstancePtr inst); public: - static MinecraftProcess *create(MinecraftInstancePtr inst); + static MinecraftLauncher *create(MinecraftInstancePtr inst); - virtual ~MinecraftProcess(){}; + virtual ~MinecraftLauncher(){}; /** * @brief start the launcher part with the provided launch script @@ -69,8 +69,9 @@ protected: QString launchScript; QString m_nativeFolder; +protected: + bool checkJava(QString path); virtual QMap<QString, QString> getVariables() const override; - QStringList javaArguments() const; virtual QString censorPrivateInfo(QString in) override; virtual MessageLevel::Enum guessLevel(const QString &message, MessageLevel::Enum defaultLevel) override; diff --git a/logic/minecraft/OneSixInstance.cpp b/logic/minecraft/OneSixInstance.cpp index b7937e31..ed757fd5 100644 --- a/logic/minecraft/OneSixInstance.cpp +++ b/logic/minecraft/OneSixInstance.cpp @@ -22,7 +22,7 @@ #include "minecraft/OneSixUpdate.h" #include "minecraft/MinecraftProfile.h" #include "minecraft/VersionBuildError.h" -#include "minecraft/MinecraftProcess.h" +#include "minecraft/MinecraftLauncher.h" #include "minecraft/OneSixProfileStrategy.h" #include "MMCZip.h" @@ -123,7 +123,7 @@ QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session) return parts; } -BaseProcess *OneSixInstance::prepareForLaunch(AuthSessionPtr session) +BaseLauncher *OneSixInstance::prepareForLaunch(AuthSessionPtr session) { QString launchScript; QIcon icon = ENV.icons()->getIcon(iconKey()); @@ -230,7 +230,7 @@ BaseProcess *OneSixInstance::prepareForLaunch(AuthSessionPtr session) } launchScript += "launcher onesix\n"; - auto process = MinecraftProcess::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr())); + auto process = MinecraftLauncher::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr())); process->setLaunchScript(launchScript); process->setWorkdir(minecraftRoot()); process->setLogin(session); diff --git a/logic/minecraft/OneSixInstance.h b/logic/minecraft/OneSixInstance.h index dd5ddf5e..5c71687d 100644 --- a/logic/minecraft/OneSixInstance.h +++ b/logic/minecraft/OneSixInstance.h @@ -49,7 +49,7 @@ public: virtual QString instanceConfigFolder() const override; virtual std::shared_ptr<Task> doUpdate() override; - virtual BaseProcess *prepareForLaunch(AuthSessionPtr account) override; + virtual BaseLauncher *prepareForLaunch(AuthSessionPtr account) override; virtual void cleanupAfterRun() override; diff --git a/logic/tools/BaseExternalTool.cpp b/logic/tools/BaseExternalTool.cpp index d2931db7..af7f9a3f 100644 --- a/logic/tools/BaseExternalTool.cpp +++ b/logic/tools/BaseExternalTool.cpp @@ -19,16 +19,6 @@ BaseExternalTool::~BaseExternalTool() { } -qint64 BaseExternalTool::pid(QProcess *process) -{ -#ifdef Q_OS_WIN - struct _PROCESS_INFORMATION *procinfo = process->pid(); - return procinfo->dwProcessId; -#else - return process->pid(); -#endif -} - BaseDetachedTool::BaseDetachedTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent) : BaseExternalTool(settings, instance, parent) { diff --git a/logic/tools/BaseExternalTool.h b/logic/tools/BaseExternalTool.h index 3c0b23ed..be23036d 100644 --- a/logic/tools/BaseExternalTool.h +++ b/logic/tools/BaseExternalTool.h @@ -17,7 +17,6 @@ public: protected: InstancePtr m_instance; SettingsObjectPtr globalSettings; - qint64 pid(QProcess *process); }; class BaseDetachedTool : public BaseExternalTool diff --git a/logic/tools/BaseProfiler.cpp b/logic/tools/BaseProfiler.cpp index 2c9fed9b..cfac47d4 100644 --- a/logic/tools/BaseProfiler.cpp +++ b/logic/tools/BaseProfiler.cpp @@ -7,7 +7,7 @@ BaseProfiler::BaseProfiler(SettingsObjectPtr settings, InstancePtr instance, QOb { } -void BaseProfiler::beginProfiling(BaseProcess *process) +void BaseProfiler::beginProfiling(BaseLauncher *process) { beginProfilingImpl(process); } diff --git a/logic/tools/BaseProfiler.h b/logic/tools/BaseProfiler.h index 56397489..54ba89ce 100644 --- a/logic/tools/BaseProfiler.h +++ b/logic/tools/BaseProfiler.h @@ -4,7 +4,7 @@ class BaseInstance; class SettingsObject; -class BaseProcess; +class BaseLauncher; class QProcess; class BaseProfiler : public BaseExternalTool @@ -15,13 +15,13 @@ public: public slots: - void beginProfiling(BaseProcess *process); + void beginProfiling(BaseLauncher *process); void abortProfiling(); protected: QProcess *m_profilerProcess; - virtual void beginProfilingImpl(BaseProcess *process) = 0; + virtual void beginProfilingImpl(BaseLauncher *process) = 0; virtual void abortProfilingImpl(); signals: diff --git a/logic/tools/JProfiler.cpp b/logic/tools/JProfiler.cpp index 2522f2b3..73121239 100644 --- a/logic/tools/JProfiler.cpp +++ b/logic/tools/JProfiler.cpp @@ -4,7 +4,7 @@ #include <QMessageBox> #include "settings/SettingsObject.h" -#include "BaseProcess.h" +#include "BaseLauncher.h" #include "BaseInstance.h" class JProfiler : public BaseProfiler @@ -18,7 +18,7 @@ private slots: void profilerFinished(int exit, QProcess::ExitStatus status); protected: - void beginProfilingImpl(BaseProcess *process); + void beginProfilingImpl(BaseLauncher *process); private: int listeningPort = 0; @@ -48,13 +48,13 @@ void JProfiler::profilerFinished(int exit, QProcess::ExitStatus status) } } -void JProfiler::beginProfilingImpl(BaseProcess *process) +void JProfiler::beginProfilingImpl(BaseLauncher *process) { listeningPort = globalSettings->get("JProfilerPort").toInt(); QProcess *profiler = new QProcess(this); QStringList profilerArgs = { - "-d", QString::number(pid(process)), + "-d", QString::number(process->pid()), "--gui", "-p", QString::number(listeningPort) }; diff --git a/logic/tools/JVisualVM.cpp b/logic/tools/JVisualVM.cpp index e148e7dc..fbfd858a 100644 --- a/logic/tools/JVisualVM.cpp +++ b/logic/tools/JVisualVM.cpp @@ -4,7 +4,7 @@ #include <QStandardPaths> #include "settings/SettingsObject.h" -#include "BaseProcess.h" +#include "BaseLauncher.h" #include "BaseInstance.h" class JVisualVM : public BaseProfiler @@ -18,7 +18,7 @@ private slots: void profilerFinished(int exit, QProcess::ExitStatus status); protected: - void beginProfilingImpl(BaseProcess *process); + void beginProfilingImpl(BaseLauncher *process); }; @@ -45,12 +45,12 @@ void JVisualVM::profilerFinished(int exit, QProcess::ExitStatus status) } } -void JVisualVM::beginProfilingImpl(BaseProcess *process) +void JVisualVM::beginProfilingImpl(BaseLauncher *process) { QProcess *profiler = new QProcess(this); QStringList profilerArgs = { - "--openpid", QString::number(pid(process)) + "--openpid", QString::number(process->pid()) }; auto programPath = globalSettings->get("JVisualVMPath").toString(); |