diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-03-26 23:35:37 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-03-26 23:35:37 +0300 |
| commit | 14d8d080aedf51d44c9943f40e17046bea4b7a74 (patch) | |
| tree | 42f0693f7363d6294c9fd72f44aa204a166deadc | |
| parent | ab30d72f8e963724e028b85f8c6164ada378ea42 (diff) | |
| download | Project-Tick-14d8d080aedf51d44c9943f40e17046bea4b7a74.tar.gz Project-Tick-14d8d080aedf51d44c9943f40e17046bea4b7a74.zip | |
NOISSUE port codebase to Qt6 APIs fix Flame modpacks not integrate instance name and more!
This commit port codebase to Qt6 APIs, Fix Flame modpacks not integrate instance name
Remove Mojang Auth system
Removed MultiMC logo for readme
Signed-off-by: Mehmet Samet Duman <yongdohyun@projecttick.org>
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; } |
