added skin manage dialog

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97
2023-09-05 00:18:36 +03:00
parent 9ad029e028
commit c86b8b0f70
33 changed files with 1425 additions and 515 deletions

View File

@ -1,21 +0,0 @@
#pragma once
#include "net/NetRequest.h"
class CapeChange : public Net::NetRequest {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<CapeChange>;
CapeChange(QString token, QString capeId);
virtual ~CapeChange() = default;
static CapeChange::Ptr make(QString token, QString capeId);
void init() override;
protected:
virtual QNetworkReply* getReply(QNetworkRequest&) override;
private:
QString m_capeId;
QString m_token;
};

View File

@ -1,20 +0,0 @@
#pragma once
#include "net/NetRequest.h"
class SkinDelete : public Net::NetRequest {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<SkinDelete>;
SkinDelete(QString token);
virtual ~SkinDelete() = default;
static SkinDelete::Ptr make(QString token);
void init() override;
protected:
virtual QNetworkReply* getReply(QNetworkRequest&) override;
private:
QString m_token;
};

View File

@ -1,25 +0,0 @@
#pragma once
#include "net/NetRequest.h"
class SkinUpload : public Net::NetRequest {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<SkinUpload>;
enum Model { STEVE, ALEX };
// Note this class takes ownership of the file.
SkinUpload(QString token, QByteArray skin, Model model = STEVE);
virtual ~SkinUpload() = default;
static SkinUpload::Ptr make(QString token, QByteArray skin, Model model = STEVE);
void init() override;
protected:
virtual QNetworkReply* getReply(QNetworkRequest&) override;
private:
Model m_model;
QByteArray m_skin;
QString m_token;
};

View File

@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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
@ -42,7 +43,7 @@
CapeChange::CapeChange(QString token, QString cape) : NetRequest(), m_capeId(cape), m_token(token)
{
logCat = taskMCServicesLogC;
logCat = taskMCSkinsLogC;
};
QNetworkReply* CapeChange::getReply(QNetworkRequest& request)
@ -52,7 +53,7 @@ QNetworkReply* CapeChange::getReply(QNetworkRequest& request)
return m_network->deleteResource(request);
} else {
setStatus(tr("Equipping cape"));
return m_network->post(request, QString("{\"capeId\":\"%1\"}").arg(m_capeId).toUtf8());
return m_network->put(request, QString("{\"capeId\":\"%1\"}").arg(m_capeId).toUtf8());
}
}
@ -67,6 +68,7 @@ CapeChange::Ptr CapeChange::make(QString token, QString capeId)
{
auto up = makeShared<CapeChange>(token, capeId);
up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active");
up->setObjectName(QString("BYTES:") + up->m_url.toString());
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
return up;
}

View File

@ -0,0 +1,39 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 "net/NetRequest.h"
class CapeChange : public Net::NetRequest {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<CapeChange>;
CapeChange(QString token, QString capeId);
virtual ~CapeChange() = default;
static CapeChange::Ptr make(QString token, QString capeId);
void init() override;
protected:
virtual QNetworkReply* getReply(QNetworkRequest&) override;
private:
QString m_capeId;
QString m_token;
};

View File

@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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
@ -40,7 +41,7 @@
SkinDelete::SkinDelete(QString token) : NetRequest(), m_token(token)
{
logCat = taskMCServicesLogC;
logCat = taskMCSkinsLogC;
};
QNetworkReply* SkinDelete::getReply(QNetworkRequest& request)

View File

@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 "net/NetRequest.h"
class SkinDelete : public Net::NetRequest {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<SkinDelete>;
SkinDelete(QString token);
virtual ~SkinDelete() = default;
static SkinDelete::Ptr make(QString token);
void init() override;
protected:
virtual QNetworkReply* getReply(QNetworkRequest&) override;
private:
QString m_token;
};

View File

@ -0,0 +1,393 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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/>.
*/
#include "SkinList.h"
#include <QFileInfo>
#include <QMimeData>
#include "FileSystem.h"
#include "Json.h"
#include "minecraft/skins/SkinModel.h"
SkinList::SkinList(QObject* parent, QString path, MinecraftAccountPtr acct) : QAbstractListModel(parent), m_acct(acct)
{
FS::ensureFolderPathExists(m_dir.absolutePath());
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
m_watcher.reset(new QFileSystemWatcher(this));
is_watching = false;
connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &SkinList::directoryChanged);
connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &SkinList::fileChanged);
directoryChanged(path);
}
void SkinList::startWatching()
{
if (is_watching) {
return;
}
update();
is_watching = m_watcher->addPath(m_dir.absolutePath());
if (is_watching) {
qDebug() << "Started watching " << m_dir.absolutePath();
} else {
qDebug() << "Failed to start watching " << m_dir.absolutePath();
}
}
void SkinList::stopWatching()
{
save();
if (!is_watching) {
return;
}
is_watching = !m_watcher->removePath(m_dir.absolutePath());
if (!is_watching) {
qDebug() << "Stopped watching " << m_dir.absolutePath();
} else {
qDebug() << "Failed to stop watching " << m_dir.absolutePath();
}
}
bool SkinList::update()
{
QVector<SkinModel> newSkins;
m_dir.refresh();
auto manifestInfo = QFileInfo(m_dir.absoluteFilePath("index.json"));
if (manifestInfo.exists()) {
try {
auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "SkinList JSON file");
const auto root = doc.object();
auto skins = Json::ensureArray(root, "skins");
for (auto jSkin : skins) {
SkinModel s(m_dir, Json::ensureObject(jSkin));
if (s.isValid()) {
newSkins << s;
}
}
} catch (const Exception& e) {
qCritical() << "Couldn't load skins json:" << e.cause();
}
} else {
newSkins = loadMinecraftSkins();
}
bool needsSave = false;
const auto& skin = m_acct->accountData()->minecraftProfile.skin;
if (!skin.url.isEmpty() && !skin.data.isEmpty()) {
QPixmap skinTexture;
SkinModel* nskin = nullptr;
for (auto i = 0; i < newSkins.size(); i++) {
if (newSkins[i].getURL() == skin.url) {
nskin = &newSkins[i];
break;
}
}
if (!nskin) {
auto name = m_acct->profileName() + ".png";
if (QFileInfo(m_dir.absoluteFilePath(name)).exists()) {
name = QUrl(skin.url).fileName() + ".png";
}
auto path = m_dir.absoluteFilePath(name);
if (skinTexture.loadFromData(skin.data, "PNG") && skinTexture.save(path)) {
SkinModel s(path);
s.setModel(SkinModel::CLASSIC); // maybe better model detection
s.setCapeId(m_acct->accountData()->minecraftProfile.currentCape);
s.setURL(skin.url);
newSkins << s;
needsSave = true;
}
} else {
nskin->setCapeId(m_acct->accountData()->minecraftProfile.currentCape);
}
}
auto folderContents = m_dir.entryInfoList();
// if there are any untracked files...
for (QFileInfo entry : folderContents) {
if (!entry.isFile() && entry.suffix() != "png")
continue;
SkinModel w(entry.absoluteFilePath());
if (w.isValid()) {
auto add = true;
for (auto s : newSkins) {
if (s.name() == w.name()) {
add = false;
break;
}
}
if (add) {
newSkins.append(w);
needsSave = true;
}
}
}
std::sort(newSkins.begin(), newSkins.end(),
[](const SkinModel& a, const SkinModel& b) { return a.getPath().localeAwareCompare(b.getPath()) < 0; });
beginResetModel();
m_skin_list.swap(newSkins);
endResetModel();
if (needsSave)
save();
return true;
}
void SkinList::directoryChanged(const QString& path)
{
QDir new_dir(path);
if (!new_dir.exists())
if (!FS::ensureFolderPathExists(new_dir.absolutePath()))
return;
if (m_dir.absolutePath() != new_dir.absolutePath()) {
m_dir.setPath(path);
m_dir.refresh();
if (is_watching)
stopWatching();
startWatching();
}
update();
}
void SkinList::fileChanged(const QString& path)
{
qDebug() << "Checking " << path;
QFileInfo checkfile(path);
if (!checkfile.exists())
return;
for (int i = 0; i < m_skin_list.count(); i++) {
if (m_skin_list[i].getPath() == checkfile.absoluteFilePath()) {
m_skin_list[i].refresh();
dataChanged(index(i), index(i));
break;
}
}
}
QStringList SkinList::mimeTypes() const
{
return { "text/uri-list" };
}
Qt::DropActions SkinList::supportedDropActions() const
{
return Qt::CopyAction;
}
bool SkinList::dropMimeData(const QMimeData* data,
Qt::DropAction action,
[[maybe_unused]] int row,
[[maybe_unused]] int column,
[[maybe_unused]] const QModelIndex& parent)
{
if (action == Qt::IgnoreAction)
return true;
// check if the action is supported
if (!data || !(action & supportedDropActions()))
return false;
// files dropped from outside?
if (data->hasUrls()) {
auto urls = data->urls();
QStringList iconFiles;
for (auto url : urls) {
// only local files may be dropped...
if (!url.isLocalFile())
continue;
iconFiles += url.toLocalFile();
}
installSkins(iconFiles);
return true;
}
return false;
}
Qt::ItemFlags SkinList::flags(const QModelIndex& index) const
{
Qt::ItemFlags f = Qt::ItemIsDropEnabled | QAbstractListModel::flags(index);
if (index.isValid()) {
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
}
return f;
}
QVariant SkinList::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
int row = index.row();
if (row < 0 || row >= m_skin_list.size())
return QVariant();
auto skin = m_skin_list[row];
switch (role) {
case Qt::DecorationRole:
return skin.getTexture();
case Qt::DisplayRole:
return skin.name();
case Qt::UserRole:
return skin.name();
case Qt::EditRole:
return skin.name();
default:
return QVariant();
}
}
int SkinList::rowCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : m_skin_list.size();
}
void SkinList::installSkins(const QStringList& iconFiles)
{
for (QString file : iconFiles)
installSkin(file, {});
}
void SkinList::installSkin(const QString& file, const QString& name)
{
QFileInfo fileinfo(file);
if (!fileinfo.isReadable() || !fileinfo.isFile())
return;
if (fileinfo.suffix() != "png" && !SkinModel(fileinfo.absoluteFilePath()).isValid())
return;
QString target = FS::PathCombine(m_dir.absolutePath(), name.isEmpty() ? fileinfo.fileName() : name);
QFile::copy(file, target);
}
int SkinList::getSkinIndex(const QString& key) const
{
for (int i = 0; i < m_skin_list.count(); i++) {
if (m_skin_list[i].name() == key) {
return i;
}
}
return -1;
}
const SkinModel* SkinList::skin(const QString& key) const
{
int idx = getSkinIndex(key);
if (idx == -1)
return nullptr;
return &m_skin_list[idx];
}
SkinModel* SkinList::skin(const QString& key)
{
int idx = getSkinIndex(key);
if (idx == -1)
return nullptr;
return &m_skin_list[idx];
}
bool SkinList::deleteSkin(const QString& key, const bool trash)
{
int idx = getSkinIndex(key);
if (idx != -1) {
auto s = m_skin_list[idx];
if (trash) {
if (FS::trash(s.getPath(), nullptr)) {
m_skin_list.remove(idx);
return true;
}
} else if (QFile::remove(s.getPath())) {
m_skin_list.remove(idx);
return true;
}
}
return false;
}
void SkinList::save()
{
QJsonObject doc;
QJsonArray arr;
for (auto s : m_skin_list) {
arr << s.toJSON();
}
doc["skins"] = arr;
Json::write(doc, m_dir.absoluteFilePath("index.json"));
}
int SkinList::getSelectedAccountSkin()
{
const auto& skin = m_acct->accountData()->minecraftProfile.skin;
for (int i = 0; i < m_skin_list.count(); i++) {
if (m_skin_list[i].getURL() == skin.url) {
return i;
}
}
return -1;
}
bool SkinList::setData(const QModelIndex& idx, const QVariant& value, int role)
{
if (!idx.isValid() || role != Qt::EditRole) {
return false;
}
int row = idx.row();
if (row < 0 || row >= m_skin_list.size())
return false;
auto skin = m_skin_list[row];
auto newName = value.toString();
if (skin.name() != newName) {
skin.rename(newName);
save();
}
return true;
}
QVector<SkinModel> SkinList::loadMinecraftSkins()
{
QString partialPath;
#if defined(Q_OS_OSX)
partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support");
#elif defined(Q_OS_WIN32)
partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", "");
#else
partialPath = QDir::homePath();
#endif
QVector<SkinModel> newSkins;
auto path = FS::PathCombine(partialPath, ".minecraft", "launcher_custom_skins.json");
auto manifestInfo = QFileInfo(path);
if (!manifestInfo.exists())
return {};
try {
auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "SkinList JSON file");
const auto root = doc.object();
auto skins = Json::ensureObject(root, "customSkins");
for (auto key : skins.keys()) {
SkinModel s(m_dir, Json::ensureObject(skins, key));
if (s.isValid()) {
newSkins << s;
}
}
} catch (const Exception& e) {
qCritical() << "Couldn't load minecraft skins json:" << e.cause();
}
return newSkins;
}

View File

@ -0,0 +1,80 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 <QAbstractListModel>
#include <QDir>
#include <QFileSystemWatcher>
#include "QObjectPtr.h"
#include "SkinModel.h"
#include "minecraft/auth/MinecraftAccount.h"
class SkinList : public QAbstractListModel {
Q_OBJECT
public:
explicit SkinList(QObject* parent, QString path, MinecraftAccountPtr acct);
virtual ~SkinList() { save(); };
int getSkinIndex(const QString& key) const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex& idx, const QVariant& value, int role) override;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
virtual QStringList mimeTypes() const override;
virtual Qt::DropActions supportedDropActions() const override;
virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override;
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
bool deleteSkin(const QString& key, const bool trash);
void installSkins(const QStringList& iconFiles);
void installSkin(const QString& file, const QString& name);
const SkinModel* skin(const QString& key) const;
SkinModel* skin(const QString& key);
void startWatching();
void stopWatching();
QString getDir() const { return m_dir.absolutePath(); }
void save();
int getSelectedAccountSkin();
private:
// hide copy constructor
SkinList(const SkinList&) = delete;
// hide assign op
SkinList& operator=(const SkinList&) = delete;
QVector<SkinModel> loadMinecraftSkins();
protected slots:
void directoryChanged(const QString& path);
void fileChanged(const QString& path);
bool update();
private:
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
bool is_watching;
QVector<SkinModel> m_skin_list;
QDir m_dir;
MinecraftAccountPtr m_acct;
};

View File

@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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/>.
*/
#include "SkinModel.h"
#include <QFileInfo>
#include <QImage>
#include <QPainter>
#include <QTransform>
#include "FileSystem.h"
#include "Json.h"
SkinModel::SkinModel(QString path) : m_path(path), m_texture(path), m_model(Model::CLASSIC) {}
SkinModel::SkinModel(QDir skinDir, QJsonObject obj)
: m_cape_id(Json::ensureString(obj, "capeId")), m_model(Model::CLASSIC), m_url(Json::ensureString(obj, "url"))
{
auto name = Json::ensureString(obj, "name");
auto skinImage = Json::ensureString(obj, "skinImage");
if (!skinImage.isEmpty()) { // minecraft skin model
skinImage = skinImage.mid(22);
m_texture.loadFromData(QByteArray::fromBase64(skinImage.toUtf8()), "PNG");
auto textureId = Json::ensureString(obj, "textureId");
if (name.isEmpty()) {
name = textureId;
}
if (Json::ensureBoolean(obj, "slim", false)) {
m_model = Model::SLIM;
}
} else {
if (auto model = Json::ensureString(obj, "model"); model == "SLIM") {
m_model = Model::SLIM;
}
}
m_path = skinDir.absoluteFilePath(name) + ".png";
if (!QFileInfo(m_path).exists() && isValid()) {
m_texture.save(m_path, "PNG");
} else {
m_texture = QPixmap(m_path);
}
}
QString SkinModel::name() const
{
return QFileInfo(m_path).baseName();
}
bool SkinModel::rename(QString newName)
{
auto info = QFileInfo(m_path);
m_path = FS::PathCombine(info.absolutePath(), newName + ".png");
return FS::move(info.absoluteFilePath(), m_path);
}
QJsonObject SkinModel::toJSON() const
{
QJsonObject obj;
obj["name"] = name();
obj["capeId"] = m_cape_id;
obj["url"] = m_url;
obj["model"] = getModelString();
return obj;
}
QString SkinModel::getModelString() const
{
switch (m_model) {
case CLASSIC:
return "CLASSIC";
case SLIM:
return "SLIM";
}
return {};
}
bool SkinModel::isValid() const
{
return !m_texture.isNull() && (m_texture.size().height() == 32 || m_texture.size().height() == 64) && m_texture.size().width() == 64;
}
QPixmap SkinModel::renderFrontBody() const
{
auto isSlim = m_model == SLIM;
auto slimOffset = isSlim ? 1 : 0;
auto isOldSkin = m_texture.height() < 64;
auto head = m_texture.copy(QRect(8, 8, 16, 16));
auto torso = m_texture.copy(QRect(20, 20, 28, 32));
auto rightArm = m_texture.copy(QRect(44, 20, 48 - slimOffset, 32));
auto rightLeg = m_texture.copy(QRect(4, 20, 8, 32));
QPixmap leftArm, leftLeg;
if (isOldSkin) {
leftArm = rightArm.transformed(QTransform().scale(-1, 1));
leftLeg = rightLeg.transformed(QTransform().scale(-1, 1));
} else {
leftArm = m_texture.copy(QRect(36, 52, 40 - slimOffset, 64));
leftLeg = m_texture.copy(QRect(20, 52, 24, 64));
}
QPixmap output(16, 32);
output.fill(Qt::black);
QPainter p;
if (!p.begin(&output))
return {};
p.drawPixmap(QPoint(4, 0), head);
p.drawPixmap(QPoint(4, 8), torso);
p.drawPixmap(QPoint(12, 8), leftArm);
p.drawPixmap(QPoint(slimOffset, 8), rightArm);
p.drawPixmap(QPoint(8, 20), leftLeg);
p.drawPixmap(QPoint(4, 20), leftArm);
return output.scaled(128, 128, Qt::KeepAspectRatioByExpanding, Qt::FastTransformation);
}

View File

@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 <QDir>
#include <QJsonObject>
#include <QPixmap>
class SkinModel {
public:
enum Model { CLASSIC, SLIM };
SkinModel(QString path);
SkinModel(QDir skinDir, QJsonObject obj);
virtual ~SkinModel() = default;
QString name() const;
QString getModelString() const;
bool isValid() const;
QString getPath() const { return m_path; }
QPixmap getTexture() const { return m_texture; }
QString getCapeId() const { return m_cape_id; }
Model getModel() const { return m_model; }
QString getURL() const { return m_url; }
bool rename(QString newName);
void setCapeId(QString capeID) { m_cape_id = capeID; }
void setModel(Model model) { m_model = model; }
void setURL(QString url) { m_url = url; }
void refresh() { m_texture = QPixmap(m_path); }
QJsonObject toJSON() const;
QPixmap renderFrontBody() const;
private:
QString m_path;
QPixmap m_texture;
QString m_cape_id;
Model m_model;
QString m_url;
};

View File

@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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
@ -37,12 +38,13 @@
#include <QHttpMultiPart>
#include "FileSystem.h"
#include "net/ByteArraySink.h"
#include "net/StaticHeaderProxy.h"
SkinUpload::SkinUpload(QString token, QByteArray skin, SkinUpload::Model model) : NetRequest(), m_model(model), m_skin(skin), m_token(token)
SkinUpload::SkinUpload(QString token, SkinModel* skin) : NetRequest(), m_skin(skin), m_token(token)
{
logCat = taskMCServicesLogC;
logCat = taskUploadLogC;
};
QNetworkReply* SkinUpload::getReply(QNetworkRequest& request)
@ -52,23 +54,12 @@ QNetworkReply* SkinUpload::getReply(QNetworkRequest& request)
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);
skin.setBody(FS::read(m_skin->getPath()));
QHttpPart model;
model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"variant\""));
switch (m_model) {
default:
qDebug() << "Unknown skin type!";
emitFailed("Unknown skin type!");
return nullptr;
case SkinUpload::STEVE:
model.setBody("CLASSIC");
break;
case SkinUpload::ALEX:
model.setBody("SLIM");
break;
}
model.setBody(m_skin->getModelString().toUtf8());
multiPart->append(skin);
multiPart->append(model);
@ -83,10 +74,11 @@ void SkinUpload::init()
}));
}
SkinUpload::Ptr SkinUpload::make(QString token, QByteArray skin, SkinUpload::Model model)
SkinUpload::Ptr SkinUpload::make(QString token, SkinModel* skin)
{
auto up = makeShared<SkinUpload>(token, skin, model);
auto up = makeShared<SkinUpload>(token, skin);
up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins");
up->setObjectName(QString("BYTES:") + up->m_url.toString());
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
return up;
}

View File

@ -0,0 +1,42 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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/skins/SkinModel.h"
#include "net/NetRequest.h"
class SkinUpload : public Net::NetRequest {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<SkinUpload>;
// Note this class takes ownership of the file.
SkinUpload(QString token, SkinModel* skin);
virtual ~SkinUpload() = default;
static SkinUpload::Ptr make(QString token, SkinModel* skin);
void init() override;
protected:
virtual QNetworkReply* getReply(QNetworkRequest&) override;
private:
SkinModel* m_skin;
QString m_token;
};