mirror of
https://github.com/hero-persson/FjordLauncherUnlocked.git
synced 2025-04-11 22:58:48 +02:00
Clean up some lingering differences from upstream
This commit is contained in:
parent
b496316241
commit
da9c085a06
36 changed files with 24 additions and 1793 deletions
2
.github/workflows/update-flake.yml
vendored
2
.github/workflows/update-flake.yml
vendored
|
@ -12,7 +12,7 @@ permissions:
|
|||
|
||||
jobs:
|
||||
update-flake:
|
||||
if: github.repository == 'unmojang/Fjord Launcher'
|
||||
if: github.repository == 'unmojang/FjordLauncher'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,7 +8,6 @@ resources/CMakeFiles
|
|||
html/
|
||||
|
||||
# Project Files
|
||||
*.orig
|
||||
*.pro.user
|
||||
CMakeLists.txt.user
|
||||
CMakeLists.txt.user.*
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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()) {
|
||||
// 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"),
|
||||
tr("In order to play Minecraft, you must pick your username or log in to an account "
|
||||
"Would you like to open the account manager to do this now?"),
|
||||
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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
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->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
|
||||
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <minecraft/auth/MinecraftAccount.h>
|
||||
#include "net/NetRequest.h"
|
||||
|
||||
class CapeChange : public Net::NetRequest {
|
||||
|
@ -27,7 +28,7 @@ class CapeChange : public Net::NetRequest {
|
|||
CapeChange(QString capeId);
|
||||
virtual ~CapeChange() = default;
|
||||
|
||||
static CapeChange::Ptr make(QString token, QString capeId);
|
||||
static CapeChange::Ptr make(MinecraftAccountPtr account, QString capeId);
|
||||
|
||||
protected:
|
||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||
|
|
|
@ -50,10 +50,11 @@ QNetworkReply* SkinDelete::getReply(QNetworkRequest& request)
|
|||
return m_network->deleteResource(request);
|
||||
}
|
||||
|
||||
SkinDelete::Ptr SkinDelete::make(QString token)
|
||||
SkinDelete::Ptr SkinDelete::make(MinecraftAccountPtr account)
|
||||
{
|
||||
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->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
|
||||
{ "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() },
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <minecraft/auth/MinecraftAccount.h>
|
||||
#include "net/NetRequest.h"
|
||||
|
||||
class SkinDelete : public Net::NetRequest {
|
||||
|
@ -27,7 +28,7 @@ class SkinDelete : public Net::NetRequest {
|
|||
SkinDelete();
|
||||
virtual ~SkinDelete() = default;
|
||||
|
||||
static SkinDelete::Ptr make(QString token);
|
||||
static SkinDelete::Ptr make(MinecraftAccountPtr account);
|
||||
|
||||
protected:
|
||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||
|
|
|
@ -67,10 +67,11 @@ QNetworkReply* SkinUpload::getReply(QNetworkRequest& request)
|
|||
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);
|
||||
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->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
|
||||
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <minecraft/auth/MinecraftAccount.h>
|
||||
#include "net/NetRequest.h"
|
||||
|
||||
class SkinUpload : public Net::NetRequest {
|
||||
|
@ -29,7 +30,7 @@ class SkinUpload : public Net::NetRequest {
|
|||
SkinUpload(QString path, QString variant);
|
||||
virtual ~SkinUpload() = default;
|
||||
|
||||
static SkinUpload::Ptr make(QString token, QString path, QString variant);
|
||||
static SkinUpload::Ptr make(MinecraftAccountPtr account, QString path, QString variant);
|
||||
|
||||
protected:
|
||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
qt.*.debug=false
|
||||
# don't log credentials by default
|
||||
launcher.auth.credentials.debug=false
|
||||
katabasis.*.debug=false
|
||||
# remove the debug lines, other log levels still get through
|
||||
launcher.task.net.download.debug=false
|
||||
# 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;
|
||||
}
|
||||
|
||||
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();
|
||||
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>());
|
||||
|
@ -267,7 +267,7 @@ void SkinManageDialog::on_resetBtn_clicked()
|
|||
{
|
||||
ProgressDialog prog(this);
|
||||
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>());
|
||||
if (prog.execWithTask(skinReset.get()) != QDialog::Accepted) {
|
||||
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/CustomMessageBox.h"
|
||||
#include "ui/dialogs/LoginDialog.h"
|
||||
#include "ui/dialogs/MSALoginDialog.h"
|
||||
#include "ui/dialogs/OfflineLoginDialog.h"
|
||||
|
||||
|
@ -59,7 +58,7 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new
|
|||
ui->setupUi(this);
|
||||
ui->listView->setEmptyString(
|
||||
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->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
|
@ -137,7 +136,7 @@ void AccountListPage::on_actionAddAuthlibInjector_triggered()
|
|||
{
|
||||
if (!m_accounts->anyAccountIsValid()) {
|
||||
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."
|
||||
"<br><br>"
|
||||
"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…
Add table
Reference in a new issue