diff options
117 files changed, 418 insertions, 1574 deletions
@@ -1,9 +1,5 @@ -<p align="center"> - <img src="https://avatars2.githubusercontent.com/u/5411890" alt="MeshMC logo"/> -</p> - MeshMC -======= +====== MeshMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity. diff --git a/cmake/UnitTest/TestUtil.h b/cmake/UnitTest/TestUtil.h index ebe3c662d1..0a11313951 100644 --- a/cmake/UnitTest/TestUtil.h +++ b/cmake/UnitTest/TestUtil.h @@ -14,7 +14,8 @@ public: static QByteArray readFile(const QString &fileName) { QFile f(fileName); - f.open(QFile::ReadOnly); + if (!f.open(QFile::ReadOnly)) + return QByteArray(); return f.readAll(); } static QString readFileUtf8(const QString &fileName) diff --git a/launcher/ApplicationMessage.cpp b/launcher/ApplicationMessage.cpp index e22bf13c81..a01324dfc7 100644 --- a/launcher/ApplicationMessage.cpp +++ b/launcher/ApplicationMessage.cpp @@ -4,7 +4,7 @@ #include <QJsonObject> void ApplicationMessage::parse(const QByteArray & input) { - auto doc = QJsonDocument::fromBinaryData(input); + auto doc = QJsonDocument::fromJson(input); auto root = doc.object(); command = root.value("command").toString(); @@ -27,5 +27,5 @@ QByteArray ApplicationMessage::serialize() { QJsonDocument out; out.setObject(root); - return out.toBinaryData(); + return out.toJson(QJsonDocument::Compact); } diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 374d4a29c6..54197fff03 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -18,6 +18,7 @@ #include <QFileInfo> #include <QDir> #include <QDebug> +#include <QRegularExpression> #include "settings/INISettingsObject.h" #include "settings/Setting.h" @@ -261,7 +262,7 @@ QString BaseInstance::name() const QString BaseInstance::windowTitle() const { - return BuildConfig.LAUNCHER_NAME + ": " + name().replace(QRegExp("[ \n\r\t]+"), " "); + return BuildConfig.LAUNCHER_NAME + ": " + name().replace(QRegularExpression("[ \n\r\t]+"), " "); } // FIXME: why is this here? move it to MinecraftInstance!!! diff --git a/launcher/BaseVersionList.cpp b/launcher/BaseVersionList.cpp index aa9cb6cf14..506844093a 100644 --- a/launcher/BaseVersionList.cpp +++ b/launcher/BaseVersionList.cpp @@ -51,7 +51,7 @@ QVariant BaseVersionList::data(const QModelIndex &index, int role) const switch (role) { case VersionPointerRole: - return qVariantFromValue(version); + return QVariant::fromValue(version); case VersionRole: return version->name(); diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c1d4fc6877..1f9bbb7fe4 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -212,13 +212,8 @@ set(MINECRAFT_SOURCES minecraft/auth/MinecraftAccount.h minecraft/auth/Parsers.cpp minecraft/auth/Parsers.h - minecraft/auth/Yggdrasil.cpp - minecraft/auth/Yggdrasil.h - minecraft/auth/flows/AuthFlow.cpp minecraft/auth/flows/AuthFlow.h - minecraft/auth/flows/Mojang.cpp - minecraft/auth/flows/Mojang.h minecraft/auth/flows/MSA.cpp minecraft/auth/flows/MSA.h @@ -228,8 +223,6 @@ set(MINECRAFT_SOURCES minecraft/auth/steps/GetSkinStep.h minecraft/auth/steps/LauncherLoginStep.cpp minecraft/auth/steps/LauncherLoginStep.h - minecraft/auth/steps/MigrationEligibilityStep.cpp - minecraft/auth/steps/MigrationEligibilityStep.h minecraft/auth/steps/MinecraftProfileStep.cpp minecraft/auth/steps/MinecraftProfileStep.h minecraft/auth/steps/MSAStep.cpp @@ -240,9 +233,6 @@ set(MINECRAFT_SOURCES minecraft/auth/steps/XboxProfileStep.h minecraft/auth/steps/XboxUserStep.cpp minecraft/auth/steps/XboxUserStep.h - minecraft/auth/steps/YggdrasilStep.cpp - minecraft/auth/steps/YggdrasilStep.h - minecraft/gameoptions/GameOptions.h minecraft/gameoptions/GameOptions.cpp @@ -767,8 +757,6 @@ SET(LAUNCHER_SOURCES ui/dialogs/ExportInstanceDialog.h ui/dialogs/IconPickerDialog.cpp ui/dialogs/IconPickerDialog.h - ui/dialogs/LoginDialog.cpp - ui/dialogs/LoginDialog.h ui/dialogs/MSALoginDialog.cpp ui/dialogs/MSALoginDialog.h ui/dialogs/NewComponentDialog.cpp @@ -883,7 +871,6 @@ qt6_wrap_ui(LAUNCHER_UI ui/dialogs/IconPickerDialog.ui ui/dialogs/MSALoginDialog.ui ui/dialogs/AboutDialog.ui - ui/dialogs/LoginDialog.ui ui/dialogs/EditAccountDialog.ui ) @@ -910,7 +897,6 @@ endif() add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES}) target_link_libraries(Launcher_logic systeminfo - Launcher_quazip Launcher_classparser ${NBT_NAME} ${ZLIB_LIBRARIES} diff --git a/launcher/Commandline.cpp b/launcher/Commandline.cpp index 2c0fde64b2..8c89cd3b2b 100644 --- a/launcher/Commandline.cpp +++ b/launcher/Commandline.cpp @@ -47,7 +47,7 @@ QStringList splitArgs(QString args) if (cchar == '\\') escape = true; else if (cchar == inquotes) - inquotes = 0; + inquotes = QChar(); else current += cchar; // otherwise diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 6de20de6ff..62e6c1e27f 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -407,7 +407,8 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na location = PathCombine(location, name + ".desktop"); QFile f(location); - f.open(QIODevice::WriteOnly | QIODevice::Text); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) + return false; QTextStream stream(&f); QString argstring; diff --git a/launcher/GZip.cpp b/launcher/GZip.cpp index 0368c32d60..c5c5e7a730 100644 --- a/launcher/GZip.cpp +++ b/launcher/GZip.cpp @@ -67,7 +67,7 @@ bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes) return true; } - unsigned compLength = std::min(uncompressedBytes.size(), 16); + unsigned compLength = std::min(static_cast<qsizetype>(16), uncompressedBytes.size()); compressedBytes.clear(); compressedBytes.resize(compLength); diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 990cc9840e..37d20c99a8 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -21,6 +21,7 @@ #include "NullInstance.h" #include "settings/INISettingsObject.h" #include "icons/IconUtils.h" +#include <QRegularExpression> #include <QtConcurrentRun> // FIXME: this does not belong here, it's Minecraft/Flame specific @@ -268,7 +269,7 @@ void InstanceImportTask::processFlame() // Hack to correct some 'special sauce'... if(mcVersion.endsWith('.')) { - mcVersion.remove(QRegExp("[.]+$")); + mcVersion.remove(QRegularExpression("[.]+$")); logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); } auto components = instance.getPackProfile(); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index ad18740b63..045afb0e80 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -133,7 +133,7 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const { case InstancePointerRole: { - QVariant v = qVariantFromValue((void *)pdata); + QVariant v = QVariant::fromValue((void *)pdata); return v; } case InstanceIDRole: @@ -249,7 +249,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name) QStringList InstanceList::getGroups() { - return m_groupNameCache.toList(); + return m_groupNameCache.values(); } void InstanceList::deleteGroup(const QString& name) @@ -350,7 +350,7 @@ QList< InstanceId > InstanceList::discoverInstances() out.append(id); qDebug() << "Found instance ID" << id; } - instanceSet = out.toSet(); + instanceSet = QSet<QString>(out.begin(), out.end()); m_instancesProbed = true; return out; } diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp index 6fa5851b1f..d946921276 100644 --- a/launcher/JavaCommon.cpp +++ b/launcher/JavaCommon.cpp @@ -1,10 +1,11 @@ #include "JavaCommon.h" #include "ui/dialogs/CustomMessageBox.h" #include <MMCStrings.h> +#include <QRegularExpression> bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent) { - if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegExp("-Xm[sx]")) + if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegularExpression("-Xm[sx]")) || jvmargs.contains("-XX-MaxHeapSize") || jvmargs.contains("-XX:InitialHeapSize")) { auto warnStr = QObject::tr( diff --git a/launcher/Json.cpp b/launcher/Json.cpp index 37ada1aa23..800f06cfea 100644 --- a/launcher/Json.cpp +++ b/launcher/Json.cpp @@ -24,11 +24,11 @@ void write(const QJsonArray &array, const QString &filename) QByteArray toBinary(const QJsonObject &obj) { - return QJsonDocument(obj).toBinaryData(); + return QJsonDocument(obj).toJson(QJsonDocument::Compact); } QByteArray toBinary(const QJsonArray &array) { - return QJsonDocument(array).toBinaryData(); + return QJsonDocument(array).toJson(QJsonDocument::Compact); } QByteArray toText(const QJsonObject &obj) { @@ -41,19 +41,15 @@ QByteArray toText(const QJsonArray &array) static bool isBinaryJson(const QByteArray &data) { - decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag; - return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0; + // Binary JSON is no longer supported in Qt6 + Q_UNUSED(data); + return false; } QJsonDocument requireDocument(const QByteArray &data, const QString &what) { if (isBinaryJson(data)) { - QJsonDocument doc = QJsonDocument::fromBinaryData(data); - if (doc.isNull()) - { - throw JsonException(what + ": Invalid JSON (binary JSON detected)"); - } - return doc; + throw JsonException(what + ": Binary JSON format is no longer supported"); } else { diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 7750be1a2a..9a7c0c6c4e 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -55,7 +55,7 @@ void LaunchController::decideAccount() auto reply = CustomMessageBox::selectable( m_parentWidget, tr("No Accounts"), - tr("In order to play Minecraft, you must have at least one Mojang or Minecraft " + tr("In order to play Minecraft, you must have at least one Microsoft " "account logged in." "Would you like to open the account manager to add an account now?"), QMessageBox::Information, @@ -275,7 +275,7 @@ void LaunchController::launchInstance() online_mode = "online"; // Prepend Server Status - QStringList servers = {"authserver.mojang.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com"}; + QStringList servers = {"session.minecraft.net", "textures.minecraft.net", "api.mojang.com"}; QString resolved_servers = ""; QHostInfo host_info; diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index 2479f4ff4b..a53ebf446d 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -8,7 +8,7 @@ LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent) connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut); connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr); connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus))); - connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError))); + connect(this, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError))); connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange); } @@ -163,11 +163,7 @@ void LoggedProcess::on_stateChange(QProcess::ProcessState state) qint64 LoggedProcess::processId() const { -#ifdef Q_OS_WIN - return pid() ? pid()->dwProcessId : 0; -#else - return pid(); -#endif + return QProcess::processId(); } void LoggedProcess::setDetachable(bool detachable) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index b25c61e759..f893c32133 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -21,9 +21,49 @@ #include "FileSystem.h" #include <QDebug> +#include <QDirIterator> + +bool MMCZip::compressDir(QString zipFile, QString dir, FilterFunction excludeFilter) +{ + QuaZip zip(zipFile); + if (!zip.open(QuaZip::mdCreate)) + { + return false; + } + + QDir directory(dir); + if (!directory.exists()) + { + return false; + } + + QDirIterator it(dir, QDir::Files | QDir::Hidden, QDirIterator::Subdirectories); + bool success = true; + while (it.hasNext()) + { + it.next(); + QString relPath = directory.relativeFilePath(it.filePath()); + if (excludeFilter && excludeFilter(relPath)) + { + continue; + } + if (!JlCompress::compressFile(&zip, it.filePath(), relPath)) + { + success = false; + break; + } + } + + zip.close(); + if (zip.getZipError() != 0) + { + return false; + } + return success; +} // ours -bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, const JlCompress::FilterFunction filter) +bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, const FilterFunction filter) { QuaZip modZip(from.filePath()); modZip.open(QuaZip::mdUnzip); @@ -128,7 +168,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QDir dir(what_to_zip); dir.cdUp(); QString parent_dir = dir.absolutePath(); - if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles)) + if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, true, QDir::NoFilter)) { zipOut.close(); QFile::remove(targetJarPath); diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index 9c47fa11f2..e5f9a7ea02 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -26,12 +26,13 @@ namespace MMCZip { + using FilterFunction = std::function<bool(const QString &)>; /** * Merge two zip files, using a filter function */ bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, - const JlCompress::FilterFunction filter = nullptr); + const FilterFunction filter = nullptr); /** * take a source jar, add mods to it, resulting in target jar @@ -56,6 +57,12 @@ namespace MMCZip /** * Extract a subdirectory from an archive */ + + /** + * Compress a directory, using a filter function to exclude entries + */ + bool compressDir(QString zipFile, QString dir, FilterFunction excludeFilter); + nonstd::optional<QStringList> extractSubDir(QuaZip *zip, const QString & subdir, const QString &target); bool extractRelFile(QuaZip *zip, const QString & file, const QString &target); diff --git a/launcher/QObjectPtr.h b/launcher/QObjectPtr.h index 57974939f1..1e45e90249 100644 --- a/launcher/QObjectPtr.h +++ b/launcher/QObjectPtr.h @@ -46,8 +46,12 @@ public: public: void reset(T * wrap) { - using namespace std::placeholders; - m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1)); + if(wrap) { + using namespace std::placeholders; + m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1)); + } else { + m_ptr.reset(); + } } void reset(const shared_qobject_ptr<T> &other) { diff --git a/launcher/UpdateController.cpp b/launcher/UpdateController.cpp index f9b7d34995..1bcd837770 100644 --- a/launcher/UpdateController.cpp +++ b/launcher/UpdateController.cpp @@ -245,7 +245,8 @@ void UpdateController::installUpdates() // we save it QFile scriptFile(scriptPath); - scriptFile.open(QIODevice::WriteOnly); + if (!scriptFile.open(QIODevice::WriteOnly)) + return; scriptFile.write(script.toLocal8Bit().replace("\n", "\r\n")); scriptFile.close(); diff --git a/launcher/Version.h b/launcher/Version.h index 9fe12d6dad..02b4244e01 100644 --- a/launcher/Version.h +++ b/launcher/Version.h @@ -39,13 +39,13 @@ private: break; } } - auto numPart = m_fullString.leftRef(cutoff); + auto numPart = QStringView{m_fullString}.left(cutoff); if(numPart.size()) { numValid = true; m_numPart = numPart.toInt(); } - auto stringPart = m_fullString.midRef(cutoff); + auto stringPart = QStringView{m_fullString}.mid(cutoff); if(stringPart.size()) { m_stringPart = stringPart.toString(); diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index b9a87c9c20..3e457d1268 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -208,15 +208,15 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const { return APPLICATION->getThemedIcon("bug"); } - auto pixmap = QPixmapCache::find("placeholder"); - if(!pixmap) + QPixmap pixmap; + if(!QPixmapCache::find("placeholder", &pixmap)) { QPixmap px(16,16); px.fill(Qt::transparent); QPixmapCache::insert("placeholder", px); return px; } - return *pixmap; + return pixmap; } } default: @@ -309,6 +309,7 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel *replacingRaw) { roles.clear(); filterModel->setSourceModel(replacing); + endResetModel(); return; } diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index 584edd69e0..e9558c8f29 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -74,7 +74,7 @@ void IconList::directoryChanged(const QString &path) QString &foo = (*it); foo = m_dir.filePath(foo); } - auto new_set = new_list.toSet(); + auto new_set = QSet<QString>(new_list.begin(), new_list.end()); QList<QString> current_list; for (auto &it : icons) { @@ -82,7 +82,7 @@ void IconList::directoryChanged(const QString &path) continue; current_list.push_back(it.m_images[IconType::FileBased].filename); } - QSet<QString> current_set = current_list.toSet(); + QSet<QString> current_set(current_list.begin(), current_list.end()); QSet<QString> to_remove = current_set; to_remove -= new_set; diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 35ddc35c23..1bb2efe1f9 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -47,7 +47,7 @@ void JavaChecker::performCheck() qDebug() << "Running java checker: " + m_path + args.join(" ");; connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus))); - connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError))); + connect(process.get(), SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError))); connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady())); connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady())); connect(&killTimer, SIGNAL(timeout()), SLOT(timeout())); @@ -99,7 +99,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) bool success = true; QMap<QString, QString> results; - QStringList lines = m_stdout.split("\n", QString::SkipEmptyParts); + QStringList lines = m_stdout.split("\n", Qt::SkipEmptyParts); for(QString line : lines) { line = line.trimmed(); @@ -108,7 +108,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) continue; } - auto parts = line.split('=', QString::SkipEmptyParts); + auto parts = line.split('=', Qt::SkipEmptyParts); if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) { continue; diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index 07f2bd8cb4..66fa26dd88 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -15,7 +15,6 @@ #include <QtNetwork> #include <QtXml> -#include <QRegExp> #include <QDebug> @@ -81,7 +80,7 @@ QVariant JavaInstallList::data(const QModelIndex &index, int role) const switch (role) { case VersionPointerRole: - return qVariantFromValue(m_vlist[index.row()]); + return QVariant::fromValue(m_vlist[index.row()]); case VersionIdRole: return version->descriptor(); case VersionRole: diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp index 179ccd8d9e..b6687001b0 100644 --- a/launcher/java/JavaVersion.cpp +++ b/launcher/java/JavaVersion.cpp @@ -56,7 +56,7 @@ bool JavaVersion::requiresPermGen() return true; } -bool JavaVersion::operator<(const JavaVersion &rhs) +bool JavaVersion::operator<(const JavaVersion &rhs) const { if(m_parseable && rhs.m_parseable) { @@ -106,7 +106,7 @@ bool JavaVersion::operator<(const JavaVersion &rhs) else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0; } -bool JavaVersion::operator==(const JavaVersion &rhs) +bool JavaVersion::operator==(const JavaVersion &rhs) const { if(m_parseable && rhs.m_parseable) { @@ -115,7 +115,7 @@ bool JavaVersion::operator==(const JavaVersion &rhs) return m_string == rhs.m_string; } -bool JavaVersion::operator>(const JavaVersion &rhs) +bool JavaVersion::operator>(const JavaVersion &rhs) const { return (!operator<(rhs)) && (!operator==(rhs)); } diff --git a/launcher/java/JavaVersion.h b/launcher/java/JavaVersion.h index 9bbf064257..298b92b4a1 100644 --- a/launcher/java/JavaVersion.h +++ b/launcher/java/JavaVersion.h @@ -19,9 +19,9 @@ public: JavaVersion & operator=(const QString & rhs); - bool operator<(const JavaVersion & rhs); - bool operator==(const JavaVersion & rhs); - bool operator>(const JavaVersion & rhs); + bool operator<(const JavaVersion & rhs) const; + bool operator==(const JavaVersion & rhs) const; + bool operator>(const JavaVersion & rhs) const; bool requiresPermGen(); diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp index 8bc05a1bce..d6a2e23637 100644 --- a/launcher/minecraft/ComponentUpdateTask.cpp +++ b/launcher/minecraft/ComponentUpdateTask.cpp @@ -189,11 +189,11 @@ void ComponentUpdateTask::loadComponents() RemoteLoadStatus status; status.type = RemoteLoadStatus::Type::Index; d->remoteLoadStatusList.append(status); - connect(indexLoadTask.get(), &Task::succeeded, [=]() + connect(indexLoadTask.get(), &Task::succeeded, [this, taskIndex]() { remoteLoadSucceeded(taskIndex); }); - connect(indexLoadTask.get(), &Task::failed, [=](const QString & error) + connect(indexLoadTask.get(), &Task::failed, [this, taskIndex](const QString & error) { remoteLoadFailed(taskIndex, error); }); @@ -235,11 +235,11 @@ void ComponentUpdateTask::loadComponents() if (loadTask) { qDebug() << "Remote loading is being run for" << component->getName(); - connect(loadTask.get(), &Task::succeeded, [=]() + connect(loadTask.get(), &Task::succeeded, [this, taskIndex]() { remoteLoadSucceeded(taskIndex); }); - connect(loadTask.get(), &Task::failed, [=](const QString & error) + connect(loadTask.get(), &Task::failed, [this, taskIndex](const QString & error) { remoteLoadFailed(taskIndex, error); }); diff --git a/launcher/minecraft/GradleSpecifier.h b/launcher/minecraft/GradleSpecifier.h index 415fb54cfd..072e3925ec 100644 --- a/launcher/minecraft/GradleSpecifier.h +++ b/launcher/minecraft/GradleSpecifier.h @@ -2,6 +2,7 @@ #include <QString> #include <QStringList> +#include <QRegularExpression> #include "DefaultVariable.h" struct GradleSpecifier @@ -25,13 +26,14 @@ struct GradleSpecifier 4 "jdk15" 5 "jar" */ - QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(?::([^:@]+))?" "(?:@([^:@]+))?"); - m_valid = matcher.exactMatch(value); + QRegularExpression matcher("^([^:@]+):([^:@]+):([^:@]+)" "(?::([^:@]+))?" "(?:@([^:@]+))?$"); + auto match = matcher.match(value); + m_valid = match.hasMatch(); if(!m_valid) { m_invalidValue = value; return *this; } - auto elements = matcher.capturedTexts(); + auto elements = match.capturedTexts(); m_groupId = elements[1]; m_artifactId = elements[2]; m_version = elements[3]; diff --git a/launcher/minecraft/Library_test.cpp b/launcher/minecraft/Library_test.cpp index 47531ad6f3..76f8547924 100644 --- a/launcher/minecraft/Library_test.cpp +++ b/launcher/minecraft/Library_test.cpp @@ -15,7 +15,8 @@ private: { auto path = QFINDTESTDATA(file); QFile jsonFile(path); - jsonFile.open(QIODevice::ReadOnly); + if (!jsonFile.open(QIODevice::ReadOnly)) + return nullptr; auto data = jsonFile.readAll(); jsonFile.close(); ProblemContainer problems; diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index ff70728839..6408e0ec27 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -5,6 +5,7 @@ #include "settings/Setting.h" #include "settings/SettingsObject.h" #include "Application.h" +#include <QRegularExpression> #include "MMCStrings.h" #include "pathmatcher/RegexpMatcher.h" @@ -392,22 +393,20 @@ QProcessEnvironment MinecraftInstance::createEnvironment() static QString replaceTokensIn(QString text, QMap<QString, QString> with) { QString result; - QRegExp token_regexp("\\$\\{(.+)\\}"); - token_regexp.setMinimal(true); - QStringList list; + QRegularExpression token_regexp("\\$\\{(.+?)\\}"); + QRegularExpressionMatchIterator it = token_regexp.globalMatch(text); int tail = 0; - int head = 0; - while ((head = token_regexp.indexIn(text, head)) != -1) + while (it.hasNext()) { - result.append(text.mid(tail, head - tail)); - QString key = token_regexp.cap(1); + QRegularExpressionMatch match = it.next(); + result.append(text.mid(tail, match.capturedStart() - tail)); + QString key = match.captured(1); auto iter = with.find(key); if (iter != with.end()) { result.append(*iter); } - head += token_regexp.matchedLength(); - tail = head; + tail = match.capturedEnd(); } result.append(text.mid(tail)); return result; @@ -459,7 +458,7 @@ QStringList MinecraftInstance::processMinecraftArgs( token_mapping["assets_root"] = absAssetsDir; token_mapping["assets_index_name"] = assets->id; - QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); + QStringList parts = args_pattern.split(' ', Qt::SkipEmptyParts); for (int i = 0; i < parts.length(); i++) { parts[i] = replaceTokensIn(parts[i], token_mapping); diff --git a/launcher/minecraft/MojangVersionFormat_test.cpp b/launcher/minecraft/MojangVersionFormat_test.cpp index 9d0953406f..183ada997c 100644 --- a/launcher/minecraft/MojangVersionFormat_test.cpp +++ b/launcher/minecraft/MojangVersionFormat_test.cpp @@ -12,7 +12,8 @@ class MojangVersionFormatTest : public QObject { auto path = QFINDTESTDATA(file); QFile jsonFile(path); - jsonFile.open(QIODevice::ReadOnly); + if (!jsonFile.open(QIODevice::ReadOnly)) + return QJsonDocument(); auto data = jsonFile.readAll(); jsonFile.close(); return QJsonDocument::fromJson(data); @@ -20,7 +21,8 @@ class MojangVersionFormatTest : public QObject static void writeJson(const char *file, QJsonDocument doc) { QFile jsonFile(file); - jsonFile.open(QIODevice::WriteOnly | QIODevice::Text); + if (!jsonFile.open(QIODevice::WriteOnly | QIODevice::Text)) + return; auto data = doc.toJson(QJsonDocument::Indented); qDebug() << QString::fromUtf8(data); jsonFile.write(data); diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp index d5e8c680d9..acc03c5960 100644 --- a/launcher/minecraft/OneSixVersionFormat.cpp +++ b/launcher/minecraft/OneSixVersionFormat.cpp @@ -272,7 +272,7 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch } writeString(root, "appletClass", patch->appletClass); writeStringList(root, "+tweakers", patch->addTweakers); - writeStringList(root, "+traits", patch->traits.toList()); + writeStringList(root, "+traits", patch->traits.values()); if (!patch->libraries.isEmpty()) { QJsonArray array; diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index f557cbc90a..e149b132ef 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -925,7 +925,7 @@ void PackProfile::move(const int index, const MoveDirection direction) return; } beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); - d->components.swap(index, theirIndex); + d->components.swapItemsAt(index, theirIndex); endMoveRows(); invalidateLaunchProfile(); scheduleSave(); diff --git a/launcher/minecraft/ProfileUtils.cpp b/launcher/minecraft/ProfileUtils.cpp index 8ca24cc8cc..88aaae0b1b 100644 --- a/launcher/minecraft/ProfileUtils.cpp +++ b/launcher/minecraft/ProfileUtils.cpp @@ -149,7 +149,7 @@ VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo) auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString()); return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); } - QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll()); + QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); file.close(); if (doc.isNull()) { diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index a2b4dac7a8..03600a01b2 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -299,7 +299,7 @@ bool World::install(const QString &to, const QString &name) if(ok && !name.isEmpty() && m_actualName != name) { - World newWorld(finalPath); + World newWorld{QFileInfo(finalPath)}; if(newWorld.isValid()) { newWorld.rename(name); diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index dcdbc32147..130f221e6f 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -197,7 +197,7 @@ QVariant WorldList::data(const QModelIndex &index, int role) const } case SeedRole: { - return qVariantFromValue<qlonglong>(world.seed()); + return QVariant::fromValue<qlonglong>(world.seed()); } case NameRole: { @@ -286,7 +286,7 @@ protected: urls.append(QUrl::fromLocalFile(worldPath)); } const_cast<WorldMimeData*>(this)->setUrls(urls); - return QMimeData::retrieveData(mimetype, type); + return QMimeData::retrieveData(mimetype, QMetaType(type)); } private: QList<World> m_worlds; diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index 7526c9517c..5470e1e7fe 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -4,6 +4,7 @@ #include <QJsonArray> #include <QDebug> #include <QUuid> +#include <QRegularExpression> namespace { void tokenToJSONV3(QJsonObject &parent, Katabasis::Token t, const char * tokenName) { @@ -238,71 +239,6 @@ bool entitlementFromJSONV3(const QJsonObject &parent, MinecraftEntitlement & out } -bool AccountData::resumeStateFromV2(QJsonObject data) { - // The JSON object must at least have a username for it to be valid. - if (!data.value("username").isString()) - { - qCritical() << "Can't load Mojang account info from JSON object. Username field is missing or of the wrong type."; - return false; - } - - QString userName = data.value("username").toString(""); - QString clientToken = data.value("clientToken").toString(""); - QString accessToken = data.value("accessToken").toString(""); - - QJsonArray profileArray = data.value("profiles").toArray(); - if (profileArray.size() < 1) - { - qCritical() << "Can't load Mojang account with username \"" << userName << "\". No profiles found."; - return false; - } - - struct AccountProfile - { - QString id; - QString name; - bool legacy; - }; - - QList<AccountProfile> profiles; - int currentProfileIndex = 0; - int index = -1; - QString currentProfile = data.value("activeProfile").toString(""); - for (QJsonValue profileVal : profileArray) - { - index++; - QJsonObject profileObject = profileVal.toObject(); - QString id = profileObject.value("id").toString(""); - QString name = profileObject.value("name").toString(""); - bool legacy = profileObject.value("legacy").toBool(false); - if (id.isEmpty() || name.isEmpty()) - { - qWarning() << "Unable to load a profile" << name << "because it was missing an ID or a name."; - continue; - } - if(id == currentProfile) { - currentProfileIndex = index; - } - profiles.append({id, name, legacy}); - } - auto & profile = profiles[currentProfileIndex]; - - type = AccountType::Mojang; - legacy = profile.legacy; - - minecraftProfile.id = profile.id; - minecraftProfile.name = profile.name; - minecraftProfile.validity = Katabasis::Validity::Assumed; - - yggdrasilToken.token = accessToken; - yggdrasilToken.extra["clientToken"] = clientToken; - yggdrasilToken.extra["userName"] = userName; - yggdrasilToken.validity = Katabasis::Validity::Assumed; - - validity_ = minecraftProfile.validity; - return true; -} - bool AccountData::resumeStateFromV3(QJsonObject data) { auto typeV = data.value("type"); if(!typeV.isString()) { @@ -312,24 +248,15 @@ bool AccountData::resumeStateFromV3(QJsonObject data) { auto typeS = typeV.toString(); if(typeS == "MSA") { type = AccountType::MSA; - } else if (typeS == "Mojang") { - type = AccountType::Mojang; } else { - qWarning() << "Failed to parse account data: type is not recognized."; + qWarning() << "Failed to parse account data: type is not recognized (only MSA is supported)."; return false; } - if(type == AccountType::Mojang) { - legacy = data.value("legacy").toBool(false); - canMigrateToMSA = data.value("canMigrateToMSA").toBool(false); - } - - if(type == AccountType::MSA) { - msaToken = tokenFromJSONV3(data, "msa"); - userToken = tokenFromJSONV3(data, "utoken"); - xboxApiToken = tokenFromJSONV3(data, "xrp-main"); - mojangservicesToken = tokenFromJSONV3(data, "xrp-mc"); - } + msaToken = tokenFromJSONV3(data, "msa"); + userToken = tokenFromJSONV3(data, "utoken"); + xboxApiToken = tokenFromJSONV3(data, "xrp-main"); + mojangservicesToken = tokenFromJSONV3(data, "xrp-mc"); yggdrasilToken = tokenFromJSONV3(data, "ygg"); minecraftProfile = profileFromJSONV3(data, "profile"); @@ -347,22 +274,11 @@ bool AccountData::resumeStateFromV3(QJsonObject data) { QJsonObject AccountData::saveState() const { QJsonObject output; - if(type == AccountType::Mojang) { - output["type"] = "Mojang"; - if(legacy) { - output["legacy"] = true; - } - if(canMigrateToMSA) { - output["canMigrateToMSA"] = true; - } - } - else if (type == AccountType::MSA) { - output["type"] = "MSA"; - tokenToJSONV3(output, msaToken, "msa"); - tokenToJSONV3(output, userToken, "utoken"); - tokenToJSONV3(output, xboxApiToken, "xrp-main"); - tokenToJSONV3(output, mojangservicesToken, "xrp-mc"); - } + output["type"] = "MSA"; + tokenToJSONV3(output, msaToken, "msa"); + tokenToJSONV3(output, userToken, "utoken"); + tokenToJSONV3(output, xboxApiToken, "xrp-main"); + tokenToJSONV3(output, mojangservicesToken, "xrp-mc"); tokenToJSONV3(output, yggdrasilToken, "ygg"); profileToJSONV3(output, minecraftProfile, "profile"); @@ -370,45 +286,10 @@ QJsonObject AccountData::saveState() const { return output; } -QString AccountData::userName() const { - if(type != AccountType::Mojang) { - return QString(); - } - return yggdrasilToken.extra["userName"].toString(); -} - QString AccountData::accessToken() const { return yggdrasilToken.token; } -QString AccountData::clientToken() const { - if(type != AccountType::Mojang) { - return QString(); - } - return yggdrasilToken.extra["clientToken"].toString(); -} - -void AccountData::setClientToken(QString clientToken) { - if(type != AccountType::Mojang) { - return; - } - yggdrasilToken.extra["clientToken"] = clientToken; -} - -void AccountData::generateClientTokenIfMissing() { - if(yggdrasilToken.extra.contains("clientToken")) { - return; - } - invalidateClientToken(); -} - -void AccountData::invalidateClientToken() { - if(type != AccountType::Mojang) { - return; - } - yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegExp("[{-}]")); -} - QString AccountData::profileId() const { return minecraftProfile.id; } @@ -423,20 +304,10 @@ QString AccountData::profileName() const { } QString AccountData::accountDisplayString() const { - switch(type) { - case AccountType::Mojang: { - return userName(); - } - case AccountType::MSA: { - if(xboxApiToken.extra.contains("gtg")) { - return xboxApiToken.extra["gtg"].toString(); - } - return "Xbox profile missing"; - } - default: { - return "Invalid Account"; - } + if(xboxApiToken.extra.contains("gtg")) { + return xboxApiToken.extra["gtg"].toString(); } + return "Xbox profile missing"; } QString AccountData::lastError() const { diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index abf84e43a6..dcd58b8d9f 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -37,8 +37,7 @@ struct MinecraftProfile { }; enum class AccountType { - MSA, - Mojang + MSA }; enum class AccountState { @@ -53,21 +52,11 @@ enum class AccountState { struct AccountData { QJsonObject saveState() const; - bool resumeStateFromV2(QJsonObject data); bool resumeStateFromV3(QJsonObject data); - //! userName for Mojang accounts, gamertag for MSA + //! Gamertag for MSA QString accountDisplayString() const; - //! Only valid for Mojang accounts. MSA does not preserve this information - QString userName() const; - - //! Only valid for Mojang accounts. - QString clientToken() const; - void setClientToken(QString clientToken); - void invalidateClientToken(); - void generateClientTokenIfMissing(); - //! Yggdrasil access token, as passed to the game. QString accessToken() const; @@ -77,8 +66,6 @@ struct AccountData { QString lastError() const; AccountType type = AccountType::MSA; - bool legacy = false; - bool canMigrateToMSA = false; Katabasis::Token msaToken; Katabasis::Token userToken; diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index ef8b435d13..47daac950d 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -301,18 +301,6 @@ QVariant AccountList::data(const QModelIndex &index, int role) const return account->profileName(); } - case MigrationColumn: { - if(account->isMSA()) { - return tr("N/A", "Can Migrate?"); - } - if (account->canMigrate()) { - return tr("Yes", "Can Migrate?"); - } - else { - return tr("No", "Can Migrate?"); - } - } - default: return QVariant(); } @@ -348,8 +336,6 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r return tr("Type"); case StatusColumn: return tr("Status"); - case MigrationColumn: - return tr("Can Migrate?"); case ProfileNameColumn: return tr("Profile"); default: @@ -362,11 +348,9 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r case NameColumn: return tr("User name of the account."); case TypeColumn: - return tr("Type of the account - Mojang or MSA."); + return tr("Type of the account."); case StatusColumn: return tr("Current status of the account."); - case MigrationColumn: - return tr("Can this account migrate to Microsoft account?"); case ProfileNameColumn: return tr("Name of the Minecraft profile associated with the account."); default: @@ -423,7 +407,7 @@ bool AccountList::loadList() { if (m_listFilePath.isEmpty()) { - qCritical() << "Can't load Mojang account list. No file path given and no default set."; + qCritical() << "Can't load account list. No file path given and no default set."; return false; } @@ -465,10 +449,6 @@ bool AccountList::loadList() // Make sure the format version matches. auto listVersion = root.value("formatVersion").toVariant().toInt(); switch(listVersion) { - case AccountListVersion::MojangOnly: { - return loadV2(root); - } - break; case AccountListVersion::MojangMSA: { return loadV3(root); } @@ -483,39 +463,6 @@ bool AccountList::loadList() } } -bool AccountList::loadV2(QJsonObject& root) { - beginResetModel(); - auto defaultUserName = root.value("activeAccount").toString(""); - QJsonArray accounts = root.value("accounts").toArray(); - for (QJsonValue accountVal : accounts) - { - QJsonObject accountObj = accountVal.toObject(); - MinecraftAccountPtr account = MinecraftAccount::loadFromJsonV2(accountObj); - if (account.get() != nullptr) - { - auto profileId = account->profileId(); - if(!profileId.size()) { - continue; - } - if(findAccountByProfileId(profileId) != -1) { - continue; - } - connect(account.get(), &MinecraftAccount::changed, this, &AccountList::accountChanged); - connect(account.get(), &MinecraftAccount::activityChanged, this, &AccountList::accountActivityChanged); - m_accounts.append(account); - if (defaultUserName.size() && account->mojangUserName() == defaultUserName) { - m_defaultAccount = account; - } - } - else - { - qWarning() << "Failed to load an account."; - } - } - endResetModel(); - return true; -} - bool AccountList::loadV3(QJsonObject& root) { beginResetModel(); QJsonArray accounts = root.value("accounts").toArray(); @@ -552,7 +499,7 @@ bool AccountList::saveList() { if (m_listFilePath.isEmpty()) { - qCritical() << "Can't save Mojang account list. No file path given and no default set."; + qCritical() << "Can't save account list. No file path given and no default set."; return false; } diff --git a/launcher/minecraft/auth/AccountList.h b/launcher/minecraft/auth/AccountList.h index 9f020a5033..c6e315a302 100644 --- a/launcher/minecraft/auth/AccountList.h +++ b/launcher/minecraft/auth/AccountList.h @@ -40,7 +40,6 @@ public: // TODO: Add icon column. NameColumn = 0, ProfileNameColumn, - MigrationColumn, TypeColumn, StatusColumn, @@ -82,7 +81,6 @@ public: void setListFilePath(QString path, bool autosave = false); bool loadList(); - bool loadV2(QJsonObject &root); bool loadV3(QJsonObject &root); bool saveList(); diff --git a/launcher/minecraft/auth/AccountTask.h b/launcher/minecraft/auth/AccountTask.h index dac3f1b551..4084da3536 100644 --- a/launcher/minecraft/auth/AccountTask.h +++ b/launcher/minecraft/auth/AccountTask.h @@ -55,8 +55,7 @@ public: } signals: - void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn); - void hideVerificationUriAndCode(); + void authorizeWithBrowser(const QUrl &url); protected: diff --git a/launcher/minecraft/auth/AuthRequest.cpp b/launcher/minecraft/auth/AuthRequest.cpp index feface80f8..1f5813d616 100644 --- a/launcher/minecraft/auth/AuthRequest.cpp +++ b/launcher/minecraft/auth/AuthRequest.cpp @@ -20,7 +20,7 @@ void AuthRequest::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) { reply_ = APPLICATION->network()->get(request_); status_ = Requesting; timedReplies_.add(new Katabasis::Reply(reply_, timeout)); - connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError))); + connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError))); connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished())); connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors); } @@ -31,7 +31,7 @@ void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int t status_ = Requesting; reply_ = APPLICATION->network()->post(request_, data_); timedReplies_.add(new Katabasis::Reply(reply_, timeout)); - connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError))); + connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError))); connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished())); connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors); connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64))); diff --git a/launcher/minecraft/auth/AuthStep.h b/launcher/minecraft/auth/AuthStep.h index 2a8dc2cac9..7ae99d63fc 100644 --- a/launcher/minecraft/auth/AuthStep.h +++ b/launcher/minecraft/auth/AuthStep.h @@ -25,8 +25,7 @@ public slots: signals: void finished(AccountTaskState resultingState, QString message); - void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn); - void hideVerificationUriAndCode(); + void authorizeWithBrowser(const QUrl &url); protected: AccountData *m_data; diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index ed9e945ee2..8fdae71c18 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -20,7 +20,7 @@ #include <QUuid> #include <QJsonObject> #include <QJsonArray> -#include <QRegExp> +#include <QRegularExpression> #include <QStringList> #include <QJsonDocument> @@ -29,21 +29,12 @@ #include <QPainter> #include "flows/MSA.h" -#include "flows/Mojang.h" MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) { - data.internalId = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); } -MinecraftAccountPtr MinecraftAccount::loadFromJsonV2(const QJsonObject& json) { - MinecraftAccountPtr account(new MinecraftAccount()); - if(account->data.resumeStateFromV2(json)) { - return account; - } - return nullptr; -} - MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) { MinecraftAccountPtr account(new MinecraftAccount()); if(account->data.resumeStateFromV3(json)) { @@ -52,14 +43,6 @@ MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) { return nullptr; } -MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString &username) -{ - MinecraftAccountPtr account = new MinecraftAccount(); - account->data.type = AccountType::Mojang; - account->data.yggdrasilToken.extra["userName"] = username; - account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); - return account; -} MinecraftAccountPtr MinecraftAccount::createBlankMSA() { @@ -91,16 +74,6 @@ QPixmap MinecraftAccount::getFace() const { } -shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password) { - Q_ASSERT(m_currentTask.get() == nullptr); - - m_currentTask.reset(new MojangLogin(&data, password)); - connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); - connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); - emit activityChanged(true); - return m_currentTask; -} - shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA() { Q_ASSERT(m_currentTask.get() == nullptr); @@ -116,12 +89,7 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::refresh() { return m_currentTask; } - if(data.type == AccountType::MSA) { m_currentTask.reset(new MSASilent(&data)); - } - else { - m_currentTask.reset(new MojangRefresh(&data)); - } connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); @@ -150,17 +118,10 @@ void MinecraftAccount::authFailed(QString reason) } break; case AccountTaskState::STATE_FAILED_HARD: { - if(isMSA()) { data.msaToken.token = QString(); data.msaToken.refresh_token = QString(); data.msaToken.validity = Katabasis::Validity::None; data.validity_ = Katabasis::Validity::None; - } - else { - data.yggdrasilToken.token = QString(); - data.yggdrasilToken.validity = Katabasis::Validity::None; - data.validity_ = Katabasis::Validity::None; - } emit changed(); } break; @@ -205,8 +166,8 @@ bool MinecraftAccount::shouldRefresh() const { } } auto now = QDateTime::currentDateTimeUtc(); - auto issuedTimestamp = data.yggdrasilToken.issueInstant; - auto expiresTimestamp = data.yggdrasilToken.notAfter; + auto issuedTimestamp = data.msaToken.issueInstant; + auto expiresTimestamp = data.msaToken.notAfter; if(!expiresTimestamp.isValid()) { expiresTimestamp = issuedTimestamp.addSecs(24 * 3600); @@ -231,13 +192,12 @@ void MinecraftAccount::fillSession(AuthSessionPtr session) } } - // the user name. you have to have an user name - // FIXME: not with MSA - session->username = data.userName(); + // the user name + session->username = data.profileName(); // volatile auth token session->access_token = data.accessToken(); // the semi-permanent client token - session->client_token = data.clientToken(); + session->client_token = QString(); // profile name session->player_name = data.profileName(); // profile ID diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index 0299d77858..fbb012f58c 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -69,11 +69,8 @@ public: /* construction */ //! Default constructor explicit MinecraftAccount(QObject *parent = 0); - static MinecraftAccountPtr createFromUsername(const QString &username); - static MinecraftAccountPtr createBlankMSA(); - static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json); static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json); //! Saves a MinecraftAccount to a JSON object and returns it. @@ -81,12 +78,6 @@ public: /* construction */ public: /* manipulation */ - /** - * Attempt to login. Empty password means we use the token. - * If the attempt fails because we already are performing some task, it returns false. - */ - shared_qobject_ptr<AccountTask> login(QString password); - shared_qobject_ptr<AccountTask> loginMSA(); shared_qobject_ptr<AccountTask> refresh(); @@ -102,10 +93,6 @@ public: /* queries */ return data.accountDisplayString(); } - QString mojangUserName() const { - return data.userName(); - } - QString accessToken() const { return data.accessToken(); } @@ -120,10 +107,6 @@ public: /* queries */ bool isActive() const; - bool canMigrate() const { - return data.canMigrateToMSA; - } - bool isMSA() const { return data.type == AccountType::MSA; } @@ -137,22 +120,7 @@ public: /* queries */ } QString typeString() const { - switch(data.type) { - case AccountType::Mojang: { - if(data.legacy) { - return "legacy"; - } - return "mojang"; - } - break; - case AccountType::MSA: { return "msa"; - } - break; - default: { - return "unknown"; - } - } } QPixmap getFace() const; diff --git a/launcher/minecraft/auth/Yggdrasil.cpp b/launcher/minecraft/auth/Yggdrasil.cpp deleted file mode 100644 index 7ac842a67d..0000000000 --- a/launcher/minecraft/auth/Yggdrasil.cpp +++ /dev/null @@ -1,331 +0,0 @@ -/* Copyright 2013-2021 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 "Yggdrasil.h" -#include "AccountData.h" - -#include <QObject> -#include <QString> -#include <QJsonObject> -#include <QJsonDocument> -#include <QNetworkReply> -#include <QByteArray> - -#include <QDebug> - -#include "Application.h" - -Yggdrasil::Yggdrasil(AccountData *data, QObject *parent) - : AccountTask(data, parent) -{ - changeState(AccountTaskState::STATE_CREATED); -} - -void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content) { - changeState(AccountTaskState::STATE_WORKING); - - QNetworkRequest netRequest(endpoint); - netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - m_netReply = APPLICATION->network()->post(netRequest, content); - connect(m_netReply, &QNetworkReply::finished, this, &Yggdrasil::processReply); - connect(m_netReply, &QNetworkReply::uploadProgress, this, &Yggdrasil::refreshTimers); - connect(m_netReply, &QNetworkReply::downloadProgress, this, &Yggdrasil::refreshTimers); - connect(m_netReply, &QNetworkReply::sslErrors, this, &Yggdrasil::sslErrors); - timeout_keeper.setSingleShot(true); - timeout_keeper.start(timeout_max); - counter.setSingleShot(false); - counter.start(time_step); - progress(0, timeout_max); - connect(&timeout_keeper, &QTimer::timeout, this, &Yggdrasil::abortByTimeout); - connect(&counter, &QTimer::timeout, this, &Yggdrasil::heartbeat); -} - -void Yggdrasil::executeTask() { -} - -void Yggdrasil::refresh() { - start(); - /* - * { - * "clientToken": "client identifier" - * "accessToken": "current access token to be refreshed" - * "selectedProfile": // specifying this causes errors - * { - * "id": "profile ID" - * "name": "profile name" - * } - * "requestUser": true/false // request the user structure - * } - */ - QJsonObject req; - req.insert("clientToken", m_data->clientToken()); - req.insert("accessToken", m_data->accessToken()); - /* - { - auto currentProfile = m_account->currentProfile(); - QJsonObject profile; - profile.insert("id", currentProfile->id()); - profile.insert("name", currentProfile->name()); - req.insert("selectedProfile", profile); - } - */ - req.insert("requestUser", false); - QJsonDocument doc(req); - - QUrl reqUrl("https://authserver.mojang.com/refresh"); - QByteArray requestData = doc.toJson(); - - sendRequest(reqUrl, requestData); -} - -void Yggdrasil::login(QString password) { - start(); - /* - * { - * "agent": { // optional - * "name": "Minecraft", // So far this is the only encountered value - * "version": 1 // This number might be increased - * // by the vanilla client in the future - * }, - * "username": "mojang account name", // Can be an email address or player name for - * // unmigrated accounts - * "password": "mojang account password", - * "clientToken": "client identifier", // optional - * "requestUser": true/false // request the user structure - * } - */ - QJsonObject req; - - { - QJsonObject agent; - // C++ makes string literals void* for some stupid reason, so we have to tell it - // QString... Thanks Obama. - agent.insert("name", QString("Minecraft")); - agent.insert("version", 1); - req.insert("agent", agent); - } - - req.insert("username", m_data->userName()); - req.insert("password", password); - req.insert("requestUser", false); - - // If we already have a client token, give it to the server. - // Otherwise, let the server give us one. - - m_data->generateClientTokenIfMissing(); - req.insert("clientToken", m_data->clientToken()); - - QJsonDocument doc(req); - - QUrl reqUrl("https://authserver.mojang.com/authenticate"); - QNetworkRequest netRequest(reqUrl); - QByteArray requestData = doc.toJson(); - - sendRequest(reqUrl, requestData); -} - - - -void Yggdrasil::refreshTimers(qint64, qint64) { - timeout_keeper.stop(); - timeout_keeper.start(timeout_max); - progress(count = 0, timeout_max); -} - -void Yggdrasil::heartbeat() { - count += time_step; - progress(count, timeout_max); -} - -bool Yggdrasil::abort() { - progress(timeout_max, timeout_max); - // TODO: actually use this in a meaningful way - m_aborted = Yggdrasil::BY_USER; - m_netReply->abort(); - return true; -} - -void Yggdrasil::abortByTimeout() { - progress(timeout_max, timeout_max); - // TODO: actually use this in a meaningful way - m_aborted = Yggdrasil::BY_TIMEOUT; - m_netReply->abort(); -} - -void Yggdrasil::sslErrors(QList<QSslError> errors) { - int i = 1; - for (auto error : errors) { - qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } -} - -void Yggdrasil::processResponse(QJsonObject responseData) { - // Read the response data. We need to get the client token, access token, and the selected - // profile. - qDebug() << "Processing authentication response."; - - // qDebug() << responseData; - // If we already have a client token, make sure the one the server gave us matches our - // existing one. - QString clientToken = responseData.value("clientToken").toString(""); - if (clientToken.isEmpty()) { - // Fail if the server gave us an empty client token - changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); - return; - } - if(m_data->clientToken().isEmpty()) { - m_data->setClientToken(clientToken); - } - else if(clientToken != m_data->clientToken()) { - changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported.")); - return; - } - - // Now, we set the access token. - qDebug() << "Getting access token."; - QString accessToken = responseData.value("accessToken").toString(""); - if (accessToken.isEmpty()) { - // Fail if the server didn't give us an access token. - changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); - return; - } - // Set the access token. - m_data->yggdrasilToken.token = accessToken; - m_data->yggdrasilToken.validity = Katabasis::Validity::Certain; - m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); - - // We've made it through the minefield of possible errors. Return true to indicate that - // we've succeeded. - qDebug() << "Finished reading authentication response."; - changeState(AccountTaskState::STATE_SUCCEEDED); -} - -void Yggdrasil::processReply() { - changeState(AccountTaskState::STATE_WORKING); - - switch (m_netReply->error()) - { - case QNetworkReply::NoError: - break; - case QNetworkReply::TimeoutError: - changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation timed out.")); - return; - case QNetworkReply::OperationCanceledError: - changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation cancelled.")); - return; - case QNetworkReply::SslHandshakeFailedError: - changeState( - AccountTaskState::STATE_FAILED_SOFT, - tr( - "<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>" - "<ul>" - "<li>You use Windows and need to update your root certificates, please install any outstanding updates.</li>" - "<li>Some device on your network is interfering with SSL traffic. In that case, " - "you have bigger worries than Minecraft not starting.</li>" - "<li>Possibly something else. Check the log file for details</li>" - "</ul>" - ) - ); - return; - // used for invalid credentials and similar errors. Fall through. - case QNetworkReply::ContentAccessDenied: - case QNetworkReply::ContentOperationNotPermittedError: - break; - case QNetworkReply::ContentGoneError: { - changeState( - AccountTaskState::STATE_FAILED_GONE, - tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account.") - ); - } - default: - changeState( - AccountTaskState::STATE_FAILED_SOFT, - tr("Authentication operation failed due to a network error: %1 (%2)").arg(m_netReply->errorString()).arg(m_netReply->error()) - ); - return; - } - - // Try to parse the response regardless of the response code. - // Sometimes the auth server will give more information and an error code. - QJsonParseError jsonError; - QByteArray replyData = m_netReply->readAll(); - QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError); - // Check the response code. - int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (responseCode == 200) { - // If the response code was 200, then there shouldn't be an error. Make sure - // anyways. - // Also, sometimes an empty reply indicates success. If there was no data received, - // pass an empty json object to the processResponse function. - if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) { - processResponse(replyData.size() > 0 ? doc.object() : QJsonObject()); - return; - } - else { - changeState( - AccountTaskState::STATE_FAILED_SOFT, - tr("Failed to parse authentication server response JSON response: %1 at offset %2.").arg(jsonError.errorString()).arg(jsonError.offset) - ); - qCritical() << replyData; - } - return; - } - - // If the response code was not 200, then Yggdrasil may have given us information - // about the error. - // If we can parse the response, then get information from it. Otherwise just say - // there was an unknown error. - if (jsonError.error == QJsonParseError::NoError) { - // We were able to parse the server's response. Woo! - // Call processError. If a subclass has overridden it then they'll handle their - // stuff there. - qDebug() << "The request failed, but the server gave us an error message. Processing error."; - processError(doc.object()); - } - else { - // The server didn't say anything regarding the error. Give the user an unknown - // error. - qDebug() << "The request failed and the server gave no error message. Unknown error."; - changeState( - AccountTaskState::STATE_FAILED_SOFT, - tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(m_netReply->errorString()) - ); - } -} - -void Yggdrasil::processError(QJsonObject responseData) { - QJsonValue errorVal = responseData.value("error"); - QJsonValue errorMessageValue = responseData.value("errorMessage"); - QJsonValue causeVal = responseData.value("cause"); - - if (errorVal.isString() && errorMessageValue.isString()) { - m_error = std::shared_ptr<Error>( - new Error { - errorVal.toString(""), - errorMessageValue.toString(""), - causeVal.toString("") - } - ); - changeState(AccountTaskState::STATE_FAILED_HARD, m_error->m_errorMessageVerbose); - } - else { - // Error is not in standard format. Don't set m_error and return unknown error. - changeState(AccountTaskState::STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred.")); - } -} diff --git a/launcher/minecraft/auth/Yggdrasil.h b/launcher/minecraft/auth/Yggdrasil.h deleted file mode 100644 index 4f52a04c2c..0000000000 --- a/launcher/minecraft/auth/Yggdrasil.h +++ /dev/null @@ -1,102 +0,0 @@ -/* Copyright 2013-2021 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 "AccountTask.h" - -#include <QString> -#include <QJsonObject> -#include <QTimer> -#include <qsslerror.h> - -#include "MinecraftAccount.h" - -class QNetworkAccessManager; -class QNetworkReply; - -/** - * A Yggdrasil task is a task that performs an operation on a given mojang account. - */ -class Yggdrasil : public AccountTask -{ - Q_OBJECT -public: - explicit Yggdrasil( - AccountData *data, - QObject *parent = 0 - ); - virtual ~Yggdrasil() = default; - - void refresh(); - void login(QString password); - - struct Error - { - QString m_errorMessageShort; - QString m_errorMessageVerbose; - QString m_cause; - }; - std::shared_ptr<Error> m_error; - - enum AbortedBy - { - BY_NOTHING, - BY_USER, - BY_TIMEOUT - } m_aborted = BY_NOTHING; - -protected: - void executeTask() override; - - /** - * Processes the response received from the server. - * If an error occurred, this should emit a failed signal. - * If Yggdrasil gave an error response, it should call setError() first, and then return false. - * Otherwise, it should return true. - * Note: If the response from the server was blank, and the HTTP code was 200, this function is called with - * an empty QJsonObject. - */ - void processResponse(QJsonObject responseData); - - /** - * Processes an error response received from the server. - * The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error. - * \returns a QString error message that will be passed to emitFailed. - */ - virtual void processError(QJsonObject responseData); - -protected slots: - void processReply(); - void refreshTimers(qint64, qint64); - void heartbeat(); - void sslErrors(QList<QSslError>); - void abortByTimeout(); - -public slots: - virtual bool abort() override; - -private: - void sendRequest(QUrl endpoint, QByteArray content); - -protected: - QNetworkReply *m_netReply = nullptr; - QTimer timeout_keeper; - QTimer counter; - int count = 0; // num msec since time reset - - const int timeout_max = 30000; - const int time_step = 50; -}; diff --git a/launcher/minecraft/auth/flows/AuthFlow.cpp b/launcher/minecraft/auth/flows/AuthFlow.cpp index 4f78e8c31e..46904a48af 100644 --- a/launcher/minecraft/auth/flows/AuthFlow.cpp +++ b/launcher/minecraft/auth/flows/AuthFlow.cpp @@ -40,8 +40,7 @@ void AuthFlow::nextStep() { qDebug() << "AuthFlow:" << m_currentStep->describe(); m_steps.pop_front(); connect(m_currentStep.get(), &AuthStep::finished, this, &AuthFlow::stepFinished); - connect(m_currentStep.get(), &AuthStep::showVerificationUriAndCode, this, &AuthFlow::showVerificationUriAndCode); - connect(m_currentStep.get(), &AuthStep::hideVerificationUriAndCode, this, &AuthFlow::hideVerificationUriAndCode); + connect(m_currentStep.get(), &AuthStep::authorizeWithBrowser, this, &AuthFlow::authorizeWithBrowser); m_currentStep->perform(); } diff --git a/launcher/minecraft/auth/flows/AuthFlow.h b/launcher/minecraft/auth/flows/AuthFlow.h index e067cc99fb..e840060be4 100644 --- a/launcher/minecraft/auth/flows/AuthFlow.h +++ b/launcher/minecraft/auth/flows/AuthFlow.h @@ -7,9 +7,6 @@ #include <QNetworkReply> #include <QImage> -#include <katabasis/DeviceFlow.h> - -#include "minecraft/auth/Yggdrasil.h" #include "minecraft/auth/AccountData.h" #include "minecraft/auth/AccountTask.h" #include "minecraft/auth/AuthStep.h" @@ -30,7 +27,7 @@ public: void executeTask() override; signals: - void activityChanged(Katabasis::Activity activity); + // No extra signals needed - authorizeWithBrowser is on AccountTask private slots: void stepFinished(AccountTaskState resultingState, QString message); diff --git a/launcher/minecraft/auth/flows/Mojang.cpp b/launcher/minecraft/auth/flows/Mojang.cpp deleted file mode 100644 index 4661dbe23a..0000000000 --- a/launcher/minecraft/auth/flows/Mojang.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "Mojang.h" - -#include "minecraft/auth/steps/YggdrasilStep.h" -#include "minecraft/auth/steps/MinecraftProfileStep.h" -#include "minecraft/auth/steps/MigrationEligibilityStep.h" -#include "minecraft/auth/steps/GetSkinStep.h" - -MojangRefresh::MojangRefresh( - AccountData *data, - QObject *parent -) : AuthFlow(data, parent) { - m_steps.append(new YggdrasilStep(m_data, QString())); - m_steps.append(new MinecraftProfileStep(m_data)); - m_steps.append(new MigrationEligibilityStep(m_data)); - m_steps.append(new GetSkinStep(m_data)); -} - -MojangLogin::MojangLogin( - AccountData *data, - QString password, - QObject *parent -): AuthFlow(data, parent), m_password(password) { - m_steps.append(new YggdrasilStep(m_data, m_password)); - m_steps.append(new MinecraftProfileStep(m_data)); - m_steps.append(new MigrationEligibilityStep(m_data)); - m_steps.append(new GetSkinStep(m_data)); -} diff --git a/launcher/minecraft/auth/flows/Mojang.h b/launcher/minecraft/auth/flows/Mojang.h deleted file mode 100644 index c09c81a807..0000000000 --- a/launcher/minecraft/auth/flows/Mojang.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include "AuthFlow.h" - -class MojangRefresh : public AuthFlow -{ - Q_OBJECT -public: - explicit MojangRefresh( - AccountData *data, - QObject *parent = 0 - ); -}; - -class MojangLogin : public AuthFlow -{ - Q_OBJECT -public: - explicit MojangLogin( - AccountData *data, - QString password, - QObject *parent = 0 - ); - -private: - QString m_password; -}; diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp index be711f7e8c..b3d57eb15e 100644 --- a/launcher/minecraft/auth/steps/MSAStep.cpp +++ b/launcher/minecraft/auth/steps/MSAStep.cpp @@ -1,27 +1,28 @@ #include "MSAStep.h" #include <QNetworkRequest> +#include <QDesktopServices> #include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" #include "Application.h" -using OAuth2 = Katabasis::DeviceFlow; -using Activity = Katabasis::Activity; - MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action) { - OAuth2::Options opts; - opts.scope = "XboxLive.signin offline_access"; - opts.clientIdentifier = APPLICATION->msaClientId(); - opts.authorizationUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode"; - opts.accessTokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; + m_replyHandler = new QOAuthHttpServerReplyHandler(this); + m_replyHandler->setCallbackText(tr("Login successful! You can close this page and return to MeshMC.")); - // FIXME: OAuth2 is not aware of our fancy shared pointers - m_oauth2 = new OAuth2(opts, m_data->msaToken, this, APPLICATION->network().get()); + m_oauth2 = new QOAuth2AuthorizationCodeFlow(this); + m_oauth2->setClientIdentifier(APPLICATION->msaClientId()); + m_oauth2->setAuthorizationUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize")); + m_oauth2->setTokenUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/token")); + m_oauth2->setScope("XboxLive.signin offline_access"); + m_oauth2->setReplyHandler(m_replyHandler); + m_oauth2->setNetworkAccessManager(APPLICATION->network().get()); - connect(m_oauth2, &OAuth2::activityChanged, this, &MSAStep::onOAuthActivityChanged); - connect(m_oauth2, &OAuth2::showVerificationUriAndCode, this, &MSAStep::showVerificationUriAndCode); + connect(m_oauth2, &QOAuth2AuthorizationCodeFlow::granted, this, &MSAStep::onGranted); + connect(m_oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, &MSAStep::onRequestFailed); + connect(m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &MSAStep::onOpenBrowser); } MSAStep::~MSAStep() noexcept = default; @@ -47,65 +48,71 @@ void MSAStep::rehydrate() { void MSAStep::perform() { switch(m_action) { case Refresh: { - m_oauth2->refresh(); + // Load the refresh token from stored account data + m_oauth2->setRefreshToken(m_data->msaToken.refresh_token); + m_oauth2->refreshTokens(); return; } case Login: { - QVariantMap extraOpts; - extraOpts["prompt"] = "select_account"; - m_oauth2->setExtraRequestParams(extraOpts); - *m_data = AccountData(); - m_oauth2->login(); + if (!m_replyHandler->isListening()) { + if (!m_replyHandler->listen(QHostAddress::LocalHost)) { + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Failed to start local HTTP server for OAuth2 callback.")); + return; + } + } + m_oauth2->setModifyParametersFunction( + [](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant> *parameters) { + if (stage == QAbstractOAuth::Stage::RequestingAuthorization) { + parameters->insert("prompt", "select_account"); + } + } + ); + m_oauth2->grant(); return; } } } -void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity) { - switch(activity) { - case Katabasis::Activity::Idle: - case Katabasis::Activity::LoggingIn: - case Katabasis::Activity::Refreshing: - case Katabasis::Activity::LoggingOut: { - // We asked it to do something, it's doing it. Nothing to act upon. - return; - } - case Katabasis::Activity::Succeeded: { - // Succeeded or did not invalidate tokens - emit hideVerificationUriAndCode(); - QVariantMap extraTokens = m_oauth2->extraTokens(); -#ifndef NDEBUG - if (!extraTokens.isEmpty()) { - qDebug() << "Extra tokens in response:"; - foreach (QString key, extraTokens.keys()) { - qDebug() << "\t" << key << ":" << extraTokens.value(key); - } - } -#endif - emit finished(AccountTaskState::STATE_WORKING, tr("Got ")); - return; - } - case Katabasis::Activity::FailedSoft: { - // NOTE: soft error in the first step means 'offline' - emit hideVerificationUriAndCode(); - emit finished(AccountTaskState::STATE_OFFLINE, tr("Microsoft user authentication ended with a network error.")); +void MSAStep::onOpenBrowser(const QUrl &url) { + emit authorizeWithBrowser(url); + QDesktopServices::openUrl(url); +} + +void MSAStep::onGranted() { + m_replyHandler->close(); + + // Store the tokens in account data + m_data->msaToken.token = m_oauth2->token(); + m_data->msaToken.refresh_token = m_oauth2->refreshToken(); + m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc(); + m_data->msaToken.notAfter = m_oauth2->expirationAt(); + if (!m_data->msaToken.notAfter.isValid()) { + m_data->msaToken.notAfter = m_data->msaToken.issueInstant.addSecs(3600); + } + m_data->msaToken.validity = Katabasis::Validity::Certain; + + emit finished(AccountTaskState::STATE_WORKING, tr("Got MSA token.")); +} + +void MSAStep::onRequestFailed(QAbstractOAuth::Error error) { + m_replyHandler->close(); + + switch(error) { + case QAbstractOAuth::Error::NetworkError: + emit finished(AccountTaskState::STATE_OFFLINE, tr("Microsoft authentication failed due to a network error.")); return; - } - case Katabasis::Activity::FailedGone: { - emit hideVerificationUriAndCode(); - emit finished(AccountTaskState::STATE_FAILED_GONE, tr("Microsoft user authentication failed - user no longer exists.")); + case QAbstractOAuth::Error::ServerError: + case QAbstractOAuth::Error::OAuthTokenNotFoundError: + case QAbstractOAuth::Error::OAuthTokenSecretNotFoundError: + case QAbstractOAuth::Error::OAuthCallbackNotVerified: + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft authentication failed.")); return; - } - case Katabasis::Activity::FailedHard: { - emit hideVerificationUriAndCode(); - emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication failed.")); + case QAbstractOAuth::Error::ExpiredError: + emit finished(AccountTaskState::STATE_FAILED_GONE, tr("Microsoft authentication token expired.")); return; - } - default: { - emit hideVerificationUriAndCode(); - emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication completed with an unrecognized result.")); + default: + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft authentication failed with an unrecognized error.")); return; - } } } diff --git a/launcher/minecraft/auth/steps/MSAStep.h b/launcher/minecraft/auth/steps/MSAStep.h index 49ba3542e7..36fc23213f 100644 --- a/launcher/minecraft/auth/steps/MSAStep.h +++ b/launcher/minecraft/auth/steps/MSAStep.h @@ -5,7 +5,8 @@ #include "QObjectPtr.h" #include "minecraft/auth/AuthStep.h" -#include <katabasis/DeviceFlow.h> +#include <QOAuth2AuthorizationCodeFlow> +#include <QOAuthHttpServerReplyHandler> class MSAStep : public AuthStep { Q_OBJECT @@ -24,9 +25,12 @@ public: QString describe() override; private slots: - void onOAuthActivityChanged(Katabasis::Activity activity); + void onGranted(); + void onRequestFailed(QAbstractOAuth::Error error); + void onOpenBrowser(const QUrl &url); private: - Katabasis::DeviceFlow *m_oauth2 = nullptr; + QOAuth2AuthorizationCodeFlow *m_oauth2 = nullptr; + QOAuthHttpServerReplyHandler *m_replyHandler = nullptr; Action m_action; }; diff --git a/launcher/minecraft/auth/steps/MigrationEligibilityStep.cpp b/launcher/minecraft/auth/steps/MigrationEligibilityStep.cpp deleted file mode 100644 index f5b5637a47..0000000000 --- a/launcher/minecraft/auth/steps/MigrationEligibilityStep.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "MigrationEligibilityStep.h" - -#include <QNetworkRequest> - -#include "minecraft/auth/AuthRequest.h" -#include "minecraft/auth/Parsers.h" - -MigrationEligibilityStep::MigrationEligibilityStep(AccountData* data) : AuthStep(data) { - -} - -MigrationEligibilityStep::~MigrationEligibilityStep() noexcept = default; - -QString MigrationEligibilityStep::describe() { - return tr("Checking for migration eligibility."); -} - -void MigrationEligibilityStep::perform() { - auto url = QUrl("https://api.minecraftservices.com/rollout/v1/msamigration"); - QNetworkRequest request = QNetworkRequest(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8()); - - AuthRequest *requestor = new AuthRequest(this); - connect(requestor, &AuthRequest::finished, this, &MigrationEligibilityStep::onRequestDone); - requestor->get(request); -} - -void MigrationEligibilityStep::rehydrate() { - // NOOP, for now. We only save bools and there's nothing to check. -} - -void MigrationEligibilityStep::onRequestDone( - QNetworkReply::NetworkError error, - QByteArray data, - QList<QNetworkReply::RawHeaderPair> headers -) { - auto requestor = qobject_cast<AuthRequest *>(QObject::sender()); - requestor->deleteLater(); - - if (error == QNetworkReply::NoError) { - Parsers::parseRolloutResponse(data, m_data->canMigrateToMSA); - } - emit finished(AccountTaskState::STATE_WORKING, tr("Got migration flags")); -} diff --git a/launcher/minecraft/auth/steps/MigrationEligibilityStep.h b/launcher/minecraft/auth/steps/MigrationEligibilityStep.h deleted file mode 100644 index b1bf9cbf49..0000000000 --- a/launcher/minecraft/auth/steps/MigrationEligibilityStep.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include <QObject> - -#include "QObjectPtr.h" -#include "minecraft/auth/AuthStep.h" - - -class MigrationEligibilityStep : public AuthStep { - Q_OBJECT - -public: - explicit MigrationEligibilityStep(AccountData *data); - virtual ~MigrationEligibilityStep() noexcept; - - void perform() override; - void rehydrate() override; - - QString describe() override; - -private slots: - void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); -}; diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index add9165998..94dddf1786 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -44,10 +44,6 @@ void MinecraftProfileStep::onRequestDone( #endif if (error == QNetworkReply::ContentNotFoundError) { // NOTE: Succeed even if we do not have a profile. This is a valid account state. - if(m_data->type == AccountType::Mojang) { - m_data->minecraftEntitlement.canPlayMinecraft = false; - m_data->minecraftEntitlement.ownsMinecraft = false; - } m_data->minecraftProfile = MinecraftProfile(); emit finished( AccountTaskState::STATE_SUCCEEDED, @@ -79,11 +75,6 @@ void MinecraftProfileStep::onRequestDone( return; } - if(m_data->type == AccountType::Mojang) { - auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain; - m_data->minecraftEntitlement.canPlayMinecraft = validProfile; - m_data->minecraftEntitlement.ownsMinecraft = validProfile; - } emit finished( AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded.") diff --git a/launcher/minecraft/auth/steps/YggdrasilStep.cpp b/launcher/minecraft/auth/steps/YggdrasilStep.cpp deleted file mode 100644 index 4c6b1624b3..0000000000 --- a/launcher/minecraft/auth/steps/YggdrasilStep.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "YggdrasilStep.h" - -#include "minecraft/auth/AuthRequest.h" -#include "minecraft/auth/Parsers.h" -#include "minecraft/auth/Yggdrasil.h" - -YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(data), m_password(password) { - m_yggdrasil = new Yggdrasil(m_data, this); - - connect(m_yggdrasil, &Task::failed, this, &YggdrasilStep::onAuthFailed); - connect(m_yggdrasil, &Task::succeeded, this, &YggdrasilStep::onAuthSucceeded); -} - -YggdrasilStep::~YggdrasilStep() noexcept = default; - -QString YggdrasilStep::describe() { - return tr("Logging in with Mojang account."); -} - -void YggdrasilStep::rehydrate() { - // NOOP, for now. -} - -void YggdrasilStep::perform() { - if(m_password.size()) { - m_yggdrasil->login(m_password); - } - else { - m_yggdrasil->refresh(); - } -} - -void YggdrasilStep::onAuthSucceeded() { - emit finished(AccountTaskState::STATE_WORKING, tr("Logged in with Mojang")); -} - -void YggdrasilStep::onAuthFailed() { - // TODO: hook these in again, expand to MSA - // m_error = m_yggdrasil->m_error; - // m_aborted = m_yggdrasil->m_aborted; - - auto state = m_yggdrasil->taskState(); - QString errorMessage = tr("Mojang user authentication failed."); - - // NOTE: soft error in the first step means 'offline' - if(state == AccountTaskState::STATE_FAILED_SOFT) { - state = AccountTaskState::STATE_OFFLINE; - errorMessage = tr("Mojang user authentication ended with a network error."); - } - emit finished(state, errorMessage); -} diff --git a/launcher/minecraft/auth/steps/YggdrasilStep.h b/launcher/minecraft/auth/steps/YggdrasilStep.h deleted file mode 100644 index ebafb8e5b1..0000000000 --- a/launcher/minecraft/auth/steps/YggdrasilStep.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include <QObject> - -#include "QObjectPtr.h" -#include "minecraft/auth/AuthStep.h" - -class Yggdrasil; - -class YggdrasilStep : public AuthStep { - Q_OBJECT - -public: - explicit YggdrasilStep(AccountData *data, QString password); - virtual ~YggdrasilStep() noexcept; - - void perform() override; - void rehydrate() override; - - QString describe() override; - -private slots: - void onAuthSucceeded(); - void onAuthFailed(); - -private: - Yggdrasil *m_yggdrasil = nullptr; - QString m_password; -}; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index f0c53c3929..dbc613d79d 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -89,9 +89,11 @@ bool ModFolderModel::update() void ModFolderModel::finishUpdate() { - QSet<QString> currentSet = modsIndex.keys().toSet(); + auto keys1 = modsIndex.keys(); + QSet<QString> currentSet(keys1.begin(), keys1.end()); auto & newMods = m_update->mods; - QSet<QString> newSet = newMods.keys().toSet(); + auto keys2 = newMods.keys(); + QSet<QString> newSet(keys2.begin(), keys2.end()); // see if the kept mods changed in some way { @@ -278,7 +280,7 @@ bool ModFolderModel::installMod(const QString &filename) return false; } FS::updateTimestamp(newpath); - installedMod.repath(newpath); + installedMod.repath(QFileInfo(newpath)); update(); return true; } @@ -296,7 +298,7 @@ bool ModFolderModel::installMod(const QString &filename) qWarning() << "Copy of folder from" << originalPath << "to" << newpath << "has (potentially partially) failed."; return false; } - installedMod.repath(newpath); + installedMod.repath(QFileInfo(newpath)); update(); return true; } diff --git a/launcher/minecraft/services/CapeChange.cpp b/launcher/minecraft/services/CapeChange.cpp index e49c166abe..25041d26e1 100644 --- a/launcher/minecraft/services/CapeChange.cpp +++ b/launcher/minecraft/services/CapeChange.cpp @@ -20,7 +20,7 @@ void CapeChange::setCape(QString& cape) { m_reply = shared_qobject_ptr<QNetworkReply>(rep); connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); } @@ -34,7 +34,7 @@ void CapeChange::clearCape() { m_reply = shared_qobject_ptr<QNetworkReply>(rep); connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); } diff --git a/launcher/minecraft/services/SkinDelete.cpp b/launcher/minecraft/services/SkinDelete.cpp index cce8364e18..cd383cfbfb 100644 --- a/launcher/minecraft/services/SkinDelete.cpp +++ b/launcher/minecraft/services/SkinDelete.cpp @@ -19,7 +19,7 @@ void SkinDelete::executeTask() setStatus(tr("Deleting skin")); connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); } diff --git a/launcher/minecraft/services/SkinUpload.cpp b/launcher/minecraft/services/SkinUpload.cpp index 7c2e833770..4aacedc34d 100644 --- a/launcher/minecraft/services/SkinUpload.cpp +++ b/launcher/minecraft/services/SkinUpload.cpp @@ -44,7 +44,7 @@ void SkinUpload::executeTask() setStatus(tr("Uploading skin")); connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 0f4f6dbbd9..2716856c55 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -18,6 +18,7 @@ #include "ATLPackInstallTask.h" #include <QtConcurrent/QtConcurrent> +#include <QRegularExpression> #include <quazip.h> @@ -475,7 +476,11 @@ void PackInstallTask::extractConfigs() return; } - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/minecraft"); + QString extractPath = extractDir.absolutePath() + "/minecraft"; + QString archivePathCopy = archivePath; + m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), [archivePathCopy, extractPath]() { + return MMCZip::extractDir(archivePathCopy, extractPath); + }); connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, [&]() { downloadMods(); @@ -623,7 +628,12 @@ void PackInstallTask::onModsDownloaded() { jobPtr.reset(); if(!modsToExtract.empty() || !modsToDecomp.empty() || !modsToCopy.empty()) { - m_modExtractFuture = QtConcurrent::run(QThreadPool::globalInstance(), this, &PackInstallTask::extractMods, modsToExtract, modsToDecomp, modsToCopy); + auto modsToExtractCopy = modsToExtract; + auto modsToDecompCopy = modsToDecomp; + auto modsToCopyCopy = modsToCopy; + m_modExtractFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, modsToExtractCopy, modsToDecompCopy, modsToCopyCopy]() { + return this->extractMods(modsToExtractCopy, modsToDecompCopy, modsToCopyCopy); + }); connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onModsExtracted); connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]() { @@ -675,7 +685,7 @@ bool PackInstallTask::extractMods( QString folderToExtract = ""; if(mod.type == ModType::Extract) { folderToExtract = mod.extractFolder; - folderToExtract.remove(QRegExp("^/")); + folderToExtract.remove(QRegularExpression("^/")); } qDebug() << "Extracting " + mod.file + " to " + extractToDir; diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp index 387529dbfe..3916665900 100644 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -98,7 +98,12 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack & pack, QJsonArray & arr) file.addonId = pack.addonId; file.fileId = Json::requireInteger(version, "id"); - auto versionArray = Json::requireArray(version, "gameVersion"); + QJsonArray versionArray; + if(version.contains("gameVersions")) { + versionArray = Json::requireArray(version, "gameVersions"); + } else { + versionArray = Json::requireArray(version, "gameVersion"); + } if(versionArray.size() < 1) { continue; } @@ -106,7 +111,7 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack & pack, QJsonArray & arr) // pick the latest version supported file.mcVersion = versionArray[0].toString(); file.version = Json::requireString(version, "displayName"); - file.downloadUrl = Json::requireString(version, "downloadUrl"); + file.downloadUrl = Json::ensureString(version, "downloadUrl", ""); unsortedVersions.append(file); } diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h index 02f39f0ea9..f24cf5b0b0 100644 --- a/launcher/modplatform/flame/PackManifest.h +++ b/launcher/modplatform/flame/PackManifest.h @@ -20,7 +20,7 @@ struct File bool resolved = false; QString fileName; QUrl url; - QString targetFolder = QLatin1Literal("mods"); + QString targetFolder = QLatin1String("mods"); enum class Type { Unknown, diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp index 961fe868cc..5bc01ed265 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp @@ -103,7 +103,7 @@ bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, Modpac if(!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol)) { - auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:3d!").arg(errorMsg, errorLine, errorCol); + auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:%3!").arg(errorMsg).arg(errorLine).arg(errorCol); qWarning() << fullErrMsg; data.clear(); return false; diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 1d300192db..9bd55271a4 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -88,7 +88,11 @@ void PackInstallTask::unzip() return; } - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip"); + QString extractPath = extractDir.absolutePath() + "/unzip"; + QString archivePathCopy = archivePath; + m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), [archivePathCopy, extractPath]() { + return MMCZip::extractDir(archivePathCopy, extractPath); + }); connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished); connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled); m_extractFutureWatcher.setFuture(m_extractFuture); @@ -137,7 +141,11 @@ void PackInstallTask::install() QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods"); if(packJson.exists()) { - packJson.open(QIODevice::ReadOnly | QIODevice::Text); + if (!packJson.open(QIODevice::ReadOnly | QIODevice::Text)) + { + emitFailed(tr("Failed to open pack.json!")); + return; + } QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll()); packJson.close(); diff --git a/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp b/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp index 501e6003ef..09e0e49764 100644 --- a/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp +++ b/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp @@ -10,7 +10,8 @@ void PrivatePackManager::load() { try { - currentPacks = QString::fromUtf8(FS::read(m_filename)).split('\n', QString::SkipEmptyParts).toSet(); + auto parts = QString::fromUtf8(FS::read(m_filename)).split('\n', Qt::SkipEmptyParts); + currentPacks = QSet<QString>(parts.begin(), parts.end()); dirty = false; } catch(...) @@ -28,7 +29,7 @@ void PrivatePackManager::save() const } try { - QStringList list = currentPacks.toList(); + QStringList list = currentPacks.values(); FS::write(m_filename, list.join('\n').toUtf8()); dirty = false; } diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index e36ed61a65..57e3692d34 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -108,7 +108,7 @@ void Download::startImpl() m_reply.reset(rep); connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors); connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead); } diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 8734e0bfbd..7d5c98ed3c 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -96,7 +96,8 @@ MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QS if (file_last_changed != entry->local_changed_timestamp) { QFile input(real_path); - input.open(QIODevice::ReadOnly); + if (!input.open(QIODevice::ReadOnly)) + return staleEntry(base, resource_path); QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5) .toHex() .constData(); diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index 9bad89edd0..d1c0d371f0 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -182,10 +182,10 @@ bool NetJob::abort() { bool fullyAborted = true; // fail all waiting - m_failed.unite(m_todo.toSet()); + m_failed.unite(QSet<int>(m_todo.begin(), m_todo.end())); m_todo.clear(); // abort active - auto toKill = m_doing.toList(); + auto toKill = m_doing.values(); for(auto index: toKill) { auto part = downloads[index]; diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 4b69b68a1b..a18f713db1 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -47,7 +47,7 @@ void PasteUpload::executeTask() m_reply = std::shared_ptr<QNetworkReply>(rep); setStatus(tr("Uploading to paste.ee")); connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); } diff --git a/launcher/news/NewsChecker.cpp b/launcher/news/NewsChecker.cpp index 4f4359b893..61a208ee16 100644 --- a/launcher/news/NewsChecker.cpp +++ b/launcher/news/NewsChecker.cpp @@ -61,7 +61,7 @@ void NewsChecker::rssDownloadFinished() // Parse the XML. if (!doc.setContent(newsData, false, &errorMsg, &errorLine, &errorCol)) { - QString fullErrorMsg = QString("Error parsing RSS feed XML. %s at %d:%d.").arg(errorMsg, errorLine, errorCol); + QString fullErrorMsg = QString("Error parsing RSS feed XML. %1 at %2:%3.").arg(errorMsg).arg(errorLine).arg(errorCol); fail(fullErrorMsg); newsData.clear(); return; diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp index d5de302a03..28b9421ffa 100644 --- a/launcher/screenshots/ImgurAlbumCreation.cpp +++ b/launcher/screenshots/ImgurAlbumCreation.cpp @@ -38,7 +38,7 @@ void ImgurAlbumCreation::startImpl() m_reply.reset(rep); connect(rep, &QNetworkReply::uploadProgress, this, &ImgurAlbumCreation::downloadProgress); connect(rep, &QNetworkReply::finished, this, &ImgurAlbumCreation::downloadFinished); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); } void ImgurAlbumCreation::downloadError(QNetworkReply::NetworkError error) { diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index 76a84947bf..63b53dc672 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -52,7 +52,7 @@ void ImgurUpload::startImpl() m_reply.reset(rep); connect(rep, &QNetworkReply::uploadProgress, this, &ImgurUpload::downloadProgress); connect(rep, &QNetworkReply::finished, this, &ImgurUpload::downloadFinished); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), + connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); } void ImgurUpload::downloadError(QNetworkReply::NetworkError error) diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp index 6a3c801d6f..be37906794 100644 --- a/launcher/settings/INIFile.cpp +++ b/launcher/settings/INIFile.cpp @@ -29,10 +29,10 @@ INIFile::INIFile() QString INIFile::unescape(QString orig) { QString out; - QChar prev = 0; + QChar prev = QChar(); for(auto c: orig) { - if(prev == '\\') + if(prev == QLatin1Char('\\')) { if(c == 'n') out += '\n'; @@ -42,7 +42,7 @@ QString INIFile::unescape(QString orig) out += '#'; else out += c; - prev = 0; + prev = QChar(); } else { @@ -52,7 +52,7 @@ QString INIFile::unescape(QString orig) continue; } out += c; - prev = 0; + prev = QChar(); } } return out; @@ -117,7 +117,7 @@ bool INIFile::loadFile(QString fileName) bool INIFile::loadFile(QByteArray file) { QTextStream in(file); - in.setCodec("UTF-8"); + // Qt6 uses UTF-8 by default QStringList lines = in.readAll().split('\n'); for (int i = 0; i < lines.count(); i++) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 5e01031526..f0b0c0f550 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -17,7 +17,7 @@ #include "Application.h" -const static QLatin1Literal defaultLangCode("en_US"); +const static QLatin1String defaultLangCode("en_US"); enum class FileType { @@ -314,7 +314,7 @@ void TranslationsModel::reloadLocalFiles() { return; } - beginInsertRows(QModelIndex(), 0, d->m_languages.size() + languages.size() - 1); + beginResetModel(); for(auto & language: languages) { d->m_languages.append(language); @@ -322,7 +322,7 @@ void TranslationsModel::reloadLocalFiles() std::sort(d->m_languages.begin(), d->m_languages.end(), [](const Language& a, const Language& b) { return a.key.compare(b.key) < 0; }); - endInsertRows(); + endResetModel(); } namespace { @@ -359,7 +359,7 @@ QVariant TranslationsModel::data(const QModelIndex& index, int role) const case Column::Completeness: { QString text; - text.sprintf("%3.1f %%", lang.percentTranslated()); + text = QString::asprintf("%3.1f %%", lang.percentTranslated()); return text; } } @@ -431,7 +431,7 @@ Language * TranslationsModel::findLanguage(const QString& key) } else { - return found; + return &(*found); } } @@ -556,7 +556,7 @@ QModelIndex TranslationsModel::selectedIndex() if(found) { // QVector iterator freely converts to pointer to contained type - return index(found - d->m_languages.begin(), 0, QModelIndex()); + return index(found - d->m_languages.data(), 0, QModelIndex()); } return QModelIndex(); } diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index f831622d02..3ce7535a7d 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -642,6 +642,12 @@ public: retranslateUi(MainWindow); QMetaObject::connectSlotsByName(MainWindow); + + // Explicit connections for actions that connectSlotsByName can't auto-connect in Qt6 + auto mainWin = qobject_cast<class MainWindow*>(MainWindow); + QObject::connect(actionREDDIT.operator->(), &QAction::triggered, mainWin, &MainWindow::on_actionREDDIT_triggered); + QObject::connect(actionDISCORD.operator->(), &QAction::triggered, mainWin, &MainWindow::on_actionDISCORD_triggered); + QObject::connect(actionReportBug.operator->(), &QAction::triggered, mainWin, &MainWindow::on_actionReportBug_triggered); } // setupUi void retranslateUi(QMainWindow *MainWindow) @@ -1240,7 +1246,7 @@ void MainWindow::updateNotAvailable() QList<int> stringToIntList(const QString &string) { - QStringList split = string.split(',', QString::SkipEmptyParts); + QStringList split = string.split(',', Qt::SkipEmptyParts); QList<int> out; for (int i = 0; i < split.size(); ++i) { diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index f5fa5735f7..38356514d3 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -31,7 +31,6 @@ QString getCreditsHtml(QStringList patrons) QString patronsHeading = QObject::tr("Patrons", "About Credits"); QString output; QTextStream stream(&output); - stream.setCodec(QTextCodec::codecForName("UTF-8")); stream << "<center>\n"; // TODO: possibly retrieve from git history at build time? stream << "<h2> MeshMC Developers </h2>\n"; @@ -55,7 +54,10 @@ QString getLicenseHtml() { HoeDown hoedown; QFile dataFile(":/documents/COPYING.md"); - dataFile.open(QIODevice::ReadOnly); + if (!dataFile.open(QIODevice::ReadOnly)) + { + return QString(); + } QString output = hoedown.process(dataFile.readAll()); return output; } @@ -94,15 +96,6 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia else ui->channelLabel->setVisible(false); - ui->redistributionText->setHtml(tr( -"<p>We keep MeshMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.</p>\n" -"<p>Part of the reason for using the Apache license is we don't want people using the "MeshMC" name when redistributing the project. " -"This means people must take the time to go through the source code and remove all references to "MeshMC", including but not limited to the project " -"icon and the title of windows, (no <b>MeshMC-fork</b> in the title).</p>\n" -"<p>The Apache license covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. " -"However, it should be abundantly clear that the project is a fork <b>without</b> implying that you have our blessing.</p>" - )); - QString urlText("<html><head/><body><p><a href=\"%1\">%1</a></p></body></html>"); ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT)); diff --git a/launcher/ui/dialogs/AboutDialog.ui b/launcher/ui/dialogs/AboutDialog.ui index 06f0b26ddf..341137f6d3 100644 --- a/launcher/ui/dialogs/AboutDialog.ui +++ b/launcher/ui/dialogs/AboutDialog.ui @@ -251,20 +251,6 @@ </item> </layout> </widget> - <widget class="QWidget" name="forkingTab"> - <attribute name="title"> - <string>Forking/Redistribution</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <widget class="QTextEdit" name="redistributionText"> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - </layout> - </widget> </widget> </item> <item> @@ -307,7 +293,6 @@ <tabstop>tabWidget</tabstop> <tabstop>creditsText</tabstop> <tabstop>licenseText</tabstop> - <tabstop>redistributionText</tabstop> <tabstop>aboutQt</tabstop> <tabstop>closeButton</tabstop> </tabstops> diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index e5113981cd..a79b0a11cd 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -39,8 +39,8 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); ui->instNameTextBox->setText(original->name()); ui->instNameTextBox->setFocus(); - auto groups = APPLICATION->instances()->getGroups().toSet(); - auto groupList = QStringList(groups.toList()); + auto groupList = APPLICATION->instances()->getGroups(); + groupList.removeDuplicates(); groupList.sort(Qt::CaseInsensitive); groupList.removeOne(""); groupList.push_front(""); diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index 1a16487517..eebe310319 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -97,7 +97,7 @@ public: flags |= Qt::ItemIsUserCheckable; if (sourceIndex.model()->hasChildren(sourceIndex)) { - flags |= Qt::ItemIsTristate; + flags |= Qt::ItemIsAutoTristate; } } @@ -190,7 +190,7 @@ public: QStack<QModelIndex> todo; while (1) { - auto node = doing.child(row, 0); + auto node = fsm->index(row, 0, doing); if (!node.isValid()) { if (!todo.size()) @@ -239,7 +239,7 @@ public: QStack<QModelIndex> todo; while (1) { - auto node = doing.child(row, 0); + auto node = doing.model()->index(row, 0, doing); if (!node.isValid()) { if (!todo.size()) @@ -403,7 +403,7 @@ bool ExportInstanceDialog::doExport() auto & blocked = proxyModel->blockedPaths(); using std::placeholders::_1; - if (!JlCompress::compressDir(output, m_instance->instanceRoot(), name, std::bind(&SeparatorPrefixTree<'/'>::covers, blocked, _1))) + if (!MMCZip::compressDir(output, m_instance->instanceRoot(), std::bind(&SeparatorPrefixTree<'/'>::covers, blocked, _1))) { QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); return false; @@ -434,7 +434,7 @@ void ExportInstanceDialog::rowsInserted(QModelIndex parent, int top, int bottom) //WARNING: possible off-by-one? for(int i = top; i < bottom; i++) { - auto node = parent.child(i, 0); + auto node = parent.model()->index(i, 0, parent); if(proxyModel->shouldExpand(node)) { auto expNode = node.parent(); @@ -462,7 +462,7 @@ void ExportInstanceDialog::loadPackIgnore() } auto data = ignoreFile.readAll(); auto string = QString::fromUtf8(data); - proxyModel->setBlockedPaths(string.split('\n', QString::SkipEmptyParts)); + proxyModel->setBlockedPaths(string.split('\n', Qt::SkipEmptyParts)); } void ExportInstanceDialog::savePackIgnore() diff --git a/launcher/ui/dialogs/LoginDialog.cpp b/launcher/ui/dialogs/LoginDialog.cpp deleted file mode 100644 index 194315a7c3..0000000000 --- a/launcher/ui/dialogs/LoginDialog.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* Copyright 2013-2021 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 "LoginDialog.h" -#include "ui_LoginDialog.h" - -#include "minecraft/auth/AccountTask.h" - -#include <QtWidgets/QPushButton> - -LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LoginDialog) -{ - ui->setupUi(this); - ui->progressBar->setVisible(false); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - - connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); -} - -LoginDialog::~LoginDialog() -{ - delete ui; -} - -// Stage 1: User interaction -void LoginDialog::accept() -{ - setUserInputsEnabled(false); - ui->progressBar->setVisible(true); - - // Setup the login task and start it - m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text()); - m_loginTask = m_account->login(ui->passTextBox->text()); - connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed); - connect(m_loginTask.get(), &Task::succeeded, this, &LoginDialog::onTaskSucceeded); - connect(m_loginTask.get(), &Task::status, this, &LoginDialog::onTaskStatus); - connect(m_loginTask.get(), &Task::progress, this, &LoginDialog::onTaskProgress); - m_loginTask->start(); -} - -void LoginDialog::setUserInputsEnabled(bool enable) -{ - ui->userTextBox->setEnabled(enable); - ui->passTextBox->setEnabled(enable); - ui->buttonBox->setEnabled(enable); -} - -// Enable the OK button only when both textboxes contain something. -void LoginDialog::on_userTextBox_textEdited(const QString &newText) -{ - ui->buttonBox->button(QDialogButtonBox::Ok) - ->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty()); -} -void LoginDialog::on_passTextBox_textEdited(const QString &newText) -{ - ui->buttonBox->button(QDialogButtonBox::Ok) - ->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty()); -} - -void LoginDialog::onTaskFailed(const QString &reason) -{ - // Set message - auto lines = reason.split('\n'); - QString processed; - for(auto line: lines) { - if(line.size()) { - processed += "<font color='red'>" + line + "</font><br />"; - } - else { - processed += "<br />"; - } - } - ui->label->setText(processed); - - // Re-enable user-interaction - setUserInputsEnabled(true); - ui->progressBar->setVisible(false); -} - -void LoginDialog::onTaskSucceeded() -{ - QDialog::accept(); -} - -void LoginDialog::onTaskStatus(const QString &status) -{ - ui->label->setText(status); -} - -void LoginDialog::onTaskProgress(qint64 current, qint64 total) -{ - ui->progressBar->setMaximum(total); - ui->progressBar->setValue(current); -} - -// Public interface -MinecraftAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg) -{ - LoginDialog dlg(parent); - dlg.ui->label->setText(msg); - if (dlg.exec() == QDialog::Accepted) - { - return dlg.m_account; - } - return 0; -} diff --git a/launcher/ui/dialogs/LoginDialog.h b/launcher/ui/dialogs/LoginDialog.h deleted file mode 100644 index f8101ff572..0000000000 --- a/launcher/ui/dialogs/LoginDialog.h +++ /dev/null @@ -1,59 +0,0 @@ -/* Copyright 2013-2021 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 <QtWidgets/QDialog> -#include <QtCore/QEventLoop> - -#include "minecraft/auth/MinecraftAccount.h" -#include "tasks/Task.h" - -namespace Ui -{ -class LoginDialog; -} - -class LoginDialog : public QDialog -{ - Q_OBJECT - -public: - ~LoginDialog(); - - static MinecraftAccountPtr newAccount(QWidget *parent, QString message); - -private: - explicit LoginDialog(QWidget *parent = 0); - - void setUserInputsEnabled(bool enable); - -protected -slots: - void accept(); - - void onTaskFailed(const QString &reason); - void onTaskSucceeded(); - void onTaskStatus(const QString &status); - void onTaskProgress(qint64 current, qint64 total); - - void on_userTextBox_textEdited(const QString &newText); - void on_passTextBox_textEdited(const QString &newText); - -private: - Ui::LoginDialog *ui; - MinecraftAccountPtr m_account; - Task::Ptr m_loginTask; -}; diff --git a/launcher/ui/dialogs/LoginDialog.ui b/launcher/ui/dialogs/LoginDialog.ui deleted file mode 100644 index 8fa4a45d25..0000000000 --- a/launcher/ui/dialogs/LoginDialog.ui +++ /dev/null @@ -1,77 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>LoginDialog</class> - <widget class="QDialog" name="LoginDialog"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>421</width> - <height>198</height> - </rect> - </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="windowTitle"> - <string>Add Account</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QLabel" name="label"> - <property name="text"> - <string notr="true">Message label placeholder.</string> - </property> - <property name="textFormat"> - <enum>Qt::RichText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="userTextBox"> - <property name="placeholderText"> - <string>Email</string> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="passTextBox"> - <property name="echoMode"> - <enum>QLineEdit::Password</enum> - </property> - <property name="placeholderText"> - <string>Password</string> - </property> - </widget> - </item> - <item> - <widget class="QProgressBar" name="progressBar"> - <property name="value"> - <number>24</number> - </property> - <property name="textVisible"> - <bool>false</bool> - </property> - </widget> - </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - </item> - </layout> - </widget> - <resources/> - <connections/> -</ui> diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index f46aa3b9a2..71c243b351 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -25,7 +25,6 @@ MSALoginDialog::MSALoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MS { ui->setupUi(this); ui->progressBar->setVisible(false); - // ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); @@ -34,6 +33,8 @@ MSALoginDialog::MSALoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MS int MSALoginDialog::exec() { setUserInputsEnabled(false); ui->progressBar->setVisible(true); + ui->progressBar->setMaximum(0); // Indeterminate progress + ui->label->setText(tr("Opening your browser for Microsoft login...")); // Setup the login task and start it m_account = MinecraftAccount::createBlankMSA(); @@ -42,9 +43,7 @@ int MSALoginDialog::exec() { connect(m_loginTask.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded); connect(m_loginTask.get(), &Task::status, this, &MSALoginDialog::onTaskStatus); connect(m_loginTask.get(), &Task::progress, this, &MSALoginDialog::onTaskProgress); - connect(m_loginTask.get(), &AccountTask::showVerificationUriAndCode, this, &MSALoginDialog::showVerificationUriAndCode); - connect(m_loginTask.get(), &AccountTask::hideVerificationUriAndCode, this, &MSALoginDialog::hideVerificationUriAndCode); - connect(&m_externalLoginTimer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick); + connect(m_loginTask.get(), &AccountTask::authorizeWithBrowser, this, &MSALoginDialog::onAuthorizeWithBrowser); m_loginTask->start(); return QDialog::exec(); @@ -56,35 +55,13 @@ MSALoginDialog::~MSALoginDialog() delete ui; } -void MSALoginDialog::externalLoginTick() { - m_externalLoginElapsed++; - ui->progressBar->setValue(m_externalLoginElapsed); - ui->progressBar->repaint(); - - if(m_externalLoginElapsed >= m_externalLoginTimeout) { - m_externalLoginTimer.stop(); - } -} - - -void MSALoginDialog::showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn) { - m_externalLoginElapsed = 0; - m_externalLoginTimeout = expiresIn; - - m_externalLoginTimer.setInterval(1000); - m_externalLoginTimer.setSingleShot(false); - m_externalLoginTimer.start(); - - ui->progressBar->setMaximum(expiresIn); - ui->progressBar->setValue(m_externalLoginElapsed); - - QString urlString = uri.toString(); - QString linkString = QString("<a href=\"%1\">%2</a>").arg(urlString, urlString); - ui->label->setText(tr("<p>Please open up %1 in a browser and put in the code <b>%2</b> to proceed with login.</p>").arg(linkString, code)); -} - -void MSALoginDialog::hideVerificationUriAndCode() { - m_externalLoginTimer.stop(); +void MSALoginDialog::onAuthorizeWithBrowser(const QUrl& url) { + QString urlString = url.toString(); + QString linkString = QString("<a href=\"%1\">%2</a>").arg(urlString, tr("here")); + ui->label->setText( + tr("<p>A browser window will open for Microsoft login.</p>" + "<p>If it doesn't open automatically, click %1.</p>").arg(linkString) + ); } void MSALoginDialog::setUserInputsEnabled(bool enable) @@ -137,5 +114,5 @@ MinecraftAccountPtr MSALoginDialog::newAccount(QWidget *parent, QString msg) { return dlg.m_account; } - return 0; + return nullptr; } diff --git a/launcher/ui/dialogs/MSALoginDialog.h b/launcher/ui/dialogs/MSALoginDialog.h index 4cf146ab3f..e4524cad6f 100644 --- a/launcher/ui/dialogs/MSALoginDialog.h +++ b/launcher/ui/dialogs/MSALoginDialog.h @@ -17,7 +17,6 @@ #include <QtWidgets/QDialog> #include <QtCore/QEventLoop> -#include <QTimer> #include "minecraft/auth/MinecraftAccount.h" @@ -47,17 +46,11 @@ slots: void onTaskSucceeded(); void onTaskStatus(const QString &status); void onTaskProgress(qint64 current, qint64 total); - void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn); - void hideVerificationUriAndCode(); - - void externalLoginTick(); + void onAuthorizeWithBrowser(const QUrl &url); private: Ui::MSALoginDialog *ui; MinecraftAccountPtr m_account; shared_qobject_ptr<AccountTask> m_loginTask; - QTimer m_externalLoginTimer; - int m_externalLoginElapsed = 0; - int m_externalLoginTimeout = 0; }; diff --git a/launcher/ui/dialogs/NewComponentDialog.cpp b/launcher/ui/dialogs/NewComponentDialog.cpp index bcb68cdaf7..e7995bcc4f 100644 --- a/launcher/ui/dialogs/NewComponentDialog.cpp +++ b/launcher/ui/dialogs/NewComponentDialog.cpp @@ -46,7 +46,8 @@ NewComponentDialog::NewComponentDialog(const QString & initialName, const QStrin connect(ui->nameTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState); connect(ui->uidTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState); - auto groups = APPLICATION->instances()->getGroups().toSet(); + auto groups = APPLICATION->instances()->getGroups(); + groups.removeDuplicates(); ui->nameTextBox->setFocus(); originalPlaceholderText = ui->uidTextBox->placeholderText(); diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index b402839cf3..d4cc9b8c65 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -53,8 +53,8 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString InstIconKey = "default"; ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); - auto groups = APPLICATION->instances()->getGroups().toSet(); - auto groupList = QStringList(groups.toList()); + auto groupList = APPLICATION->instances()->getGroups(); + groupList.removeDuplicates(); groupList.sort(Qt::CaseInsensitive); groupList.removeOne(""); groupList.push_front(initialGroup); diff --git a/launcher/ui/dialogs/ProfileSetupDialog.cpp b/launcher/ui/dialogs/ProfileSetupDialog.cpp index 76b6af4981..a534744546 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.cpp +++ b/launcher/ui/dialogs/ProfileSetupDialog.cpp @@ -18,7 +18,7 @@ #include <QPushButton> #include <QAction> -#include <QRegExpValidator> +#include <QRegularExpressionValidator> #include <QJsonDocument> #include <QDebug> @@ -39,9 +39,9 @@ ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidg yellowIcon = APPLICATION->getThemedIcon("status-yellow"); badIcon = APPLICATION->getThemedIcon("status-bad"); - QRegExp permittedNames("[a-zA-Z0-9_]{3,16}"); + QRegularExpression permittedNames("[a-zA-Z0-9_]{3,16}"); auto nameEdit = ui->nameEdit; - nameEdit->setValidator(new QRegExpValidator(permittedNames)); + nameEdit->setValidator(new QRegularExpressionValidator(permittedNames)); nameEdit->setClearButtonEnabled(true); validityAction = nameEdit->addAction(yellowIcon, QLineEdit::LeadingPosition); connect(nameEdit, &QLineEdit::textEdited, this, &ProfileSetupDialog::nameEdited); diff --git a/launcher/ui/dialogs/SkinUploadDialog.cpp b/launcher/ui/dialogs/SkinUploadDialog.cpp index 6a5a324f50..ef2ae432e5 100644 --- a/launcher/ui/dialogs/SkinUploadDialog.cpp +++ b/launcher/ui/dialogs/SkinUploadDialog.cpp @@ -1,6 +1,7 @@ #include <QFileInfo> #include <QFileDialog> #include <QPainter> +#include <QRegularExpression> #include <FileSystem.h> @@ -22,10 +23,10 @@ void SkinUploadDialog::on_buttonBox_accepted() { QString fileName; QString input = ui->skinPathTextBox->text(); - QRegExp urlPrefixMatcher("^([a-z]+)://.+$"); + QRegularExpression urlPrefixMatcher("^([a-z]+)://.+$"); bool isLocalFile = false; // it has an URL prefix -> it is an URL - if(urlPrefixMatcher.exactMatch(input)) + if(urlPrefixMatcher.match(input).hasMatch()) { QUrl fileURL = input; if(fileURL.isValid()) diff --git a/launcher/ui/dialogs/UpdateDialog.cpp b/launcher/ui/dialogs/UpdateDialog.cpp index c352e0935b..502bba0652 100644 --- a/launcher/ui/dialogs/UpdateDialog.cpp +++ b/launcher/ui/dialogs/UpdateDialog.cpp @@ -1,6 +1,7 @@ #include "UpdateDialog.h" #include "ui_UpdateDialog.h" #include <QDebug> +#include <QRegularExpression> #include "Application.h" #include <settings/SettingsObject.h> #include <Json.h> @@ -58,7 +59,7 @@ QString reprocessMarkdown(QByteArray markdown) QString output = hoedown.process(markdown); // HACK: easier than customizing hoedown - output.replace(QRegExp("GH-([0-9]+)"), "<a href=\"https://github.com/Project-Tick/MeshMC/issues/\\1\">GH-\\1</a>"); + output.replace(QRegularExpression("GH-([0-9]+)"), "<a href=\"https://github.com/Project-Tick/MeshMC/issues/\\1\">GH-\\1</a>"); qDebug() << output; return output; } diff --git a/launcher/ui/instanceview/InstanceDelegate.cpp b/launcher/ui/instanceview/InstanceDelegate.cpp index 3c4ca63f23..063ce70504 100644 --- a/launcher/ui/instanceview/InstanceDelegate.cpp +++ b/launcher/ui/instanceview/InstanceDelegate.cpp @@ -61,7 +61,7 @@ void drawSelectionRect(QPainter *painter, const QStyleOptionViewItem &option, painter->fillRect(rect, option.palette.brush(QPalette::Highlight)); else { - QColor backgroundColor = option.palette.color(QPalette::Background); + QColor backgroundColor = option.palette.color(QPalette::Window); backgroundColor.setAlpha(160); painter->fillRect(rect, QBrush(backgroundColor)); } diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index 25aec1abe9..89d4e7e544 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -794,7 +794,7 @@ QPair<VisualGroup *, VisualGroup::HitResults> InstanceView::rowDropPos(const QPo { VisualGroup::HitResults hitresult; auto group = categoryAt(pos + offset(), hitresult); - return qMakePair<VisualGroup*, int>(group, hitresult); + return qMakePair(group, hitresult); } QPoint InstanceView::offset() const diff --git a/launcher/ui/instanceview/InstanceView.h b/launcher/ui/instanceview/InstanceView.h index 406362e628..f3ba26b778 100644 --- a/launcher/ui/instanceview/InstanceView.h +++ b/launcher/ui/instanceview/InstanceView.h @@ -40,6 +40,12 @@ public: InstanceView(QWidget *parent = 0); ~InstanceView(); + QStyleOptionViewItem viewOptions() const { + QStyleOptionViewItem option; + initViewItemOption(&option); + return option; + } + void setModel(QAbstractItemModel *model) override; using visibilityFunction = std::function<bool(const QString &)>; diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index c0386ea6e7..4ab57319b1 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -24,7 +24,6 @@ #include "net/NetJob.h" #include "ui/dialogs/ProgressDialog.h" -#include "ui/dialogs/LoginDialog.h" #include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/SkinUploadDialog.h" @@ -43,7 +42,7 @@ AccountListPage::AccountListPage(QWidget *parent) ui->setupUi(this); ui->listView->setEmptyString(tr( "Welcome!\n" - "If you're new here, you can click the \"Add\" button to add your Mojang or Minecraft account." + "If you're new here, you can click the \"Add Microsoft\" button to add your Microsoft account." )); ui->listView->setEmptyMode(VersionListView::String); ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); @@ -51,12 +50,10 @@ AccountListPage::AccountListPage(QWidget *parent) m_accounts = APPLICATION->accounts(); ui->listView->setModel(m_accounts.get()); + // Expand the account column ui->listView->header()->setSectionResizeMode(0, QHeaderView::Stretch); ui->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch); ui->listView->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); - ui->listView->setSelectionMode(QAbstractItemView::SingleSelection); - - // Expand the account column QItemSelectionModel *selectionModel = ui->listView->selectionModel(); @@ -109,22 +106,6 @@ void AccountListPage::listChanged() updateButtonStates(); } -void AccountListPage::on_actionAddMojang_triggered() -{ - MinecraftAccountPtr account = LoginDialog::newAccount( - this, - tr("Please enter your Mojang account email and password to add your account.") - ); - - if (account) - { - m_accounts->addAccount(account); - if (m_accounts->count() == 1) { - m_accounts->setDefaultAccount(account); - } - } -} - void AccountListPage::on_actionAddMicrosoft_triggered() { if(BuildConfig.BUILD_PLATFORM == "osx64") { @@ -141,7 +122,7 @@ void AccountListPage::on_actionAddMicrosoft_triggered() } MinecraftAccountPtr account = MSALoginDialog::newAccount( this, - tr("Please enter your Mojang account email and password to add your account.") + tr("Log in with your Microsoft account to add it.") ); if (account) diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h index 1c65e70814..83105604a3 100644 --- a/launcher/ui/pages/global/AccountListPage.h +++ b/launcher/ui/pages/global/AccountListPage.h @@ -60,7 +60,6 @@ public: } public slots: - void on_actionAddMojang_triggered(); void on_actionAddMicrosoft_triggered(); void on_actionRemove_triggered(); void on_actionRefresh_triggered(); diff --git a/launcher/ui/pages/global/AccountListPage.ui b/launcher/ui/pages/global/AccountListPage.ui index 29738c0237..96d0dc7518 100644 --- a/launcher/ui/pages/global/AccountListPage.ui +++ b/launcher/ui/pages/global/AccountListPage.ui @@ -53,7 +53,6 @@ <bool>false</bool> </attribute> <addaction name="actionAddMicrosoft"/> - <addaction name="actionAddMojang"/> <addaction name="actionRefresh"/> <addaction name="actionRemove"/> <addaction name="actionSetDefault"/> @@ -62,11 +61,6 @@ <addaction name="actionUploadSkin"/> <addaction name="actionDeleteSkin"/> </widget> - <action name="actionAddMojang"> - <property name="text"> - <string>Add Mojang</string> - </property> - </action> <action name="actionRemove"> <property name="text"> <string>Remove</string> diff --git a/launcher/ui/pages/global/ProxyPage.cpp b/launcher/ui/pages/global/ProxyPage.cpp index 5bc8199eea..75323d0437 100644 --- a/launcher/ui/pages/global/ProxyPage.cpp +++ b/launcher/ui/pages/global/ProxyPage.cpp @@ -29,7 +29,7 @@ ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage) loadSettings(); updateCheckboxStuff(); - connect(ui->proxyGroup, SIGNAL(buttonClicked(int)), SLOT(proxyChanged(int))); + connect(ui->proxyGroup, &QButtonGroup::idClicked, this, &ProxyPage::proxyChanged); } ProxyPage::~ProxyPage() diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index b66c6cc7b5..55b5fbbca0 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -27,7 +27,7 @@ public: { case Qt::FontRole: return m_font; - case Qt::TextColorRole: + case Qt::ForegroundRole: { MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt(); return m_colors->getFront(level); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index e63b1434eb..4ceba14556 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -60,14 +60,14 @@ protected: return false; } const auto &mod = model->at(source_row); - if(mod.name().contains(filterRegExp())) { + if(mod.name().contains(filterRegularExpression())) { return true; } - if(mod.description().contains(filterRegExp())) { + if(mod.description().contains(filterRegularExpression())) { return true; } for(auto & author: mod.authors()) { - if (author.contains(filterRegExp())) { + if (author.contains(filterRegularExpression())) { return true; } } diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index f568ef0d99..9a8389754e 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -9,6 +9,7 @@ #include <QFileSystemModel> #include <QStyledItemDelegate> #include <QLineEdit> +#include <QRegularExpression> #include <QEvent> #include <QPainter> #include <QClipboard> @@ -118,7 +119,7 @@ public: if (role == Qt::DisplayRole || role == Qt::EditRole) { QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); - return result.toString().remove(QRegExp("\\.png$")); + return result.toString().remove(QRegularExpression("\\.png$")); } if (role == Qt::DecorationRole) { @@ -234,7 +235,7 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent) ui->listView->setViewMode(QListView::IconMode); ui->listView->setResizeMode(QListView::Adjust); ui->listView->installEventFilter(this); - ui->listView->setEditTriggers(0); + ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->listView->setItemDelegate(new CenteredEditingDelegate(this)); ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::ShowContextMenu); diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 8116d2bfaf..8c525a48e2 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -252,7 +252,7 @@ public: return false; } beginMoveRows(QModelIndex(), row, row, QModelIndex(), row - 1); - m_servers.swap(row-1, row); + m_servers.swapItemsAt(row-1, row); endMoveRows(); scheduleSave(); return true; @@ -270,7 +270,7 @@ public: return false; } beginMoveRows(QModelIndex(), row, row, QModelIndex(), row + 2); - m_servers.swap(row+1, row); + m_servers.swapItemsAt(row+1, row); endMoveRows(); scheduleSave(); return true; diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index 1138a2987e..f94cc19010 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -106,13 +106,18 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) ui->packDescription->setHtml(text + current.description); + if(isOpened) + { + dialog->setSuggestedPack(current.name); + } + if (current.versionsLoaded == false) { qDebug() << "Loading flame modpack versions"; NetJob *netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network()); std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>(); int addonId = current.addonId; - netJob->addNetAction(Net::Download::makeByteArray(QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId), response.get())); + netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response.get())); QObject::connect(netJob, &NetJob::succeeded, this, [this, response] { @@ -123,7 +128,12 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) qWarning() << *response; return; } - QJsonArray arr = doc.array(); + QJsonArray arr; + if(doc.isObject() && doc.object().contains("data")) { + arr = doc.object().value("data").toArray(); + } else { + arr = doc.array(); + } try { Flame::loadIndexedPackVersions(current, arr); diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index 9c46e887f9..fce16e35d1 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -133,7 +133,7 @@ QVariant ListModel::data(const QModelIndex &index, int role) const ((ListModel *)this)->requestLogo(pack.logo); return icon; } - else if(role == Qt::TextColorRole) + else if(role == Qt::ForegroundRole) { if(pack.broken) { diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 891704de8d..8d17ad2805 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -116,7 +116,7 @@ void Page::openedImpl() ftbFetchTask->fetch(); ftbPrivatePacks->load(); - ftbFetchTask->fetchPrivate(ftbPrivatePacks->getCurrentPackCodes().toList()); + ftbFetchTask->fetchPrivate(ftbPrivatePacks->getCurrentPackCodes().values()); initialized = true; } suggestCurrent(); diff --git a/launcher/ui/themes/BrightTheme.cpp b/launcher/ui/themes/BrightTheme.cpp index b9188bdd2c..7469edfcc7 100644 --- a/launcher/ui/themes/BrightTheme.cpp +++ b/launcher/ui/themes/BrightTheme.cpp @@ -1,5 +1,7 @@ #include "BrightTheme.h" +#include <QObject> + QString BrightTheme::id() { return "bright"; diff --git a/launcher/ui/themes/DarkTheme.cpp b/launcher/ui/themes/DarkTheme.cpp index 31ecd559fb..30cbb543b2 100644 --- a/launcher/ui/themes/DarkTheme.cpp +++ b/launcher/ui/themes/DarkTheme.cpp @@ -1,5 +1,7 @@ #include "DarkTheme.h" +#include <QObject> + QString DarkTheme::id() { return "dark"; diff --git a/launcher/ui/widgets/LabeledToolButton.cpp b/launcher/ui/widgets/LabeledToolButton.cpp index ab2d3278af..7d3d1e57a8 100644 --- a/launcher/ui/widgets/LabeledToolButton.cpp +++ b/launcher/ui/widgets/LabeledToolButton.cpp @@ -81,7 +81,7 @@ QSize LabeledToolButton::sizeHint() const w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this); QSize rawSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this); - QSize sizeHint = rawSize.expandedTo(QApplication::globalStrut()); + QSize sizeHint = rawSize; return sizeHint; } diff --git a/launcher/ui/widgets/LogView.cpp b/launcher/ui/widgets/LogView.cpp index 26a2a527ef..3bb5c69afb 100644 --- a/launcher/ui/widgets/LogView.cpp +++ b/launcher/ui/widgets/LogView.cpp @@ -102,7 +102,7 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last) { format.setFont(font.value<QFont>()); } - auto fg = m_model->data(idx, Qt::TextColorRole); + auto fg = m_model->data(idx, Qt::ForegroundRole); if(fg.isValid()) { format.setForeground(fg.value<QColor>()); diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp index 9578628778..c2bc5fcb23 100644 --- a/launcher/ui/widgets/PageContainer.cpp +++ b/launcher/ui/widgets/PageContainer.cpp @@ -44,7 +44,7 @@ public: protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { - const QString pattern = filterRegExp().pattern(); + const QString pattern = filterRegularExpression().pattern(); const auto model = static_cast<PageModel *>(sourceModel()); const auto page = model->pages().at(sourceRow); if (!page->shouldDisplay()) @@ -149,7 +149,7 @@ void PageContainer::createUI() headerHLayout->addSpacerItem(new QSpacerItem(rightMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); headerHLayout->setContentsMargins(0, 6, 0, 0); - m_pageStack->setMargin(0); + m_pageStack->setContentsMargins(0, 0, 0, 0); m_pageStack->addWidget(new QWidget(this)); m_layout = new QGridLayout; diff --git a/launcher/ui/widgets/VersionListView.cpp b/launcher/ui/widgets/VersionListView.cpp index aba0b1a10d..6419d0377d 100644 --- a/launcher/ui/widgets/VersionListView.cpp +++ b/launcher/ui/widgets/VersionListView.cpp @@ -136,7 +136,7 @@ void VersionListView::paintInfoLabel(QPaintEvent *event) const auto innerBounds = bounds; innerBounds.adjust(10, 10, -10, -10); - QColor background = QApplication::palette().color(QPalette::Foreground); + QColor background = QApplication::palette().color(QPalette::Text); QColor foreground = QApplication::palette().color(QPalette::Base); foreground.setAlpha(190); painter.setFont(font); diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp index cb218466fc..3145b418c7 100644 --- a/libraries/LocalPeer/src/LocalPeer.cpp +++ b/libraries/LocalPeer/src/LocalPeer.cpp @@ -62,6 +62,7 @@ static PProcessIdToSessionId pProcessIdToSessionId = 0; #include <chrono> #include <thread> #include <QCryptographicHash> +#include <QRegularExpression> static const char* ack = "ack"; @@ -72,7 +73,7 @@ ApplicationId ApplicationId::fromTraditionalApp() protoId = protoId.toLower(); #endif auto prefix = protoId.section(QLatin1Char('/'), -1); - prefix.remove(QRegExp("[^a-zA-Z]")); + prefix.remove(QRegularExpression("[^a-zA-Z]")); prefix.truncate(6); QByteArray idc = protoId.toUtf8(); quint16 idNum = qChecksum(idc.constData(), idc.size()); diff --git a/libraries/classparser/src/classparser.cpp b/libraries/classparser/src/classparser.cpp index 8825ea399f..601521f682 100644 --- a/libraries/classparser/src/classparser.cpp +++ b/libraries/classparser/src/classparser.cpp @@ -18,7 +18,7 @@ #include "classparser.h" #include <QFile> -#include <quazipfile.h> +#include <quazip/quazipfile.h> #include <QDebug> namespace classparser diff --git a/libraries/iconfix/internal/qiconloader.cpp b/libraries/iconfix/internal/qiconloader.cpp index 0d8466f07d..b91ddba888 100644 --- a/libraries/iconfix/internal/qiconloader.cpp +++ b/libraries/iconfix/internal/qiconloader.cpp @@ -42,7 +42,7 @@ #include <QtCore/QSettings> #include <QtGui/QPainter> #include <QApplication> -#include <QLatin1Literal> +#include <QString> #include "qhexstring_p.h" @@ -651,38 +651,25 @@ QString QIconLoaderEngineFixed::key() const return QLatin1String("QIconLoaderEngineFixed"); } -void QIconLoaderEngineFixed::virtual_hook(int id, void *data) +QList<QSize> QIconLoaderEngineFixed::availableSizes(QIcon::Mode mode, QIcon::State state) { ensureLoaded(); - switch (id) - { - case QIconEngine::AvailableSizesHook: - { - QIconEngine::AvailableSizesArgument &arg = - *reinterpret_cast<QIconEngine::AvailableSizesArgument *>(data); - const int N = m_entries.size(); - QList<QSize> sizes; - sizes.reserve(N); - - // Gets all sizes from the DirectoryInfo entries - for (int i = 0; i < N; ++i) - { - int size = m_entries.at(i)->dir.size; - sizes.append(QSize(size, size)); - } - arg.sizes.swap(sizes); // commit - } - break; - case QIconEngine::IconNameHook: + const int N = m_entries.size(); + QList<QSize> sizes; + sizes.reserve(N); + + for (int i = 0; i < N; ++i) { - QString &name = *reinterpret_cast<QString *>(data); - name = m_iconName; - } - break; - default: - QIconEngine::virtual_hook(id, data); + int size = m_entries.at(i)->dir.size; + sizes.append(QSize(size, size)); } + return sizes; +} + +QString QIconLoaderEngineFixed::iconName() +{ + return m_iconName; } } // QtXdg diff --git a/libraries/iconfix/internal/qiconloader_p.h b/libraries/iconfix/internal/qiconloader_p.h index e45a08d642..3fb6d75ea2 100644 --- a/libraries/iconfix/internal/qiconloader_p.h +++ b/libraries/iconfix/internal/qiconloader_p.h @@ -123,7 +123,8 @@ private: QString key() const; bool hasIcon() const; void ensureLoaded(); - void virtual_hook(int id, void *data); + QList<QSize> availableSizes(QIcon::Mode mode = QIcon::Normal, QIcon::State state = QIcon::Off) override; + QString iconName() override; QIconLoaderEngineEntry *entryForSize(const QSize &size); QIconLoaderEngineFixed(const QIconLoaderEngineFixed &other); QThemeIconEntries m_entries; diff --git a/libraries/systeminfo/src/distroutils.cpp b/libraries/systeminfo/src/distroutils.cpp index fb9ae25d16..37ff9783ce 100644 --- a/libraries/systeminfo/src/distroutils.cpp +++ b/libraries/systeminfo/src/distroutils.cpp @@ -36,6 +36,7 @@ SOFTWARE. #include <QProcess> #include <QDebug> #include <QDir> +#include <QRegularExpression> #include <functional> @@ -170,7 +171,7 @@ void Sys::lsb_postprocess(Sys::LsbInfo & lsb, Sys::DistributionInfo & out) else { // ubuntu, debian, gentoo, scientific, slackware, ... ? - auto parts = dist.split(QRegExp("\\s+"), QString::SkipEmptyParts); + auto parts = dist.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); if(parts.size()) { dist = parts[0]; @@ -209,7 +210,7 @@ QString Sys::_extract_distribution(const QString & x) { return "sles"; } - QStringList list = release.split(QRegExp("\\s+"), QString::SkipEmptyParts); + QStringList list = release.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); if(list.size()) { return list[0]; @@ -219,12 +220,12 @@ QString Sys::_extract_distribution(const QString & x) QString Sys::_extract_version(const QString & x) { - QRegExp versionish_string("\\d+(?:\\.\\d+)*$"); - QStringList list = x.split(QRegExp("\\s+"), QString::SkipEmptyParts); + QRegularExpression versionish_string("\\d+(?:\\.\\d+)*$"); + QStringList list = x.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); for(int i = list.size() - 1; i >= 0; --i) { QString chunk = list[i]; - if(versionish_string.exactMatch(chunk)) + if(versionish_string.match(chunk).hasMatch()) { return chunk; } |
