diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:51:45 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:51:45 +0300 |
| commit | d3261e64152397db2dca4d691a990c6bc2a6f4dd (patch) | |
| tree | fac2f7be638651181a72453d714f0f96675c2b8b /archived/projt-launcher/launcher/LaunchController.cpp | |
| parent | 31b9a8949ed0a288143e23bf739f2eb64fdc63be (diff) | |
| download | Project-Tick-d3261e64152397db2dca4d691a990c6bc2a6f4dd.tar.gz Project-Tick-d3261e64152397db2dca4d691a990c6bc2a6f4dd.zip | |
NOISSUE add archived projects
Signed-off-by: Mehmet Samet Duman <yongdohyun@projecttick.org>
Diffstat (limited to 'archived/projt-launcher/launcher/LaunchController.cpp')
| -rw-r--r-- | archived/projt-launcher/launcher/LaunchController.cpp | 601 |
1 files changed, 601 insertions, 0 deletions
diff --git a/archived/projt-launcher/launcher/LaunchController.cpp b/archived/projt-launcher/launcher/LaunchController.cpp new file mode 100644 index 0000000000..fa43307ef8 --- /dev/null +++ b/archived/projt-launcher/launcher/LaunchController.cpp @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-3.0-only +// SPDX-FileCopyrightText: 2026 Project Tick +// SPDX-FileContributor: Project Tick Team +/* + * ProjT Launcher - Minecraft Launcher + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/* === Upstream License Block (Do Not Modify) ============================== + * + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ======================================================================== */ + +#include "LaunchController.h" + +#include <QInputDialog> +#include <QLineEdit> +#include <QList> +#include <QPushButton> +#include <QRegularExpression> +#include <QStringList> + +#include "Application.h" +#include "BuildConfig.h" +#include "JavaCommon.h" +#include "launch/LaunchPipeline.hpp" +#include "launch/steps/HostLookupReportStep.hpp" +#include "launch/steps/LogMessageStep.hpp" +#include "minecraft/auth/AccountData.hpp" +#include "minecraft/auth/AccountList.hpp" +#include "tasks/Task.h" +#include "ui/InstanceWindow.h" +#include "ui/MainWindow.h" +#include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/MSALoginDialog.h" +#include "ui/dialogs/ProfileSelectDialog.h" +#include "ui/dialogs/ProfileSetupDialog.h" +#include "ui/dialogs/ProgressDialog.h" + +namespace steps = projt::launch::steps; +using projt::launch::LaunchPipeline; + +LaunchController::LaunchController() = default; + +void LaunchController::executeTask() +{ + if (!m_instance) + { + emitFailed(tr("No instance specified!")); + return; + } + + if (!JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget)) + { + emitFailed(tr("Invalid Java arguments specified. Please fix this first.")); + return; + } + + login(); +} + +void LaunchController::decideAccount() +{ + if (m_accountToUse) + { + return; + } + + auto accounts = APPLICATION->accounts(); + auto instanceAccountId = m_instance->settings()->get("InstanceAccountId").toString(); + auto instanceAccountIndex = accounts->findAccountByProfileId(instanceAccountId); + if (instanceAccountIndex == -1 || instanceAccountId.isEmpty()) + { + m_accountToUse = accounts->defaultAccount(); + } + else + { + m_accountToUse = accounts->at(instanceAccountIndex); + } + + if (!accounts->anyAccountIsValid()) + { + auto reply = CustomMessageBox::selectable(m_parentWidget, + tr("No Accounts"), + tr("In order to play Minecraft, you must have at least one Microsoft " + "account which owns Minecraft logged in. " + "Would you like to open the account manager to add an account now?"), + QMessageBox::Information, + QMessageBox::Yes | QMessageBox::No) + ->exec(); + + if (reply == QMessageBox::Yes) + { + APPLICATION->ShowGlobalSettings(m_parentWidget, "accounts"); + } + else if (reply == QMessageBox::No) + { + return; + } + } + + if (!m_accountToUse) + { + ProfileSelectDialog selectDialog(tr("Which account would you like to use?"), + ProfileSelectDialog::GlobalDefaultCheckbox, + m_parentWidget); + + selectDialog.exec(); + m_accountToUse = selectDialog.selectedAccount(); + + if (selectDialog.useAsGlobalDefault() && m_accountToUse) + { + accounts->setDefaultAccount(m_accountToUse); + } + } +} + +LaunchDecision LaunchController::decideLaunchMode() +{ + if (!m_accountToUse || m_wantedLaunchMode == LaunchMode::Demo) + { + m_actualLaunchMode = LaunchMode::Demo; + return LaunchDecision::Continue; + } + + if (m_wantedLaunchMode == LaunchMode::Normal + && (m_accountToUse->shouldRefresh() || m_accountToUse->accountState() == AccountState::Offline)) + { + m_accountToUse->refresh(); + } + + const auto accounts = APPLICATION->accounts(); + MinecraftAccountPtr accountToCheck = nullptr; + + if (m_accountToUse->accountType() != AccountType::Offline) + { + accountToCheck = m_accountToUse->ownsMinecraft() ? m_accountToUse : nullptr; + } + else if (const auto defaultAccount = accounts->defaultAccount(); + defaultAccount && defaultAccount->ownsMinecraft()) + { + accountToCheck = defaultAccount; + } + else + { + for (int i = 0; i < accounts->count(); i++) + { + if (const auto account = accounts->at(i); account->ownsMinecraft()) + { + accountToCheck = account; + break; + } + } + } + + if (!accountToCheck) + { + m_actualLaunchMode = LaunchMode::Demo; + return LaunchDecision::Continue; + } + + auto state = accountToCheck->accountState(); + if (state == AccountState::Unchecked || state == AccountState::Errored) + { + accountToCheck->refresh(); + state = AccountState::Working; + } + + if (state == AccountState::Working) + { + ProgressDialog progDialog(m_parentWidget); + progDialog.setSkipButton(true, tr("Abort")); + + auto task = accountToCheck->currentTask(); + progDialog.execWithTask(*task); + + if (task->getState() == Task::State::AbortedByUser) + { + return LaunchDecision::Abort; + } + + state = accountToCheck->accountState(); + } + + QString reauthReason; + switch (state) + { + case AccountState::Errored: + case AccountState::Expired: + reauthReason = tr("'%1' has expired and needs to be reauthenticated").arg(accountToCheck->profileName()); + break; + case AccountState::Disabled: + reauthReason = tr("The launcher's client identification has changed"); + break; + case AccountState::Gone: + reauthReason = tr("'%1' no longer exists on the servers").arg(accountToCheck->profileName()); + break; + default: + m_actualLaunchMode = + state == AccountState::Online && m_wantedLaunchMode == LaunchMode::Normal ? LaunchMode::Normal + : LaunchMode::Offline; + return LaunchDecision::Continue; + } + + if (reauthenticateAccount(accountToCheck, reauthReason)) + { + return LaunchDecision::Undecided; + } + + return LaunchDecision::Abort; +} + +bool LaunchController::askPlayDemo() +{ + QMessageBox box(m_parentWidget); + box.setWindowTitle(tr("Play demo?")); + + QString text = m_accountToUse + ? tr("This account does not own Minecraft.\nYou need to purchase the game first to play the full version.") + : tr("No account was selected for launch."); + text += tr("\n\nDo you want to play the demo?"); + + box.setText(text); + box.setIcon(QMessageBox::Warning); + auto demoButton = box.addButton(tr("Play Demo"), QMessageBox::ButtonRole::YesRole); + auto cancelButton = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::NoRole); + box.setDefaultButton(cancelButton); + + box.exec(); + return box.clickedButton() == demoButton; +} + +QString LaunchController::askOfflineName(QString playerName, bool* ok) +{ + if (ok) + { + *ok = false; + } + + QString message; + switch (m_actualLaunchMode) + { + case LaunchMode::Normal: + Q_ASSERT(false); + return {}; + case LaunchMode::Demo: + message = tr("Choose your demo mode player name."); + break; + case LaunchMode::Offline: + if (m_wantedLaunchMode == LaunchMode::Normal) + { + message = tr("You are not connected to the Internet, launching in offline mode.\n\n"); + } + message += tr("Choose your offline mode player name."); + break; + } + + QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString(); + QString usedName = lastOfflinePlayerName.isEmpty() ? playerName : lastOfflinePlayerName; + bool accepted = false; + QString name = QInputDialog::getText(m_parentWidget, tr("Player name"), message, QLineEdit::Normal, usedName, &accepted); + if (!accepted) + { + return {}; + } + if (!name.isEmpty()) + { + usedName = name; + APPLICATION->settings()->set("LastOfflinePlayerName", usedName); + } + if (ok) + { + *ok = true; + } + return usedName; +} + +void LaunchController::login() +{ + decideAccount(); + + LaunchDecision decision = decideLaunchMode(); + while (decision == LaunchDecision::Undecided) + { + decision = decideLaunchMode(); + } + if (decision == LaunchDecision::Abort) + { + emitAborted(); + return; + } + + if (m_actualLaunchMode == LaunchMode::Demo) + { + if (m_wantedLaunchMode == LaunchMode::Demo || askPlayDemo()) + { + bool ok = false; + auto name = askOfflineName("Player", &ok); + if (ok) + { + m_session = std::make_shared<AuthSession>(); + static const QRegularExpression s_removeChars("[{}-]"); + m_session->MakeDemo(name, MinecraftAccount::uuidFromUsername(name).toString().remove(s_removeChars)); + launchInstance(); + return; + } + } + + emitFailed(tr("No account selected for launch.")); + return; + } + + m_session = std::make_shared<AuthSession>(); + m_session->launchMode = m_actualLaunchMode; + m_accountToUse->fillSession(m_session); + + if (m_accountToUse->accountType() != AccountType::Offline) + { + if (m_actualLaunchMode == LaunchMode::Normal && !m_accountToUse->hasProfile()) + { + ProfileSetupDialog dialog(m_accountToUse, m_parentWidget); + if (dialog.exec() != QDialog::Accepted) + { + emitAborted(); + return; + } + } + + if (m_actualLaunchMode == LaunchMode::Offline) + { + bool ok = false; + QString name = m_offlineName; + if (name.isEmpty()) + { + name = askOfflineName(m_session->player_name, &ok); + if (!ok) + { + emitAborted(); + return; + } + } + m_session->MakeOffline(name); + } + } + + launchInstance(); +} + +bool LaunchController::reauthenticateAccount(MinecraftAccountPtr account, QString reason) +{ + if (reason.isEmpty()) + { + reason = tr("'%1' has expired and needs to be reauthenticated").arg(account->profileName()); + } + + auto button = QMessageBox::warning( + m_parentWidget, + tr("Account refresh failed"), + tr("%1. Do you want to reauthenticate this account?").arg(reason), + QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, + QMessageBox::StandardButton::Yes); + if (button == QMessageBox::StandardButton::Yes) + { + auto accounts = APPLICATION->accounts(); + bool isDefault = accounts->defaultAccount() == account; + accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(account->profileId()))); + if (account->accountType() == AccountType::MSA) + { + auto newAccount = MSALoginDialog::newAccount(m_parentWidget); + + if (newAccount != nullptr) + { + accounts->addAccount(newAccount); + + if (isDefault) + { + accounts->setDefaultAccount(newAccount); + } + + if (m_accountToUse == account) + { + m_accountToUse = nullptr; + decideAccount(); + } + return true; + } + } + } + + emitFailed(reason); + return false; +} + +void LaunchController::launchInstance() +{ + Q_ASSERT_X(m_instance != nullptr, "launchInstance", "instance is NULL"); + Q_ASSERT_X(m_session.get() != nullptr, "launchInstance", "session is NULL"); + + if (!m_instance->reloadSettings()) + { + QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't load the instance profile.")); + emitFailed(tr("Couldn't load the instance profile.")); + return; + } + + m_launcher = m_instance->createLaunchPipeline(m_session, m_targetToJoin); + if (!m_launcher) + { + emitFailed(tr("Couldn't instantiate a launcher.")); + return; + } + + auto console = qobject_cast<InstanceWindow*>(m_parentWidget); + auto showConsole = m_instance->settings()->get("ShowConsole").toBool(); + if (!console && showConsole) + { + APPLICATION->showInstanceWindow(m_instance); + } + connect(m_launcher.get(), &LaunchPipeline::readyForLaunch, this, &LaunchController::readyForLaunch); + connect(m_launcher.get(), &LaunchPipeline::succeeded, this, &LaunchController::onSucceeded); + connect(m_launcher.get(), &LaunchPipeline::failed, this, &LaunchController::onFailed); + connect(m_launcher.get(), &LaunchPipeline::requestProgress, this, &LaunchController::onProgressRequested); + + QString onlineMode; + if (m_actualLaunchMode == LaunchMode::Normal) + { + onlineMode = "online"; + + QStringList servers = { "login.microsoftonline.com", + "session.minecraft.net", + "textures.minecraft.net", + "api.mojang.com" }; + m_launcher->prependStage(makeShared<steps::HostLookupReportStep>(m_launcher.get(), servers)); + } + else + { + onlineMode = m_actualLaunchMode == LaunchMode::Demo ? "demo" : "offline"; + } + + m_launcher->prependStage(makeShared<steps::LogMessageStep>(m_launcher.get(), + "Launched instance in " + onlineMode + " mode\n", + MessageLevel::Launcher)); + + auto versionString = QString("%1 version: %2 (%3)") + .arg(BuildConfig.LAUNCHER_DISPLAYNAME, + BuildConfig.printableVersionString(), + BuildConfig.BUILD_PLATFORM); + m_launcher->prependStage( + makeShared<steps::LogMessageStep>(m_launcher.get(), versionString + "\n\n", MessageLevel::Launcher)); + m_launcher->start(); +} + +void LaunchController::readyForLaunch() +{ + if (!m_profiler) + { + m_launcher->proceed(); + return; + } + + QString error; + if (!m_profiler->check(&error)) + { + m_launcher->abort(); + emitFailed("Profiler startup failed!"); + QMessageBox::critical(m_parentWidget, + tr("Error!"), + tr("Profiler check for %1 failed: %2").arg(m_profiler->name(), error)); + return; + } + BaseProfiler* profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this); + + connect(profilerInstance, + &BaseProfiler::readyToLaunch, + [this](const QString& message) + { + QMessageBox msg(m_parentWidget); + msg.setText(tr("The game launch is delayed until you press the " + "button. This is the right time to setup the profiler, as the " + "profiler server is running now.\n\n%1") + .arg(message)); + msg.setWindowTitle(tr("Waiting.")); + msg.setIcon(QMessageBox::Information); + msg.addButton(tr("&Launch"), QMessageBox::AcceptRole); + msg.exec(); + m_launcher->proceed(); + }); + connect(profilerInstance, + &BaseProfiler::abortLaunch, + [this](const QString& message) + { + QMessageBox msg; + msg.setText(tr("Couldn't start the profiler: %1").arg(message)); + msg.setWindowTitle(tr("Error")); + msg.setIcon(QMessageBox::Critical); + msg.addButton(QMessageBox::Ok); + msg.setModal(true); + msg.exec(); + m_launcher->abort(); + emitFailed("Profiler startup failed!"); + }); + profilerInstance->beginProfiling(m_launcher); +} + +void LaunchController::onSucceeded() +{ + emitSucceeded(); +} + +void LaunchController::onFailed(QString reason) +{ + if (m_instance->settings()->get("ShowConsoleOnError").toBool()) + { + APPLICATION->showInstanceWindow(m_instance, "console"); + } + emitFailed(reason); +} + +void LaunchController::onProgressRequested(Task* task) +{ + if (!task) + { + return; + } + + ProgressDialog progDialog(m_parentWidget); + progDialog.setSkipButton(true, tr("Abort")); + m_launcher->proceed(); + progDialog.execWithTask(*task); +} + +bool LaunchController::abort() +{ + if (!m_launcher) + { + return true; + } + if (!m_launcher->canAbort()) + { + return false; + } + auto response = CustomMessageBox::selectable( + m_parentWidget, + tr("Kill Minecraft?"), + tr("This can cause the instance to get corrupted and should only be used if Minecraft " + "is frozen for some reason"), + QMessageBox::Question, + QMessageBox::Yes | QMessageBox::No, + QMessageBox::Yes) + ->exec(); + if (response == QMessageBox::Yes) + { + return m_launcher->abort(); + } + return false; +} |
