mirror of
https://github.com/hero-persson/FjordLauncherUnlocked.git
synced 2025-04-11 22:58:48 +02:00
Merge tag '9.2.4' into develop
This commit is contained in:
commit
c97a005058
21 changed files with 379 additions and 61 deletions
|
@ -182,7 +182,7 @@ set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE S
|
|||
######## Set version numbers ########
|
||||
set(Launcher_VERSION_MAJOR 9)
|
||||
set(Launcher_VERSION_MINOR 2)
|
||||
set(Launcher_VERSION_PATCH 2)
|
||||
set(Launcher_VERSION_PATCH 4)
|
||||
|
||||
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}")
|
||||
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}.0")
|
||||
|
|
6
flake.lock
generated
6
flake.lock
generated
|
@ -49,11 +49,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1739446958,
|
||||
"narHash": "sha256-+/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc=",
|
||||
"lastModified": 1742422364,
|
||||
"narHash": "sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2ff53fe64443980e139eaa286017f53f88336dd0",
|
||||
"rev": "a84ebe20c6bc2ecbcfb000a50776219f48d134cc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -238,6 +238,8 @@ set(MINECRAFT_SOURCES
|
|||
minecraft/auth/AuthFlow.cpp
|
||||
minecraft/auth/AuthFlow.h
|
||||
|
||||
minecraft/auth/steps/AuthlibInjectorMetadataStep.cpp
|
||||
minecraft/auth/steps/AuthlibInjectorMetadataStep.h
|
||||
minecraft/auth/steps/EntitlementsStep.cpp
|
||||
minecraft/auth/steps/EntitlementsStep.h
|
||||
minecraft/auth/steps/GetSkinStep.cpp
|
||||
|
@ -385,6 +387,10 @@ set(MINECRAFT_SOURCES
|
|||
minecraft/AssetsUtils.cpp
|
||||
|
||||
# Minecraft skins
|
||||
minecraft/skins/AuthlibInjectorTextureDelete.cpp
|
||||
minecraft/skins/AuthlibInjectorTextureDelete.h
|
||||
minecraft/skins/AuthlibInjectorTextureUpload.cpp
|
||||
minecraft/skins/AuthlibInjectorTextureUpload.h
|
||||
minecraft/skins/CapeChange.cpp
|
||||
minecraft/skins/CapeChange.h
|
||||
minecraft/skins/SkinUpload.cpp
|
||||
|
|
|
@ -72,7 +72,8 @@ auto GetAuthlibInjectorApiLocation::Sink::finalize(QNetworkReply& reply) -> Task
|
|||
qDebug() << "X-Authlib-Injector-API-Location header not found!";
|
||||
}
|
||||
|
||||
m_outer.m_account.reset(MinecraftAccount::createFromUsernameAuthlibInjector(m_outer.m_username, url.toString()));
|
||||
const auto& encodedUrl = url.toEncoded(QUrl::FullyEncoded);
|
||||
m_outer.m_account.reset(MinecraftAccount::createFromUsernameAuthlibInjector(m_outer.m_username, encodedUrl));
|
||||
return Task::State::Succeeded;
|
||||
}
|
||||
|
||||
|
|
|
@ -133,6 +133,8 @@ void profileToJSONV3(QJsonObject& parent, MinecraftProfile p, const char* tokenN
|
|||
out["skin"] = skinObj;
|
||||
}
|
||||
|
||||
out["canUploadSkins"] = p.canUploadSkins;
|
||||
|
||||
QJsonArray capesArray;
|
||||
for (auto& cape : p.capes) {
|
||||
QJsonObject capeObj;
|
||||
|
@ -195,6 +197,11 @@ MinecraftProfile profileFromJSONV3(const QJsonObject& parent, const char* tokenN
|
|||
}
|
||||
}
|
||||
|
||||
out.canUploadSkins = true;
|
||||
if (tokenObject.value("canUploadSkins").isBool()) {
|
||||
out.canUploadSkins = tokenObject.value("canUploadskins").toBool();
|
||||
}
|
||||
|
||||
{
|
||||
auto capesV = tokenObject.value("capes");
|
||||
if (!capesV.isArray()) {
|
||||
|
@ -383,9 +390,9 @@ bool AccountData::usesCustomApiServers() const
|
|||
return type == AccountType::AuthlibInjector;
|
||||
}
|
||||
|
||||
bool AccountData::supportsSkinManagement() const
|
||||
bool AccountData::canUploadSkins() const
|
||||
{
|
||||
return type == AccountType::MSA;
|
||||
return minecraftProfile.canUploadSkins;
|
||||
}
|
||||
|
||||
QString AccountData::authServerUrl() const
|
||||
|
|
|
@ -82,6 +82,7 @@ struct MinecraftEntitlement {
|
|||
struct MinecraftProfile {
|
||||
QString id;
|
||||
QString name;
|
||||
bool canUploadSkins = true;
|
||||
Skin skin;
|
||||
QString currentCape;
|
||||
QMap<QString, Cape> capes;
|
||||
|
@ -96,7 +97,7 @@ struct AccountData {
|
|||
QJsonObject saveState() const;
|
||||
bool resumeStateFromV3(QJsonObject data);
|
||||
|
||||
bool supportsSkinManagement() const;
|
||||
bool canUploadSkins() const;
|
||||
bool usesCustomApiServers() const;
|
||||
QString authServerUrl() const;
|
||||
QString accountServerUrl() const;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <QNetworkRequest>
|
||||
|
||||
#include "minecraft/auth/AccountData.h"
|
||||
#include "minecraft/auth/steps/AuthlibInjectorMetadataStep.h"
|
||||
#include "minecraft/auth/steps/EntitlementsStep.h"
|
||||
#include "minecraft/auth/steps/GetSkinStep.h"
|
||||
#include "minecraft/auth/steps/LauncherLoginStep.h"
|
||||
|
@ -46,6 +47,7 @@ AuthFlow::AuthFlow(AccountData* data, Action action, const std::optional<QString
|
|||
} else if (data->type == AccountType::AuthlibInjector) {
|
||||
m_steps.append(makeShared<YggdrasilStep>(m_data, password));
|
||||
m_steps.append(makeShared<YggdrasilMinecraftProfileStep>(m_data));
|
||||
m_steps.append(makeShared<AuthlibInjectorMetadataStep>(m_data));
|
||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
||||
}
|
||||
changeState(AccountTaskState::STATE_CREATED);
|
||||
|
|
|
@ -119,7 +119,7 @@ class MinecraftAccount : public QObject, public Usable {
|
|||
|
||||
bool usesCustomApiServers() const { return data.usesCustomApiServers(); }
|
||||
|
||||
bool supportsSkinManagement() const { return data.supportsSkinManagement(); }
|
||||
bool canUploadSkins() const { return data.canUploadSkins(); }
|
||||
|
||||
QString accountDisplayString() const { return data.accountDisplayString(); }
|
||||
|
||||
|
|
|
@ -188,6 +188,9 @@ bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output)
|
|||
output.skin = skinOut;
|
||||
break;
|
||||
}
|
||||
|
||||
output.canUploadSkins = true;
|
||||
|
||||
auto capesArray = obj.value("capes").toArray();
|
||||
|
||||
QString currentCape;
|
||||
|
@ -306,26 +309,38 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output)
|
|||
|
||||
auto propsArray = obj.value("properties").toArray();
|
||||
QByteArray texturePayload;
|
||||
bool canUploadSkins = true;
|
||||
for (auto p : propsArray) {
|
||||
auto pObj = p.toObject();
|
||||
auto name = pObj.value("name");
|
||||
if (!name.isString() || name.toString() != "textures") {
|
||||
if (!name.isString()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto value = pObj.value("value");
|
||||
if (value.isString()) {
|
||||
const auto& nameString = name.toString();
|
||||
if (nameString == "textures") {
|
||||
auto value = pObj.value("value");
|
||||
if (value.isString()) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
texturePayload = QByteArray::fromBase64(value.toString().toUtf8(), QByteArray::AbortOnBase64DecodingErrors);
|
||||
texturePayload = QByteArray::fromBase64(value.toString().toUtf8(), QByteArray::AbortOnBase64DecodingErrors);
|
||||
#else
|
||||
texturePayload = QByteArray::fromBase64(value.toString().toUtf8());
|
||||
texturePayload = QByteArray::fromBase64(value.toString().toUtf8());
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!texturePayload.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
} else if (nameString == "uploadableTextures") {
|
||||
// https://github.com/yushijinhun/authlib-injector/wiki/Yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#uploadabletextures-%E5%8F%AF%E4%B8%8A%E4%BC%A0%E7%9A%84%E6%9D%90%E8%B4%A8%E7%B1%BB%E5%9E%8B
|
||||
const auto& value = pObj.value("value");
|
||||
if (value.isString()) {
|
||||
canUploadSkins = false;
|
||||
for (const auto& textureType : value.toString().split(",")) {
|
||||
if (textureType == "skin") {
|
||||
canUploadSkins = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
output.canUploadSkins = canUploadSkins;
|
||||
|
||||
if (texturePayload.isNull()) {
|
||||
qWarning() << "No texture payload data";
|
||||
|
@ -379,7 +394,6 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output)
|
|||
// we don't know the cape ID as it is not returned from the session server
|
||||
// so just fake it - changing capes is probably locked anyway :(
|
||||
capeOut.alias = "cape";
|
||||
capeOut.id = "00000000-0000-0000-0000-000000000000";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "Application.h"
|
||||
|
||||
AuthlibInjectorMetadataStep::AuthlibInjectorMetadataStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
|
@ -21,29 +19,32 @@ void AuthlibInjectorMetadataStep::perform()
|
|||
emit finished(AccountTaskState::STATE_WORKING, tr("Account has no authlib-injector URL."));
|
||||
return;
|
||||
}
|
||||
QNetworkRequest request = QNetworkRequest(m_data->customAuthlibInjectorUrl);
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &AuthlibInjectorMetadataStep::onRequestDone);
|
||||
requestor->get(request);
|
||||
|
||||
QUrl url{m_data->customAuthlibInjectorUrl};
|
||||
|
||||
m_response.reset(new QByteArray());
|
||||
m_request = Net::Download::makeByteArray(url, m_response);
|
||||
|
||||
m_task.reset(new NetJob("AuthlibInjectorMetadataStep", APPLICATION->network()));
|
||||
m_task->setAskRetry(false);
|
||||
m_task->setAutoRetryLimit(0);
|
||||
m_task->addNetAction(m_request);
|
||||
|
||||
connect(m_task.get(), &Task::finished, this, &AuthlibInjectorMetadataStep::onRequestDone);
|
||||
|
||||
m_task->start();
|
||||
}
|
||||
|
||||
void AuthlibInjectorMetadataStep::rehydrate() {}
|
||||
|
||||
void AuthlibInjectorMetadataStep::onRequestDone(QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers)
|
||||
void AuthlibInjectorMetadataStep::onRequestDone()
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
if (error == QNetworkReply::NoError && data.size() > 0) {
|
||||
if (m_request->error() == QNetworkReply::NoError && m_response->size() > 0) {
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*m_response, &jsonError);
|
||||
if (jsonError.error == QJsonParseError::NoError) {
|
||||
m_data->authlibInjectorMetadata = data.toBase64();
|
||||
m_data->authlibInjectorMetadata = m_response->toBase64();
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got authlib-injector metadata."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Didn't get authlib-injector metadata, continuing anyway."));
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Couldn't get authlib-injector metadata, continuing anyway."));
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#pragma once
|
||||
#include <QObject>
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "net/Download.h"
|
||||
|
||||
class AuthlibInjectorMetadataStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
@ -12,10 +13,14 @@ class AuthlibInjectorMetadataStep : public AuthStep {
|
|||
virtual ~AuthlibInjectorMetadataStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
void rehydrate() override;
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
void onRequestDone();
|
||||
|
||||
private:
|
||||
std::shared_ptr<QByteArray> m_response;
|
||||
Net::Download::Ptr m_request;
|
||||
NetJob::Ptr m_task;
|
||||
};
|
||||
|
|
62
launcher/minecraft/skins/AuthlibInjectorTextureDelete.cpp
Normal file
62
launcher/minecraft/skins/AuthlibInjectorTextureDelete.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Fjord Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2024 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/>.
|
||||
*
|
||||
* 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 "AuthlibInjectorTextureDelete.h"
|
||||
|
||||
#include "net/ByteArraySink.h"
|
||||
#include "net/RawHeaderProxy.h"
|
||||
|
||||
AuthlibInjectorTextureDelete::AuthlibInjectorTextureDelete(QString textureType) : NetRequest(), m_textureType(textureType)
|
||||
{
|
||||
logCat = taskMCSkinsLogC;
|
||||
}
|
||||
|
||||
QNetworkReply* AuthlibInjectorTextureDelete::getReply(QNetworkRequest& request)
|
||||
{
|
||||
setStatus(tr("Deleting texture"));
|
||||
return m_network->deleteResource(request);
|
||||
}
|
||||
|
||||
AuthlibInjectorTextureDelete::Ptr AuthlibInjectorTextureDelete::make(MinecraftAccountPtr account, QString textureType)
|
||||
{
|
||||
auto up = makeShared<AuthlibInjectorTextureDelete>(textureType);
|
||||
QString token = account->accessToken();
|
||||
up->m_url = QUrl(account->accountServerUrl() + "/user/profile/" + account->profileId() + "/" + textureType);
|
||||
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() },
|
||||
}));
|
||||
return up;
|
||||
}
|
38
launcher/minecraft/skins/AuthlibInjectorTextureDelete.h
Normal file
38
launcher/minecraft/skins/AuthlibInjectorTextureDelete.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Fjord Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2024 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 <minecraft/auth/MinecraftAccount.h>
|
||||
#include "net/NetRequest.h"
|
||||
|
||||
class AuthlibInjectorTextureDelete : public Net::NetRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<AuthlibInjectorTextureDelete>;
|
||||
AuthlibInjectorTextureDelete(QString textureType);
|
||||
virtual ~AuthlibInjectorTextureDelete() = default;
|
||||
|
||||
static AuthlibInjectorTextureDelete::Ptr make(MinecraftAccountPtr account, QString textureType);
|
||||
|
||||
protected:
|
||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||
|
||||
private:
|
||||
QString m_textureType;
|
||||
};
|
83
launcher/minecraft/skins/AuthlibInjectorTextureUpload.cpp
Normal file
83
launcher/minecraft/skins/AuthlibInjectorTextureUpload.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Fjord Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2024 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/>.
|
||||
*
|
||||
* 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 "AuthlibInjectorTextureUpload.h"
|
||||
|
||||
#include <QHttpMultiPart>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "net/ByteArraySink.h"
|
||||
#include "net/RawHeaderProxy.h"
|
||||
|
||||
AuthlibInjectorTextureUpload::AuthlibInjectorTextureUpload(QString path, std::optional<QString> skin_variant) : NetRequest(), m_path(path), m_skin_variant(skin_variant)
|
||||
{
|
||||
logCat = taskMCSkinsLogC;
|
||||
}
|
||||
|
||||
QNetworkReply* AuthlibInjectorTextureUpload::getReply(QNetworkRequest& request)
|
||||
{
|
||||
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this);
|
||||
|
||||
QHttpPart file;
|
||||
file.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png"));
|
||||
file.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"texture.png\""));
|
||||
file.setBody(FS::read(m_path));
|
||||
multiPart->append(file);
|
||||
|
||||
if (m_skin_variant.has_value()) {
|
||||
QHttpPart model;
|
||||
model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"model\""));
|
||||
model.setBody(m_skin_variant->toUtf8());
|
||||
multiPart->append(model);
|
||||
}
|
||||
|
||||
setStatus(tr("Uploading texture"));
|
||||
return m_network->put(request, multiPart);
|
||||
}
|
||||
|
||||
AuthlibInjectorTextureUpload::Ptr AuthlibInjectorTextureUpload::make(MinecraftAccountPtr account, QString path, std::optional<QString> skin_variant)
|
||||
{
|
||||
auto up = makeShared<AuthlibInjectorTextureUpload>(path, skin_variant);
|
||||
QString token = account->accessToken();
|
||||
|
||||
QString textureType = skin_variant.has_value() ? "skin" : "cape";
|
||||
up->m_url = QUrl(account->accountServerUrl() + "/user/profile/" + account->profileId() + "/" + textureType);
|
||||
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>{
|
||||
{ "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() },
|
||||
}));
|
||||
return up;
|
||||
}
|
41
launcher/minecraft/skins/AuthlibInjectorTextureUpload.h
Normal file
41
launcher/minecraft/skins/AuthlibInjectorTextureUpload.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Fjord Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2024 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 <minecraft/auth/MinecraftAccount.h>
|
||||
#include "net/NetRequest.h"
|
||||
|
||||
class AuthlibInjectorTextureUpload : public Net::NetRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<AuthlibInjectorTextureUpload>;
|
||||
|
||||
// Note this class takes ownership of the file.
|
||||
AuthlibInjectorTextureUpload(QString path, std::optional<QString> skin_variant);
|
||||
virtual ~AuthlibInjectorTextureUpload() = default;
|
||||
|
||||
static AuthlibInjectorTextureUpload::Ptr make(MinecraftAccountPtr account, QString path, std::optional<QString> skin_variant);
|
||||
|
||||
protected:
|
||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||
|
||||
private:
|
||||
QString m_path;
|
||||
std::optional<QString> m_skin_variant;
|
||||
};
|
|
@ -179,8 +179,7 @@ QList<BasePage*> NewInstanceDialog::getPages()
|
|||
pages.append(new CustomPage(this));
|
||||
pages.append(importPage);
|
||||
pages.append(new AtlPage(this));
|
||||
if (APPLICATION->capabilities() & Application::SupportsFlame)
|
||||
pages.append(new FlamePage(this));
|
||||
pages.append(new FlamePage(this));
|
||||
pages.append(new FtbPage(this));
|
||||
pages.append(new LegacyFTB::Page(this));
|
||||
pages.append(new FTBImportAPP::ImportFTBPage(this));
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
#include "QObjectPtr.h"
|
||||
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "minecraft/skins/AuthlibInjectorTextureDelete.h"
|
||||
#include "minecraft/skins/AuthlibInjectorTextureUpload.h"
|
||||
#include "minecraft/skins/CapeChange.h"
|
||||
#include "minecraft/skins/SkinDelete.h"
|
||||
#include "minecraft/skins/SkinList.h"
|
||||
|
@ -45,6 +47,7 @@
|
|||
|
||||
#include "net/Download.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "net/Upload.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
|
@ -162,6 +165,12 @@ QPixmap previewCape(QPixmap capeImage)
|
|||
|
||||
void SkinManageDialog::setupCapes()
|
||||
{
|
||||
// Capes are currently unsupported on authlib-injector accounts.
|
||||
if (m_acct->accountType() == AccountType::AuthlibInjector) {
|
||||
ui->capeCombo->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: add a model for this, download/refresh the capes on demand
|
||||
auto& accountData = *m_acct->accountData();
|
||||
int index = 0;
|
||||
|
@ -252,10 +261,26 @@ void SkinManageDialog::accept()
|
|||
return;
|
||||
}
|
||||
|
||||
skinUpload->addNetAction(SkinUpload::make(m_acct, skin->getPath(), skin->getModelString()));
|
||||
switch (m_acct->accountType()) {
|
||||
case AccountType::MSA: {
|
||||
skinUpload->addNetAction(SkinUpload::make(m_acct, skin->getPath(), skin->getModelString()));
|
||||
break;
|
||||
};
|
||||
case AccountType::AuthlibInjector: {
|
||||
const auto& variant = skin->getModel() == SkinModel::SLIM ? "slim" : "";
|
||||
skinUpload->addNetAction(AuthlibInjectorTextureUpload::make(m_acct, skin->getPath(), variant));
|
||||
break;
|
||||
};
|
||||
case AccountType::Offline: {
|
||||
qDebug() << "Unhandled account type: Offline";
|
||||
reject();
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
const bool canChangeCapes = m_acct->accountType() != AccountType::AuthlibInjector;
|
||||
auto selectedCape = skin->getCapeId();
|
||||
if (selectedCape != m_acct->accountData()->minecraftProfile.currentCape) {
|
||||
if (canChangeCapes && selectedCape != m_acct->accountData()->minecraftProfile.currentCape) {
|
||||
skinUpload->addNetAction(CapeChange::make(m_acct, selectedCape));
|
||||
}
|
||||
|
||||
|
@ -273,7 +298,23 @@ 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));
|
||||
|
||||
switch (m_acct->accountType()) {
|
||||
case AccountType::MSA: {
|
||||
skinReset->addNetAction(SkinDelete::make(m_acct));
|
||||
break;
|
||||
};
|
||||
case AccountType::AuthlibInjector: {
|
||||
skinReset->addNetAction(AuthlibInjectorTextureDelete::make(m_acct, "skin"));
|
||||
break;
|
||||
};
|
||||
case AccountType::Offline: {
|
||||
qDebug() << "Unhandled account type: Offline";
|
||||
reject();
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -409,6 +450,8 @@ void SkinManageDialog::on_userBtn_clicked()
|
|||
if (user.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const auto & account = m_acct;
|
||||
|
||||
MinecraftProfile mcProfile;
|
||||
auto path = FS::PathCombine(m_list.getDir(), user + ".png");
|
||||
|
||||
|
@ -421,7 +464,9 @@ void SkinManageDialog::on_userBtn_clicked()
|
|||
auto uuidLoop = makeShared<WaitTask>();
|
||||
auto profileLoop = makeShared<WaitTask>();
|
||||
|
||||
auto getUUID = Net::Download::makeByteArray("https://api.mojang.com/users/profiles/minecraft/" + user, uuidOut);
|
||||
// authlib-injector only specifies the POST /profiles/minecraft route, so we have to use it.
|
||||
const auto & payload = QJsonDocument(QJsonArray{user}).toJson(QJsonDocument::Compact);
|
||||
auto getUUID = Net::Upload::makeByteArray(m_acct->accountServerUrl()+"/profiles/minecraft", uuidOut, payload);
|
||||
auto getProfile = Net::Download::makeByteArray(QUrl(), profileOut);
|
||||
auto downloadSkin = Net::Download::makeFile(QUrl(), path);
|
||||
|
||||
|
@ -444,7 +489,7 @@ void SkinManageDialog::on_userBtn_clicked()
|
|||
failReason = tr("failed to download skin");
|
||||
});
|
||||
|
||||
connect(getUUID.get(), &Task::succeeded, this, [uuidLoop, uuidOut, job, getProfile, &failReason] {
|
||||
connect(getUUID.get(), &Task::succeeded, this, [account, uuidLoop, uuidOut, job, getProfile, &failReason] {
|
||||
try {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*uuidOut, &parse_error);
|
||||
|
@ -455,14 +500,15 @@ void SkinManageDialog::on_userBtn_clicked()
|
|||
uuidLoop->quit();
|
||||
return;
|
||||
}
|
||||
const auto root = doc.object();
|
||||
auto id = Json::ensureString(root, "id");
|
||||
if (!id.isEmpty()) {
|
||||
getProfile->setUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + id);
|
||||
} else {
|
||||
const auto& root = doc.array();
|
||||
if (root.size() != 1) {
|
||||
failReason = tr("user id is empty");
|
||||
job->abort();
|
||||
}
|
||||
|
||||
const auto& nameIdObject = root[0].toObject();
|
||||
const auto& id = nameIdObject.value("id").toString();
|
||||
getProfile->setUrl(account->sessionServerUrl()+"/session/minecraft/profile/"+id);
|
||||
} catch (const Exception& e) {
|
||||
qCritical() << "Couldn't load skin json:" << e.cause();
|
||||
failReason = tr("failed to parse get user UUID response");
|
||||
|
|
|
@ -274,7 +274,7 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>(Default)</string>
|
||||
<string>(None)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -221,17 +221,17 @@ void AccountListPage::updateButtonStates()
|
|||
bool hasSelection = !selection.empty();
|
||||
bool accountIsReady = false;
|
||||
bool accountIsOnline = false;
|
||||
bool accountSupportsSkinManagement = false;
|
||||
bool accountCanUploadSkins = false;
|
||||
if (hasSelection) {
|
||||
QModelIndex selected = selection.first();
|
||||
MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value<MinecraftAccountPtr>();
|
||||
accountIsReady = !account->isActive();
|
||||
accountIsOnline = account->accountType() != AccountType::Offline;
|
||||
accountSupportsSkinManagement = account->supportsSkinManagement();
|
||||
accountCanUploadSkins = account->canUploadSkins();
|
||||
}
|
||||
ui->actionRemove->setEnabled(accountIsReady);
|
||||
ui->actionSetDefault->setEnabled(accountIsReady);
|
||||
ui->actionManageSkins->setEnabled(accountIsReady && accountIsOnline && accountSupportsSkinManagement);
|
||||
ui->actionManageSkins->setEnabled(accountIsReady && accountIsOnline && accountCanUploadSkins);
|
||||
ui->actionRefresh->setEnabled(accountIsReady && accountIsOnline);
|
||||
|
||||
if (m_accounts->defaultAccount().get() == nullptr) {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Note: CurseForge allows creators to block access to third-party tools like Prism Launcher. As such, you may need to manually download some mods to be able to install a modpack.</string>
|
||||
<string>Note: a valid CurseForge API key is required to download modpacks from CurseForge. Set one in Settings → APIs → API Keys → CurseForge Core API.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
|
|
|
@ -51,8 +51,11 @@ import java.util.Map;
|
|||
|
||||
public final class OnlineModeFix {
|
||||
public static URLConnection openConnection(URL address, Proxy proxy) throws IOException {
|
||||
// we start with "http://www.minecraft.net/game/joinserver.jsp?user=..."
|
||||
if (!(address.getHost().equals("www.minecraft.net") && address.getPath().equals("/game/joinserver.jsp"))) {
|
||||
// We start with "http://www.minecraft.net/game/joinserver.jsp?user=..."
|
||||
// Or, from Beta 1.8 onward, "http://session.minecraft.net/game/joinserver.jsp?user=..."
|
||||
String host = address.getHost();
|
||||
if (!((host.equals("www.minecraft.net") || host.equals("session.minecraft.net")) &&
|
||||
address.getPath().equals("/game/joinserver.jsp"))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -81,7 +84,16 @@ public final class OnlineModeFix {
|
|||
|
||||
// sessionId has the form:
|
||||
// token:<accessToken>:<player UUID>
|
||||
String accessToken = sessionId.split(":")[1];
|
||||
// or, as of Minecraft release 1.3.1, it may be URL encoded:
|
||||
// token%3A<accessToken>%3A<player UUID>
|
||||
String accessToken;
|
||||
if (sessionId.contains(":")) {
|
||||
accessToken = sessionId.split(":")[1];
|
||||
} else if (sessionId.contains("%3A")) {
|
||||
accessToken = sessionId.split("%3A")[1];
|
||||
} else {
|
||||
throw new AssertionError("invalid sessionId");
|
||||
}
|
||||
|
||||
String uuid = null;
|
||||
uuid = MojangApi.getUuid(user, proxy);
|
||||
|
|
Loading…
Add table
Reference in a new issue