diff options
Diffstat (limited to 'logic/minecraft')
53 files changed, 5353 insertions, 19 deletions
diff --git a/logic/minecraft/RawLibrary.h b/logic/minecraft/RawLibrary.h index 7f0aed2f..62791239 100644 --- a/logic/minecraft/RawLibrary.h +++ b/logic/minecraft/RawLibrary.h @@ -8,7 +8,7 @@ #include <QUrl> #include <memory> -#include "minecraft/OneSixRule.h" +#include "OneSixRule.h" #include "minecraft/OpSys.h" #include "GradleSpecifier.h" #include "net/URLConstants.h" diff --git a/logic/minecraft/auth/AuthSession.cpp b/logic/minecraft/auth/AuthSession.cpp new file mode 100644 index 00000000..8758bfbd --- /dev/null +++ b/logic/minecraft/auth/AuthSession.cpp @@ -0,0 +1,30 @@ +#include "AuthSession.h" +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonDocument> +#include <QStringList> + +QString AuthSession::serializeUserProperties() +{ + QJsonObject userAttrs; + for (auto key : u.properties.keys()) + { + auto array = QJsonArray::fromStringList(u.properties.values(key)); + userAttrs.insert(key, array); + } + QJsonDocument value(userAttrs); + return value.toJson(QJsonDocument::Compact); + +} + +bool AuthSession::MakeOffline(QString offline_playername) +{ + if (status != PlayableOffline && status != PlayableOnline) + { + return false; + } + session = "-"; + player_name = offline_playername; + status = PlayableOffline; + return true; +} diff --git a/logic/minecraft/auth/AuthSession.h b/logic/minecraft/auth/AuthSession.h new file mode 100644 index 00000000..dede90a9 --- /dev/null +++ b/logic/minecraft/auth/AuthSession.h @@ -0,0 +1,51 @@ +#pragma once + +#include <QString> +#include <QMultiMap> +#include <memory> + +#include "multimc_logic_export.h" + +struct User +{ + QString id; + QMultiMap<QString, QString> properties; +}; + +struct MULTIMC_LOGIC_EXPORT AuthSession +{ + bool MakeOffline(QString offline_playername); + + QString serializeUserProperties(); + + enum Status + { + Undetermined, + RequiresPassword, + PlayableOffline, + PlayableOnline + } status = Undetermined; + + User u; + + // client token + QString client_token; + // account user name + QString username; + // combined session ID + QString session; + // volatile auth token + QString access_token; + // profile name + QString player_name; + // profile ID + QString uuid; + // 'legacy' or 'mojang', depending on account type + QString user_type; + // Did the auth server reply? + bool auth_server_online = false; + // Did the user request online mode? + bool wants_online = true; +}; + +typedef std::shared_ptr<AuthSession> AuthSessionPtr; diff --git a/logic/minecraft/auth/MojangAccount.cpp b/logic/minecraft/auth/MojangAccount.cpp new file mode 100644 index 00000000..69a24c09 --- /dev/null +++ b/logic/minecraft/auth/MojangAccount.cpp @@ -0,0 +1,278 @@ +/* Copyright 2013-2015 MultiMC Contributors + * + * Authors: Orochimarufan <orochimarufan.x3@gmail.com> + * + * 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 "MojangAccount.h" +#include "flows/RefreshTask.h" +#include "flows/AuthenticateTask.h" + +#include <QUuid> +#include <QJsonObject> +#include <QJsonArray> +#include <QRegExp> +#include <QStringList> +#include <QJsonDocument> + +#include <QDebug> + +MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object) +{ + // The JSON object must at least have a username for it to be valid. + if (!object.value("username").isString()) + { + qCritical() << "Can't load Mojang account info from JSON object. Username field is " + "missing or of the wrong type."; + return nullptr; + } + + QString username = object.value("username").toString(""); + QString clientToken = object.value("clientToken").toString(""); + QString accessToken = object.value("accessToken").toString(""); + + QJsonArray profileArray = object.value("profiles").toArray(); + if (profileArray.size() < 1) + { + qCritical() << "Can't load Mojang account with username \"" << username + << "\". No profiles found."; + return nullptr; + } + + QList<AccountProfile> profiles; + for (QJsonValue profileVal : profileArray) + { + QJsonObject profileObject = profileVal.toObject(); + QString id = profileObject.value("id").toString(""); + QString name = profileObject.value("name").toString(""); + bool legacy = profileObject.value("legacy").toBool(false); + if (id.isEmpty() || name.isEmpty()) + { + qWarning() << "Unable to load a profile because it was missing an ID or a name."; + continue; + } + profiles.append({id, name, legacy}); + } + + MojangAccountPtr account(new MojangAccount()); + if (object.value("user").isObject()) + { + User u; + QJsonObject userStructure = object.value("user").toObject(); + u.id = userStructure.value("id").toString(); + /* + QJsonObject propMap = userStructure.value("properties").toObject(); + for(auto key: propMap.keys()) + { + auto values = propMap.operator[](key).toArray(); + for(auto value: values) + u.properties.insert(key, value.toString()); + } + */ + account->m_user = u; + } + account->m_username = username; + account->m_clientToken = clientToken; + account->m_accessToken = accessToken; + account->m_profiles = profiles; + + // Get the currently selected profile. + QString currentProfile = object.value("activeProfile").toString(""); + if (!currentProfile.isEmpty()) + account->setCurrentProfile(currentProfile); + + return account; +} + +MojangAccountPtr MojangAccount::createFromUsername(const QString &username) +{ + MojangAccountPtr account(new MojangAccount()); + account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + account->m_username = username; + return account; +} + +QJsonObject MojangAccount::saveToJson() const +{ + QJsonObject json; + json.insert("username", m_username); + json.insert("clientToken", m_clientToken); + json.insert("accessToken", m_accessToken); + + QJsonArray profileArray; + for (AccountProfile profile : m_profiles) + { + QJsonObject profileObj; + profileObj.insert("id", profile.id); + profileObj.insert("name", profile.name); + profileObj.insert("legacy", profile.legacy); + profileArray.append(profileObj); + } + json.insert("profiles", profileArray); + + QJsonObject userStructure; + { + userStructure.insert("id", m_user.id); + /* + QJsonObject userAttrs; + for(auto key: m_user.properties.keys()) + { + auto array = QJsonArray::fromStringList(m_user.properties.values(key)); + userAttrs.insert(key, array); + } + userStructure.insert("properties", userAttrs); + */ + } + json.insert("user", userStructure); + + if (m_currentProfile != -1) + json.insert("activeProfile", currentProfile()->id); + + return json; +} + +bool MojangAccount::setCurrentProfile(const QString &profileId) +{ + for (int i = 0; i < m_profiles.length(); i++) + { + if (m_profiles[i].id == profileId) + { + m_currentProfile = i; + return true; + } + } + return false; +} + +const AccountProfile *MojangAccount::currentProfile() const +{ + if (m_currentProfile == -1) + return nullptr; + return &m_profiles[m_currentProfile]; +} + +AccountStatus MojangAccount::accountStatus() const +{ + if (m_accessToken.isEmpty()) + return NotVerified; + else + return Verified; +} + +std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session, + QString password) +{ + Q_ASSERT(m_currentTask.get() == nullptr); + + // take care of the true offline status + if (accountStatus() == NotVerified && password.isEmpty()) + { + if (session) + { + session->status = AuthSession::RequiresPassword; + fillSession(session); + } + return nullptr; + } + + if (password.isEmpty()) + { + m_currentTask.reset(new RefreshTask(this)); + } + else + { + m_currentTask.reset(new AuthenticateTask(this, password)); + } + m_currentTask->assignSession(session); + + connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); + connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + return m_currentTask; +} + +void MojangAccount::authSucceeded() +{ + auto session = m_currentTask->getAssignedSession(); + if (session) + { + session->status = + session->wants_online ? AuthSession::PlayableOnline : AuthSession::PlayableOffline; + fillSession(session); + session->auth_server_online = true; + } + m_currentTask.reset(); + emit changed(); +} + +void MojangAccount::authFailed(QString reason) +{ + auto session = m_currentTask->getAssignedSession(); + // This is emitted when the yggdrasil tasks time out or are cancelled. + // -> we treat the error as no-op + if (m_currentTask->state() == YggdrasilTask::STATE_FAILED_SOFT) + { + if (session) + { + session->status = accountStatus() == Verified ? AuthSession::PlayableOffline + : AuthSession::RequiresPassword; + session->auth_server_online = false; + fillSession(session); + } + } + else + { + m_accessToken = QString(); + emit changed(); + if (session) + { + session->status = AuthSession::RequiresPassword; + session->auth_server_online = true; + fillSession(session); + } + } + m_currentTask.reset(); +} + +void MojangAccount::fillSession(AuthSessionPtr session) +{ + // the user name. you have to have an user name + session->username = m_username; + // volatile auth token + session->access_token = m_accessToken; + // the semi-permanent client token + session->client_token = m_clientToken; + if (currentProfile()) + { + // profile name + session->player_name = currentProfile()->name; + // profile ID + session->uuid = currentProfile()->id; + // 'legacy' or 'mojang', depending on account type + session->user_type = currentProfile()->legacy ? "legacy" : "mojang"; + if (!session->access_token.isEmpty()) + { + session->session = "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id; + } + else + { + session->session = "-"; + } + } + else + { + session->player_name = "Player"; + session->session = "-"; + } + session->u = user(); +} diff --git a/logic/minecraft/auth/MojangAccount.h b/logic/minecraft/auth/MojangAccount.h new file mode 100644 index 00000000..2de0c19c --- /dev/null +++ b/logic/minecraft/auth/MojangAccount.h @@ -0,0 +1,173 @@ +/* Copyright 2013-2015 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 <QObject> +#include <QString> +#include <QList> +#include <QJsonObject> +#include <QPair> +#include <QMap> + +#include <memory> +#include "AuthSession.h" + +#include "multimc_logic_export.h" + +class Task; +class YggdrasilTask; +class MojangAccount; + +typedef std::shared_ptr<MojangAccount> MojangAccountPtr; +Q_DECLARE_METATYPE(MojangAccountPtr) + +/** + * A profile within someone's Mojang account. + * + * Currently, the profile system has not been implemented by Mojang yet, + * but we might as well add some things for it in MultiMC right now so + * we don't have to rip the code to pieces to add it later. + */ +struct AccountProfile +{ + QString id; + QString name; + bool legacy; +}; + +enum AccountStatus +{ + NotVerified, + Verified +}; + +/** + * Object that stores information about a certain Mojang account. + * + * Said information may include things such as that account's username, client token, and access + * token if the user chose to stay logged in. + */ +class MULTIMC_LOGIC_EXPORT MojangAccount : public QObject +{ + Q_OBJECT +public: /* construction */ + //! Do not copy accounts. ever. + explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete; + + //! Default constructor + explicit MojangAccount(QObject *parent = 0) : QObject(parent) {}; + + //! Creates an empty account for the specified user name. + static MojangAccountPtr createFromUsername(const QString &username); + + //! Loads a MojangAccount from the given JSON object. + static MojangAccountPtr loadFromJson(const QJsonObject &json); + + //! Saves a MojangAccount to a JSON object and returns it. + QJsonObject saveToJson() const; + +public: /* manipulation */ + /** + * Sets the currently selected profile to the profile with the given ID string. + * If profileId is not in the list of available profiles, the function will simply return + * false. + */ + bool setCurrentProfile(const QString &profileId); + + /** + * Attempt to login. Empty password means we use the token. + * If the attempt fails because we already are performing some task, it returns false. + */ + std::shared_ptr<YggdrasilTask> login(AuthSessionPtr session, + QString password = QString()); + +public: /* queries */ + const QString &username() const + { + return m_username; + } + + const QString &clientToken() const + { + return m_clientToken; + } + + const QString &accessToken() const + { + return m_accessToken; + } + + const QList<AccountProfile> &profiles() const + { + return m_profiles; + } + + const User &user() + { + return m_user; + } + + //! Returns the currently selected profile (if none, returns nullptr) + const AccountProfile *currentProfile() const; + + //! Returns whether the account is NotVerified, Verified or Online + AccountStatus accountStatus() const; + +signals: + /** + * This signal is emitted when the account changes + */ + void changed(); + + // TODO: better signalling for the various possible state changes - especially errors + +protected: /* variables */ + QString m_username; + + // Used to identify the client - the user can have multiple clients for the same account + // Think: different launchers, all connecting to the same account/profile + QString m_clientToken; + + // Blank if not logged in. + QString m_accessToken; + + // Index of the selected profile within the list of available + // profiles. -1 if nothing is selected. + int m_currentProfile = -1; + + // List of available profiles. + QList<AccountProfile> m_profiles; + + // the user structure, whatever it is. + User m_user; + + // current task we are executing here + std::shared_ptr<YggdrasilTask> m_currentTask; + +private +slots: + void authSucceeded(); + void authFailed(QString reason); + +private: + void fillSession(AuthSessionPtr session); + +public: + friend class YggdrasilTask; + friend class AuthenticateTask; + friend class ValidateTask; + friend class RefreshTask; +}; diff --git a/logic/minecraft/auth/MojangAccountList.cpp b/logic/minecraft/auth/MojangAccountList.cpp new file mode 100644 index 00000000..26cbc81a --- /dev/null +++ b/logic/minecraft/auth/MojangAccountList.cpp @@ -0,0 +1,427 @@ +/* Copyright 2013-2015 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 "MojangAccountList.h" +#include "MojangAccount.h" + +#include <QIODevice> +#include <QFile> +#include <QTextStream> +#include <QJsonDocument> +#include <QJsonArray> +#include <QJsonObject> +#include <QJsonParseError> +#include <QDir> + +#include <QDebug> + +#include <FileSystem.h> + +#define ACCOUNT_LIST_FORMAT_VERSION 2 + +MojangAccountList::MojangAccountList(QObject *parent) : QAbstractListModel(parent) +{ +} + +MojangAccountPtr MojangAccountList::findAccount(const QString &username) const +{ + for (int i = 0; i < count(); i++) + { + MojangAccountPtr account = at(i); + if (account->username() == username) + return account; + } + return nullptr; +} + +const MojangAccountPtr MojangAccountList::at(int i) const +{ + return MojangAccountPtr(m_accounts.at(i)); +} + +void MojangAccountList::addAccount(const MojangAccountPtr account) +{ + beginResetModel(); + connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); + m_accounts.append(account); + endResetModel(); + onListChanged(); +} + +void MojangAccountList::removeAccount(const QString &username) +{ + beginResetModel(); + for (auto account : m_accounts) + { + if (account->username() == username) + { + m_accounts.removeOne(account); + return; + } + } + endResetModel(); + onListChanged(); +} + +void MojangAccountList::removeAccount(QModelIndex index) +{ + beginResetModel(); + m_accounts.removeAt(index.row()); + endResetModel(); + onListChanged(); +} + +MojangAccountPtr MojangAccountList::activeAccount() const +{ + return m_activeAccount; +} + +void MojangAccountList::setActiveAccount(const QString &username) +{ + beginResetModel(); + if (username.isEmpty()) + { + m_activeAccount = nullptr; + } + else + { + for (MojangAccountPtr account : m_accounts) + { + if (account->username() == username) + m_activeAccount = account; + } + } + endResetModel(); + onActiveChanged(); +} + +void MojangAccountList::accountChanged() +{ + // the list changed. there is no doubt. + onListChanged(); +} + +void MojangAccountList::onListChanged() +{ + if (m_autosave) + // TODO: Alert the user if this fails. + saveList(); + + emit listChanged(); +} + +void MojangAccountList::onActiveChanged() +{ + if (m_autosave) + saveList(); + + emit activeAccountChanged(); +} + +int MojangAccountList::count() const +{ + return m_accounts.count(); +} + +QVariant MojangAccountList::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() > count()) + return QVariant(); + + MojangAccountPtr account = at(index.row()); + + switch (role) + { + case Qt::DisplayRole: + switch (index.column()) + { + case NameColumn: + return account->username(); + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + return account->username(); + + case PointerRole: + return qVariantFromValue(account); |
