Clean up some lingering differences from upstream
This commit is contained in:
parent
b496316241
commit
da9c085a06
2
.github/workflows/update-flake.yml
vendored
2
.github/workflows/update-flake.yml
vendored
@ -12,7 +12,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-flake:
|
update-flake:
|
||||||
if: github.repository == 'unmojang/Fjord Launcher'
|
if: github.repository == 'unmojang/FjordLauncher'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,7 +8,6 @@ resources/CMakeFiles
|
|||||||
html/
|
html/
|
||||||
|
|
||||||
# Project Files
|
# Project Files
|
||||||
*.orig
|
|
||||||
*.pro.user
|
*.pro.user
|
||||||
CMakeLists.txt.user
|
CMakeLists.txt.user
|
||||||
CMakeLists.txt.user.*
|
CMakeLists.txt.user.*
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
/*
|
/*
|
||||||
* Prism Launcher - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
* Prism Launcher - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
|
||||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -92,8 +92,9 @@ void LaunchController::decideAccount()
|
|||||||
if (accounts->count() <= 0 || !accounts->anyAccountIsValid()) {
|
if (accounts->count() <= 0 || !accounts->anyAccountIsValid()) {
|
||||||
// Tell the user they need to log in at least one account in order to play.
|
// Tell the user they need to log in at least one account in order to play.
|
||||||
auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"),
|
auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"),
|
||||||
tr("In order to play Minecraft, you must pick your username or log in to an account "
|
tr("In order to play Minecraft, you must have at least one Microsoft "
|
||||||
"Would you like to open the account manager to do this now?"),
|
"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)
|
QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)
|
||||||
->exec();
|
->exec();
|
||||||
|
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QtNetwork>
|
|
||||||
#include "JavaChecker.h"
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
|
|
||||||
class JavaCheckerJob;
|
|
||||||
using JavaCheckerJobPtr = shared_qobject_ptr<JavaCheckerJob>;
|
|
||||||
|
|
||||||
// FIXME: this just seems horribly redundant
|
|
||||||
class JavaCheckerJob : public Task {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {};
|
|
||||||
virtual ~JavaCheckerJob() {};
|
|
||||||
|
|
||||||
bool addJavaCheckerAction(JavaCheckerPtr base)
|
|
||||||
{
|
|
||||||
javacheckers.append(base);
|
|
||||||
// if this is already running, the action needs to be started right away!
|
|
||||||
if (isRunning()) {
|
|
||||||
setProgress(num_finished, javacheckers.size());
|
|
||||||
connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
|
|
||||||
base->performCheck();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
QList<JavaCheckResult> getResults() { return javaresults; }
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void partFinished(JavaCheckResult result);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void executeTask() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString m_job_name;
|
|
||||||
QList<JavaCheckerPtr> javacheckers;
|
|
||||||
QList<JavaCheckResult> javaresults;
|
|
||||||
int num_finished = 0;
|
|
||||||
};
|
|
@ -1,57 +0,0 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QList>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QUrl>
|
|
||||||
|
|
||||||
#include <quazip/quazip.h>
|
|
||||||
#include "minecraft/VersionFilterData.h"
|
|
||||||
#include "net/NetJob.h"
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
|
|
||||||
class MinecraftVersion;
|
|
||||||
class MinecraftInstance;
|
|
||||||
|
|
||||||
// FIXME: This looks very similar to a SequentialTask. Maybe we can reduce code duplications? :^)
|
|
||||||
|
|
||||||
class MinecraftUpdate : public Task {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit MinecraftUpdate(MinecraftInstance* inst, QObject* parent = 0);
|
|
||||||
virtual ~MinecraftUpdate() {};
|
|
||||||
|
|
||||||
void executeTask() override;
|
|
||||||
bool canAbort() const override;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
bool abort() override;
|
|
||||||
void subtaskSucceeded();
|
|
||||||
void subtaskFailed(QString error);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void next();
|
|
||||||
|
|
||||||
private:
|
|
||||||
MinecraftInstance* m_inst = nullptr;
|
|
||||||
QList<Task::Ptr> m_tasks;
|
|
||||||
QString m_preFailure;
|
|
||||||
int m_currentTask = -1;
|
|
||||||
bool m_abort = false;
|
|
||||||
bool m_failed_out_of_order = false;
|
|
||||||
QString m_fail_reason;
|
|
||||||
};
|
|
@ -1,23 +0,0 @@
|
|||||||
#include "AuthlibInjector.h"
|
|
||||||
|
|
||||||
#include "minecraft/auth/steps/AuthlibInjectorMetadataStep.h"
|
|
||||||
#include "minecraft/auth/steps/GetSkinStep.h"
|
|
||||||
#include "minecraft/auth/steps/MinecraftProfileStepMojang.h"
|
|
||||||
#include "minecraft/auth/steps/YggdrasilStep.h"
|
|
||||||
|
|
||||||
AuthlibInjectorRefresh::AuthlibInjectorRefresh(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
|
||||||
{
|
|
||||||
m_steps.append(makeShared<YggdrasilStep>(m_data, QString()));
|
|
||||||
m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
|
|
||||||
m_steps.append(makeShared<AuthlibInjectorMetadataStep>(m_data));
|
|
||||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthlibInjectorLogin::AuthlibInjectorLogin(AccountData* data, QString password, QObject* parent)
|
|
||||||
: AuthFlow(data, parent), m_password(password)
|
|
||||||
{
|
|
||||||
m_steps.append(makeShared<YggdrasilStep>(m_data, m_password));
|
|
||||||
m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
|
|
||||||
m_steps.append(makeShared<AuthlibInjectorMetadataStep>(m_data));
|
|
||||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "AuthFlow.h"
|
|
||||||
|
|
||||||
class AuthlibInjectorRefresh : public AuthFlow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit AuthlibInjectorRefresh(AccountData* data, QObject* parent = 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
class AuthlibInjectorLogin : public AuthFlow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit AuthlibInjectorLogin(AccountData* data, QString password, QObject* parent = 0);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString m_password;
|
|
||||||
};
|
|
@ -1,22 +0,0 @@
|
|||||||
#include "Mojang.h"
|
|
||||||
|
|
||||||
#include "minecraft/auth/steps/GetSkinStep.h"
|
|
||||||
#include "minecraft/auth/steps/MigrationEligibilityStep.h"
|
|
||||||
#include "minecraft/auth/steps/MinecraftProfileStepMojang.h"
|
|
||||||
#include "minecraft/auth/steps/YggdrasilStep.h"
|
|
||||||
|
|
||||||
MojangRefresh::MojangRefresh(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
|
||||||
{
|
|
||||||
m_steps.append(makeShared<YggdrasilStep>(m_data, QString()));
|
|
||||||
m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
|
|
||||||
m_steps.append(makeShared<MigrationEligibilityStep>(m_data));
|
|
||||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
|
||||||
}
|
|
||||||
|
|
||||||
MojangLogin::MojangLogin(AccountData* data, QString password, QObject* parent) : AuthFlow(data, parent), m_password(password)
|
|
||||||
{
|
|
||||||
m_steps.append(makeShared<YggdrasilStep>(m_data, m_password));
|
|
||||||
m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
|
|
||||||
m_steps.append(makeShared<MigrationEligibilityStep>(m_data));
|
|
||||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "AuthFlow.h"
|
|
||||||
|
|
||||||
class MojangRefresh : public AuthFlow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit MojangRefresh(AccountData* data, QObject* parent = 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
class MojangLogin : public AuthFlow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit MojangLogin(AccountData* data, QString password, QObject* parent = 0);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString m_password;
|
|
||||||
};
|
|
@ -1,123 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
||||||
*
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* This file incorporates work covered by the following copyright and
|
|
||||||
* permission notice:
|
|
||||||
*
|
|
||||||
* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "CapeChange.h"
|
|
||||||
|
|
||||||
#include <QHttpMultiPart>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
|
|
||||||
CapeChange::CapeChange(QObject* parent, MinecraftAccountPtr account, QString cape) : Task(parent), m_capeId(cape), m_account(account) {}
|
|
||||||
|
|
||||||
void CapeChange::setCape([[maybe_unused]] QString& cape)
|
|
||||||
{
|
|
||||||
QNetworkRequest request(QUrl(m_account->servicesServerUrl() + "/minecraft/profile/capes/active"));
|
|
||||||
QString token = m_account->accessToken();
|
|
||||||
auto requestString = QString("{\"capeId\":\"%1\"}").arg(m_capeId);
|
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toLocal8Bit());
|
|
||||||
QNetworkReply* rep = APPLICATION->network()->put(request, requestString.toUtf8());
|
|
||||||
|
|
||||||
setStatus(tr("Equipping cape"));
|
|
||||||
|
|
||||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
|
||||||
connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress);
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
|
||||||
connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError);
|
|
||||||
#else
|
|
||||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &CapeChange::downloadError);
|
|
||||||
#endif
|
|
||||||
connect(rep, &QNetworkReply::sslErrors, this, &CapeChange::sslErrors);
|
|
||||||
connect(rep, &QNetworkReply::finished, this, &CapeChange::downloadFinished);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CapeChange::clearCape()
|
|
||||||
{
|
|
||||||
QNetworkRequest request(QUrl(m_account->servicesServerUrl() + "/minecraft/profile/capes/active"));
|
|
||||||
QString token = m_account->accessToken();
|
|
||||||
auto requestString = QString("{\"capeId\":\"%1\"}").arg(m_capeId);
|
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toLocal8Bit());
|
|
||||||
QNetworkReply* rep = APPLICATION->network()->deleteResource(request);
|
|
||||||
|
|
||||||
setStatus(tr("Removing cape"));
|
|
||||||
|
|
||||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
|
||||||
connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress);
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
|
||||||
connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError);
|
|
||||||
#else
|
|
||||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &CapeChange::downloadError);
|
|
||||||
#endif
|
|
||||||
connect(rep, &QNetworkReply::sslErrors, this, &CapeChange::sslErrors);
|
|
||||||
connect(rep, &QNetworkReply::finished, this, &CapeChange::downloadFinished);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CapeChange::executeTask()
|
|
||||||
{
|
|
||||||
if (m_capeId.isEmpty()) {
|
|
||||||
clearCape();
|
|
||||||
} else {
|
|
||||||
setCape(m_capeId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CapeChange::downloadError(QNetworkReply::NetworkError error)
|
|
||||||
{
|
|
||||||
// error happened during download.
|
|
||||||
qCritical() << "Network error: " << error;
|
|
||||||
emitFailed(m_reply->errorString());
|
|
||||||
}
|
|
||||||
|
|
||||||
void CapeChange::sslErrors(const QList<QSslError>& errors)
|
|
||||||
{
|
|
||||||
int i = 1;
|
|
||||||
for (auto error : errors) {
|
|
||||||
qCritical() << "Cape change SSL Error #" << i << " : " << error.errorString();
|
|
||||||
auto cert = error.certificate();
|
|
||||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CapeChange::downloadFinished()
|
|
||||||
{
|
|
||||||
// if the download failed
|
|
||||||
if (m_reply->error() != QNetworkReply::NetworkError::NoError) {
|
|
||||||
emitFailed(QString("Network error: %1").arg(m_reply->errorString()));
|
|
||||||
m_reply.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
emitSucceeded();
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <minecraft/auth/MinecraftAccount.h>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QtNetwork/QtNetwork>
|
|
||||||
#include <memory>
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
|
|
||||||
class CapeChange : public Task {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
CapeChange(QObject* parent, MinecraftAccountPtr m_account, QString capeId);
|
|
||||||
virtual ~CapeChange() {}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void setCape(QString& cape);
|
|
||||||
void clearCape();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString m_capeId;
|
|
||||||
MinecraftAccountPtr m_account;
|
|
||||||
shared_qobject_ptr<QNetworkReply> m_reply;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void executeTask();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void downloadError(QNetworkReply::NetworkError);
|
|
||||||
void sslErrors(const QList<QSslError>& errors);
|
|
||||||
void downloadFinished();
|
|
||||||
};
|
|
@ -1,91 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
||||||
*
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* This file incorporates work covered by the following copyright and
|
|
||||||
* permission notice:
|
|
||||||
*
|
|
||||||
* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "SkinDelete.h"
|
|
||||||
|
|
||||||
#include <QHttpMultiPart>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
|
|
||||||
SkinDelete::SkinDelete(QObject* parent, MinecraftAccountPtr account) : Task(parent), m_account(account) {}
|
|
||||||
|
|
||||||
void SkinDelete::executeTask()
|
|
||||||
{
|
|
||||||
QNetworkRequest request(QUrl(m_account->servicesServerUrl() + "/minecraft/profile/skins/active"));
|
|
||||||
QString token = m_account->accessToken();
|
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toLocal8Bit());
|
|
||||||
QNetworkReply* rep = APPLICATION->network()->deleteResource(request);
|
|
||||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
|
||||||
|
|
||||||
setStatus(tr("Deleting skin"));
|
|
||||||
connect(rep, &QNetworkReply::uploadProgress, this, &SkinDelete::setProgress);
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
|
||||||
connect(rep, &QNetworkReply::errorOccurred, this, &SkinDelete::downloadError);
|
|
||||||
#else
|
|
||||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &SkinDelete::downloadError);
|
|
||||||
#endif
|
|
||||||
connect(rep, &QNetworkReply::sslErrors, this, &SkinDelete::sslErrors);
|
|
||||||
connect(rep, &QNetworkReply::finished, this, &SkinDelete::downloadFinished);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkinDelete::downloadError(QNetworkReply::NetworkError error)
|
|
||||||
{
|
|
||||||
// error happened during download.
|
|
||||||
qCritical() << "Network error: " << error;
|
|
||||||
emitFailed(m_reply->errorString());
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkinDelete::sslErrors(const QList<QSslError>& errors)
|
|
||||||
{
|
|
||||||
int i = 1;
|
|
||||||
for (auto error : errors) {
|
|
||||||
qCritical() << "Skin Delete SSL Error #" << i << " : " << error.errorString();
|
|
||||||
auto cert = error.certificate();
|
|
||||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkinDelete::downloadFinished()
|
|
||||||
{
|
|
||||||
// if the download failed
|
|
||||||
if (m_reply->error() != QNetworkReply::NetworkError::NoError) {
|
|
||||||
emitFailed(QString("Network error: %1").arg(m_reply->errorString()));
|
|
||||||
m_reply.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
emitSucceeded();
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <minecraft/auth/MinecraftAccount.h>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QtNetwork/QtNetwork>
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
|
|
||||||
using SkinDeletePtr = shared_qobject_ptr<class SkinDelete>;
|
|
||||||
|
|
||||||
class SkinDelete : public Task {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
SkinDelete(QObject* parent, MinecraftAccountPtr acct);
|
|
||||||
virtual ~SkinDelete() = default;
|
|
||||||
|
|
||||||
private:
|
|
||||||
MinecraftAccountPtr m_account;
|
|
||||||
shared_qobject_ptr<QNetworkReply> m_reply;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void executeTask();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void downloadError(QNetworkReply::NetworkError);
|
|
||||||
void sslErrors(const QList<QSslError>& errors);
|
|
||||||
void downloadFinished();
|
|
||||||
};
|
|
@ -1,119 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
||||||
*
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* This file incorporates work covered by the following copyright and
|
|
||||||
* permission notice:
|
|
||||||
*
|
|
||||||
* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "SkinUpload.h"
|
|
||||||
|
|
||||||
#include <QHttpMultiPart>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
|
|
||||||
QByteArray getVariant(SkinUpload::Model model)
|
|
||||||
{
|
|
||||||
switch (model) {
|
|
||||||
default:
|
|
||||||
qDebug() << "Unknown skin type!";
|
|
||||||
case SkinUpload::STEVE:
|
|
||||||
return "CLASSIC";
|
|
||||||
case SkinUpload::ALEX:
|
|
||||||
return "SLIM";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SkinUpload::SkinUpload(QObject* parent, MinecraftAccountPtr account, QByteArray skin, SkinUpload::Model model)
|
|
||||||
: Task(parent), m_model(model), m_skin(skin), m_account(account)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void SkinUpload::executeTask()
|
|
||||||
{
|
|
||||||
QNetworkRequest request(QUrl(m_account->servicesServerUrl() + "/minecraft/profile/skins"));
|
|
||||||
QString token = m_account->accessToken();
|
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toLocal8Bit());
|
|
||||||
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
|
||||||
|
|
||||||
QHttpPart skin;
|
|
||||||
skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png"));
|
|
||||||
skin.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"skin.png\""));
|
|
||||||
skin.setBody(m_skin);
|
|
||||||
|
|
||||||
QHttpPart model;
|
|
||||||
model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"variant\""));
|
|
||||||
model.setBody(getVariant(m_model));
|
|
||||||
|
|
||||||
multiPart->append(skin);
|
|
||||||
multiPart->append(model);
|
|
||||||
|
|
||||||
QNetworkReply* rep = APPLICATION->network()->post(request, multiPart);
|
|
||||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
|
||||||
|
|
||||||
setStatus(tr("Uploading skin"));
|
|
||||||
connect(rep, &QNetworkReply::uploadProgress, this, &SkinUpload::setProgress);
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
|
||||||
connect(rep, &QNetworkReply::errorOccurred, this, &SkinUpload::downloadError);
|
|
||||||
#else
|
|
||||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &SkinUpload::downloadError);
|
|
||||||
#endif
|
|
||||||
connect(rep, &QNetworkReply::sslErrors, this, &SkinUpload::sslErrors);
|
|
||||||
connect(rep, &QNetworkReply::finished, this, &SkinUpload::downloadFinished);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkinUpload::downloadError(QNetworkReply::NetworkError error)
|
|
||||||
{
|
|
||||||
// error happened during download.
|
|
||||||
qCritical() << "Network error: " << error;
|
|
||||||
emitFailed(m_reply->errorString());
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkinUpload::sslErrors(const QList<QSslError>& errors)
|
|
||||||
{
|
|
||||||
int i = 1;
|
|
||||||
for (auto error : errors) {
|
|
||||||
qCritical() << "Skin Upload SSL Error #" << i << " : " << error.errorString();
|
|
||||||
auto cert = error.certificate();
|
|
||||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkinUpload::downloadFinished()
|
|
||||||
{
|
|
||||||
// if the download failed
|
|
||||||
if (m_reply->error() != QNetworkReply::NetworkError::NoError) {
|
|
||||||
emitFailed(QString("Network error: %1").arg(m_reply->errorString()));
|
|
||||||
m_reply.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
emitSucceeded();
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <minecraft/auth/MinecraftAccount.h>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QtNetwork/QtNetwork>
|
|
||||||
#include <memory>
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
|
|
||||||
using SkinUploadPtr = shared_qobject_ptr<class SkinUpload>;
|
|
||||||
|
|
||||||
class SkinUpload : public Task {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum Model { STEVE, ALEX };
|
|
||||||
|
|
||||||
// Note this class takes ownership of the file.
|
|
||||||
SkinUpload(QObject* parent, MinecraftAccountPtr account, QByteArray skin, Model model = STEVE);
|
|
||||||
virtual ~SkinUpload() {}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Model m_model;
|
|
||||||
QByteArray m_skin;
|
|
||||||
MinecraftAccountPtr m_account;
|
|
||||||
shared_qobject_ptr<QNetworkReply> m_reply;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void executeTask();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
|
|
||||||
void downloadError(QNetworkReply::NetworkError);
|
|
||||||
void sslErrors(const QList<QSslError>& errors);
|
|
||||||
|
|
||||||
void downloadFinished();
|
|
||||||
};
|
|
@ -57,10 +57,11 @@ QNetworkReply* CapeChange::getReply(QNetworkRequest& request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CapeChange::Ptr CapeChange::make(QString token, QString capeId)
|
CapeChange::Ptr CapeChange::make(MinecraftAccountPtr account, QString capeId)
|
||||||
{
|
{
|
||||||
auto up = makeShared<CapeChange>(capeId);
|
auto up = makeShared<CapeChange>(capeId);
|
||||||
up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active");
|
QString token = account->accessToken();
|
||||||
|
up->m_url = QUrl(account->servicesServerUrl() + "/minecraft/profile/capes/active");
|
||||||
up->setObjectName(QString("BYTES:") + up->m_url.toString());
|
up->setObjectName(QString("BYTES:") + up->m_url.toString());
|
||||||
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
|
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
|
||||||
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
|
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <minecraft/auth/MinecraftAccount.h>
|
||||||
#include "net/NetRequest.h"
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
class CapeChange : public Net::NetRequest {
|
class CapeChange : public Net::NetRequest {
|
||||||
@ -27,7 +28,7 @@ class CapeChange : public Net::NetRequest {
|
|||||||
CapeChange(QString capeId);
|
CapeChange(QString capeId);
|
||||||
virtual ~CapeChange() = default;
|
virtual ~CapeChange() = default;
|
||||||
|
|
||||||
static CapeChange::Ptr make(QString token, QString capeId);
|
static CapeChange::Ptr make(MinecraftAccountPtr account, QString capeId);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||||
|
@ -50,10 +50,11 @@ QNetworkReply* SkinDelete::getReply(QNetworkRequest& request)
|
|||||||
return m_network->deleteResource(request);
|
return m_network->deleteResource(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
SkinDelete::Ptr SkinDelete::make(QString token)
|
SkinDelete::Ptr SkinDelete::make(MinecraftAccountPtr account)
|
||||||
{
|
{
|
||||||
auto up = makeShared<SkinDelete>();
|
auto up = makeShared<SkinDelete>();
|
||||||
up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active");
|
QString token = account->accessToken();
|
||||||
|
up->m_url = QUrl(account->servicesServerUrl() + "/minecraft/profile/skins/active");
|
||||||
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
|
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
|
||||||
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
|
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
|
||||||
{ "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() },
|
{ "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() },
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <minecraft/auth/MinecraftAccount.h>
|
||||||
#include "net/NetRequest.h"
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
class SkinDelete : public Net::NetRequest {
|
class SkinDelete : public Net::NetRequest {
|
||||||
@ -27,7 +28,7 @@ class SkinDelete : public Net::NetRequest {
|
|||||||
SkinDelete();
|
SkinDelete();
|
||||||
virtual ~SkinDelete() = default;
|
virtual ~SkinDelete() = default;
|
||||||
|
|
||||||
static SkinDelete::Ptr make(QString token);
|
static SkinDelete::Ptr make(MinecraftAccountPtr account);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||||
|
@ -67,10 +67,11 @@ QNetworkReply* SkinUpload::getReply(QNetworkRequest& request)
|
|||||||
return m_network->post(request, multiPart);
|
return m_network->post(request, multiPart);
|
||||||
}
|
}
|
||||||
|
|
||||||
SkinUpload::Ptr SkinUpload::make(QString token, QString path, QString variant)
|
SkinUpload::Ptr SkinUpload::make(MinecraftAccountPtr account, QString path, QString variant)
|
||||||
{
|
{
|
||||||
auto up = makeShared<SkinUpload>(path, variant);
|
auto up = makeShared<SkinUpload>(path, variant);
|
||||||
up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins");
|
QString token = account->accessToken();
|
||||||
|
up->m_url = QUrl(account->servicesServerUrl() + "/minecraft/profile/skins");
|
||||||
up->setObjectName(QString("BYTES:") + up->m_url.toString());
|
up->setObjectName(QString("BYTES:") + up->m_url.toString());
|
||||||
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
|
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
|
||||||
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
|
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <minecraft/auth/MinecraftAccount.h>
|
||||||
#include "net/NetRequest.h"
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
class SkinUpload : public Net::NetRequest {
|
class SkinUpload : public Net::NetRequest {
|
||||||
@ -29,7 +30,7 @@ class SkinUpload : public Net::NetRequest {
|
|||||||
SkinUpload(QString path, QString variant);
|
SkinUpload(QString path, QString variant);
|
||||||
virtual ~SkinUpload() = default;
|
virtual ~SkinUpload() = default;
|
||||||
|
|
||||||
static SkinUpload::Ptr make(QString token, QString path, QString variant);
|
static SkinUpload::Ptr make(MinecraftAccountPtr account, QString path, QString variant);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
qt.*.debug=false
|
qt.*.debug=false
|
||||||
# don't log credentials by default
|
# don't log credentials by default
|
||||||
launcher.auth.credentials.debug=false
|
launcher.auth.credentials.debug=false
|
||||||
katabasis.*.debug=false
|
|
||||||
# remove the debug lines, other log levels still get through
|
# remove the debug lines, other log levels still get through
|
||||||
launcher.task.net.download.debug=false
|
launcher.task.net.download.debug=false
|
||||||
# enable or disable whole catageries
|
# enable or disable whole catageries
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (C) 2023 Evan Goode <mail@evangoo.de>
|
|
||||||
*
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QtCore/QEventLoop>
|
|
||||||
#include <QtWidgets/QDialog>
|
|
||||||
|
|
||||||
#include "minecraft/auth/MinecraftAccount.h"
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
class LoginDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoginDialog : public QDialog {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
~LoginDialog();
|
|
||||||
|
|
||||||
static MinecraftAccountPtr newAccount(QWidget* parent, QString message);
|
|
||||||
|
|
||||||
private:
|
|
||||||
explicit LoginDialog(QWidget* parent = 0);
|
|
||||||
|
|
||||||
void setUserInputsEnabled(bool enable);
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
void accept();
|
|
||||||
|
|
||||||
void onTaskFailed(const QString& reason);
|
|
||||||
void onTaskSucceeded();
|
|
||||||
void onTaskStatus(const QString& status);
|
|
||||||
void onTaskProgress(qint64 current, qint64 total);
|
|
||||||
|
|
||||||
void on_userTextBox_textEdited(const QString& newText);
|
|
||||||
void on_passTextBox_textEdited(const QString& newText);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ui::LoginDialog* ui;
|
|
||||||
MinecraftAccountPtr m_account;
|
|
||||||
Task::Ptr m_loginTask;
|
|
||||||
};
|
|
@ -1,77 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>LoginDialog</class>
|
|
||||||
<widget class="QDialog" name="LoginDialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>421</width>
|
|
||||||
<height>198</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Add Account</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="text">
|
|
||||||
<string notr="true">Message label placeholder.</string>
|
|
||||||
</property>
|
|
||||||
<property name="textFormat">
|
|
||||||
<enum>Qt::RichText</enum>
|
|
||||||
</property>
|
|
||||||
<property name="textInteractionFlags">
|
|
||||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="userTextBox">
|
|
||||||
<property name="placeholderText">
|
|
||||||
<string>Email</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="passTextBox">
|
|
||||||
<property name="echoMode">
|
|
||||||
<enum>QLineEdit::Password</enum>
|
|
||||||
</property>
|
|
||||||
<property name="placeholderText">
|
|
||||||
<string>Password</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QProgressBar" name="progressBar">
|
|
||||||
<property name="value">
|
|
||||||
<number>24</number>
|
|
||||||
</property>
|
|
||||||
<property name="textVisible">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="standardButtons">
|
|
||||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections/>
|
|
||||||
</ui>
|
|
@ -1,165 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
||||||
*
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* This file incorporates work covered by the following copyright and
|
|
||||||
* permission notice:
|
|
||||||
*
|
|
||||||
* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <QFileDialog>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QPainter>
|
|
||||||
|
|
||||||
#include <FileSystem.h>
|
|
||||||
|
|
||||||
#include <minecraft/services/CapeChange.h>
|
|
||||||
#include <minecraft/services/SkinUpload.h>
|
|
||||||
#include <tasks/SequentialTask.h>
|
|
||||||
|
|
||||||
#include "CustomMessageBox.h"
|
|
||||||
#include "ProgressDialog.h"
|
|
||||||
#include "SkinUploadDialog.h"
|
|
||||||
#include "ui_SkinUploadDialog.h"
|
|
||||||
|
|
||||||
void SkinUploadDialog::on_buttonBox_rejected()
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkinUploadDialog::on_buttonBox_accepted()
|
|
||||||
{
|
|
||||||
QString fileName;
|
|
||||||
QString input = ui->skinPathTextBox->text();
|
|
||||||
ProgressDialog prog(this);
|
|
||||||
SequentialTask skinUpload;
|
|
||||||
|
|
||||||
if (!input.isEmpty()) {
|
|
||||||
QRegularExpression urlPrefixMatcher(QRegularExpression::anchoredPattern("^([a-z]+)://.+$"));
|
|
||||||
bool isLocalFile = false;
|
|
||||||
// it has an URL prefix -> it is an URL
|
|
||||||
if (urlPrefixMatcher.match(input).hasMatch()) {
|
|
||||||
QUrl fileURL = input;
|
|
||||||
if (fileURL.isValid()) {
|
|
||||||
// local?
|
|
||||||
if (fileURL.isLocalFile()) {
|
|
||||||
isLocalFile = true;
|
|
||||||
fileName = fileURL.toLocalFile();
|
|
||||||
} else {
|
|
||||||
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Using remote URLs for setting skins is not implemented yet."),
|
|
||||||
QMessageBox::Warning)
|
|
||||||
->exec();
|
|
||||||
close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("You cannot use an invalid URL for uploading skins."),
|
|
||||||
QMessageBox::Warning)
|
|
||||||
->exec();
|
|
||||||
close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// just assume it's a path then
|
|
||||||
isLocalFile = true;
|
|
||||||
fileName = ui->skinPathTextBox->text();
|
|
||||||
}
|
|
||||||
if (isLocalFile && !QFile::exists(fileName)) {
|
|
||||||
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec();
|
|
||||||
close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SkinUpload::Model model = SkinUpload::STEVE;
|
|
||||||
if (ui->steveBtn->isChecked()) {
|
|
||||||
model = SkinUpload::STEVE;
|
|
||||||
} else if (ui->alexBtn->isChecked()) {
|
|
||||||
model = SkinUpload::ALEX;
|
|
||||||
}
|
|
||||||
skinUpload.addTask(shared_qobject_ptr<SkinUpload>(new SkinUpload(this, m_account, FS::read(fileName), model)));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto selectedCape = ui->capeCombo->currentData().toString();
|
|
||||||
if (selectedCape != m_account->accountData()->minecraftProfile.currentCape) {
|
|
||||||
skinUpload.addTask(shared_qobject_ptr<CapeChange>(new CapeChange(this, m_account, selectedCape)));
|
|
||||||
}
|
|
||||||
if (prog.execWithTask(&skinUpload) != QDialog::Accepted) {
|
|
||||||
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec();
|
|
||||||
close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Success"), QMessageBox::Information)->exec();
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkinUploadDialog::on_skinBrowseBtn_clicked()
|
|
||||||
{
|
|
||||||
auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString();
|
|
||||||
QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter);
|
|
||||||
if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QString cooked_path = FS::NormalizePath(raw_path);
|
|
||||||
ui->skinPathTextBox->setText(cooked_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
SkinUploadDialog::SkinUploadDialog(MinecraftAccountPtr account, QWidget* parent)
|
|
||||||
: QDialog(parent), m_account(account), ui(new Ui::SkinUploadDialog)
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
|
||||||
|
|
||||||
// FIXME: add a model for this, download/refresh the capes on demand
|
|
||||||
auto& accountData = *account->accountData();
|
|
||||||
int index = 0;
|
|
||||||
ui->capeCombo->addItem(tr("No Cape"), QVariant());
|
|
||||||
auto currentCape = accountData.minecraftProfile.currentCape;
|
|
||||||
if (currentCape.isEmpty()) {
|
|
||||||
ui->capeCombo->setCurrentIndex(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& cape : accountData.minecraftProfile.capes) {
|
|
||||||
index++;
|
|
||||||
if (cape.data.size()) {
|
|
||||||
QPixmap capeImage;
|
|
||||||
if (capeImage.loadFromData(cape.data, "PNG")) {
|
|
||||||
QPixmap preview = QPixmap(10, 16);
|
|
||||||
QPainter painter(&preview);
|
|
||||||
painter.drawPixmap(0, 0, capeImage.copy(1, 1, 10, 16));
|
|
||||||
ui->capeCombo->addItem(capeImage, cape.alias, cape.id);
|
|
||||||
if (currentCape == cape.id) {
|
|
||||||
ui->capeCombo->setCurrentIndex(index);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ui->capeCombo->addItem(cape.alias, cape.id);
|
|
||||||
if (currentCape == cape.id) {
|
|
||||||
ui->capeCombo->setCurrentIndex(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <minecraft/auth/MinecraftAccount.h>
|
|
||||||
#include <QDialog>
|
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
class SkinUploadDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SkinUploadDialog : public QDialog {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit SkinUploadDialog(MinecraftAccountPtr acct, QWidget* parent = 0);
|
|
||||||
virtual ~SkinUploadDialog() {};
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void on_buttonBox_accepted();
|
|
||||||
|
|
||||||
void on_buttonBox_rejected();
|
|
||||||
|
|
||||||
void on_skinBrowseBtn_clicked();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
MinecraftAccountPtr m_account;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ui::SkinUploadDialog* ui;
|
|
||||||
};
|
|
@ -246,11 +246,11 @@ void SkinManageDialog::accept()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
skinUpload->addNetAction(SkinUpload::make(m_acct->accessToken(), skin->getPath(), skin->getModelString()));
|
skinUpload->addNetAction(SkinUpload::make(m_acct, skin->getPath(), skin->getModelString()));
|
||||||
|
|
||||||
auto selectedCape = skin->getCapeId();
|
auto selectedCape = skin->getCapeId();
|
||||||
if (selectedCape != m_acct->accountData()->minecraftProfile.currentCape) {
|
if (selectedCape != m_acct->accountData()->minecraftProfile.currentCape) {
|
||||||
skinUpload->addNetAction(CapeChange::make(m_acct->accessToken(), selectedCape));
|
skinUpload->addNetAction(CapeChange::make(m_acct, selectedCape));
|
||||||
}
|
}
|
||||||
|
|
||||||
skinUpload->addTask(m_acct->refresh().staticCast<Task>());
|
skinUpload->addTask(m_acct->refresh().staticCast<Task>());
|
||||||
@ -267,7 +267,7 @@ void SkinManageDialog::on_resetBtn_clicked()
|
|||||||
{
|
{
|
||||||
ProgressDialog prog(this);
|
ProgressDialog prog(this);
|
||||||
NetJob::Ptr skinReset{ new NetJob(tr("Reset skin"), APPLICATION->network(), 1) };
|
NetJob::Ptr skinReset{ new NetJob(tr("Reset skin"), APPLICATION->network(), 1) };
|
||||||
skinReset->addNetAction(SkinDelete::make(m_acct->accessToken()));
|
skinReset->addNetAction(SkinDelete::make(m_acct));
|
||||||
skinReset->addTask(m_acct->refresh().staticCast<Task>());
|
skinReset->addTask(m_acct->refresh().staticCast<Task>());
|
||||||
if (prog.execWithTask(skinReset.get()) != QDialog::Accepted) {
|
if (prog.execWithTask(skinReset.get()) != QDialog::Accepted) {
|
||||||
CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to delete current skin!"), QMessageBox::Warning)->exec();
|
CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to delete current skin!"), QMessageBox::Warning)->exec();
|
||||||
|
@ -48,7 +48,6 @@
|
|||||||
|
|
||||||
#include "ui/dialogs/AuthlibInjectorLoginDialog.h"
|
#include "ui/dialogs/AuthlibInjectorLoginDialog.h"
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
#include "ui/dialogs/LoginDialog.h"
|
|
||||||
#include "ui/dialogs/MSALoginDialog.h"
|
#include "ui/dialogs/MSALoginDialog.h"
|
||||||
#include "ui/dialogs/OfflineLoginDialog.h"
|
#include "ui/dialogs/OfflineLoginDialog.h"
|
||||||
|
|
||||||
@ -59,7 +58,7 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new
|
|||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
ui->listView->setEmptyString(
|
ui->listView->setEmptyString(
|
||||||
tr("Welcome!\n"
|
tr("Welcome!\n"
|
||||||
"If you're new here, you can select the \"Add Offline\" button to play without an account."));
|
"If you're new here, you can select the \"Add Microsoft\" button to link your Microsoft account."));
|
||||||
ui->listView->setEmptyMode(VersionListView::String);
|
ui->listView->setEmptyMode(VersionListView::String);
|
||||||
ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
|
ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
|
||||||
@ -137,7 +136,7 @@ void AccountListPage::on_actionAddAuthlibInjector_triggered()
|
|||||||
{
|
{
|
||||||
if (!m_accounts->anyAccountIsValid()) {
|
if (!m_accounts->anyAccountIsValid()) {
|
||||||
QMessageBox::warning(this, tr("Error"),
|
QMessageBox::warning(this, tr("Error"),
|
||||||
tr("You must add a Microsoft or Mojang account that owns Minecraft before you can add an account on a custom "
|
tr("You must add a Microsoft account that owns Minecraft before you can add an account on a custom "
|
||||||
"authentication server."
|
"authentication server."
|
||||||
"<br><br>"
|
"<br><br>"
|
||||||
"If you have lost your account you can contact Microsoft for support."));
|
"If you have lost your account you can contact Microsoft for support."));
|
||||||
|
@ -1,471 +0,0 @@
|
|||||||
#include <QCryptographicHash>
|
|
||||||
#include <QDataStream>
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QList>
|
|
||||||
#include <QMap>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
#include <QPair>
|
|
||||||
#include <QTcpServer>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QUuid>
|
|
||||||
#include <QVariantMap>
|
|
||||||
|
|
||||||
#include <QUrlQuery>
|
|
||||||
|
|
||||||
#include "katabasis/DeviceFlow.h"
|
|
||||||
#include "katabasis/Globals.h"
|
|
||||||
#include "katabasis/PollServer.h"
|
|
||||||
|
|
||||||
#include "JsonResponse.h"
|
|
||||||
#include "KatabasisLogging.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// ref: https://tools.ietf.org/html/rfc8628#section-3.2
|
|
||||||
// Exception: Google sign-in uses "verification_url" instead of "*_uri" - we'll accept both.
|
|
||||||
bool hasMandatoryDeviceAuthParams(const QVariantMap& params)
|
|
||||||
{
|
|
||||||
if (!params.contains(Katabasis::OAUTH2_DEVICE_CODE))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!params.contains(Katabasis::OAUTH2_USER_CODE))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!(params.contains(Katabasis::OAUTH2_VERIFICATION_URI) || params.contains(Katabasis::OAUTH2_VERIFICATION_URL)))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!params.contains(Katabasis::OAUTH2_EXPIRES_IN))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray createQueryParameters(const QList<Katabasis::RequestParameter>& parameters)
|
|
||||||
{
|
|
||||||
QByteArray ret;
|
|
||||||
bool first = true;
|
|
||||||
for (auto& h : parameters) {
|
|
||||||
if (first) {
|
|
||||||
first = false;
|
|
||||||
} else {
|
|
||||||
ret.append("&");
|
|
||||||
}
|
|
||||||
ret.append(QUrl::toPercentEncoding(h.name) + "=" + QUrl::toPercentEncoding(h.value));
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace Katabasis {
|
|
||||||
|
|
||||||
DeviceFlow::DeviceFlow(Options& opts, Token& token, QObject* parent, QNetworkAccessManager* manager) : QObject(parent), token_(token)
|
|
||||||
{
|
|
||||||
manager_ = manager ? manager : new QNetworkAccessManager(this);
|
|
||||||
qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
|
|
||||||
options_ = opts;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceFlow::linked()
|
|
||||||
{
|
|
||||||
return token_.validity != Validity::None;
|
|
||||||
}
|
|
||||||
void DeviceFlow::setLinked(bool v)
|
|
||||||
{
|
|
||||||
qDebug() << "DeviceFlow::setLinked:" << (v ? "true" : "false");
|
|
||||||
token_.validity = v ? Validity::Certain : Validity::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::updateActivity(Activity activity)
|
|
||||||
{
|
|
||||||
if (activity_ == activity) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
activity_ = activity;
|
|
||||||
switch (activity) {
|
|
||||||
case Katabasis::Activity::Idle:
|
|
||||||
case Katabasis::Activity::LoggingIn:
|
|
||||||
case Katabasis::Activity::LoggingOut:
|
|
||||||
case Katabasis::Activity::Refreshing:
|
|
||||||
// non-terminal states...
|
|
||||||
break;
|
|
||||||
case Katabasis::Activity::FailedSoft:
|
|
||||||
// terminal state, tokens did not change
|
|
||||||
break;
|
|
||||||
case Katabasis::Activity::FailedHard:
|
|
||||||
case Katabasis::Activity::FailedGone:
|
|
||||||
// terminal state, tokens are invalid
|
|
||||||
token_ = Token();
|
|
||||||
break;
|
|
||||||
case Katabasis::Activity::Succeeded:
|
|
||||||
setLinked(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
emit activityChanged(activity_);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString DeviceFlow::token()
|
|
||||||
{
|
|
||||||
return token_.token;
|
|
||||||
}
|
|
||||||
void DeviceFlow::setToken(const QString& v)
|
|
||||||
{
|
|
||||||
token_.token = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantMap DeviceFlow::extraTokens()
|
|
||||||
{
|
|
||||||
return token_.extra;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::setExtraTokens(QVariantMap extraTokens)
|
|
||||||
{
|
|
||||||
token_.extra = extraTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::setPollServer(PollServer* server)
|
|
||||||
{
|
|
||||||
if (pollServer_)
|
|
||||||
pollServer_->deleteLater();
|
|
||||||
|
|
||||||
pollServer_ = server;
|
|
||||||
}
|
|
||||||
|
|
||||||
PollServer* DeviceFlow::pollServer() const
|
|
||||||
{
|
|
||||||
return pollServer_;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantMap DeviceFlow::extraRequestParams()
|
|
||||||
{
|
|
||||||
return extraReqParams_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::setExtraRequestParams(const QVariantMap& value)
|
|
||||||
{
|
|
||||||
extraReqParams_ = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString DeviceFlow::grantType()
|
|
||||||
{
|
|
||||||
if (!grantType_.isEmpty())
|
|
||||||
return grantType_;
|
|
||||||
|
|
||||||
return OAUTH2_GRANT_TYPE_DEVICE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::setGrantType(const QString& value)
|
|
||||||
{
|
|
||||||
grantType_ = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First get the URL and token to display to the user
|
|
||||||
void DeviceFlow::login()
|
|
||||||
{
|
|
||||||
qDebug() << "DeviceFlow::link";
|
|
||||||
|
|
||||||
updateActivity(Activity::LoggingIn);
|
|
||||||
setLinked(false);
|
|
||||||
setToken("");
|
|
||||||
setExtraTokens(QVariantMap());
|
|
||||||
setRefreshToken(QString());
|
|
||||||
setExpires(QDateTime());
|
|
||||||
|
|
||||||
QList<RequestParameter> parameters;
|
|
||||||
parameters.append(RequestParameter(OAUTH2_CLIENT_ID, options_.clientIdentifier.toUtf8()));
|
|
||||||
parameters.append(RequestParameter(OAUTH2_SCOPE, options_.scope.toUtf8()));
|
|
||||||
parameters.append(RequestParameter(OAUTH2_RESPONSE_TYPE, options_.responseType.toUtf8()));
|
|
||||||
QByteArray payload = createQueryParameters(parameters);
|
|
||||||
|
|
||||||
QUrl url(options_.authorizationUrl);
|
|
||||||
QNetworkRequest deviceRequest(url);
|
|
||||||
deviceRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
|
||||||
QNetworkReply* tokenReply = manager_->post(deviceRequest, payload);
|
|
||||||
|
|
||||||
connect(tokenReply, &QNetworkReply::finished, this, &DeviceFlow::onDeviceAuthReplyFinished, Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then, once we get them, present them to the user
|
|
||||||
void DeviceFlow::onDeviceAuthReplyFinished()
|
|
||||||
{
|
|
||||||
qDebug() << "DeviceFlow::onDeviceAuthReplyFinished";
|
|
||||||
QNetworkReply* tokenReply = qobject_cast<QNetworkReply*>(sender());
|
|
||||||
if (!tokenReply) {
|
|
||||||
qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: reply is null";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (tokenReply->error() == QNetworkReply::NoError) {
|
|
||||||
QByteArray replyData = tokenReply->readAll();
|
|
||||||
|
|
||||||
// Dump replyData
|
|
||||||
// SENSITIVE DATA in RelWithDebInfo or Debug builds
|
|
||||||
// qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: replyData\n";
|
|
||||||
// qDebug() << QString( replyData );
|
|
||||||
|
|
||||||
QVariantMap params = parseJsonResponse(replyData);
|
|
||||||
|
|
||||||
// Dump tokens
|
|
||||||
qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: Tokens returned:\n";
|
|
||||||
foreach (QString key, params.keys()) {
|
|
||||||
// SENSITIVE DATA in RelWithDebInfo or Debug builds, so it is truncated first
|
|
||||||
qDebug() << key << ": " << params.value(key).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for mandatory parameters
|
|
||||||
if (hasMandatoryDeviceAuthParams(params)) {
|
|
||||||
qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: Device auth request response";
|
|
||||||
|
|
||||||
const QString userCode = params.take(OAUTH2_USER_CODE).toString();
|
|
||||||
QUrl uri = params.take(OAUTH2_VERIFICATION_URI).toUrl();
|
|
||||||
if (uri.isEmpty())
|
|
||||||
uri = params.take(OAUTH2_VERIFICATION_URL).toUrl();
|
|
||||||
|
|
||||||
if (params.contains(OAUTH2_VERIFICATION_URI_COMPLETE))
|
|
||||||
emit openBrowser(params.take(OAUTH2_VERIFICATION_URI_COMPLETE).toUrl());
|
|
||||||
|
|
||||||
bool ok = false;
|
|
||||||
int expiresIn = params[OAUTH2_EXPIRES_IN].toInt(&ok);
|
|
||||||
if (!ok) {
|
|
||||||
qWarning() << "DeviceFlow::startPollServer: No expired_in parameter";
|
|
||||||
updateActivity(Activity::FailedHard);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit showVerificationUriAndCode(uri, userCode, expiresIn);
|
|
||||||
|
|
||||||
startPollServer(params, expiresIn);
|
|
||||||
} else {
|
|
||||||
qWarning() << "DeviceFlow::onDeviceAuthReplyFinished: Mandatory parameters missing from response";
|
|
||||||
updateActivity(Activity::FailedHard);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokenReply->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spin up polling for the user completing the login flow out of band
|
|
||||||
void DeviceFlow::startPollServer(const QVariantMap& params, int expiresIn)
|
|
||||||
{
|
|
||||||
qDebug() << "DeviceFlow::startPollServer: device_ and user_code expires in" << expiresIn << "seconds";
|
|
||||||
|
|
||||||
QUrl url(options_.accessTokenUrl);
|
|
||||||
QNetworkRequest authRequest(url);
|
|
||||||
authRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
|
||||||
|
|
||||||
const QString deviceCode = params[OAUTH2_DEVICE_CODE].toString();
|
|
||||||
const QString grantType = grantType_.isEmpty() ? OAUTH2_GRANT_TYPE_DEVICE : grantType_;
|
|
||||||
|
|
||||||
QList<RequestParameter> parameters;
|
|
||||||
parameters.append(RequestParameter(OAUTH2_CLIENT_ID, options_.clientIdentifier.toUtf8()));
|
|
||||||
if (!options_.clientSecret.isEmpty()) {
|
|
||||||
parameters.append(RequestParameter(OAUTH2_CLIENT_SECRET, options_.clientSecret.toUtf8()));
|
|
||||||
}
|
|
||||||
parameters.append(RequestParameter(OAUTH2_DEVICE_CODE, deviceCode.toUtf8()));
|
|
||||||
parameters.append(RequestParameter(OAUTH2_GRANT_TYPE, grantType.toUtf8()));
|
|
||||||
parameters.append(RequestParameter(OAUTH2_RESPONSE_TYPE, options_.responseType.toUtf8()));
|
|
||||||
|
|
||||||
QByteArray payload = createQueryParameters(parameters);
|
|
||||||
|
|
||||||
PollServer* pollServer = new PollServer(manager_, authRequest, payload, expiresIn, this);
|
|
||||||
if (params.contains(OAUTH2_INTERVAL)) {
|
|
||||||
bool ok = false;
|
|
||||||
int interval = params[OAUTH2_INTERVAL].toInt(&ok);
|
|
||||||
if (ok) {
|
|
||||||
pollServer->setInterval(interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connect(pollServer, &PollServer::verificationReceived, this, &DeviceFlow::onVerificationReceived);
|
|
||||||
connect(pollServer, &PollServer::serverClosed, this, &DeviceFlow::serverHasClosed);
|
|
||||||
setPollServer(pollServer);
|
|
||||||
pollServer->startPolling();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once the user completes the flow, update the internal state and report it to observers
|
|
||||||
void DeviceFlow::onVerificationReceived(const QMap<QString, QString> response)
|
|
||||||
{
|
|
||||||
qDebug() << "DeviceFlow::onVerificationReceived: Emitting closeBrowser()";
|
|
||||||
emit closeBrowser();
|
|
||||||
|
|
||||||
if (response.contains("error")) {
|
|
||||||
qWarning() << "DeviceFlow::onVerificationReceived: Verification failed:" << response;
|
|
||||||
updateActivity(Activity::FailedHard);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for mandatory tokens
|
|
||||||
if (response.contains(OAUTH2_ACCESS_TOKEN)) {
|
|
||||||
qDebug() << "DeviceFlow::onVerificationReceived: Access token returned for implicit or device flow";
|
|
||||||
setToken(response.value(OAUTH2_ACCESS_TOKEN));
|
|
||||||
if (response.contains(OAUTH2_EXPIRES_IN)) {
|
|
||||||
bool ok = false;
|
|
||||||
int expiresIn = response.value(OAUTH2_EXPIRES_IN).toInt(&ok);
|
|
||||||
if (ok) {
|
|
||||||
qDebug() << "DeviceFlow::onVerificationReceived: Token expires in" << expiresIn << "seconds";
|
|
||||||
setExpires(QDateTime::currentDateTimeUtc().addSecs(expiresIn));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (response.contains(OAUTH2_REFRESH_TOKEN)) {
|
|
||||||
setRefreshToken(response.value(OAUTH2_REFRESH_TOKEN));
|
|
||||||
}
|
|
||||||
updateActivity(Activity::Succeeded);
|
|
||||||
} else {
|
|
||||||
qWarning() << "DeviceFlow::onVerificationReceived: Access token missing from response for implicit or device flow";
|
|
||||||
updateActivity(Activity::FailedHard);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Or if the flow fails or the polling times out, update the internal state with error and report it to observers
|
|
||||||
void DeviceFlow::serverHasClosed(bool paramsfound)
|
|
||||||
{
|
|
||||||
if (!paramsfound) {
|
|
||||||
// server has probably timed out after receiving first response
|
|
||||||
updateActivity(Activity::FailedHard);
|
|
||||||
}
|
|
||||||
// poll server is not re-used for later auth requests
|
|
||||||
setPollServer(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::logout()
|
|
||||||
{
|
|
||||||
qDebug() << "DeviceFlow::unlink";
|
|
||||||
updateActivity(Activity::LoggingOut);
|
|
||||||
// FIXME: implement logout flows... if they exist
|
|
||||||
token_ = Token();
|
|
||||||
updateActivity(Activity::FailedHard);
|
|
||||||
}
|
|
||||||
|
|
||||||
QDateTime DeviceFlow::expires()
|
|
||||||
{
|
|
||||||
return token_.notAfter;
|
|
||||||
}
|
|
||||||
void DeviceFlow::setExpires(QDateTime v)
|
|
||||||
{
|
|
||||||
token_.notAfter = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString DeviceFlow::refreshToken()
|
|
||||||
{
|
|
||||||
return token_.refresh_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::setRefreshToken(const QString& v)
|
|
||||||
{
|
|
||||||
qCDebug(katabasisCredentials) << "new refresh token:" << v;
|
|
||||||
token_.refresh_token = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
QByteArray buildRequestBody(const QMap<QString, QString>& parameters)
|
|
||||||
{
|
|
||||||
QByteArray body;
|
|
||||||
bool first = true;
|
|
||||||
foreach (QString key, parameters.keys()) {
|
|
||||||
if (first) {
|
|
||||||
first = false;
|
|
||||||
} else {
|
|
||||||
body.append("&");
|
|
||||||
}
|
|
||||||
QString value = parameters.value(key);
|
|
||||||
body.append(QUrl::toPercentEncoding(key) + QString("=").toUtf8() + QUrl::toPercentEncoding(value));
|
|
||||||
}
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool DeviceFlow::refresh()
|
|
||||||
{
|
|
||||||
qDebug() << "DeviceFlow::refresh: Token: ..." << refreshToken().right(7);
|
|
||||||
|
|
||||||
updateActivity(Activity::Refreshing);
|
|
||||||
|
|
||||||
if (refreshToken().isEmpty()) {
|
|
||||||
qWarning() << "DeviceFlow::refresh: No refresh token";
|
|
||||||
onRefreshError(QNetworkReply::AuthenticationRequiredError, nullptr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (options_.accessTokenUrl.isEmpty()) {
|
|
||||||
qWarning() << "DeviceFlow::refresh: Refresh token URL not set";
|
|
||||||
onRefreshError(QNetworkReply::AuthenticationRequiredError, nullptr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QNetworkRequest refreshRequest(options_.accessTokenUrl);
|
|
||||||
refreshRequest.setHeader(QNetworkRequest::ContentTypeHeader, MIME_TYPE_XFORM);
|
|
||||||
QMap<QString, QString> parameters;
|
|
||||||
parameters.insert(OAUTH2_CLIENT_ID, options_.clientIdentifier);
|
|
||||||
if (!options_.clientSecret.isEmpty()) {
|
|
||||||
parameters.insert(OAUTH2_CLIENT_SECRET, options_.clientSecret);
|
|
||||||
}
|
|
||||||
parameters.insert(OAUTH2_SCOPE, options_.scope.toUtf8());
|
|
||||||
parameters.insert(OAUTH2_REFRESH_TOKEN, refreshToken());
|
|
||||||
parameters.insert(OAUTH2_GRANT_TYPE, OAUTH2_REFRESH_TOKEN);
|
|
||||||
|
|
||||||
QByteArray data = buildRequestBody(parameters);
|
|
||||||
QNetworkReply* refreshReply = manager_->post(refreshRequest, data);
|
|
||||||
timedReplies_.add(refreshReply);
|
|
||||||
connect(refreshReply, &QNetworkReply::finished, this, &DeviceFlow::onRefreshFinished, Qt::QueuedConnection);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::onRefreshFinished()
|
|
||||||
{
|
|
||||||
QNetworkReply* refreshReply = qobject_cast<QNetworkReply*>(sender());
|
|
||||||
|
|
||||||
auto networkError = refreshReply->error();
|
|
||||||
if (networkError == QNetworkReply::NoError) {
|
|
||||||
QByteArray reply = refreshReply->readAll();
|
|
||||||
QVariantMap tokens = parseJsonResponse(reply);
|
|
||||||
setToken(tokens.value(OAUTH2_ACCESS_TOKEN).toString());
|
|
||||||
setExpires(QDateTime::currentDateTimeUtc().addSecs(tokens.value(OAUTH2_EXPIRES_IN).toInt()));
|
|
||||||
QString refreshToken = tokens.value(OAUTH2_REFRESH_TOKEN).toString();
|
|
||||||
if (!refreshToken.isEmpty()) {
|
|
||||||
setRefreshToken(refreshToken);
|
|
||||||
} else {
|
|
||||||
qDebug() << "No new refresh token. Keep the old one.";
|
|
||||||
}
|
|
||||||
timedReplies_.remove(refreshReply);
|
|
||||||
refreshReply->deleteLater();
|
|
||||||
updateActivity(Activity::Succeeded);
|
|
||||||
qDebug() << "New token expires in" << expires() << "seconds";
|
|
||||||
} else {
|
|
||||||
// FIXME: differentiate the error more here
|
|
||||||
onRefreshError(networkError, refreshReply);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceFlow::onRefreshError(QNetworkReply::NetworkError error, QNetworkReply* refreshReply)
|
|
||||||
{
|
|
||||||
QString errorString = "No Reply";
|
|
||||||
if (refreshReply) {
|
|
||||||
timedReplies_.remove(refreshReply);
|
|
||||||
errorString = refreshReply->errorString();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (error) {
|
|
||||||
// used for invalid credentials and similar errors. Fall through.
|
|
||||||
case QNetworkReply::AuthenticationRequiredError:
|
|
||||||
case QNetworkReply::ContentAccessDenied:
|
|
||||||
case QNetworkReply::ContentOperationNotPermittedError:
|
|
||||||
case QNetworkReply::ProtocolInvalidOperationError:
|
|
||||||
updateActivity(Activity::FailedHard);
|
|
||||||
break;
|
|
||||||
case QNetworkReply::ContentGoneError: {
|
|
||||||
updateActivity(Activity::FailedGone);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case QNetworkReply::TimeoutError:
|
|
||||||
case QNetworkReply::OperationCanceledError:
|
|
||||||
case QNetworkReply::SslHandshakeFailedError:
|
|
||||||
default:
|
|
||||||
updateActivity(Activity::FailedSoft);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (refreshReply) {
|
|
||||||
refreshReply->deleteLater();
|
|
||||||
}
|
|
||||||
qDebug() << "DeviceFlow::onRefreshFinished: Error" << static_cast<int>(error) << " - " << errorString;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Katabasis
|
|
47
nix/dev.nix
47
nix/dev.nix
@ -1,47 +0,0 @@
|
|||||||
{
|
|
||||||
perSystem =
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
pre-commit.settings = {
|
|
||||||
hooks = {
|
|
||||||
markdownlint.enable = true;
|
|
||||||
|
|
||||||
alejandra.enable = true;
|
|
||||||
deadnix.enable = true;
|
|
||||||
nil.enable = true;
|
|
||||||
|
|
||||||
clang-format = {
|
|
||||||
enable = true;
|
|
||||||
types_or = [
|
|
||||||
"c"
|
|
||||||
"c++"
|
|
||||||
"java"
|
|
||||||
"json"
|
|
||||||
"objective-c"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
tools.clang-tools = lib.mkForce pkgs.clang-tools_18;
|
|
||||||
};
|
|
||||||
|
|
||||||
devShells.default = pkgs.mkShell {
|
|
||||||
shellHook = ''
|
|
||||||
${config.pre-commit.installationScript}
|
|
||||||
'';
|
|
||||||
|
|
||||||
inputsFrom = [ config.packages.fjordlauncher-unwrapped ];
|
|
||||||
buildInputs = with pkgs; [
|
|
||||||
ccache
|
|
||||||
ninja
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
formatter = pkgs.alejandra;
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
inputs,
|
|
||||||
self,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
perSystem =
|
|
||||||
{
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
packages =
|
|
||||||
let
|
|
||||||
ourPackages = lib.fix (final: self.overlays.default final pkgs);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit (ourPackages) fjordlauncher-unwrapped fjordlauncher;
|
|
||||||
default = ourPackages.fjordlauncher;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
flake = {
|
|
||||||
overlays.default =
|
|
||||||
final: prev:
|
|
||||||
let
|
|
||||||
version = builtins.substring 0 8 self.lastModifiedDate or "dirty";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
fjordlauncher-unwrapped = prev.callPackage ./pkg {
|
|
||||||
inherit (inputs) libnbtplusplus;
|
|
||||||
inherit version;
|
|
||||||
};
|
|
||||||
|
|
||||||
fjordlauncher = prev.qt6Packages.callPackage ./pkg/wrapper.nix {
|
|
||||||
inherit (final) fjordlauncher-unwrapped;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
stdenv,
|
|
||||||
cmake,
|
|
||||||
cmark,
|
|
||||||
darwin,
|
|
||||||
extra-cmake-modules,
|
|
||||||
gamemode,
|
|
||||||
ghc_filesystem,
|
|
||||||
jdk17,
|
|
||||||
kdePackages,
|
|
||||||
ninja,
|
|
||||||
stripJavaArchivesHook,
|
|
||||||
tomlplusplus,
|
|
||||||
zlib,
|
|
||||||
msaClientID ? null,
|
|
||||||
gamemodeSupport ? stdenv.isLinux,
|
|
||||||
version,
|
|
||||||
libnbtplusplus,
|
|
||||||
}:
|
|
||||||
assert lib.assertMsg (
|
|
||||||
gamemodeSupport -> stdenv.isLinux
|
|
||||||
) "gamemodeSupport is only available on Linux.";
|
|
||||||
stdenv.mkDerivation {
|
|
||||||
pname = "fjordlauncher-unwrapped";
|
|
||||||
inherit version;
|
|
||||||
|
|
||||||
src = lib.fileset.toSource {
|
|
||||||
root = ../../.;
|
|
||||||
fileset = lib.fileset.unions (
|
|
||||||
map (fileName: ../../${fileName}) [
|
|
||||||
"buildconfig"
|
|
||||||
"cmake"
|
|
||||||
"launcher"
|
|
||||||
"libraries"
|
|
||||||
"program_info"
|
|
||||||
"tests"
|
|
||||||
"COPYING.md"
|
|
||||||
"CMakeLists.txt"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
postUnpack = ''
|
|
||||||
rm -rf source/libraries/libnbtplusplus
|
|
||||||
ln -s ${libnbtplusplus} source/libraries/libnbtplusplus
|
|
||||||
'';
|
|
||||||
|
|
||||||
nativeBuildInputs = [
|
|
||||||
cmake
|
|
||||||
ninja
|
|
||||||
extra-cmake-modules
|
|
||||||
jdk17
|
|
||||||
stripJavaArchivesHook
|
|
||||||
];
|
|
||||||
|
|
||||||
buildInputs =
|
|
||||||
[
|
|
||||||
cmark
|
|
||||||
ghc_filesystem
|
|
||||||
kdePackages.qtbase
|
|
||||||
kdePackages.qtnetworkauth
|
|
||||||
kdePackages.quazip
|
|
||||||
tomlplusplus
|
|
||||||
zlib
|
|
||||||
]
|
|
||||||
++ lib.optionals stdenv.isDarwin [ darwin.apple_sdk.frameworks.Cocoa ]
|
|
||||||
++ lib.optional gamemodeSupport gamemode;
|
|
||||||
|
|
||||||
hardeningEnable = lib.optionals stdenv.isLinux [ "pie" ];
|
|
||||||
|
|
||||||
cmakeFlags =
|
|
||||||
[
|
|
||||||
(lib.cmakeFeature "Launcher_BUILD_PLATFORM" "nixpkgs")
|
|
||||||
]
|
|
||||||
++ lib.optionals (msaClientID != null) [
|
|
||||||
(lib.cmakeFeature "Launcher_MSA_CLIENT_ID" (toString msaClientID))
|
|
||||||
]
|
|
||||||
++ lib.optionals (lib.versionOlder kdePackages.qtbase.version "6") [
|
|
||||||
(lib.cmakeFeature "Launcher_QT_VERSION_MAJOR" "5")
|
|
||||||
]
|
|
||||||
++ lib.optionals stdenv.isDarwin [
|
|
||||||
# we wrap our binary manually
|
|
||||||
(lib.cmakeFeature "INSTALL_BUNDLE" "nodeps")
|
|
||||||
# disable built-in updater
|
|
||||||
(lib.cmakeFeature "MACOSX_SPARKLE_UPDATE_FEED_URL" "''")
|
|
||||||
(lib.cmakeFeature "CMAKE_INSTALL_PREFIX" "${placeholder "out"}/Applications/")
|
|
||||||
];
|
|
||||||
|
|
||||||
dontWrapQtApps = true;
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
description = "Prism Launcher fork with support for alternative auth servers";
|
|
||||||
longDescription = ''
|
|
||||||
Allows you to have multiple, separate instances of Minecraft (each with
|
|
||||||
their own mods, texture packs, saves, etc) and helps you manage them and
|
|
||||||
their associated options with a simple interface.
|
|
||||||
'';
|
|
||||||
homepage = "https://fjordlauncher.org/";
|
|
||||||
license = lib.licenses.gpl3Only;
|
|
||||||
maintainers = with lib.maintainers; [
|
|
||||||
evan-goode
|
|
||||||
];
|
|
||||||
mainProgram = "fjordlauncher";
|
|
||||||
platforms = lib.platforms.linux ++ lib.platforms.darwin;
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,143 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
stdenv,
|
|
||||||
symlinkJoin,
|
|
||||||
fjordlauncher-unwrapped,
|
|
||||||
addOpenGLRunpath,
|
|
||||||
flite,
|
|
||||||
gamemode,
|
|
||||||
glfw,
|
|
||||||
glfw-wayland-minecraft,
|
|
||||||
glxinfo,
|
|
||||||
jdk8,
|
|
||||||
jdk17,
|
|
||||||
jdk21,
|
|
||||||
kdePackages,
|
|
||||||
libGL,
|
|
||||||
libpulseaudio,
|
|
||||||
libusb1,
|
|
||||||
makeWrapper,
|
|
||||||
openal,
|
|
||||||
pciutils,
|
|
||||||
udev,
|
|
||||||
vulkan-loader,
|
|
||||||
xorg,
|
|
||||||
additionalLibs ? [ ],
|
|
||||||
additionalPrograms ? [ ],
|
|
||||||
controllerSupport ? stdenv.isLinux,
|
|
||||||
gamemodeSupport ? stdenv.isLinux,
|
|
||||||
jdks ? [
|
|
||||||
jdk21
|
|
||||||
jdk17
|
|
||||||
jdk8
|
|
||||||
],
|
|
||||||
msaClientID ? null,
|
|
||||||
textToSpeechSupport ? stdenv.isLinux,
|
|
||||||
# Adds `glfw-wayland-minecraft` to `LD_LIBRARY_PATH`
|
|
||||||
# when launched on wayland, allowing for the game to be run natively.
|
|
||||||
# Make sure to enable "Use system installation of GLFW" in instance settings
|
|
||||||
# for this to take effect
|
|
||||||
#
|
|
||||||
# Warning: This build of glfw may be unstable, and the launcher
|
|
||||||
# itself can take slightly longer to start
|
|
||||||
withWaylandGLFW ? false,
|
|
||||||
}:
|
|
||||||
assert lib.assertMsg (
|
|
||||||
controllerSupport -> stdenv.isLinux
|
|
||||||
) "controllerSupport only has an effect on Linux.";
|
|
||||||
assert lib.assertMsg (
|
|
||||||
textToSpeechSupport -> stdenv.isLinux
|
|
||||||
) "textToSpeechSupport only has an effect on Linux.";
|
|
||||||
assert lib.assertMsg (
|
|
||||||
withWaylandGLFW -> stdenv.isLinux
|
|
||||||
) "withWaylandGLFW is only available on Linux.";
|
|
||||||
let
|
|
||||||
fjordlauncher' = fjordlauncher-unwrapped.override { inherit msaClientID gamemodeSupport; };
|
|
||||||
in
|
|
||||||
symlinkJoin {
|
|
||||||
name = "fjordlauncher-${fjordlauncher'.version}";
|
|
||||||
|
|
||||||
paths = [ fjordlauncher' ];
|
|
||||||
|
|
||||||
nativeBuildInputs =
|
|
||||||
[ kdePackages.wrapQtAppsHook ]
|
|
||||||
# purposefully using a shell wrapper here for variable expansion
|
|
||||||
# see https://github.com/NixOS/nixpkgs/issues/172583
|
|
||||||
++ lib.optional withWaylandGLFW makeWrapper;
|
|
||||||
|
|
||||||
buildInputs =
|
|
||||||
[
|
|
||||||
kdePackages.qtbase
|
|
||||||
kdePackages.qtsvg
|
|
||||||
]
|
|
||||||
++ lib.optional (
|
|
||||||
lib.versionAtLeast kdePackages.qtbase.version "6" && stdenv.isLinux
|
|
||||||
) kdePackages.qtwayland;
|
|
||||||
|
|
||||||
env = {
|
|
||||||
waylandPreExec = lib.optionalString withWaylandGLFW ''
|
|
||||||
if [ -n "$WAYLAND_DISPLAY" ]; then
|
|
||||||
export LD_LIBRARY_PATH=${lib.getLib glfw-wayland-minecraft}/lib:"$LD_LIBRARY_PATH"
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
postBuild =
|
|
||||||
lib.optionalString withWaylandGLFW ''
|
|
||||||
qtWrapperArgs+=(--run "$waylandPreExec")
|
|
||||||
''
|
|
||||||
+ ''
|
|
||||||
wrapQtAppsHook
|
|
||||||
'';
|
|
||||||
|
|
||||||
qtWrapperArgs =
|
|
||||||
let
|
|
||||||
runtimeLibs =
|
|
||||||
[
|
|
||||||
# lwjgl
|
|
||||||
glfw
|
|
||||||
libpulseaudio
|
|
||||||
libGL
|
|
||||||
openal
|
|
||||||
stdenv.cc.cc.lib
|
|
||||||
|
|
||||||
vulkan-loader # VulkanMod's lwjgl
|
|
||||||
|
|
||||||
udev # oshi
|
|
||||||
|
|
||||||
xorg.libX11
|
|
||||||
xorg.libXext
|
|
||||||
xorg.libXcursor
|
|
||||||
xorg.libXrandr
|
|
||||||
xorg.libXxf86vm
|
|
||||||
]
|
|
||||||
++ lib.optional textToSpeechSupport flite
|
|
||||||
++ lib.optional gamemodeSupport gamemode.lib
|
|
||||||
++ lib.optional controllerSupport libusb1
|
|
||||||
++ additionalLibs;
|
|
||||||
|
|
||||||
runtimePrograms = [
|
|
||||||
glxinfo
|
|
||||||
pciutils # need lspci
|
|
||||||
xorg.xrandr # needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128
|
|
||||||
] ++ additionalPrograms;
|
|
||||||
in
|
|
||||||
[ "--prefix FJORDLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks}" ]
|
|
||||||
++ lib.optionals stdenv.isLinux [
|
|
||||||
"--set LD_LIBRARY_PATH ${addOpenGLRunpath.driverLink}/lib:${lib.makeLibraryPath runtimeLibs}"
|
|
||||||
"--prefix PATH : ${lib.makeBinPath runtimePrograms}"
|
|
||||||
];
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
inherit (fjordlauncher'.meta)
|
|
||||||
description
|
|
||||||
longDescription
|
|
||||||
homepage
|
|
||||||
changelog
|
|
||||||
license
|
|
||||||
maintainers
|
|
||||||
mainProgram
|
|
||||||
platforms
|
|
||||||
;
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
# Reimplementation of https://git.sakamoto.pl/domi/curseme/-/blob/meow/getToken.sh in python
|
|
||||||
|
|
||||||
import io
|
|
||||||
import requests
|
|
||||||
from re import search
|
|
||||||
|
|
||||||
# i have no idea why this is ripping the key from 4chan but what works works i guess
|
|
||||||
token = requests.get("https://arch.b4k.co/vg/thread/388569358").text
|
|
||||||
token = search("and put .*? ", token).group(0)
|
|
||||||
token = search("\$.*$", token).group(0).strip()
|
|
||||||
|
|
||||||
output = io.open("token", "w")
|
|
||||||
output.write(token)
|
|
||||||
output.close()
|
|
Loading…
Reference in New Issue
Block a user