From 984c36e571aae45cdd55da2fb689538198aadd3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Mon, 23 Sep 2013 00:23:50 +0200 Subject: Implement basic yggdrasil auth. No fancy login token saving involved. --- CMakeLists.txt | 5 +- MultiMC.cpp | 158 +++++++++++++------------- MultiMC.h | 1 + gui/mainwindow.cpp | 8 +- gui/mainwindow.h | 2 +- logic/BaseInstance.h | 3 +- logic/InstanceLauncher.cpp | 4 +- logic/LegacyInstance.cpp | 6 +- logic/LegacyInstance.h | 2 +- logic/MinecraftProcess.cpp | 83 +++++++------- logic/OneSixInstance.cpp | 20 ++-- logic/OneSixInstance.h | 4 +- logic/net/LoginTask.cpp | 269 +++++++++++++++++++++++++++++++++++++++++++++ logic/net/LoginTask.h | 62 +++++++++++ logic/tasks/LoginTask.cpp | 111 ------------------- logic/tasks/LoginTask.h | 58 ---------- 16 files changed, 482 insertions(+), 314 deletions(-) create mode 100644 logic/net/LoginTask.cpp create mode 100644 logic/net/LoginTask.h delete mode 100644 logic/tasks/LoginTask.cpp delete mode 100644 logic/tasks/LoginTask.h diff --git a/CMakeLists.txt b/CMakeLists.txt index aa7a91f6..04886184 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,6 +226,8 @@ logic/net/DownloadJob.h logic/net/DownloadJob.cpp logic/net/HttpMetaCache.h logic/net/HttpMetaCache.cpp +logic/net/LoginTask.h +logic/net/LoginTask.cpp # legacy instances logic/LegacyInstance.h @@ -281,8 +283,7 @@ logic/EnabledItemFilter.cpp logic/tasks/ProgressProvider.h logic/tasks/Task.h logic/tasks/Task.cpp -logic/tasks/LoginTask.h -logic/tasks/LoginTask.cpp + ) diff --git a/MultiMC.cpp b/MultiMC.cpp index decc22bf..ee9a9bf8 100644 --- a/MultiMC.cpp +++ b/MultiMC.cpp @@ -16,7 +16,6 @@ #include "logic/InstanceLauncher.h" #include "logic/net/HttpMetaCache.h" - #include "pathutils.h" #include "cmdutils.h" #include @@ -25,23 +24,22 @@ #include "config.h" using namespace Util::Commandline; -MultiMC::MultiMC ( int& argc, char** argv ) - :QApplication ( argc, argv ) +MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) { setOrganizationName("Forkk"); setApplicationName("MultiMC 5"); - + initTranslations(); - + // Print app header std::cout << "MultiMC 5" << std::endl; std::cout << "(c) 2013 MultiMC Contributors" << std::endl << std::endl; - + // Commandline parsing QHash args; { Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals); - + // --help parser.addSwitch("help"); parser.addShortOpt("help", 'h'); @@ -53,33 +51,37 @@ MultiMC::MultiMC ( int& argc, char** argv ) // --dir parser.addOption("dir", applicationDirPath()); parser.addShortOpt("dir", 'd'); - parser.addDocumentation("dir", "use the supplied directory as MultiMC root instead of the binary location (use '.' for current)"); + parser.addDocumentation("dir", "use the supplied directory as MultiMC root instead of " + "the binary location (use '.' for current)"); // --update parser.addOption("update"); parser.addShortOpt("update", 'u'); - parser.addDocumentation("update", "replaces the given file with the running executable", ""); + parser.addDocumentation("update", "replaces the given file with the running executable", + ""); // --quietupdate parser.addSwitch("quietupdate"); parser.addShortOpt("quietupdate", 'U'); - parser.addDocumentation("quietupdate", "doesn't restart MultiMC after installing updates"); + parser.addDocumentation("quietupdate", + "doesn't restart MultiMC after installing updates"); // --launch parser.addOption("launch"); parser.addShortOpt("launch", 'l'); parser.addDocumentation("launch", "tries to launch the given instance", ""); - + // parse the arguments try { args = parser.parse(arguments()); } - catch(ParsingError e) + catch (ParsingError e) { std::cerr << "CommandLineError: " << e.what() << std::endl; - std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters." << std::endl; + std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters." + << std::endl; m_status = MultiMC::Failed; return; } - + // display help and exit if (args["help"].toBool()) { @@ -87,27 +89,29 @@ MultiMC::MultiMC ( int& argc, char** argv ) m_status = MultiMC::Succeeded; return; } - + // display version and exit if (args["version"].toBool()) { std::cout << "Version " << VERSION_STR << std::endl; std::cout << "Git " << GIT_COMMIT << std::endl; - std::cout << "Tag: " << JENKINS_BUILD_TAG << " " << (ARCH==x64?"x86_64":"x86") << std::endl; + std::cout << "Tag: " << JENKINS_BUILD_TAG << " " << (ARCH == x64 ? "x86_64" : "x86") + << std::endl; m_status = MultiMC::Succeeded; return; } - + // update // Note: cwd is always the current executable path! if (!args["update"].isNull()) { - std::cout << "Performing MultiMC update: " << qPrintable(args["update"].toString()) << std::endl; + std::cout << "Performing MultiMC update: " << qPrintable(args["update"].toString()) + << std::endl; QString cwd = QDir::currentPath(); QDir::setCurrent(applicationDirPath()); QFile file(applicationFilePath()); file.copy(args["update"].toString()); - if(args["quietupdate"].toBool()) + if (args["quietupdate"].toBool()) { m_status = MultiMC::Succeeded; return; @@ -115,31 +119,31 @@ MultiMC::MultiMC ( int& argc, char** argv ) QDir::setCurrent(cwd); } } - + // change directory QDir::setCurrent(args["dir"].toString()); - + // load settings initGlobalSettings(); - + // and instances - m_instances.reset(new InstanceList(m_settings->get("InstanceDir").toString(),this)); + m_instances.reset(new InstanceList(m_settings->get("InstanceDir").toString(), this)); std::cout << "Loading Instances..." << std::endl; m_instances->loadList(); - + // init the http meta cache initHttpMetaCache(); - + // create the global network manager m_qnam.reset(new QNetworkAccessManager(this)); - + // Register meta types. qRegisterMetaType("LoginResponse"); - + // launch instance, if that's what should be done if (!args["launch"].isNull()) { - if(InstanceLauncher(args["launch"].toString()).launch()) + if (InstanceLauncher(args["launch"].toString()).launch()) m_status = MultiMC::Succeeded; else m_status = MultiMC::Failed; @@ -150,11 +154,11 @@ MultiMC::MultiMC ( int& argc, char** argv ) MultiMC::~MultiMC() { - if(m_mmc_translator) + if (m_mmc_translator) { removeTranslator(m_mmc_translator.data()); } - if(m_qt_translator) + if (m_qt_translator) { removeTranslator(m_qt_translator.data()); } @@ -163,13 +167,12 @@ MultiMC::~MultiMC() void MultiMC::initTranslations() { m_qt_translator.reset(new QTranslator()); - if(m_qt_translator->load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + if (m_qt_translator->load("qt_" + QLocale::system().name(), + QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { - std::cout - << "Loading Qt Language File for " - << QLocale::system().name().toLocal8Bit().constData() - << "..."; - if(!installTranslator(m_qt_translator.data())) + std::cout << "Loading Qt Language File for " + << QLocale::system().name().toLocal8Bit().constData() << "..."; + if (!installTranslator(m_qt_translator.data())) { std::cout << " failed."; m_qt_translator.reset(); @@ -182,13 +185,12 @@ void MultiMC::initTranslations() } m_mmc_translator.reset(new QTranslator()); - if(m_mmc_translator->load("mmc_" + QLocale::system().name(), QDir("translations").absolutePath())) + if (m_mmc_translator->load("mmc_" + QLocale::system().name(), + QDir("translations").absolutePath())) { - std::cout - << "Loading MMC Language File for " - << QLocale::system().name().toLocal8Bit().constData() - << "..."; - if(!installTranslator(m_mmc_translator.data())) + std::cout << "Loading MMC Language File for " + << QLocale::system().name().toLocal8Bit().constData() << "..."; + if (!installTranslator(m_mmc_translator.data())) { std::cout << " failed."; m_mmc_translator.reset(); @@ -201,58 +203,66 @@ void MultiMC::initTranslations() } } - void MultiMC::initGlobalSettings() { m_settings.reset(new INISettingsObject("multimc.cfg", this)); - // Updates + // Updates m_settings->registerSetting(new Setting("UseDevBuilds", false)); m_settings->registerSetting(new Setting("AutoUpdate", true)); - + // Folders m_settings->registerSetting(new Setting("InstanceDir", "instances")); m_settings->registerSetting(new Setting("CentralModsDir", "mods")); m_settings->registerSetting(new Setting("LWJGLDir", "lwjgl")); - + // Console m_settings->registerSetting(new Setting("ShowConsole", true)); m_settings->registerSetting(new Setting("AutoCloseConsole", true)); - + // Toolbar settings m_settings->registerSetting(new Setting("InstanceToolbarVisible", true)); m_settings->registerSetting(new Setting("InstanceToolbarPosition", QPoint())); - + // Console Colors -// m_settings->registerSetting(new Setting("SysMessageColor", QColor(Qt::blue))); -// m_settings->registerSetting(new Setting("StdOutColor", QColor(Qt::black))); -// m_settings->registerSetting(new Setting("StdErrColor", QColor(Qt::red))); - + // m_settings->registerSetting(new Setting("SysMessageColor", QColor(Qt::blue))); + // m_settings->registerSetting(new Setting("StdOutColor", QColor(Qt::black))); + // m_settings->registerSetting(new Setting("StdErrColor", QColor(Qt::red))); + // Window Size m_settings->registerSetting(new Setting("LaunchMaximized", false)); m_settings->registerSetting(new Setting("MinecraftWinWidth", 854)); m_settings->registerSetting(new Setting("MinecraftWinHeight", 480)); - + // Auto login m_settings->registerSetting(new Setting("AutoLogin", false)); - + // Memory m_settings->registerSetting(new Setting("MinMemAlloc", 512)); m_settings->registerSetting(new Setting("MaxMemAlloc", 1024)); m_settings->registerSetting(new Setting("PermGen", 64)); - + // Java Settings m_settings->registerSetting(new Setting("JavaPath", "java")); m_settings->registerSetting(new Setting("JvmArgs", "")); - + // Custom Commands m_settings->registerSetting(new Setting("PreLaunchCommand", "")); m_settings->registerSetting(new Setting("PostExitCommand", "")); - + // The cat m_settings->registerSetting(new Setting("TheCat", false)); - + // Shall the main window hide on instance launch m_settings->registerSetting(new Setting("NoHide", false)); + + // Persistent value for the client ID + m_settings->registerSetting(new Setting("YggdrasilClientToken", "")); + QString currentYggID = m_settings->get("YggdrasilClientToken").toString(); + if (currentYggID.isEmpty()) + { + QUuid uuid = QUuid::createUuid(); + m_settings->set("YggdrasilClientToken", uuid.toString()); + } } void MultiMC::initHttpMetaCache() @@ -265,10 +275,9 @@ void MultiMC::initHttpMetaCache() m_metacache->Load(); } - QSharedPointer MultiMC::icons() { - if ( !m_icons ) + if (!m_icons) { m_icons.reset(new IconList); } @@ -277,7 +286,7 @@ QSharedPointer MultiMC::icons() QSharedPointer MultiMC::lwjgllist() { - if ( !m_lwjgllist ) + if (!m_lwjgllist) { m_lwjgllist.reset(new LWJGLVersionList()); } @@ -286,7 +295,7 @@ QSharedPointer MultiMC::lwjgllist() QSharedPointer MultiMC::forgelist() { - if ( !m_forgelist ) + if (!m_forgelist) { m_forgelist.reset(new ForgeVersionList()); } @@ -295,36 +304,31 @@ QSharedPointer MultiMC::forgelist() QSharedPointer MultiMC::minecraftlist() { - if ( !m_minecraftlist ) + if (!m_minecraftlist) { m_minecraftlist.reset(new MinecraftVersionList()); } return m_minecraftlist; } - int main(int argc, char *argv[]) { // initialize Qt MultiMC app(argc, argv); - + // show main window MainWindow mainWin; mainWin.show(); - - - - switch(app.status()) + + switch (app.status()) { - case MultiMC::Initialized: - return app.exec(); - case MultiMC::Failed: - return 1; - case MultiMC::Succeeded: - return 0; + case MultiMC::Initialized: + return app.exec(); + case MultiMC::Failed: + return 1; + case MultiMC::Succeeded: + return 0; } } #include "MultiMC.moc" - - diff --git a/MultiMC.h b/MultiMC.h index 1c1298e2..c634dd33 100644 --- a/MultiMC.h +++ b/MultiMC.h @@ -71,6 +71,7 @@ public: QSharedPointer forgelist(); QSharedPointer minecraftlist(); + private: void initGlobalSettings(); diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index d3b167fa..d7b77c8b 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -54,7 +54,7 @@ #include "logic/lists/LwjglVersionList.h" #include "logic/lists/IconList.h" -#include "logic/tasks/LoginTask.h" +#include "logic/net/LoginTask.h" #include "logic/BaseInstance.h" #include "logic/InstanceFactory.h" #include "logic/MinecraftProcess.h" @@ -491,7 +491,7 @@ void MainWindow::doLogin(const QString& errorMsg) QString user = loginDlg->getUsername(); if (user.length() == 0) user = QString("Offline"); - m_activeLogin = {user, QString("Offline"), qint64(-1)}; + m_activeLogin = {user, QString("Offline"), QString(), QString()}; m_activeInst = m_selectedInstance; launchInstance(m_activeInst, m_activeLogin); } @@ -533,7 +533,7 @@ void MainWindow::launchInstance(BaseInstance *instance, LoginResponse response) { Q_ASSERT_X(instance != NULL, "launchInstance", "instance is NULL"); - proc = instance->prepareForLaunch(response.username, response.sessionID); + proc = instance->prepareForLaunch(response); if(!proc) return; @@ -552,7 +552,7 @@ void MainWindow::launchInstance(BaseInstance *instance, LoginResponse response) connect(proc, SIGNAL(log(QString, MessageLevel::Enum)), console, SLOT(write(QString, MessageLevel::Enum))); connect(proc, SIGNAL(ended()), this, SLOT(instanceEnded())); - proc->setLogin(m_activeLogin.username, m_activeLogin.sessionID); + proc->setLogin(response.username, response.session_id); proc->launch(); } diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 2b0e1d34..cc9b0b7b 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -19,7 +19,7 @@ #include #include "logic/lists/InstanceList.h" -#include "logic/tasks/LoginTask.h" +#include "logic/net/LoginTask.h" #include "logic/BaseInstance.h" class LabeledToolButton; diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index 374d1437..0056327a 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -22,6 +22,7 @@ #include "inifile.h" #include "lists/BaseVersionList.h" +#include "net/LoginTask.h" class QDialog; class BaseUpdate; @@ -147,7 +148,7 @@ public: virtual BaseUpdate* doUpdate() = 0; /// returns a valid minecraft process, ready for launch - virtual MinecraftProcess* prepareForLaunch(QString user, QString session) = 0; + virtual MinecraftProcess* prepareForLaunch(LoginResponse response) = 0; /// do any necessary cleanups after the instance finishes. also runs before 'prepareForLaunch' virtual void cleanupAfterRun() = 0; diff --git a/logic/InstanceLauncher.cpp b/logic/InstanceLauncher.cpp index f2f792c9..93b87f23 100644 --- a/logic/InstanceLauncher.cpp +++ b/logic/InstanceLauncher.cpp @@ -5,7 +5,7 @@ #include "gui/logindialog.h" #include "gui/ProgressDialog.h" #include "gui/consolewindow.h" -#include "logic/tasks/LoginTask.h" +#include "logic/net/LoginTask.h" #include "logic/MinecraftProcess.h" #include "lists/InstanceList.h" @@ -25,7 +25,7 @@ void InstanceLauncher::onLoginComplete() LoginTask * task = ( LoginTask * ) QObject::sender(); auto result = task->getResult(); auto instance = MMC->instances()->getInstanceById(instId); - proc = instance->prepareForLaunch ( result.username, result.sessionID ); + proc = instance->prepareForLaunch ( result ); if ( !proc ) { //FIXME: report error diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index 0672d2c8..4f367980 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -29,7 +29,7 @@ BaseUpdate* LegacyInstance::doUpdate() return new LegacyUpdate(this, this); } -MinecraftProcess* LegacyInstance::prepareForLaunch(QString user, QString session) +MinecraftProcess* LegacyInstance::prepareForLaunch(LoginResponse response) { MinecraftProcess * proc = new MinecraftProcess(this); @@ -73,8 +73,8 @@ MinecraftProcess* LegacyInstance::prepareForLaunch(QString user, QString session args << QString("-Xmx%1m").arg(settings().get("MaxMemAlloc").toInt()); args << QString("-XX:PermSize=%1m").arg(settings().get("PermGen").toInt()); args << "-jar" << LAUNCHER_FILE; - args << user; - args << session; + args << response.player_name; + args << response.session_id; args << windowTitle; args << windowSize; args << lwjgl; diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h index b36026fc..2eab9035 100644 --- a/logic/LegacyInstance.h +++ b/logic/LegacyInstance.h @@ -57,7 +57,7 @@ public: virtual void setShouldUpdate(bool val); virtual BaseUpdate* doUpdate(); - virtual MinecraftProcess* prepareForLaunch( QString user, QString session ); + virtual MinecraftProcess* prepareForLaunch(LoginResponse response); virtual void cleanupAfterRun(); virtual QDialog * createModEditDialog ( QWidget* parent ); diff --git a/logic/MinecraftProcess.cpp b/logic/MinecraftProcess.cpp index 299f00be..06b7a1f1 100644 --- a/logic/MinecraftProcess.cpp +++ b/logic/MinecraftProcess.cpp @@ -32,46 +32,45 @@ #define IBUS "@im=ibus" // constructor -MinecraftProcess::MinecraftProcess( BaseInstance* inst ) : - m_instance(inst) +MinecraftProcess::MinecraftProcess(BaseInstance *inst) : m_instance(inst) { - connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(finish(int, QProcess::ExitStatus))); - + connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), + SLOT(finish(int, QProcess::ExitStatus))); + // prepare the process environment QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - + #ifdef LINUX // Strip IBus if (env.value("XMODIFIERS").contains(IBUS)) env.insert("XMODIFIERS", env.value("XMODIFIERS").replace(IBUS, "")); #endif - + // export some infos env.insert("INST_NAME", inst->name()); env.insert("INST_ID", inst->id()); env.insert("INST_DIR", QDir(inst->instanceRoot()).absolutePath()); - + this->setProcessEnvironment(env); m_prepostlaunchprocess.setProcessEnvironment(env); - + // std channels connect(this, SIGNAL(readyReadStandardError()), SLOT(on_stdErr())); connect(this, SIGNAL(readyReadStandardOutput()), SLOT(on_stdOut())); } -void MinecraftProcess::setMinecraftArguments ( QStringList args ) +void MinecraftProcess::setMinecraftArguments(QStringList args) { m_args = args; } -void MinecraftProcess::setMinecraftWorkdir ( QString path ) +void MinecraftProcess::setMinecraftWorkdir(QString path) { QDir mcDir(path); this->setWorkingDirectory(mcDir.absolutePath()); m_prepostlaunchprocess.setWorkingDirectory(mcDir.absolutePath()); } - // console window void MinecraftProcess::on_stdErr() { @@ -80,18 +79,17 @@ void MinecraftProcess::on_stdErr() m_err_leftover.clear(); QStringList lines = str.split("\n"); bool complete = str.endsWith("\n"); - - for(int i = 0; i < lines.size() - 1; i++) + + for (int i = 0; i < lines.size() - 1; i++) { - QString & line = lines[i]; - emit log(line.replace(username, "").replace(sessionID, "").toLocal8Bit(), getLevel(line, MessageLevel::Error)); + QString &line = lines[i]; + emit log(line /*.replace(username, "").replace(sessionID, "")*/, + getLevel(line, MessageLevel::Error)); } - if(!complete) + if (!complete) m_err_leftover = lines.last(); } - - void MinecraftProcess::on_stdOut() { QByteArray data = readAllStandardOutput(); @@ -99,13 +97,14 @@ void MinecraftProcess::on_stdOut() m_out_leftover.clear(); QStringList lines = str.split("\n"); bool complete = str.endsWith("\n"); - - for(int i = 0; i < lines.size() - 1; i++) + + for (int i = 0; i < lines.size() - 1; i++) { - QString & line = lines[i]; - emit log(line.replace(username, "").replace(sessionID, "").toLocal8Bit(), getLevel(line, MessageLevel::Message)); + QString &line = lines[i]; + emit log(line /*.replace(username, "").replace(sessionID, "")*/, + getLevel(line, MessageLevel::Message)); } - if(!complete) + if (!complete) m_out_leftover = lines.last(); } @@ -114,20 +113,20 @@ void MinecraftProcess::finish(int code, ExitStatus status) { if (status != NormalExit) { - //TODO: error handling + // TODO: error handling } - + // TODO: Localization - + if (!killed) //: Message displayed on instance exit emit log(tr("Minecraft exited with exitcode %1.").arg(status)); else //: Message displayed after the instance exits due to kill request emit log(tr("Minecraft was killed by user."), MessageLevel::Error); - + m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code)); - + // run post-exit if (!m_instance->settings().get("PostExitCommand").toString().isEmpty()) { @@ -135,7 +134,7 @@ void MinecraftProcess::finish(int code, ExitStatus status) m_prepostlaunchprocess.waitForFinished(); if (m_prepostlaunchprocess.exitStatus() != NormalExit) { - //TODO: error handling + // TODO: error handling } } m_instance->cleanupAfterRun(); @@ -156,40 +155,42 @@ void MinecraftProcess::launch() m_prepostlaunchprocess.waitForFinished(); if (m_prepostlaunchprocess.exitStatus() != NormalExit) { - //TODO: error handling + // TODO: error handling return; } } - + m_instance->setLastLaunch(); - + emit log(QString("Minecraft folder is: '%1'").arg(workingDirectory())); QString JavaPath = m_instance->settings().get("JavaPath").toString(); emit log(QString("Java path: '%1'").arg(JavaPath)); - emit log(QString("Arguments: '%1'").arg(m_args.join("' '").replace(username, "").replace(sessionID, ""))); + emit log(QString("Arguments: '%1'").arg( + m_args.join("' '") /*.replace(username, "").replace(sessionID, "")*/)); start(JavaPath, m_args); if (!waitForStarted()) { //: Error message displayed if instace can't start emit log(tr("Could not launch minecraft!")); return; - //TODO: error handling + // TODO: error handling } } MessageLevel::Enum MinecraftProcess::getLevel(const QString &line, MessageLevel::Enum level) { - - if(line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") || line.contains("[FINEST]") ) + + if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || + line.contains("[FINER]") || line.contains("[FINEST]")) level = MessageLevel::Message; - if(line.contains("[SEVERE]") || line.contains("[STDERR]")) + if (line.contains("[SEVERE]") || line.contains("[STDERR]")) level = MessageLevel::Error; - if(line.contains("[WARNING]")) + if (line.contains("[WARNING]")) level = MessageLevel::Warning; - if(line.contains("Exception in thread") || line.contains(" at ")) + if (line.contains("Exception in thread") || line.contains(" at ")) level = MessageLevel::Fatal; - if(line.contains("[DEBUG]")) + if (line.contains("[DEBUG]")) level = MessageLevel::Debug; return level; - } \ No newline at end of file diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index e22a8890..6e39b5b5 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -49,21 +49,19 @@ QString replaceTokensIn(QString text, QMap with) return result; } -QStringList OneSixInstance::processMinecraftArgs(QString user, QString session) +QStringList OneSixInstance::processMinecraftArgs(LoginResponse response) { I_D(OneSixInstance); auto version = d->version; QString args_pattern = version->minecraftArguments; QMap token_mapping; - token_mapping["auth_username"] = user; - token_mapping["auth_session"] = session; - // FIXME: user and player name are DIFFERENT! - token_mapping["auth_player_name"] = user; - // FIXME: WTF is this. I just plugged in a random UUID here. - token_mapping["auth_uuid"] = "7d4bacf0-fd62-11e2-b778-0800200c9a66"; // obviously fake. - - // this is for offline: + token_mapping["auth_username"] = response.username; + token_mapping["auth_session"] = response.session_id; + token_mapping["auth_player_name"] = response.player_name; + token_mapping["auth_uuid"] = response.player_id; + + // this is for offline?: /* map["auth_player_name"] = "Player"; map["auth_player_name"] = "00000000-0000-0000-0000-000000000000"; @@ -85,7 +83,7 @@ QStringList OneSixInstance::processMinecraftArgs(QString user, QString session) return parts; } -MinecraftProcess *OneSixInstance::prepareForLaunch(QString user, QString session) +MinecraftProcess *OneSixInstance::prepareForLaunch(LoginResponse response) { I_D(OneSixInstance); cleanupAfterRun(); @@ -142,7 +140,7 @@ MinecraftProcess *OneSixInstance::prepareForLaunch(QString user, QString session args << classPath; } args << version->mainClass; - args.append(processMinecraftArgs(user, session)); + args.append(processMinecraftArgs(response)); // create the process and set its parameters MinecraftProcess *proc = new MinecraftProcess(this); diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index 0139b645..33091188 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -23,7 +23,7 @@ public: virtual QString instanceConfigFolder() const; virtual BaseUpdate* doUpdate(); - virtual MinecraftProcess* prepareForLaunch ( QString user, QString session ); + virtual MinecraftProcess* prepareForLaunch ( LoginResponse response ); virtual void cleanupAfterRun(); virtual QString intendedVersionId() const; @@ -54,5 +54,5 @@ public: virtual bool menuActionEnabled ( QString action_name ) const; virtual QString getStatusbarDescription(); private: - QStringList processMinecraftArgs( QString user, QString session ); + QStringList processMinecraftArgs( LoginResponse response ); }; \ No newline at end of file diff --git a/logic/net/LoginTask.cpp b/logic/net/LoginTask.cpp new file mode 100644 index 00000000..2a45400e --- /dev/null +++ b/logic/net/LoginTask.cpp @@ -0,0 +1,269 @@ +/* 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 "LoginTask.h" +#include "MultiMC.h" +#include + +#include + +#include +#include + +#include +#include +#include +#include + +LoginTask::LoginTask(const UserInfo &uInfo, QObject *parent) : Task(parent), uInfo(uInfo) +{ +} + +void LoginTask::executeTask() +{ + yggdrasilLogin(); +} + +void LoginTask::legacyLogin() +{ + setStatus(tr("Logging in...")); + auto worker = MMC->qnam(); + connect(worker.data(), SIGNAL(finished(QNetworkReply *)), this, + SLOT(processLegacyReply(QNetworkReply *))); + + QUrl loginURL("https://login.minecraft.net/"); + QNetworkRequest netRequest(loginURL); + netRequest.setHeader(QNetworkRequest::ContentTypeHeader, + "application/x-www-form-urlencoded"); + + QUrlQuery params; + params.addQueryItem("user", uInfo.username); + params.addQueryItem("password", uInfo.password); + params.addQueryItem("version", "13"); + + netReply = worker->post(netRequest, params.query(QUrl::EncodeSpaces).toUtf8()); +} + +void LoginTask::processLegacyReply(QNetworkReply *reply) +{ + if (netReply != reply) + return; + // Check for errors. + switch (reply->error()) + { + case QNetworkReply::NoError: + { + // Check the response code. + int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (responseCode == 200) + { + parseLegacyReply(reply->readAll()); + } + else if (responseCode == 503) + { + emitFailed(tr("The login servers are currently unavailable. Check " + "http://help.mojang.com/ for more info.")); + } + else + { + emitFailed(tr("Login failed: Unknown HTTP error %1 occurred.") + .arg(QString::number(responseCode))); + } + break; + } + + case QNetworkReply::OperationCanceledError: + emitFailed(tr("Login canceled.")); + break; + + default: + emitFailed(tr("Login failed: %1").arg(reply->errorString())); + break; + } +} + +void LoginTask::parseLegacyReply(QByteArray data) +{ + QString responseStr = QString::fromUtf8(data); + + QStringList strings = responseStr.split(":"); + if (strings.count() >= 4) + { + // strings[1] is the download ticket. It isn't used anymore. + QString username = strings[2]; + QString sessionID = strings[3]; + /* + struct LoginResponse + { + QString username; + QString session_id; + QString player_name; + QString player_id; + QString client_id; + }; + */ + result = {username, sessionID, username, QString()}; + emitSucceeded(); + } + else + { + if (responseStr.toLower() == "bad login") + emitFailed(tr("Invalid username or password.")); + else if (responseStr.toLower() == "old version") + emitFailed(tr("Launcher outdated, please update.")); + else + emitFailed(tr("Login failed: %1").arg(responseStr)); + } +} + + +void LoginTask::yggdrasilLogin() +{ + setStatus(tr("Logging in...")); + auto worker = MMC->qnam(); + connect(worker.data(), SIGNAL(finished(QNetworkReply *)), this, + SLOT(processYggdrasilReply(QNetworkReply *))); + + /* + { + // agent def. version might be incremented at some point + "agent":{"name":"Minecraft","version":1}, + "username": "mojang account name", + "password": "mojang account password", + // client token is optional. but we supply one anyway + "clientToken": "client identifier" + } + */ + + QUrl loginURL("https://authserver.mojang.com/authenticate"); + QNetworkRequest netRequest(loginURL); + netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + auto settings = MMC->settings(); + QString clientToken = settings->get("YggdrasilClientToken").toString(); + // escape the {} + clientToken.remove('{'); + clientToken.remove('}'); + // create the request + QString requestConstent; + requestConstent += "{"; + requestConstent += " \"agent\":{\"name\":\"Minecraft\",\"version\":1},\n"; + requestConstent += " \"username\":\"" + uInfo.username + "\",\n"; + requestConstent += " \"password\":\"" + uInfo.password + "\",\n"; + requestConstent += " \"clientToken\":\"" + clientToken + "\"\n"; + requestConstent += "}"; + netReply = worker->post(netRequest, requestConstent.toUtf8()); +} + +void LoginTask::processYggdrasilReply(QNetworkReply *reply) +{ + if (netReply != reply) + return; + // Check for errors. + switch (reply->error()) + { + case QNetworkReply::NoError: + { + // Check the response code. + int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (responseCode == 200) + { + parseYggdrasilReply(reply->readAll()); + } + else if (responseCode == 503) + { + emitFailed(tr("The login servers are currently unavailable. Check " + "http://help.mojang.com/ for more info.")); + } + else + { + emitFailed(tr("Login failed: Unknown HTTP error %1 occurred.") + .arg(QString::number(responseCode))); + } + break; + } + + case QNetworkReply::OperationCanceledError: + emitFailed(tr("Login canceled.")); + break; + + default: + emitFailed(tr("Login failed: %1").arg(reply->errorString())); + break; + } +} + +/* +{ + "accessToken": "random access token", // hexadecimal + "clientToken": "client identifier", // identical to the one received + "availableProfiles": [ // only present if the agent field was received + { + "id": "profile identifier", // hexadecimal + "name": "player name" + } + ], + "selectedProfile": { // only present if the agent field was received + "id": "profile identifier", + "name": "player name" + } +} +*/ +void LoginTask::parseYggdrasilReply(QByteArray data) +{ + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error != QJsonParseError::NoError) + { + emitFailed(tr("Login failed: %1").arg(jsonError.errorString())); + return; + } + + if (!jsonDoc.isObject()) + { + emitFailed(tr("Login failed: BAD FORMAT #1")); + return; + } + + QJsonObject root = jsonDoc.object(); + + QString accessToken = root.value("accessToken").toString(); + QString clientToken = root.value("clientToken").toString(); + QString playerID; + QString playerName; + auto selectedProfile = root.value("selectedProfile"); + if(selectedProfile.isObject()) + { + auto selectedProfileO = selectedProfile.toObject(); + playerID = selectedProfileO.value("id").toString(); + playerName = selectedProfileO.value("name").toString(); + } + QString sessionID = "token:" + accessToken + ":" + playerID; + /* + struct LoginResponse + { + QString username; + QString session_id; + QString player_name; + QString player_id; + QString client_id; + }; + */ + + result = {uInfo.username, sessionID, playerName, playerID}; + emitSucceeded(); +} diff --git a/logic/net/LoginTask.h b/logic/net/LoginTask.h new file mode 100644 index 00000000..ba87142d --- /dev/null +++ b/logic/net/LoginTask.h @@ -0,0 +1,62 @@ +/* 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 "logic/tasks/Task.h" +#include + +struct UserInfo +{ + QString username; + QString password; +}; + +struct LoginResponse +{ + QString username; + QString session_id; + QString player_name; + QString player_id; +}; + +class QNetworkReply; + +class LoginTask : public Task +{ + Q_OBJECT +public: + explicit LoginTask(const UserInfo &uInfo, QObject *parent = 0); + LoginResponse getResult() + { + return result; + } + +protected slots: + void legacyLogin(); + void processLegacyReply(QNetworkReply *reply); + void parseLegacyReply(QByteArray data); + + void yggdrasilLogin(); + void processYggdrasilReply(QNetworkReply *reply); + void parseYggdrasilReply(QByteArray data); + +protected: + void executeTask(); + + LoginResponse result; + QNetworkReply *netReply; + UserInfo uInfo; +}; diff --git a/logic/tasks/LoginTask.cpp b/logic/tasks/LoginTask.cpp deleted file mode 100644 index 222af618..00000000 --- a/logic/tasks/LoginTask.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* 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 "LoginTask.h" -#include "MultiMC.h" - -#include - -#include -#include - -#include -#include - -LoginTask::LoginTask( const UserInfo& uInfo, QObject* parent ) : Task(parent), uInfo(uInfo){} - -void LoginTask::executeTask() -{ - setStatus(tr("Logging in...")); - auto worker = MMC->qnam(); - connect(worker.data(), SIGNAL(finished(QNetworkReply*)), this, SLOT(processNetReply(QNetworkReply*))); - - QUrl loginURL("https://login.minecraft.net/"); - QNetworkRequest netRequest(loginURL); - netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - - QUrlQuery params; - params.addQueryItem("user", uInfo.username); - params.addQueryItem("password", uInfo.password); - params.addQueryItem("version", "13"); - - netReply = worker->post(netRequest, params.query(QUrl::EncodeSpaces).toUtf8()); -} - -void LoginTask::processNetReply(QNetworkReply *reply) -{ - if(netReply != reply) - return; - // Check for errors. - switch (reply->error()) - { - case QNetworkReply::NoError: - { - // Check the response code. - int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (responseCode == 200) - { - QString responseStr(reply->readAll()); - - QStringList strings = responseStr.split(":"); - if (strings.count() >= 4) - { - bool parseSuccess; - qint64 latestVersion = strings[0].toLongLong(&parseSuccess); - if (parseSuccess) - { - // strings[1] is the download ticket. It isn't used anymore. - QString username = strings[2]; - QString sessionID = strings[3]; - - result = {username, sessionID, latestVersion}; - emitSucceeded(); - } - else - { - emitFailed(tr("Failed to parse Minecraft version string.")); - } - } - else - { - if (responseStr.toLower() == "bad login") - emitFailed(tr("Invalid username or password.")); - else if (responseStr.toLower() == "old version") - emitFailed(tr("Launcher outdated, please update.")); - else - emitFailed(tr("Login failed: %1").arg(responseStr)); - } - } - else if (responseCode == 503) - { - emitFailed(tr("The login servers are currently unavailable. Check http://help.mojang.com/ for more info.")); - } - else - { - emitFailed(tr("Login failed: Unknown HTTP error %1 occurred.").arg(QString::number(responseCode))); - } - break; - } - - case QNetworkReply::OperationCanceledError: - emitFailed(tr("Login canceled.")); - break; - - default: - emitFailed(tr("Login failed: %1").arg(reply->errorString())); - break; - } -} diff --git a/logic/tasks/LoginTask.h b/logic/tasks/LoginTask.h deleted file mode 100644 index bde672b8..00000000 --- a/logic/tasks/LoginTask.h +++ /dev/null @@ -1,58 +0,0 @@ -/* 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. - */ - -#ifndef LOGINTASK_H -#define LOGINTASK_H - -#include "Task.h" -#include - -struct UserInfo -{ - QString username; - QString password; -}; - -struct LoginResponse -{ - QString username; - QString sessionID; - qint64 latestVersion; -}; - -class QNetworkReply; - -class LoginTask : public Task -{ - Q_OBJECT -public: - explicit LoginTask(const UserInfo& uInfo, QObject *parent = 0); - LoginResponse getResult() - { - return result; - }; - -protected slots: - void processNetReply(QNetworkReply* reply); - -protected: - void executeTask(); - - LoginResponse result; - QNetworkReply* netReply; - UserInfo uInfo; -}; - -#endif // LOGINTASK_H -- cgit v1.2.3