diff --git a/application/ConsoleWindow.cpp b/application/ConsoleWindow.cpp index 35bc9a8a..9dc5874d 100644 --- a/application/ConsoleWindow.cpp +++ b/application/ConsoleWindow.cpp @@ -211,7 +211,7 @@ void ConsoleWindow::on_btnKillMinecraft_clicked() "is frozen for some reason"), QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec(); if (response == QMessageBox::Yes) - m_proc->killProcess(); + m_proc->abort(); else m_killButton->setEnabled(true); } diff --git a/application/LaunchInteraction.cpp b/application/LaunchInteraction.cpp index 8ccc46a4..070c8249 100644 --- a/application/LaunchInteraction.cpp +++ b/application/LaunchInteraction.cpp @@ -13,6 +13,7 @@ #include #include #include +#include LaunchController::LaunchController(QObject *parent) : QObject(parent) { @@ -172,7 +173,7 @@ void LaunchController::launchInstance() connect(m_console, &ConsoleWindow::isClosing, this, &LaunchController::instanceEnded); connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch); - m_launcher->setHeader("MultiMC version: " + BuildConfig.printableVersionString() + "\n\n"); + m_launcher->prependStep(std::make_shared(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC)); m_launcher->start(); } @@ -180,7 +181,7 @@ void LaunchController::readyForLaunch() { if (!m_profiler) { - m_launcher->launch(); + m_launcher->proceed(); return; } @@ -204,7 +205,7 @@ void LaunchController::readyForLaunch() msg.addButton(tr("Launch"), QMessageBox::AcceptRole); msg.setModal(true); msg.exec(); - m_launcher->launch(); + m_launcher->proceed(); }); connect(profilerInstance, &BaseProfiler::abortLaunch, [this](const QString & message) { diff --git a/logic/BaseInstance.cpp b/logic/BaseInstance.cpp index 066d2642..9ed7ce50 100644 --- a/logic/BaseInstance.cpp +++ b/logic/BaseInstance.cpp @@ -52,6 +52,21 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput")); } +QString BaseInstance::getPreLaunchCommand() +{ + return settings()->get("PreLaunchCommand").toString(); +} + +QString BaseInstance::getWrapperCommand() +{ + return settings()->get("WrapperCommand").toString(); +} + +QString BaseInstance::getPostExitCommand() +{ + return settings()->get("PostExitCommand").toString(); +} + void BaseInstance::iconUpdated(QString key) { if(iconKey() == key) diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index 7152ba2d..6201f1ac 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -18,6 +18,7 @@ #include #include #include +#include #include "settings/SettingsObject.h" @@ -89,6 +90,10 @@ public: void setGroupInitial(QString val); void setGroupPost(QString val); + QString getPreLaunchCommand(); + QString getPostExitCommand(); + QString getWrapperCommand(); + virtual QStringList extraArguments() const; virtual QString intendedVersionId() const = 0; @@ -146,6 +151,12 @@ public: */ virtual std::shared_ptr createJarModdingTask() = 0; + + /*! + * Create envrironment variables for running the instance + */ + virtual QProcessEnvironment createEnvironment() = 0; + /*! * does any necessary cleanups after the instance finishes. also runs before\ * TODO: turn into a task that can run asynchronously @@ -157,6 +168,9 @@ public: /// FIXME: this really should be elsewhere... virtual QString instanceConfigFolder() const = 0; + /// get variables this instance exports + virtual QMap getVariables() const = 0; + enum InstanceFlag { VersionBrokenFlag = 0x01, diff --git a/logic/CMakeLists.txt b/logic/CMakeLists.txt index 53715fa6..7afc0213 100644 --- a/logic/CMakeLists.txt +++ b/logic/CMakeLists.txt @@ -93,12 +93,28 @@ set(LOGIC_SOURCES auth/flows/ValidateTask.cpp # Game launch logic - launch/LoggedProcess.h + launch/steps/CheckJava.cpp + launch/steps/CheckJava.h + launch/steps/LaunchCommand.cpp + launch/steps/LaunchCommand.h + launch/steps/ModMinecraftJar.cpp + launch/steps/ModMinecraftJar.h + launch/steps/PostLaunchCommand.cpp + launch/steps/PostLaunchCommand.h + launch/steps/PreLaunchCommand.cpp + launch/steps/PreLaunchCommand.h + launch/steps/TextPrint.cpp + launch/steps/TextPrint.h + launch/steps/Update.cpp + launch/steps/Update.h + launch/LaunchStep.cpp + launch/LaunchStep.h + launch/LaunchTask.cpp + launch/LaunchTask.h launch/LoggedProcess.cpp + launch/LoggedProcess.h launch/MessageLevel.cpp launch/MessageLevel.h - launch/LaunchTask.h - launch/LaunchTask.cpp # Update system updater/GoUpdate.h diff --git a/logic/NullInstance.h b/logic/NullInstance.h index bad4b4de..aba3e484 100644 --- a/logic/NullInstance.h +++ b/logic/NullInstance.h @@ -62,4 +62,12 @@ public: { return nullptr; }; + virtual QProcessEnvironment createEnvironment() + { + return QProcessEnvironment(); + } + virtual QMap getVariables() const + { + return QMap(); + } }; diff --git a/logic/auth/YggdrasilTask.cpp b/logic/auth/YggdrasilTask.cpp index 3b6e0cfb..929ab0bf 100644 --- a/logic/auth/YggdrasilTask.cpp +++ b/logic/auth/YggdrasilTask.cpp @@ -73,12 +73,13 @@ void YggdrasilTask::heartbeat() progress(count, timeout_max); } -void YggdrasilTask::abort() +bool YggdrasilTask::abort() { progress(timeout_max, timeout_max); // TODO: actually use this in a meaningful way m_aborted = YggdrasilTask::BY_USER; m_netReply->abort(); + return true; } void YggdrasilTask::abortByTimeout() diff --git a/logic/auth/YggdrasilTask.h b/logic/auth/YggdrasilTask.h index 306d7358..e81eeb44 100644 --- a/logic/auth/YggdrasilTask.h +++ b/logic/auth/YggdrasilTask.h @@ -131,7 +131,7 @@ slots: void changeState(State newState, QString reason=QString()); public slots: - virtual void abort() override; + virtual bool abort() override; void abortByTimeout(); State state(); protected: diff --git a/logic/forge/ForgeVersionList.cpp b/logic/forge/ForgeVersionList.cpp index 4f9870f7..7c0e851a 100644 --- a/logic/forge/ForgeVersionList.cpp +++ b/logic/forge/ForgeVersionList.cpp @@ -144,9 +144,9 @@ void ForgeListLoadTask::executeTask() listJob->start(); } -void ForgeListLoadTask::abort() +bool ForgeListLoadTask::abort() { - listJob->abort(); + return listJob->abort(); } bool ForgeListLoadTask::parseForgeList(QList &out) diff --git a/logic/forge/ForgeVersionList.h b/logic/forge/ForgeVersionList.h index 2ffc2d68..35af3233 100644 --- a/logic/forge/ForgeVersionList.h +++ b/logic/forge/ForgeVersionList.h @@ -66,7 +66,7 @@ public: explicit ForgeListLoadTask(ForgeVersionList *vlist); virtual void executeTask(); - virtual void abort(); + virtual bool abort(); protected slots: diff --git a/logic/java/JavaCheckerJob.h b/logic/java/JavaCheckerJob.h index 84b6fc6f..4e2038ca 100644 --- a/logic/java/JavaCheckerJob.h +++ b/logic/java/JavaCheckerJob.h @@ -68,9 +68,6 @@ signals: void started(); void finished(QList); -public slots: - virtual void abort() {}; - private slots: void partFinished(JavaCheckResult result); diff --git a/logic/launch/LaunchStep.cpp b/logic/launch/LaunchStep.cpp new file mode 100644 index 00000000..9799d767 --- /dev/null +++ b/logic/launch/LaunchStep.cpp @@ -0,0 +1,26 @@ +/* Copyright 2013-2015 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); +} diff --git a/logic/launch/LaunchStep.h b/logic/launch/LaunchStep.h new file mode 100644 index 00000000..9b9631cb --- /dev/null +++ b/logic/launch/LaunchStep.h @@ -0,0 +1,47 @@ +/* Copyright 2013-2015 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 + +class LaunchTask; +class LaunchStep: public Task +{ + Q_OBJECT +public: /* methods */ + explicit LaunchStep(LaunchTask *parent):Task(nullptr), m_parent(parent) + { + bind(parent); + }; + virtual ~LaunchStep() {}; + +protected: /* methods */ + virtual void bind(LaunchTask *parent); + +signals: + void logLines(QStringList lines, MessageLevel::Enum level); + void logLine(QString line, MessageLevel::Enum level); + void readyForLaunch(); + +public slots: + virtual void proceed() {}; + +protected: /* data */ + LaunchTask *m_parent; +}; \ No newline at end of file diff --git a/logic/launch/LaunchTask.cpp b/logic/launch/LaunchTask.cpp index 83bbba97..248761f0 100644 --- a/logic/launch/LaunchTask.cpp +++ b/logic/launch/LaunchTask.cpp @@ -27,105 +27,14 @@ #include #include #include - -#define IBUS "@im=ibus" - -void LaunchTask::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 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; - } - if(key == "GAME_PRELOAD") - { - m_env.insert("LD_PRELOAD", value); - continue; - } - if(key == "GAME_LIBRARY_PATH") - { - m_env.insert("LD_LIBRARY_PATH", value); - continue; - } -#endif - qDebug() << "Env: " << key << value; - m_env.insert(key, value); - } -#ifdef Q_OS_LINUX - // HACK: Workaround for QTBUG42500 - if(!m_env.contains("LD_LIBRARY_PATH")) - { - 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()); - } -} +#include void LaunchTask::init() { - initializeEnvironment(); - - m_process.setProcessEnvironment(m_env); - connect(&m_process, &LoggedProcess::log, this, &LaunchTask::on_log); - connect(&m_process, &LoggedProcess::stateChanged, this, &LaunchTask::on_state); - - m_prelaunchprocess.setProcessEnvironment(m_env); - connect(&m_prelaunchprocess, &LoggedProcess::log, this, &LaunchTask::on_log); - connect(&m_prelaunchprocess, &LoggedProcess::stateChanged, this, &LaunchTask::on_pre_state); - - m_postlaunchprocess.setProcessEnvironment(m_env); - connect(&m_postlaunchprocess, &LoggedProcess::log, this, &LaunchTask::on_log); - connect(&m_postlaunchprocess, &LoggedProcess::stateChanged, this, &LaunchTask::on_post_state); - m_instance->setRunning(true); } -std::shared_ptr LaunchTask::create(MinecraftInstancePtr inst) +std::shared_ptr LaunchTask::create(InstancePtr inst) { std::shared_ptr proc(new LaunchTask(inst)); proc->init(); @@ -136,25 +45,77 @@ LaunchTask::LaunchTask(InstancePtr instance): m_instance(instance) { } -QString LaunchTask::censorPrivateInfo(QString in) +void LaunchTask::appendStep(std::shared_ptr step) { - if (!m_session) - return in; + m_steps.append(step); +} - if (m_session->session != "-") - in.replace(m_session->session, ""); - in.replace(m_session->access_token, ""); - in.replace(m_session->client_token, ""); - in.replace(m_session->uuid, ""); - in.replace(m_session->player_name, ""); +void LaunchTask::prependStep(std::shared_ptr step) +{ + m_steps.prepend(step); +} - auto i = m_session->u.properties.begin(); - while (i != m_session->u.properties.end()) +void LaunchTask::executeTask() +{ + if(!m_steps.size()) { - in.replace(i.value(), "<" + i.key().toUpper() + ">"); - ++i; + 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->successful()) + { + // end? + if(currentStep == m_steps.size() - 1) + { + emitSucceeded(); + } + else + { + currentStep ++; + step = m_steps[currentStep]; + step->start(); + } + } + else + { + emitFailed(step->failReason()); + } +} + +void LaunchTask::setCensorFilter(QMap 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; } @@ -199,163 +160,58 @@ MessageLevel::Enum LaunchTask::guessLevel(const QString &line, MessageLevel::Enu return level; } -QMap LaunchTask::getVariables() const +void LaunchTask::proceed() { - auto mcInstance = std::dynamic_pointer_cast(m_instance); - QMap out; - out.insert("INST_NAME", mcInstance->name()); - out.insert("INST_ID", mcInstance->id()); - out.insert("INST_DIR", QDir(mcInstance->instanceRoot()).absolutePath()); - out.insert("INST_MC_DIR", QDir(mcInstance->minecraftRoot()).absolutePath()); - out.insert("INST_JAVA", mcInstance->settings()->get("JavaPath").toString()); - out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); - return out; + if(state != LaunchTask::Waiting) + { + return; + } + m_steps[currentStep]->proceed(); } -QStringList LaunchTask::javaArguments() const +bool LaunchTask::abort() { - QStringList args; - - // custom args go first. we want to override them if we have our own here. - args.append(m_instance->extraArguments()); - - // OSX dock icon and name -#ifdef Q_OS_MAC - args << "-Xdock:icon=icon.png"; - args << QString("-Xdock:name=\"%1\"").arg(m_instance->windowTitle()); -#endif - - // HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767 -#ifdef Q_OS_WIN32 - args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_" - "minecraft.exe.heapdump"); -#endif - - args << QString("-Xms%1m").arg(m_instance->settings()->get("MinMemAlloc").toInt()); - args << QString("-Xmx%1m").arg(m_instance->settings()->get("MaxMemAlloc").toInt()); - - // No PermGen in newer java. - auto javaVersion = m_instance->settings()->get("JavaVersion"); - if(Strings::naturalCompare(javaVersion.toString(), "1.8.0", Qt::CaseInsensitive) < 0) + switch(state) { - auto permgen = m_instance->settings()->get("PermGen").toInt(); - if (permgen != 64) + case LaunchTask::Aborted: + case LaunchTask::Failed: + case LaunchTask::Finished: + return true; + case LaunchTask::NotStarted: { - args << QString("-XX:PermSize=%1m").arg(permgen); + state = LaunchTask::Aborted; + emitFailed("Aborted"); + return true; } - } - - args << "-Duser.language=en"; - if (!m_nativeFolder.isEmpty()) - args << QString("-Djava.library.path=%1").arg(m_nativeFolder); - args << "-jar" << PathCombine(QCoreApplication::applicationDirPath(), "jars", "NewLaunch.jar"); - - return args; -} - -void LaunchTask::checkJava() -{ - m_javaPath = m_instance->settings()->get("JavaPath").toString(); - emit log("Java path is:\n" + m_javaPath + "\n\n"); - - auto realJavaPath = QStandardPaths::findExecutable(m_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(m_javaPath), - MessageLevel::Warning); - } - - QFileInfo javaInfo(realJavaPath); - qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch(); - auto storedUnixTime = m_instance->settings()->get("JavaTimestamp").toLongLong(); - this->m_javaUnixTime = javaUnixTime; - // if they are not the same, check! - if(javaUnixTime != storedUnixTime) - { - m_JavaChecker = std::make_shared(); - bool successful = false; - QString errorLog; - QString version; - emit log(tr("Checking Java version..."), MessageLevel::MultiMC); - connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &LaunchTask::checkJavaFinished); - m_JavaChecker->m_path = realJavaPath; - m_JavaChecker->performCheck(); - } - preLaunch(); -} - -void LaunchTask::checkJavaFinished(JavaCheckResult result) -{ - if(!result.valid) - { - // Error message displayed if java can't start - emit log(tr("Could not start java:"), MessageLevel::Error); - auto lines = result.errorLog.split('\n'); - for(auto line: lines) + case LaunchTask::Running: + case LaunchTask::Waiting: { - emit log(line, MessageLevel::Error); + auto step = m_steps[currentStep]; + if(!step->canAbort()) + { + return false; + } + if(step->abort()) + { + state = LaunchTask::Aborted; + return true; + } } - emit log("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC); - emitFailed(tr("Could not start java!")); - } - else - { - emit log(tr("Java version is %1!\n").arg(result.javaVersion), MessageLevel::MultiMC); - m_instance->settings()->set("JavaVersion", result.javaVersion); - m_instance->settings()->set("JavaTimestamp", m_javaUnixTime); - preLaunch(); + default: + break; } + return false; } -void LaunchTask::executeTask() -{ - printHeader(); - emit log("Minecraft folder is:\n" + m_process.workingDirectory() + "\n\n"); - - checkJava(); -} - -void LaunchTask::launch() -{ - QString launchString("launch\n"); - m_process.write(launchString.toUtf8()); -} - -void LaunchTask::abort() -{ - QString launchString("abort\n"); - m_process.write(launchString.toUtf8()); -} - - -void LaunchTask::setWorkdir(QString path) -{ - QDir mcDir(path); - m_process.setWorkingDirectory(mcDir.absolutePath()); - m_prelaunchprocess.setWorkingDirectory(mcDir.absolutePath()); - m_postlaunchprocess.setWorkingDirectory(mcDir.absolutePath()); -} - -void LaunchTask::printHeader() -{ - emit log(m_header); -} - -void LaunchTask::on_log(QStringList lines, MessageLevel::Enum level) -{ - logOutput(lines, level); -} - -void LaunchTask::logOutput(const QStringList &lines, MessageLevel::Enum defaultLevel) +void LaunchTask::onLogLines(const QStringList &lines, MessageLevel::Enum defaultLevel) { for (auto & line: lines) { - logOutput(line, defaultLevel); + onLogLine(line, defaultLevel); } } -void LaunchTask::logOutput(QString line, MessageLevel::Enum level) +void LaunchTask::onLogLine(QString line, MessageLevel::Enum level) { // if the launcher part set a log level, use it auto innerLevel = MessageLevel::fromLine(line); @@ -376,230 +232,6 @@ void LaunchTask::logOutput(QString line, MessageLevel::Enum level) emit log(line, level); } -void LaunchTask::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 LaunchTask::on_pre_state(LoggedProcess::State state) -{ - switch(state) - { - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - case LoggedProcess::FailedToStart: - { - QString error = tr("Pre-Launch command failed with code %1.\n\n").arg(m_prelaunchprocess.exitCode()); - emit log(error, MessageLevel::Fatal); - emitFailed(error); - return; - } - case LoggedProcess::Finished: - { - emit log(tr("Pre-Launch command ran successfully.\n\n")); - } - case LoggedProcess::Skipped: - { - m_instance->reload(); - updateInstance(); - } - default: - break; - } -} - -void LaunchTask::updateInstance() -{ - m_updateTask = m_instance->createUpdateTask(); - if(m_updateTask) - { - connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished())); - m_updateTask->start(); - return; - } - makeReady(); -} - -void LaunchTask::updateFinished() -{ - if(m_updateTask->successful()) - { - doJarModding(); - } - else - { - QString reason = tr("Instance update failed because: %1.\n\n").arg(m_updateTask->failReason()); - emit log(reason, MessageLevel::Fatal); - emitFailed(reason); - } -} - -void LaunchTask::doJarModding() -{ - m_jarModTask = m_instance->createJarModdingTask(); - if(!m_jarModTask) - { - jarModdingSucceeded(); - } - connect(m_jarModTask.get(), SIGNAL(succeeded()), this, SLOT(jarModdingSucceeded())); - connect(m_jarModTask.get(), SIGNAL(failed(QString)), this, SLOT(jarModdingFailed(QString))); - m_jarModTask->start(); -} - -void LaunchTask::jarModdingSucceeded() -{ - makeReady(); -} - -void LaunchTask::jarModdingFailed(QString reason) -{ - emitFailed(reason); -} - -void LaunchTask::makeReady() -{ - QStringList args = javaArguments(); - QString allArgs = args.join(", "); - emit log("Java Arguments:\n[" + censorPrivateInfo(allArgs) + "]\n\n"); - - QString wrapperCommand = m_instance->settings()->get("WrapperCommand").toString(); - if(!wrapperCommand.isEmpty()) - { - auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); - if (realWrapperCommand.isEmpty()) - { - QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand); - emit log(reason, MessageLevel::Fatal); - emitFailed(reason); - return; - } - emit log("Wrapper command is:\n" + wrapperCommand + "\n\n"); - args.prepend(m_javaPath); - m_process.start(wrapperCommand, args); - } - else - { - m_process.start(m_javaPath, args); - } - - // instantiate the launcher part - if (!m_process.waitForStarted()) - { - //: Error message displayed if instace can't start - QString reason = tr("Could not launch minecraft!"); - emit log(reason, MessageLevel::Fatal); - emitFailed(reason); - return; - } - - emit log(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); - - // send the launch script to the launcher part - m_process.write(launchScript.toUtf8()); - - emit readyForLaunch(); -} - -void LaunchTask::on_state(LoggedProcess::State state) -{ - QProcess::ExitStatus estat = QProcess::NormalExit; - switch(state) - { - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - case LoggedProcess::FailedToStart: - { - estat = QProcess::CrashExit; - emitFailed("Game crashed."); - return; - } - case LoggedProcess::Finished: - { - auto exitCode = m_process.exitCode(); - m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); - // run post-exit - postLaunch(); - break; - } - case LoggedProcess::Skipped: - qWarning() << "Illegal game state: Skipped"; - break; - case LoggedProcess::Running: - m_instance->setLastLaunch(); - break; - default: - break; - } -} - -void LaunchTask::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 LaunchTask::postLaunch() -{ - if(killed) - return; - 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); - return; - } - emitSucceeded(); -} - -void LaunchTask::on_post_state(LoggedProcess::State state) -{ - switch(state) - { - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - case LoggedProcess::FailedToStart: - { - QString error = tr("Post-Launch command failed with code %1.\n\n").arg(m_postlaunchprocess.exitCode()); - emit log(error, MessageLevel::Error); - emitFailed(error); - } - case LoggedProcess::Finished: - { - emit log(tr("Post-Launch command ran successfully.\n\n")); - } - case LoggedProcess::Skipped: - { - emitSucceeded(); - } - default: - break; - } -} - void LaunchTask::emitSucceeded() { m_instance->cleanupAfterRun(); @@ -617,7 +249,7 @@ void LaunchTask::emitFailed(QString reason) QString LaunchTask::substituteVariables(const QString &cmd) const { QString out = cmd; - auto variables = getVariables(); + auto variables = m_instance->getVariables(); for (auto it = variables.begin(); it != variables.end(); ++it) { out.replace("$" + it.key(), it.value()); @@ -630,12 +262,3 @@ QString LaunchTask::substituteVariables(const QString &cmd) const return out; } -qint64 LaunchTask::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/launch/LaunchTask.h b/logic/launch/LaunchTask.h index 38142189..75dc1d35 100644 --- a/logic/launch/LaunchTask.h +++ b/logic/launch/LaunchTask.h @@ -20,6 +20,7 @@ #include "BaseInstance.h" #include "MessageLevel.h" #include "LoggedProcess.h" +#include "LaunchStep.h" /* HACK: MINECRAFT: split! */ #include "minecraft/MinecraftInstance.h" #include "java/JavaChecker.h" @@ -39,26 +40,39 @@ protected: explicit LaunchTask(InstancePtr instance); void init(); +public: + enum State + { + NotStarted, + Running, + Waiting, + Failed, + Aborted, + Finished + }; + public: /* methods */ - static std::shared_ptr create(MinecraftInstancePtr inst); + static std::shared_ptr create(InstancePtr inst); virtual ~LaunchTask() {}; + void appendStep(std::shared_ptr step); + void prependStep(std::shared_ptr step); + void setCensorFilter(QMap filter); + InstancePtr instance() { return m_instance; } - /// Set the text printed on top of the log - void setHeader(QString header) + void setPid(qint64 pid) { - m_header = header; + m_pid = pid; } - void setWorkdir(QString path); - - void killProcess(); - - qint64 pid(); + qint64 pid() + { + return m_pid; + } /** * @brief prepare the process for launch (for multi-stage launch) @@ -68,50 +82,24 @@ public: /* methods */ /** * @brief launch the armed instance */ - virtual void launch(); + void proceed(); /** * @brief abort launch */ - virtual void abort(); - -public: /* HACK: MINECRAFT: split! */ - void setLaunchScript(QString script) - { - launchScript = script; - } - - void setNativeFolder(QString natives) - { - m_nativeFolder = natives; - } - - inline void setLogin(AuthSessionPtr session) - { - m_session = session; - } + virtual bool abort() override; -protected: /* methods */ - void preLaunch(); - void updateInstance(); - void doJarModding(); - void makeReady(); - void postLaunch(); - virtual void emitFailed(QString reason); - virtual void emitSucceeded(); + +public: /* HACK: remove this from here! */ + QString substituteVariables(const QString &cmd) const; - void initializeEnvironment(); - - void printHeader(); - - virtual QMap getVariables() const; - virtual QString censorPrivateInfo(QString in); + QString censorPrivateInfo(QString in); virtual MessageLevel::Enum guessLevel(const QString &message, MessageLevel::Enum defaultLevel); -protected slots: - void jarModdingSucceeded(); - void jarModdingFailed(QString reason); +protected: /* methods */ + virtual void emitFailed(QString reason); + virtual void emitSucceeded(); signals: /** @@ -126,53 +114,17 @@ signals: */ void log(QString text, MessageLevel::Enum level = MessageLevel::MultiMC); -protected slots: - 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); +public slots: + void onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC); + void onLogLine(QString line, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC); + void onReadyForLaunch(); + void onStepFinished(); - void on_pre_state(LoggedProcess::State state); - void on_state(LoggedProcess::State state); - void on_post_state(LoggedProcess::State state); - - - -protected: +protected: /* data */ InstancePtr m_instance; - - LoggedProcess m_prelaunchprocess; - LoggedProcess m_postlaunchprocess; - LoggedProcess m_process; - QProcessEnvironment m_env; - BaseProfilerFactory * m_profiler = nullptr; - - bool killed = false; - QString m_header; - -/** - * java check step - */ -protected slots: - void checkJavaFinished(JavaCheckResult result); - -protected: - // for java checker and launch - QString m_javaPath; - qlonglong m_javaUnixTime; - std::shared_ptr m_JavaChecker; - -protected: /* HACK: MINECRAFT: split! */ - AuthSessionPtr m_session; - QString launchScript; - QString m_nativeFolder; - std::shared_ptr m_updateTask; - std::shared_ptr m_jarModTask; - -protected: /* HACK: MINECRAFT: split! */ - void checkJava(); - QStringList javaArguments() const; -private slots: - void updateFinished(); + QList > m_steps; + QMap m_censorFilter; + int currentStep = -1; + State state = NotStarted; + qint64 m_pid = -1; }; - -class BaseProfilerFactory; \ No newline at end of file diff --git a/logic/launch/LoggedProcess.h b/logic/launch/LoggedProcess.h index 253be2c1..edbc78d0 100644 --- a/logic/launch/LoggedProcess.h +++ b/logic/launch/LoggedProcess.h @@ -1,4 +1,20 @@ +/* Copyright 2013-2015 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 #include "MessageLevel.h" @@ -18,8 +34,7 @@ public: Running, Finished, Crashed, - Aborted, - Skipped + Aborted }; public: diff --git a/logic/launch/steps/CheckJava.cpp b/logic/launch/steps/CheckJava.cpp new file mode 100644 index 00000000..bd4c4856 --- /dev/null +++ b/logic/launch/steps/CheckJava.cpp @@ -0,0 +1,71 @@ +/* Copyright 2013-2015 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 +#include + +void CheckJava::executeTask() +{ + auto instance = m_parent->instance(); + auto javaPath = instance->settings()->get("JavaPath").toString(); + emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::MultiMC); + + auto realJavaPath = QStandardPaths::findExecutable(m_javaPath); + if (realJavaPath.isEmpty()) + { + emit logLine(tr("The java binary \"%1\" couldn't be found. You may have to set up java " + "if Minecraft fails to launch.").arg(m_javaPath), + MessageLevel::Warning); + } + + QFileInfo javaInfo(realJavaPath); + qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch(); + auto storedUnixTime = instance->settings()->get("JavaTimestamp").toLongLong(); + m_javaUnixTime = javaUnixTime; + // if they are not the same, check! + if(javaUnixTime != storedUnixTime) + { + m_JavaChecker = std::make_shared(); + bool successful = false; + QString errorLog; + QString version; + emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC); + connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished); + m_JavaChecker->m_path = realJavaPath; + m_JavaChecker->performCheck(); + } + emitSucceeded(); +} + +void CheckJava::checkJavaFinished(JavaCheckResult result) +{ + if(!result.valid) + { + // Error message displayed if java can't start + emit logLine(tr("Could not start java:"), MessageLevel::Error); + emit logLines(result.errorLog.split('\n'), MessageLevel::Error); + emit logLine("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC); + emitFailed(tr("Could not start java!")); + } + else + { + auto instance = m_parent->instance(); + emit logLine(tr("Java version is %1!\n").arg(result.javaVersion), MessageLevel::MultiMC); + instance->settings()->set("JavaVersion", result.javaVersion); + instance->settings()->set("JavaTimestamp", m_javaUnixTime); + emitSucceeded(); + } +} diff --git a/logic/launch/steps/CheckJava.h b/logic/launch/steps/CheckJava.h new file mode 100644 index 00000000..b63dd4f4 --- /dev/null +++ b/logic/launch/steps/CheckJava.h @@ -0,0 +1,41 @@ +/* Copyright 2013-2015 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 +#include +#include + +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: + QString m_javaPath; + qlonglong m_javaUnixTime; + JavaCheckerPtr m_JavaChecker; +}; diff --git a/logic/launch/steps/LaunchCommand.cpp b/logic/launch/steps/LaunchCommand.cpp new file mode 100644 index 00000000..8b133dea --- /dev/null +++ b/logic/launch/steps/LaunchCommand.cpp @@ -0,0 +1,138 @@ +/* Copyright 2013-2015 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 "LaunchCommand.h" +#include +#include +#include + +LaunchCommand::LaunchCommand(LaunchTask *parent) : LaunchStep(parent) +{ + connect(&m_process, &LoggedProcess::log, this, &LaunchCommand::logLines); + connect(&m_process, &LoggedProcess::stateChanged, this, &LaunchCommand::on_state); +} + +void LaunchCommand::executeTask() +{ + auto instance = m_parent->instance(); + std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); + QStringList args = minecraftInstance->javaArguments(); + + QString allArgs = args.join(", "); + emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC); + + auto javaPath = instance->settings()->get("JavaPath").toString(); + + m_process.setProcessEnvironment(instance->createEnvironment()); + + QString wrapperCommand = instance->getWrapperCommand(); + if(!wrapperCommand.isEmpty()) + { + auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); + if (realWrapperCommand.isEmpty()) + { + QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + return; + } + emit logLine("Wrapper command is:\n" + wrapperCommand + "\n\n", MessageLevel::MultiMC); + args.prepend(javaPath); + m_process.start(wrapperCommand, args); + } + else + { + m_process.start(javaPath, args); + } +} + +void LaunchCommand::on_state(LoggedProcess::State state) +{ + switch(state) + { + case LoggedProcess::FailedToStart: + { + //: Error message displayed if instace can't start + QString reason = tr("Could not launch minecraft!"); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + return; + } + case LoggedProcess::Aborted: + case LoggedProcess::Crashed: + + { + m_parent->setPid(-1); + emitFailed("Game crashed."); + return; + } + case LoggedProcess::Finished: + { + m_parent->setPid(-1); + auto exitCode = m_process.exitCode(); + //FIXME: make this work again + // m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); + // run post-exit + emitSucceeded(); + break; + } + case LoggedProcess::Running: + emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); + m_parent->setPid(m_process.pid()); + m_parent->instance()->setLastLaunch(); + // send the launch script to the launcher part + m_process.write(m_launchScript.toUtf8()); + + mayProceed = true; + emit readyForLaunch(); + break; + default: + break; + } +} + +void LaunchCommand::setWorkingDirectory(const QString &wd) +{ + m_process.setWorkingDirectory(wd); +} + +void LaunchCommand::proceed() +{ + if(mayProceed) + { + QString launchString("launch\n"); + m_process.write(launchString.toUtf8()); + mayProceed = false; + } +} + +bool LaunchCommand::abort() +{ + if(mayProceed) + { + mayProceed = false; + QString launchString("abort\n"); + m_process.write(launchString.toUtf8()); + } + else + { + auto state = m_process.state(); + if (state == LoggedProcess::Running || state == LoggedProcess::Starting) + { + m_process.kill(); + } + } + return true; +} diff --git a/logic/launch/steps/LaunchCommand.h b/logic/launch/steps/LaunchCommand.h new file mode 100644 index 00000000..4a631054 --- /dev/null +++ b/logic/launch/steps/LaunchCommand.h @@ -0,0 +1,46 @@ +/* Copyright 2013-2015 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 +#include + +class LaunchCommand: public LaunchStep +{ + Q_OBJECT +public: + explicit LaunchCommand(LaunchTask *parent); + virtual void executeTask(); + virtual bool abort(); + virtual void proceed(); + virtual bool canAbort() const + { + return true; + } + void setWorkingDirectory(const QString &wd); + void setLaunchScript(const QString &ls) + { + m_launchScript = ls; + } +private slots: + void on_state(LoggedProcess::State state); + +private: + LoggedProcess m_process; + QString m_command; + QString m_launchScript; + bool mayProceed = false; +}; diff --git a/logic/launch/steps/ModMinecraftJar.cpp b/logic/launch/steps/ModMinecraftJar.cpp new file mode 100644 index 00000000..fce2d70a --- /dev/null +++ b/logic/launch/steps/ModMinecraftJar.cpp @@ -0,0 +1,44 @@ +/* Copyright 2013-2015 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 "ModMinecraftJar.h" +#include +#include + +void ModMinecraftJar::executeTask() +{ + m_jarModTask = m_parent->instance()->createJarModdingTask(); + if(m_jarModTask) + { + connect(m_jarModTask.get(), SIGNAL(finished()), this, SLOT(jarModdingFinished())); + m_jarModTask->start(); + return; + } + emitSucceeded(); +} + +void ModMinecraftJar::jarModdingFinished() +{ + if(m_jarModTask->successful()) + { + emitSucceeded(); + } + else + { + QString reason = tr("jar modding failed because: %1.\n\n").arg(m_jarModTask->failReason()); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + } +} diff --git a/logic/launch/steps/ModMinecraftJar.h b/logic/launch/steps/ModMinecraftJar.h new file mode 100644 index 00000000..b35dfafa --- /dev/null +++ b/logic/launch/steps/ModMinecraftJar.h @@ -0,0 +1,39 @@ +/* Copyright 2013-2015 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 +#include + +// FIXME: temporary wrapper for existing task. +class ModMinecraftJar: public LaunchStep +{ + Q_OBJECT +public: + explicit ModMinecraftJar(LaunchTask *parent) : LaunchStep(parent) {}; + virtual ~ModMinecraftJar(){}; + + virtual void executeTask(); + virtual bool canAbort() const + { + return false; + } +private slots: + void jarModdingFinished(); + +private: + std::shared_ptr m_jarModTask; +}; diff --git a/logic/launch/steps/PostLaunchCommand.cpp b/logic/launch/steps/PostLaunchCommand.cpp new file mode 100644 index 00000000..c7342188 --- /dev/null +++ b/logic/launch/steps/PostLaunchCommand.cpp @@ -0,0 +1,70 @@ +/* Copyright 2013-2015 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 + +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::MultiMC); + m_process.start(postlaunch_cmd); +} + +void PostLaunchCommand::on_state(LoggedProcess::State state) +{ + switch(state) + { + case LoggedProcess::Aborted: + case LoggedProcess::Crashed: + case LoggedProcess::FailedToStart: + { + QString error = tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); + emit logLine(error, MessageLevel::Error); + emitFailed(error); + } + case LoggedProcess::Finished: + { + emit logLine(tr("Post-Launch command ran successfully.\n\n"), MessageLevel::MultiMC); + 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/logic/launch/steps/PostLaunchCommand.h b/logic/launch/steps/PostLaunchCommand.h new file mode 100644 index 00000000..4d5b0a52 --- /dev/null +++ b/logic/launch/steps/PostLaunchCommand.h @@ -0,0 +1,39 @@ +/* Copyright 2013-2015 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 +#include + +class PostLaunchCommand: public LaunchStep +{ + Q_OBJECT +public: + explicit PostLaunchCommand(LaunchTask *parent); + 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/logic/launch/steps/PreLaunchCommand.cpp b/logic/launch/steps/PreLaunchCommand.cpp new file mode 100644 index 00000000..5d5862d8 --- /dev/null +++ b/logic/launch/steps/PreLaunchCommand.cpp @@ -0,0 +1,72 @@ +/* Copyright 2013-2015 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 + +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::MultiMC); + m_process.start(prelaunch_cmd); +} + +void PreLaunchCommand::on_state(LoggedProcess::State state) +{ + switch(state) + { + case LoggedProcess::Aborted: + case LoggedProcess::Crashed: + case LoggedProcess::FailedToStart: + { + QString error = tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); + emit logLine(error, MessageLevel::Fatal); + emitFailed(error); + return; + } + case LoggedProcess::Finished: + { + emit logLine(tr("Pre-Launch command ran successfully.\n\n"), MessageLevel::MultiMC); + 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/logic/launch/steps/PreLaunchCommand.h b/logic/launch/steps/PreLaunchCommand.h new file mode 100644 index 00000000..077bdfca --- /dev/null +++ b/logic/launch/steps/PreLaunchCommand.h @@ -0,0 +1,39 @@ +/* Copyright 2013-2015 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 +#include + +class PreLaunchCommand: public LaunchStep +{ + Q_OBJECT +public: + explicit PreLaunchCommand(LaunchTask *parent); + 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/logic/launch/steps/TextPrint.cpp b/logic/launch/steps/TextPrint.cpp new file mode 100644 index 00000000..f307b1fd --- /dev/null +++ b/logic/launch/steps/TextPrint.cpp @@ -0,0 +1,29 @@ +#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/logic/launch/steps/TextPrint.h b/logic/launch/steps/TextPrint.h new file mode 100644 index 00000000..3846bf84 --- /dev/null +++ b/logic/launch/steps/TextPrint.h @@ -0,0 +1,37 @@ +/* Copyright 2013-2015 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 +#include +#include + +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/logic/launch/steps/Update.cpp b/logic/launch/steps/Update.cpp new file mode 100644 index 00000000..069b4f41 --- /dev/null +++ b/logic/launch/steps/Update.cpp @@ -0,0 +1,43 @@ +/* Copyright 2013-2015 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 + +void Update::executeTask() +{ + m_updateTask = m_parent->instance()->createUpdateTask(); + if(m_updateTask) + { + connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished())); + m_updateTask->start(); + return; + } + emitSucceeded(); +} + +void Update::updateFinished() +{ + if(m_updateTask->successful()) + { + emitSucceeded(); + } + else + { + QString reason = tr("Instance update failed because: %1.\n\n").arg(m_updateTask->failReason()); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + } +} \ No newline at end of file diff --git a/logic/launch/steps/Update.h b/logic/launch/steps/Update.h new file mode 100644 index 00000000..3ad07264 --- /dev/null +++ b/logic/launch/steps/Update.h @@ -0,0 +1,40 @@ +/* Copyright 2013-2015 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 +#include +#include + +// 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):LaunchStep(parent) {}; + virtual ~Update() {}; + + virtual void executeTask(); + virtual bool canAbort() const + { + return false; + } +private slots: + void updateFinished(); + +private: + std::shared_ptr m_updateTask; +}; diff --git a/logic/minecraft/LegacyInstance.cpp b/logic/minecraft/LegacyInstance.cpp index 0991b7b5..a0af19dc 100644 --- a/logic/minecraft/LegacyInstance.cpp +++ b/logic/minecraft/LegacyInstance.cpp @@ -124,9 +124,9 @@ std::shared_ptr LegacyInstance::createLaunchTask(AuthSessionPtr acco launchScript += "launcher legacy\n"; } auto process = LaunchTask::create(std::dynamic_pointer_cast(getSharedPtr())); - process->setLaunchScript(launchScript); - process->setWorkdir(minecraftRoot()); - process->setLogin(account); + // process->setLaunchScript(launchScript); + // process->setWorkdir(minecraftRoot()); + // process->setLogin(account); return process; } diff --git a/logic/minecraft/MinecraftInstance.cpp b/logic/minecraft/MinecraftInstance.cpp index 71bec71e..a078c8fd 100644 --- a/logic/minecraft/MinecraftInstance.cpp +++ b/logic/minecraft/MinecraftInstance.cpp @@ -4,6 +4,9 @@ #include #include "Env.h" #include "minecraft/MinecraftVersionList.h" +#include + +#define IBUS "@im=ibus" // all of this because keeping things compatible with deprecated old settings // if either of the settings {a, b} is true, this also resolves to true @@ -72,4 +75,133 @@ std::shared_ptr< BaseVersionList > MinecraftInstance::versionList() const return ENV.getVersionList("net.minecraft"); } +QStringList MinecraftInstance::javaArguments() const +{ + QStringList args; + + // custom args go first. we want to override them if we have our own here. + args.append(extraArguments()); + + // OSX dock icon and name +#ifdef Q_OS_MAC + args << "-Xdock:icon=icon.png"; + args << QString("-Xdock:name=\"%1\"").arg(windowTitle()); +#endif + + // HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767 +#ifdef Q_OS_WIN32 + args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_" + "minecraft.exe.heapdump"); +#endif + + args << QString("-Xms%1m").arg(settings()->get("MinMemAlloc").toInt()); + args << QString("-Xmx%1m").arg(settings()->get("MaxMemAlloc").toInt()); + + // No PermGen in newer java. + auto javaVersion = settings()->get("JavaVersion"); + if(Strings::naturalCompare(javaVersion.toString(), "1.8.0", Qt::CaseInsensitive) < 0) + { + auto permgen = settings()->get("PermGen").toInt(); + if (permgen != 64) + { + args << QString("-XX:PermSize=%1m").arg(permgen); + } + } + + args << "-Duser.language=en"; + args << "-jar" << PathCombine(QCoreApplication::applicationDirPath(), "jars", "NewLaunch.jar"); + + return args; +} + +QMap MinecraftInstance::getVariables() const +{ + QMap out; + out.insert("INST_NAME", name()); + out.insert("INST_ID", id()); + out.insert("INST_DIR", QDir(instanceRoot()).absolutePath()); + out.insert("INST_MC_DIR", QDir(minecraftRoot()).absolutePath()); + out.insert("INST_JAVA", settings()->get("JavaPath").toString()); + out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); + return out; +} + +QProcessEnvironment MinecraftInstance::createEnvironment() +{ + // 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" + }; + 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; + } + if(key == "GAME_PRELOAD") + { + env.insert("LD_PRELOAD", value); + continue; + } + if(key == "GAME_LIBRARY_PATH") + { + env.insert("LD_LIBRARY_PATH", value); + continue; + } +#endif + qDebug() << "Env: " << key << value; + env.insert(key, value); + } +#ifdef Q_OS_LINUX + // HACK: Workaround for QTBUG42500 + 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()); + } + return env; +} + #include "MinecraftInstance.moc" diff --git a/logic/minecraft/MinecraftInstance.h b/logic/minecraft/MinecraftInstance.h index d7ae18ea..45589daf 100644 --- a/logic/minecraft/MinecraftInstance.h +++ b/logic/minecraft/MinecraftInstance.h @@ -1,6 +1,7 @@ #pragma once #include "BaseInstance.h" #include "minecraft/Mod.h" +#include class ModList; @@ -27,7 +28,18 @@ public: { return QList(); } + + //FIXME: nuke? virtual std::shared_ptr< BaseVersionList > versionList() const; + + /// get arguments passed to java + QStringList javaArguments() const; + + /// get variables for launch command variable substitution/environment + virtual QMap getVariables() const override; + + /// create an environment for launching processes + virtual QProcessEnvironment createEnvironment() override; }; typedef std::shared_ptr MinecraftInstancePtr; diff --git a/logic/minecraft/OneSixInstance.cpp b/logic/minecraft/OneSixInstance.cpp index d66236f4..5e2f5c64 100644 --- a/logic/minecraft/OneSixInstance.cpp +++ b/logic/minecraft/OneSixInstance.cpp @@ -23,6 +23,12 @@ #include "minecraft/MinecraftProfile.h" #include "minecraft/VersionBuildError.h" #include "launch/LaunchTask.h" +#include +#include +#include +#include +#include +#include #include "minecraft/OneSixProfileStrategy.h" #include "MMCZip.h" @@ -231,9 +237,62 @@ std::shared_ptr OneSixInstance::createLaunchTask(AuthSessionPtr sess launchScript += "launcher onesix\n"; auto process = LaunchTask::create(std::dynamic_pointer_cast(getSharedPtr())); - process->setLaunchScript(launchScript); - process->setWorkdir(minecraftRoot()); - process->setLogin(session); + auto pptr = process.get(); + + // print a header + { + process->appendStep(std::make_shared(pptr, "Minecraft folder is:\n" + minecraftRoot() + "\n\n", MessageLevel::MultiMC)); + } + // run pre-launch command if that's needed + if(getPreLaunchCommand().size()) + { + auto step = std::make_shared(pptr); + step->setWorkingDirectory(minecraftRoot()); + process->appendStep(step); + } + // if we aren't in offline mode,. + if(session->status != AuthSession::PlayableOffline) + { + process->appendStep(std::make_shared(pptr)); + } + // if there are any jar mods + if(getJarMods().size()) + { + auto step = std::make_shared(pptr); + process->appendStep(step); + } + // actually launch the game + { + auto step = std::make_shared(pptr); + step->setWorkingDirectory(minecraftRoot()); + step->setLaunchScript(launchScript); + process->appendStep(step); + } + // run post-exit command if that's needed + if(getPostExitCommand().size()) + { + auto step = std::make_shared(pptr); + step->setWorkingDirectory(minecraftRoot()); + process->appendStep(step); + } + if (session) + { + QMap filter; + if (session->session != "-") + filter[session->session] = tr(""); + filter[session->access_token] = tr(""); + filter[session->client_token] = tr(""); + filter[session->uuid] = tr(""); + filter[session->player_name] = tr(""); + + auto i = session->u.properties.begin(); + while (i != session->u.properties.end()) + { + filter[i.value()] = "<" + i.key().toUpper() + ">"; + ++i; + } + process->setCensorFilter(filter); + } return process; } diff --git a/logic/net/NetJob.h b/logic/net/NetJob.h index c0172bea..4ac6d769 100644 --- a/logic/net/NetJob.h +++ b/logic/net/NetJob.h @@ -89,7 +89,7 @@ private slots: public slots: virtual void executeTask(); // FIXME: implement - virtual void abort() {}; + virtual bool abort() {return false;}; private slots: void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal); diff --git a/logic/tasks/Task.h b/logic/tasks/Task.h index 93ca620d..9362348a 100644 --- a/logic/tasks/Task.h +++ b/logic/tasks/Task.h @@ -52,7 +52,7 @@ signals: public slots: virtual void start(); - virtual void abort() {}; + virtual bool abort() { return false; }; protected: virtual void executeTask() = 0;