Merge remote-tracking branch 'upstream/feature_derpstances' into feature_derpstances

This commit is contained in:
Jan Dalheimer 2014-02-01 19:58:13 +01:00
commit 983a40698c
36 changed files with 667 additions and 306 deletions

View File

@ -315,10 +315,14 @@ gui/dialogs/UpdateDialog.h
gui/dialogs/UpdateDialog.cpp gui/dialogs/UpdateDialog.cpp
# GUI - widgets # GUI - widgets
gui/widgets/Common.h
gui/widgets/Common.cpp
gui/widgets/InstanceDelegate.h gui/widgets/InstanceDelegate.h
gui/widgets/InstanceDelegate.cpp gui/widgets/InstanceDelegate.cpp
gui/widgets/ModListView.h gui/widgets/ModListView.h
gui/widgets/ModListView.cpp gui/widgets/ModListView.cpp
gui/widgets/VersionListView.h
gui/widgets/VersionListView.cpp
gui/widgets/LabeledToolButton.h gui/widgets/LabeledToolButton.h
gui/widgets/LabeledToolButton.cpp gui/widgets/LabeledToolButton.cpp
gui/widgets/MCModInfoFrame.h gui/widgets/MCModInfoFrame.h
@ -365,6 +369,8 @@ logic/net/PasteUpload.cpp
logic/net/URLConstants.h logic/net/URLConstants.h
# Yggdrasil login stuff # Yggdrasil login stuff
logic/auth/AuthSession.h
logic/auth/AuthSession.cpp
logic/auth/MojangAccountList.h logic/auth/MojangAccountList.h
logic/auth/MojangAccountList.cpp logic/auth/MojangAccountList.cpp
logic/auth/MojangAccount.h logic/auth/MojangAccount.h

View File

@ -71,7 +71,6 @@
#include "logic/auth/flows/AuthenticateTask.h" #include "logic/auth/flows/AuthenticateTask.h"
#include "logic/auth/flows/RefreshTask.h" #include "logic/auth/flows/RefreshTask.h"
#include "logic/auth/flows/ValidateTask.h"
#include "logic/updater/DownloadUpdateTask.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->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel); ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel);
QObject::connect(newsLabel, &QAbstractButton::clicked, this, &MainWindow::newsButtonClicked); QObject::connect(newsLabel, &QAbstractButton::clicked, this,
QObject::connect(MMC->newsChecker().get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel); &MainWindow::newsButtonClicked);
QObject::connect(MMC->newsChecker().get(), &NewsChecker::newsLoaded, this,
&MainWindow::updateNewsLabel);
updateNewsLabel(); updateNewsLabel();
} }
@ -173,8 +174,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
view->setModel(proxymodel); view->setModel(proxymodel);
view->setContextMenuPolicy(Qt::CustomContextMenu); view->setContextMenuPolicy(Qt::CustomContextMenu);
connect(view, SIGNAL(customContextMenuRequested(const QPoint&)), connect(view, SIGNAL(customContextMenuRequested(const QPoint &)), this,
this, SLOT(showInstanceContextMenu(const QPoint&))); SLOT(showInstanceContextMenu(const QPoint &)));
ui->horizontalLayout->addWidget(view); ui->horizontalLayout->addWidget(view);
} }
@ -213,8 +214,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
// Start status checker // Start status checker
{ {
connect(MMC->statusChecker().get(), &StatusChecker::statusLoaded, this, &MainWindow::updateStatusUI); connect(MMC->statusChecker().get(), &StatusChecker::statusLoaded, this,
connect(MMC->statusChecker().get(), &StatusChecker::statusLoadingFailed, this, &MainWindow::updateStatusFailedUI); &MainWindow::updateStatusUI);
connect(MMC->statusChecker().get(), &StatusChecker::statusLoadingFailed, this,
&MainWindow::updateStatusFailedUI);
connect(m_statusRefresh, &QAbstractButton::clicked, this, &MainWindow::reloadStatus); connect(m_statusRefresh, &QAbstractButton::clicked, this, &MainWindow::reloadStatus);
connect(&statusTimer, &QTimer::timeout, 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()) if (MMC->settings()->get("AutoUpdate").toBool())
on_actionCheckUpdate_triggered(); on_actionCheckUpdate_triggered();
connect(MMC->notificationChecker().get(), &NotificationChecker::notificationCheckFinished, connect(MMC->notificationChecker().get(),
this, &MainWindow::notificationsChanged); &NotificationChecker::notificationCheckFinished, this,
&MainWindow::notificationsChanged);
} }
setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString()); setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString());
@ -330,9 +334,9 @@ MainWindow::~MainWindow()
delete drawer; 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; return;
} }
@ -522,9 +526,12 @@ static QString convertStatus(const QString &status)
{ {
QString ret = "?"; QString ret = "?";
if(status == "green") ret = ""; if (status == "green")
else if(status == "yellow") ret = "-"; ret = "";
else if(status == "red") ret=""; else if (status == "yellow")
ret = "-";
else if (status == "red")
ret = "";
return "<span style=\"font-size:11pt; font-weight:600;\">" + ret + "</span>"; return "<span style=\"font-size:11pt; font-weight:600;\">" + ret + "</span>";
} }
@ -533,7 +540,7 @@ void MainWindow::reloadStatus()
{ {
m_statusRefresh->setChecked(true); m_statusRefresh->setChecked(true);
MMC->statusChecker()->reloadStatus(); MMC->statusChecker()->reloadStatus();
//updateStatusUI(); // updateStatusUI();
} }
static QString makeStatusString(const QMap<QString, QString> statuses) 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); 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.setDefaultButton(QMessageBox::Close);
box.exec(); box.exec();
if (box.clickedButton() == dontShowAgainButton) if (box.clickedButton() == dontShowAgainButton)
@ -657,9 +665,9 @@ void MainWindow::downloadUpdates(QString repo, int versionId, bool installOnExit
if (updateDlg.exec(&updateTask)) if (updateDlg.exec(&updateTask))
{ {
UpdateFlags baseFlags = None; UpdateFlags baseFlags = None;
#ifdef MultiMC_UPDATER_DRY_RUN #ifdef MultiMC_UPDATER_DRY_RUN
baseFlags |= DryRun; baseFlags |= DryRun;
#endif #endif
if (installOnExit) if (installOnExit)
MMC->installUpdates(updateTask.updateFilesDir(), baseFlags | OnExit); MMC->installUpdates(updateTask.updateFilesDir(), baseFlags | OnExit);
else else
@ -751,7 +759,7 @@ void MainWindow::on_actionAddInstance_triggered()
if (MMC->accounts()->anyAccountIsValid()) if (MMC->accounts()->anyAccountIsValid())
{ {
ProgressDialog loadDialog(this); ProgressDialog loadDialog(this);
auto update = newInstance->doUpdate(false); auto update = newInstance->doUpdate();
connect(update.get(), &Task::failed, [this](QString reason) connect(update.get(), &Task::failed, [this](QString reason)
{ {
QString error = QString("Instance load failed: %1").arg(reason); QString error = QString("Instance load failed: %1").arg(reason);
@ -837,7 +845,7 @@ void MainWindow::on_actionChangeInstIcon_triggered()
void MainWindow::iconUpdated(QString icon) void MainWindow::iconUpdated(QString icon)
{ {
if(icon == m_currentInstIcon) if (icon == m_currentInstIcon)
{ {
ui->actionChangeInstIcon->setIcon(MMC->icons()->getBigIcon(m_currentInstIcon)); ui->actionChangeInstIcon->setIcon(MMC->icons()->getBigIcon(m_currentInstIcon));
} }
@ -860,7 +868,8 @@ void MainWindow::setSelectedInstanceById(const QString &id)
selectionIndex = proxymodel->mapFromSource(index); selectionIndex = proxymodel->mapFromSource(index);
} }
} }
view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect); view->selectionModel()->setCurrentIndex(selectionIndex,
QItemSelectionModel::ClearAndSelect);
} }
void MainWindow::on_actionChangeInstGroup_triggered() 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) if (!m_selectedInstance)
return; return;
@ -1104,89 +1122,111 @@ void MainWindow::doLaunch()
if (!account.get()) if (!account.get())
return; 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 " QString failReason = tr("Your account is currently not logged in. Please enter "
"your password to log in again."); "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)
{
// 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(); while (tryagain)
if (status != NotVerified) {
{ AuthSessionPtr session(new AuthSession());
updateInstance(m_selectedInstance, account); session->wants_online = online;
} auto task = account->login(session, password);
else 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()) if (!task->successful())
{ {
failReason = task->failReason(); failReason = task->failReason();
} }
if (loginWithPassword(account, failReason))
updateInstance(m_selectedInstance, account);
} }
// in any case, revert from online to verified. switch (session->status)
account->downgrade();
}
else
{
if (loginWithPassword(account, failReason))
{ {
updateInstance(m_selectedInstance, account); case AuthSession::Undetermined:
account->downgrade(); {
QLOG_ERROR() << "Received undetermined session status during login. Bye.";
tryagain = false;
break;
}
case AuthSession::RequiresPassword:
{
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;
}
} }
// in any case, revert from online to verified.
account->downgrade();
} }
} }
bool MainWindow::loginWithPassword(MojangAccountPtr account, const QString &errorMsg) void MainWindow::updateInstance(BaseInstance *instance, AuthSessionPtr session)
{ {
EditAccountDialog passDialog(errorMsg, this, EditAccountDialog::PasswordField); auto updateTask = instance->doUpdate();
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
{
// If the authentication task failed, recurse with the task's error message.
return loginWithPassword(account, task->failReason());
}
}
return false;
}
void MainWindow::updateInstance(BaseInstance *instance, MojangAccountPtr account)
{
bool only_prepare = account->accountStatus() != Online;
auto updateTask = instance->doUpdate(only_prepare);
if (!updateTask) if (!updateTask)
{ {
launchInstance(instance, account); launchInstance(instance, session);
return; return;
} }
ProgressDialog tDialog(this); ProgressDialog tDialog(this);
connect(updateTask.get(), &Task::succeeded, [this, instance, account] connect(updateTask.get(), &Task::succeeded, [this, instance, session]
{ launchInstance(instance, account); }); { launchInstance(instance, session); });
connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString))); connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
tDialog.exec(updateTask.get()); 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(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) if (!proc)
return; return;
@ -1195,7 +1235,7 @@ void MainWindow::launchInstance(BaseInstance *instance, MojangAccountPtr account
console = new ConsoleWindow(proc); console = new ConsoleWindow(proc);
connect(console, SIGNAL(isClosing()), this, SLOT(instanceEnded())); connect(console, SIGNAL(isClosing()), this, SLOT(instanceEnded()));
proc->setLogin(account); proc->setLogin(session);
proc->launch(); proc->launch();
} }
@ -1258,7 +1298,7 @@ void MainWindow::on_actionChangeInstMCVersion_triggered()
VersionSelectDialog vselect(m_selectedInstance->versionList().get(), VersionSelectDialog vselect(m_selectedInstance->versionList().get(),
tr("Change Minecraft version"), this); tr("Change Minecraft version"), this);
vselect.setFilter(1, "OneSix"); vselect.setFilter(1, "OneSix");
if(!vselect.exec() || !vselect.selectedVersion()) if (!vselect.exec() || !vselect.selectedVersion())
return; return;
if (!MMC->accounts()->anyAccountIsValid()) if (!MMC->accounts()->anyAccountIsValid())
@ -1276,7 +1316,7 @@ void MainWindow::on_actionChangeInstMCVersion_triggered()
auto result = CustomMessageBox::selectable( auto result = CustomMessageBox::selectable(
this, tr("Are you sure?"), this, tr("Are you sure?"),
tr("This will remove any library/version customization you did previously. " 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::Warning, QMessageBox::Ok | QMessageBox::Abort,
QMessageBox::Abort)->exec(); QMessageBox::Abort)->exec();
@ -1285,7 +1325,7 @@ void MainWindow::on_actionChangeInstMCVersion_triggered()
} }
m_selectedInstance->setIntendedVersionId(vselect.selectedVersion()->descriptor()); m_selectedInstance->setIntendedVersionId(vselect.selectedVersion()->descriptor());
auto updateTask = m_selectedInstance->doUpdate(false); auto updateTask = m_selectedInstance->doUpdate();
if (!updateTask) if (!updateTask)
{ {
return; return;
@ -1384,7 +1424,7 @@ void MainWindow::instanceEnded()
void MainWindow::checkMigrateLegacyAssets() void MainWindow::checkMigrateLegacyAssets()
{ {
int legacyAssets = AssetsUtils::findLegacyAssets(); int legacyAssets = AssetsUtils::findLegacyAssets();
if(legacyAssets > 0) if (legacyAssets > 0)
{ {
ProgressDialog migrateDlg(this); ProgressDialog migrateDlg(this);
AssetsMigrateTask migrateTask(legacyAssets, &migrateDlg); AssetsMigrateTask migrateTask(legacyAssets, &migrateDlg);

View File

@ -96,6 +96,8 @@ slots:
void on_actionLaunchInstance_triggered(); void on_actionLaunchInstance_triggered();
void on_actionLaunchInstanceOffline_triggered();
void on_actionDeleteInstance_triggered(); void on_actionDeleteInstance_triggered();
void on_actionRenameInstance_triggered(); void on_actionRenameInstance_triggered();
@ -112,25 +114,18 @@ slots:
* Launches the currently selected instance with the default account. * Launches the currently selected instance with the default account.
* If no default account is selected, prompts the user to pick an account. * If no default account is selected, prompts the user to pick an account.
*/ */
void doLaunch(); void doLaunch(bool online = true);
/*!
* 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="");
/*! /*!
* Launches the given instance with the given account. * Launches the given instance with the given account.
* This function assumes that the given account has a valid, usable access token. * 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. * 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); void onGameUpdateError(QString error);

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>688</width> <width>694</width>
<height>460</height> <height>563</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -107,6 +107,7 @@
</attribute> </attribute>
<addaction name="actionChangeInstIcon"/> <addaction name="actionChangeInstIcon"/>
<addaction name="actionLaunchInstance"/> <addaction name="actionLaunchInstance"/>
<addaction name="actionLaunchInstanceOffline"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionEditInstNotes"/> <addaction name="actionEditInstNotes"/>
<addaction name="actionChangeInstGroup"/> <addaction name="actionChangeInstGroup"/>
@ -152,7 +153,9 @@
</widget> </widget>
<action name="actionAddInstance"> <action name="actionAddInstance">
<property name="icon"> <property name="icon">
<iconset theme="new"/> <iconset theme="new">
<normaloff/>
</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>Add Instance</string> <string>Add Instance</string>
@ -166,7 +169,9 @@
</action> </action>
<action name="actionViewInstanceFolder"> <action name="actionViewInstanceFolder">
<property name="icon"> <property name="icon">
<iconset theme="viewfolder"/> <iconset theme="viewfolder">
<normaloff/>
</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>View Instance Folder</string> <string>View Instance Folder</string>
@ -180,7 +185,9 @@
</action> </action>
<action name="actionRefresh"> <action name="actionRefresh">
<property name="icon"> <property name="icon">
<iconset theme="refresh"/> <iconset theme="refresh">
<normaloff/>
</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>Refresh</string> <string>Refresh</string>
@ -194,7 +201,9 @@
</action> </action>
<action name="actionViewCentralModsFolder"> <action name="actionViewCentralModsFolder">
<property name="icon"> <property name="icon">
<iconset theme="centralmods"/> <iconset theme="centralmods">
<normaloff/>
</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>View Central Mods Folder</string> <string>View Central Mods Folder</string>
@ -208,7 +217,9 @@
</action> </action>
<action name="actionCheckUpdate"> <action name="actionCheckUpdate">
<property name="icon"> <property name="icon">
<iconset theme="checkupdate"/> <iconset theme="checkupdate">
<normaloff/>
</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>Check for Updates</string> <string>Check for Updates</string>
@ -222,7 +233,9 @@
</action> </action>
<action name="actionSettings"> <action name="actionSettings">
<property name="icon"> <property name="icon">
<iconset theme="settings"/> <iconset theme="settings">
<normaloff/>
</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>Settings</string> <string>Settings</string>
@ -239,7 +252,9 @@
</action> </action>
<action name="actionReportBug"> <action name="actionReportBug">
<property name="icon"> <property name="icon">
<iconset theme="bug"/> <iconset theme="bug">
<normaloff/>
</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>Report a Bug</string> <string>Report a Bug</string>
@ -253,7 +268,9 @@
</action> </action>
<action name="actionMoreNews"> <action name="actionMoreNews">
<property name="icon"> <property name="icon">
<iconset theme="news"/> <iconset theme="news">
<normaloff/>
</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>More News</string> <string>More News</string>
@ -270,7 +287,9 @@
</action> </action>
<action name="actionAbout"> <action name="actionAbout">
<property name="icon"> <property name="icon">
<iconset theme="about"/> <iconset theme="about">
<normaloff/>
</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>About MultiMC</string> <string>About MultiMC</string>
@ -463,7 +482,9 @@
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="icon"> <property name="icon">
<iconset theme="cat"/> <iconset theme="cat">
<normaloff/>
</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>Meow</string> <string>Meow</string>
@ -474,7 +495,9 @@
</action> </action>
<action name="actionCopyInstance"> <action name="actionCopyInstance">
<property name="icon"> <property name="icon">
<iconset theme="copy"/> <iconset theme="copy">
<normaloff/>
</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>Copy Instance</string> <string>Copy Instance</string>
@ -494,6 +517,17 @@
<string>Manage your Mojang or Minecraft accounts.</string> <string>Manage your Mojang or Minecraft accounts.</string>
</property> </property>
</action> </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> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<resources> <resources>

View File

@ -126,7 +126,7 @@ void AccountListDialog::addAccount(const QString& errMsg)
MojangAccountPtr account = MojangAccount::createFromUsername(username); MojangAccountPtr account = MojangAccount::createFromUsername(username);
ProgressDialog progDialog(this); ProgressDialog progDialog(this);
auto task = account->login(password); auto task = account->login(nullptr, password);
progDialog.exec(task.get()); progDialog.exec(task.get());
if(task->successful()) if(task->successful())
{ {

View File

@ -161,6 +161,8 @@ void OneSixModEditDialog::on_forgeBtn_clicked()
} }
VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this);
vselect.setFilter(1, m_inst->currentVersionId()); 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()) if (vselect.exec() && vselect.selectedVersion())
{ {
ForgeVersionPtr forgeVersion = ForgeVersionPtr forgeVersion =
@ -224,9 +226,9 @@ void OneSixModEditDialog::on_liteloaderBtn_clicked()
} }
if (!liteloader.add(m_inst)) if (!liteloader.add(m_inst))
{ {
QMessageBox::critical( QMessageBox::critical(this, tr("LiteLoader"),
this, tr("LiteLoader"), tr("For reasons unknown, the LiteLoader installation failed. "
tr("For reasons unknown, the LiteLoader installation failed. Check your MultiMC log files for details.")); "Check your MultiMC log files for details."));
} }
else else
{ {

View File

@ -51,6 +51,11 @@ VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title,
} }
} }
void VersionSelectDialog::setEmptyString(QString emptyString)
{
ui->listView->setEmptyString(emptyString);
}
VersionSelectDialog::~VersionSelectDialog() VersionSelectDialog::~VersionSelectDialog()
{ {
delete ui; delete ui;

View File

@ -44,6 +44,7 @@ public:
BaseVersionPtr selectedVersion() const; BaseVersionPtr selectedVersion() const;
void setFilter(int column, QString filter); void setFilter(int column, QString filter);
void setEmptyString(QString emptyString);
void setResizeOn(int column); void setResizeOn(int column);
private private

View File

@ -15,7 +15,7 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QTreeView" name="listView"> <widget class="VersionListView" name="listView">
<property name="horizontalScrollBarPolicy"> <property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum> <enum>Qt::ScrollBarAlwaysOff</enum>
</property> </property>
@ -65,6 +65,13 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>VersionListView</class>
<extends>QTreeView</extends>
<header>gui/widgets/VersionListView.h</header>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections> <connections>
<connection> <connection>

27
gui/widgets/Common.cpp Normal file
View File

@ -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;
}

6
gui/widgets/Common.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
#include <QStringList>
#include <QTextLayout>
QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
qreal &widthUsed);

View File

@ -19,30 +19,7 @@
#include <QTextLayout> #include <QTextLayout>
#include <QApplication> #include <QApplication>
#include <QtCore/qmath.h> #include <QtCore/qmath.h>
#include "Common.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();
}
#define QFIXED_MAX (INT_MAX / 256) #define QFIXED_MAX (INT_MAX / 256)
ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent) ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent)

View File

@ -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);
}
}
*/

View File

@ -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;
};

View File

@ -155,10 +155,10 @@ public:
virtual SettingsObject &settings() const; virtual SettingsObject &settings() const;
/// returns a valid update task /// 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. /// 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 /// do any necessary cleanups after the instance finishes. also runs before
/// 'prepareForLaunch' /// 'prepareForLaunch'

View File

@ -42,15 +42,15 @@ LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings,
settings->registerSetting("IntendedJarVersion", ""); 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. // make sure the jar mods list is initialized by asking for it.
auto list = jarModList(); auto list = jarModList();
// create an update task // 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); MinecraftProcess *proc = new MinecraftProcess(this);
@ -66,13 +66,14 @@ MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account)
if (settings().get("LaunchMaximized").toBool()) if (settings().get("LaunchMaximized").toBool())
windowParams = "max"; windowParams = "max";
else else
windowParams = QString("%1x%2").arg(settings().get("MinecraftWinWidth").toInt()).arg( windowParams = QString("%1x%2")
settings().get("MinecraftWinHeight").toInt()); .arg(settings().get("MinecraftWinWidth").toInt())
.arg(settings().get("MinecraftWinHeight").toInt());
QString lwjgl = QDir(MMC->settings()->get("LWJGLDir").toString() + "/" + lwjglVersion()) QString lwjgl = QDir(MMC->settings()->get("LWJGLDir").toString() + "/" + lwjglVersion())
.absolutePath(); .absolutePath();
launchScript += "userName " + account->currentProfile()->name + "\n"; launchScript += "userName " + account->player_name + "\n";
launchScript += "sessionId " + account->sessionId() + "\n"; launchScript += "sessionId " + account->session + "\n";
launchScript += "windowTitle " + windowTitle() + "\n"; launchScript += "windowTitle " + windowTitle() + "\n";
launchScript += "windowParams " + windowParams + "\n"; launchScript += "windowParams " + windowParams + "\n";
launchScript += "lwjgl " + lwjgl + "\n"; launchScript += "lwjgl " + lwjgl + "\n";

View File

@ -76,9 +76,9 @@ public:
virtual bool shouldUpdate() const override; virtual bool shouldUpdate() const override;
virtual void setShouldUpdate(bool val) 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 void cleanupAfterRun() override;
virtual QDialog *createModEditDialog(QWidget *parent) override; virtual QDialog *createModEditDialog(QWidget *parent) override;

View File

@ -27,13 +27,13 @@
#include "logger/QsLog.h" #include "logger/QsLog.h"
#include "logic/net/URLConstants.h" #include "logic/net/URLConstants.h"
LegacyUpdate::LegacyUpdate(BaseInstance *inst, bool only_prepare, QObject *parent) LegacyUpdate::LegacyUpdate(BaseInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
: Task(parent), m_inst(inst), m_only_prepare(only_prepare)
{ {
} }
void LegacyUpdate::executeTask() void LegacyUpdate::executeTask()
{ {
/*
if(m_only_prepare) if(m_only_prepare)
{ {
// FIXME: think this through some more. // FIXME: think this through some more.
@ -49,8 +49,9 @@ void LegacyUpdate::executeTask()
} }
else else
{ {
lwjglStart(); */
} lwjglStart();
//}
} }
void LegacyUpdate::lwjglStart() void LegacyUpdate::lwjglStart()
@ -268,7 +269,6 @@ void LegacyUpdate::jarStart()
auto dljob = new NetJob("Minecraft.jar for version " + version_id); auto dljob = new NetJob("Minecraft.jar for version " + version_id);
auto metacache = MMC->metacache(); auto metacache = MMC->metacache();
auto entry = metacache->resolveEntry("versions", localPath); auto entry = metacache->resolveEntry("versions", localPath);
dljob->addNetAction(CacheDownload::make(QUrl(urlstr), entry)); dljob->addNetAction(CacheDownload::make(QUrl(urlstr), entry));
@ -425,7 +425,7 @@ void LegacyUpdate::ModTheJar()
auto &mod = modList->operator[](i); auto &mod = modList->operator[](i);
// do not merge disabled mods. // do not merge disabled mods.
if(!mod.enabled()) if (!mod.enabled())
continue; continue;
if (mod.type() == Mod::MOD_ZIPFILE) if (mod.type() == Mod::MOD_ZIPFILE)

View File

@ -31,7 +31,7 @@ class LegacyUpdate : public Task
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit LegacyUpdate(BaseInstance *inst, bool only_prepare, QObject *parent = 0); explicit LegacyUpdate(BaseInstance *inst, QObject *parent = 0);
virtual void executeTask(); virtual void executeTask();
private private
@ -72,5 +72,4 @@ private:
private: private:
NetJobPtr legacyDownloadJob; NetJobPtr legacyDownloadJob;
BaseInstance *m_inst = nullptr; BaseInstance *m_inst = nullptr;
bool m_only_prepare = false;
}; };

View File

@ -79,26 +79,18 @@ void MinecraftProcess::setWorkdir(QString path)
QString MinecraftProcess::censorPrivateInfo(QString in) QString MinecraftProcess::censorPrivateInfo(QString in)
{ {
if(!m_account) if(!m_session)
return in; return in;
QString sessionId = m_account->sessionId(); if(m_session->session != "-")
QString accessToken = m_account->accessToken(); in.replace(m_session->session, "<SESSION ID>");
QString clientToken = m_account->clientToken(); in.replace(m_session->access_token, "<ACCESS TOKEN>");
in.replace(sessionId, "<SESSION ID>"); in.replace(m_session->client_token, "<CLIENT TOKEN>");
in.replace(accessToken, "<ACCESS TOKEN>"); in.replace(m_session->uuid, "<PROFILE ID>");
in.replace(clientToken, "<CLIENT TOKEN>"); in.replace(m_session->player_name, "<PROFILE NAME>");
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>");
}
auto i = m_account->user().properties.begin(); auto i = m_session->u.properties.begin();
while (i != m_account->user().properties.end()) while (i != m_session->u.properties.end())
{ {
in.replace(i.value(), "<" + i.key().toUpper() + ">"); in.replace(i.value(), "<" + i.key().toUpper() + ">");
++i; ++i;

View File

@ -78,9 +78,9 @@ public:
void killMinecraft(); void killMinecraft();
inline void setLogin(MojangAccountPtr account) inline void setLogin(AuthSessionPtr session)
{ {
m_account = account; m_session = session;
} }
signals: signals:
@ -117,7 +117,7 @@ protected:
QString m_out_leftover; QString m_out_leftover;
QProcess m_prepostlaunchprocess; QProcess m_prepostlaunchprocess;
bool killed = false; bool killed = false;
MojangAccountPtr m_account; AuthSessionPtr m_session;
QString launchScript; QString launchScript;
QString m_nativeFolder; QString m_nativeFolder;

View File

@ -104,7 +104,7 @@ bool OneSixFTBInstance::menuActionEnabled(QString action_name) const
return false; return false;
} }
std::shared_ptr<Task> OneSixFTBInstance::doUpdate(bool only_prepare) std::shared_ptr<Task> OneSixFTBInstance::doUpdate()
{ {
std::shared_ptr<SequentialTask> task; std::shared_ptr<SequentialTask> task;
task.reset(new SequentialTask(this)); 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(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))); 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. //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. //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; return task;
} }

View File

@ -13,7 +13,7 @@ public:
virtual QString getStatusbarDescription(); virtual QString getStatusbarDescription();
virtual bool menuActionEnabled(QString action_name) const; 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; virtual QString id() const;

View File

@ -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) QString replaceTokensIn(QString text, QMap<QString, QString> with)
@ -135,7 +135,7 @@ QDir OneSixInstance::reconstructAssets(std::shared_ptr<OneSixVersion> version)
return virtualRoot; return virtualRoot;
} }
QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account) QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session)
{ {
I_D(OneSixInstance); I_D(OneSixInstance);
auto version = d->version; auto version = d->version;
@ -147,17 +147,11 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account)
QMap<QString, QString> token_mapping; QMap<QString, QString> token_mapping;
// yggdrasil! // yggdrasil!
token_mapping["auth_username"] = account->username(); token_mapping["auth_username"] = session->username;
token_mapping["auth_session"] = account->sessionId(); token_mapping["auth_session"] = session->session;
token_mapping["auth_access_token"] = account->accessToken(); token_mapping["auth_access_token"] = session->access_token;
token_mapping["auth_player_name"] = account->currentProfile()->name; token_mapping["auth_player_name"] = session->player_name;
token_mapping["auth_uuid"] = account->currentProfile()->id; token_mapping["auth_uuid"] = session->uuid;
// this is for offline?:
/*
map["auth_player_name"] = "Player";
map["auth_player_name"] = "00000000-0000-0000-0000-000000000000";
*/
// these do nothing and are stupid. // these do nothing and are stupid.
token_mapping["profile_name"] = name(); token_mapping["profile_name"] = name();
@ -168,17 +162,8 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account)
QString absAssetsDir = QDir("assets/").absolutePath(); QString absAssetsDir = QDir("assets/").absolutePath();
token_mapping["game_assets"] = reconstructAssets(d->version).absolutePath(); token_mapping["game_assets"] = reconstructAssets(d->version).absolutePath();
auto user = account->user(); token_mapping["user_properties"] = session->serializeUserProperties();
QJsonObject userAttrs; token_mapping["user_type"] = session->user_type;
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";
// 1.7.3+ assets tokens // 1.7.3+ assets tokens
token_mapping["assets_root"] = absAssetsDir; token_mapping["assets_root"] = absAssetsDir;
token_mapping["assets_index_name"] = version->assets; token_mapping["assets_index_name"] = version->assets;
@ -191,7 +176,7 @@ QStringList OneSixInstance::processMinecraftArgs(MojangAccountPtr account)
return parts; return parts;
} }
MinecraftProcess *OneSixInstance::prepareForLaunch(MojangAccountPtr account) MinecraftProcess *OneSixInstance::prepareForLaunch(AuthSessionPtr session)
{ {
I_D(OneSixInstance); I_D(OneSixInstance);
@ -216,7 +201,7 @@ MinecraftProcess *OneSixInstance::prepareForLaunch(MojangAccountPtr account)
} }
launchScript += "mainClass " + version->mainClass + "\n"; launchScript += "mainClass " + version->mainClass + "\n";
for (auto param : processMinecraftArgs(account)) for (auto param : processMinecraftArgs(session))
{ {
launchScript += "param " + param + "\n"; launchScript += "param " + param + "\n";
} }

View File

@ -36,8 +36,8 @@ public:
QString loaderModsDir() const; QString loaderModsDir() const;
virtual QString instanceConfigFolder() const override; virtual QString instanceConfigFolder() const 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 session) override;
virtual void cleanupAfterRun() override; virtual void cleanupAfterRun() override;
@ -72,6 +72,6 @@ signals:
void versionReloaded(); void versionReloaded();
private: private:
QStringList processMinecraftArgs(MojangAccountPtr account); QStringList processMinecraftArgs(AuthSessionPtr account);
QDir reconstructAssets(std::shared_ptr<OneSixVersion> version); QDir reconstructAssets(std::shared_ptr<OneSixVersion> version);
}; };

View File

@ -35,8 +35,8 @@
#include "pathutils.h" #include "pathutils.h"
#include <JlCompress.h> #include <JlCompress.h>
OneSixUpdate::OneSixUpdate(BaseInstance *inst, bool only_prepare, QObject *parent) OneSixUpdate::OneSixUpdate(BaseInstance *inst, QObject *parent)
: Task(parent), m_inst(inst), m_only_prepare(only_prepare) : Task(parent), m_inst(inst)
{ {
} }
@ -52,12 +52,6 @@ void OneSixUpdate::executeTask()
return; return;
} }
if (m_only_prepare)
{
prepareForLaunch();
return;
}
if (m_inst->shouldUpdate()) if (m_inst->shouldUpdate())
{ {
// Get a pointer to the version object that corresponds to the instance's version. // Get a pointer to the version object that corresponds to the instance's version.
@ -222,7 +216,7 @@ void OneSixUpdate::assetIndexFailed()
void OneSixUpdate::assetsFinished() void OneSixUpdate::assetsFinished()
{ {
prepareForLaunch(); emitSucceeded();
} }
void OneSixUpdate::assetsFailed() void OneSixUpdate::assetsFailed()
@ -330,43 +324,3 @@ void OneSixUpdate::jarlibFailed()
emitFailed("Failed to download the following files:\n" + failed_all + emitFailed("Failed to download the following files:\n" + failed_all +
"\n\nPlease try again."); "\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();
}

View File

@ -29,7 +29,7 @@ class OneSixUpdate : public Task
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit OneSixUpdate(BaseInstance *inst, bool prepare_for_launch, QObject *parent = 0); explicit OneSixUpdate(BaseInstance *inst, QObject *parent = 0);
virtual void executeTask(); virtual void executeTask();
private private
@ -49,9 +49,6 @@ slots:
void assetsFinished(); void assetsFinished();
void assetsFailed(); void assetsFailed();
// extract the appropriate libraries
void prepareForLaunch();
private: private:
NetJobPtr specificVersionDownloadJob; NetJobPtr specificVersionDownloadJob;
NetJobPtr jarlibDownloadJob; NetJobPtr jarlibDownloadJob;
@ -59,5 +56,4 @@ private:
// target version, determined during this task // target version, determined during this task
std::shared_ptr<MinecraftVersion> targetVersion; std::shared_ptr<MinecraftVersion> targetVersion;
BaseInstance *m_inst = nullptr; BaseInstance *m_inst = nullptr;
bool m_only_prepare = false;
}; };

View File

@ -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;
}

49
logic/auth/AuthSession.h Normal file
View File

@ -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;

View File

@ -24,6 +24,7 @@
#include <QJsonArray> #include <QJsonArray>
#include <QRegExp> #include <QRegExp>
#include <QStringList> #include <QStringList>
#include <QJsonDocument>
#include <logger/QsLog.h> #include <logger/QsLog.h>
@ -165,15 +166,26 @@ AccountStatus MojangAccount::accountStatus() const
{ {
if (m_accessToken.isEmpty()) if (m_accessToken.isEmpty())
return NotVerified; return NotVerified;
if (!m_online) else
return Verified; 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) Q_ASSERT(m_currentTask.get() == nullptr);
return m_currentTask;
// 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()) if (password.isEmpty())
{ {
m_currentTask.reset(new RefreshTask(this)); 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.reset(new AuthenticateTask(this, password));
} }
m_currentTask->assignSession(session);
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
return m_currentTask; return m_currentTask;
@ -189,24 +203,76 @@ std::shared_ptr<YggdrasilTask> MojangAccount::login(QString password)
void MojangAccount::authSucceeded() 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(); m_currentTask.reset();
emit changed(); emit changed();
} }
void MojangAccount::authFailed(QString reason) void MojangAccount::authFailed(QString reason)
{ {
auto session = m_currentTask->getAssignedSession();
// This is emitted when the yggdrasil tasks time out or are cancelled. // This is emitted when the yggdrasil tasks time out or are cancelled.
// -> we treat the error as no-op // -> we treat the error as no-op
if (reason == "Yggdrasil task cancelled.") 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 else
{ {
m_online = false;
m_accessToken = QString(); m_accessToken = QString();
emit changed(); emit changed();
if (session)
{
session->status = AuthSession::RequiresPassword;
session->auth_server_online = true;
fillSession(session);
}
} }
m_currentTask.reset(); 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();
}

View File

@ -23,6 +23,7 @@
#include <QMap> #include <QMap>
#include <memory> #include <memory>
#include "AuthSession.h"
class Task; class Task;
class YggdrasilTask; class YggdrasilTask;
@ -45,17 +46,10 @@ struct AccountProfile
bool legacy; bool legacy;
}; };
struct User
{
QString id;
QMultiMap<QString,QString> properties;
};
enum AccountStatus enum AccountStatus
{ {
NotVerified, NotVerified,
Verified, Verified
Online
}; };
/** /**
@ -84,7 +78,7 @@ public: /* construction */
QJsonObject saveToJson() const; QJsonObject saveToJson() const;
public: /* manipulation */ public: /* manipulation */
/** /**
* Sets the currently selected profile to the profile with the given ID string. * 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 * If profileId is not in the list of available profiles, the function will simply return
* false. * false.
@ -95,12 +89,9 @@ public: /* manipulation */
* Attempt to login. Empty password means we use the token. * Attempt to login. Empty password means we use the token.
* If the attempt fails because we already are performing some task, it returns false. * 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 */ public: /* queries */
const QString &username() const const QString &username() const
{ {
@ -122,19 +113,11 @@ public: /* queries */
return m_profiles; return m_profiles;
} }
const User & user() const User &user()
{ {
return m_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) //! Returns the currently selected profile (if none, returns nullptr)
const AccountProfile *currentProfile() const; const AccountProfile *currentProfile() const;
@ -169,16 +152,17 @@ protected: /* variables */
// the user structure, whatever it is. // the user structure, whatever it is.
User m_user; User m_user;
// true when the account is verified
bool m_online = false;
// current task we are executing here // current task we are executing here
std::shared_ptr<YggdrasilTask> m_currentTask; std::shared_ptr<YggdrasilTask> m_currentTask;
private slots: private
slots:
void authSucceeded(); void authSucceeded();
void authFailed(QString reason); void authFailed(QString reason);
private:
void fillSession(AuthSessionPtr session);
public: public:
friend class YggdrasilTask; friend class YggdrasilTask;
friend class AuthenticateTask; friend class AuthenticateTask;

View File

@ -35,6 +35,21 @@ class YggdrasilTask : public Task
public: public:
explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0); 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. * Class describing a Yggdrasil error response.
*/ */
@ -117,4 +132,6 @@ protected:
const int timeout_max = 10000; const int timeout_max = 10000;
const int time_step = 50; const int time_step = 50;
AuthSessionPtr m_session;
}; };

View File

@ -25,8 +25,7 @@
#include "logger/QsLog.h" #include "logger/QsLog.h"
RefreshTask::RefreshTask(MojangAccount *account, QObject *parent) RefreshTask::RefreshTask(MojangAccount *account) : YggdrasilTask(account)
: YggdrasilTask(account, parent)
{ {
} }
@ -126,7 +125,6 @@ bool RefreshTask::processResponse(QJsonObject responseData)
m_account->m_user = u; m_account->m_user = u;
} }
// We've made it through the minefield of possible errors. Return true to indicate that // We've made it through the minefield of possible errors. Return true to indicate that
// we've succeeded. // we've succeeded.
QLOG_DEBUG() << "Finished reading refresh response."; QLOG_DEBUG() << "Finished reading refresh response.";

View File

@ -30,7 +30,7 @@ class RefreshTask : public YggdrasilTask
{ {
Q_OBJECT Q_OBJECT
public: public:
RefreshTask(MojangAccount * account, QObject *parent = 0); RefreshTask(MojangAccount * account);
protected: protected:
virtual QJsonObject getRequestContent() const; virtual QJsonObject getRequestContent() const;
@ -41,3 +41,4 @@ protected:
QString getStateMessage(const YggdrasilTask::State state) const; QString getStateMessage(const YggdrasilTask::State state) const;
}; };

View File

@ -31,7 +31,6 @@ void NetJob::partSucceeded(int index)
num_succeeded++; num_succeeded++;
QLOG_INFO() << m_job_name.toLocal8Bit() << "progress:" << num_succeeded << "/" QLOG_INFO() << m_job_name.toLocal8Bit() << "progress:" << num_succeeded << "/"
<< downloads.size(); << downloads.size();
emit filesProgress(num_succeeded, num_failed, downloads.size());
if (num_failed + num_succeeded == 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 << ")"; QLOG_ERROR() << "Part" << index << "failed 3 times (" << downloads[index]->m_url << ")";
num_failed++; num_failed++;
emit filesProgress(num_succeeded, num_failed, downloads.size());
if (num_failed + num_succeeded == downloads.size()) if (num_failed + num_succeeded == downloads.size())
{ {
QLOG_ERROR() << m_job_name.toLocal8Bit() << "failed."; QLOG_ERROR() << m_job_name.toLocal8Bit() << "failed.";

View File

@ -84,7 +84,6 @@ public:
{ {
return m_job_name; return m_job_name;
} }
;
virtual bool isRunning() const virtual bool isRunning() const
{ {
return m_running; return m_running;
@ -94,7 +93,6 @@ public:
signals: signals:
void started(); void started();
void progress(qint64 current, qint64 total); void progress(qint64 current, qint64 total);
void filesProgress(int, int, int);
void succeeded(); void succeeded();
void failed(); void failed();
public public