// SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * * 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, see . * * 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 "MSAStep.h" #include #include #include #include #include "Application.h" #include "BuildConfig.h" #include #include #include #include bool isSchemeHandlerRegistered() { QProcess process; process.start("xdg-mime", { "query", "default", "x-scheme-handler/" + BuildConfig.LAUNCHER_APP_BINARY_NAME }); process.waitForFinished(); QString output = process.readAllStandardOutput().trimmed(); return output.contains(BuildConfig.LAUNCHER_APP_BINARY_NAME); } bool registerSchemeHandler() { #ifdef Q_OS_LINUX // Paths for user-specific installations QString desktopFilePath = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QString("/%1.desktop").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); QString mimeFilePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QString("/mime/packages/x-scheme-handler-%1.xml").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); QFile desktopFile(desktopFilePath); QFile mimeFile(mimeFilePath); if ((desktopFile.exists() && mimeFile.exists()) || isSchemeHandlerRegistered()) { return true; } // Create and write the .desktop file if (desktopFile.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(&desktopFile); out << QString(R"XXX([Desktop Entry] Version=1.0 Name=%1 Comment=Discover, manage, and play Minecraft instances Type=Application Terminal=false Exec=%2 %U StartupNotify=true Icon=org.%2.%3 Categories=Game;ActionGame;AdventureGame;Simulation; Keywords=game;minecraft;mc; StartupWMClass=%3 MimeType=application/zip;application/x-modrinth-modpack+zip;x-scheme-handler/curseforge;x-scheme-handler/%2; )XXX") .arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.LAUNCHER_APP_BINARY_NAME, BuildConfig.LAUNCHER_NAME); desktopFile.close(); } else { qDebug() << "Failed to write .desktop file:" << desktopFilePath; return false; } // Create and write the MIME type XML file if (mimeFile.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(&mimeFile); out << "\n"; out << "\n"; out << QString(" \n").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); out << QString(" \n").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); out << " \n"; out << "\n"; mimeFile.close(); } else { qDebug() << "Failed to write MIME type XML file:" << mimeFilePath; return false; } // Update the MIME database QProcess::execute("update-mime-database", { QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) }); // Update the desktop database QProcess::execute("update-desktop-database", { QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) }); qDebug() << "Custom URL scheme handler registered successfully."; #elif defined(Q_OS_WIN) QString regPath = QString("HKEY_CURRENT_USER\\Software\\Classes\\%1").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); QSettings settings(regPath, QSettings::NativeFormat); if (settings.contains("shell/open/command/.")) { return true; } QString appPath = QCoreApplication::applicationFilePath().replace("/", "\\"); settings.setValue(".", QString("URL:%1 Protocol").arg(BuildConfig.LAUNCHER_NAME)); settings.setValue("URL Protocol", ""); settings.setValue("DefaultIcon/.", QString("\"%1\",1").arg(appPath)); settings.setValue("shell/open/command/.", QString("\"%1\" \"%2\"").arg(appPath).arg("%1")); qDebug() << "Custom URL scheme handler registered successfully in Windows Registry."; #endif return true; } class CustomOAuthOobReplyHandler : public QOAuthOobReplyHandler { Q_OBJECT public: explicit CustomOAuthOobReplyHandler(QObject* parent = nullptr) : QOAuthOobReplyHandler(parent) { connect(APPLICATION, &Application::oauthReplyRecieved, this, &QOAuthOobReplyHandler::callbackReceived); } ~CustomOAuthOobReplyHandler() override { disconnect(APPLICATION, &Application::oauthReplyRecieved, this, &QOAuthOobReplyHandler::callbackReceived); } QString callback() const override { return BuildConfig.LAUNCHER_APP_BINARY_NAME + "://oauth/microsoft"; } }; MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent) { m_clientId = APPLICATION->getMSAClientID(); if (!registerSchemeHandler()) { auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this); replyHandler->setCallbackText(R"XXX( Login Successful, redirecting... )XXX"); oauth2.setReplyHandler(replyHandler); } else { oauth2.setReplyHandler(new CustomOAuthOobReplyHandler(this)); } oauth2.setAuthorizationUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize")); oauth2.setAccessTokenUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/token")); oauth2.setScope("XboxLive.SignIn XboxLive.offline_access"); oauth2.setClientIdentifier(m_clientId); oauth2.setNetworkAccessManager(APPLICATION->network().get()); connect(&oauth2, &QOAuth2AuthorizationCodeFlow::granted, this, [this] { m_data->msaClientID = oauth2.clientIdentifier(); m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc(); m_data->msaToken.notAfter = oauth2.expirationAt(); m_data->msaToken.extra = oauth2.extraTokens(); m_data->msaToken.refresh_token = oauth2.refreshToken(); m_data->msaToken.token = oauth2.token(); emit finished(AccountTaskState::STATE_WORKING, tr("Got ")); }); connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &MSAStep::authorizeWithBrowser); connect(&oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this](const QAbstractOAuth2::Error err) { emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication failed.")); }); connect(&oauth2, &QOAuth2AuthorizationCodeFlow::extraTokensChanged, this, [this](const QVariantMap& tokens) { m_data->msaToken.extra = tokens; }); connect(&oauth2, &QOAuth2AuthorizationCodeFlow::clientIdentifierChanged, this, [this](const QString& clientIdentifier) { m_data->msaClientID = clientIdentifier; }); } QString MSAStep::describe() { return tr("Logging in with Microsoft account."); } void MSAStep::perform() { if (m_silent) { if (m_data->msaClientID != m_clientId) { emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - client identification has changed.")); } oauth2.setRefreshToken(m_data->msaToken.refresh_token); oauth2.refreshAccessToken(); } else { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0 oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMultiMap* map) { #else oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMap* map) { #endif map->insert("prompt", "select_account"); }); *m_data = AccountData(); m_data->msaClientID = m_clientId; oauth2.grant(); } } #include "MSAStep.moc"