diff --git a/CMakeLists.txt b/CMakeLists.txt index b7dd6ea3..d00fbd5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -256,6 +256,8 @@ logic/net/HttpMetaCache.h logic/net/HttpMetaCache.cpp logic/net/LoginTask.h logic/net/LoginTask.cpp +logic/net/SkinDownload.h +logic/net/SkinDownload.cpp # legacy instances logic/LegacyInstance.h diff --git a/MultiMC.cpp b/MultiMC.cpp index 6c3c0269..e38733a1 100644 --- a/MultiMC.cpp +++ b/MultiMC.cpp @@ -294,6 +294,7 @@ void MultiMC::initHttpMetaCache() m_metacache->addBase("versions", QDir("versions").absolutePath()); m_metacache->addBase("libraries", QDir("libraries").absolutePath()); m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath()); + m_metacache->addBase("skins", QDir("playerdata/skins").absolutePath()); m_metacache->Load(); } diff --git a/gui/logindialog.cpp b/gui/logindialog.cpp index ed3983b7..332b5d38 100644 --- a/gui/logindialog.cpp +++ b/gui/logindialog.cpp @@ -17,6 +17,13 @@ #include "ui_logindialog.h" #include "keyring.h" #include "gui/platform.h" +#include "MultiMC.h" + +#include +#include +#include +#include +#include "logic/net/HttpMetaCache.h" #include LoginDialog::LoginDialog(QWidget *parent, const QString& loginErrMsg) : @@ -51,6 +58,8 @@ LoginDialog::LoginDialog(QWidget *parent, const QString& loginErrMsg) : arg(loginErrMsg)); } + ui->lblFace->setVisible(false); + resize(minimumSizeHint()); layout()->setSizeConstraint(QLayout::SetFixedSize); Keyring * k = Keyring::instance(); @@ -151,13 +160,54 @@ void LoginDialog::userTextChanged ( const QString& user ) blockToggles = true; Keyring * k = Keyring::instance(); QStringList sl = k->getStoredAccounts("minecraft"); + bool gotFace = false; + if(sl.contains(user)) { ui->rememberUsernameCheckbox->setChecked(true); QString passwd = k->getPassword("minecraft",user); ui->rememberPasswordCheckbox->setChecked(!passwd.isEmpty()); ui->passwordTextBox->setText(passwd); + + QByteArray data; + { + auto filename = MMC->metacache()->resolveEntry("skins", "skins.json")->getFullPath(); + QFile listFile(filename); + if(!listFile.open(QIODevice::ReadOnly)) + return; + data = listFile.readAll(); + } + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + QJsonObject root = jsonDoc.object(); + QJsonObject mappings = root.value("mappings").toObject(); + + if(!mappings[user].isUndefined()) + { + QJsonArray usernames = mappings.value(user).toArray(); + if(!usernames.isEmpty()) + { + QString mapped_username = usernames[0].toString(); + + if(!mapped_username.isEmpty()) + { + QFile fskin(MMC->metacache()->resolveEntry("skins", mapped_username + ".png")->getFullPath()); + if(fskin.exists()) + { + QPixmap skin(MMC->metacache()->resolveEntry("skins", mapped_username + ".png")->getFullPath()); + QPixmap face = skin.copy(8, 8, 8, 8).scaled(48, 48, Qt::KeepAspectRatio); + + ui->lblFace->setPixmap(face); + gotFace = true; + } + } + } + } } + + if(gotFace) ui->lblFace->setVisible(true); + else ui->lblFace->setVisible(false); blockToggles = false; } diff --git a/gui/logindialog.ui b/gui/logindialog.ui index 0aaad52b..46965425 100644 --- a/gui/logindialog.ui +++ b/gui/logindialog.ui @@ -23,10 +23,34 @@ - - + + + + + 48 + 48 + + + + + 48 + 48 + + + + + 1 + 1 + + - Username: + + + + :/icons/instances/steve + + + true @@ -37,6 +61,13 @@ + + + + Username: + + + @@ -54,7 +85,7 @@ - + @@ -111,7 +142,9 @@ - + + + loginButtonBox diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 0bd6f651..b68af5fa 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -57,6 +57,8 @@ #include "logic/lists/JavaVersionList.h" #include "logic/net/LoginTask.h" +#include "logic/net/SkinDownload.h" + #include "logic/BaseInstance.h" #include "logic/InstanceFactory.h" #include "logic/MinecraftProcess.h" @@ -517,6 +519,42 @@ void MainWindow::onLoginComplete() tDialog.exec(updateTask); delete updateTask; } + + auto download = new SkinDownload(m_activeLogin.player_name); + download->start(); + + auto filename = MMC->metacache()->resolveEntry("skins", "skins.json")->getFullPath(); + QFile listFile(filename); + + // Add skin mapping + QByteArray data; + { + if(!listFile.open(QIODevice::ReadWrite)) + { + QLOG_ERROR() << "Failed to open/make skins list JSON"; + return; + } + + data = listFile.readAll(); + } + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + QJsonObject root = jsonDoc.object(); + QJsonObject mappings = root.value("mappings").toObject(); + QJsonArray usernames = mappings.value(m_activeLogin.username).toArray(); + + if(!usernames.contains(m_activeLogin.player_name)) + { + usernames.prepend(m_activeLogin.player_name); + mappings[m_activeLogin.username] = usernames; + root["mappings"] = mappings; + jsonDoc.setObject(root); + + // QJson hack - shouldn't have to clear the file every time a save happens + listFile.resize(0); + listFile.write(jsonDoc.toJson()); + } } void MainWindow::onGameUpdateComplete() diff --git a/logic/net/SkinDownload.cpp b/logic/net/SkinDownload.cpp new file mode 100644 index 00000000..fa7be2b5 --- /dev/null +++ b/logic/net/SkinDownload.cpp @@ -0,0 +1,60 @@ +#include "MultiMC.h" +#include "SkinDownload.h" +#include "DownloadJob.h" +#include + +#include +#include +#include +#include +#include +#include + +SkinDownload::SkinDownload(QString name) +{ + m_name = name; + m_entry = MMC->metacache()->resolveEntry("skins", name + ".png"); + m_entry->stale = true; + m_url = QUrl("http://skins.minecraft.net/MinecraftSkins/" + name + ".png"); +} + +void SkinDownload::start() +{ + auto job = new DownloadJob("Player skin: " + m_name); + + job->addCacheDownload(m_url, m_entry); + m_job.reset(job); + + connect(m_job.get(), SIGNAL(started()), SLOT(downloadStarted())); + connect(m_job.get(), SIGNAL(progress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); + connect(m_job.get(), SIGNAL(succeeded()), SLOT(downloadSucceeded())); + connect(m_job.get(), SIGNAL(failed()), SLOT(downloadFailed())); + + m_job->start(); +} + +void SkinDownload::downloadStarted() +{ + //QLOG_INFO() << "Started skin download for " << m_name << "."; + + emit started(); +} + +void SkinDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + emit progress(bytesReceived, bytesTotal); +} + +void SkinDownload::downloadSucceeded() +{ + //QLOG_INFO() << "Got skin for " << m_name << ", cropping."; + + emit succeeded(); +} + +void SkinDownload::downloadFailed() +{ + //QLOG_ERROR() << "Failed to download skin for: " << m_name; + + emit failed(); +} diff --git a/logic/net/SkinDownload.h b/logic/net/SkinDownload.h new file mode 100644 index 00000000..56e5c01d --- /dev/null +++ b/logic/net/SkinDownload.h @@ -0,0 +1,38 @@ +#pragma once + +#include "Download.h" +#include "HttpMetaCache.h" +#include "DownloadJob.h" +#include +#include + +class SkinDownload : public QObject +{ + Q_OBJECT + +public: + explicit SkinDownload(QString name); + QString m_name; + QUrl m_url; + MetaEntryPtr m_entry; + DownloadJobPtr m_job; + + void start(); + +protected slots: + void downloadStarted(); + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void downloadSucceeded(); + void downloadFailed(); + +signals: + void started(); + void progress(qint64 current, qint64 total); + void succeeded(); + void failed(); + +protected: + +}; + +typedef std::shared_ptr SkinDownloadPtr;