Offline mode support, part 1
Refactor MojangAccount so it exposes a less generic interface and supports login. Hide the ugly details. Yggdrasil tasks are now only used from MojangAccount.
This commit is contained in:
		| @@ -69,10 +69,6 @@ | ||||
| #include "logic/lists/IconList.h" | ||||
| #include "logic/lists/JavaVersionList.h" | ||||
|  | ||||
| #include "logic/auth/flows/AuthenticateTask.h" | ||||
| #include "logic/auth/flows/RefreshTask.h" | ||||
| #include "logic/auth/flows/ValidateTask.h" | ||||
|  | ||||
| #include "logic/BaseInstance.h" | ||||
| #include "logic/InstanceFactory.h" | ||||
| #include "logic/MinecraftProcess.h" | ||||
| @@ -210,9 +206,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi | ||||
|  | ||||
| 			for(AccountProfile profile : account->profiles()) | ||||
| 			{ | ||||
| 				auto meta = MMC->metacache()->resolveEntry("skins", profile.name() + ".png"); | ||||
| 				auto meta = MMC->metacache()->resolveEntry("skins", profile.name + ".png"); | ||||
| 				auto action = CacheDownload::make( | ||||
| 					QUrl("http://skins.minecraft.net/MinecraftSkins/" + profile.name() + ".png"), | ||||
| 					QUrl("http://skins.minecraft.net/MinecraftSkins/" + profile.name + ".png"), | ||||
| 					meta); | ||||
| 				job->addNetAction(action); | ||||
| 				meta->stale = true; | ||||
| @@ -310,9 +306,9 @@ void MainWindow::repopulateAccountsMenu() | ||||
| 			section->setEnabled(false); | ||||
| 			accountMenu->addAction(section); | ||||
|  | ||||
| 			for (AccountProfile profile : account->profiles()) | ||||
| 			for (auto profile : account->profiles()) | ||||
| 			{ | ||||
| 				QAction *action = new QAction(profile.name(), this); | ||||
| 				QAction *action = new QAction(profile.name, this); | ||||
| 				action->setData(account->username()); | ||||
| 				action->setCheckable(true); | ||||
| 				if(active_username == account->username()) | ||||
| @@ -320,7 +316,7 @@ void MainWindow::repopulateAccountsMenu() | ||||
| 					action->setChecked(true); | ||||
| 				} | ||||
|  | ||||
| 				action->setIcon(SkinUtils::getFaceFromCache(profile.name())); | ||||
| 				action->setIcon(SkinUtils::getFaceFromCache(profile.name)); | ||||
| 				accountMenu->addAction(action); | ||||
| 				connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); | ||||
| 			} | ||||
| @@ -378,7 +374,7 @@ void MainWindow::activeAccountChanged() | ||||
| 		const AccountProfile *profile = account->currentProfile(); | ||||
| 		if (profile != nullptr) | ||||
| 		{ | ||||
| 			accountMenuButton->setIcon(SkinUtils::getFaceFromCache(profile->name())); | ||||
| 			accountMenuButton->setIcon(SkinUtils::getFaceFromCache(profile->name)); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| @@ -790,6 +786,7 @@ void MainWindow::doLaunch() | ||||
| void MainWindow::doLaunchInst(BaseInstance* instance, MojangAccountPtr account) | ||||
| { | ||||
| 	// We'll need to validate the access token to make sure the account is still logged in. | ||||
| 	/* | ||||
| 	ProgressDialog progDialog(this); | ||||
| 	RefreshTask refreshtask(account, &progDialog); | ||||
| 	progDialog.exec(&refreshtask); | ||||
| @@ -829,10 +826,12 @@ void MainWindow::doLaunchInst(BaseInstance* instance, MojangAccountPtr account) | ||||
| 				QMessageBox::Warning, QMessageBox::Ok)->exec(); | ||||
| 		} | ||||
| 	} | ||||
| 	*/ | ||||
| } | ||||
|  | ||||
| bool MainWindow::doRefreshToken(MojangAccountPtr account, const QString& errorMsg) | ||||
| { | ||||
| 	/* | ||||
| 	EditAccountDialog passDialog(errorMsg, this, EditAccountDialog::PasswordField); | ||||
| 	if (passDialog.exec() == QDialog::Accepted) | ||||
| 	{ | ||||
| @@ -848,7 +847,8 @@ bool MainWindow::doRefreshToken(MojangAccountPtr account, const QString& errorMs | ||||
| 			return doRefreshToken(account, authTask.failReason()); | ||||
| 		} | ||||
| 	} | ||||
| 	else return false; | ||||
| 	else return false;*/ | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| void MainWindow::prepareLaunch(BaseInstance* instance, MojangAccountPtr account) | ||||
|   | ||||
| @@ -20,7 +20,6 @@ | ||||
|  | ||||
| #include <logger/QsLog.h> | ||||
|  | ||||
| #include <logic/auth/flows/AuthenticateTask.h> | ||||
| #include <logic/net/NetJob.h> | ||||
|  | ||||
| #include <gui/dialogs/EditAccountDialog.h> | ||||
| @@ -117,8 +116,8 @@ void AccountListDialog::addAccount(const QString& errMsg) | ||||
| 		QString username(loginDialog.username()); | ||||
| 		QString password(loginDialog.password()); | ||||
|  | ||||
| 		MojangAccountPtr account = MojangAccountPtr(new MojangAccount(username)); | ||||
|  | ||||
| 		MojangAccountPtr account = MojangAccount::createFromUsername(username); | ||||
| /* | ||||
| 		ProgressDialog progDialog(this); | ||||
| 		AuthenticateTask authTask(account, password, &progDialog); | ||||
| 		if (progDialog.exec(&authTask)) | ||||
| @@ -132,9 +131,9 @@ void AccountListDialog::addAccount(const QString& errMsg) | ||||
|  | ||||
| 			for(AccountProfile profile : account->profiles()) | ||||
| 			{ | ||||
| 				auto meta = MMC->metacache()->resolveEntry("skins", profile.name() + ".png"); | ||||
| 				auto meta = MMC->metacache()->resolveEntry("skins", profile.name + ".png"); | ||||
| 				auto action = CacheDownload::make( | ||||
| 					QUrl("http://skins.minecraft.net/MinecraftSkins/" + profile.name() + ".png"), | ||||
| 					QUrl("http://skins.minecraft.net/MinecraftSkins/" + profile.name + ".png"), | ||||
| 					meta); | ||||
| 				job->addNetAction(action); | ||||
| 				meta->stale = true; | ||||
| @@ -142,5 +141,6 @@ void AccountListDialog::addAccount(const QString& errMsg) | ||||
|  | ||||
| 			job->start(); | ||||
| 		} | ||||
| 		*/ | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -20,8 +20,6 @@ | ||||
|  | ||||
| #include <logger/QsLog.h> | ||||
|  | ||||
| #include <logic/auth/flows/AuthenticateTask.h> | ||||
|  | ||||
| #include <gui/dialogs/ProgressDialog.h> | ||||
|  | ||||
| #include <MultiMC.h> | ||||
|   | ||||
| @@ -105,7 +105,7 @@ MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account) | ||||
| #endif | ||||
|  | ||||
| 		args << "-jar" << LAUNCHER_FILE; | ||||
| 		args << account->currentProfile()->name(); | ||||
| 		args << account->currentProfile()->name; | ||||
| 		args << account->sessionId(); | ||||
| 		args << windowTitle; | ||||
| 		args << windowSize; | ||||
|   | ||||
| @@ -75,20 +75,22 @@ QString MinecraftProcess::censorPrivateInfo(QString in) | ||||
| { | ||||
| 	if(!m_account) | ||||
| 		return in; | ||||
| 	else | ||||
|  | ||||
| 	QString sessionId = m_account->sessionId(); | ||||
| 	QString accessToken = m_account->accessToken(); | ||||
| 	QString clientToken = m_account->clientToken(); | ||||
| 	in.replace(sessionId, "<SESSION ID>"); | ||||
| 	in.replace(accessToken, "<ACCESS TOKEN>"); | ||||
| 	in.replace(clientToken, "<CLIENT TOKEN>"); | ||||
| 	auto profile = m_account->currentProfile(); | ||||
| 	if(profile) | ||||
| 	{ | ||||
| 		QString sessionId = m_account->sessionId(); | ||||
| 		QString accessToken = m_account->accessToken(); | ||||
| 		QString clientToken = m_account->clientToken(); | ||||
| 		QString profileId = m_account->currentProfile()->id(); | ||||
| 		QString profileName = m_account->currentProfile()->name(); | ||||
| 		in.replace(sessionId, "<SESSION ID>"); | ||||
| 		in.replace(accessToken, "<ACCESS TOKEN>"); | ||||
| 		in.replace(clientToken, "<CLIENT TOKEN>"); | ||||
| 		QString profileId = profile->id; | ||||
| 		QString profileName = profile->name; | ||||
| 		in.replace(profileId, "<PROFILE ID>"); | ||||
| 		in.replace(profileName, "<PROFILE NAME>"); | ||||
| 		return in; | ||||
| 	} | ||||
| 	return in; | ||||
| } | ||||
|  | ||||
| // console window | ||||
|   | ||||
| @@ -77,8 +77,8 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account) | ||||
| 	token_mapping["auth_username"] = account->username(); | ||||
| 	token_mapping["auth_session"] = account->sessionId(); | ||||
| 	token_mapping["auth_access_token"] = account->accessToken(); | ||||
| 	token_mapping["auth_player_name"] = account->currentProfile()->name(); | ||||
| 	token_mapping["auth_uuid"] = account->currentProfile()->id(); | ||||
| 	token_mapping["auth_player_name"] = account->currentProfile()->name; | ||||
| 	token_mapping["auth_uuid"] = account->currentProfile()->id; | ||||
|  | ||||
| 	// this is for offline?: | ||||
| 	/* | ||||
|   | ||||
| @@ -16,113 +16,16 @@ | ||||
|  */ | ||||
|  | ||||
| #include "MojangAccount.h" | ||||
| #include "flows/RefreshTask.h" | ||||
| #include "flows/AuthenticateTask.h" | ||||
|  | ||||
| #include <QUuid> | ||||
| #include <QJsonObject> | ||||
| #include <QJsonArray> | ||||
| #include <QRegExp> | ||||
|  | ||||
| #include <logger/QsLog.h> | ||||
|  | ||||
| MojangAccount::MojangAccount(const QString &username, QObject *parent) : QObject(parent) | ||||
| { | ||||
| 	// Generate a client token. | ||||
| 	m_clientToken = QUuid::createUuid().toString(); | ||||
|  | ||||
| 	m_username = username; | ||||
|  | ||||
| 	m_currentProfile = -1; | ||||
| } | ||||
|  | ||||
| MojangAccount::MojangAccount(const QString &username, const QString &clientToken, | ||||
| 							 const QString &accessToken, QObject *parent) | ||||
| 	: QObject(parent) | ||||
| { | ||||
| 	m_username = username; | ||||
| 	m_clientToken = clientToken; | ||||
| 	m_accessToken = accessToken; | ||||
|  | ||||
| 	m_currentProfile = -1; | ||||
| } | ||||
|  | ||||
| MojangAccount::MojangAccount(const MojangAccount &other, QObject *parent) | ||||
| { | ||||
| 	m_username = other.username(); | ||||
| 	m_clientToken = other.clientToken(); | ||||
| 	m_accessToken = other.accessToken(); | ||||
|  | ||||
| 	m_profiles = other.m_profiles; | ||||
| 	m_currentProfile = other.m_currentProfile; | ||||
| } | ||||
|  | ||||
| QString MojangAccount::username() const | ||||
| { | ||||
| 	return m_username; | ||||
| } | ||||
|  | ||||
| QString MojangAccount::clientToken() const | ||||
| { | ||||
| 	return m_clientToken; | ||||
| } | ||||
|  | ||||
| void MojangAccount::setClientToken(const QString &clientToken) | ||||
| { | ||||
| 	m_clientToken = clientToken; | ||||
| } | ||||
|  | ||||
| QString MojangAccount::accessToken() const | ||||
| { | ||||
| 	return m_accessToken; | ||||
| } | ||||
|  | ||||
| void MojangAccount::setAccessToken(const QString &accessToken) | ||||
| { | ||||
| 	m_accessToken = accessToken; | ||||
| } | ||||
|  | ||||
| QString MojangAccount::sessionId() const | ||||
| { | ||||
| 	return "token:" + m_accessToken + ":" + currentProfile()->id(); | ||||
| } | ||||
|  | ||||
| const QList<AccountProfile> MojangAccount::profiles() const | ||||
| { | ||||
| 	return m_profiles; | ||||
| } | ||||
|  | ||||
| const AccountProfile *MojangAccount::currentProfile() const | ||||
| { | ||||
| 	if (m_currentProfile < 0) | ||||
| 	{ | ||||
| 		if (m_profiles.length() > 0) | ||||
| 			return &m_profiles.at(0); | ||||
| 		else | ||||
| 			return nullptr; | ||||
| 	} | ||||
| 	else | ||||
| 		return &m_profiles.at(m_currentProfile); | ||||
| } | ||||
|  | ||||
| bool MojangAccount::setProfile(const QString &profileId) | ||||
| { | ||||
| 	const QList<AccountProfile> &profiles = this->profiles(); | ||||
| 	for (int i = 0; i < profiles.length(); i++) | ||||
| 	{ | ||||
| 		if (profiles.at(i).id() == profileId) | ||||
| 		{ | ||||
| 			m_currentProfile = i; | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| void MojangAccount::loadProfiles(const ProfileList &profiles) | ||||
| { | ||||
| 	m_profiles.clear(); | ||||
| 	for (auto profile : profiles) | ||||
| 		m_profiles.append(profile); | ||||
| } | ||||
|  | ||||
| MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object) | ||||
| { | ||||
| 	// The JSON object must at least have a username for it to be valid. | ||||
| @@ -143,7 +46,7 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object) | ||||
| 		return nullptr; | ||||
| 	} | ||||
|  | ||||
| 	ProfileList profiles; | ||||
| 	QList<AccountProfile> profiles; | ||||
| 	for (QJsonValue profileVal : profileArray) | ||||
| 	{ | ||||
| 		QJsonObject profileObject = profileVal.toObject(); | ||||
| @@ -154,67 +57,112 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object) | ||||
| 			QLOG_WARN() << "Unable to load a profile because it was missing an ID or a name."; | ||||
| 			continue; | ||||
| 		} | ||||
| 		profiles.append(AccountProfile(id, name)); | ||||
| 		profiles.append({id, name}); | ||||
| 	} | ||||
|  | ||||
| 	MojangAccountPtr account(new MojangAccount(username, clientToken, accessToken)); | ||||
| 	account->loadProfiles(profiles); | ||||
| 	MojangAccountPtr account(new MojangAccount()); | ||||
| 	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->setProfile(currentProfile); | ||||
| 		account->setCurrentProfile(currentProfile); | ||||
|  | ||||
| 	return account; | ||||
| } | ||||
|  | ||||
| QJsonObject MojangAccount::saveToJson() | ||||
| 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", username()); | ||||
| 	json.insert("clientToken", clientToken()); | ||||
| 	json.insert("accessToken", accessToken()); | ||||
| 	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("id", profile.id); | ||||
| 		profileObj.insert("name", profile.name); | ||||
| 		profileArray.append(profileObj); | ||||
| 	} | ||||
| 	json.insert("profiles", profileArray); | ||||
|  | ||||
| 	if (currentProfile() != nullptr) | ||||
| 		json.insert("activeProfile", currentProfile()->id()); | ||||
| 	if (m_currentProfile != -1) | ||||
| 		json.insert("activeProfile", currentProfile()->id); | ||||
|  | ||||
| 	return json; | ||||
| } | ||||
|  | ||||
|  | ||||
| AccountProfile::AccountProfile(const QString& id, const QString& name) | ||||
| bool MojangAccount::setCurrentProfile(const QString &profileId) | ||||
| { | ||||
| 	m_id = id; | ||||
| 	m_name = name; | ||||
| 	for (int i = 0; i < m_profiles.length(); i++) | ||||
| 	{ | ||||
| 		if (m_profiles[i].id == profileId) | ||||
| 		{ | ||||
| 			m_currentProfile = i; | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| AccountProfile::AccountProfile(const AccountProfile &other) | ||||
| const AccountProfile* MojangAccount::currentProfile() const | ||||
| { | ||||
| 	m_id = other.m_id; | ||||
| 	m_name = other.m_name; | ||||
| 	if(m_currentProfile == -1) | ||||
| 		return nullptr; | ||||
| 	return &m_profiles[m_currentProfile]; | ||||
| } | ||||
|  | ||||
| QString AccountProfile::id() const | ||||
| AccountStatus MojangAccount::accountStatus() const | ||||
| { | ||||
| 	return m_id; | ||||
| 	if(m_accessToken.isEmpty()) | ||||
| 		return NotVerified; | ||||
| 	if(!m_online) | ||||
| 		return Verified; | ||||
| 	return Online; | ||||
| } | ||||
|  | ||||
| QString AccountProfile::name() const | ||||
| bool MojangAccount::login(QString password) | ||||
| { | ||||
| 	return m_name; | ||||
| 	if(m_currentTask) | ||||
| 		return false; | ||||
| 	if(password.isEmpty()) | ||||
| 	{ | ||||
| 		m_currentTask.reset(new RefreshTask(this, this)); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		m_currentTask.reset(new AuthenticateTask(this, password, this)); | ||||
| 	} | ||||
| 	connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); | ||||
| 	connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); | ||||
| 	m_currentTask->start(); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| void MojangAccount::propagateChange() | ||||
| void MojangAccount::authSucceeded() | ||||
| { | ||||
| 	m_online = true; | ||||
| 	m_currentTask.reset(); | ||||
| 	emit changed(); | ||||
| } | ||||
|  | ||||
| void MojangAccount::authFailed(QString reason) | ||||
| { | ||||
| 	m_online = false; | ||||
| 	m_accessToken = QString(); | ||||
| 	m_currentTask.reset(); | ||||
| 	emit changed(); | ||||
| } | ||||
|   | ||||
| @@ -23,34 +23,25 @@ | ||||
|  | ||||
| #include <memory> | ||||
|  | ||||
| class YggdrasilTask; | ||||
| class MojangAccount; | ||||
|  | ||||
| typedef std::shared_ptr<MojangAccount> MojangAccountPtr; | ||||
| Q_DECLARE_METATYPE(MojangAccountPtr) | ||||
|  | ||||
| /** | ||||
|  * Class that represents a profile within someone's Mojang account. | ||||
|  * 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. | ||||
|  */ | ||||
| class AccountProfile | ||||
| struct AccountProfile | ||||
| { | ||||
| public: | ||||
| 	AccountProfile(const QString &id, const QString &name); | ||||
| 	AccountProfile(const AccountProfile &other); | ||||
|  | ||||
| 	QString id() const; | ||||
| 	QString name() const; | ||||
|  | ||||
| protected: | ||||
| 	QString m_id; | ||||
| 	QString m_name; | ||||
| 	QString id; | ||||
| 	QString name; | ||||
| }; | ||||
|  | ||||
| typedef QList<AccountProfile> ProfileList; | ||||
|  | ||||
| struct User | ||||
| { | ||||
| 	QString id; | ||||
| @@ -59,6 +50,13 @@ struct User | ||||
| 	QList<QPair<QString, QString>> properties; | ||||
| }; | ||||
|  | ||||
| enum AccountStatus | ||||
| { | ||||
| 	NotVerified, | ||||
| 	Verified, | ||||
| 	Online | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Object that stores information about a certain Mojang account. | ||||
|  * | ||||
| @@ -68,106 +66,112 @@ struct User | ||||
| class MojangAccount : public QObject | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
| 	/** | ||||
| 	 * Constructs a new MojangAccount with the given username. | ||||
| 	 * The client token will be generated automatically and the access token will be blank. | ||||
| 	 */ | ||||
| 	explicit MojangAccount(const QString &username, QObject *parent = 0); | ||||
| public: /* construction */ | ||||
| 	//! Do not copy accounts. ever. | ||||
| 	explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete; | ||||
|  | ||||
| 	/** | ||||
| 	 * Constructs a new MojangAccount with the given username, client token, and access token. | ||||
| 	 */ | ||||
| 	explicit MojangAccount(const QString &username, const QString &clientToken, | ||||
| 						   const QString &accessToken, QObject *parent = 0); | ||||
| 	//! Default constructor | ||||
| 	explicit MojangAccount(QObject *parent = 0) : QObject(parent) {}; | ||||
|  | ||||
| 	/** | ||||
| 	 * Constructs a new MojangAccount matching the given account. | ||||
| 	 */ | ||||
| 	MojangAccount(const MojangAccount &other, 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. | ||||
| 	 */ | ||||
| 	//! 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(); | ||||
|  | ||||
| 	/** | ||||
| 	 * Update the account on disk and lists (it changed, for whatever reason) | ||||
| 	 * This is called by various Yggdrasil tasks. | ||||
| 	 */ | ||||
| 	void propagateChange(); | ||||
|  | ||||
| 	/** | ||||
| 	 * This MojangAccount's username. May be an email address if the account is migrated. | ||||
| 	 */ | ||||
| 	QString username() const; | ||||
|  | ||||
| 	/** | ||||
| 	 * This MojangAccount's client token. This is a UUID used by Mojang's auth servers to identify this client. | ||||
| 	 * This is unique for each MojangAccount. | ||||
| 	 */ | ||||
| 	QString clientToken() const; | ||||
|  | ||||
| 	/** | ||||
| 	 * Sets the MojangAccount's client token to the given value. | ||||
| 	 */ | ||||
| 	void setClientToken(const QString &token); | ||||
|  | ||||
| 	/** | ||||
| 	 * This MojangAccount's access token. | ||||
| 	 * If the user has not chosen to stay logged in, this will be an empty string. | ||||
| 	 */ | ||||
| 	QString accessToken() const; | ||||
|  | ||||
| 	/** | ||||
| 	 * Changes this MojangAccount's access token to the given value. | ||||
| 	 */ | ||||
| 	void setAccessToken(const QString &token); | ||||
|  | ||||
| 	/** | ||||
| 	 * Get full session ID | ||||
| 	 */ | ||||
| 	QString sessionId() const; | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns a list of the available account profiles. | ||||
| 	 */ | ||||
| 	const ProfileList profiles() const; | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns a pointer to the currently selected profile. | ||||
| 	 * If no profile is selected, returns the first profile in the profile list or nullptr if there are none. | ||||
| 	 */ | ||||
| 	const AccountProfile *currentProfile() const; | ||||
| 	//! 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. | ||||
| 	 * If profileId is not in the list of available profiles, the function will simply return | ||||
| 	 * false. | ||||
| 	 */ | ||||
| 	bool setProfile(const QString &profileId); | ||||
| 	bool setCurrentProfile(const QString &profileId); | ||||
|  | ||||
| 	/** | ||||
| 	 * Clears the current account profile list and replaces it with the given profile list. | ||||
| 	 * Attempt to login. Empty password means we use the token. | ||||
| 	 * If the attempt fails because we already are performing some task, it returns false. | ||||
| 	 */ | ||||
| 	void loadProfiles(const ProfileList &profiles); | ||||
| 	bool login(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; | ||||
| 	} | ||||
|  | ||||
| 	//! Get the session ID required for legacy Minecraft versions | ||||
| 	QString sessionId() const | ||||
| 	{ | ||||
| 		if (m_currentProfile != -1 && !m_accessToken.isEmpty()) | ||||
| 			return "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id; | ||||
| 		return "-"; | ||||
| 	} | ||||
|  | ||||
| 	//! 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 isgnal is emitted whrn the account changes | ||||
| 	 * This signal is emitted when the account changes | ||||
| 	 */ | ||||
| 	void changed(); | ||||
|  | ||||
| protected: | ||||
| 	// 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; | ||||
| 	QString m_accessToken;  // Blank if not logged in. | ||||
| 	int m_currentProfile;   // Index of the selected profile within the list of available | ||||
| 							// profiles. -1 if nothing is selected. | ||||
| 	ProfileList m_profiles; // List of available profiles. | ||||
| 	User m_user;			// the user structure, whatever it is. | ||||
|  | ||||
| 	// 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; | ||||
|  | ||||
| 	// true when the account is verified | ||||
| 	bool m_online = false; | ||||
|  | ||||
| 	// current task we are executing here | ||||
| 	std::shared_ptr<YggdrasilTask> m_currentTask; | ||||
|  | ||||
| private slots: | ||||
| 	void authSucceeded(); | ||||
| 	void authFailed(QString reason); | ||||
|  | ||||
| public: | ||||
| 	friend class YggdrasilTask; | ||||
| 	friend class AuthenticateTask; | ||||
| 	friend class ValidateTask; | ||||
| 	friend class RefreshTask; | ||||
| }; | ||||
|   | ||||
| @@ -25,10 +25,9 @@ | ||||
| #include <MultiMC.h> | ||||
| #include <logic/auth/MojangAccount.h> | ||||
|  | ||||
| YggdrasilTask::YggdrasilTask(MojangAccountPtr account, QObject *parent) : Task(parent) | ||||
| YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent) | ||||
| 	: Task(parent), m_account(account) | ||||
| { | ||||
| 	m_error = nullptr; | ||||
| 	m_account = account; | ||||
| } | ||||
|  | ||||
| YggdrasilTask::~YggdrasilTask() | ||||
| @@ -81,8 +80,9 @@ void YggdrasilTask::processReply(QNetworkReply *reply) | ||||
|  | ||||
| 		if (responseCode == 200) | ||||
| 		{ | ||||
| 			// If the response code was 200, then there shouldn't be an error. Make sure anyways. | ||||
| 			// Also, sometimes an empty reply indicates success. If there was no data received,  | ||||
| 			// If the response code was 200, then there shouldn't be an error. Make sure | ||||
| 			// anyways. | ||||
| 			// Also, sometimes an empty reply indicates success. If there was no data received, | ||||
| 			// pass an empty json object to the processResponse function. | ||||
| 			if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) | ||||
| 			{ | ||||
| @@ -102,25 +102,34 @@ void YggdrasilTask::processReply(QNetworkReply *reply) | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				emitFailed(tr("Failed to parse Yggdrasil JSON response: %1 at offset %2.").arg(jsonError.errorString()).arg(jsonError.offset)); | ||||
| 				emitFailed(tr("Failed to parse Yggdrasil JSON response: %1 at offset %2.") | ||||
| 							   .arg(jsonError.errorString()) | ||||
| 							   .arg(jsonError.offset)); | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			// If the response code was not 200, then Yggdrasil may have given us information about the error. | ||||
| 			// If we can parse the response, then get information from it. Otherwise just say there was an unknown error. | ||||
| 			// If the response code was not 200, then Yggdrasil may have given us information | ||||
| 			// about the error. | ||||
| 			// If we can parse the response, then get information from it. Otherwise just say | ||||
| 			// there was an unknown error. | ||||
| 			if (jsonError.error == QJsonParseError::NoError) | ||||
| 			{ | ||||
| 				// We were able to parse the server's response. Woo! | ||||
| 				// Call processError. If a subclass has overridden it then they'll handle their stuff there. | ||||
| 				QLOG_DEBUG() << "The request failed, but the server gave us an error message. Processing error."; | ||||
| 				// Call processError. If a subclass has overridden it then they'll handle their | ||||
| 				// stuff there. | ||||
| 				QLOG_DEBUG() << "The request failed, but the server gave us an error message. " | ||||
| 								"Processing error."; | ||||
| 				emitFailed(processError(doc.object())); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				// The server didn't say anything regarding the error. Give the user an unknown error. | ||||
| 				QLOG_DEBUG() << "The request failed and the server gave no error message. Unknown error."; | ||||
| 				emitFailed(tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(reply->errorString())); | ||||
| 				// The server didn't say anything regarding the error. Give the user an unknown | ||||
| 				// error. | ||||
| 				QLOG_DEBUG() << "The request failed and the server gave no error message. " | ||||
| 								"Unknown error."; | ||||
| 				emitFailed(tr("An unknown error occurred when trying to communicate with the " | ||||
| 							  "authentication server: %1").arg(reply->errorString())); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @@ -161,8 +170,3 @@ YggdrasilTask::Error *YggdrasilTask::getError() const | ||||
| { | ||||
| 	return this->m_error; | ||||
| } | ||||
|  | ||||
| MojangAccountPtr YggdrasilTask::getMojangAccount() const | ||||
| { | ||||
| 	return this->m_account; | ||||
| } | ||||
|   | ||||
| @@ -31,7 +31,7 @@ class YggdrasilTask : public Task | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
| 	explicit YggdrasilTask(MojangAccountPtr account, QObject *parent = 0); | ||||
| 	explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0); | ||||
| 	~YggdrasilTask(); | ||||
|  | ||||
| 	/** | ||||
| @@ -59,11 +59,6 @@ public: | ||||
| 		QString m_cause; | ||||
| 	}; | ||||
|  | ||||
| 	/** | ||||
| 	 * Gets the Mojang account that this task is operating on. | ||||
| 	 */ | ||||
| 	virtual MojangAccountPtr getMojangAccount() const; | ||||
|  | ||||
| 	/** | ||||
| 	 * Returns a pointer to a YggdrasilTask::Error object if an error has occurred. | ||||
| 	 * If no error has occurred, returns a null pointer. | ||||
| @@ -120,11 +115,11 @@ protected: | ||||
| 	 */ | ||||
| 	virtual QString getStateMessage(const State state) const; | ||||
|  | ||||
| 	MojangAccountPtr m_account; | ||||
| 	MojangAccount *m_account = nullptr; | ||||
|  | ||||
| 	QNetworkReply *m_netReply; | ||||
|  | ||||
| 	Error *m_error; | ||||
| 	Error *m_error = nullptr; | ||||
|  | ||||
| protected | ||||
| slots: | ||||
|   | ||||
| @@ -26,7 +26,7 @@ | ||||
|  | ||||
| #include "logger/QsLog.h" | ||||
|  | ||||
| AuthenticateTask::AuthenticateTask(MojangAccountPtr account, const QString &password, | ||||
| AuthenticateTask::AuthenticateTask(MojangAccount * account, const QString &password, | ||||
| 								   QObject *parent) | ||||
| 	: YggdrasilTask(account, parent), m_password(password) | ||||
| { | ||||
| @@ -59,14 +59,14 @@ QJsonObject AuthenticateTask::getRequestContent() const | ||||
| 		req.insert("agent", agent); | ||||
| 	} | ||||
|  | ||||
| 	req.insert("username", getMojangAccount()->username()); | ||||
| 	req.insert("username", m_account->username()); | ||||
| 	req.insert("password", m_password); | ||||
| 	req.insert("requestUser", true); | ||||
|  | ||||
| 	// If we already have a client token, give it to the server. | ||||
| 	// Otherwise, let the server give us one. | ||||
| 	if (!getMojangAccount()->clientToken().isEmpty()) | ||||
| 		req.insert("clientToken", getMojangAccount()->clientToken()); | ||||
| 	if (!m_account->m_clientToken.isEmpty()) | ||||
| 		req.insert("clientToken", m_account->m_clientToken); | ||||
|  | ||||
| 	return req; | ||||
| } | ||||
| @@ -88,8 +88,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData) | ||||
| 		QLOG_ERROR() << "Server didn't send a client token."; | ||||
| 		return false; | ||||
| 	} | ||||
| 	if (!getMojangAccount()->clientToken().isEmpty() && | ||||
| 		clientToken != getMojangAccount()->clientToken()) | ||||
| 	if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) | ||||
| 	{ | ||||
| 		// The server changed our client token! Obey its wishes, but complain. That's what I do | ||||
| 		// for my parents, so... | ||||
| @@ -97,7 +96,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData) | ||||
| 					<< "'. This shouldn't happen, but it isn't really a big deal."; | ||||
| 	} | ||||
| 	// Set the client token. | ||||
| 	getMojangAccount()->setClientToken(clientToken); | ||||
| 	m_account->m_clientToken = clientToken; | ||||
|  | ||||
| 	// Now, we set the access token. | ||||
| 	QLOG_DEBUG() << "Getting access token."; | ||||
| @@ -109,7 +108,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData) | ||||
| 		QLOG_ERROR() << "Server didn't send an access token."; | ||||
| 	} | ||||
| 	// Set the access token. | ||||
| 	getMojangAccount()->setAccessToken(accessToken); | ||||
| 	m_account->m_accessToken = accessToken; | ||||
|  | ||||
| 	// Now we load the list of available profiles. | ||||
| 	// Mojang hasn't yet implemented the profile system, | ||||
| @@ -117,7 +116,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData) | ||||
| 	// don't have trouble implementing it later. | ||||
| 	QLOG_DEBUG() << "Loading profile list."; | ||||
| 	QJsonArray availableProfiles = responseData.value("availableProfiles").toArray(); | ||||
| 	ProfileList loadedProfiles; | ||||
| 	QList<AccountProfile> loadedProfiles; | ||||
| 	for (auto iter : availableProfiles) | ||||
| 	{ | ||||
| 		QJsonObject profile = iter.toObject(); | ||||
| @@ -135,10 +134,10 @@ bool AuthenticateTask::processResponse(QJsonObject responseData) | ||||
| 		} | ||||
|  | ||||
| 		// Now, add a new AccountProfile entry to the list. | ||||
| 		loadedProfiles.append(AccountProfile(id, name)); | ||||
| 		loadedProfiles.append({id, name}); | ||||
| 	} | ||||
| 	// Put the list of profiles we loaded into the MojangAccount object. | ||||
| 	getMojangAccount()->loadProfiles(loadedProfiles); | ||||
| 	m_account->m_profiles = loadedProfiles; | ||||
|  | ||||
| 	// Finally, we set the current profile to the correct value. This is pretty simple. | ||||
| 	// We do need to make sure that the current profile that the server gave us | ||||
| @@ -153,7 +152,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData) | ||||
| 		QLOG_ERROR() << "Server didn't specify a currently selected profile."; | ||||
| 		return false; | ||||
| 	} | ||||
| 	if (!getMojangAccount()->setProfile(currentProfileId)) | ||||
| 	if (!m_account->setCurrentProfile(currentProfileId)) | ||||
| 	{ | ||||
| 		// TODO: Set an error to display to the user. | ||||
| 		QLOG_ERROR() << "Server specified a selected profile that wasn't in the available " | ||||
|   | ||||
| @@ -30,7 +30,7 @@ class AuthenticateTask : public YggdrasilTask | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
| 	AuthenticateTask(MojangAccountPtr account, const QString &password, QObject *parent = 0); | ||||
| 	AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0); | ||||
|  | ||||
| protected: | ||||
| 	virtual QJsonObject getRequestContent() const; | ||||
|   | ||||
| @@ -25,7 +25,7 @@ | ||||
|  | ||||
| #include "logger/QsLog.h" | ||||
|  | ||||
| RefreshTask::RefreshTask(MojangAccountPtr account, QObject *parent) | ||||
| RefreshTask::RefreshTask(MojangAccount *account, QObject *parent) | ||||
| 	: YggdrasilTask(account, parent) | ||||
| { | ||||
| } | ||||
| @@ -44,13 +44,12 @@ QJsonObject RefreshTask::getRequestContent() const | ||||
| 	 *  "requestUser": true/false               // request the user structure | ||||
| 	 * } | ||||
| 	 */ | ||||
| 	auto account = getMojangAccount(); | ||||
| 	QJsonObject req; | ||||
| 	req.insert("clientToken", account->clientToken()); | ||||
| 	req.insert("accessToken", account->accessToken()); | ||||
| 	req.insert("clientToken", m_account->m_clientToken); | ||||
| 	req.insert("accessToken", m_account->m_accessToken); | ||||
| 	/* | ||||
| 	{ | ||||
| 		auto currentProfile = account->currentProfile(); | ||||
| 		auto currentProfile = m_account->currentProfile(); | ||||
| 		QJsonObject profile; | ||||
| 		profile.insert("id", currentProfile->id()); | ||||
| 		profile.insert("name", currentProfile->name()); | ||||
| @@ -64,8 +63,6 @@ QJsonObject RefreshTask::getRequestContent() const | ||||
|  | ||||
| bool RefreshTask::processResponse(QJsonObject responseData) | ||||
| { | ||||
| 	auto account = getMojangAccount(); | ||||
|  | ||||
| 	// Read the response data. We need to get the client token, access token, and the selected | ||||
| 	// profile. | ||||
| 	QLOG_DEBUG() << "Processing authentication response."; | ||||
| @@ -80,7 +77,7 @@ bool RefreshTask::processResponse(QJsonObject responseData) | ||||
| 		QLOG_ERROR() << "Server didn't send a client token."; | ||||
| 		return false; | ||||
| 	} | ||||
| 	if (!account->clientToken().isEmpty() && clientToken != account->clientToken()) | ||||
| 	if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) | ||||
| 	{ | ||||
| 		// The server changed our client token! Obey its wishes, but complain. That's what I do | ||||
| 		// for my parents, so... | ||||
| @@ -104,7 +101,7 @@ bool RefreshTask::processResponse(QJsonObject responseData) | ||||
| 	// profile) | ||||
| 	QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); | ||||
| 	QString currentProfileId = currentProfile.value("id").toString(""); | ||||
| 	if (account->currentProfile()->id() != currentProfileId) | ||||
| 	if (m_account->currentProfile()->id != currentProfileId) | ||||
| 	{ | ||||
| 		// TODO: Set an error to display to the user. | ||||
| 		QLOG_ERROR() << "Server didn't specify the same selected profile as ours."; | ||||
| @@ -132,8 +129,7 @@ bool RefreshTask::processResponse(QJsonObject responseData) | ||||
| 	// we've succeeded. | ||||
| 	QLOG_DEBUG() << "Finished reading refresh response."; | ||||
| 	// Reset the access token. | ||||
| 	account->setAccessToken(accessToken); | ||||
| 	account->propagateChange(); | ||||
| 	m_account->m_accessToken = accessToken; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -30,7 +30,7 @@ class RefreshTask : public YggdrasilTask | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
| 	RefreshTask(MojangAccountPtr account, QObject *parent = 0); | ||||
| 	RefreshTask(MojangAccount * account, QObject *parent = 0); | ||||
|  | ||||
| protected: | ||||
| 	virtual QJsonObject getRequestContent() const; | ||||
|   | ||||
| @@ -26,7 +26,7 @@ | ||||
|  | ||||
| #include "logger/QsLog.h" | ||||
|  | ||||
| ValidateTask::ValidateTask(MojangAccountPtr account, QObject *parent) | ||||
| ValidateTask::ValidateTask(MojangAccount * account, QObject *parent) | ||||
| 	: YggdrasilTask(account, parent) | ||||
| { | ||||
| } | ||||
| @@ -34,7 +34,7 @@ ValidateTask::ValidateTask(MojangAccountPtr account, QObject *parent) | ||||
| QJsonObject ValidateTask::getRequestContent() const | ||||
| { | ||||
| 	QJsonObject req; | ||||
| 	req.insert("accessToken", getMojangAccount()->accessToken()); | ||||
| 	req.insert("accessToken", m_account->m_accessToken); | ||||
| 	return req; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -28,7 +28,7 @@ class ValidateTask : public YggdrasilTask | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
| 	ValidateTask(MojangAccountPtr account, QObject *parent = 0); | ||||
| 	ValidateTask(MojangAccount *account, QObject *parent = 0); | ||||
|  | ||||
| protected: | ||||
| 	virtual QJsonObject getRequestContent() const; | ||||
|   | ||||
| @@ -83,10 +83,7 @@ void MojangAccountList::removeAccount(QModelIndex index) | ||||
|  | ||||
| MojangAccountPtr MojangAccountList::activeAccount() const | ||||
| { | ||||
| 	if (m_activeAccount.isEmpty()) | ||||
| 		return nullptr; | ||||
| 	else | ||||
| 		return findAccount(m_activeAccount); | ||||
| 	return m_activeAccount; | ||||
| } | ||||
|  | ||||
| void MojangAccountList::setActiveAccount(const QString &username) | ||||
| @@ -94,14 +91,14 @@ void MojangAccountList::setActiveAccount(const QString &username) | ||||
| 	beginResetModel(); | ||||
| 	if (username.isEmpty()) | ||||
| 	{ | ||||
| 		m_activeAccount = ""; | ||||
| 		m_activeAccount = nullptr; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		for (MojangAccountPtr account : m_accounts) | ||||
| 		{ | ||||
| 			if (account->username() == username) | ||||
| 				m_activeAccount = username; | ||||
| 				m_activeAccount = account; | ||||
| 		} | ||||
| 	} | ||||
| 	endResetModel(); | ||||
| @@ -152,7 +149,7 @@ QVariant MojangAccountList::data(const QModelIndex &index, int role) const | ||||
| 		switch (index.column()) | ||||
| 		{ | ||||
| 		case ActiveColumn: | ||||
| 			return account->username() == m_activeAccount; | ||||
| 			return account == m_activeAccount; | ||||
|  | ||||
| 		case NameColumn: | ||||
| 			return account->username(); | ||||
| @@ -297,11 +294,9 @@ bool MojangAccountList::loadList(const QString &filePath) | ||||
| 			QLOG_WARN() << "Failed to load an account."; | ||||
| 		} | ||||
| 	} | ||||
| 	endResetModel(); | ||||
|  | ||||
| 	// Load the active account. | ||||
| 	m_activeAccount = root.value("activeAccount").toString(""); | ||||
|  | ||||
| 	m_activeAccount = findAccount(root.value("activeAccount").toString("")); | ||||
| 	endResetModel(); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| @@ -336,8 +331,11 @@ bool MojangAccountList::saveList(const QString &filePath) | ||||
| 	// Insert the account list into the root object. | ||||
| 	root.insert("accounts", accounts); | ||||
|  | ||||
| 	// Save the active account. | ||||
| 	root.insert("activeAccount", m_activeAccount); | ||||
| 	if(m_activeAccount) | ||||
| 	{ | ||||
| 		// Save the active account. | ||||
| 		root.insert("activeAccount", m_activeAccount->username()); | ||||
| 	} | ||||
|  | ||||
| 	// Create a JSON document object to convert our JSON to bytes. | ||||
| 	QJsonDocument doc(root); | ||||
|   | ||||
| @@ -161,10 +161,9 @@ protected: | ||||
| 	QList<MojangAccountPtr> m_accounts; | ||||
|  | ||||
| 	/*! | ||||
| 	 * Username of the account that is currently active. | ||||
| 	 * Empty string if no account is active. | ||||
| 	 * Account that is currently active. | ||||
| 	 */ | ||||
| 	QString m_activeAccount; | ||||
| 	MojangAccountPtr m_activeAccount; | ||||
|  | ||||
| 	//! Path to the account list file. Empty string if there isn't one. | ||||
| 	QString m_listFilePath; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user