diff options
36 files changed, 665 insertions, 304 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 0acebacf..ba28a042 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -315,10 +315,14 @@ gui/dialogs/UpdateDialog.h gui/dialogs/UpdateDialog.cpp # GUI - widgets +gui/widgets/Common.h +gui/widgets/Common.cpp gui/widgets/InstanceDelegate.h gui/widgets/InstanceDelegate.cpp gui/widgets/ModListView.h gui/widgets/ModListView.cpp +gui/widgets/VersionListView.h +gui/widgets/VersionListView.cpp gui/widgets/LabeledToolButton.h gui/widgets/LabeledToolButton.cpp gui/widgets/MCModInfoFrame.h @@ -365,6 +369,8 @@ logic/net/PasteUpload.cpp logic/net/URLConstants.h # Yggdrasil login stuff +logic/auth/AuthSession.h +logic/auth/AuthSession.cpp logic/auth/MojangAccountList.h logic/auth/MojangAccountList.cpp logic/auth/MojangAccount.h diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index ee9c3fad..9977dc75 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -71,7 +71,6 @@ #include "logic/auth/flows/AuthenticateTask.h" #include "logic/auth/flows/RefreshTask.h" -#include "logic/auth/flows/ValidateTask.h" #include "logic/updater/DownloadUpdateTask.h" @@ -132,8 +131,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel); - QObject::connect(newsLabel, &QAbstractButton::clicked, this, &MainWindow::newsButtonClicked); - QObject::connect(MMC->newsChecker().get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel); + QObject::connect(newsLabel, &QAbstractButton::clicked, this, + &MainWindow::newsButtonClicked); + QObject::connect(MMC->newsChecker().get(), &NewsChecker::newsLoaded, this, + &MainWindow::updateNewsLabel); updateNewsLabel(); } @@ -173,8 +174,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi view->setModel(proxymodel); view->setContextMenuPolicy(Qt::CustomContextMenu); - connect(view, SIGNAL(customContextMenuRequested(const QPoint&)), - this, SLOT(showInstanceContextMenu(const QPoint&))); + connect(view, SIGNAL(customContextMenuRequested(const QPoint &)), this, + SLOT(showInstanceContextMenu(const QPoint &))); ui->horizontalLayout->addWidget(view); } @@ -213,8 +214,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi // Start status checker { - connect(MMC->statusChecker().get(), &StatusChecker::statusLoaded, this, &MainWindow::updateStatusUI); - connect(MMC->statusChecker().get(), &StatusChecker::statusLoadingFailed, this, &MainWindow::updateStatusFailedUI); + connect(MMC->statusChecker().get(), &StatusChecker::statusLoaded, this, + &MainWindow::updateStatusUI); + connect(MMC->statusChecker().get(), &StatusChecker::statusLoadingFailed, this, + &MainWindow::updateStatusFailedUI); connect(m_statusRefresh, &QAbstractButton::clicked, this, &MainWindow::reloadStatus); connect(&statusTimer, &QTimer::timeout, this, &MainWindow::reloadStatus); @@ -313,8 +316,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi if (MMC->settings()->get("AutoUpdate").toBool()) on_actionCheckUpdate_triggered(); - connect(MMC->notificationChecker().get(), &NotificationChecker::notificationCheckFinished, - this, &MainWindow::notificationsChanged); + connect(MMC->notificationChecker().get(), + &NotificationChecker::notificationCheckFinished, this, + &MainWindow::notificationsChanged); } setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString()); @@ -330,9 +334,9 @@ MainWindow::~MainWindow() delete drawer; } -void MainWindow::showInstanceContextMenu(const QPoint& pos) +void MainWindow::showInstanceContextMenu(const QPoint &pos) { - if(!view->indexAt(pos).isValid()) + if (!view->indexAt(pos).isValid()) { return; } @@ -522,9 +526,12 @@ static QString convertStatus(const QString &status) { QString ret = "?"; - if(status == "green") ret = "↑"; - else if(status == "yellow") ret = "-"; - else if(status == "red") ret="↓"; + if (status == "green") + ret = "↑"; + else if (status == "yellow") + ret = "-"; + else if (status == "red") + ret = "↓"; return "<span style=\"font-size:11pt; font-weight:600;\">" + ret + "</span>"; } @@ -533,7 +540,7 @@ void MainWindow::reloadStatus() { m_statusRefresh->setChecked(true); MMC->statusChecker()->reloadStatus(); - //updateStatusUI(); + // updateStatusUI(); } static QString makeStatusString(const QMap<QString, QString> statuses) @@ -632,7 +639,8 @@ void MainWindow::notificationsChanged() } QMessageBox box(icon, tr("Notification"), entry.message, QMessageBox::Close, this); - QPushButton *dontShowAgainButton = box.addButton(tr("Don't show again"), QMessageBox::AcceptRole); + QPushButton *dontShowAgainButton = + box.addButton(tr("Don't show again"), QMessageBox::AcceptRole); box.setDefaultButton(QMessageBox::Close); box.exec(); if (box.clickedButton() == dontShowAgainButton) @@ -657,9 +665,9 @@ void MainWindow::downloadUpdates(QString repo, int versionId, bool installOnExit if (updateDlg.exec(&updateTask)) { UpdateFlags baseFlags = None; - #ifdef MultiMC_UPDATER_DRY_RUN - baseFlags |= DryRun; - #endif +#ifdef MultiMC_UPDATER_DRY_RUN + baseFlags |= DryRun; +#endif if (installOnExit) MMC->installUpdates(updateTask.updateFilesDir(), baseFlags | OnExit); else @@ -751,7 +759,7 @@ void MainWindow::on_actionAddInstance_triggered() if (MMC->accounts()->anyAccountIsValid()) { ProgressDialog loadDialog(this); - auto update = newInstance->doUpdate(false); + auto update = newInstance->doUpdate(); connect(update.get(), &Task::failed, [this](QString reason) { QString error = QString("Instance load failed: %1").arg(reason); @@ -837,7 +845,7 @@ void MainWindow::on_actionChangeInstIcon_triggered() void MainWindow::iconUpdated(QString icon) { - if(icon == m_currentInstIcon) + if (icon == m_currentInstIcon) { ui->actionChangeInstIcon->setIcon(MMC->icons()->getBigIcon(m_currentInstIcon)); } @@ -860,7 +868,8 @@ void MainWindow::setSelectedInstanceById(const QString &id) selectionIndex = proxymodel->mapFromSource(index); } } - view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect); + view->selectionModel()->setCurrentIndex(selectionIndex, + QItemSelectionModel::ClearAndSelect); } void MainWindow::on_actionChangeInstGroup_triggered() @@ -1060,7 +1069,16 @@ void MainWindow::on_actionLaunchInstance_triggered() } } -void MainWindow::doLaunch() +void MainWindow::on_actionLaunchInstanceOffline_triggered() +{ + if (m_selectedInstance) + { + NagUtils::checkJVMArgs(m_selectedInstance->settings().get("JvmArgs").toString(), this); + doLaunch(false); + } +} + +void MainWindow::doLaunch(bool online) { if (!m_selectedInstance) return; @@ -1104,89 +1122,111 @@ void MainWindow::doLaunch() if (!account.get()) return; + // we try empty password first :) + QString password; + // we loop until the user succeeds in logging in or gives up + bool tryagain = true; + // the failure. the default failure. QString failReason = tr("Your account is currently not logged in. Please enter " "your password to log in again."); - // do the login. if the account has an access token, try to refresh it first. - if (account->accountStatus() != NotVerified) + + while (tryagain) { - // We'll need to validate the access token to make sure the account is still logged in. - ProgressDialog progDialog(this); - progDialog.setSkipButton(true, tr("Play Offline")); - auto task = account->login(); - progDialog.exec(task.get()); - - auto status = account->accountStatus(); - if (status != NotVerified) - { - updateInstance(m_selectedInstance, account); - } - else + AuthSessionPtr session(new AuthSession()); + session->wants_online = online; + auto task = account->login(session, password); + if (task) { + // We'll need to validate the access token to make sure the account + // is still logged in. + ProgressDialog progDialog(this); + if (online) + progDialog.setSkipButton(true, tr("Play Offline")); + progDialog.exec(task.get()); if (!task->successful()) { failReason = task->failReason(); } - if (loginWithPassword(account, failReason)) - updateInstance(m_selectedInstance, account); } - // in any case, revert from online to verified. - account->downgrade(); - } - else - { - if (loginWithPassword(account, failReason)) + switch (session->status) { - updateInstance(m_selectedInstance, account); - account->downgrade(); + case AuthSession::Undetermined: + { + QLOG_ERROR() << "Received undetermined session status during login. Bye."; + tryagain = false; + break; } - // in any case, revert from online to verified. - account->downgrade(); - } -} - -bool MainWindow::loginWithPassword(MojangAccountPtr account, const QString &errorMsg) -{ - EditAccountDialog passDialog(errorMsg, this, EditAccountDialog::PasswordField); - if (passDialog.exec() == QDialog::Accepted) - { - // To refresh the token, we just create an authenticate task with the given account and - // the user's password. - ProgressDialog progDialog(this); - auto task = account->login(passDialog.password()); - progDialog.exec(task.get()); - if (task->successful()) - return true; - else + case AuthSession::RequiresPassword: { - // If the authentication task failed, recurse with the task's error message. - return loginWithPassword(account, task->failReason()); + EditAccountDialog passDialog(failReason, this, EditAccountDialog::PasswordField); + if (passDialog.exec() == QDialog::Accepted) + { + password = passDialog.password(); + } + else + { + tryagain = false; + } + break; + } + case AuthSession::PlayableOffline: + { + // we ask the user for a player name + bool ok = false; + QString usedname = session->player_name; + QString name = QInputDialog::getText(this, tr("Player name"), + tr("Choose your offline mode player name."), + QLineEdit::Normal, session->player_name, &ok); + if (!ok) + { + tryagain = false; + break; + } + if (name.length()) + { + usedname = name; + } + session->MakeOffline(usedname); + // offline flavored game from here :3 + } + case AuthSession::PlayableOnline: + { + // update first if the server actually responded + if (session->auth_server_online) + { + updateInstance(m_selectedInstance, session); + } + else + { + launchInstance(m_selectedInstance, session); + } + tryagain = false; + } } } - return false; } -void MainWindow::updateInstance(BaseInstance *instance, MojangAccountPtr account) +void MainWindow::updateInstance(BaseInstance *instance, AuthSessionPtr session) { - bool only_prepare = account->accountStatus() != Online; - auto updateTask = instance->doUpdate(only_prepare); + auto updateTask = instance->doUpdate(); if (!updateTask) { - launchInstance(instance, account); + launchInstance(instance, session); return; } ProgressDialog tDialog(this); - connect(updateTask.get(), &Task::succeeded, [this, instance, account] - { launchInstance(instance, account); }); + connect(updateTask.get(), &Task::succeeded, [this, instance, session] + { launchInstance(instance, session); }); connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString))); tDialog.exec(updateTask.get()); } -void MainWindow::launchInstance(BaseInstance *instance, MojangAccountPtr account) +void MainWindow::launchInstance(BaseInstance *instance, AuthSessionPtr session) { Q_ASSERT_X(instance != NULL, "launchInstance", "instance is NULL"); - Q_ASSERT_X(account.get() != nullptr, "launchInstance", "account is NULL"); + Q_ASSERT_X(session.get() != nullptr, "launchInstance", "session is NULL"); - proc = instance->prepareForLaunch(account); + proc = instance->prepareForLaunch(session); if (!proc) return; @@ -1195,7 +1235,7 @@ void MainWindow::launchInstance(BaseInstance *instance, MojangAccountPtr account console = new ConsoleWindow(proc); connect(console, SIGNAL(isClosing()), this, SLOT(instanceEnded())); - proc->setLogin(account); + proc->setLogin(session); proc->launch(); } @@ -1258,7 +1298,7 @@ void MainWindow::on_actionChangeInstMCVersion_triggered() VersionSelectDialog vselect(m_selectedInstance->versionList().get(), tr("Change Minecraft version"), this); vselect.setFilter(1, "OneSix"); - if(!vselect.exec() || !vselect.selectedVersion()) + if (!vselect.exec() || !vselect.selectedVersion()) return; if (!MMC->accounts()->anyAccountIsValid()) @@ -1276,7 +1316,7 @@ void MainWindow::on_actionChangeInstMCVersion_triggered() auto result = CustomMessageBox::selectable( this, tr("Are you sure?"), tr("This will remove any library/version customization you did previously. " - "This includes things like Forge install and similar."), + "This includes things like Forge install and similar."), QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Abort, QMessageBox::Abort)->exec(); @@ -1285,7 +1325,7 @@ void MainWindow::on_actionChangeInstMCVersion_triggered() } m_selectedInstance->setIntendedVersionId(vselect.selectedVersion()->descriptor()); - auto updateTask = m_selectedInstance->doUpdate(false); + auto updateTask = m_selectedInstance->doUpdate(); if (!updateTask) { return; @@ -1384,7 +1424,7 @@ void MainWindow::instanceEnded() void MainWindow::checkMigrateLegacyAssets() { int legacyAssets = AssetsUtils::findLegacyAssets(); - if(legacyAssets > 0) + if (legacyAssets > 0) { ProgressDialog migrateDlg(this); AssetsMigrateTask migrateTask(legacyAssets, &migrateDlg); diff --git a/gui/MainWindow.h b/gui/MainWindow.h index eb478776..eeba2c26 100644 --- a/gui/MainWindow.h +++ b/gui/MainWindow.h @@ -96,6 +96,8 @@ slots: void on_actionLaunchInstance_triggered(); + void on_actionLaunchInstanceOffline_triggered(); + void on_actionDeleteInstance_triggered(); void on_actionRenameInstance_triggered(); @@ -112,25 +114,18 @@ slots: * Launches the currently selected instance with the default account. * If no default account is selected, prompts the user to pick an account. */ - void doLaunch(); - - /*! - * Opens an input dialog, allowing the user to input their password and refresh its access token. - * This function will execute the proper Yggdrasil task to refresh the access token. - * Returns true if successful. False if the user cancelled. - */ - bool loginWithPassword(MojangAccountPtr account, const QString& errorMsg=""); + void doLaunch(bool online = true); /*! * Launches the given instance with the given account. * This function assumes that the given account has a valid, usable access token. */ - void launchInstance(BaseInstance* instance, MojangAccountPtr account); + void launchInstance(BaseInstance *instance, AuthSessionPtr session); /*! * Prepares the given instance for launch with the given account. */ - void updateInstance(BaseInstance* instance, MojangAccountPtr account); + void updateInstance(BaseInstance *instance, AuthSessionPtr account); void onGameUpdateError(QString error); diff --git a/gui/MainWindow.ui b/gui/MainWindow.ui index 25af6f60..8cf26d18 100644 --- a/gui/MainWindow.ui +++ b/gui/MainWindow.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>688</width> - <height>460</height> + <width>694</width> + <height>563</height> </rect> </property> <property name="windowTitle"> @@ -107,6 +107,7 @@ </attribute> <addaction name="actionChangeInstIcon"/> <addaction name="actionLaunchInstance"/> + <addaction name="actionLaunchInstanceOffline"/> <addaction name="separator"/> <addaction name="actionEditInstNotes"/> <addaction name="actionChangeInstGroup"/> @@ -152,7 +153,9 @@ </widget> <action name="actionAddInstance"> <property name="icon"> - <iconset theme="new"/> + <iconset theme="new"> + <normaloff/> + </iconset> </property> <property name="text"> <string>Add Instance</string> @@ -166,7 +169,9 @@ </action> <action name="actionViewInstanceFolder"> <property name="icon"> - <iconset theme="viewfolder"/> + <iconset theme="viewfolder"> + <normaloff/> + </iconset> </property> <property name="text"> <string>View Instance Folder</string> @@ -180,7 +185,9 @@ </action> <action name="actionRefresh"> <property name="icon"> - <iconset theme="refresh"/> + <iconset theme="refresh"> + <normaloff/> + </iconset> </property> <property name="text"> <string>Refresh</string> @@ -194,7 +201,9 @@ </action> <action name="actionViewCentralModsFolder"> <property name="icon"> - <iconset theme="centralmods"/> + <iconset theme="centralmods"> + <normaloff/> + </iconset> </property> <property name="text"> <string>View Central Mods Folder</string> @@ -208,7 +217,9 @@ </action> <action name="actionCheckUpdate"> <property name="icon"> - <iconset theme="checkupdate"/> + <iconset theme="checkupdate"> + <normaloff/> + </iconset> </property> <property name="text"> <string>Check for Updates</string> @@ -222,7 +233,9 @@ </action> <action name="actionSettings"> <property name="icon"> - <iconset theme="settings"/> + <iconset theme="settings"> + <normaloff/> + </iconset> </property> <property name="text"> <string>Settings</string> @@ -239,7 +252,9 @@ </action> <action name="actionReportBug"> <property name="icon"> - <iconset theme="bug"/> + <iconset theme="bug"> + <normaloff/> + </iconset> </property> <property name="text"> <string>Report a Bug</string> @@ -253,7 +268,9 @@ </action> <action name="actionMoreNews"> <property name="icon"> - <iconset theme="news"/> + <iconset theme="news"> + <normaloff/> + </iconset> </property> <property name="text"> <string>More News</string> @@ -270,7 +287,9 @@ </action> <action name="actionAbout"> <property name="icon"> - <iconset theme="about"/> + <iconset theme="about"> + <normaloff/> + </iconset> </property> <property name="text"> <string>About MultiMC</string> @@ -463,7 +482,9 @@ <bool>true</bool> </property> <property name="icon"> - <iconset theme="cat"/> + <iconset theme="cat"> + <normaloff/> + </iconset> </property> <property name="text"> <string>Meow</string> @@ -474,7 +495,9 @@ </action> <action name="actionCopyInstance"> <property name="icon"> - <iconset theme="copy"/> + <iconset theme="copy"> + <normaloff/> + </iconset> </property> <property name="text"> <string>Copy Instance</string> @@ -494,6 +517,17 @@ <string>Manage your Mojang or Minecraft accounts.</string> </property> </action> + <action name="actionLaunchInstanceOffline"> + <property name="text"> + <string>Play Offline</string> + </property> + <property name="toolTip"> + <string>Launch the selected instance in offline mode.</string> + </property> + <property name="statusTip"> + <string>Launch the selected instance.</string> + </property> + </action> </widget> <layoutdefault spacing="6" margin="11"/> <resources> diff --git a/gui/dialogs/AccountListDialog.cpp b/gui/dialogs/AccountListDialog.cpp index 1712e352..a38035a6 100644 --- a/gui/dialogs/AccountListDialog.cpp +++ b/gui/dialogs/AccountListDialog.cpp @@ -126,7 +126,7 @@ void AccountListDialog::addAccount(const QString& errMsg) MojangAccountPtr account = MojangAccount::createFromUsername(username); ProgressDialog progDialog(this); - auto task = account->login(password); + auto task = account->login(nullptr, password); progDialog.exec(task.get()); if(task->successful()) { diff --git a/gui/dialogs/OneSixModEditDialog.cpp b/gui/dialogs/OneSixModEditDialog.cpp index ebd685e8..1742ff80 100644 --- a/gui/dialogs/OneSixModEditDialog.cpp +++ b/gui/dialogs/OneSixModEditDialog.cpp @@ -161,6 +161,8 @@ void OneSixModEditDialog::on_forgeBtn_clicked() } VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); vselect.setFilter(1, m_inst->currentVersionId()); + vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + + m_inst->currentVersionId()); if (vselect.exec() && vselect.selectedVersion()) { ForgeVersionPtr forgeVersion = @@ -224,9 +226,9 @@ void OneSixModEditDialog::on_liteloaderBtn_clicked() } if (!liteloader.add(m_inst)) { - QMessageBox::critical( - this, tr("LiteLoader"), - tr("For reasons unknown, the LiteLoader installation failed. Check your MultiMC log files for details.")); + QMessageBox::critical(this, tr("LiteLoader"), + tr("For reasons unknown, the LiteLoader installation failed. " + "Check your MultiMC log files for details.")); } else { diff --git a/gui/dialogs/VersionSelectDialog.cpp b/gui/dialogs/VersionSelectDialog.cpp index d6efe3c0..0f379f56 100644 --- a/gui/dialogs/VersionSelectDialog.cpp +++ b/gui/dialogs/VersionSelectDialog.cpp @@ -51,6 +51,11 @@ VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title, } } +void VersionSelectDialog::setEmptyString(QString emptyString) +{ + ui->listView->setEmptyString(emptyString); +} + VersionSelectDialog::~VersionSelectDialog() { delete ui; diff --git a/gui/dialogs/VersionSelectDialog.h b/gui/dialogs/VersionSelectDialog.h index e36341db..61fa8ab6 100644 --- a/gui/dialogs/VersionSelectDialog.h +++ b/gui/dialogs/VersionSelectDialog.h @@ -44,6 +44,7 @@ public: BaseVersionPtr selectedVersion() const; void setFilter(int column, QString filter); + void setEmptyString(QString emptyString); void setResizeOn(int column); private diff --git a/gui/dialogs/VersionSelectDialog.ui b/gui/dialogs/VersionSelectDialog.ui index 58264f24..07e9e73e 100644 --- a/gui/dialogs/VersionSelectDialog.ui +++ b/gui/dialogs/VersionSelectDialog.ui @@ -15,7 +15,7 @@ </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> - <widget class="QTreeView" name="listView"> + <widget class="VersionListView" name="listView"> <property name="horizontalScrollBarPolicy"> <enum>Qt::ScrollBarAlwaysOff</enum> </property> @@ -65,6 +65,13 @@ </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>VersionListView</class> + <extends>QTreeView</extends> + <header>gui/widgets/VersionListView.h</header> + </customwidget> + </customwidgets> <resources/> <connections> <connection> diff --git a/gui/widgets/Common.cpp b/gui/widgets/Common.cpp new file mode 100644 index 00000000..9b730d6c --- /dev/null +++ b/gui/widgets/Common.cpp @@ -0,0 +1,27 @@ +#include "Common.h" + +// Origin: Qt +QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, + qreal &widthUsed) +{ + QStringList lines; + height = 0; + widthUsed = 0; + textLayout.beginLayout(); + QString str = textLayout.text(); + while (true) + { + QTextLine line = textLayout.createLine(); + if (!line.isValid()) + break; + if (line.textLength() == 0) + break; + line.setLineWidth(lineWidth); + line.setPosition(QPointF(0, height)); + height += line.height(); + lines.append(str.mid(line.textStart(), line.textLength())); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + textLayout.endLayout(); + return lines; +} diff --git a/gui/widgets/Common.h b/gui/widgets/Common.h new file mode 100644 index 00000000..fc46e08f --- /dev/null +++ b/gui/widgets/Common.h @@ -0,0 +1,6 @@ +#pragma once +#include <QStringList> +#include <QTextLayout> + +QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, + qreal &widthUsed);
\ No newline at end of file diff --git a/gui/widgets/InstanceDelegate.cpp b/gui/widgets/InstanceDelegate.cpp index 5020b8b6..33da7130 100644 --- a/gui/widgets/InstanceDelegate.cpp +++ b/gui/widgets/InstanceDelegate.cpp @@ -19,30 +19,7 @@ #include <QTextLayout> #include <QApplication> #include <QtCore/qmath.h> - -// Origin: Qt -static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, - qreal &widthUsed) -{ - height = 0; - widthUsed = 0; - textLayout.beginLayout(); - QString str = textLayout.text(); - while (true) - { - QTextLine line = textLayout.createLine(); - if (!line.isValid()) - break; - if (line.textLength() == 0) - break; - line.setLineWidth(lineWidth); - line.setPosition(QPointF(0, height)); - height += line.height(); - widthUsed = qMax(widthUsed, line.naturalTextWidth()); - } - textLayout.endLayout(); -} - +#include "Common.h" #define QFIXED_MAX (INT_MAX / 256) ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent) diff --git a/gui/widgets/VersionListView.cpp b/gui/widgets/VersionListView.cpp new file mode 100644 index 00000000..b7f45f27 --- /dev/null +++ b/gui/widgets/VersionListView.cpp @@ -0,0 +1,150 @@ +/* Copyright 2013 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 <QHeaderView> +#include <QApplication> +#include <QMouseEvent> +#include <QDrag> +#include <QPainter> +#include "VersionListView.h" +#include "Common.h" + +VersionListView::VersionListView(QWidget *parent) + :QTreeView ( parent ) +{ + m_emptyString = tr("No versions are currently available."); +} + +void VersionListView::rowsInserted(const QModelIndex &parent, int start, int end) +{ + if(!m_itemCount) + viewport()->update(); + m_itemCount += end-start+1; + QTreeView::rowsInserted(parent, start, end); +} + + +void VersionListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + m_itemCount -= end-start+1; + if(!m_itemCount) + viewport()->update(); + QTreeView::rowsInserted(parent, start, end); +} + +void VersionListView::setModel(QAbstractItemModel *model) +{ + m_itemCount = model->rowCount(); + if(!m_itemCount) + viewport()->update(); + QTreeView::setModel(model); +} + +void VersionListView::reset() +{ + if(model()) + { + m_itemCount = model()->rowCount(); + } + viewport()->update(); + QTreeView::reset(); +} + +void VersionListView::setEmptyString(QString emptyString) +{ + m_emptyString = emptyString; + if(!m_itemCount) + { + viewport()->update(); + } +} + +void VersionListView::paintEvent(QPaintEvent *event) +{ + if(m_itemCount) + { + QTreeView::paintEvent(event); + } + else + { + paintInfoLabel(event); + } +} + +void VersionListView::paintInfoLabel(QPaintEvent *event) +{ + int scrollInterval = 500; + + //calculate the rect for the overlay + QPainter painter(viewport()); + painter.setRenderHint(QPainter::Antialiasing, true); + const QChar letter = 'Q'; + QFont font("sans", 20); + font.setBold(true); + + QRect bounds = viewport()->geometry(); + bounds.moveTop(0); + QTextLayout layout(m_emptyString, font); + qreal height = 0.0; + qreal widthUsed = 0.0; + QStringList lines = viewItemTextLayout(layout, bounds.width() - 20, height, widthUsed); + QRect rect (0,0, widthUsed, height); + rect.setWidth(rect.width()+20); + rect.setHeight(rect.height()+20); + rect.moveCenter(bounds.center()); + //check if we are allowed to draw in our area + if (!event->rect().intersects(rect)) { + return; + } + //draw the letter of the topmost item semitransparent in the middle + QColor background = QApplication::palette().color(QPalette::Foreground); + QColor foreground = QApplication::palette().color(QPalette::Base); + /* + background.setAlpha(128 - scrollFade); + foreground.setAlpha(128 - scrollFade); + */ + painter.setBrush(QBrush(background)); + painter.setPen(foreground); + painter.drawRoundedRect(rect, 5.0, 5.0); + foreground.setAlpha(190); + painter.setPen(foreground); + painter.setFont(font); + painter.drawText(rect, Qt::AlignCenter, lines.join("\n")); + +} + +/* +void ModListView::setModel ( QAbstractItemModel* model ) +{ + QTreeView::setModel ( model ); + auto head = header(); + head->setStretchLastSection(false); + // HACK: this is true for the checkbox column of mod lists + auto string = model->headerData(0,head->orientation()).toString(); + if(!string.size()) + { + head->setSectionResizeMode(0, QHeaderView::ResizeToContents); + head->setSectionResizeMode(1, QHeaderView::Stretch); + for(int i = 2; i < head->count(); i++) + head->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + else + { + head->setSectionResizeMode(0, QHeaderView::Stretch); + for(int i = 1; i < head->count(); i++) + head->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } +} +*/
\ No newline at end of file diff --git a/gui/widgets/VersionListView.h b/gui/widgets/VersionListView.h new file mode 100644 index 00000000..af9b1f6a --- /dev/null +++ b/gui/widgets/VersionListView.h @@ -0,0 +1,43 @@ +/* Copyright 2013 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 <QTreeView> + +class Mod; + +class VersionListView : public QTreeView +{ + Q_OBJECT +public: + explicit VersionListView(QWidget *parent = 0); + virtual void paintEvent(QPaintEvent *event) override; + void setEmptyString(QString emptyString); + virtual void setModel ( QAbstractItemModel* model ); + +public slots: + virtual void reset() override; + +protected slots: + virtual void rowsAboutToBeRemoved(const QModelIndex & parent, int start, int end) override; + virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; + +private: /* methods */ + void paintInfoLabel(QPaintEvent *event); + +private: /* variables */ + int m_itemCount = 0; + QString m_emptyString; +}; diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index a861e9b2..cd49f99b 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -155,10 +155,10 @@ public: virtual SettingsObject &settings() const; /// returns a valid update task - virtual std::shared_ptr<Task> doUpdate(bool only_prepare) = 0; + virtual std::shared_ptr<Task> doUpdate() = 0; /// returns a valid minecraft process, ready for launch with the given account. - virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) = 0; + virtual MinecraftProcess *prepareForLaunch(AuthSessionPtr account) = 0; /// do any necessary cleanups after the instance finishes. also runs before /// 'prepareForLaunch' diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index 2828bcbf..a9f0d112 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -42,15 +42,15 @@ LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings, settings->registerSetting("IntendedJarVersion", ""); } -std::shared_ptr<Task> LegacyInstance::doUpdate(bool only_prepare) +std::shared_ptr<Task> LegacyInstance::doUpdate() { // make sure the jar mods list is initialized by asking for it. auto list = jarModList(); // create an update task - return std::shared_ptr<Task>(new LegacyUpdate(this, only_prepare, this)); + return std::shared_ptr<Task>(new LegacyUpdate(this, this)); } -MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account) +MinecraftProcess *LegacyInstance::prepareForLaunch(AuthSessionPtr account) { MinecraftProcess *proc = new MinecraftProcess(this); @@ -66,13 +66,14 @@ MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account) if (settings().get("LaunchMaximized").toBool()) windowParams = "max"; else - windowParams = QString("%1x%2").arg(settings().get("MinecraftWinWidth").toInt()).arg( - settings().get("MinecraftWinHeight").toInt()); + windowParams = QString("%1x%2") + .arg(settings().get("MinecraftWinWidth").toInt()) + .arg(settings().get("MinecraftWinHeight").toInt()); QString lwjgl = QDir(MMC->settings()->get("LWJGLDir").toString() + "/" + lwjglVersion()) .absolutePath(); - launchScript += "userName " + account->currentProfile()->name + "\n"; - launchScript += "sessionId " + account->sessionId() + "\n"; + launchScript += "userName " + account->player_name + "\n"; + launchScript += "sessionId " + account->session + "\n"; launchScript += "windowTitle " + windowTitle() + "\n"; launchScript += "windowParams " + windowParams + "\n"; launchScript += "lwjgl " + lwjgl + "\n"; diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h index 1e7d9eb6..636addeb 100644 --- a/logic/LegacyInstance.h +++ b/logic/LegacyInstance.h @@ -76,9 +76,9 @@ public: virtual bool shouldUpdate() const override; virtual void setShouldUpdate(bool val) override; - virtual std::shared_ptr<Task> doUpdate(bool only_prepare) override; + virtual std::shared_ptr<Task> doUpdate() override; - virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override; + virtual MinecraftProcess *prepareForLaunch(AuthSessionPtr account) override; virtual void cleanupAfterRun() override; virtual QDialog *createModEditDialog(QWidget *parent) override; diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp index cb3598a7..5d82a76b 100644 --- a/logic/LegacyUpdate.cpp +++ b/logic/LegacyUpdate.cpp @@ -27,13 +27,13 @@ #include "logger/QsLog.h" #include "logic/net/URLConstants.h" -LegacyUpdate::LegacyUpdate(BaseInstance *inst, bool only_prepare, QObject *parent) - : Task(parent), m_inst(inst), m_only_prepare(only_prepare) +LegacyUpdate::LegacyUpdate(BaseInstance *inst, QObject *parent) : Task(parent), m_inst(inst) { } void LegacyUpdate::executeTask() { + /* if(m_only_prepare) { // FIXME: think this through some more. @@ -49,8 +49,9 @@ void LegacyUpdate::executeTask() } else { - lwjglStart(); - } + */ + lwjglStart(); + //} } void LegacyUpdate::lwjglStart() @@ -268,7 +269,6 @@ void LegacyUpdate::jarStart() auto dljob = new NetJob("Minecraft.jar for version " + version_id); - auto metacache = MMC->metacache(); auto entry = metacache->resolveEntry("versions", localPath); dljob->addNetAction(CacheDownload::make(QUrl(urlstr), entry)); @@ -425,7 +425,7 @@ void LegacyUpdate::ModTheJar() auto &mod = modList->operator[](i); // do not merge disabled mods. - if(!mod.enabled()) + if (!mod.enabled()) continue; if (mod.type() == Mod::MOD_ZIPFILE) diff --git a/logic/LegacyUpdate.h b/logic/LegacyUpdate.h index 0b573ca5..613eb1f9 100644 --- a/logic/LegacyUpdate.h +++ b/logic/LegacyUpdate.h @@ -31,7 +31,7 @@ class LegacyUpdate : public Task { Q_OBJECT public: - explicit LegacyUpdate(BaseInstance *inst, bool only_prepare, QObject *parent = 0); + explicit LegacyUpdate(BaseInstance *inst, QObject *parent = 0); virtual void executeTask(); private @@ -72,5 +72,4 @@ private: private: NetJobPtr legacyDownloadJob; BaseInstance *m_inst = nullptr; - bool m_only_prepare = false; }; diff --git a/logic/MinecraftProcess.cpp b/logic/MinecraftProcess.cpp index 84610021..9c0a7074 100644 --- a/logic/MinecraftProcess.cpp +++ b/logic/MinecraftProcess.cpp @@ -79,26 +79,18 @@ void MinecraftProcess::setWorkdir(QString path) QString MinecraftProcess::censorPrivateInfo(QString in) { - if(!m_account) + if(!m_session) return in; - 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 profileId = profile->id; - QString profileName = profile->name; - in.replace(profileId, "<PROFILE ID>"); - in.replace(profileName, "<PROFILE NAME>"); - } + if(m_session->session != "-") + in.replace(m_session->session, "<SESSION ID>"); + in.replace(m_session->access_token, "<ACCESS TOKEN>"); + in.replace(m_session->client_token, "<CLIENT TOKEN>"); + in.replace(m_session->uuid, "<PROFILE ID>"); + in.replace(m_session->player_name, "<PROFILE NAME>"); - auto i = m_account->user().properties.begin(); - while (i != m_account->user().properties.end()) + auto i = m_session->u.properties.begin(); + while (i != m_session->u.properties.end()) { in.replace(i.value(), "<" + i.key().toUpper() + ">"); ++i; diff --git a/logic/MinecraftProcess.h b/logic/MinecraftProcess.h index 70e5df52..26214026 100644 --- a/logic/MinecraftProcess.h +++ b/logic/MinecraftProcess.h @@ -78,9 +78,9 @@ public: void killMinecraft(); - inline void setLogin(MojangAccountPtr account) + inline void setLogin(AuthSessionPtr session) { - m_account = account; + m_session = session; } signals: @@ -117,7 +117,7 @@ protected: QString m_out_leftover; QProcess m_prepostlaunchprocess; bool killed = false; - MojangAccountPtr m_account; + AuthSessionPtr m_session; QString launchScript; QString m_nativeFolder; diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp index 54bfbe2b..ca88142a 100644 --- a/logic/OneSixFTBInstance.cpp +++ b/logic/OneSixFTBInstance.cpp @@ -104,7 +104,7 @@ bool OneSixFTBInstance::menuActionEnabled(QString action_name) const return false; } -std::shared_ptr<Task> OneSixFTBInstance::doUpdate(bool only_prepare) +std::shared_ptr<Task> OneSixFTBInstance::doUpdate() { std::shared_ptr<SequentialTask> task; task.reset(new SequentialTask(this)); @@ -112,11 +112,11 @@ std::shared_ptr<Task> OneSixFTBInstance::doUpdate(bool only_prepare) { task->addTask(std::shared_ptr<Task>(MMC->forgelist()->getLoadTask())); } - task->addTask(OneSixInstance::doUpdate(only_prepare)); + task->addTask(OneSixInstance::doUpdate()); task->addTask(std::shared_ptr<Task>(new OneSixFTBInstanceForge(m_forge->version(), this, this))); //FIXME: yes. this may appear dumb. but the previous step can change the list, so we do it all again. //TODO: Add a graph task. Construct graphs of tasks so we may capture the logic properly. - task->addTask(OneSixInstance::doUpdate(only_prepare)); + task->addTask(OneSixInstance::doUpdate()); return task; } diff --git a/logic/OneSixFTBInstance.h b/logic/OneSixFTBInstance.h index dc028819..bc543aeb 100644 --- a/logic/OneSixFTBInstance.h +++ b/logic/OneSixFTBInstance.h @@ -13,7 +13,7 @@ public: virtual QString getStatusbarDescription(); virtual bool menuActionEnabled(QString action_name) const; - virtual std::shared_ptr<Task> doUpdate(bool only_prepare) override; + virtual std::shared_ptr<Task> doUpdate() override; virtual QString id() const; diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index 88c40316..8ecbe7a0 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -46,9 +46,9 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, } } -std::shared_ptr<Task> OneSixInstance::doUpdate(bool only_prepare) +std::shared_ptr<Task> OneSixInstance::doUpdate() { - return std::shared_ptr<Task>(new OneSixUpdate(this, only_prepare)); + return std::shared_ptr<Task>(new OneSixUpdate(this)); } QString replaceTokensIn(QString text, QMap<QString, QString> with) @@ -135,7 +135,7 @@ QDir OneSixInstance::reconstructAssets(std::shared_ptr<OneSixVersion> version) return virtualRoot; } -QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account) +QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session) { I_D(OneSixInstance); auto version = d->version; @@ -147,17 +147,11 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account) QMap<QString, QString> token_mapping; // yggdrasil! - 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; - - // this is for offline?: - /* - map["auth_player_name"] = "Player"; - map["auth_player_name"] = "00000000-0000-0000-0000-000000000000"; - */ + token_mapping["auth_username"] = session->username; + token_mapping["auth_session"] = session->session; + token_mapping["auth_access_token"] = session->access_token; + token_mapping["auth_player_name"] = session->player_name; + token_mapping["auth_uuid"] = session->uuid; // these do nothing and are stupid. token_mapping["profile_name"] = name(); @@ -168,17 +162,8 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account) QString absAssetsDir = QDir("assets/").absolutePath(); token_mapping["game_assets"] = reconstructAssets(d->version).absolutePath(); - auto user = account->user(); - QJsonObject userAttrs; - for (auto key : user.properties.keys()) - { - auto array = QJsonArray::fromStringList(user.properties.values(key)); - userAttrs.insert(key, array); - } - QJsonDocument value(userAttrs); - - token_mapping["user_properties"] = value.toJson(QJsonDocument::Compact); - token_mapping["user_type"] = account->currentProfile()->legacy ? "legacy" : "mojang"; + token_mapping["user_properties"] = session->serializeUserProperties(); + token_mapping["user_type"] = session->user_type; // 1.7.3+ assets tokens token_mapping["assets_root"] = absAssetsDir; token_mapping["assets_index_name"] = version->assets; @@ -191,7 +176,7 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account) return parts; } -MinecraftProcess *OneSixInstance::prepareForLaunch(MojangAccountPtr account) +MinecraftProcess *OneSixInstance::prepareForLaunch(AuthSessionPtr session) { I_D(OneSixInstance); @@ -216,7 +201,7 @@ MinecraftProcess *OneSixInstance::prepareForLaunch(MojangAccountPtr account) } launchScript += "mainClass " + version->mainClass + "\n"; - for (auto param : processMinecraftArgs(account)) + for (auto param : processMinecraftArgs(session)) { launchScript += "param " + param + "\n"; } diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index ee64e886..5aa12e44 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -36,8 +36,8 @@ public: QString loaderModsDir() const; virtual QString instanceConfigFolder() const override; - virtual std::shared_ptr<Task> doUpdate(bool only_prepare) override; - virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) override; + virtual std::shared_ptr<Task> doUpdate() override; + virtual MinecraftProcess *prepareForLaunch(AuthSessionPtr session) override; virtual void cleanupAfterRun() override; @@ -72,6 +72,6 @@ signals: void versionReloaded(); private: - QStringList processMinecraftArgs(MojangAccountPtr account); + QStringList processMinecraftArgs(AuthSessionPtr account); QDir reconstructAssets(std::shared_ptr<OneSixVersion> version); }; diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index c056bc4c..d3ac80c2 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -35,8 +35,8 @@ #include "pathutils.h" #include <JlCompress.h> -OneSixUpdate::OneSixUpdate(BaseInstance *inst, bool only_prepare, QObject *parent) - : Task(parent), m_inst(inst), m_only_prepare(only_prepare) +OneSixUpdate::OneSixUpdate(BaseInstance *inst, QObject *parent) + : Task(parent), m_inst(inst) { } @@ -52,12 +52,6 @@ void OneSixUpdate::executeTask() return; } - if (m_only_prepare) - { - prepareForLaunch(); - return; - } - if (m_inst->shouldUpdate()) { // Get a pointer to the version object that corresponds to the instance's version. @@ -222,7 +216,7 @@ void OneSixUpdate::assetIndexFailed() void OneSixUpdate::assetsFinished() { - prepareForLaunch(); + emitSucceeded(); } void OneSixUpdate::assetsFailed() @@ -330,43 +324,3 @@ void OneSixUpdate::jarlibFailed() emitFailed("Failed to download the following files:\n" + failed_all + "\n\nPlease try again."); } - -void OneSixUpdate::prepareForLaunch() -{ - setStatus(tr("Preparing for launch...")); - QLOG_INFO() << m_inst->name() << ": preparing for launch"; - auto OneSix_inst = (OneSixInstance *)m_inst; - - // delete any leftovers, if they are present. - OneSix_inst->cleanupAfterRun(); - - QString natives_dir_raw = PathCombine(OneSix_inst->instanceRoot(), "natives/"); - auto version = OneSix_inst->getFullVersion(); - if (!version) - { - emitFailed("The version information for this instance is not complete. Try re-creating " - "it or changing the version."); - return; - } - /* - for (auto lib : version->getActiveNativeLibs()) - { - if (!lib->filesExist()) - { - emitFailed("Native library is missing some files:\n" + lib->storagePath() + - "\n\nRun the instance at least once in online mode to get all the " - "required files."); - return; - } - if (!lib->extractTo(natives_dir_raw)) - { - emitFailed("Could not extract the native library:\n" + lib->storagePath() + " to " + - natives_dir_raw + - "\n\nMake sure MultiMC has appropriate permissions and there is enough " - "space on the storage device."); - return; - } - } -*/ - emitSucceeded(); -} diff --git a/logic/OneSixUpdate.h b/logic/OneSixUpdate.h index bc717a94..3c18211e 100644 --- a/logic/OneSixUpdate.h +++ b/logic/OneSixUpdate.h @@ -29,7 +29,7 @@ class OneSixUpdate : public Task { Q_OBJECT public: - explicit OneSixUpdate(BaseInstance *inst, bool prepare_for_launch, QObject *parent = 0); + explicit OneSixUpdate(BaseInstance *inst, QObject *parent = 0); virtual void executeTask(); private @@ -49,9 +49,6 @@ slots: void assetsFinished(); void assetsFailed(); - // extract the appropriate libraries - void prepareForLaunch(); - private: NetJobPtr specificVersionDownloadJob; NetJobPtr jarlibDownloadJob; @@ -59,5 +56,4 @@ private: // target version, determined during this task std::shared_ptr<MinecraftVersion> targetVersion; BaseInstance *m_inst = nullptr; - bool m_only_prepare = false; }; diff --git a/logic/auth/AuthSession.cpp b/logic/auth/AuthSession.cpp new file mode 100644 index 00000000..8758bfbd --- /dev/null +++ b/logic/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/auth/AuthSession.h b/logic/auth/AuthSession.h new file mode 100644 index 00000000..2ac170fa --- /dev/null +++ b/logic/auth/AuthSession.h @@ -0,0 +1,49 @@ +#pragma once + +#include <QString> +#include <QMultiMap> +#include <memory> + +struct User +{ + QString id; + QMultiMap<QString, QString> properties; +}; + +struct 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/auth/MojangAccount.cpp b/logic/auth/MojangAccount.cpp index f41985ec..6c937ef1 100644 --- a/logic/auth/MojangAccount.cpp +++ b/logic/auth/MojangAccount.cpp @@ -24,6 +24,7 @@ #include <QJsonArray> #include <QRegExp> #include <QStringList> +#include <QJsonDocument> #include <logger/QsLog.h> @@ -165,15 +166,26 @@ AccountStatus MojangAccount::accountStatus() const { if (m_accessToken.isEmpty()) return NotVerified; - if (!m_online) + else return Verified; - return Online; } -std::shared_ptr<YggdrasilTask> MojangAccount::login(QString password) +std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session, + QString password) { - if (m_currentTask) - return m_currentTask; + 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)); @@ -182,6 +194,8 @@ std::shared_ptr<YggdrasilTask> MojangAccount::login(QString password) { 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; @@ -189,24 +203,76 @@ std::shared_ptr<YggdrasilTask> MojangAccount::login(QString password) void MojangAccount::authSucceeded() { - m_online = true; + 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 (reason == "Yggdrasil task cancelled.") { - // do nothing + if (session) + { + session->status = accountStatus() == Verified ? AuthSession::PlayableOffline + : AuthSession::RequiresPassword; + session->auth_server_online = false; + fillSession(session); + } } else { - m_online = false; 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/auth/MojangAccount.h b/logic/auth/MojangAccount.h index dd5d54ae..a0565e2c 100644 --- a/logic/auth/MojangAccount.h +++ b/logic/auth/MojangAccount.h @@ -23,6 +23,7 @@ #include <QMap> #include <memory> +#include "AuthSession.h" class Task; class YggdrasilTask; @@ -45,17 +46,10 @@ struct AccountProfile bool legacy; }; -struct User -{ - QString id; - QMultiMap<QString,QString> properties; -}; - enum AccountStatus { NotVerified, - Verified, - Online + Verified }; /** @@ -84,7 +78,7 @@ public: /* construction */ 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. @@ -95,12 +89,9 @@ public: /* manipulation */ * 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(QString password = QString()); + std::shared_ptr<YggdrasilTask> login(AuthSessionPtr session, + QString password = QString()); - void downgrade() - { - m_online = false; - } public: /* queries */ const QString &username() const { @@ -122,19 +113,11 @@ public: /* queries */ return m_profiles; } - const User & user() + const User &user() { return m_user; } - //! 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; @@ -169,16 +152,17 @@ protected: /* variables */ // 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: +private +slots: void authSucceeded(); void authFailed(QString reason); +private: + void fillSession(AuthSessionPtr session); + public: friend class YggdrasilTask; friend class AuthenticateTask; diff --git a/logic/auth/YggdrasilTask.h b/logic/auth/YggdrasilTask.h index 85f5a1e1..4a87067a 100644 --- a/logic/auth/YggdrasilTask.h +++ b/logic/auth/YggdrasilTask.h @@ -36,6 +36,21 @@ public: explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0); /** + * assign a session to this task. the session will be filled with required infomration + * upon completion + */ + void assignSession(AuthSessionPtr session) + { + m_session = session; + } + + /// get the assigned session for filling with information. + AuthSessionPtr getAssignedSession() + { + return m_session; + } + + /** * Class describing a Yggdrasil error response. */ struct Error @@ -117,4 +132,6 @@ protected: const int timeout_max = 10000; const int time_step = 50; + + AuthSessionPtr m_session; }; diff --git a/logic/auth/flows/RefreshTask.cpp b/logic/auth/flows/RefreshTask.cpp index f63c736e..5a55ed91 100644 --- a/logic/auth/flows/RefreshTask.cpp +++ b/logic/auth/flows/RefreshTask.cpp @@ -25,8 +25,7 @@ #include "logger/QsLog.h" -RefreshTask::RefreshTask(MojangAccount *account, QObject *parent) - : YggdrasilTask(account, parent) +RefreshTask::RefreshTask(MojangAccount *account) : YggdrasilTask(account) { } @@ -126,7 +125,6 @@ bool RefreshTask::processResponse(QJsonObject responseData) m_account->m_user = u; } - // We've made it through the minefield of possible errors. Return true to indicate that // we've succeeded. QLOG_DEBUG() << "Finished reading refresh response."; diff --git a/logic/auth/flows/RefreshTask.h b/logic/auth/flows/RefreshTask.h index 2fd50c60..0dadc025 100644 --- a/logic/auth/flows/RefreshTask.h +++ b/logic/auth/flows/RefreshTask.h @@ -30,7 +30,7 @@ class RefreshTask : public YggdrasilTask { Q_OBJECT public: - RefreshTask(MojangAccount * account, QObject *parent = 0); + RefreshTask(MojangAccount * account); protected: virtual QJsonObject getRequestContent() const; @@ -41,3 +41,4 @@ protected: QString getStateMessage(const YggdrasilTask::State state) const; }; + diff --git a/logic/net/NetJob.cpp b/logic/net/NetJob.cpp index 8b79bc54..9e800d13 100644 --- a/logic/net/NetJob.cpp +++ b/logic/net/NetJob.cpp @@ -31,7 +31,6 @@ void NetJob::partSucceeded(int index) num_succeeded++; QLOG_INFO() << m_job_name.toLocal8Bit() << "progress:" << num_succeeded << "/" << downloads.size(); - emit filesProgress(num_succeeded, num_failed, downloads.size()); if (num_failed + num_succeeded == downloads.size()) { @@ -55,7 +54,6 @@ void NetJob::partFailed(int index) { QLOG_ERROR() << "Part" << index << "failed 3 times (" << downloads[index]->m_url << ")"; num_failed++; - emit filesProgress(num_succeeded, num_failed, downloads.size()); if (num_failed + num_succeeded == downloads.size()) { QLOG_ERROR() << m_job_name.toLocal8Bit() << "failed."; diff --git a/logic/net/NetJob.h b/logic/net/NetJob.h index 68c4c408..03d6a36e 100644 --- a/logic/net/NetJob.h +++ b/logic/net/NetJob.h @@ -84,7 +84,6 @@ public: { return m_job_name; } - ; virtual bool isRunning() const { return m_running; @@ -94,7 +93,6 @@ public: signals: void started(); void progress(qint64 current, qint64 total); - void filesProgress(int, int, int); void succeeded(); void failed(); public |