From f95856d06285a65caf0da7377f42f45c33d9603f Mon Sep 17 00:00:00 2001 From: timoreo Date: Thu, 30 Jun 2022 18:07:08 +0200 Subject: [PATCH 0001/2054] Add the base of java downloading Signed-off-by: timoreo --- launcher/CMakeLists.txt | 2 + launcher/JavaDownloader.cpp | 165 ++++++++++++++++++++++++++++++++++++ launcher/JavaDownloader.h | 8 ++ 3 files changed, 175 insertions(+) create mode 100644 launcher/JavaDownloader.cpp create mode 100644 launcher/JavaDownloader.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 79ac49c76e..4e55085798 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -881,6 +881,8 @@ SET(LAUNCHER_SOURCES ui/instanceview/InstanceDelegate.h ui/instanceview/VisualGroup.cpp ui/instanceview/VisualGroup.h + JavaDownloader.cpp + JavaDownloader.h ) if(WIN32) diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp new file mode 100644 index 0000000000..43da2df01c --- /dev/null +++ b/launcher/JavaDownloader.cpp @@ -0,0 +1,165 @@ +#include "JavaDownloader.h" +#include "net/NetJob.h" +#include "Application.h" +#include "FileSystem.h" +#include "quazip.h" +#include "MMCZip.h" +#include "net/ChecksumValidator.h" + +//Quick & dirty struct to store files +struct File{ + QString path; + QString url; + QByteArray hash; +}; + +void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) { + //Query the adoptium API to get a good version + auto netJob = new NetJob(QString("JRE::QueryVersions"), APPLICATION->network()); + auto response = new QByteArray(); + netJob->addNetAction(Net::Download::makeByteArray(QUrl("https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"), response)); + QObject::connect(netJob, &NetJob::finished,[netJob, response]{ + netJob->deleteLater(); + delete response; + }); + QObject::connect(netJob, &NetJob::succeeded,[response, &OS, isLegacy] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + auto versionArray = doc.object()[OS].toObject()[isLegacy ? "jre-legacy" : "java-runtime-gamma"].toArray(); + if(!versionArray.empty()){ + auto url = versionArray[0].toObject()["manifest"].toObject()["url"].toString(); + auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); + auto files = new QByteArray(); + + download->addNetAction(Net::Download::makeByteArray(QUrl(url), files)); + + QObject::connect(download, &NetJob::finished,[download, files]{ + download->deleteLater(); + delete files; + }); + QObject::connect(download, &NetJob::succeeded,[files, isLegacy] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *files; + return; + } + + //valid json doc, begin making jre spot + auto output = FS::PathCombine(QCoreApplication::applicationDirPath(), QString("java/") + (isLegacy ? "java-legacy" : "java-current")); + FS::ensureFolderPathExists(output); + std::vector toDownload; + auto list = doc.object()["files"].toObject(); + for(auto element : list){ + auto obj = element.toObject(); + for(const auto& paths : obj.keys()){ + auto file = FS::PathCombine(output,paths); + + auto type = obj[paths].toObject()["type"].toString(); + if(type == "directory"){ + FS::ensureFolderPathExists(file); + }else if(type == "link"){ + //this is linux only ! + auto target = FS::PathCombine(file,"../"+obj[paths].toObject()["target"].toString()); + QFile(target).link(file); + }else if(type == "file"){ + //TODO download compressed version if it exists ? + auto raw = obj[paths].toObject()["downloads"].toObject()["raw"].toObject(); + auto f = File{file,raw["url"].toString(), QByteArray::fromHex(raw["sha1"].toString().toLatin1())}; + toDownload.push_back(f); + } + } + } + auto elementDownload = new NetJob("JRE::FileDownload", APPLICATION->network()); + for(const auto& file : toDownload){ + auto dl = Net::Download::makeFile(file.url,file.path); + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.hash)); + elementDownload->addNetAction(dl); + } + QObject::connect(elementDownload, &NetJob::finished,[elementDownload]{ + elementDownload->deleteLater(); + }); + elementDownload->start(); + }); + download->start(); + }else{ + //mojang does not have a JRE for us, let's get azul zulu + QString javaVersion = isLegacy ? QString("8.0") : QString("17.0"); + QString azulOS; + QString arch; + QString bitness; + + if(OS == "mac-os-arm64"){ + //macos arm64 + azulOS = "macos"; + arch = "arm"; + bitness = "64"; + }else if(OS == "linux-aarch64"){ + //linux aarch64 + azulOS = "linux"; + arch = "arm"; + bitness = "64"; + } + auto metaResponse = new QByteArray(); + auto downloadJob = new NetJob(QString("JRE::QueryAzulMeta"), APPLICATION->network()); + downloadJob->addNetAction(Net::Download::makeByteArray(QString( + "https://api.azul.com/zulu/download/community/v1.0/bundles/?" + "java_version=%1" + "&os=%2" + "&arch=%3" + "&hw_bitness=%4" + "&ext=zip" //as a zip for all os, even linux + "&bundle_type=jre" //jre only + "&latest=true" //only get the one latest entry + ).arg(javaVersion,azulOS,arch,bitness), metaResponse)); + QObject::connect(downloadJob, &NetJob::finished,[downloadJob, metaResponse]{ + downloadJob->deleteLater(); + delete metaResponse; + }); + + QObject::connect(downloadJob, &NetJob::succeeded,[metaResponse, isLegacy] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*metaResponse, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *metaResponse; + return; + } + auto array = doc.array(); + if(!array.empty()){ + //JRE found ! download the zip + auto downloadURL = QUrl(array[0].toObject()["url"].toString()); + auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); + const QString path = downloadURL.host() + '/' + downloadURL.path(); + auto entry = APPLICATION->metacache()->resolveEntry("general", path); + entry->setStale(true); + download->addNetAction(Net::Download::makeCached(downloadURL,entry)); + auto zippath = entry->getFullPath(); + QObject::connect(download, &NetJob::finished,[download]{ + download->deleteLater(); + + }); + QObject::connect(download, &NetJob::succeeded,[isLegacy, zippath]{ + auto output = FS::PathCombine(FS::PathCombine(QCoreApplication::applicationDirPath(), "java"),isLegacy ? "java-legacy" : "java-current"); + //This should do all of the extracting and creating folders + MMCZip::extractDir(zippath, output); + }); + }else{ + qWarning() << "No suitable JRE found !!"; + } + }); + } + }); + + netJob->start(); + +} \ No newline at end of file diff --git a/launcher/JavaDownloader.h b/launcher/JavaDownloader.h new file mode 100644 index 0000000000..cc073b54cc --- /dev/null +++ b/launcher/JavaDownloader.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace JavaDownloader { + /*Downloads the java to the runtimes folder*/ + void downloadJava(bool isLegacy, const QString& OS); +} From 3433c102b70c1c74c882dd2fa19242725676ffe5 Mon Sep 17 00:00:00 2001 From: timoreo Date: Thu, 30 Jun 2022 18:13:52 +0200 Subject: [PATCH 0002/2054] Remove old comment and change to piston-meta Signed-off-by: timoreo --- launcher/JavaDownloader.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp index 43da2df01c..f1a24a68cc 100644 --- a/launcher/JavaDownloader.cpp +++ b/launcher/JavaDownloader.cpp @@ -14,10 +14,9 @@ struct File{ }; void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) { - //Query the adoptium API to get a good version auto netJob = new NetJob(QString("JRE::QueryVersions"), APPLICATION->network()); auto response = new QByteArray(); - netJob->addNetAction(Net::Download::makeByteArray(QUrl("https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"), response)); + netJob->addNetAction(Net::Download::makeByteArray(QUrl("https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"), response)); QObject::connect(netJob, &NetJob::finished,[netJob, response]{ netJob->deleteLater(); delete response; From 9a4a92de4f90334a9dcf1b7b7712f628b232d480 Mon Sep 17 00:00:00 2001 From: timoreo Date: Fri, 1 Jul 2022 08:52:56 +0200 Subject: [PATCH 0003/2054] Clang format Signed-off-by: timoreo --- launcher/JavaDownloader.cpp | 124 +++++++++++++++++------------------- launcher/JavaDownloader.h | 6 +- 2 files changed, 63 insertions(+), 67 deletions(-) diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp index f1a24a68cc..5c3d04a6df 100644 --- a/launcher/JavaDownloader.cpp +++ b/launcher/JavaDownloader.cpp @@ -1,158 +1,155 @@ #include "JavaDownloader.h" -#include "net/NetJob.h" #include "Application.h" #include "FileSystem.h" -#include "quazip.h" #include "MMCZip.h" #include "net/ChecksumValidator.h" +#include "net/NetJob.h" +#include "quazip.h" -//Quick & dirty struct to store files -struct File{ +// Quick & dirty struct to store files +struct File { QString path; QString url; QByteArray hash; }; -void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) { +void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) +{ auto netJob = new NetJob(QString("JRE::QueryVersions"), APPLICATION->network()); auto response = new QByteArray(); - netJob->addNetAction(Net::Download::makeByteArray(QUrl("https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"), response)); - QObject::connect(netJob, &NetJob::finished,[netJob, response]{ + netJob->addNetAction(Net::Download::makeByteArray( + QUrl("https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"), response)); + QObject::connect(netJob, &NetJob::finished, [netJob, response] { netJob->deleteLater(); delete response; }); - QObject::connect(netJob, &NetJob::succeeded,[response, &OS, isLegacy] { + QObject::connect(netJob, &NetJob::succeeded, [response, &OS, isLegacy] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response at " << parse_error.offset - << " reason: " << parse_error.errorString(); + qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << *response; return; } auto versionArray = doc.object()[OS].toObject()[isLegacy ? "jre-legacy" : "java-runtime-gamma"].toArray(); - if(!versionArray.empty()){ + if (!versionArray.empty()) { auto url = versionArray[0].toObject()["manifest"].toObject()["url"].toString(); auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); auto files = new QByteArray(); download->addNetAction(Net::Download::makeByteArray(QUrl(url), files)); - QObject::connect(download, &NetJob::finished,[download, files]{ + QObject::connect(download, &NetJob::finished, [download, files] { download->deleteLater(); delete files; }); - QObject::connect(download, &NetJob::succeeded,[files, isLegacy] { + QObject::connect(download, &NetJob::succeeded, [files, isLegacy] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error); if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response at " << parse_error.offset - << " reason: " << parse_error.errorString(); + qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << *files; return; } - //valid json doc, begin making jre spot - auto output = FS::PathCombine(QCoreApplication::applicationDirPath(), QString("java/") + (isLegacy ? "java-legacy" : "java-current")); + // valid json doc, begin making jre spot + auto output = + FS::PathCombine(QCoreApplication::applicationDirPath(), QString("java/") + (isLegacy ? "java-legacy" : "java-current")); FS::ensureFolderPathExists(output); std::vector toDownload; auto list = doc.object()["files"].toObject(); - for(auto element : list){ + for (auto element : list) { auto obj = element.toObject(); - for(const auto& paths : obj.keys()){ - auto file = FS::PathCombine(output,paths); + for (const auto& paths : obj.keys()) { + auto file = FS::PathCombine(output, paths); auto type = obj[paths].toObject()["type"].toString(); - if(type == "directory"){ + if (type == "directory") { FS::ensureFolderPathExists(file); - }else if(type == "link"){ - //this is linux only ! - auto target = FS::PathCombine(file,"../"+obj[paths].toObject()["target"].toString()); + } else if (type == "link") { + // this is linux only ! + auto target = FS::PathCombine(file, "../" + obj[paths].toObject()["target"].toString()); QFile(target).link(file); - }else if(type == "file"){ - //TODO download compressed version if it exists ? + } else if (type == "file") { + // TODO download compressed version if it exists ? auto raw = obj[paths].toObject()["downloads"].toObject()["raw"].toObject(); - auto f = File{file,raw["url"].toString(), QByteArray::fromHex(raw["sha1"].toString().toLatin1())}; + auto f = File{ file, raw["url"].toString(), QByteArray::fromHex(raw["sha1"].toString().toLatin1()) }; toDownload.push_back(f); } } } auto elementDownload = new NetJob("JRE::FileDownload", APPLICATION->network()); - for(const auto& file : toDownload){ - auto dl = Net::Download::makeFile(file.url,file.path); + for (const auto& file : toDownload) { + auto dl = Net::Download::makeFile(file.url, file.path); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.hash)); elementDownload->addNetAction(dl); } - QObject::connect(elementDownload, &NetJob::finished,[elementDownload]{ - elementDownload->deleteLater(); - }); + QObject::connect(elementDownload, &NetJob::finished, [elementDownload] { elementDownload->deleteLater(); }); elementDownload->start(); }); download->start(); - }else{ - //mojang does not have a JRE for us, let's get azul zulu + } else { + // mojang does not have a JRE for us, let's get azul zulu QString javaVersion = isLegacy ? QString("8.0") : QString("17.0"); QString azulOS; QString arch; QString bitness; - if(OS == "mac-os-arm64"){ - //macos arm64 + if (OS == "mac-os-arm64") { + // macos arm64 azulOS = "macos"; arch = "arm"; bitness = "64"; - }else if(OS == "linux-aarch64"){ - //linux aarch64 + } else if (OS == "linux-aarch64") { + // linux aarch64 azulOS = "linux"; arch = "arm"; bitness = "64"; } auto metaResponse = new QByteArray(); auto downloadJob = new NetJob(QString("JRE::QueryAzulMeta"), APPLICATION->network()); - downloadJob->addNetAction(Net::Download::makeByteArray(QString( - "https://api.azul.com/zulu/download/community/v1.0/bundles/?" - "java_version=%1" - "&os=%2" - "&arch=%3" - "&hw_bitness=%4" - "&ext=zip" //as a zip for all os, even linux - "&bundle_type=jre" //jre only - "&latest=true" //only get the one latest entry - ).arg(javaVersion,azulOS,arch,bitness), metaResponse)); - QObject::connect(downloadJob, &NetJob::finished,[downloadJob, metaResponse]{ + downloadJob->addNetAction(Net::Download::makeByteArray(QString("https://api.azul.com/zulu/download/community/v1.0/bundles/?" + "java_version=%1" + "&os=%2" + "&arch=%3" + "&hw_bitness=%4" + "&ext=zip" // as a zip for all os, even linux + "&bundle_type=jre" // jre only + "&latest=true" // only get the one latest entry + ) + .arg(javaVersion, azulOS, arch, bitness), + metaResponse)); + QObject::connect(downloadJob, &NetJob::finished, [downloadJob, metaResponse] { downloadJob->deleteLater(); delete metaResponse; }); - QObject::connect(downloadJob, &NetJob::succeeded,[metaResponse, isLegacy] { + QObject::connect(downloadJob, &NetJob::succeeded, [metaResponse, isLegacy] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*metaResponse, &parse_error); if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response at " << parse_error.offset - << " reason: " << parse_error.errorString(); + qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << *metaResponse; return; } auto array = doc.array(); - if(!array.empty()){ - //JRE found ! download the zip + if (!array.empty()) { + // JRE found ! download the zip auto downloadURL = QUrl(array[0].toObject()["url"].toString()); auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); const QString path = downloadURL.host() + '/' + downloadURL.path(); auto entry = APPLICATION->metacache()->resolveEntry("general", path); entry->setStale(true); - download->addNetAction(Net::Download::makeCached(downloadURL,entry)); + download->addNetAction(Net::Download::makeCached(downloadURL, entry)); auto zippath = entry->getFullPath(); - QObject::connect(download, &NetJob::finished,[download]{ - download->deleteLater(); - - }); - QObject::connect(download, &NetJob::succeeded,[isLegacy, zippath]{ - auto output = FS::PathCombine(FS::PathCombine(QCoreApplication::applicationDirPath(), "java"),isLegacy ? "java-legacy" : "java-current"); - //This should do all of the extracting and creating folders + QObject::connect(download, &NetJob::finished, [download] { download->deleteLater(); }); + QObject::connect(download, &NetJob::succeeded, [isLegacy, zippath] { + auto output = FS::PathCombine(FS::PathCombine(QCoreApplication::applicationDirPath(), "java"), + isLegacy ? "java-legacy" : "java-current"); + // This should do all of the extracting and creating folders MMCZip::extractDir(zippath, output); }); - }else{ + } else { qWarning() << "No suitable JRE found !!"; } }); @@ -160,5 +157,4 @@ void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) { }); netJob->start(); - } \ No newline at end of file diff --git a/launcher/JavaDownloader.h b/launcher/JavaDownloader.h index cc073b54cc..3dd57eebe6 100644 --- a/launcher/JavaDownloader.h +++ b/launcher/JavaDownloader.h @@ -3,6 +3,6 @@ #include namespace JavaDownloader { - /*Downloads the java to the runtimes folder*/ - void downloadJava(bool isLegacy, const QString& OS); -} +/*Downloads the java to the runtimes folder*/ +void downloadJava(bool isLegacy, const QString& OS); +} // namespace JavaDownloader From a97387b692456cae3b13b48ea9e5901712334f4c Mon Sep 17 00:00:00 2001 From: timoreo Date: Fri, 1 Jul 2022 09:37:23 +0200 Subject: [PATCH 0004/2054] Cherry-pick SysInfo from #680 Signed-off-by: timoreo --- launcher/CMakeLists.txt | 2 + launcher/SysInfo.cpp | 155 ++++++++++++++++++++++++++++++++++++++++ launcher/SysInfo.h | 13 ++++ 3 files changed, 170 insertions(+) create mode 100644 launcher/SysInfo.cpp create mode 100644 launcher/SysInfo.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 4e55085798..c4d9cb74f3 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -580,6 +580,8 @@ SET(LAUNCHER_SOURCES UpdateController.h ApplicationMessage.h ApplicationMessage.cpp + SysInfo.h + SysInfo.cpp # GUI - general utilities DesktopServices.h diff --git a/launcher/SysInfo.cpp b/launcher/SysInfo.cpp new file mode 100644 index 0000000000..4abf39301d --- /dev/null +++ b/launcher/SysInfo.cpp @@ -0,0 +1,155 @@ +#include +#include +#include "settings/SettingsObject.h" +#ifdef Q_OS_MACOS +#include +#endif +#include +#include +#include +#include "MessageLevel.h" +#include +#include +#include +#include "java/JavaUtils.h" +#include "FileSystem.h" +#include "Commandline.h" +#include "Application.h" + +#ifdef Q_OS_MACOS +bool rosettaDetect() { + int ret = 0; + size_t size = sizeof(ret); + if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) + { + return false; + } + if(ret == 0) + { + return false; + } + if(ret == 1) + { + return true; + } + return false; +} +#endif + +namespace SysInfo { +QString currentSystem() { +#if defined(Q_OS_LINUX) + return "linux"; +#elif defined(Q_OS_MACOS) + return "osx"; +#elif defined(Q_OS_WINDOWS) + return "windows"; +#elif defined(Q_OS_FREEBSD) + return "freebsd"; +#elif defined(Q_OS_OPENBSD) + return "openbsd"; +#else + return "unknown"; +#endif +} + +QString useQTForArch(){ + auto qtArch = QSysInfo::currentCpuArchitecture(); +#if defined(Q_OS_MACOS) && !defined(Q_PROCESSOR_ARM) + if(rosettaDetect()) + { + return "arm64"; + } + else + { + return "x86_64"; + } +#endif + return qtArch; +} + +QString runCheckerForArch(const SettingsObjectPtr& settingsObj){ + QString checkerJar = FS::PathCombine(APPLICATION->getJarsPath(), "JavaCheck.jar"); + + QStringList args; + + QProcessPtr process = new QProcess(); + args.append({"-jar", checkerJar}); + process->setArguments(args); + process->setProgram(settingsObj->get("JavaPath").toString()); + process->setProcessChannelMode(QProcess::SeparateChannels); + process->setProcessEnvironment(CleanEnviroment()); + qDebug() << "Running java checker: " + settingsObj->get("JavaPath").toString() + args.join(" ");; + + process->start(); + if(!process->waitForFinished(15000)){ + // we've been waiting for 15 seconds! wtf! OR... it already finished. But HOW WOULD THAT HAPPEN? + process->kill(); // die. BUUURNNNN + // fallback to using polymc arch + return useQTForArch(); + } else { + // yay we can use the java arch + QString stdout_javaChecker; + QString stderr_javaChecker; + + // process stdout + QByteArray data = process->readAllStandardOutput(); + QString added = QString::fromLocal8Bit(data); + added.remove('\r'); + stdout_javaChecker += added; + + // process stderr + data = process->readAllStandardError(); + added = QString::fromLocal8Bit(data); + added.remove('\r'); + stderr_javaChecker += added; + + QMap results; + QStringList lines = stdout_javaChecker.split("\n", QString::SkipEmptyParts); + for(QString line : lines) + { + line = line.trimmed(); + // NOTE: workaround for GH-4125, where garbage is getting printed into stdout on bedrock linux + if (line.contains("/bedrock/strata")) { + continue; + } + + auto parts = line.split('=', QString::SkipEmptyParts); + if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) + { + continue; + } + else + { + results.insert(parts[0], parts[1]); + } + } + + if(!results.contains("os.arch") || !results.contains("java.version") || !results.contains("java.vendor")) + { + // wtf man why + // fallback to using polymc arch + return useQTForArch(); + } + + return results["os.arch"]; + } +} + +QString currentArch(const SettingsObjectPtr& settingsObj) { + auto realJavaArchitecture = settingsObj->get("JavaRealArchitecture").toString(); + if(realJavaArchitecture == ""){ + //BRO WHY NOW I HAVE TO USE A JAVA CHECKER >:( + qDebug() << "SysInfo: BRO I HAVE TO USE A JAVA CHECKER WHY IS JAVA ARCH BORKED"; + settingsObj->set("JavaRealArchitecture", runCheckerForArch(settingsObj)); + realJavaArchitecture = settingsObj->get("JavaRealArchitecture").toString(); + } + //qDebug() << "SysInfo: realJavaArch = " << realJavaArchitecture; + if(realJavaArchitecture == "aarch64"){ + return "arm64"; + } else { + return realJavaArchitecture; + } +} +} + diff --git a/launcher/SysInfo.h b/launcher/SysInfo.h new file mode 100644 index 0000000000..a4e41e674a --- /dev/null +++ b/launcher/SysInfo.h @@ -0,0 +1,13 @@ +#include +#include "settings/SettingsObject.h" +#ifdef Q_OS_MACOS +#include +#endif + +namespace SysInfo { +QString currentSystem(); +QString currentArch(const SettingsObjectPtr& settingsObj); +QString runCheckerForArch(const SettingsObjectPtr& settingsObj); +QString useQTForArch(); +} + From 98a82cd4847160f41e728403efee51ebc4d2b60a Mon Sep 17 00:00:00 2001 From: timoreo Date: Fri, 1 Jul 2022 14:03:45 +0200 Subject: [PATCH 0005/2054] Fix MMCZip bugs Signed-off-by: timoreo --- launcher/MMCZip.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 9f4e968f77..3b5c444252 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -300,6 +300,11 @@ std::optional MMCZip::extractSubDir(QuaZip *zip, const QString & su name.remove(0, subdir.size()); auto original_name = name; + // Fix subdirs/files ending with a / getting transformed into absolute paths + if(name.startsWith('/')){ + name = name.mid(1); + } + // Fix weird "folders with a single file get squashed" thing QString path; if(name.contains('/') && !name.endsWith('/')){ @@ -319,6 +324,11 @@ std::optional MMCZip::extractSubDir(QuaZip *zip, const QString & su absFilePath = directory.absoluteFilePath(path + name); } + //Block potential file traversal issues + if(!absFilePath.startsWith(directory.absolutePath())){ + qWarning() << "Potential file traversal issue, for path " << absFilePath << " with base name as " << directory.absolutePath(); + continue; + } if (!JlCompress::extractFile(zip, "", absFilePath)) { qWarning() << "Failed to extract file" << original_name << "to" << absFilePath; From 89ce80b27960e66efccfaee20b3baf1596a8ba53 Mon Sep 17 00:00:00 2001 From: timoreo Date: Fri, 1 Jul 2022 14:05:14 +0200 Subject: [PATCH 0006/2054] Fix Java downloader bugs + Add a test button in JavaPage Signed-off-by: timoreo --- launcher/JavaDownloader.cpp | 63 ++++++++++++++++----------- launcher/ui/pages/global/JavaPage.cpp | 41 +++++++++++++++++ launcher/ui/pages/global/JavaPage.h | 1 + launcher/ui/pages/global/JavaPage.ui | 11 ++++- 4 files changed, 88 insertions(+), 28 deletions(-) diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp index 5c3d04a6df..3af69f025c 100644 --- a/launcher/JavaDownloader.cpp +++ b/launcher/JavaDownloader.cpp @@ -1,6 +1,7 @@ #include "JavaDownloader.h" #include "Application.h" #include "FileSystem.h" +#include "Json.h" #include "MMCZip.h" #include "net/ChecksumValidator.h" #include "net/NetJob.h" @@ -11,6 +12,7 @@ struct File { QString path; QString url; QByteArray hash; + bool isExec; }; void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) @@ -23,7 +25,7 @@ void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) netJob->deleteLater(); delete response; }); - QObject::connect(netJob, &NetJob::succeeded, [response, &OS, isLegacy] { + QObject::connect(netJob, &NetJob::succeeded, [response, OS, isLegacy] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -31,7 +33,7 @@ void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) qWarning() << *response; return; } - auto versionArray = doc.object()[OS].toObject()[isLegacy ? "jre-legacy" : "java-runtime-gamma"].toArray(); + auto versionArray = Json::ensureArray(Json::ensureObject(doc.object(), OS), isLegacy ? "jre-legacy" : "java-runtime-gamma"); if (!versionArray.empty()) { auto url = versionArray[0].toObject()["manifest"].toObject()["url"].toString(); auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); @@ -53,35 +55,37 @@ void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) } // valid json doc, begin making jre spot - auto output = - FS::PathCombine(QCoreApplication::applicationDirPath(), QString("java/") + (isLegacy ? "java-legacy" : "java-current")); + auto output = FS::PathCombine(QString("java"), (isLegacy ? "java-legacy" : "java-current")); FS::ensureFolderPathExists(output); std::vector toDownload; auto list = doc.object()["files"].toObject(); - for (auto element : list) { - auto obj = element.toObject(); - for (const auto& paths : obj.keys()) { - auto file = FS::PathCombine(output, paths); + for (const auto& paths : list.keys()) { + auto file = FS::PathCombine(output, paths); - auto type = obj[paths].toObject()["type"].toString(); - if (type == "directory") { - FS::ensureFolderPathExists(file); - } else if (type == "link") { - // this is linux only ! - auto target = FS::PathCombine(file, "../" + obj[paths].toObject()["target"].toString()); - QFile(target).link(file); - } else if (type == "file") { - // TODO download compressed version if it exists ? - auto raw = obj[paths].toObject()["downloads"].toObject()["raw"].toObject(); - auto f = File{ file, raw["url"].toString(), QByteArray::fromHex(raw["sha1"].toString().toLatin1()) }; - toDownload.push_back(f); - } + auto type = list[paths].toObject()["type"].toString(); + if (type == "directory") { + FS::ensureFolderPathExists(file); + } else if (type == "link") { + // this is linux only ! + auto target = FS::PathCombine(file, "../" + list[paths].toObject()["target"].toString()); + QFile(target).link(file); + } else if (type == "file") { + // TODO download compressed version if it exists ? + auto raw = list[paths].toObject()["downloads"].toObject()["raw"].toObject(); + auto isExec = list[paths].toObject()["executable"].toBool(); + auto f = File{ file, raw["url"].toString(), QByteArray::fromHex(raw["sha1"].toString().toLatin1()), isExec }; + toDownload.push_back(f); } } auto elementDownload = new NetJob("JRE::FileDownload", APPLICATION->network()); for (const auto& file : toDownload) { auto dl = Net::Download::makeFile(file.url, file.path); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.hash)); + if (file.isExec) { + QObject::connect(dl.get(), &Net::Download::succeeded, [file] { + QFile(file.path).setPermissions(QFile(file.path).permissions() | QFileDevice::Permissions(0x1111)); + }); + } elementDownload->addNetAction(dl); } QObject::connect(elementDownload, &NetJob::finished, [elementDownload] { elementDownload->deleteLater(); }); @@ -90,7 +94,7 @@ void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) download->start(); } else { // mojang does not have a JRE for us, let's get azul zulu - QString javaVersion = isLegacy ? QString("8.0") : QString("17.0"); + QString javaVersion = isLegacy ? QString("8.0") : QString("18.0"); QString azulOS; QString arch; QString bitness; @@ -100,11 +104,16 @@ void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) azulOS = "macos"; arch = "arm"; bitness = "64"; - } else if (OS == "linux-aarch64") { - // linux aarch64 + } else if (OS == "linux-arm64") { + // linux arm64 azulOS = "linux"; arch = "arm"; bitness = "64"; + } else if (OS == "linux-arm") { + // linux arm (32) + azulOS = "linux"; + arch = "arm"; + bitness = "32"; } auto metaResponse = new QByteArray(); auto downloadJob = new NetJob(QString("JRE::QueryAzulMeta"), APPLICATION->network()); @@ -143,16 +152,18 @@ void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) download->addNetAction(Net::Download::makeCached(downloadURL, entry)); auto zippath = entry->getFullPath(); QObject::connect(download, &NetJob::finished, [download] { download->deleteLater(); }); - QObject::connect(download, &NetJob::succeeded, [isLegacy, zippath] { + QObject::connect(download, &NetJob::succeeded, [isLegacy, zippath, downloadURL] { auto output = FS::PathCombine(FS::PathCombine(QCoreApplication::applicationDirPath(), "java"), isLegacy ? "java-legacy" : "java-current"); // This should do all of the extracting and creating folders - MMCZip::extractDir(zippath, output); + MMCZip::extractDir(zippath, downloadURL.fileName().chopped(4), output); }); + download->start(); } else { qWarning() << "No suitable JRE found !!"; } }); + downloadJob->start(); } }); diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 2cee15bf1b..a57a8ee068 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -52,6 +52,9 @@ #include #include "Application.h" #include +#include "SysInfo.h" +#include "JavaDownloader.h" + JavaPage::JavaPage(QWidget *parent) : QWidget(parent), ui(new Ui::JavaPage) { @@ -177,6 +180,44 @@ void JavaPage::on_javaTestBtn_clicked() checker->run(); } +void JavaPage::on_javaDownloadBtn_clicked(){ + QString sys = SysInfo::currentSystem(); + if(sys == "osx"){ + sys = "mac-os"; + } + QString arch = SysInfo::useQTForArch(); + QString version; + if(sys == "windows"){ + if(arch == "x86_64"){ + version = "windows-x64"; + }else if(arch == "i386"){ + version = "windows-x86"; + }else{ + //Unknown, maybe arm, appending arch for downloader + version = "windows-"+arch; + } + }else if(sys == "mac-os"){ + if(arch == "arm64"){ + version = "mac-os-arm64"; + }else{ + version = "mac-os"; + } + }else if(sys == "linux"){ + if(arch == "x86_64"){ + version = "linux"; + }else { + // will work for i386, and arm(64) + version = "linux-" + arch; + } + }else{ + // ? ? ? ? ? unknown os, at least it won't have a java version on mojang or azul, display warning + QMessageBox::warning(this, tr("Unknown OS"), tr("The OS you are running is not supported by Mojang or Azul. Please install Java manually.")); + return; + } + //TODO display a selection for java 8 or 18 + JavaDownloader::downloadJava(false, version); +} + void JavaPage::checkerFinished() { checker.reset(); diff --git a/launcher/ui/pages/global/JavaPage.h b/launcher/ui/pages/global/JavaPage.h index 64d4098e5e..aa06dc0be0 100644 --- a/launcher/ui/pages/global/JavaPage.h +++ b/launcher/ui/pages/global/JavaPage.h @@ -85,6 +85,7 @@ private void on_javaDetectBtn_clicked(); void on_javaTestBtn_clicked(); void on_javaBrowseBtn_clicked(); + void on_javaDownloadBtn_clicked(); void checkerFinished(); private: diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index 6ccffed4da..c109efbfc0 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -6,8 +6,8 @@ 0 0 - 545 - 580 + 559 + 659 @@ -279,6 +279,13 @@ + + + + Download Java + + + From 54ad91c3b79dbd3379e6f0e1c7bc76f149fc6794 Mon Sep 17 00:00:00 2001 From: timoreo Date: Fri, 1 Jul 2022 14:18:34 +0200 Subject: [PATCH 0007/2054] Anti-scrumped Signed-off-by: timoreo --- launcher/JavaDownloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp index 3af69f025c..09aeed5de9 100644 --- a/launcher/JavaDownloader.cpp +++ b/launcher/JavaDownloader.cpp @@ -168,4 +168,4 @@ void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) }); netJob->start(); -} \ No newline at end of file +} From 53ddba80778e7c232cd778c1eabc0bc36c3f6414 Mon Sep 17 00:00:00 2001 From: timoreo Date: Sun, 10 Jul 2022 19:56:52 +0200 Subject: [PATCH 0008/2054] Made JavaDownloader a task and added some quick UI Signed-off-by: timoreo --- launcher/JavaDownloader.cpp | 51 +++++++++++++++++---------- launcher/JavaDownloader.h | 17 ++++++--- launcher/ui/pages/global/JavaPage.cpp | 28 +++++++++++---- 3 files changed, 67 insertions(+), 29 deletions(-) diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp index 09aeed5de9..d72f66f36b 100644 --- a/launcher/JavaDownloader.cpp +++ b/launcher/JavaDownloader.cpp @@ -15,17 +15,21 @@ struct File { bool isExec; }; -void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) +void JavaDownloader::executeTask() { + auto OS = m_OS; + auto isLegacy = m_isLegacy; auto netJob = new NetJob(QString("JRE::QueryVersions"), APPLICATION->network()); auto response = new QByteArray(); + setStatus(tr("Querying mojang meta")); netJob->addNetAction(Net::Download::makeByteArray( QUrl("https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"), response)); QObject::connect(netJob, &NetJob::finished, [netJob, response] { netJob->deleteLater(); delete response; }); - QObject::connect(netJob, &NetJob::succeeded, [response, OS, isLegacy] { + QObject::connect(netJob, &NetJob::progress, this, &JavaDownloader::progress); + QObject::connect(netJob, &NetJob::succeeded, [response, OS, isLegacy, this] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -35,6 +39,7 @@ void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) } auto versionArray = Json::ensureArray(Json::ensureObject(doc.object(), OS), isLegacy ? "jre-legacy" : "java-runtime-gamma"); if (!versionArray.empty()) { + setStatus(tr("Downloading java from Mojang")); auto url = versionArray[0].toObject()["manifest"].toObject()["url"].toString(); auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); auto files = new QByteArray(); @@ -45,7 +50,8 @@ void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) download->deleteLater(); delete files; }); - QObject::connect(download, &NetJob::succeeded, [files, isLegacy] { + QObject::connect(download, &NetJob::progress, this, &JavaDownloader::progress); + QObject::connect(download, &NetJob::succeeded, [files, isLegacy, this] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -89,12 +95,14 @@ void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) elementDownload->addNetAction(dl); } QObject::connect(elementDownload, &NetJob::finished, [elementDownload] { elementDownload->deleteLater(); }); + QObject::connect(elementDownload, &NetJob::succeeded, [this]{emitSucceeded();}); elementDownload->start(); }); download->start(); } else { // mojang does not have a JRE for us, let's get azul zulu - QString javaVersion = isLegacy ? QString("8.0") : QString("18.0"); + setStatus(tr("Querying Azul meta")); + QString javaVersion = isLegacy ? QString("8.0") : QString("17.0"); QString azulOS; QString arch; QString bitness; @@ -117,23 +125,24 @@ void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) } auto metaResponse = new QByteArray(); auto downloadJob = new NetJob(QString("JRE::QueryAzulMeta"), APPLICATION->network()); - downloadJob->addNetAction(Net::Download::makeByteArray(QString("https://api.azul.com/zulu/download/community/v1.0/bundles/?" - "java_version=%1" - "&os=%2" - "&arch=%3" - "&hw_bitness=%4" - "&ext=zip" // as a zip for all os, even linux - "&bundle_type=jre" // jre only - "&latest=true" // only get the one latest entry - ) - .arg(javaVersion, azulOS, arch, bitness), - metaResponse)); + downloadJob->addNetAction( + Net::Download::makeByteArray(QString("https://api.azul.com/zulu/download/community/v1.0/bundles/?" + "java_version=%1" + "&os=%2" + "&arch=%3" + "&hw_bitness=%4" + "&ext=zip" // as a zip for all os, even linux NOTE !! Linux ARM is .deb only !! + "&bundle_type=jre" // jre only + "&latest=true" // only get the one latest entry + ) + .arg(javaVersion, azulOS, arch, bitness), + metaResponse)); QObject::connect(downloadJob, &NetJob::finished, [downloadJob, metaResponse] { downloadJob->deleteLater(); delete metaResponse; }); - - QObject::connect(downloadJob, &NetJob::succeeded, [metaResponse, isLegacy] { + QObject::connect(downloadJob, &NetJob::progress, this, &JavaDownloader::progress); + QObject::connect(downloadJob, &NetJob::succeeded, [metaResponse, isLegacy, this] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*metaResponse, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -144,6 +153,7 @@ void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) auto array = doc.array(); if (!array.empty()) { // JRE found ! download the zip + setStatus(tr("Downloading java from Azul")); auto downloadURL = QUrl(array[0].toObject()["url"].toString()); auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); const QString path = downloadURL.host() + '/' + downloadURL.path(); @@ -152,15 +162,18 @@ void JavaDownloader::downloadJava(bool isLegacy, const QString& OS) download->addNetAction(Net::Download::makeCached(downloadURL, entry)); auto zippath = entry->getFullPath(); QObject::connect(download, &NetJob::finished, [download] { download->deleteLater(); }); - QObject::connect(download, &NetJob::succeeded, [isLegacy, zippath, downloadURL] { + QObject::connect(download, &NetJob::progress, this, &JavaDownloader::progress); + QObject::connect(download, &NetJob::succeeded, [isLegacy, zippath, downloadURL, this] { + setStatus(tr("Extracting java")); auto output = FS::PathCombine(FS::PathCombine(QCoreApplication::applicationDirPath(), "java"), isLegacy ? "java-legacy" : "java-current"); // This should do all of the extracting and creating folders MMCZip::extractDir(zippath, downloadURL.fileName().chopped(4), output); + emitSucceeded(); }); download->start(); } else { - qWarning() << "No suitable JRE found !!"; + emitFailed(tr("No suitable JRE found")); } }); downloadJob->start(); diff --git a/launcher/JavaDownloader.h b/launcher/JavaDownloader.h index 3dd57eebe6..3b7a7c425d 100644 --- a/launcher/JavaDownloader.h +++ b/launcher/JavaDownloader.h @@ -1,8 +1,17 @@ #pragma once #include +#include "tasks/Task.h" -namespace JavaDownloader { -/*Downloads the java to the runtimes folder*/ -void downloadJava(bool isLegacy, const QString& OS); -} // namespace JavaDownloader +class JavaDownloader : public Task { + Q_OBJECT + public: + /*Downloads the java to the runtimes folder*/ + explicit JavaDownloader(bool isLegacy, const QString& OS) : m_isLegacy(isLegacy), m_OS(OS) {} + + void executeTask() override; + + private: + bool m_isLegacy; + const QString& m_OS; +}; diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index a57a8ee068..2cc627b7d5 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -48,13 +48,14 @@ #include "java/JavaUtils.h" #include "java/JavaInstallList.h" -#include "settings/SettingsObject.h" #include -#include "Application.h" #include -#include "SysInfo.h" +#include "Application.h" #include "JavaDownloader.h" - +#include "SysInfo.h" +#include "settings/SettingsObject.h" +#include "ui/dialogs/ProgressDialog.h" +#include JavaPage::JavaPage(QWidget *parent) : QWidget(parent), ui(new Ui::JavaPage) { @@ -214,8 +215,23 @@ void JavaPage::on_javaDownloadBtn_clicked(){ QMessageBox::warning(this, tr("Unknown OS"), tr("The OS you are running is not supported by Mojang or Azul. Please install Java manually.")); return; } - //TODO display a selection for java 8 or 18 - JavaDownloader::downloadJava(false, version); + //Selection using QMessageBox for java 8 or 17 + QMessageBox box(QMessageBox::Icon::Question, tr("Java version"), tr("Do you want to download Java version 8 or 17?\n Java 8 is recommended for minecraft versions below 1.17\n Java 17 is recommended for minecraft versions above or equal to 1.17"), + QMessageBox::NoButton, this); + box.addButton("Java 17", QMessageBox::YesRole); + auto no = box.addButton("Java 8", QMessageBox::NoRole); + auto cancel = box.addButton(tr("Download both"), QMessageBox::AcceptRole); + box.exec(); + bool isLegacy = box.clickedButton() == no; + + auto down = new JavaDownloader(isLegacy, version); + ProgressDialog dialog(this); + dialog.execWithTask(down); + if(box.clickedButton() == cancel) { + auto dwn = new JavaDownloader(false, version); + ProgressDialog dg(this); + dg.execWithTask(dwn); + } } void JavaPage::checkerFinished() From f946964490c181700cbc0b1b479fd6089f4ba02e Mon Sep 17 00:00:00 2001 From: timoreo Date: Sun, 10 Jul 2022 20:21:52 +0200 Subject: [PATCH 0009/2054] Regrab a few changes on SysInfo Signed-off-by: timoreo --- launcher/CMakeLists.txt | 2 ++ launcher/SysInfo.cpp | 32 ++++++++++++++++------- launcher/SysInfo.h | 9 +++---- launcher/minecraft/LaunchContext.cpp | 39 ++++++++++++++++++++++++++++ launcher/minecraft/LaunchContext.h | 34 ++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 14 deletions(-) create mode 100644 launcher/minecraft/LaunchContext.cpp create mode 100644 launcher/minecraft/LaunchContext.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c4d9cb74f3..c467957a89 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -271,6 +271,8 @@ set(MINECRAFT_SOURCES minecraft/GradleSpecifier.h minecraft/MinecraftInstance.cpp minecraft/MinecraftInstance.h + minecraft/LaunchContext.cpp + minecraft/LaunchContext.h minecraft/LaunchProfile.cpp minecraft/LaunchProfile.h minecraft/Component.cpp diff --git a/launcher/SysInfo.cpp b/launcher/SysInfo.cpp index 4abf39301d..010eb84c8d 100644 --- a/launcher/SysInfo.cpp +++ b/launcher/SysInfo.cpp @@ -1,6 +1,6 @@ #include #include -#include "settings/SettingsObject.h" +#include "minecraft/LaunchContext.h" #ifdef Q_OS_MACOS #include #endif @@ -68,18 +68,24 @@ QString useQTForArch(){ return qtArch; } -QString runCheckerForArch(const SettingsObjectPtr& settingsObj){ - QString checkerJar = FS::PathCombine(APPLICATION->getJarsPath(), "JavaCheck.jar"); +QString runCheckerForArch(LaunchContext launchContext){ + QString checkerJar = JavaUtils::getJavaCheckPath(); + + if (checkerJar.isEmpty()) + { + qDebug() << "Java checker library could not be found. Please check your installation."; + return useQTForArch(); + } QStringList args; QProcessPtr process = new QProcess(); args.append({"-jar", checkerJar}); process->setArguments(args); - process->setProgram(settingsObj->get("JavaPath").toString()); + process->setProgram(launchContext.getJavaPath().toString()); process->setProcessChannelMode(QProcess::SeparateChannels); process->setProcessEnvironment(CleanEnviroment()); - qDebug() << "Running java checker: " + settingsObj->get("JavaPath").toString() + args.join(" ");; + qDebug() << "Running java checker: " + launchContext.getJavaPath().toString() + args.join(" ");; process->start(); if(!process->waitForFinished(15000)){ @@ -105,7 +111,11 @@ QString runCheckerForArch(const SettingsObjectPtr& settingsObj){ stderr_javaChecker += added; QMap results; +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QStringList lines = stdout_javaChecker.split("\n", Qt::SkipEmptyParts); +#else QStringList lines = stdout_javaChecker.split("\n", QString::SkipEmptyParts); +#endif for(QString line : lines) { line = line.trimmed(); @@ -114,7 +124,11 @@ QString runCheckerForArch(const SettingsObjectPtr& settingsObj){ continue; } +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + auto parts = line.split('=', Qt::SkipEmptyParts); +#else auto parts = line.split('=', QString::SkipEmptyParts); +#endif if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) { continue; @@ -136,13 +150,13 @@ QString runCheckerForArch(const SettingsObjectPtr& settingsObj){ } } -QString currentArch(const SettingsObjectPtr& settingsObj) { - auto realJavaArchitecture = settingsObj->get("JavaRealArchitecture").toString(); +QString currentArch(LaunchContext launchContext) { + auto realJavaArchitecture = launchContext.getRealJavaArch().toString(); if(realJavaArchitecture == ""){ //BRO WHY NOW I HAVE TO USE A JAVA CHECKER >:( qDebug() << "SysInfo: BRO I HAVE TO USE A JAVA CHECKER WHY IS JAVA ARCH BORKED"; - settingsObj->set("JavaRealArchitecture", runCheckerForArch(settingsObj)); - realJavaArchitecture = settingsObj->get("JavaRealArchitecture").toString(); + launchContext.setRealJavaArch(runCheckerForArch(launchContext)); + realJavaArchitecture = launchContext.getRealJavaArch().toString(); } //qDebug() << "SysInfo: realJavaArch = " << realJavaArchitecture; if(realJavaArchitecture == "aarch64"){ diff --git a/launcher/SysInfo.h b/launcher/SysInfo.h index a4e41e674a..2807576743 100644 --- a/launcher/SysInfo.h +++ b/launcher/SysInfo.h @@ -1,13 +1,12 @@ #include -#include "settings/SettingsObject.h" +#include "minecraft/LaunchContext.h" #ifdef Q_OS_MACOS #include #endif namespace SysInfo { QString currentSystem(); -QString currentArch(const SettingsObjectPtr& settingsObj); -QString runCheckerForArch(const SettingsObjectPtr& settingsObj); +QString currentArch(LaunchContext launchContext); +QString runCheckerForArch(LaunchContext launchContext); QString useQTForArch(); -} - +} \ No newline at end of file diff --git a/launcher/minecraft/LaunchContext.cpp b/launcher/minecraft/LaunchContext.cpp new file mode 100644 index 0000000000..b445d9ae17 --- /dev/null +++ b/launcher/minecraft/LaunchContext.cpp @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Toshit Chawda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "LaunchContext.h" + +LaunchContext::LaunchContext(SettingsObjectPtr instanceSettings){ + m_instanceSettings = instanceSettings; +} + +void LaunchContext::setRealJavaArch(QVariant realJavaArch) +{ + m_instanceSettings->set("JavaRealArchitecture", realJavaArch); +} + +QVariant LaunchContext::getRealJavaArch() +{ + return m_instanceSettings->get("JavaRealArchitecture"); +} + +QVariant LaunchContext::getJavaPath() +{ + return m_instanceSettings->get("JavaPath"); +} \ No newline at end of file diff --git a/launcher/minecraft/LaunchContext.h b/launcher/minecraft/LaunchContext.h new file mode 100644 index 0000000000..0d322a2eab --- /dev/null +++ b/launcher/minecraft/LaunchContext.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Toshit Chawda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include "settings/SettingsObject.h" + +#pragma once + +class LaunchContext +{ + public: + LaunchContext(SettingsObjectPtr instanceSettings); + void setRealJavaArch(QVariant realJavaArch); + QVariant getRealJavaArch(); + QVariant getJavaPath(); + private: + SettingsObjectPtr m_instanceSettings; +}; \ No newline at end of file From 68446c2a5442d568a75229d63d6addc502237b4a Mon Sep 17 00:00:00 2001 From: timoreo Date: Sun, 10 Jul 2022 20:34:29 +0200 Subject: [PATCH 0010/2054] Obligatory formatting fix commit Signed-off-by: timoreo --- launcher/JavaDownloader.cpp | 2 +- launcher/SysInfo.h | 2 +- launcher/minecraft/LaunchContext.cpp | 2 +- launcher/minecraft/LaunchContext.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp index d72f66f36b..2cca83a9b6 100644 --- a/launcher/JavaDownloader.cpp +++ b/launcher/JavaDownloader.cpp @@ -95,7 +95,7 @@ void JavaDownloader::executeTask() elementDownload->addNetAction(dl); } QObject::connect(elementDownload, &NetJob::finished, [elementDownload] { elementDownload->deleteLater(); }); - QObject::connect(elementDownload, &NetJob::succeeded, [this]{emitSucceeded();}); + QObject::connect(elementDownload, &NetJob::succeeded, [this] { emitSucceeded(); }); elementDownload->start(); }); download->start(); diff --git a/launcher/SysInfo.h b/launcher/SysInfo.h index 2807576743..9107cacb8b 100644 --- a/launcher/SysInfo.h +++ b/launcher/SysInfo.h @@ -9,4 +9,4 @@ QString currentSystem(); QString currentArch(LaunchContext launchContext); QString runCheckerForArch(LaunchContext launchContext); QString useQTForArch(); -} \ No newline at end of file +} diff --git a/launcher/minecraft/LaunchContext.cpp b/launcher/minecraft/LaunchContext.cpp index b445d9ae17..ea6a549f0e 100644 --- a/launcher/minecraft/LaunchContext.cpp +++ b/launcher/minecraft/LaunchContext.cpp @@ -36,4 +36,4 @@ QVariant LaunchContext::getRealJavaArch() QVariant LaunchContext::getJavaPath() { return m_instanceSettings->get("JavaPath"); -} \ No newline at end of file +} diff --git a/launcher/minecraft/LaunchContext.h b/launcher/minecraft/LaunchContext.h index 0d322a2eab..2c6e1307f8 100644 --- a/launcher/minecraft/LaunchContext.h +++ b/launcher/minecraft/LaunchContext.h @@ -31,4 +31,4 @@ class LaunchContext QVariant getJavaPath(); private: SettingsObjectPtr m_instanceSettings; -}; \ No newline at end of file +}; From 24dc154856cc45b78404b47fa085f8ea0a1adeb0 Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 11 Jul 2022 15:11:08 +0200 Subject: [PATCH 0011/2054] Add download button to more pages + UI fixes Signed-off-by: timoreo --- launcher/JavaDownloader.cpp | 79 +++++++++++++++++++ launcher/JavaDownloader.h | 1 + launcher/ui/pages/global/JavaPage.cpp | 57 +------------ .../pages/instance/InstanceSettingsPage.cpp | 6 ++ .../ui/pages/instance/InstanceSettingsPage.h | 1 + .../ui/pages/instance/InstanceSettingsPage.ui | 7 ++ launcher/ui/widgets/JavaSettingsWidget.cpp | 11 +++ launcher/ui/widgets/JavaSettingsWidget.h | 3 + 8 files changed, 111 insertions(+), 54 deletions(-) diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp index 2cca83a9b6..cb8fd11167 100644 --- a/launcher/JavaDownloader.cpp +++ b/launcher/JavaDownloader.cpp @@ -1,11 +1,15 @@ #include "JavaDownloader.h" +#include +#include #include "Application.h" #include "FileSystem.h" #include "Json.h" #include "MMCZip.h" +#include "SysInfo.h" #include "net/ChecksumValidator.h" #include "net/NetJob.h" #include "quazip.h" +#include "ui/dialogs/ProgressDialog.h" // Quick & dirty struct to store files struct File { @@ -95,6 +99,7 @@ void JavaDownloader::executeTask() elementDownload->addNetAction(dl); } QObject::connect(elementDownload, &NetJob::finished, [elementDownload] { elementDownload->deleteLater(); }); + QObject::connect(elementDownload, &NetJob::progress, this, &JavaDownloader::progress); QObject::connect(elementDownload, &NetJob::succeeded, [this] { emitSucceeded(); }); elementDownload->start(); }); @@ -182,3 +187,77 @@ void JavaDownloader::executeTask() netJob->start(); } +void JavaDownloader::showPrompts(QWidget* parent) +{ + QString sys = SysInfo::currentSystem(); + if (sys == "osx") { + sys = "mac-os"; + } + QString arch = SysInfo::useQTForArch(); + QString version; + if (sys == "windows") { + if (arch == "x86_64") { + version = "windows-x64"; + } else if (arch == "i386") { + version = "windows-x86"; + } else { + // Unknown, maybe arm, appending arch for downloader + version = "windows-" + arch; + } + } else if (sys == "mac-os") { + if (arch == "arm64") { + version = "mac-os-arm64"; + } else { + version = "mac-os"; + } + } else if (sys == "linux") { + if (arch == "x86_64") { + version = "linux"; + } else { + // will work for i386, and arm(64) + version = "linux-" + arch; + } + } else { + // ? ? ? ? ? unknown os, at least it won't have a java version on mojang or azul, display warning + QMessageBox::warning(parent, tr("Unknown OS"), + tr("The OS you are running is not supported by Mojang or Azul. Please install Java manually.")); + return; + } + // Selection using QMessageBox for java 8 or 17 + QMessageBox box(QMessageBox::Icon::Question, tr("Java version"), + tr("Do you want to download Java version 8 or 17?\n Java 8 is recommended for minecraft versions below 1.17\n Java 17 " + "is recommended for minecraft versions above or equal to 1.17"), + QMessageBox::NoButton, parent); + auto yes = box.addButton("Java 17", QMessageBox::AcceptRole); + auto no = box.addButton("Java 8", QMessageBox::AcceptRole); + auto both = box.addButton(tr("Download both"), QMessageBox::AcceptRole); + auto cancel = box.addButton(QMessageBox::Cancel); + + if (QFileInfo::exists(FS::PathCombine(QString("java"), "java-legacy"))) { + no->setEnabled(false); + } + if (QFileInfo::exists(FS::PathCombine(QString("java"), "java-current"))) { + yes->setEnabled(false); + } + if (!yes->isEnabled() || !no->isEnabled()) { + both->setEnabled(false); + } + if (!yes->isEnabled() && !no->isEnabled()) { + QMessageBox::warning(parent, tr("Already installed !"), tr("Both versions of java are already installed !")); + return; + } + box.exec(); + if (box.clickedButton() == nullptr || box.clickedButton() == cancel) { + return; + } + bool isLegacy = box.clickedButton() == no; + + auto down = new JavaDownloader(isLegacy, version); + ProgressDialog dialog(parent); + dialog.execWithTask(down); + if (box.clickedButton() == both) { + auto dwn = new JavaDownloader(false, version); + ProgressDialog dg(parent); + dg.execWithTask(dwn); + } +} diff --git a/launcher/JavaDownloader.h b/launcher/JavaDownloader.h index 3b7a7c425d..274a95e49f 100644 --- a/launcher/JavaDownloader.h +++ b/launcher/JavaDownloader.h @@ -10,6 +10,7 @@ class JavaDownloader : public Task { explicit JavaDownloader(bool isLegacy, const QString& OS) : m_isLegacy(isLegacy), m_OS(OS) {} void executeTask() override; + static void showPrompts(QWidget* parent = nullptr); private: bool m_isLegacy; diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 2cc627b7d5..d7f48f552b 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -52,10 +52,7 @@ #include #include "Application.h" #include "JavaDownloader.h" -#include "SysInfo.h" #include "settings/SettingsObject.h" -#include "ui/dialogs/ProgressDialog.h" -#include JavaPage::JavaPage(QWidget *parent) : QWidget(parent), ui(new Ui::JavaPage) { @@ -181,57 +178,9 @@ void JavaPage::on_javaTestBtn_clicked() checker->run(); } -void JavaPage::on_javaDownloadBtn_clicked(){ - QString sys = SysInfo::currentSystem(); - if(sys == "osx"){ - sys = "mac-os"; - } - QString arch = SysInfo::useQTForArch(); - QString version; - if(sys == "windows"){ - if(arch == "x86_64"){ - version = "windows-x64"; - }else if(arch == "i386"){ - version = "windows-x86"; - }else{ - //Unknown, maybe arm, appending arch for downloader - version = "windows-"+arch; - } - }else if(sys == "mac-os"){ - if(arch == "arm64"){ - version = "mac-os-arm64"; - }else{ - version = "mac-os"; - } - }else if(sys == "linux"){ - if(arch == "x86_64"){ - version = "linux"; - }else { - // will work for i386, and arm(64) - version = "linux-" + arch; - } - }else{ - // ? ? ? ? ? unknown os, at least it won't have a java version on mojang or azul, display warning - QMessageBox::warning(this, tr("Unknown OS"), tr("The OS you are running is not supported by Mojang or Azul. Please install Java manually.")); - return; - } - //Selection using QMessageBox for java 8 or 17 - QMessageBox box(QMessageBox::Icon::Question, tr("Java version"), tr("Do you want to download Java version 8 or 17?\n Java 8 is recommended for minecraft versions below 1.17\n Java 17 is recommended for minecraft versions above or equal to 1.17"), - QMessageBox::NoButton, this); - box.addButton("Java 17", QMessageBox::YesRole); - auto no = box.addButton("Java 8", QMessageBox::NoRole); - auto cancel = box.addButton(tr("Download both"), QMessageBox::AcceptRole); - box.exec(); - bool isLegacy = box.clickedButton() == no; - - auto down = new JavaDownloader(isLegacy, version); - ProgressDialog dialog(this); - dialog.execWithTask(down); - if(box.clickedButton() == cancel) { - auto dwn = new JavaDownloader(false, version); - ProgressDialog dg(this); - dg.execWithTask(dwn); - } +void JavaPage::on_javaDownloadBtn_clicked() +{ + JavaDownloader::showPrompts(this); } void JavaPage::checkerFinished() diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 5da7f19f56..62e780ce82 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -49,6 +49,7 @@ #include "JavaCommon.h" #include "Application.h" +#include "JavaDownloader.h" #include "java/JavaInstallList.h" #include "java/JavaUtils.h" #include "FileSystem.h" @@ -374,6 +375,11 @@ void InstanceSettingsPage::loadSettings() ui->serverJoinAddress->setText(m_settings->get("JoinServerOnLaunchAddress").toString()); } +void InstanceSettingsPage::on_javaDownloadBtn_clicked() +{ + JavaDownloader::showPrompts(this); +} + void InstanceSettingsPage::on_javaDetectBtn_clicked() { if (JavaUtils::getJavaCheckPath().isEmpty()) { diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h index 97d1296fe7..ffb226781a 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.h +++ b/launcher/ui/pages/instance/InstanceSettingsPage.h @@ -81,6 +81,7 @@ private slots: void on_javaDetectBtn_clicked(); void on_javaTestBtn_clicked(); void on_javaBrowseBtn_clicked(); + void on_javaDownloadBtn_clicked(); void applySettings(); void loadSettings(); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index 8b3c337026..4ce59a0aec 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -95,6 +95,13 @@ + + + + Download Java... + + + diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 314a126e45..e62cdd1f07 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -14,6 +14,7 @@ #include "JavaCommon.h" #include "java/JavaInstall.h" #include "java/JavaUtils.h" +#include "JavaDownloader.h" #include "FileSystem.h" #include "ui/dialogs/CustomMessageBox.h" @@ -38,6 +39,8 @@ JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent) connect(m_javaBrowseBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaBrowseBtn_clicked); connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaSettingsWidget::javaPathEdited); connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaSettingsWidget::on_javaStatusBtn_clicked); + connect(m_javaDownloadBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaDownloadBtn_clicked); + } void JavaSettingsWidget::setupUi() @@ -115,6 +118,10 @@ void JavaSettingsWidget::setupUi() m_verticalLayout->addWidget(m_memoryGroupBox); + m_javaDownloadBtn = new QPushButton("Download Java",this); + + m_verticalLayout->addWidget(m_javaDownloadBtn); + retranslate(); } @@ -276,7 +283,11 @@ void JavaSettingsWidget::on_javaBrowseBtn_clicked() m_javaPathTextBox->setText(cooked_path); checkJavaPath(cooked_path); } +void JavaSettingsWidget::on_javaDownloadBtn_clicked() +{ + JavaDownloader::showPrompts(this); +} void JavaSettingsWidget::on_javaStatusBtn_clicked() { QString text; diff --git a/launcher/ui/widgets/JavaSettingsWidget.h b/launcher/ui/widgets/JavaSettingsWidget.h index 0d280daf3d..fe6ee2f584 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.h +++ b/launcher/ui/widgets/JavaSettingsWidget.h @@ -63,6 +63,7 @@ protected slots: void javaVersionSelected(BaseVersionPtr version); void on_javaBrowseBtn_clicked(); void on_javaStatusBtn_clicked(); + void on_javaDownloadBtn_clicked(); void checkFinished(JavaCheckResult result); protected: /* methods */ @@ -88,6 +89,8 @@ protected slots: QSpinBox *m_minMemSpinBox = nullptr; QLabel *m_labelPermGen = nullptr; QSpinBox *m_permGenSpinBox = nullptr; + + QPushButton *m_javaDownloadBtn = nullptr; QIcon goodIcon; QIcon yellowIcon; QIcon badIcon; From a902f29ccff347f1c51ab95330748cb6e71d1539 Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 12 Sep 2022 08:19:42 +0200 Subject: [PATCH 0012/2054] Changed to a temporary file Signed-off-by: timoreo --- launcher/JavaDownloader.cpp | 21 ++++++++++++++------- launcher/ui/widgets/JavaSettingsWidget.cpp | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp index cb8fd11167..e56a87d0c0 100644 --- a/launcher/JavaDownloader.cpp +++ b/launcher/JavaDownloader.cpp @@ -1,6 +1,7 @@ #include "JavaDownloader.h" #include #include +#include #include "Application.h" #include "FileSystem.h" #include "Json.h" @@ -127,6 +128,11 @@ void JavaDownloader::executeTask() azulOS = "linux"; arch = "arm"; bitness = "32"; + } else if (OS == "linux"){ + // linux x86 64 (used for debugging, should never reach here) + azulOS = "linux"; + arch = "x86"; + bitness = "64"; } auto metaResponse = new QByteArray(); auto downloadJob = new NetJob(QString("JRE::QueryAzulMeta"), APPLICATION->network()); @@ -161,19 +167,20 @@ void JavaDownloader::executeTask() setStatus(tr("Downloading java from Azul")); auto downloadURL = QUrl(array[0].toObject()["url"].toString()); auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); - const QString path = downloadURL.host() + '/' + downloadURL.path(); - auto entry = APPLICATION->metacache()->resolveEntry("general", path); - entry->setStale(true); - download->addNetAction(Net::Download::makeCached(downloadURL, entry)); - auto zippath = entry->getFullPath(); + auto temp = std::make_unique(FS::PathCombine(APPLICATION->root(), "temp", "XXXXXX.zip")); + FS::ensureFolderPathExists(FS::PathCombine(APPLICATION->root(),"temp")); + // Have to open at least once to generate path + temp->open(); + temp->close(); + download->addNetAction(Net::Download::makeFile(downloadURL, temp->fileName())); QObject::connect(download, &NetJob::finished, [download] { download->deleteLater(); }); QObject::connect(download, &NetJob::progress, this, &JavaDownloader::progress); - QObject::connect(download, &NetJob::succeeded, [isLegacy, zippath, downloadURL, this] { + QObject::connect(download, &NetJob::succeeded, [isLegacy, file = std::move(temp), downloadURL, this] { setStatus(tr("Extracting java")); auto output = FS::PathCombine(FS::PathCombine(QCoreApplication::applicationDirPath(), "java"), isLegacy ? "java-legacy" : "java-current"); // This should do all of the extracting and creating folders - MMCZip::extractDir(zippath, downloadURL.fileName().chopped(4), output); + MMCZip::extractDir(file->fileName(), downloadURL.fileName().chopped(4), output); emitSucceeded(); }); download->start(); diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index e62cdd1f07..6494b79fd4 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -118,7 +118,7 @@ void JavaSettingsWidget::setupUi() m_verticalLayout->addWidget(m_memoryGroupBox); - m_javaDownloadBtn = new QPushButton("Download Java",this); + m_javaDownloadBtn = new QPushButton(tr("Download Java"), this); m_verticalLayout->addWidget(m_javaDownloadBtn); From dc84a6199932fed417796ec1183598669b4dde2f Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 12 Sep 2022 14:17:41 +0200 Subject: [PATCH 0013/2054] Added failed and aborted handlers Signed-off-by: timoreo --- launcher/JavaDownloader.cpp | 61 +++++++++++++++++++++++++++++++------ launcher/JavaDownloader.h | 3 ++ 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp index e56a87d0c0..b0cde7d986 100644 --- a/launcher/JavaDownloader.cpp +++ b/launcher/JavaDownloader.cpp @@ -24,17 +24,29 @@ void JavaDownloader::executeTask() { auto OS = m_OS; auto isLegacy = m_isLegacy; + auto netJob = new NetJob(QString("JRE::QueryVersions"), APPLICATION->network()); auto response = new QByteArray(); setStatus(tr("Querying mojang meta")); netJob->addNetAction(Net::Download::makeByteArray( QUrl("https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"), response)); - QObject::connect(netJob, &NetJob::finished, [netJob, response] { + + QObject::connect(this, &Task::aborted, [isLegacy]{ + QDir(FS::PathCombine("java",(isLegacy ? "java-legacy" : "java-current"))).removeRecursively(); + }); + + QObject::connect(netJob, &NetJob::finished, [netJob, response, this] { + //delete so that it's not called on a deleted job + QObject::disconnect(this, &Task::aborted, netJob, &NetJob::abort); netJob->deleteLater(); delete response; }); QObject::connect(netJob, &NetJob::progress, this, &JavaDownloader::progress); - QObject::connect(netJob, &NetJob::succeeded, [response, OS, isLegacy, this] { + QObject::connect(netJob, &NetJob::failed, this, &JavaDownloader::emitFailed); + + QObject::connect(this, &Task::aborted, netJob, &NetJob::abort); + + QObject::connect(netJob, &NetJob::succeeded, [response, OS, isLegacy, this, netJob] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -51,12 +63,16 @@ void JavaDownloader::executeTask() download->addNetAction(Net::Download::makeByteArray(QUrl(url), files)); - QObject::connect(download, &NetJob::finished, [download, files] { + QObject::connect(download, &NetJob::finished, [download, files, this] { + QObject::disconnect(this, &Task::aborted, download, &NetJob::abort); download->deleteLater(); delete files; }); QObject::connect(download, &NetJob::progress, this, &JavaDownloader::progress); - QObject::connect(download, &NetJob::succeeded, [files, isLegacy, this] { + QObject::connect(download, &NetJob::failed, this, &JavaDownloader::emitFailed); + QObject::connect(this, &Task::aborted, download, &NetJob::abort); + + QObject::connect(download, &NetJob::succeeded, [download, files, isLegacy, this] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -99,8 +115,15 @@ void JavaDownloader::executeTask() } elementDownload->addNetAction(dl); } - QObject::connect(elementDownload, &NetJob::finished, [elementDownload] { elementDownload->deleteLater(); }); + QObject::connect(elementDownload, &NetJob::finished, [elementDownload, this] { + QObject::disconnect(this, &Task::aborted, elementDownload, &NetJob::abort); + elementDownload->deleteLater(); + }); QObject::connect(elementDownload, &NetJob::progress, this, &JavaDownloader::progress); + QObject::connect(elementDownload, &NetJob::failed, this, &JavaDownloader::emitFailed); + + + QObject::connect(this, &Task::aborted, elementDownload, &NetJob::abort); QObject::connect(elementDownload, &NetJob::succeeded, [this] { emitSucceeded(); }); elementDownload->start(); }); @@ -148,12 +171,15 @@ void JavaDownloader::executeTask() ) .arg(javaVersion, azulOS, arch, bitness), metaResponse)); - QObject::connect(downloadJob, &NetJob::finished, [downloadJob, metaResponse] { + QObject::connect(downloadJob, &NetJob::finished, [downloadJob, metaResponse, this] { + QObject::disconnect(this, &Task::aborted, downloadJob, &NetJob::abort); downloadJob->deleteLater(); delete metaResponse; }); + QObject::connect(this, &Task::aborted, downloadJob, &NetJob::abort); + QObject::connect(netJob, &NetJob::failed, this, &JavaDownloader::emitFailed); QObject::connect(downloadJob, &NetJob::progress, this, &JavaDownloader::progress); - QObject::connect(downloadJob, &NetJob::succeeded, [metaResponse, isLegacy, this] { + QObject::connect(downloadJob, &NetJob::succeeded, [metaResponse, isLegacy, this, downloadJob] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*metaResponse, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -173,8 +199,13 @@ void JavaDownloader::executeTask() temp->open(); temp->close(); download->addNetAction(Net::Download::makeFile(downloadURL, temp->fileName())); - QObject::connect(download, &NetJob::finished, [download] { download->deleteLater(); }); + QObject::connect(download, &NetJob::finished, [download, this] { + QObject::disconnect(this, &Task::aborted, download, &NetJob::abort); + download->deleteLater(); + }); QObject::connect(download, &NetJob::progress, this, &JavaDownloader::progress); + QObject::connect(download, &NetJob::failed, this, &JavaDownloader::emitFailed); + QObject::connect(this, &Task::aborted, download, &NetJob::abort); QObject::connect(download, &NetJob::succeeded, [isLegacy, file = std::move(temp), downloadURL, this] { setStatus(tr("Extracting java")); auto output = FS::PathCombine(FS::PathCombine(QCoreApplication::applicationDirPath(), "java"), @@ -194,6 +225,14 @@ void JavaDownloader::executeTask() netJob->start(); } +void JavaDownloader::abortNetJob(NetJob* elementDownload) +{ + if(elementDownload->isRunning()){ + elementDownload->abort(); + }else{ + emit elementDownload->aborted(); + } +} void JavaDownloader::showPrompts(QWidget* parent) { QString sys = SysInfo::currentSystem(); @@ -261,10 +300,12 @@ void JavaDownloader::showPrompts(QWidget* parent) auto down = new JavaDownloader(isLegacy, version); ProgressDialog dialog(parent); - dialog.execWithTask(down); - if (box.clickedButton() == both) { + dialog.setSkipButton(true, tr("Abort")); + + if (dialog.execWithTask(down) && box.clickedButton() == both) { auto dwn = new JavaDownloader(false, version); ProgressDialog dg(parent); + dg.setSkipButton(true, tr("Abort")); dg.execWithTask(dwn); } } diff --git a/launcher/JavaDownloader.h b/launcher/JavaDownloader.h index 274a95e49f..00ccdf2bc2 100644 --- a/launcher/JavaDownloader.h +++ b/launcher/JavaDownloader.h @@ -1,6 +1,7 @@ #pragma once #include +#include "net/NetJob.h" #include "tasks/Task.h" class JavaDownloader : public Task { @@ -10,9 +11,11 @@ class JavaDownloader : public Task { explicit JavaDownloader(bool isLegacy, const QString& OS) : m_isLegacy(isLegacy), m_OS(OS) {} void executeTask() override; + [[nodiscard]] bool canAbort() const override { return true; } static void showPrompts(QWidget* parent = nullptr); private: bool m_isLegacy; const QString& m_OS; + static void abortNetJob(NetJob* elementDownload); }; From 89f1b60538068ac033f7ca1899b4d310eb43f8c2 Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 12 Sep 2022 15:00:31 +0200 Subject: [PATCH 0014/2054] Delete out non-needed functions from SysInfo Delete LaunchContext Signed-off-by: timoreo --- launcher/CMakeLists.txt | 4 +- launcher/SysInfo.cpp | 103 --------------------------- launcher/SysInfo.h | 6 -- launcher/minecraft/LaunchContext.cpp | 39 ---------- launcher/minecraft/LaunchContext.h | 34 --------- 5 files changed, 1 insertion(+), 185 deletions(-) delete mode 100644 launcher/minecraft/LaunchContext.cpp delete mode 100644 launcher/minecraft/LaunchContext.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c467957a89..4bb686106d 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -271,9 +271,7 @@ set(MINECRAFT_SOURCES minecraft/GradleSpecifier.h minecraft/MinecraftInstance.cpp minecraft/MinecraftInstance.h - minecraft/LaunchContext.cpp - minecraft/LaunchContext.h - minecraft/LaunchProfile.cpp + minecraft/LaunchProfile.cpp minecraft/LaunchProfile.h minecraft/Component.cpp minecraft/Component.h diff --git a/launcher/SysInfo.cpp b/launcher/SysInfo.cpp index 010eb84c8d..f3ff200fca 100644 --- a/launcher/SysInfo.cpp +++ b/launcher/SysInfo.cpp @@ -1,18 +1,13 @@ #include #include -#include "minecraft/LaunchContext.h" #ifdef Q_OS_MACOS #include #endif -#include #include -#include -#include "MessageLevel.h" #include #include #include #include "java/JavaUtils.h" -#include "FileSystem.h" #include "Commandline.h" #include "Application.h" @@ -67,103 +62,5 @@ QString useQTForArch(){ #endif return qtArch; } - -QString runCheckerForArch(LaunchContext launchContext){ - QString checkerJar = JavaUtils::getJavaCheckPath(); - - if (checkerJar.isEmpty()) - { - qDebug() << "Java checker library could not be found. Please check your installation."; - return useQTForArch(); - } - - QStringList args; - - QProcessPtr process = new QProcess(); - args.append({"-jar", checkerJar}); - process->setArguments(args); - process->setProgram(launchContext.getJavaPath().toString()); - process->setProcessChannelMode(QProcess::SeparateChannels); - process->setProcessEnvironment(CleanEnviroment()); - qDebug() << "Running java checker: " + launchContext.getJavaPath().toString() + args.join(" ");; - - process->start(); - if(!process->waitForFinished(15000)){ - // we've been waiting for 15 seconds! wtf! OR... it already finished. But HOW WOULD THAT HAPPEN? - process->kill(); // die. BUUURNNNN - // fallback to using polymc arch - return useQTForArch(); - } else { - // yay we can use the java arch - QString stdout_javaChecker; - QString stderr_javaChecker; - - // process stdout - QByteArray data = process->readAllStandardOutput(); - QString added = QString::fromLocal8Bit(data); - added.remove('\r'); - stdout_javaChecker += added; - - // process stderr - data = process->readAllStandardError(); - added = QString::fromLocal8Bit(data); - added.remove('\r'); - stderr_javaChecker += added; - - QMap results; -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - QStringList lines = stdout_javaChecker.split("\n", Qt::SkipEmptyParts); -#else - QStringList lines = stdout_javaChecker.split("\n", QString::SkipEmptyParts); -#endif - for(QString line : lines) - { - line = line.trimmed(); - // NOTE: workaround for GH-4125, where garbage is getting printed into stdout on bedrock linux - if (line.contains("/bedrock/strata")) { - continue; - } - -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - auto parts = line.split('=', Qt::SkipEmptyParts); -#else - auto parts = line.split('=', QString::SkipEmptyParts); -#endif - if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) - { - continue; - } - else - { - results.insert(parts[0], parts[1]); - } - } - - if(!results.contains("os.arch") || !results.contains("java.version") || !results.contains("java.vendor")) - { - // wtf man why - // fallback to using polymc arch - return useQTForArch(); - } - - return results["os.arch"]; - } -} - -QString currentArch(LaunchContext launchContext) { - auto realJavaArchitecture = launchContext.getRealJavaArch().toString(); - if(realJavaArchitecture == ""){ - //BRO WHY NOW I HAVE TO USE A JAVA CHECKER >:( - qDebug() << "SysInfo: BRO I HAVE TO USE A JAVA CHECKER WHY IS JAVA ARCH BORKED"; - launchContext.setRealJavaArch(runCheckerForArch(launchContext)); - realJavaArchitecture = launchContext.getRealJavaArch().toString(); - } - //qDebug() << "SysInfo: realJavaArch = " << realJavaArchitecture; - if(realJavaArchitecture == "aarch64"){ - return "arm64"; - } else { - return realJavaArchitecture; - } -} } diff --git a/launcher/SysInfo.h b/launcher/SysInfo.h index 9107cacb8b..3cb1c8de69 100644 --- a/launcher/SysInfo.h +++ b/launcher/SysInfo.h @@ -1,12 +1,6 @@ #include -#include "minecraft/LaunchContext.h" -#ifdef Q_OS_MACOS -#include -#endif namespace SysInfo { QString currentSystem(); -QString currentArch(LaunchContext launchContext); -QString runCheckerForArch(LaunchContext launchContext); QString useQTForArch(); } diff --git a/launcher/minecraft/LaunchContext.cpp b/launcher/minecraft/LaunchContext.cpp deleted file mode 100644 index ea6a549f0e..0000000000 --- a/launcher/minecraft/LaunchContext.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Toshit Chawda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#include "LaunchContext.h" - -LaunchContext::LaunchContext(SettingsObjectPtr instanceSettings){ - m_instanceSettings = instanceSettings; -} - -void LaunchContext::setRealJavaArch(QVariant realJavaArch) -{ - m_instanceSettings->set("JavaRealArchitecture", realJavaArch); -} - -QVariant LaunchContext::getRealJavaArch() -{ - return m_instanceSettings->get("JavaRealArchitecture"); -} - -QVariant LaunchContext::getJavaPath() -{ - return m_instanceSettings->get("JavaPath"); -} diff --git a/launcher/minecraft/LaunchContext.h b/launcher/minecraft/LaunchContext.h deleted file mode 100644 index 2c6e1307f8..0000000000 --- a/launcher/minecraft/LaunchContext.h +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Toshit Chawda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#include -#include "settings/SettingsObject.h" - -#pragma once - -class LaunchContext -{ - public: - LaunchContext(SettingsObjectPtr instanceSettings); - void setRealJavaArch(QVariant realJavaArch); - QVariant getRealJavaArch(); - QVariant getJavaPath(); - private: - SettingsObjectPtr m_instanceSettings; -}; From 86e3518430917483f35777a25a76159274e72d38 Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 12 Sep 2022 16:34:37 +0200 Subject: [PATCH 0015/2054] Apply most of the suggestions from @flowln Signed-off-by: timoreo --- launcher/JavaDownloader.cpp | 360 ++++++++++++++++++------------------ launcher/JavaDownloader.h | 10 +- 2 files changed, 194 insertions(+), 176 deletions(-) diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp index b0cde7d986..425a7d4705 100644 --- a/launcher/JavaDownloader.cpp +++ b/launcher/JavaDownloader.cpp @@ -9,7 +9,6 @@ #include "SysInfo.h" #include "net/ChecksumValidator.h" #include "net/NetJob.h" -#include "quazip.h" #include "ui/dialogs/ProgressDialog.h" // Quick & dirty struct to store files @@ -25,28 +24,31 @@ void JavaDownloader::executeTask() auto OS = m_OS; auto isLegacy = m_isLegacy; + downloadMojangJavaList(OS, isLegacy); +} +void JavaDownloader::downloadMojangJavaList(const QString& OS, bool isLegacy) +{ auto netJob = new NetJob(QString("JRE::QueryVersions"), APPLICATION->network()); auto response = new QByteArray(); setStatus(tr("Querying mojang meta")); netJob->addNetAction(Net::Download::makeByteArray( QUrl("https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"), response)); - QObject::connect(this, &Task::aborted, [isLegacy]{ - QDir(FS::PathCombine("java",(isLegacy ? "java-legacy" : "java-current"))).removeRecursively(); - }); + connect(this, &Task::aborted, + [isLegacy] { QDir(FS::PathCombine("java", (isLegacy ? "java-legacy" : "java-current"))).removeRecursively(); }); - QObject::connect(netJob, &NetJob::finished, [netJob, response, this] { - //delete so that it's not called on a deleted job - QObject::disconnect(this, &Task::aborted, netJob, &NetJob::abort); + connect(netJob, &NetJob::finished, [netJob, response, this] { + // delete so that it's not called on a deleted job + disconnect(this, &Task::aborted, netJob, &NetJob::abort); netJob->deleteLater(); delete response; }); - QObject::connect(netJob, &NetJob::progress, this, &JavaDownloader::progress); - QObject::connect(netJob, &NetJob::failed, this, &JavaDownloader::emitFailed); + connect(netJob, &NetJob::progress, this, &JavaDownloader::progress); + connect(netJob, &NetJob::failed, this, &JavaDownloader::emitFailed); - QObject::connect(this, &Task::aborted, netJob, &NetJob::abort); + connect(this, &Task::aborted, netJob, &NetJob::abort); - QObject::connect(netJob, &NetJob::succeeded, [response, OS, isLegacy, this, netJob] { + connect(netJob, &NetJob::succeeded, [response, OS, isLegacy, this, netJob] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -56,183 +58,191 @@ void JavaDownloader::executeTask() } auto versionArray = Json::ensureArray(Json::ensureObject(doc.object(), OS), isLegacy ? "jre-legacy" : "java-runtime-gamma"); if (!versionArray.empty()) { - setStatus(tr("Downloading java from Mojang")); - auto url = versionArray[0].toObject()["manifest"].toObject()["url"].toString(); - auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); - auto files = new QByteArray(); + parseMojangManifest(isLegacy, versionArray); + + } else { + // mojang does not have a JRE for us, let's get azul zulu + downloadAzulMeta(OS, isLegacy, netJob); + } + }); + + netJob->start(); +} +void JavaDownloader::parseMojangManifest(bool isLegacy, const QJsonArray& versionArray) +{ + setStatus(tr("Downloading java from Mojang")); + auto url = versionArray[0].toObject()["manifest"].toObject()["url"].toString(); + auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); + auto files = new QByteArray(); - download->addNetAction(Net::Download::makeByteArray(QUrl(url), files)); + download->addNetAction(Net::Download::makeByteArray(QUrl(url), files)); - QObject::connect(download, &NetJob::finished, [download, files, this] { - QObject::disconnect(this, &Task::aborted, download, &NetJob::abort); - download->deleteLater(); - delete files; - }); - QObject::connect(download, &NetJob::progress, this, &JavaDownloader::progress); - QObject::connect(download, &NetJob::failed, this, &JavaDownloader::emitFailed); - QObject::connect(this, &Task::aborted, download, &NetJob::abort); + connect(download, &NetJob::finished, [download, files, this] { + disconnect(this, &Task::aborted, download, &NetJob::abort); + download->deleteLater(); + delete files; + }); + connect(download, &NetJob::progress, this, &JavaDownloader::progress); + connect(download, &NetJob::failed, this, &JavaDownloader::emitFailed); + connect(this, &Task::aborted, download, &NetJob::abort); - QObject::connect(download, &NetJob::succeeded, [download, files, isLegacy, this] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << *files; - return; - } + connect(download, &NetJob::succeeded, [files, isLegacy, this] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << *files; + return; + } + downloadMojangJava(isLegacy, doc); + }); + download->start(); +} +void JavaDownloader::downloadMojangJava(bool isLegacy, const QJsonDocument& doc) +{ // valid json doc, begin making jre spot + auto output = FS::PathCombine(QString("java"), (isLegacy ? "java-legacy" : "java-current")); + FS::ensureFolderPathExists(output); + std::vector toDownload; + auto list = doc.object()["files"].toObject(); + for (const auto& paths : list.keys()) { + auto file = FS::PathCombine(output, paths); - // valid json doc, begin making jre spot - auto output = FS::PathCombine(QString("java"), (isLegacy ? "java-legacy" : "java-current")); - FS::ensureFolderPathExists(output); - std::vector toDownload; - auto list = doc.object()["files"].toObject(); - for (const auto& paths : list.keys()) { - auto file = FS::PathCombine(output, paths); + auto type = list[paths].toObject()["type"].toString(); + if (type == "directory") { + FS::ensureFolderPathExists(file); + } else if (type == "link") { + // this is linux only ! + auto target = FS::PathCombine(file, "../" + list[paths].toObject()["target"].toString()); + QFile(target).link(file); + } else if (type == "file") { + // TODO download compressed version if it exists ? + auto raw = list[paths].toObject()["downloads"].toObject()["raw"].toObject(); + auto isExec = list[paths].toObject()["executable"].toBool(); + auto f = File{ file, raw["url"].toString(), QByteArray::fromHex(raw["sha1"].toString().toLatin1()), isExec }; + toDownload.push_back(f); + } + } + auto elementDownload = new NetJob("JRE::FileDownload", APPLICATION->network()); + for (const auto& file : toDownload) { + auto dl = Net::Download::makeFile(file.url, file.path); + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.hash)); + if (file.isExec) { + connect(dl.get(), &Net::Download::succeeded, + [file] { QFile(file.path).setPermissions(QFile(file.path).permissions() | QFileDevice::Permissions(0x1111)); }); + } + elementDownload->addNetAction(dl); + } + connect(elementDownload, &NetJob::finished, [elementDownload, this] { + disconnect(this, &Task::aborted, elementDownload, &NetJob::abort); + elementDownload->deleteLater(); + }); + connect(elementDownload, &NetJob::progress, this, &JavaDownloader::progress); + connect(elementDownload, &NetJob::failed, this, &JavaDownloader::emitFailed); - auto type = list[paths].toObject()["type"].toString(); - if (type == "directory") { - FS::ensureFolderPathExists(file); - } else if (type == "link") { - // this is linux only ! - auto target = FS::PathCombine(file, "../" + list[paths].toObject()["target"].toString()); - QFile(target).link(file); - } else if (type == "file") { - // TODO download compressed version if it exists ? - auto raw = list[paths].toObject()["downloads"].toObject()["raw"].toObject(); - auto isExec = list[paths].toObject()["executable"].toBool(); - auto f = File{ file, raw["url"].toString(), QByteArray::fromHex(raw["sha1"].toString().toLatin1()), isExec }; - toDownload.push_back(f); - } - } - auto elementDownload = new NetJob("JRE::FileDownload", APPLICATION->network()); - for (const auto& file : toDownload) { - auto dl = Net::Download::makeFile(file.url, file.path); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.hash)); - if (file.isExec) { - QObject::connect(dl.get(), &Net::Download::succeeded, [file] { - QFile(file.path).setPermissions(QFile(file.path).permissions() | QFileDevice::Permissions(0x1111)); - }); - } - elementDownload->addNetAction(dl); - } - QObject::connect(elementDownload, &NetJob::finished, [elementDownload, this] { - QObject::disconnect(this, &Task::aborted, elementDownload, &NetJob::abort); - elementDownload->deleteLater(); - }); - QObject::connect(elementDownload, &NetJob::progress, this, &JavaDownloader::progress); - QObject::connect(elementDownload, &NetJob::failed, this, &JavaDownloader::emitFailed); + connect(this, &Task::aborted, elementDownload, &NetJob::abort); + connect(elementDownload, &NetJob::succeeded, [this] { emitSucceeded(); }); + elementDownload->start(); +} +void JavaDownloader::downloadAzulMeta(const QString& OS, bool isLegacy, const NetJob* netJob) +{ + setStatus(tr("Querying Azul meta")); + QString javaVersion = isLegacy ? QString("8.0") : QString("17.0"); + QString azulOS; + QString arch; + QString bitness; - QObject::connect(this, &Task::aborted, elementDownload, &NetJob::abort); - QObject::connect(elementDownload, &NetJob::succeeded, [this] { emitSucceeded(); }); - elementDownload->start(); - }); - download->start(); + mojangOStoAzul(OS, azulOS, arch, bitness); + auto metaResponse = new QByteArray(); + auto downloadJob = new NetJob(QString("JRE::QueryAzulMeta"), APPLICATION->network()); + downloadJob->addNetAction( + Net::Download::makeByteArray(QString("https://api.azul.com/zulu/download/community/v1.0/bundles/?" + "java_version=%1" + "&os=%2" + "&arch=%3" + "&hw_bitness=%4" + "&ext=zip" // as a zip for all os, even linux NOTE !! Linux ARM is .deb only !! + "&bundle_type=jre" // jre only + "&latest=true" // only get the one latest entry + ) + .arg(javaVersion, azulOS, arch, bitness), + metaResponse)); + connect(downloadJob, &NetJob::finished, [downloadJob, metaResponse, this] { + disconnect(this, &Task::aborted, downloadJob, &NetJob::abort); + downloadJob->deleteLater(); + delete metaResponse; + }); + connect(this, &Task::aborted, downloadJob, &NetJob::abort); + connect(netJob, &NetJob::failed, this, &JavaDownloader::emitFailed); + connect(downloadJob, &NetJob::progress, this, &JavaDownloader::progress); + connect(downloadJob, &NetJob::succeeded, [metaResponse, isLegacy, this] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*metaResponse, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << *metaResponse; + return; + } + auto array = doc.array(); + if (!array.empty()) { + downloadAzulJava(isLegacy, array); } else { - // mojang does not have a JRE for us, let's get azul zulu - setStatus(tr("Querying Azul meta")); - QString javaVersion = isLegacy ? QString("8.0") : QString("17.0"); - QString azulOS; - QString arch; - QString bitness; - - if (OS == "mac-os-arm64") { - // macos arm64 - azulOS = "macos"; - arch = "arm"; - bitness = "64"; - } else if (OS == "linux-arm64") { - // linux arm64 - azulOS = "linux"; - arch = "arm"; - bitness = "64"; - } else if (OS == "linux-arm") { - // linux arm (32) - azulOS = "linux"; - arch = "arm"; - bitness = "32"; - } else if (OS == "linux"){ - // linux x86 64 (used for debugging, should never reach here) - azulOS = "linux"; - arch = "x86"; - bitness = "64"; - } - auto metaResponse = new QByteArray(); - auto downloadJob = new NetJob(QString("JRE::QueryAzulMeta"), APPLICATION->network()); - downloadJob->addNetAction( - Net::Download::makeByteArray(QString("https://api.azul.com/zulu/download/community/v1.0/bundles/?" - "java_version=%1" - "&os=%2" - "&arch=%3" - "&hw_bitness=%4" - "&ext=zip" // as a zip for all os, even linux NOTE !! Linux ARM is .deb only !! - "&bundle_type=jre" // jre only - "&latest=true" // only get the one latest entry - ) - .arg(javaVersion, azulOS, arch, bitness), - metaResponse)); - QObject::connect(downloadJob, &NetJob::finished, [downloadJob, metaResponse, this] { - QObject::disconnect(this, &Task::aborted, downloadJob, &NetJob::abort); - downloadJob->deleteLater(); - delete metaResponse; - }); - QObject::connect(this, &Task::aborted, downloadJob, &NetJob::abort); - QObject::connect(netJob, &NetJob::failed, this, &JavaDownloader::emitFailed); - QObject::connect(downloadJob, &NetJob::progress, this, &JavaDownloader::progress); - QObject::connect(downloadJob, &NetJob::succeeded, [metaResponse, isLegacy, this, downloadJob] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*metaResponse, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << *metaResponse; - return; - } - auto array = doc.array(); - if (!array.empty()) { - // JRE found ! download the zip - setStatus(tr("Downloading java from Azul")); - auto downloadURL = QUrl(array[0].toObject()["url"].toString()); - auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); - auto temp = std::make_unique(FS::PathCombine(APPLICATION->root(), "temp", "XXXXXX.zip")); - FS::ensureFolderPathExists(FS::PathCombine(APPLICATION->root(),"temp")); - // Have to open at least once to generate path - temp->open(); - temp->close(); - download->addNetAction(Net::Download::makeFile(downloadURL, temp->fileName())); - QObject::connect(download, &NetJob::finished, [download, this] { - QObject::disconnect(this, &Task::aborted, download, &NetJob::abort); - download->deleteLater(); - }); - QObject::connect(download, &NetJob::progress, this, &JavaDownloader::progress); - QObject::connect(download, &NetJob::failed, this, &JavaDownloader::emitFailed); - QObject::connect(this, &Task::aborted, download, &NetJob::abort); - QObject::connect(download, &NetJob::succeeded, [isLegacy, file = std::move(temp), downloadURL, this] { - setStatus(tr("Extracting java")); - auto output = FS::PathCombine(FS::PathCombine(QCoreApplication::applicationDirPath(), "java"), - isLegacy ? "java-legacy" : "java-current"); - // This should do all of the extracting and creating folders - MMCZip::extractDir(file->fileName(), downloadURL.fileName().chopped(4), output); - emitSucceeded(); - }); - download->start(); - } else { - emitFailed(tr("No suitable JRE found")); - } - }); - downloadJob->start(); + emitFailed(tr("No suitable JRE found")); } }); - - netJob->start(); + downloadJob->start(); } -void JavaDownloader::abortNetJob(NetJob* elementDownload) +void JavaDownloader::mojangOStoAzul(const QString& OS, QString& azulOS, QString& arch, QString& bitness) { - if(elementDownload->isRunning()){ - elementDownload->abort(); - }else{ - emit elementDownload->aborted(); + if (OS == "mac-os-arm64") { + // macos arm64 + azulOS = "macos"; + arch = "arm"; + bitness = "64"; + } else if (OS == "linux-arm64") { + // linux arm64 + azulOS = "linux"; + arch = "arm"; + bitness = "64"; + } else if (OS == "linux-arm") { + // linux arm (32) + azulOS = "linux"; + arch = "arm"; + bitness = "32"; + } else if (OS == "linux") { + // linux x86 64 (used for debugging, should never reach here) + azulOS = "linux"; + arch = "x86"; + bitness = "64"; } } +void JavaDownloader::downloadAzulJava(bool isLegacy, const QJsonArray& array) +{ // JRE found ! download the zip + setStatus(tr("Downloading java from Azul")); + auto downloadURL = QUrl(array[0].toObject()["url"].toString()); + auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); + auto temp = std::make_unique(FS::PathCombine(APPLICATION->root(), "temp", "XXXXXX.zip")); + FS::ensureFolderPathExists(FS::PathCombine(APPLICATION->root(), "temp")); + // Have to open at least once to generate path + temp->open(); + temp->close(); + download->addNetAction(Net::Download::makeFile(downloadURL, temp->fileName())); + connect(download, &NetJob::finished, [download, this] { + disconnect(this, &Task::aborted, download, &NetJob::abort); + download->deleteLater(); + }); + connect(download, &NetJob::progress, this, &JavaDownloader::progress); + connect(download, &NetJob::failed, this, &JavaDownloader::emitFailed); + connect(this, &Task::aborted, download, &NetJob::abort); + connect(download, &NetJob::succeeded, [isLegacy, file = std::move(temp), downloadURL, this] { + setStatus(tr("Extracting java")); + auto output = FS::PathCombine(QCoreApplication::applicationDirPath(), "java", isLegacy ? "java-legacy" : "java-current"); + // This should do all of the extracting and creating folders + MMCZip::extractDir(file->fileName(), downloadURL.fileName().chopped(4), output); + emitSucceeded(); + }); + download->start(); +} void JavaDownloader::showPrompts(QWidget* parent) { QString sys = SysInfo::currentSystem(); @@ -289,7 +299,7 @@ void JavaDownloader::showPrompts(QWidget* parent) both->setEnabled(false); } if (!yes->isEnabled() && !no->isEnabled()) { - QMessageBox::warning(parent, tr("Already installed !"), tr("Both versions of java are already installed !")); + QMessageBox::warning(parent, tr("Already installed!"), tr("Both versions of java are already installed!")); return; } box.exec(); diff --git a/launcher/JavaDownloader.h b/launcher/JavaDownloader.h index 00ccdf2bc2..6ed67263a1 100644 --- a/launcher/JavaDownloader.h +++ b/launcher/JavaDownloader.h @@ -17,5 +17,13 @@ class JavaDownloader : public Task { private: bool m_isLegacy; const QString& m_OS; - static void abortNetJob(NetJob* elementDownload); + + void downloadMojangJavaList(const QString& OS, bool isLegacy); + void parseMojangManifest(bool isLegacy, const QJsonArray& versionArray); + void downloadMojangJava(bool isLegacy, const QJsonDocument& doc); + + static void mojangOStoAzul(const QString& OS, QString& azulOS, QString& arch, QString& bitness) ; + void downloadAzulMeta(const QString& OS, bool isLegacy, const NetJob* netJob); + void downloadAzulJava(bool isLegacy, const QJsonArray& array); + }; From a65f20a78920efdb974b78f0c72df95d401ad337 Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 12 Sep 2022 17:21:55 +0200 Subject: [PATCH 0016/2054] Fix a tiny formatting issue Co-authored-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Signed-off-by: timoreo --- launcher/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 4bb686106d..c4d9cb74f3 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -271,7 +271,7 @@ set(MINECRAFT_SOURCES minecraft/GradleSpecifier.h minecraft/MinecraftInstance.cpp minecraft/MinecraftInstance.h - minecraft/LaunchProfile.cpp + minecraft/LaunchProfile.cpp minecraft/LaunchProfile.h minecraft/Component.cpp minecraft/Component.h From dcbd6cc1afe3495908511fb9179dd27fe8a70c98 Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 12 Sep 2022 19:31:07 +0200 Subject: [PATCH 0017/2054] More Json:: everywhere ! Signed-off-by: timoreo --- launcher/JavaDownloader.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp index 425a7d4705..d0b8c5240c 100644 --- a/launcher/JavaDownloader.cpp +++ b/launcher/JavaDownloader.cpp @@ -71,7 +71,7 @@ void JavaDownloader::downloadMojangJavaList(const QString& OS, bool isLegacy) void JavaDownloader::parseMojangManifest(bool isLegacy, const QJsonArray& versionArray) { setStatus(tr("Downloading java from Mojang")); - auto url = versionArray[0].toObject()["manifest"].toObject()["url"].toString(); + auto url = Json::ensureString(Json::ensureObject(Json::ensureObject(versionArray[0]), "manifest"), "url"); auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); auto files = new QByteArray(); @@ -103,22 +103,22 @@ void JavaDownloader::downloadMojangJava(bool isLegacy, const QJsonDocument& doc) auto output = FS::PathCombine(QString("java"), (isLegacy ? "java-legacy" : "java-current")); FS::ensureFolderPathExists(output); std::vector toDownload; - auto list = doc.object()["files"].toObject(); + auto list = Json::ensureObject(Json::ensureObject(doc.object()), "files"); for (const auto& paths : list.keys()) { auto file = FS::PathCombine(output, paths); - auto type = list[paths].toObject()["type"].toString(); + auto type = Json::requireString(Json::requireObject(list, paths), "type"); if (type == "directory") { FS::ensureFolderPathExists(file); } else if (type == "link") { // this is linux only ! - auto target = FS::PathCombine(file, "../" + list[paths].toObject()["target"].toString()); + auto target = FS::PathCombine(file, "../" + Json::requireString(Json::requireObject(list, paths), "target")); QFile(target).link(file); } else if (type == "file") { // TODO download compressed version if it exists ? - auto raw = list[paths].toObject()["downloads"].toObject()["raw"].toObject(); - auto isExec = list[paths].toObject()["executable"].toBool(); - auto f = File{ file, raw["url"].toString(), QByteArray::fromHex(raw["sha1"].toString().toLatin1()), isExec }; + auto raw = Json::requireObject(Json::requireObject(Json::requireObject(list, paths), "downloads"), "raw"); + auto isExec = Json::ensureBoolean(Json::requireObject(list, paths), "executable", false); + auto f = File{ file, Json::requireString(raw, "url"), QByteArray::fromHex(Json::ensureString(raw, "sha1").toLatin1()), isExec }; toDownload.push_back(f); } } @@ -183,7 +183,7 @@ void JavaDownloader::downloadAzulMeta(const QString& OS, bool isLegacy, const Ne qWarning() << *metaResponse; return; } - auto array = doc.array(); + auto array = Json::ensureArray(doc.array()); if (!array.empty()) { downloadAzulJava(isLegacy, array); } else { From 6a5db12c6a3350b74eb9cd75a526881bc5b68572 Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 24 Oct 2022 14:55:51 +0200 Subject: [PATCH 0018/2054] Apply some more suggestions from @flowln Co-authored-by: flow Signed-off-by: timoreo --- launcher/JavaDownloader.cpp | 82 +++++++++++-------- .../ui/pages/instance/InstanceSettingsPage.ui | 34 ++++---- 2 files changed, 66 insertions(+), 50 deletions(-) diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp index d0b8c5240c..165573599d 100644 --- a/launcher/JavaDownloader.cpp +++ b/launcher/JavaDownloader.cpp @@ -2,8 +2,10 @@ #include #include #include +#include #include "Application.h" #include "FileSystem.h" +#include "InstanceList.h" #include "Json.h" #include "MMCZip.h" #include "SysInfo.h" @@ -34,8 +36,10 @@ void JavaDownloader::downloadMojangJavaList(const QString& OS, bool isLegacy) netJob->addNetAction(Net::Download::makeByteArray( QUrl("https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"), response)); - connect(this, &Task::aborted, - [isLegacy] { QDir(FS::PathCombine("java", (isLegacy ? "java-legacy" : "java-current"))).removeRecursively(); }); + connect(this, &Task::aborted, [isLegacy] { + QDir(FS::PathCombine(QCoreApplication::applicationDirPath(), "java", (isLegacy ? "java-legacy" : "java-current"))) + .removeRecursively(); + }); connect(netJob, &NetJob::finished, [netJob, response, this] { // delete so that it's not called on a deleted job @@ -70,7 +74,7 @@ void JavaDownloader::downloadMojangJavaList(const QString& OS, bool isLegacy) } void JavaDownloader::parseMojangManifest(bool isLegacy, const QJsonArray& versionArray) { - setStatus(tr("Downloading java from Mojang")); + setStatus(tr("Downloading Java from Mojang")); auto url = Json::ensureString(Json::ensureObject(Json::ensureObject(versionArray[0]), "manifest"), "url"); auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); auto files = new QByteArray(); @@ -100,32 +104,41 @@ void JavaDownloader::parseMojangManifest(bool isLegacy, const QJsonArray& versio } void JavaDownloader::downloadMojangJava(bool isLegacy, const QJsonDocument& doc) { // valid json doc, begin making jre spot - auto output = FS::PathCombine(QString("java"), (isLegacy ? "java-legacy" : "java-current")); + auto output = FS::PathCombine(QCoreApplication::applicationDirPath(), QString("java"), (isLegacy ? "java-legacy" : "java-current")); FS::ensureFolderPathExists(output); std::vector toDownload; auto list = Json::ensureObject(Json::ensureObject(doc.object()), "files"); for (const auto& paths : list.keys()) { auto file = FS::PathCombine(output, paths); - auto type = Json::requireString(Json::requireObject(list, paths), "type"); + const QJsonObject& meta = Json::ensureObject(list, paths); + auto type = Json::ensureString(meta, "type"); if (type == "directory") { FS::ensureFolderPathExists(file); } else if (type == "link") { // this is linux only ! - auto target = FS::PathCombine(file, "../" + Json::requireString(Json::requireObject(list, paths), "target")); - QFile(target).link(file); + auto path = Json::ensureString(meta, "target"); + if (!path.isEmpty()) { + auto target = FS::PathCombine(file, "../" + path); + QFile(target).link(file); + } } else if (type == "file") { // TODO download compressed version if it exists ? - auto raw = Json::requireObject(Json::requireObject(Json::requireObject(list, paths), "downloads"), "raw"); - auto isExec = Json::ensureBoolean(Json::requireObject(list, paths), "executable", false); - auto f = File{ file, Json::requireString(raw, "url"), QByteArray::fromHex(Json::ensureString(raw, "sha1").toLatin1()), isExec }; - toDownload.push_back(f); + auto raw = Json::ensureObject(Json::ensureObject(meta, "downloads"), "raw"); + auto isExec = Json::ensureBoolean(meta, "executable", false); + auto url = Json::ensureString(raw, "url"); + if (!url.isEmpty() && QUrl(url).isValid()) { + auto f = File{ file, url, QByteArray::fromHex(Json::ensureString(raw, "sha1").toLatin1()), isExec }; + toDownload.push_back(f); + } } } auto elementDownload = new NetJob("JRE::FileDownload", APPLICATION->network()); for (const auto& file : toDownload) { auto dl = Net::Download::makeFile(file.url, file.path); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.hash)); + if (!file.hash.isEmpty()) { + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.hash)); + } if (file.isExec) { connect(dl.get(), &Net::Download::succeeded, [file] { QFile(file.path).setPermissions(QFile(file.path).permissions() | QFileDevice::Permissions(0x1111)); }); @@ -161,7 +174,7 @@ void JavaDownloader::downloadAzulMeta(const QString& OS, bool isLegacy, const Ne "&os=%2" "&arch=%3" "&hw_bitness=%4" - "&ext=zip" // as a zip for all os, even linux NOTE !! Linux ARM is .deb only !! + "&ext=zip" // as a zip for all os, even linux NOTE !! Linux ARM is .deb or .tar.gz only !! "&bundle_type=jre" // jre only "&latest=true" // only get the one latest entry ) @@ -218,27 +231,30 @@ void JavaDownloader::mojangOStoAzul(const QString& OS, QString& azulOS, QString& } void JavaDownloader::downloadAzulJava(bool isLegacy, const QJsonArray& array) { // JRE found ! download the zip - setStatus(tr("Downloading java from Azul")); + setStatus(tr("Downloading Java from Azul")); auto downloadURL = QUrl(array[0].toObject()["url"].toString()); auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); - auto temp = std::make_unique(FS::PathCombine(APPLICATION->root(), "temp", "XXXXXX.zip")); - FS::ensureFolderPathExists(FS::PathCombine(APPLICATION->root(), "temp")); - // Have to open at least once to generate path - temp->open(); - temp->close(); - download->addNetAction(Net::Download::makeFile(downloadURL, temp->fileName())); + auto path = APPLICATION->instances()->getStagedInstancePath(); + auto temp = FS::PathCombine(path, "azulJRE.zip"); + + download->addNetAction(Net::Download::makeFile(downloadURL, temp)); connect(download, &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download, &NetJob::abort); download->deleteLater(); }); + connect(download, &NetJob::aborted, [path] { APPLICATION->instances()->destroyStagingPath(path); }); connect(download, &NetJob::progress, this, &JavaDownloader::progress); - connect(download, &NetJob::failed, this, &JavaDownloader::emitFailed); + connect(download, &NetJob::failed, this, [this, path](QString reason) { + APPLICATION->instances()->destroyStagingPath(path); + emitFailed(std::move(reason)); + }); connect(this, &Task::aborted, download, &NetJob::abort); - connect(download, &NetJob::succeeded, [isLegacy, file = std::move(temp), downloadURL, this] { + connect(download, &NetJob::succeeded, [isLegacy, temp, downloadURL, path, this] { setStatus(tr("Extracting java")); auto output = FS::PathCombine(QCoreApplication::applicationDirPath(), "java", isLegacy ? "java-legacy" : "java-current"); // This should do all of the extracting and creating folders - MMCZip::extractDir(file->fileName(), downloadURL.fileName().chopped(4), output); + MMCZip::extractDir(temp, downloadURL.fileName().chopped(4), output); + APPLICATION->instances()->destroyStagingPath(path); emitSucceeded(); }); download->start(); @@ -280,26 +296,27 @@ void JavaDownloader::showPrompts(QWidget* parent) return; } // Selection using QMessageBox for java 8 or 17 - QMessageBox box(QMessageBox::Icon::Question, tr("Java version"), - tr("Do you want to download Java version 8 or 17?\n Java 8 is recommended for minecraft versions below 1.17\n Java 17 " - "is recommended for minecraft versions above or equal to 1.17"), - QMessageBox::NoButton, parent); + QMessageBox box( + QMessageBox::Icon::Question, tr("Java version"), + tr("Do you want to download Java version 8 or 17?\n Java 8 is recommended for older Minecraft versions, below 1.17\n Java 17 " + "is recommended for newer Minecraft versions, starting from 1.17"), + QMessageBox::NoButton, parent); auto yes = box.addButton("Java 17", QMessageBox::AcceptRole); auto no = box.addButton("Java 8", QMessageBox::AcceptRole); auto both = box.addButton(tr("Download both"), QMessageBox::AcceptRole); auto cancel = box.addButton(QMessageBox::Cancel); - if (QFileInfo::exists(FS::PathCombine(QString("java"), "java-legacy"))) { + if (QFileInfo::exists(FS::PathCombine(QCoreApplication::applicationDirPath(), QString("java"), "java-legacy"))) { no->setEnabled(false); } - if (QFileInfo::exists(FS::PathCombine(QString("java"), "java-current"))) { + if (QFileInfo::exists(FS::PathCombine(QCoreApplication::applicationDirPath(), QString("java"), "java-current"))) { yes->setEnabled(false); } if (!yes->isEnabled() || !no->isEnabled()) { both->setEnabled(false); } if (!yes->isEnabled() && !no->isEnabled()) { - QMessageBox::warning(parent, tr("Already installed!"), tr("Both versions of java are already installed!")); + QMessageBox::information(parent, tr("Already installed!"), tr("Both versions of Java are already installed!")); return; } box.exec(); @@ -311,8 +328,9 @@ void JavaDownloader::showPrompts(QWidget* parent) auto down = new JavaDownloader(isLegacy, version); ProgressDialog dialog(parent); dialog.setSkipButton(true, tr("Abort")); - - if (dialog.execWithTask(down) && box.clickedButton() == both) { + bool finished_successfully = dialog.execWithTask(down); + // Run another download task for the other option as well! + if (finished_successfully && box.clickedButton() == both) { auto dwn = new JavaDownloader(false, version); ProgressDialog dg(parent); dg.setSkipButton(true, tr("Abort")); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index 4ce59a0aec..a28332d5af 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -61,9 +61,6 @@ false - - - @@ -71,31 +68,34 @@ - - + + + + If enabled, the launcher will not check if an instance is compatible with the selected Java version. + - Browse... + Skip Java compatibility checks - - + + + + + - Test + Browse... - - - - If enabled, the launcher will not check if an instance is compatible with the selected Java version. - + + - Skip Java compatibility checks + Test - + Download Java... @@ -635,8 +635,6 @@ javaSettingsGroupBox javaPathTextBox javaDetectBtn - javaBrowseBtn - javaTestBtn memoryGroupBox minMemSpinBox maxMemSpinBox From 68b5fdf7489b440b61ca375452b0150bd94675e9 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 15 Jul 2023 00:03:11 +0300 Subject: [PATCH 0019/2054] Ask user to retry if netjob fails Signed-off-by: Trial97 --- launcher/net/NetJob.cpp | 29 +++++++++++++++++++++++++++-- launcher/net/NetJob.h | 1 + 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index 3869316e30..7b5a877889 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -36,6 +36,8 @@ */ #include "NetJob.h" +#include "tasks/ConcurrentTask.h" +#include "ui/dialogs/CustomMessageBox.h" auto NetJob::addNetAction(NetAction::Ptr action) -> bool { @@ -52,8 +54,11 @@ void NetJob::startNext() // We're finished, check for failures and retry if we can (up to 3 times) if (!m_failed.isEmpty() && m_try < 3) { m_try += 1; - while (!m_failed.isEmpty()) - m_queue.enqueue(m_failed.take(*m_failed.keyBegin())); + while (!m_failed.isEmpty()) { + auto task = m_failed.take(*m_failed.keyBegin()); + m_done.remove(task.get()); + m_queue.enqueue(task); + } } } @@ -127,3 +132,23 @@ void NetJob::updateState() setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)") .arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize()))); } + +void NetJob::emitFailed(QString reason) +{ + auto response = CustomMessageBox::selectable(nullptr, "Confirm retry", + "The tasks failed\n" + "Failed urls\n" + + getFailedFiles().join("\n\t") + + "\n" + "If this continues to happen please check the logs of the application" + "Do you want to retry?", + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response == QMessageBox::Yes) { + m_try = 0; + startNext(); + return; + } + ConcurrentTask::emitFailed(reason); +} \ No newline at end of file diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h index 764cec18b3..406ddda63b 100644 --- a/launcher/net/NetJob.h +++ b/launcher/net/NetJob.h @@ -68,6 +68,7 @@ class NetJob : public ConcurrentTask { public slots: // Qt can't handle auto at the start for some reason? bool abort() override; + void emitFailed(QString reason) override; protected: void updateState() override; From 43cc04433d2bccd8c81bae56920bd464cbb27fe8 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 19 Jul 2023 11:58:27 +0300 Subject: [PATCH 0020/2054] feat: refactored Instance ImportTask Signed-off-by: Trial97 --- launcher/InstanceImportTask.cpp | 201 +++++++++---------- launcher/InstanceImportTask.h | 50 ++--- launcher/MMCZip.cpp | 105 ++++++++++ launcher/MMCZip.h | 25 +++ launcher/ui/pages/modplatform/ImportPage.cpp | 1 + 5 files changed, 245 insertions(+), 137 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 352848f020..36c5b8f94a 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -45,14 +45,15 @@ #include "icons/IconList.h" #include "icons/IconUtils.h" -#include "modplatform/technic/TechnicPackProcessor.h" -#include "modplatform/modrinth/ModrinthInstanceCreationTask.h" #include "modplatform/flame/FlameInstanceCreationTask.h" +#include "modplatform/modrinth/ModrinthInstanceCreationTask.h" +#include "modplatform/technic/TechnicPackProcessor.h" #include "settings/INISettingsObject.h" #include #include +#include #include @@ -65,15 +66,8 @@ bool InstanceImportTask::abort() if (!canAbort()) return false; - if (m_filesNetJob) - m_filesNetJob->abort(); - if (m_extractFuture.isRunning()) { - // NOTE: The tasks created by QtConcurrent::run() can't actually get cancelled, - // but we can use this call to check the state when the extraction finishes. - m_extractFuture.cancel(); - m_extractFuture.waitForFinished(); - } - + if (task) + task->abort(); return Task::abort(); } @@ -86,7 +80,6 @@ void InstanceImportTask::executeTask() processZipPack(); } else { setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString())); - m_downloadRequired = true; const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path()); @@ -94,153 +87,153 @@ void InstanceImportTask::executeTask() entry->setStale(true); m_archivePath = entry->getFullPath(); - m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network())); - m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); + auto filesNetJob = makeShared(tr("Modpack download"), APPLICATION->network()); + filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); - connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); - connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); - connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propogateStepProgress); - connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed); - connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted); - - m_filesNetJob->start(); + connect(filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::processZipPack); + connect(filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::setProgress); + connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propogateStepProgress); + connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed); + connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted); + task.reset(filesNetJob); + filesNetJob->start(); } } -void InstanceImportTask::downloadSucceeded() +QString InstanceImportTask::getRootFromZip(QuaZip* zip, const QString& root) { - processZipPack(); - m_filesNetJob.reset(); -} + if (!isRunning()) { + return {}; + } + QuaZipDir rootDir(zip, root); + for (auto&& fileName : rootDir.entryList(QDir::Files)) { + setDetails(fileName); + if (fileName == "instance.cfg") { + qDebug() << "MultiMC:" << true; + m_modpackType = ModpackType::MultiMC; + return root; + } + if (fileName == "manifest.json") { + qDebug() << "Flame:" << true; + m_modpackType = ModpackType::Flame; + return root; + } -void InstanceImportTask::downloadFailed(QString reason) -{ - emitFailed(reason); - m_filesNetJob.reset(); -} + QCoreApplication::processEvents(); + } -void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total) -{ - setProgress(current, total); -} + // Recurse the search to non-ignored subfolders + for (auto&& fileName : rootDir.entryList(QDir::Dirs)) { + if ("overrides/" == fileName) + continue; -void InstanceImportTask::downloadAborted() -{ - emitAborted(); - m_filesNetJob.reset(); + QString result = getRootFromZip(zip, root + fileName); + if (!result.isEmpty()) + return result; + } + + return {}; } void InstanceImportTask::processZipPack() { - setStatus(tr("Extracting modpack")); + setStatus(tr("Attempting to determine instance type")); QDir extractDir(m_stagingPath); qDebug() << "Attempting to create instance from" << m_archivePath; // open the zip and find relevant files in it - m_packZip.reset(new QuaZip(m_archivePath)); - if (!m_packZip->open(QuaZip::mdUnzip)) - { + auto packZip = std::make_shared(m_archivePath); + if (!packZip->open(QuaZip::mdUnzip)) { emitFailed(tr("Unable to open supplied modpack zip file.")); return; } - QuaZipDir packZipDir(m_packZip.get()); + QuaZipDir packZipDir(packZip.get()); + qDebug() << "Attempting to determine instance type"; - // https://docs.modrinth.com/docs/modpacks/format_definition/#storage - bool modrinthFound = packZipDir.exists("/modrinth.index.json"); - bool technicFound = packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json"); QString root; // NOTE: Prioritize modpack platforms that aren't searched for recursively. // Especially Flame has a very common filename for its manifest, which may appear inside overrides for example - if(modrinthFound) - { + // https://docs.modrinth.com/docs/modpacks/format_definition/#storage + if (packZipDir.exists("/modrinth.index.json")) { // process as Modrinth pack - qDebug() << "Modrinth:" << modrinthFound; + qDebug() << "Modrinth:" << true; m_modpackType = ModpackType::Modrinth; - } - else if (technicFound) - { + } else if (packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json")) { // process as Technic pack - qDebug() << "Technic:" << technicFound; + qDebug() << "Technic:" << true; extractDir.mkpath(".minecraft"); extractDir.cd(".minecraft"); m_modpackType = ModpackType::Technic; + } else { + root = getRootFromZip(packZip.get()); + setDetails(""); } - else - { - QStringList paths_to_ignore { "overrides/" }; - - if (QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg", paths_to_ignore); !mmcRoot.isNull()) { - // process as MultiMC instance/pack - qDebug() << "MultiMC:" << mmcRoot; - root = mmcRoot; - m_modpackType = ModpackType::MultiMC; - } else if (QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json", paths_to_ignore); !flameRoot.isNull()) { - // process as Flame pack - qDebug() << "Flame:" << flameRoot; - root = flameRoot; - m_modpackType = ModpackType::Flame; - } - } - if(m_modpackType == ModpackType::Unknown) - { + if (m_modpackType == ModpackType::Unknown) { emitFailed(tr("Archive does not contain a recognized modpack type.")); return; } + setStatus(tr("Extracting modpack")); // make sure we extract just the pack - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath()); - connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &InstanceImportTask::extractFinished); - m_extractFutureWatcher.setFuture(m_extractFuture); + auto zipTask = makeShared(packZip, extractDir, root); + + auto progressStep = std::make_shared(); + connect(zipTask.get(), &Task::finished, this, [this, progressStep] { + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + }); + + connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished); + connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted); + connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) { + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + emitFailed(reason); + }); + connect(zipTask.get(), &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress); + + connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { + progressStep->update(current, total); + stepProgress(*progressStep); + }); + connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) { + progressStep->status = status; + stepProgress(*progressStep); + }); + task.reset(zipTask); + zipTask->start(); } void InstanceImportTask::extractFinished() { - m_packZip.reset(); - - if (m_extractFuture.isCanceled()) - return; - if (!m_extractFuture.result().has_value()) { - emitFailed(tr("Failed to extract modpack")); - return; - } - QDir extractDir(m_stagingPath); qDebug() << "Fixing permissions for extracted pack files..."; QDirIterator it(extractDir, QDirIterator::Subdirectories); - while (it.hasNext()) - { + while (it.hasNext()) { auto filepath = it.next(); QFileInfo file(filepath); auto permissions = QFile::permissions(filepath); auto origPermissions = permissions; - if(file.isDir()) - { + if (file.isDir()) { // Folder +rwx for current user permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser; - } - else - { + } else { // File +rw for current user permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; } - if(origPermissions != permissions) - { - if(!QFile::setPermissions(filepath, permissions)) - { + if (origPermissions != permissions) { + if (!QFile::setPermissions(filepath, permissions)) { logWarning(tr("Could not fix permissions for %1").arg(filepath)); - } - else - { + } else { qDebug() << "Fixed" << filepath; } } } - switch(m_modpackType) - { + switch (m_modpackType) { case ModpackType::MultiMC: processMultiMC(); return; @@ -276,7 +269,8 @@ void InstanceImportTask::processFlame() if (original_instance_id_it != m_extra_info.constEnd()) original_instance_id = original_instance_id_it.value(); - inst_creation_task = makeShared(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); + inst_creation_task = + makeShared(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); } else { // FIXME: Find a way to get IDs in directly imported ZIPs inst_creation_task = makeShared(m_stagingPath, m_globalSettings, m_parent, QString(), QString()); @@ -286,7 +280,7 @@ void InstanceImportTask::processFlame() inst_creation_task->setIcon(m_instIcon); inst_creation_task->setGroup(m_instGroup); inst_creation_task->setConfirmUpdate(shouldConfirmUpdate()); - + connect(inst_creation_task.get(), &Task::succeeded, this, [this, inst_creation_task] { setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID()); emitSucceeded(); @@ -362,7 +356,8 @@ void InstanceImportTask::processModrinth() if (original_instance_id_it != m_extra_info.constEnd()) original_instance_id = original_instance_id_it.value(); - inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); + inst_creation_task = + new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); } else { QString pack_id; if (!m_sourceUrl.isEmpty()) { @@ -378,7 +373,7 @@ void InstanceImportTask::processModrinth() inst_creation_task->setIcon(m_instIcon); inst_creation_task->setGroup(m_instGroup); inst_creation_task->setConfirmUpdate(shouldConfirmUpdate()); - + connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] { setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID()); emitSucceeded(); diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index 7fda439fcc..cf4025194c 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -35,64 +35,46 @@ #pragma once -#include "InstanceTask.h" -#include "net/NetJob.h" -#include #include #include -#include "settings/SettingsObject.h" -#include "QObjectPtr.h" -#include "modplatform/flame/PackManifest.h" +#include +#include "InstanceTask.h" +#include #include class QuaZip; -namespace Flame -{ - class FileResolvingTask; +namespace Flame { +class FileResolvingTask; } -class InstanceImportTask : public InstanceTask -{ +class InstanceImportTask : public InstanceTask { Q_OBJECT -public: + public: explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr, QMap&& extra_info = {}); bool abort() override; - const QVector &getBlockedFiles() const - { - return m_blockedMods; - } -protected: + protected: //! Entry point for tasks. virtual void executeTask() override; -private: - void processZipPack(); + private: void processMultiMC(); void processTechnic(); void processFlame(); void processModrinth(); + QString getRootFromZip(QuaZip* zip, const QString& root = ""); -private slots: - void downloadSucceeded(); - void downloadFailed(QString reason); - void downloadProgressChanged(qint64 current, qint64 total); - void downloadAborted(); + private slots: + void processZipPack(); void extractFinished(); -private: /* data */ - NetJob::Ptr m_filesNetJob; - shared_qobject_ptr m_modIdResolver; + private: /* data */ QUrl m_sourceUrl; QString m_archivePath; - bool m_downloadRequired = false; - std::unique_ptr m_packZip; - QFuture> m_extractFuture; - QFutureWatcher> m_extractFutureWatcher; - QVector m_blockedMods; - enum class ModpackType{ + Task::Ptr task; + enum class ModpackType { Unknown, MultiMC, Technic, @@ -104,6 +86,6 @@ private slots: // the source URL / the resource it points to alone. QMap m_extra_info; - //FIXME: nuke + // FIXME: nuke QWidget* m_parent; }; diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index acd6bf7e42..13ad96e4ea 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -501,4 +501,109 @@ bool ExportToZipTask::abort() return false; } +void ExtractZipTask::executeTask() +{ + m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); }); + connect(&m_zip_watcher, &QFutureWatcher::finished, this, &ExtractZipTask::finish); + m_zip_watcher.setFuture(m_zip_future); +} + +auto ExtractZipTask::extractZip() -> ZipResult +{ + auto target = m_output_dir.absolutePath(); + auto target_top_dir = QUrl::fromLocalFile(target); + + QStringList extracted; + + qDebug() << "Extracting subdir" << m_subdirectory << "from" << m_input->getZipName() << "to" << target; + auto numEntries = m_input->getEntriesCount(); + if (numEntries < 0) { + return ZipResult(tr("Failed to enumerate files in archive")); + } + if (numEntries == 0) { + logWarning(tr("Extracting empty archives seems odd...")); + return ZipResult(); + } + if (!m_input->goToFirstFile()) { + return ZipResult(tr("Failed to seek to first file in zip")); + } + + setStatus("Extracting files..."); + setProgress(0, numEntries); + do { + if (m_zip_future.isCanceled()) + return ZipResult(); + setProgress(m_progress + 1, m_progressTotal); + QString file_name = m_input->getCurrentFileName(); + if (!file_name.startsWith(m_subdirectory)) + continue; + + auto relative_file_name = QDir::fromNativeSeparators(file_name.remove(0, m_subdirectory.size())); + auto original_name = relative_file_name; + setStatus("Unziping: " + relative_file_name); + + // Fix subdirs/files ending with a / getting transformed into absolute paths + if (relative_file_name.startsWith('/')) + relative_file_name = relative_file_name.mid(1); + + // Fix weird "folders with a single file get squashed" thing + QString sub_path; + if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) { + sub_path = relative_file_name.section('/', 0, -2) + '/'; + FS::ensureFolderPathExists(FS::PathCombine(target, sub_path)); + + relative_file_name = relative_file_name.split('/').last(); + } + + QString target_file_path; + if (relative_file_name.isEmpty()) { + target_file_path = target + '/'; + } else { + target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name); + if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/')) + target_file_path += '/'; + } + + if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) { + return ZipResult(tr("Extracting %1 was cancelled, because it was effectively outside of the target path %2") + .arg(relative_file_name, target)); + } + + if (!JlCompress::extractFile(m_input.get(), "", target_file_path)) { + JlCompress::removeFile(extracted); + return ZipResult(tr("Failed to extract file %1 to %2").arg(original_name, target_file_path)); + } + + extracted.append(target_file_path); + QFile::setPermissions(target_file_path, + QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser); + + qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; + } while (m_input->goToNextFile()); + + return ZipResult(); +} + +void ExtractZipTask::finish() +{ + if (m_zip_future.isCanceled()) { + emitAborted(); + } else if (auto result = m_zip_future.result(); result.has_value()) { + emitFailed(result.value()); + } else { + emitSucceeded(); + } +} + +bool ExtractZipTask::abort() +{ + if (m_zip_future.isRunning()) { + m_zip_future.cancel(); + // NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur + // immediately. + return true; + } + return false; +} + } // namespace MMCZip \ No newline at end of file diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index bc527ad1bb..212be6f516 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -189,4 +189,29 @@ class ExportToZipTask : public Task { QFuture m_build_zip_future; QFutureWatcher m_build_zip_watcher; }; + +class ExtractZipTask : public Task { + public: + ExtractZipTask(std::shared_ptr input, QDir outputDir, QString subdirectory = "") + : m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory) + {} + virtual ~ExtractZipTask() = default; + + typedef std::optional ZipResult; + + protected: + virtual void executeTask() override; + bool abort() override; + + ZipResult extractZip(); + void finish(); + + private: + std::shared_ptr m_input; + QDir m_output_dir; + QString m_subdirectory; + + QFuture m_zip_future; + QFutureWatcher m_zip_watcher; +}; } // namespace MMCZip diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 30196aad62..a45f3a81c9 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -38,6 +38,7 @@ #include "ui_ImportPage.h" #include +#include #include #include "ui/dialogs/NewInstanceDialog.h" From ce6a36c8b57fb9eb0a22c4f70b74e8fa200a88ad Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 28 Jul 2023 14:31:53 +0100 Subject: [PATCH 0021/2054] Deduplicate fix Signed-off-by: TheKodeToad --- launcher/MMCZip.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 49fd0e707d..1a336375b0 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -315,11 +315,6 @@ std::optional MMCZip::extractSubDir(QuaZip *zip, const QString & su if (relative_file_name.startsWith('/')) relative_file_name = relative_file_name.mid(1); - // Fix subdirs/files ending with a / getting transformed into absolute paths - if(name.startsWith('/')){ - name = name.mid(1); - } - // Fix weird "folders with a single file get squashed" thing QString sub_path; if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) { From 5538c1d0afba06d1a639dca45e88b0e94a3742f0 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 28 Jul 2023 15:11:24 +0100 Subject: [PATCH 0022/2054] Fix compilation (actually this time!), incorporating new changes Signed-off-by: TheKodeToad --- launcher/JavaDownloader.cpp | 55 +++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp index 165573599d..f5960bbe93 100644 --- a/launcher/JavaDownloader.cpp +++ b/launcher/JavaDownloader.cpp @@ -30,8 +30,8 @@ void JavaDownloader::executeTask() } void JavaDownloader::downloadMojangJavaList(const QString& OS, bool isLegacy) { - auto netJob = new NetJob(QString("JRE::QueryVersions"), APPLICATION->network()); - auto response = new QByteArray(); + auto netJob = makeShared(QString("JRE::QueryVersions"), APPLICATION->network()); + auto response = std::make_shared(); setStatus(tr("Querying mojang meta")); netJob->addNetAction(Net::Download::makeByteArray( QUrl("https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"), response)); @@ -41,18 +41,17 @@ void JavaDownloader::downloadMojangJavaList(const QString& OS, bool isLegacy) .removeRecursively(); }); - connect(netJob, &NetJob::finished, [netJob, response, this] { + connect(netJob.get(), &NetJob::finished, [netJob, response, this] { // delete so that it's not called on a deleted job - disconnect(this, &Task::aborted, netJob, &NetJob::abort); - netJob->deleteLater(); - delete response; + // FIXME: is this needed? qt should handle this + disconnect(this, &Task::aborted, netJob.get(), &NetJob::abort); }); - connect(netJob, &NetJob::progress, this, &JavaDownloader::progress); - connect(netJob, &NetJob::failed, this, &JavaDownloader::emitFailed); + connect(netJob.get(), &NetJob::progress, this, &JavaDownloader::progress); + connect(netJob.get(), &NetJob::failed, this, &JavaDownloader::emitFailed); - connect(this, &Task::aborted, netJob, &NetJob::abort); + connect(this, &Task::aborted, netJob.get(), &NetJob::abort); - connect(netJob, &NetJob::succeeded, [response, OS, isLegacy, this, netJob] { + connect(netJob.get(), &NetJob::succeeded, [response, OS, isLegacy, this, netJob] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -66,7 +65,7 @@ void JavaDownloader::downloadMojangJavaList(const QString& OS, bool isLegacy) } else { // mojang does not have a JRE for us, let's get azul zulu - downloadAzulMeta(OS, isLegacy, netJob); + downloadAzulMeta(OS, isLegacy, netJob.get()); } }); @@ -76,21 +75,19 @@ void JavaDownloader::parseMojangManifest(bool isLegacy, const QJsonArray& versio { setStatus(tr("Downloading Java from Mojang")); auto url = Json::ensureString(Json::ensureObject(Json::ensureObject(versionArray[0]), "manifest"), "url"); - auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); - auto files = new QByteArray(); + auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); + auto files = std::make_shared(); download->addNetAction(Net::Download::makeByteArray(QUrl(url), files)); - connect(download, &NetJob::finished, [download, files, this] { - disconnect(this, &Task::aborted, download, &NetJob::abort); - download->deleteLater(); - delete files; + connect(download.get(), &NetJob::finished, [download, files, this] { + disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); - connect(download, &NetJob::progress, this, &JavaDownloader::progress); - connect(download, &NetJob::failed, this, &JavaDownloader::emitFailed); - connect(this, &Task::aborted, download, &NetJob::abort); + connect(download.get(), &NetJob::progress, this, &JavaDownloader::progress); + connect(download.get(), &NetJob::failed, this, &JavaDownloader::emitFailed); + connect(this, &Task::aborted, download.get(), &NetJob::abort); - connect(download, &NetJob::succeeded, [files, isLegacy, this] { + connect(download.get(), &NetJob::succeeded, [files, isLegacy, this] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -166,8 +163,8 @@ void JavaDownloader::downloadAzulMeta(const QString& OS, bool isLegacy, const Ne QString bitness; mojangOStoAzul(OS, azulOS, arch, bitness); - auto metaResponse = new QByteArray(); - auto downloadJob = new NetJob(QString("JRE::QueryAzulMeta"), APPLICATION->network()); + auto metaResponse = std::make_shared(); + auto downloadJob = makeShared(QString("JRE::QueryAzulMeta"), APPLICATION->network()); downloadJob->addNetAction( Net::Download::makeByteArray(QString("https://api.azul.com/zulu/download/community/v1.0/bundles/?" "java_version=%1" @@ -180,15 +177,13 @@ void JavaDownloader::downloadAzulMeta(const QString& OS, bool isLegacy, const Ne ) .arg(javaVersion, azulOS, arch, bitness), metaResponse)); - connect(downloadJob, &NetJob::finished, [downloadJob, metaResponse, this] { - disconnect(this, &Task::aborted, downloadJob, &NetJob::abort); - downloadJob->deleteLater(); - delete metaResponse; + connect(downloadJob.get(), &NetJob::finished, [downloadJob, metaResponse, this] { + disconnect(this, &Task::aborted, downloadJob.get(), &NetJob::abort); }); - connect(this, &Task::aborted, downloadJob, &NetJob::abort); + connect(this, &Task::aborted, downloadJob.get(), &NetJob::abort); connect(netJob, &NetJob::failed, this, &JavaDownloader::emitFailed); - connect(downloadJob, &NetJob::progress, this, &JavaDownloader::progress); - connect(downloadJob, &NetJob::succeeded, [metaResponse, isLegacy, this] { + connect(downloadJob.get(), &NetJob::progress, this, &JavaDownloader::progress); + connect(downloadJob.get(), &NetJob::succeeded, [metaResponse, isLegacy, this] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*metaResponse, &parse_error); if (parse_error.error != QJsonParseError::NoError) { From 748a644e2cf173540a60b7fc5815c89f02382cfd Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 15 Aug 2023 18:50:32 +0300 Subject: [PATCH 0023/2054] added wait profiler Signed-off-by: Trial97 --- launcher/Application.cpp | 2 ++ launcher/CMakeLists.txt | 2 ++ launcher/tools/WaitProfiler.cpp | 44 +++++++++++++++++++++++++++++++++ launcher/tools/WaitProfiler.h | 29 ++++++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 launcher/tools/WaitProfiler.cpp create mode 100644 launcher/tools/WaitProfiler.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index a139351016..a546d0a854 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -49,6 +49,7 @@ #include "pathmatcher/MultiMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h" #include "settings/INIFile.h" +#include "tools/WaitProfiler.h" #include "ui/InstanceWindow.h" #include "ui/MainWindow.h" @@ -816,6 +817,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // FIXME: what to do with these? m_profilers.insert("jprofiler", std::shared_ptr(new JProfilerFactory())); m_profilers.insert("jvisualvm", std::shared_ptr(new JVisualVMFactory())); + m_profilers.insert("waitprofiler", std::shared_ptr(new WaitProfilerFactory())); for (auto profiler : m_profilers.values()) { profiler->registerSettings(m_settings); } diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 18e0acab10..28359a4b17 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -457,6 +457,8 @@ set(TOOLS_SOURCES tools/JVisualVM.h tools/MCEditTool.cpp tools/MCEditTool.h + tools/WaitProfiler.cpp + tools/WaitProfiler.h ) set(META_SOURCES diff --git a/launcher/tools/WaitProfiler.cpp b/launcher/tools/WaitProfiler.cpp new file mode 100644 index 0000000000..c43af0a89b --- /dev/null +++ b/launcher/tools/WaitProfiler.cpp @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "WaitProfiler.h" + +#include "BaseInstance.h" +#include "launch/LaunchTask.h" +#include "settings/SettingsObject.h" + +class WaitProfiler : public BaseProfiler { + Q_OBJECT + public: + WaitProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject* parent = 0); + + protected: + void beginProfilingImpl(shared_qobject_ptr process); +}; + +WaitProfiler::WaitProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject* parent) : BaseProfiler(settings, instance, parent) {} + +void WaitProfiler::beginProfilingImpl(shared_qobject_ptr process) +{ + emit readyToLaunch(tr("Started process: %1").arg(process->pid())); +} + +BaseExternalTool* WaitProfilerFactory::createTool(InstancePtr instance, QObject* parent) +{ + return new WaitProfiler(globalSettings, instance, parent); +} +#include "WaitProfiler.moc" \ No newline at end of file diff --git a/launcher/tools/WaitProfiler.h b/launcher/tools/WaitProfiler.h new file mode 100644 index 0000000000..ff2b9c2bfc --- /dev/null +++ b/launcher/tools/WaitProfiler.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "BaseProfiler.h" + +class WaitProfilerFactory : public BaseProfilerFactory { + public: + QString name() const override { return "WaitProfiler"; } + void registerSettings(SettingsObjectPtr settings) override{}; + BaseExternalTool* createTool(InstancePtr instance, QObject* parent = 0) override; + bool check(QString* error) override { return true; }; + bool check(const QString& path, QString* error) override { return true; }; +}; From c28d9161cfc52aa21f1217d36032eb4e174b215c Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 15 Aug 2023 18:54:22 +0300 Subject: [PATCH 0024/2054] added unused Signed-off-by: Trial97 --- launcher/tools/WaitProfiler.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/tools/WaitProfiler.h b/launcher/tools/WaitProfiler.h index ff2b9c2bfc..fe813402c1 100644 --- a/launcher/tools/WaitProfiler.h +++ b/launcher/tools/WaitProfiler.h @@ -22,8 +22,8 @@ class WaitProfilerFactory : public BaseProfilerFactory { public: QString name() const override { return "WaitProfiler"; } - void registerSettings(SettingsObjectPtr settings) override{}; + void registerSettings([[maybe_unused]] SettingsObjectPtr settings) override{}; BaseExternalTool* createTool(InstancePtr instance, QObject* parent = 0) override; - bool check(QString* error) override { return true; }; - bool check(const QString& path, QString* error) override { return true; }; + bool check([[maybe_unused]] QString* error) override { return true; }; + bool check([[maybe_unused]] const QString& path, [[maybe_unused]] QString* error) override { return true; }; }; From f7f7bc68653a3bfbfd9a571624582d45ef5e224b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 15 Aug 2023 20:52:17 +0300 Subject: [PATCH 0025/2054] Removed update disabled warning Signed-off-by: Trial97 --- launcher/minecraft/mod/ModFolderModel.cpp | 5 ++++- launcher/modplatform/CheckUpdateTask.h | 11 ++++++++++- launcher/modplatform/flame/FlameCheckUpdate.cpp | 7 +------ launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp | 7 +------ launcher/ui/dialogs/ModUpdateDialog.cpp | 4 +--- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 280e70d7be..1b96590a49 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -196,7 +196,10 @@ Task* ModFolderModel::createParseTask(Resource& resource) bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata) { for (auto mod : allMods()) { - if (mod->fileinfo().fileName() == filename) { + auto modfilename = mod->fileinfo().fileName(); + if (!mod->enabled() && modfilename.endsWith(".disabled")) + modfilename.chop(9); + if (modfilename == filename) { auto index_dir = indexDir(); mod->destroy(index_dir, preserve_metadata, false); diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index 6d968ea487..6afa9c2902 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -24,6 +24,7 @@ class CheckUpdateTask : public Task { QString old_version; QString new_version; QString changelog; + bool enabled; ModPlatform::ResourceProvider provider; shared_qobject_ptr download; @@ -33,9 +34,17 @@ class CheckUpdateTask : public Task { QString old_v, QString new_v, QString changelog, + bool enabled, ModPlatform::ResourceProvider p, shared_qobject_ptr t) - : name(name), old_hash(old_h), old_version(old_v), new_version(new_v), changelog(changelog), provider(p), download(t) + : name(name) + , old_hash(old_h) + , old_version(old_v) + , new_version(new_v) + , changelog(changelog) + , enabled(enabled) + , provider(p) + , download(t) {} }; diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index e10fedc3c4..79ae82e69d 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -124,11 +124,6 @@ void FlameCheckUpdate::executeTask() int i = 0; for (auto* mod : m_mods) { - if (!mod->enabled()) { - emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!")); - continue; - } - setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name())); setProgress(i++, m_mods.size()); @@ -176,7 +171,7 @@ void FlameCheckUpdate::executeTask() auto download_task = makeShared(pack, latest_ver, m_mods_folder); m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver.version, - api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), + api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), mod->enabled(), ModPlatform::ResourceProvider::FLAME, download_task); } } diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index a7c22832a5..41eac64150 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -41,11 +41,6 @@ void ModrinthCheckUpdate::executeTask() ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", 10); for (auto* mod : m_mods) { - if (!mod->enabled()) { - emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!")); - continue; - } - auto hash = mod->metadata()->hash; // Sadly the API can only handle one hash type per call, se we @@ -163,7 +158,7 @@ void ModrinthCheckUpdate::executeTask() auto download_task = makeShared(pack, project_ver, m_mods_folder); m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.changelog, - ModPlatform::ResourceProvider::MODRINTH, download_task); + mod->enabled(), ModPlatform::ResourceProvider::MODRINTH, download_task); } } } catch (Json::JsonException& e) { diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index 0af1ec59bc..639887a5d0 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -5,8 +5,6 @@ #include "ScrollMessageBox.h" #include "ui_ReviewMessageBox.h" -#include "FileSystem.h" -#include "Json.h" #include "Markdown.h" #include "tasks/ConcurrentTask.h" @@ -351,7 +349,7 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::R void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info) { auto item_top = new QTreeWidgetItem(ui->modTreeWidget); - item_top->setCheckState(0, Qt::CheckState::Checked); + item_top->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); item_top->setText(0, info.name); item_top->setExpanded(true); From fa164aa0813687eed862fbd781aab47288d9bb8f Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 15 Aug 2023 22:18:28 +0300 Subject: [PATCH 0026/2054] rename to GenericProfiler Signed-off-by: Trial97 --- launcher/Application.cpp | 4 ++-- launcher/CMakeLists.txt | 4 ++-- .../{WaitProfiler.cpp => GenericProfiler.cpp} | 18 ++++++++++-------- .../{WaitProfiler.h => GenericProfiler.h} | 4 ++-- 4 files changed, 16 insertions(+), 14 deletions(-) rename launcher/tools/{WaitProfiler.cpp => GenericProfiler.cpp} (63%) rename launcher/tools/{WaitProfiler.h => GenericProfiler.h} (90%) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index a546d0a854..7f03b4d10b 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -49,7 +49,7 @@ #include "pathmatcher/MultiMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h" #include "settings/INIFile.h" -#include "tools/WaitProfiler.h" +#include "tools/GenericProfiler.h" #include "ui/InstanceWindow.h" #include "ui/MainWindow.h" @@ -817,7 +817,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // FIXME: what to do with these? m_profilers.insert("jprofiler", std::shared_ptr(new JProfilerFactory())); m_profilers.insert("jvisualvm", std::shared_ptr(new JVisualVMFactory())); - m_profilers.insert("waitprofiler", std::shared_ptr(new WaitProfilerFactory())); + m_profilers.insert("generic", std::shared_ptr(new GenericProfilerFactory())); for (auto profiler : m_profilers.values()) { profiler->registerSettings(m_settings); } diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 28359a4b17..a14f279e78 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -457,8 +457,8 @@ set(TOOLS_SOURCES tools/JVisualVM.h tools/MCEditTool.cpp tools/MCEditTool.h - tools/WaitProfiler.cpp - tools/WaitProfiler.h + tools/GenericProfiler.cpp + tools/GenericProfiler.h ) set(META_SOURCES diff --git a/launcher/tools/WaitProfiler.cpp b/launcher/tools/GenericProfiler.cpp similarity index 63% rename from launcher/tools/WaitProfiler.cpp rename to launcher/tools/GenericProfiler.cpp index c43af0a89b..594024a7d7 100644 --- a/launcher/tools/WaitProfiler.cpp +++ b/launcher/tools/GenericProfiler.cpp @@ -15,30 +15,32 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "WaitProfiler.h" +#include "GenericProfiler.h" #include "BaseInstance.h" #include "launch/LaunchTask.h" #include "settings/SettingsObject.h" -class WaitProfiler : public BaseProfiler { +class GenericProfiler : public BaseProfiler { Q_OBJECT public: - WaitProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject* parent = 0); + GenericProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject* parent = 0); protected: void beginProfilingImpl(shared_qobject_ptr process); }; -WaitProfiler::WaitProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject* parent) : BaseProfiler(settings, instance, parent) {} +GenericProfiler::GenericProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject* parent) + : BaseProfiler(settings, instance, parent) +{} -void WaitProfiler::beginProfilingImpl(shared_qobject_ptr process) +void GenericProfiler::beginProfilingImpl(shared_qobject_ptr process) { emit readyToLaunch(tr("Started process: %1").arg(process->pid())); } -BaseExternalTool* WaitProfilerFactory::createTool(InstancePtr instance, QObject* parent) +BaseExternalTool* GenericProfilerFactory::createTool(InstancePtr instance, QObject* parent) { - return new WaitProfiler(globalSettings, instance, parent); + return new GenericProfiler(globalSettings, instance, parent); } -#include "WaitProfiler.moc" \ No newline at end of file +#include "GenericProfiler.moc" \ No newline at end of file diff --git a/launcher/tools/WaitProfiler.h b/launcher/tools/GenericProfiler.h similarity index 90% rename from launcher/tools/WaitProfiler.h rename to launcher/tools/GenericProfiler.h index fe813402c1..e99fc059f3 100644 --- a/launcher/tools/WaitProfiler.h +++ b/launcher/tools/GenericProfiler.h @@ -19,9 +19,9 @@ #include "BaseProfiler.h" -class WaitProfilerFactory : public BaseProfilerFactory { +class GenericProfilerFactory : public BaseProfilerFactory { public: - QString name() const override { return "WaitProfiler"; } + QString name() const override { return "Generic"; } void registerSettings([[maybe_unused]] SettingsObjectPtr settings) override{}; BaseExternalTool* createTool(InstancePtr instance, QObject* parent = 0) override; bool check([[maybe_unused]] QString* error) override { return true; }; From c41c39b5d86b168a189a82460fc6edb7873cfa6e Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 15 Aug 2023 23:55:09 +0300 Subject: [PATCH 0027/2054] cleanup the mod name checking Signed-off-by: Trial97 --- launcher/minecraft/mod/ModFolderModel.cpp | 5 +---- launcher/minecraft/mod/Resource.cpp | 8 ++++++++ launcher/minecraft/mod/Resource.h | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 1b96590a49..1f10a5a2fa 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -196,10 +196,7 @@ Task* ModFolderModel::createParseTask(Resource& resource) bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata) { for (auto mod : allMods()) { - auto modfilename = mod->fileinfo().fileName(); - if (!mod->enabled() && modfilename.endsWith(".disabled")) - modfilename.chop(9); - if (modfilename == filename) { + if (mod->getOriginalFileName() == filename) { auto index_dir = indexDir(); mod->destroy(index_dir, preserve_metadata, false); diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index da806f0f46..6a60473fbf 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -169,3 +169,11 @@ bool Resource::isMoreThanOneHardLink() const { return FS::hardLinkCount(m_file_info.absoluteFilePath()) > 1; } + +auto Resource::getOriginalFileName() const -> QString +{ + auto fileName = m_file_info.fileName(); + if (!m_enabled) + fileName.chop(9); + return fileName; +} \ No newline at end of file diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index c1ed49461e..f0d34705cc 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -45,6 +45,7 @@ class Resource : public QObject { [[nodiscard]] auto internal_id() const -> QString { return m_internal_id; } [[nodiscard]] auto type() const -> ResourceType { return m_type; } [[nodiscard]] bool enabled() const { return m_enabled; } + [[nodiscard]] auto getOriginalFileName() const -> QString; [[nodiscard]] virtual auto name() const -> QString { return m_name; } [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; } From f3baa420b2154c37a8f418a090c4392d526482c2 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 16 Aug 2023 13:43:07 +0100 Subject: [PATCH 0028/2054] Fix broken tab order Signed-off-by: TheKodeToad --- launcher/ui/pages/global/JavaPage.ui | 8 +++++++- launcher/ui/pages/instance/InstanceSettingsPage.ui | 12 ++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index 44289eb86c..0bf27fef1c 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -320,8 +320,14 @@ minMemSpinBox maxMemSpinBox permGenSpinBox - javaBrowseBtn javaPathTextBox + javaBrowseBtn + javaDownloadBtn + javaDetectBtn + javaTestBtn + skipCompatibilityCheckbox + skipJavaWizardCheckbox + jvmArgsTextBox tabWidget diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index f9e679b2eb..08a74671c6 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -714,6 +714,12 @@ openGlobalJavaSettingsButton settingsTabs javaSettingsGroupBox + javaPathTextBox + javaBrowseBtn + javaDownloadBtn + javaDetectBtn + javaTestBtn + skipCompatibilityCheckbox memoryGroupBox minMemSpinBox maxMemSpinBox @@ -733,12 +739,6 @@ useNativeOpenALCheck showGameTime recordGameTime - skipCompatibilityCheckbox - javaPathTextBox - javaBrowseBtn - javaDownloadBtn - javaDetectBtn - javaTestBtn miscellaneousSettingsBox closeAfterLaunchCheck quitAfterGameStopCheck From 907c2fd19c148230d58a3d02fd31ecd8bf7aae86 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Aug 2023 09:16:17 +0300 Subject: [PATCH 0029/2054] added missing header Signed-off-by: Trial97 --- launcher/ui/pages/modplatform/ImportPage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 3cb1616295..cc11d2c168 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -52,6 +52,7 @@ #include "Json.h" #include "InstanceImportTask.h" +#include "net/NetJob.h" class UrlValidator : public QValidator { public: From bf810053b720ac728affd45878147ce593b4f6fd Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 21 Aug 2023 17:21:11 +0300 Subject: [PATCH 0030/2054] updated instance copy Signed-off-by: Trial97 --- launcher/FileSystem.h | 1 + launcher/InstanceCopyTask.cpp | 83 +++++++++++++++++++++++------------ launcher/InstanceCopyTask.h | 1 + 3 files changed, 56 insertions(+), 29 deletions(-) diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index bfed576c1f..946e55a2c5 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -218,6 +218,7 @@ class create_link : public QObject { bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } int totalLinked() { return m_linked; } + int totalToLink() { return static_cast(m_links_to_make.size()); } void runPrivileged() { runPrivileged(QString()); } void runPrivileged(const QString& offset); diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 8abf306406..3a40bc06d1 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -1,10 +1,12 @@ #include "InstanceCopyTask.h" #include #include +#include #include "FileSystem.h" #include "NullInstance.h" #include "pathmatcher/RegexpMatcher.h" #include "settings/INISettingsObject.h" +#include "tasks/Task.h" InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs) { @@ -38,38 +40,50 @@ void InstanceCopyTask::executeTask() { setStatus(tr("Copying instance %1").arg(m_origInstance->name())); - auto copySaves = [&]() { - QFileInfo mcDir(FS::PathCombine(m_stagingPath, "minecraft")); - QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft")); - - QString staging_mc_dir; - if (mcDir.exists() && !dotMCDir.exists()) - staging_mc_dir = mcDir.filePath(); - else - staging_mc_dir = dotMCDir.filePath(); - - FS::copy savesCopy(FS::PathCombine(m_origInstance->gameRoot(), "saves"), FS::PathCombine(staging_mc_dir, "saves")); - savesCopy.followSymlinks(true); - - return savesCopy(); - }; - - m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves] { + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] { if (m_useClone) { FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath); folderClone.matcher(m_matcher.get()); + folderClone(true); + setProgress(0, folderClone.totalCloned()); + connect(&folderClone, &FS::clone::fileCloned, + [this](QString src, QString dst) { setProgress(m_progress + 1, m_progressTotal); }); return folderClone(); - } else if (m_useLinks || m_useHardLinks) { + } + if (m_useLinks || m_useHardLinks) { + std::unique_ptr savesCopy; + if (m_copySaves) { + QFileInfo mcDir(FS::PathCombine(m_stagingPath, "minecraft")); + QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft")); + + QString staging_mc_dir; + if (mcDir.exists() && !dotMCDir.exists()) + staging_mc_dir = mcDir.filePath(); + else + staging_mc_dir = dotMCDir.filePath(); + + savesCopy = std::make_unique(FS::PathCombine(m_origInstance->gameRoot(), "saves"), + FS::PathCombine(staging_mc_dir, "saves")); + savesCopy->followSymlinks(true); + (*savesCopy)(true); + setProgress(0, savesCopy->totalCopied()); + connect(savesCopy.get(), &FS::copy::fileCopied, [this](QString src) { setProgress(m_progress + 1, m_progressTotal); }); + } FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath); int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); + folderLink(true); + setProgress(0, m_progressTotal + folderLink.totalToLink()); + connect(&folderLink, &FS::create_link::fileLinked, + [this](QString src, QString dst) { setProgress(m_progress + 1, m_progressTotal); }); bool there_were_errors = false; if (!folderLink()) { #if defined Q_OS_WIN32 if (!m_useHardLinks) { + setProgress(0, m_progressTotal); qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; qDebug() << "attempting to run with privelage"; @@ -94,13 +108,11 @@ void InstanceCopyTask::executeTask() } } - if (m_copySaves) { - there_were_errors |= !copySaves(); + if (savesCopy) { + there_were_errors |= !(*savesCopy)(); } return got_priv_results && !there_were_errors; - } else { - qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str(); } #else qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str(); @@ -108,17 +120,19 @@ void InstanceCopyTask::executeTask() return false; } - if (m_copySaves) { - there_were_errors |= !copySaves(); + if (savesCopy) { + there_were_errors |= !(*savesCopy)(); } return !there_were_errors; - } else { - FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); - folderCopy.followSymlinks(false).matcher(m_matcher.get()); - - return folderCopy(); } + FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); + folderCopy.followSymlinks(false).matcher(m_matcher.get()); + + folderCopy(true); + setProgress(0, folderCopy.totalCopied()); + connect(&folderCopy, &FS::copy::fileCopied, [this](QString src) { setProgress(m_progress + 1, m_progressTotal); }); + return folderCopy(); }); connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &InstanceCopyTask::copyFinished); connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &InstanceCopyTask::copyAborted); @@ -171,3 +185,14 @@ void InstanceCopyTask::copyAborted() emitFailed(tr("Instance folder copy has been aborted.")); return; } + +bool InstanceCopyTask::abort() +{ + if (m_copyFutureWatcher.isRunning()) { + m_copyFutureWatcher.cancel(); + // NOTE: Here we don't do `emitAborted()` because it will be done when `m_copyFutureWatcher` actually cancels, which may not occur + // immediately. + return true; + } + return false; +} \ No newline at end of file diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index 357c6df0b8..0f7f1020dc 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -19,6 +19,7 @@ class InstanceCopyTask : public InstanceTask { protected: //! Entry point for tasks. virtual void executeTask() override; + bool abort() override; void copyFinished(); void copyAborted(); From 93876e27f80a63b36c2cce73b014bf031054bb99 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 31 Aug 2023 17:32:55 +0100 Subject: [PATCH 0031/2054] Generalise resource metadata Signed-off-by: TheKodeToad --- launcher/CMakeLists.txt | 5 +- launcher/QObjectPtr.h | 6 +- launcher/minecraft/MinecraftInstance.cpp | 13 ++-- launcher/minecraft/mod/MetadataHandler.h | 51 ++++++++----- launcher/minecraft/mod/Mod.cpp | 73 +----------------- launcher/minecraft/mod/Mod.h | 15 +--- launcher/minecraft/mod/ModDetails.h | 48 +----------- launcher/minecraft/mod/ModFolderModel.cpp | 46 ++---------- launcher/minecraft/mod/ModFolderModel.h | 14 +--- launcher/minecraft/mod/Resource.cpp | 45 ++++++++++- launcher/minecraft/mod/Resource.h | 28 ++++++- .../minecraft/mod/ResourceFolderModel.cpp | 14 ++-- launcher/minecraft/mod/ResourceFolderModel.h | 11 ++- .../minecraft/mod/ResourcePackFolderModel.cpp | 10 +-- .../minecraft/mod/ResourcePackFolderModel.h | 6 +- .../minecraft/mod/ShaderPackFolderModel.h | 4 +- .../minecraft/mod/TexturePackFolderModel.cpp | 10 +-- .../minecraft/mod/TexturePackFolderModel.h | 5 +- .../minecraft/mod/tasks/BasicFolderLoadTask.h | 74 ------------------- .../minecraft/mod/tasks/LocalModParseTask.h | 5 -- ...oadTask.cpp => ResourceFolderLoadTask.cpp} | 71 +++++++++--------- ...derLoadTask.h => ResourceFolderLoadTask.h} | 12 ++- launcher/modplatform/EnsureMetadataTask.cpp | 2 +- .../modplatform/flame/FlameCheckUpdate.cpp | 4 +- .../modrinth/ModrinthCheckUpdate.cpp | 2 +- .../modrinth/ModrinthPackExportTask.cpp | 2 +- launcher/modplatform/packwiz/Packwiz.h | 1 + launcher/ui/dialogs/ModUpdateDialog.cpp | 2 +- 28 files changed, 209 insertions(+), 370 deletions(-) delete mode 100644 launcher/minecraft/mod/tasks/BasicFolderLoadTask.h rename launcher/minecraft/mod/tasks/{ModFolderLoadTask.cpp => ResourceFolderLoadTask.cpp} (55%) rename launcher/minecraft/mod/tasks/{ModFolderLoadTask.h => ResourceFolderLoadTask.h} (84%) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 18e0acab10..cd4c64dec4 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -350,9 +350,8 @@ set(MINECRAFT_SOURCES minecraft/mod/TexturePackFolderModel.h minecraft/mod/TexturePackFolderModel.cpp minecraft/mod/ShaderPackFolderModel.h - minecraft/mod/tasks/BasicFolderLoadTask.h - minecraft/mod/tasks/ModFolderLoadTask.h - minecraft/mod/tasks/ModFolderLoadTask.cpp + minecraft/mod/tasks/ResourceFolderLoadTask.h + minecraft/mod/tasks/ResourceFolderLoadTask.cpp minecraft/mod/tasks/LocalModParseTask.h minecraft/mod/tasks/LocalModParseTask.cpp minecraft/mod/tasks/LocalModUpdateTask.h diff --git a/launcher/QObjectPtr.h b/launcher/QObjectPtr.h index a1c64b433e..5f7b64df0c 100644 --- a/launcher/QObjectPtr.h +++ b/launcher/QObjectPtr.h @@ -33,11 +33,7 @@ class shared_qobject_ptr : public QSharedPointer { {} void reset() { QSharedPointer::reset(); } - void reset(T*&& other) - { - shared_qobject_ptr t(other); - this->swap(t); - } + void reset(T* other) { QSharedPointer::reset(other); } void reset(const shared_qobject_ptr& other) { shared_qobject_ptr t(other); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 0e64c46d46..a75d017b74 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1091,7 +1091,7 @@ std::shared_ptr MinecraftInstance::loaderModList() { if (!m_loader_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed)); + m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed, true)); } return m_loader_mod_list; } @@ -1100,7 +1100,7 @@ std::shared_ptr MinecraftInstance::coreModList() { if (!m_core_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed)); + m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed, true)); } return m_core_mod_list; } @@ -1117,7 +1117,8 @@ std::shared_ptr MinecraftInstance::nilModList() std::shared_ptr MinecraftInstance::resourcePackList() { if (!m_resource_pack_list) { - m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this)); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this, is_indexed, true)); } return m_resource_pack_list; } @@ -1125,7 +1126,8 @@ std::shared_ptr MinecraftInstance::resourcePackList() std::shared_ptr MinecraftInstance::texturePackList() { if (!m_texture_pack_list) { - m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this)); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this, is_indexed, true)); } return m_texture_pack_list; } @@ -1133,7 +1135,8 @@ std::shared_ptr MinecraftInstance::texturePackList() std::shared_ptr MinecraftInstance::shaderPackList() { if (!m_shader_pack_list) { - m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this)); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this, is_indexed, true)); } return m_shader_pack_list; } diff --git a/launcher/minecraft/mod/MetadataHandler.h b/launcher/minecraft/mod/MetadataHandler.h index 88e9ff2b62..747375d3f3 100644 --- a/launcher/minecraft/mod/MetadataHandler.h +++ b/launcher/minecraft/mod/MetadataHandler.h @@ -25,30 +25,41 @@ // launcher/minecraft/mod/Mod.h class Mod; -/* Abstraction file for easily changing the way metadata is stored / handled - * Needs to be a class because of -Wunused-function and no C++17 [[maybe_unused]] - * */ -class Metadata { - public: - using ModStruct = Packwiz::V1::Mod; +namespace Metadata { +using ModStruct = Packwiz::V1::Mod; - static auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct - { - return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version); - } +inline auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct +{ + return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version); +} - static auto create(QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct - { - return Packwiz::V1::createModFormat(index_dir, internal_mod, mod_slug); - } +inline auto create(QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct +{ + return Packwiz::V1::createModFormat(index_dir, internal_mod, std::move(mod_slug)); +} - static void update(QDir& index_dir, ModStruct& mod) { Packwiz::V1::updateModIndex(index_dir, mod); } +inline void update(QDir& index_dir, ModStruct& mod) +{ + Packwiz::V1::updateModIndex(index_dir, mod); +} - static void remove(QDir& index_dir, QString mod_slug) { Packwiz::V1::deleteModIndex(index_dir, mod_slug); } +inline void remove(QDir& index_dir, QString mod_slug) +{ + Packwiz::V1::deleteModIndex(index_dir, mod_slug); +} - static void remove(QDir& index_dir, QVariant& mod_id) { Packwiz::V1::deleteModIndex(index_dir, mod_id); } +inline void remove(QDir& index_dir, QVariant& mod_id) +{ + Packwiz::V1::deleteModIndex(index_dir, mod_id); +} - static auto get(QDir& index_dir, QString mod_slug) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_slug); } +inline auto get(QDir& index_dir, QString mod_slug) -> ModStruct +{ + return Packwiz::V1::getIndexForMod(index_dir, std::move(mod_slug)); +} - static auto get(QDir& index_dir, QVariant& mod_id) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_id); } -}; +inline auto get(QDir& index_dir, QVariant& mod_id) -> ModStruct +{ + return Packwiz::V1::getIndexForMod(index_dir, mod_id); +} +}; // namespace Metadata diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index ae3dea8d87..53f7d72de2 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -43,35 +43,16 @@ #include "MTPixmapCache.h" #include "MetadataHandler.h" +#include "Resource.h" #include "Version.h" #include "minecraft/mod/ModDetails.h" #include "minecraft/mod/tasks/LocalModParseTask.h" -static ModPlatform::ProviderCapabilities ProviderCaps; - Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details() { m_enabled = (file.suffix() != "disabled"); } -Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) : Mod(mods_dir.absoluteFilePath(metadata.filename)) -{ - m_name = metadata.name; - m_local_details.metadata = std::make_shared(std::move(metadata)); -} - -void Mod::setStatus(ModStatus status) -{ - m_local_details.status = status; -} -void Mod::setMetadata(std::shared_ptr&& metadata) -{ - if (status() == ModStatus::NoMetadata) - setStatus(ModStatus::Installed); - - m_local_details.metadata = metadata; -} - void Mod::setDetails(const ModDetails& details) { m_local_details = details; @@ -103,8 +84,7 @@ std::pair Mod::compare(const Resource& other, SortType type) const break; } case SortType::PROVIDER: { - auto compare_result = - QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive); + auto compare_result = QString::compare(provider(), cast_other->provider(), Qt::CaseInsensitive); if (compare_result != 0) return { compare_result, type == SortType::PROVIDER }; break; @@ -127,22 +107,6 @@ bool Mod::applyFilter(QRegularExpression filter) const return Resource::applyFilter(filter); } -auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool -{ - if (!preserve_metadata) { - qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name()); - - if (metadata()) { - Metadata::remove(index_dir, metadata()->slug); - } else { - auto n = name(); - Metadata::remove(index_dir, n); - } - } - - return Resource::destroy(attempt_trash); -} - auto Mod::details() const -> const ModDetails& { return m_local_details; @@ -154,10 +118,7 @@ auto Mod::name() const -> QString if (!d_name.isEmpty()) return d_name; - if (metadata()) - return metadata()->name; - - return m_name; + return Resource::name(); } auto Mod::version() const -> QString @@ -187,45 +148,17 @@ auto Mod::authors() const -> QStringList return details().authors; } -auto Mod::status() const -> ModStatus -{ - return details().status; -} - -auto Mod::metadata() -> std::shared_ptr -{ - return m_local_details.metadata; -} - -auto Mod::metadata() const -> const std::shared_ptr -{ - return m_local_details.metadata; -} - void Mod::finishResolvingWithDetails(ModDetails&& details) { m_is_resolving = false; m_is_resolved = true; - std::shared_ptr metadata = details.metadata; - if (details.status == ModStatus::Unknown) - details.status = m_local_details.status; - m_local_details = std::move(details); - if (metadata) - setMetadata(std::move(metadata)); if (!iconPath().isEmpty()) { m_pack_image_cache_key.was_read_attempt = false; } } -auto Mod::provider() const -> std::optional -{ - if (metadata()) - return ProviderCaps.readableName(metadata()->provider); - return {}; -} - auto Mod::licenses() const -> const QList& { return details().licenses; diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 6dafecfc58..5f82572a2d 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -56,7 +56,6 @@ class Mod : public Resource { Mod() = default; Mod(const QFileInfo& file); - Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata); Mod(QString file_path) : Mod(QFileInfo(file_path)) {} auto details() const -> const ModDetails&; @@ -65,11 +64,10 @@ class Mod : public Resource { auto homeurl() const -> QString; auto description() const -> QString; auto authors() const -> QStringList; - auto status() const -> ModStatus; - auto provider() const -> std::optional; auto licenses() const -> const QList&; auto issueTracker() const -> QString; auto metaurl() const -> QString; + void setDetails(const ModDetails& details); /** Get the intneral path to the mod's icon file*/ QString iconPath() const { return m_local_details.icon_file; } @@ -78,22 +76,11 @@ class Mod : public Resource { /** Thread-safe. */ void setIcon(QImage new_image) const; - auto metadata() -> std::shared_ptr; - auto metadata() const -> const std::shared_ptr; - - void setStatus(ModStatus status); - void setMetadata(std::shared_ptr&& metadata); - void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared(metadata)); } - void setDetails(const ModDetails& details); - bool valid() const override; [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; - // Delete all the files of this mod - auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool; - void finishResolvingWithDetails(ModDetails&& details); protected: diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h index a00d5a24b2..9195c03681 100644 --- a/launcher/minecraft/mod/ModDetails.h +++ b/launcher/minecraft/mod/ModDetails.h @@ -43,13 +43,6 @@ #include "minecraft/mod/MetadataHandler.h" -enum class ModStatus { - Installed, // Both JAR and Metadata are present - NotInstalled, // Only the Metadata is present - NoMetadata, // Only the JAR is present - Unknown, // Default status -}; - struct ModLicense { QString name = {}; QString id = {}; @@ -149,12 +142,6 @@ struct ModDetails { /* Path of mod logo */ QString icon_file = {}; - /* Installation status of the mod */ - ModStatus status = ModStatus::Unknown; - - /* Metadata information, if any */ - std::shared_ptr metadata = nullptr; - ModDetails() = default; /** Metadata should be handled manually to properly set the mod status. */ @@ -169,40 +156,9 @@ struct ModDetails { , issue_tracker(other.issue_tracker) , licenses(other.licenses) , icon_file(other.icon_file) - , status(other.status) {} - ModDetails& operator=(const ModDetails& other) - { - this->mod_id = other.mod_id; - this->name = other.name; - this->version = other.version; - this->mcversion = other.mcversion; - this->homeurl = other.homeurl; - this->description = other.description; - this->authors = other.authors; - this->issue_tracker = other.issue_tracker; - this->licenses = other.licenses; - this->icon_file = other.icon_file; - this->status = other.status; - - return *this; - } - - ModDetails& operator=(const ModDetails&& other) - { - this->mod_id = other.mod_id; - this->name = other.name; - this->version = other.version; - this->mcversion = other.mcversion; - this->homeurl = other.homeurl; - this->description = other.description; - this->authors = other.authors; - this->issue_tracker = other.issue_tracker; - this->licenses = other.licenses; - this->icon_file = other.icon_file; - this->status = other.status; + ModDetails& operator=(const ModDetails& other) = default; - return *this; - } + ModDetails& operator=(ModDetails&& other) = default; }; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index eed35615cf..2f0e9b3e40 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -52,15 +52,16 @@ #include "Application.h" #include "Json.h" +#include "Resource.h" #include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModUpdateTask.h" -#include "minecraft/mod/tasks/ModFolderLoadTask.h" +#include "minecraft/mod/tasks/ResourceFolderLoadTask.h" #include "modplatform/ModIndex.h" #include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameModIndex.h" -ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir) - : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) +ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) + : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) { m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider" }); m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") }); @@ -96,15 +97,8 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const } case DateColumn: return m_resources[row]->dateTimeChanged(); - case ProviderColumn: { - auto provider = at(row)->provider(); - if (!provider.has_value()) { - //: Unknown mod provider (i.e. not Modrinth, CurseForge, etc...) - return tr("Unknown"); - } - - return provider.value(); - } + case ProviderColumn: + return at(row)->provider(); default: return QVariant(); } @@ -185,14 +179,6 @@ int ModFolderModel::columnCount(const QModelIndex& parent) const return parent.isValid() ? 0 : NUM_COLUMNS; } -Task* ModFolderModel::createUpdateTask() -{ - auto index_dir = indexDir(); - auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load); - m_first_folder_load = false; - return task; -} - Task* ModFolderModel::createParseTask(Resource& resource) { return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo()); @@ -273,26 +259,6 @@ auto ModFolderModel::allMods() -> QList return mods; } -void ModFolderModel::onUpdateSucceeded() -{ - auto update_results = static_cast(m_current_update_task.get())->result(); - - auto& new_mods = update_results->mods; - -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - auto current_list = m_resources_index.keys(); - QSet current_set(current_list.begin(), current_list.end()); - - auto new_list = new_mods.keys(); - QSet new_set(new_list.begin(), new_list.end()); -#else - QSet current_set(m_resources_index.keys().toSet()); - QSet new_set(new_mods.keys().toSet()); -#endif - - applyUpdates(current_set, new_set, new_mods); -} - void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) { auto iter = m_active_parse_tasks.constFind(ticket); diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index f1890e87e7..9326158d4d 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -47,10 +47,9 @@ #include "ResourceFolderModel.h" #include "minecraft/mod/tasks/LocalModParseTask.h" -#include "minecraft/mod/tasks/ModFolderLoadTask.h" +#include "minecraft/mod/tasks/ResourceFolderLoadTask.h" #include "modplatform/ModIndex.h" -class LegacyInstance; class BaseInstance; class QFileSystemWatcher; @@ -63,7 +62,7 @@ class ModFolderModel : public ResourceFolderModel { public: enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, VersionColumn, DateColumn, ProviderColumn, NUM_COLUMNS }; enum ModStatusAction { Disable, Enable, Toggle }; - ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true); + ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); virtual QString id() const override { return "mods"; } @@ -72,7 +71,7 @@ class ModFolderModel : public ResourceFolderModel { QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; int columnCount(const QModelIndex& parent) const override; - [[nodiscard]] Task* createUpdateTask() override; + [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new Mod(file); } [[nodiscard]] Task* createParseTask(Resource&) override; bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); } @@ -87,18 +86,11 @@ class ModFolderModel : public ResourceFolderModel { bool startWatching() override; bool stopWatching() override; - QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; } - auto selectedMods(QModelIndexList& indexes) -> QList; auto allMods() -> QList; RESOURCE_HELPERS(Mod) private slots: - void onUpdateSucceeded() override; void onParseSucceeded(int ticket, QString resource_id) override; - - protected: - bool m_is_indexed; - bool m_first_folder_load = true; }; diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index da806f0f46..0dd7f89929 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -5,6 +5,8 @@ #include "FileSystem.h" +static ModPlatform::ProviderCapabilities ProviderCaps; + Resource::Resource(QObject* parent) : QObject(parent) {} Resource::Resource(QFileInfo file_info) : QObject() @@ -54,6 +56,14 @@ void Resource::parseFile() m_changed_date_time = m_file_info.lastModified(); } +auto Resource::name() const -> QString +{ + if (metadata()) + return metadata()->name; + + return m_name; +} + static void removeThePrefix(QString& string) { QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption); @@ -61,6 +71,22 @@ static void removeThePrefix(QString& string) string = string.trimmed(); } +auto Resource::provider() const -> QString +{ + if (metadata()) + return ProviderCaps.readableName(metadata()->provider); + + return tr("Unknown"); +} + +void Resource::setMetadata(std::shared_ptr&& metadata) +{ + if (status() == ResourceStatus::NO_METADATA) + setStatus(ResourceStatus::INSTALLED); + + m_metadata = metadata; +} + std::pair Resource::compare(const Resource& other, SortType type) const { switch (type) { @@ -152,6 +178,23 @@ bool Resource::destroy(bool attemptTrash) return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath()); } + +auto Resource::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool +{ + if (!preserve_metadata) { + qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name()); + + if (metadata()) { + Metadata::remove(index_dir, metadata()->slug); + } else { + auto n = name(); + Metadata::remove(index_dir, n); + } + } + + return destroy(attempt_trash); +} + bool Resource::isSymLinkUnder(const QString& instPath) const { if (isSymLink()) @@ -168,4 +211,4 @@ bool Resource::isSymLinkUnder(const QString& instPath) const bool Resource::isMoreThanOneHardLink() const { return FS::hardLinkCount(m_file_info.absoluteFilePath()) > 1; -} +} \ No newline at end of file diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index c1ed49461e..5012c23a48 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -5,6 +5,8 @@ #include #include +#include "MetadataHandler.h" +#include "ModDetails.h" #include "QObjectPtr.h" enum class ResourceType { @@ -15,6 +17,13 @@ enum class ResourceType { LITEMOD, //!< The resource is a litemod }; +enum class ResourceStatus { + INSTALLED, // Both JAR and Metadata are present + NOT_INSTALLED, // Only the Metadata is present + NO_METADATA, // Only the JAR is present + UNKNOWN, // Default status +}; + enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER }; enum class EnableAction { ENABLE, DISABLE, TOGGLE }; @@ -46,9 +55,18 @@ class Resource : public QObject { [[nodiscard]] auto type() const -> ResourceType { return m_type; } [[nodiscard]] bool enabled() const { return m_enabled; } - [[nodiscard]] virtual auto name() const -> QString { return m_name; } + [[nodiscard]] virtual auto name() const -> QString; [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; } + [[nodiscard]] auto status() const -> ResourceStatus { return m_status; }; + [[nodiscard]] auto metadata() -> std::shared_ptr { return m_metadata; } + [[nodiscard]] auto metadata() const -> std::shared_ptr { return m_metadata; } + [[nodiscard]] auto provider() const -> QString; + + void setStatus(ResourceStatus status) { m_status = status; } + void setMetadata(std::shared_ptr&& metadata); + void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared(metadata)); } + /** Compares two Resources, for sorting purposes, considering a ascending order, returning: * > 0: 'this' comes after 'other' * = 0: 'this' is equal to 'other' @@ -81,7 +99,8 @@ class Resource : public QObject { } // Delete all files of this resource. - bool destroy(bool attemptTrash = true); + auto destroy(bool attemptTrash = true) -> bool; + auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool; [[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); } @@ -110,6 +129,11 @@ class Resource : public QObject { /* The type of file we're dealing with. */ ResourceType m_type = ResourceType::UNKNOWN; + /* Installation status of the resource. */ + ResourceStatus m_status = ResourceStatus::UNKNOWN; + + std::shared_ptr m_metadata = nullptr; + /* Whether the resource is enabled (e.g. shows up in the game) or not. */ bool m_enabled = true; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index d3237b34b0..a39e9adee9 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -15,14 +15,14 @@ #include "FileSystem.h" #include "QVariantUtils.h" -#include "minecraft/mod/tasks/BasicFolderLoadTask.h" +#include "minecraft/mod/tasks/ResourceFolderLoadTask.h" #include "settings/Setting.h" #include "tasks/Task.h" #include "ui/dialogs/CustomMessageBox.h" -ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir) - : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this) +ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) + : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this), m_is_indexed(is_indexed) { if (create_dir) { FS::ensureFolderPathExists(m_dir.absolutePath()); @@ -290,7 +290,7 @@ void ResourceFolderModel::resolveResource(Resource* res) void ResourceFolderModel::onUpdateSucceeded() { - auto update_results = static_cast(m_current_update_task.get())->result(); + auto update_results = static_cast(m_current_update_task.get())->result(); auto& new_resources = update_results->resources; @@ -320,7 +320,11 @@ void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id) Task* ResourceFolderModel::createUpdateTask() { - return new BasicFolderLoadTask(m_dir); + auto index_dir = indexDir(); + auto task = new ResourceFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load, + [this](const QFileInfo& file) { return createResource(file); }); + m_first_folder_load = false; + return task; } bool ResourceFolderModel::hasPendingParseTasks() const diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 80c31e456a..3a4287b303 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -29,7 +29,7 @@ class QSortFilterProxyModel; class ResourceFolderModel : public QAbstractListModel { Q_OBJECT public: - ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true); + ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); ~ResourceFolderModel() override; virtual QString id() const { return "resource"; } @@ -52,6 +52,8 @@ class ResourceFolderModel : public QAbstractListModel { virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); } virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); } + QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; } + /** Given a path in the system, install that resource, moving it to its place in the * instance file hierarchy. * @@ -152,7 +154,9 @@ class ResourceFolderModel : public QAbstractListModel { * This Task is normally executed when opening a page, so it shouldn't contain much heavy work. * If such work is needed, try using it in the Task create by createParseTask() instead! */ - [[nodiscard]] virtual Task* createUpdateTask(); + [[nodiscard]] Task* createUpdateTask(); + + [[nodiscard]] virtual Resource* createResource(const QFileInfo& info) { return new Resource(info); } /** This creates a new parse task to be executed by onUpdateSucceeded(). * @@ -210,6 +214,9 @@ class ResourceFolderModel : public QAbstractListModel { QFileSystemWatcher m_watcher; bool m_is_watching = false; + bool m_is_indexed; + bool m_first_folder_load = true; + Task::Ptr m_current_update_task = nullptr; bool m_scheduled_update = false; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index f27431576f..2e72e02f28 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -44,10 +44,11 @@ #include "Application.h" #include "Version.h" -#include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/LocalResourcePackParseTask.h" +#include "minecraft/mod/tasks/ResourceFolderLoadTask.h" -ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) +ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) + : ResourceFolderModel(dir, instance, is_indexed, create_dir, parent) { m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" }); m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") }); @@ -173,11 +174,6 @@ int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const return parent.isValid() ? 0 : NUM_COLUMNS; } -Task* ResourcePackFolderModel::createUpdateTask() -{ - return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared(entry); }); -} - Task* ResourcePackFolderModel::createParseTask(Resource& resource) { return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast(resource)); diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index 29c2c59954..fd56c8c73f 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -9,16 +9,16 @@ class ResourcePackFolderModel : public ResourceFolderModel { public: enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, NUM_COLUMNS }; - explicit ResourcePackFolderModel(const QString& dir, BaseInstance* instance); + explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); - virtual QString id() const override { return "resourcepacks"; } + QString id() const override { return "resourcepacks"; } [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; [[nodiscard]] int columnCount(const QModelIndex& parent) const override; - [[nodiscard]] Task* createUpdateTask() override; + [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new ResourcePack(file); } [[nodiscard]] Task* createParseTask(Resource&) override; RESOURCE_HELPERS(ResourcePack) diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h index 44ed37a47c..02faee49c8 100644 --- a/launcher/minecraft/mod/ShaderPackFolderModel.h +++ b/launcher/minecraft/mod/ShaderPackFolderModel.h @@ -6,7 +6,9 @@ class ShaderPackFolderModel : public ResourceFolderModel { Q_OBJECT public: - explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) {} + explicit ShaderPackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr) + : ResourceFolderModel(dir, instance, is_indexed, create_dir, parent) + {} virtual QString id() const override { return "shaderpacks"; } }; diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 5c5f2b7c1c..23a3924214 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -39,10 +39,11 @@ #include "TexturePackFolderModel.h" -#include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h" +#include "minecraft/mod/tasks/ResourceFolderLoadTask.h" -TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) +TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) + : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) { m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified" }); m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified") }); @@ -52,11 +53,6 @@ TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* m_columnsHideable = { false, true, false, true }; } -Task* TexturePackFolderModel::createUpdateTask() -{ - return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared(entry); }); -} - Task* TexturePackFolderModel::createParseTask(Resource& resource) { return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast(resource)); diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index b975d86417..44b19b57a3 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -46,7 +46,7 @@ class TexturePackFolderModel : public ResourceFolderModel { public: enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, NUM_COLUMNS }; - explicit TexturePackFolderModel(const QString& dir, std::shared_ptr instance); + explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); virtual QString id() const override { return "texturepacks"; } @@ -55,8 +55,7 @@ class TexturePackFolderModel : public ResourceFolderModel { [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; [[nodiscard]] int columnCount(const QModelIndex& parent) const override; - explicit TexturePackFolderModel(const QString& dir, BaseInstance* instance); - [[nodiscard]] Task* createUpdateTask() override; + [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new TexturePack(file); } [[nodiscard]] Task* createParseTask(Resource&) override; RESOURCE_HELPERS(TexturePack) diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h deleted file mode 100644 index 23a2b649ab..0000000000 --- a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include - -#include "minecraft/mod/Resource.h" - -#include "tasks/Task.h" - -/** Very simple task that just loads a folder's contents directly. - */ -class BasicFolderLoadTask : public Task { - Q_OBJECT - public: - struct Result { - QMap resources; - }; - using ResultPtr = std::shared_ptr; - - [[nodiscard]] ResultPtr result() const { return m_result; } - - public: - BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread()) - { - m_create_func = [](QFileInfo const& entry) -> Resource::Ptr { return makeShared(entry); }; - } - BasicFolderLoadTask(QDir dir, std::function create_function) - : Task(nullptr, false) - , m_dir(dir) - , m_result(new Result) - , m_create_func(std::move(create_function)) - , m_thread_to_spawn_into(thread()) - {} - - [[nodiscard]] bool canAbort() const override { return true; } - bool abort() override - { - m_aborted.store(true); - return true; - } - - void executeTask() override - { - if (thread() != m_thread_to_spawn_into) - connect(this, &Task::finished, this->thread(), &QThread::quit); - - m_dir.refresh(); - for (auto entry : m_dir.entryInfoList()) { - auto resource = m_create_func(entry); - resource->moveToThread(m_thread_to_spawn_into); - m_result->resources.insert(resource->internal_id(), resource); - } - - if (m_aborted) - emit finished(); - else - emitSucceeded(); - } - - private: - QDir m_dir; - ResultPtr m_result; - - std::atomic m_aborted = false; - - std::function m_create_func; - - /** This is the thread in which we should put new mod objects */ - QThread* m_thread_to_spawn_into; -}; diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h index a032170933..ff1ff822eb 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h @@ -47,11 +47,6 @@ class LocalModParseTask : public Task { [[nodiscard]] int token() const { return m_token; } - private: - void processAsZip(); - void processAsFolder(); - void processAsLitemod(); - private: int m_token; ResourceType m_type; diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp similarity index 55% rename from launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp rename to launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp index 9f79ba0987..1004bcff00 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp @@ -34,23 +34,20 @@ * limitations under the License. */ -#include "ModFolderLoadTask.h" +#include "ResourceFolderLoadTask.h" #include "minecraft/mod/MetadataHandler.h" #include -ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan) - : Task(nullptr, false) - , m_mods_dir(mods_dir) - , m_index_dir(index_dir) - , m_is_indexed(is_indexed) - , m_clean_orphan(clean_orphan) - , m_result(new Result()) - , m_thread_to_spawn_into(thread()) +ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resource_dir, + const QDir& index_dir, + bool is_indexed, + bool clean_orphan, + std::function create_function) {} -void ModFolderLoadTask::executeTask() +void ResourceFolderLoadTask::executeTask() { if (thread() != m_thread_to_spawn_into) connect(this, &Task::finished, this->thread(), &QThread::quit); @@ -63,32 +60,32 @@ void ModFolderLoadTask::executeTask() // Read JAR files that don't have metadata m_mods_dir.refresh(); for (auto entry : m_mods_dir.entryInfoList()) { - Mod* mod(new Mod(entry)); + Resource* resource = m_create_func(entry); - if (mod->enabled()) { - if (m_result->mods.contains(mod->internal_id())) { - m_result->mods[mod->internal_id()]->setStatus(ModStatus::Installed); + if (resource->enabled()) { + if (m_result->resources.contains(resource->internal_id())) { + m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED); // Delete the object we just created, since a valid one is already in the mods list. - delete mod; + delete resource; } else { - m_result->mods[mod->internal_id()].reset(std::move(mod)); - m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata); + m_result->resources[resource->internal_id()].reset(resource); + m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA); } } else { - QString chopped_id = mod->internal_id().chopped(9); - if (m_result->mods.contains(chopped_id)) { - m_result->mods[mod->internal_id()].reset(std::move(mod)); + QString chopped_id = resource->internal_id().chopped(9); + if (m_result->resources.contains(chopped_id)) { + m_result->resources[resource->internal_id()].reset(resource); - auto metadata = m_result->mods[chopped_id]->metadata(); + auto metadata = m_result->resources[chopped_id]->metadata(); if (metadata) { - mod->setMetadata(*metadata); + resource->setMetadata(*metadata); - m_result->mods[mod->internal_id()]->setStatus(ModStatus::Installed); - m_result->mods.remove(chopped_id); + m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED); + m_result->resources.remove(chopped_id); } } else { - m_result->mods[mod->internal_id()].reset(std::move(mod)); - m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata); + m_result->resources[resource->internal_id()].reset(resource); + m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA); } } } @@ -96,17 +93,17 @@ void ModFolderLoadTask::executeTask() // Remove orphan metadata to prevent issues // See https://github.com/PolyMC/PolyMC/issues/996 if (m_clean_orphan) { - QMutableMapIterator iter(m_result->mods); + QMutableMapIterator iter(m_result->resources); while (iter.hasNext()) { - auto mod = iter.next().value(); - if (mod->status() == ModStatus::NotInstalled) { - mod->destroy(m_index_dir, false, false); + auto resource = iter.next().value(); + if (resource->status() == ResourceStatus::NOT_INSTALLED) { + resource->destroy(m_index_dir, false, false); iter.remove(); } } } - for (auto mod : m_result->mods) + for (auto mod : m_result->resources) mod->moveToThread(m_thread_to_spawn_into); if (m_aborted) @@ -115,18 +112,18 @@ void ModFolderLoadTask::executeTask() emitSucceeded(); } -void ModFolderLoadTask::getFromMetadata() +void ResourceFolderLoadTask::getFromMetadata() { m_index_dir.refresh(); for (auto entry : m_index_dir.entryList(QDir::Files)) { auto metadata = Metadata::get(m_index_dir, entry); - if (!metadata.isValid()) { + if (!metadata.isValid()) return; - } - auto* mod = new Mod(m_mods_dir, metadata); - mod->setStatus(ModStatus::NotInstalled); - m_result->mods[mod->internal_id()].reset(std::move(mod)); + auto* resource = m_create_func(QFileInfo(m_mods_dir.filePath(metadata.filename))); + resource->setMetadata(metadata); + resource->setStatus(ResourceStatus::NOT_INSTALLED); + m_result->resources[resource->internal_id()].reset(resource); } } diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h similarity index 84% rename from launcher/minecraft/mod/tasks/ModFolderLoadTask.h rename to launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h index 4200ef6d9c..616597ca4c 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h @@ -44,17 +44,21 @@ #include "minecraft/mod/Mod.h" #include "tasks/Task.h" -class ModFolderLoadTask : public Task { +class ResourceFolderLoadTask : public Task { Q_OBJECT public: struct Result { - QMap mods; + QMap resources; }; using ResultPtr = std::shared_ptr; ResultPtr result() const { return m_result; } public: - ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan = false); + ResourceFolderLoadTask(const QDir& resource_dir, + const QDir& index_dir, + bool is_indexed, + bool clean_orphan, + std::function create_function); [[nodiscard]] bool canAbort() const override { return true; } bool abort() override @@ -76,6 +80,8 @@ class ModFolderLoadTask : public Task { std::atomic m_aborted = false; + std::function m_create_func; + /** This is the thread in which we should put new mod objects */ QThread* m_thread_to_spawn_into; }; diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index c3eadd06d5..05caa33c91 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -94,7 +94,7 @@ void EnsureMetadataTask::executeTask() } // They already have the right metadata :o - if (mod->status() != ModStatus::NoMetadata && mod->metadata() && mod->metadata()->provider == m_provider) { + if (mod->status() != ResourceStatus::NO_METADATA && mod->metadata() && mod->metadata()->provider == m_provider) { qDebug() << "Mod" << mod->name() << "already has metadata!"; emitReady(mod); continue; diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 476a4667aa..6653e477e6 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -154,7 +154,7 @@ void FlameCheckUpdate::executeTask() continue; } - if (!latest_ver.hash.isEmpty() && (mod->metadata()->hash != latest_ver.hash || mod->status() == ModStatus::NotInstalled)) { + if (!latest_ver.hash.isEmpty() && (mod->metadata()->hash != latest_ver.hash || mod->status() == ResourceStatus::NOT_INSTALLED)) { // Fake pack with the necessary info to pass to the download task :) auto pack = std::make_shared(); pack->name = mod->name(); @@ -167,7 +167,7 @@ void FlameCheckUpdate::executeTask() pack->provider = ModPlatform::ResourceProvider::FLAME; auto old_version = mod->version(); - if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) { + if (old_version.isEmpty() && mod->status() != ResourceStatus::NOT_INSTALLED) { auto current_ver = getFileInfo(latest_ver.addonId.toInt(), mod->metadata()->file_id.toInt()); old_version = current_ver.version; } diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index c65f4fa800..574e99ac74 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -144,7 +144,7 @@ void ModrinthCheckUpdate::executeTask() auto mod = *mod_iter; auto key = project_ver.hash; - if ((key != hash && project_ver.is_preferred) || (mod->status() == ModStatus::NotInstalled)) { + if ((key != hash && project_ver.is_preferred) || (mod->status() == ResourceStatus::NOT_INSTALLED)) { if (mod->version() == project_ver.version_number) continue; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index a9ddb0c919..72b52836f9 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -121,7 +121,7 @@ void ModrinthPackExportTask::collectHashes() modIter != allMods.end()) { const Mod* mod = *modIter; if (mod->metadata() != nullptr) { - QUrl& url = mod->metadata()->url; + const QUrl& url = mod->metadata()->url; // ensure the url is permitted on modrinth.com if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) { qDebug() << "Resolving" << relative << "from index"; diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 7edc18cdeb..c49165557f 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -35,6 +35,7 @@ auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool shoul class V1 { public: + // can also represent other resources beside loader mods - but this is what packwiz calls it struct Mod { QString slug{}; QString name{}; diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index 1f0fa7cd27..f1216572f8 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -228,7 +228,7 @@ auto ModUpdateDialog::ensureMetadata() -> bool }; for (auto candidate : m_candidates) { - if (candidate->status() != ModStatus::NoMetadata) { + if (candidate->status() != ResourceStatus::NO_METADATA) { onMetadataEnsured(candidate); continue; } From ee487669960ba8ee7c1997621b9fd7e778d3f65a Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 1 Sep 2023 00:27:05 +0100 Subject: [PATCH 0032/2054] More generalistaion for ResourceFolderModels Signed-off-by: TheKodeToad --- launcher/ResourceDownloadTask.cpp | 8 +- launcher/minecraft/mod/MetadataHandler.h | 14 +-- launcher/minecraft/mod/Mod.cpp | 1 - launcher/minecraft/mod/ModFolderModel.cpp | 95 ------------------- launcher/minecraft/mod/ModFolderModel.h | 10 -- launcher/minecraft/mod/Resource.cpp | 9 +- launcher/minecraft/mod/Resource.h | 3 +- .../minecraft/mod/ResourceFolderModel.cpp | 58 ++++++++++- launcher/minecraft/mod/ResourceFolderModel.h | 8 +- launcher/modplatform/packwiz/Packwiz.cpp | 16 ++-- launcher/modplatform/packwiz/Packwiz.h | 16 ++-- launcher/ui/MainWindow.cpp | 2 +- launcher/ui/pages/instance/ModFolderPage.cpp | 2 +- 13 files changed, 90 insertions(+), 152 deletions(-) diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index a02151ca13..dffbaf4781 100644 --- a/launcher/ResourceDownloadTask.cpp +++ b/launcher/ResourceDownloadTask.cpp @@ -67,12 +67,8 @@ void ResourceDownloadTask::downloadSucceeded() m_filesNetJob.reset(); auto name = std::get<0>(to_delete); auto filename = std::get<1>(to_delete); - if (!name.isEmpty() && filename != m_pack_version.fileName) { - if (auto model = dynamic_cast(m_pack_model.get()); model) - model->uninstallMod(filename, true); - else - m_pack_model->uninstallResource(filename); - } + if (!name.isEmpty() && filename != m_pack_version.fileName) + m_pack_model->uninstallResource(filename, true); } void ResourceDownloadTask::downloadFailed(QString reason) diff --git a/launcher/minecraft/mod/MetadataHandler.h b/launcher/minecraft/mod/MetadataHandler.h index 747375d3f3..3794db4846 100644 --- a/launcher/minecraft/mod/MetadataHandler.h +++ b/launcher/minecraft/mod/MetadataHandler.h @@ -28,37 +28,37 @@ class Mod; namespace Metadata { using ModStruct = Packwiz::V1::Mod; -inline auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct +inline auto create(const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct { return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version); } -inline auto create(QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct +inline auto create(const QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct { return Packwiz::V1::createModFormat(index_dir, internal_mod, std::move(mod_slug)); } -inline void update(QDir& index_dir, ModStruct& mod) +inline void update(const QDir& index_dir, ModStruct& mod) { Packwiz::V1::updateModIndex(index_dir, mod); } -inline void remove(QDir& index_dir, QString mod_slug) +inline void remove(const QDir& index_dir, QString mod_slug) { Packwiz::V1::deleteModIndex(index_dir, mod_slug); } -inline void remove(QDir& index_dir, QVariant& mod_id) +inline void remove(const QDir& index_dir, QVariant& mod_id) { Packwiz::V1::deleteModIndex(index_dir, mod_id); } -inline auto get(QDir& index_dir, QString mod_slug) -> ModStruct +inline auto get(const QDir& index_dir, QString mod_slug) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, std::move(mod_slug)); } -inline auto get(QDir& index_dir, QVariant& mod_id) -> ModStruct +inline auto get(const QDir& index_dir, QVariant& mod_id) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_id); } diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 53f7d72de2..27688b5f27 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -36,7 +36,6 @@ #include "Mod.h" -#include #include #include #include diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 2f0e9b3e40..733e1f6559 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -51,13 +51,9 @@ #include "Application.h" -#include "Json.h" #include "Resource.h" #include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModUpdateTask.h" -#include "minecraft/mod/tasks/ResourceFolderLoadTask.h" -#include "modplatform/ModIndex.h" -#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameModIndex.h" ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) @@ -184,58 +180,11 @@ Task* ModFolderModel::createParseTask(Resource& resource) return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo()); } -bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata) -{ - for (auto mod : allMods()) { - if (mod->fileinfo().fileName() == filename) { - auto index_dir = indexDir(); - mod->destroy(index_dir, preserve_metadata, false); - - update(); - - return true; - } - } - - return false; -} - -bool ModFolderModel::deleteMods(const QModelIndexList& indexes) -{ - if (indexes.isEmpty()) - return true; - - for (auto i : indexes) { - if (i.column() != 0) { - continue; - } - auto m = at(i.row()); - auto index_dir = indexDir(); - m->destroy(index_dir); - } - - update(); - - return true; -} - bool ModFolderModel::isValid() { return m_dir.exists() && m_dir.isReadable(); } -bool ModFolderModel::startWatching() -{ - // Remove orphaned metadata next time - m_first_folder_load = true; - return ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); -} - -bool ModFolderModel::stopWatching() -{ - return ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); -} - auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList { QList selected_resources; @@ -280,47 +229,3 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); } - -static const FlameAPI flameAPI; -bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers) -{ - if (vers.addonId.isValid()) { - ModPlatform::IndexedPack pack{ - vers.addonId, - ModPlatform::ResourceProvider::FLAME, - }; - - QEventLoop loop; - - auto response = std::make_shared(); - auto job = flameAPI.getProject(vers.addonId.toString(), response); - - QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); }); - QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit); - QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qDebug() << *response; - return; - } - try { - auto obj = Json::requireObject(Json::requireObject(doc), "data"); - FlameMod::loadIndexedPack(pack, obj); - } catch (const JSONValidationError& e) { - qDebug() << doc; - qWarning() << "Error while reading mod info: " << e.cause(); - } - LocalModUpdateTask update_metadata(indexDir(), pack, vers); - QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit); - update_metadata.start(); - }); - - job->start(); - - loop.exec(); - } - return ResourceFolderModel::installResource(file_path); -} diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 9326158d4d..c1db33c0cd 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -74,18 +74,8 @@ class ModFolderModel : public ResourceFolderModel { [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new Mod(file); } [[nodiscard]] Task* createParseTask(Resource&) override; - bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); } - bool installMod(QString file_path, ModPlatform::IndexedVersion& vers); - bool uninstallMod(const QString& filename, bool preserve_metadata = false); - - /// Deletes all the selected mods - bool deleteMods(const QModelIndexList& indexes); - bool isValid(); - bool startWatching() override; - bool stopWatching() override; - auto selectedMods(QModelIndexList& indexes) -> QList; auto allMods() -> QList; diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 0dd7f89929..bfe2a88027 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -172,15 +172,10 @@ bool Resource::enable(EnableAction action) return true; } -bool Resource::destroy(bool attemptTrash) +auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool { m_type = ResourceType::UNKNOWN; - return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath()); -} - -auto Resource::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool -{ if (!preserve_metadata) { qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name()); @@ -192,7 +187,7 @@ auto Resource::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_tra } } - return destroy(attempt_trash); + return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath()); } bool Resource::isSymLinkUnder(const QString& instPath) const diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 5012c23a48..77063cbf00 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -99,8 +99,7 @@ class Resource : public QObject { } // Delete all files of this resource. - auto destroy(bool attemptTrash = true) -> bool; - auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool; + auto destroy(const QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool; [[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); } diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index a39e9adee9..9ac3879047 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "Application.h" #include "FileSystem.h" @@ -17,6 +18,10 @@ #include "QVariantUtils.h" #include "minecraft/mod/tasks/ResourceFolderLoadTask.h" +#include "Json.h" +#include "minecraft/mod/tasks/LocalModUpdateTask.h" +#include "modplatform/flame/FlameAPI.h" +#include "modplatform/flame/FlameModIndex.h" #include "settings/Setting.h" #include "tasks/Task.h" #include "ui/dialogs/CustomMessageBox.h" @@ -43,6 +48,9 @@ ResourceFolderModel::~ResourceFolderModel() bool ResourceFolderModel::startWatching(const QStringList paths) { + // Remove orphaned metadata next time + m_first_folder_load = true; + if (m_is_watching) return false; @@ -153,11 +161,55 @@ bool ResourceFolderModel::installResource(QString original_path) return false; } -bool ResourceFolderModel::uninstallResource(QString file_name) +bool ResourceFolderModel::installResource(QString path, ModPlatform::IndexedVersion& vers) +{ + if (vers.addonId.isValid()) { + ModPlatform::IndexedPack pack{ + vers.addonId, + ModPlatform::ResourceProvider::FLAME, + }; + + QEventLoop loop; + + auto response = std::make_shared(); + auto job = FlameAPI().getProject(vers.addonId.toString(), response); + + QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); }); + QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit); + QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qDebug() << *response; + return; + } + try { + auto obj = Json::requireObject(Json::requireObject(doc), "data"); + FlameMod::loadIndexedPack(pack, obj); + } catch (const JSONValidationError& e) { + qDebug() << doc; + qWarning() << "Error while reading mod info: " << e.cause(); + } + LocalModUpdateTask update_metadata(indexDir(), pack, vers); + QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit); + update_metadata.start(); + }); + + job->start(); + + loop.exec(); + } + + return installResource(std::move(path)); +} + +bool ResourceFolderModel::uninstallResource(QString file_name, bool preserve_metadata) { for (auto& resource : m_resources) { if (resource->fileinfo().fileName() == file_name) { - auto res = resource->destroy(false); + auto res = resource->destroy(indexDir(), preserve_metadata, false); update(); @@ -179,7 +231,7 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) auto& resource = m_resources.at(i.row()); - resource->destroy(); + resource->destroy(indexDir()); } update(); diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 3a4287b303..e8f8d2b9e9 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -49,8 +49,8 @@ class ResourceFolderModel : public QAbstractListModel { bool stopWatching(const QStringList paths); /* Helper methods for subclasses, using a predetermined list of paths. */ - virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); } - virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); } + virtual bool startWatching() { return startWatching({ indexDir().absolutePath(), m_dir.absolutePath() }); } + virtual bool stopWatching() { return stopWatching({ indexDir().absolutePath(), m_dir.absolutePath() }); } QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; } @@ -61,11 +61,13 @@ class ResourceFolderModel : public QAbstractListModel { */ virtual bool installResource(QString path); + virtual bool installResource(QString path, ModPlatform::IndexedVersion& vers); + /** Uninstall (i.e. remove all data about it) a resource, given its file name. * * Returns whether the removal was successful. */ - virtual bool uninstallResource(QString file_name); + virtual bool uninstallResource(QString file_name, bool preserve_metadata = false); virtual bool deleteResources(const QModelIndexList&); /** Applies the given 'action' to the resources in 'indexes'. diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 71f66bf3ec..bfc1dea44d 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -32,7 +32,7 @@ namespace Packwiz { -auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString +auto getRealIndexName(const QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString { QFile index_file(index_dir.absoluteFilePath(normalized_fname)); @@ -89,7 +89,7 @@ auto intEntry(toml::table table, QString entry_name) -> int return node.value_or(0); } -auto V1::createModFormat([[maybe_unused]] QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) +auto V1::createModFormat([[maybe_unused]] const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod { Mod mod; @@ -115,7 +115,7 @@ auto V1::createModFormat([[maybe_unused]] QDir& index_dir, ModPlatform::IndexedP return mod; } -auto V1::createModFormat(QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, QString slug) -> Mod +auto V1::createModFormat(const QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, QString slug) -> Mod { // Try getting metadata if it exists Mod mod{ getIndexForMod(index_dir, slug) }; @@ -127,7 +127,7 @@ auto V1::createModFormat(QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, return {}; } -void V1::updateModIndex(QDir& index_dir, Mod& mod) +void V1::updateModIndex(const QDir& index_dir, Mod& mod) { if (!mod.isValid()) { qCritical() << QString("Tried to update metadata of an invalid mod!"); @@ -192,7 +192,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) index_file.close(); } -void V1::deleteModIndex(QDir& index_dir, QString& mod_slug) +void V1::deleteModIndex(const QDir& index_dir, QString& mod_slug) { auto normalized_fname = indexFileName(mod_slug); auto real_fname = getRealIndexName(index_dir, normalized_fname); @@ -211,7 +211,7 @@ void V1::deleteModIndex(QDir& index_dir, QString& mod_slug) } } -void V1::deleteModIndex(QDir& index_dir, QVariant& mod_id) +void V1::deleteModIndex(const QDir& index_dir, QVariant& mod_id) { for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) { auto mod = getIndexForMod(index_dir, file_name); @@ -223,7 +223,7 @@ void V1::deleteModIndex(QDir& index_dir, QVariant& mod_id) } } -auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod +auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod { Mod mod; @@ -301,7 +301,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod return mod; } -auto V1::getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod +auto V1::getIndexForMod(const QDir& index_dir, QVariant& mod_id) -> Mod { for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) { auto mod = getIndexForMod(index_dir, file_name); diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index c49165557f..ffc1d40c8f 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -31,7 +31,7 @@ class Mod; namespace Packwiz { -auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString; +auto getRealIndexName(const QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString; class V1 { public: @@ -67,33 +67,33 @@ class V1 { /* Generates the object representing the information in a mod.pw.toml file via * its common representation in the launcher, when downloading mods. * */ - static auto createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod; + static auto createModFormat(const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod; /* Generates the object representing the information in a mod.pw.toml file via * its common representation in the launcher, plus a necessary slug. * */ - static auto createModFormat(QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod; + static auto createModFormat(const QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod; /* Updates the mod index for the provided mod. * This creates a new index if one does not exist already * TODO: Ask the user if they want to override, and delete the old mod's files, or keep the old one. * */ - static void updateModIndex(QDir& index_dir, Mod& mod); + static void updateModIndex(const QDir& index_dir, Mod& mod); /* Deletes the metadata for the mod with the given slug. If the metadata doesn't exist, it does nothing. */ - static void deleteModIndex(QDir& index_dir, QString& mod_slug); + static void deleteModIndex(const QDir& index_dir, QString& mod_slug); /* Deletes the metadata for the mod with the given id. If the metadata doesn't exist, it does nothing. */ - static void deleteModIndex(QDir& index_dir, QVariant& mod_id); + static void deleteModIndex(const QDir& index_dir, QVariant& mod_id); /* Gets the metadata for a mod with a particular file name. * If the mod doesn't have a metadata, it simply returns an empty Mod object. * */ - static auto getIndexForMod(QDir& index_dir, QString slug) -> Mod; + static auto getIndexForMod(const QDir& index_dir, QString slug) -> Mod; /* Gets the metadata for a mod with a particular id. * If the mod doesn't have a metadata, it simply returns an empty Mod object. * */ - static auto getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod; + static auto getIndexForMod(const QDir& index_dir, QVariant& mod_id) -> Mod; }; } // namespace Packwiz diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 5e55a5abbb..a7e3def386 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1048,7 +1048,7 @@ void MainWindow::processURLs(QList urls) qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName; break; case PackedResourceType::Mod: - minecraftInst->loaderModList()->installMod(localFileName, version); + minecraftInst->loaderModList()->installResource(localFileName, version); break; case PackedResourceType::ShaderPack: minecraftInst->shaderPackList()->installResource(localFileName); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 0f5e29cb66..6333dec1d4 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -150,7 +150,7 @@ void ModFolderPage::removeItems(const QItemSelection& selection) if (response != QMessageBox::Yes) return; } - m_model->deleteMods(selection.indexes()); + m_model->deleteResources(selection.indexes()); } void ModFolderPage::installMods() From 6aecbfc38fe3ec2b6fc6d9f3e2a76fc03229729b Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 1 Sep 2023 12:50:20 +0100 Subject: [PATCH 0033/2054] Fix crashes Signed-off-by: TheKodeToad --- launcher/QObjectPtr.h | 6 +++++- .../minecraft/mod/tasks/ResourceFolderLoadTask.cpp | 14 +++++++++++--- .../minecraft/mod/tasks/ResourceFolderLoadTask.h | 5 ++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/launcher/QObjectPtr.h b/launcher/QObjectPtr.h index 5f7b64df0c..88c17c0b25 100644 --- a/launcher/QObjectPtr.h +++ b/launcher/QObjectPtr.h @@ -33,7 +33,11 @@ class shared_qobject_ptr : public QSharedPointer { {} void reset() { QSharedPointer::reset(); } - void reset(T* other) { QSharedPointer::reset(other); } + void reset(T* other) + { + shared_qobject_ptr t(other); + this->swap(t); + } void reset(const shared_qobject_ptr& other) { shared_qobject_ptr t(other); diff --git a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp index 1004bcff00..2a02eb1776 100644 --- a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp @@ -45,6 +45,14 @@ ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resource_dir, bool is_indexed, bool clean_orphan, std::function create_function) + : Task(nullptr, false) + , m_resource_dir(resource_dir) + , m_index_dir(index_dir) + , m_is_indexed(is_indexed) + , m_clean_orphan(clean_orphan) + , m_create_func(create_function) + , m_result(new Result()) + , m_thread_to_spawn_into(thread()) {} void ResourceFolderLoadTask::executeTask() @@ -58,8 +66,8 @@ void ResourceFolderLoadTask::executeTask() } // Read JAR files that don't have metadata - m_mods_dir.refresh(); - for (auto entry : m_mods_dir.entryInfoList()) { + m_resource_dir.refresh(); + for (auto entry : m_resource_dir.entryInfoList()) { Resource* resource = m_create_func(entry); if (resource->enabled()) { @@ -121,7 +129,7 @@ void ResourceFolderLoadTask::getFromMetadata() if (!metadata.isValid()) return; - auto* resource = m_create_func(QFileInfo(m_mods_dir.filePath(metadata.filename))); + auto* resource = m_create_func(QFileInfo(m_resource_dir.filePath(metadata.filename))); resource->setMetadata(metadata); resource->setStatus(ResourceStatus::NOT_INSTALLED); m_result->resources[resource->internal_id()].reset(resource); diff --git a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h index 616597ca4c..9950345ef9 100644 --- a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h @@ -73,15 +73,14 @@ class ResourceFolderLoadTask : public Task { void getFromMetadata(); private: - QDir m_mods_dir, m_index_dir; + QDir m_resource_dir, m_index_dir; bool m_is_indexed; bool m_clean_orphan; + std::function m_create_func; ResultPtr m_result; std::atomic m_aborted = false; - std::function m_create_func; - /** This is the thread in which we should put new mod objects */ QThread* m_thread_to_spawn_into; }; From ad16d612087beab6f918c96168ad5628e77487f5 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 1 Sep 2023 17:42:05 +0100 Subject: [PATCH 0034/2054] Resource metadata writing Signed-off-by: TheKodeToad --- launcher/CMakeLists.txt | 4 +- launcher/ResourceDownloadTask.cpp | 6 +- launcher/ResourceDownloadTask.h | 4 +- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- .../minecraft/mod/ResourceFolderModel.cpp | 4 +- ...teTask.cpp => LocalResourceUpdateTask.cpp} | 26 +-- ...UpdateTask.h => LocalResourceUpdateTask.h} | 12 +- launcher/modplatform/EnsureMetadataTask.cpp | 168 +++++++++--------- launcher/modplatform/EnsureMetadataTask.h | 23 +-- .../ui/pages/modplatform/ResourcePage.cpp | 4 +- .../ui/pages/modplatform/ShaderPackPage.cpp | 4 +- 11 files changed, 131 insertions(+), 126 deletions(-) rename launcher/minecraft/mod/tasks/{LocalModUpdateTask.cpp => LocalResourceUpdateTask.cpp} (63%) rename launcher/minecraft/mod/tasks/{LocalModUpdateTask.h => LocalResourceUpdateTask.h} (74%) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index cd4c64dec4..5bc2d73a73 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -354,8 +354,8 @@ set(MINECRAFT_SOURCES minecraft/mod/tasks/ResourceFolderLoadTask.cpp minecraft/mod/tasks/LocalModParseTask.h minecraft/mod/tasks/LocalModParseTask.cpp - minecraft/mod/tasks/LocalModUpdateTask.h - minecraft/mod/tasks/LocalModUpdateTask.cpp + minecraft/mod/tasks/LocalResourceUpdateTask.h + minecraft/mod/tasks/LocalResourceUpdateTask.cpp minecraft/mod/tasks/LocalDataPackParseTask.h minecraft/mod/tasks/LocalDataPackParseTask.cpp minecraft/mod/tasks/LocalResourcePackParseTask.h diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index dffbaf4781..e5828b5697 100644 --- a/launcher/ResourceDownloadTask.cpp +++ b/launcher/ResourceDownloadTask.cpp @@ -33,9 +33,9 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, QString custom_target_folder) : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs), m_custom_target_folder(custom_target_folder) { - if (auto model = dynamic_cast(m_pack_model.get()); model && is_indexed) { - m_update_task.reset(new LocalModUpdateTask(model->indexDir(), *m_pack, m_pack_version)); - connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource); + if (is_indexed) { + m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version)); + connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource); addTask(m_update_task); } diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h index 2baddf8a84..7da5a4aad4 100644 --- a/launcher/ResourceDownloadTask.h +++ b/launcher/ResourceDownloadTask.h @@ -22,7 +22,7 @@ #include "net/NetJob.h" #include "tasks/SequentialTask.h" -#include "minecraft/mod/tasks/LocalModUpdateTask.h" +#include "minecraft/mod/tasks/LocalResourceUpdateTask.h" #include "modplatform/ModIndex.h" class ResourceFolderModel; @@ -50,7 +50,7 @@ class ResourceDownloadTask : public SequentialTask { QString m_custom_target_folder; NetJob::Ptr m_filesNetJob; - LocalModUpdateTask::Ptr m_update_task; + LocalResourceUpdateTask::Ptr m_update_task; void downloadProgressChanged(qint64 current, qint64 total); void downloadFailed(QString reason); diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 733e1f6559..43c64a6671 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -53,7 +53,7 @@ #include "Resource.h" #include "minecraft/mod/tasks/LocalModParseTask.h" -#include "minecraft/mod/tasks/LocalModUpdateTask.h" +#include "minecraft/mod/tasks/LocalResourceUpdateTask.h" #include "modplatform/flame/FlameModIndex.h" ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 9ac3879047..27eb4d2e56 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -19,7 +19,7 @@ #include "minecraft/mod/tasks/ResourceFolderLoadTask.h" #include "Json.h" -#include "minecraft/mod/tasks/LocalModUpdateTask.h" +#include "minecraft/mod/tasks/LocalResourceUpdateTask.h" #include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameModIndex.h" #include "settings/Setting.h" @@ -192,7 +192,7 @@ bool ResourceFolderModel::installResource(QString path, ModPlatform::IndexedVers qDebug() << doc; qWarning() << "Error while reading mod info: " << e.cause(); } - LocalModUpdateTask update_metadata(indexDir(), pack, vers); + LocalResourceUpdateTask update_metadata(indexDir(), pack, vers); QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit); update_metadata.start(); }); diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalResourceUpdateTask.cpp similarity index 63% rename from launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp rename to launcher/minecraft/mod/tasks/LocalResourceUpdateTask.cpp index 4352fad910..c8fe1050ab 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourceUpdateTask.cpp @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -#include "LocalModUpdateTask.h" +#include "LocalResourceUpdateTask.h" #include "FileSystem.h" #include "minecraft/mod/MetadataHandler.h" @@ -26,12 +26,12 @@ #include #endif -LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version) - : m_index_dir(index_dir), m_mod(mod), m_mod_version(mod_version) +LocalResourceUpdateTask::LocalResourceUpdateTask(QDir index_dir, ModPlatform::IndexedPack& project, ModPlatform::IndexedVersion& version) + : m_index_dir(index_dir), m_project(project), m_version(version) { // Ensure a '.index' folder exists in the mods folder, and create it if it does not if (!FS::ensureFolderPathExists(index_dir.path())) { - emitFailed(QString("Unable to create index for mod %1!").arg(m_mod.name)); + emitFailed(QString("Unable to create index directory at %1!").arg(index_dir.absolutePath())); } #ifdef Q_OS_WIN32 @@ -39,28 +39,28 @@ LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& #endif } -void LocalModUpdateTask::executeTask() +void LocalResourceUpdateTask::executeTask() { - setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name)); + setStatus(tr("Updating index for resource:\n%1").arg(m_project.name)); - auto old_metadata = Metadata::get(m_index_dir, m_mod.addonId); + auto old_metadata = Metadata::get(m_index_dir, m_project.addonId); if (old_metadata.isValid()) { - emit hasOldMod(old_metadata.name, old_metadata.filename); - if (m_mod.slug.isEmpty()) - m_mod.slug = old_metadata.slug; + emit hasOldResource(old_metadata.name, old_metadata.filename); + if (m_project.slug.isEmpty()) + m_project.slug = old_metadata.slug; } - auto pw_mod = Metadata::create(m_index_dir, m_mod, m_mod_version); + auto pw_mod = Metadata::create(m_index_dir, m_project, m_version); if (pw_mod.isValid()) { Metadata::update(m_index_dir, pw_mod); emitSucceeded(); } else { - qCritical() << "Tried to update an invalid mod!"; + qCritical() << "Tried to update an invalid resource!"; emitFailed(tr("Invalid metadata")); } } -auto LocalModUpdateTask::abort() -> bool +auto LocalResourceUpdateTask::abort() -> bool { emitAborted(); return true; diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.h b/launcher/minecraft/mod/tasks/LocalResourceUpdateTask.h similarity index 74% rename from launcher/minecraft/mod/tasks/LocalModUpdateTask.h rename to launcher/minecraft/mod/tasks/LocalResourceUpdateTask.h index 0809992940..6e2efbd6a3 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.h +++ b/launcher/minecraft/mod/tasks/LocalResourceUpdateTask.h @@ -23,12 +23,12 @@ #include "modplatform/ModIndex.h" #include "tasks/Task.h" -class LocalModUpdateTask : public Task { +class LocalResourceUpdateTask : public Task { Q_OBJECT public: - using Ptr = shared_qobject_ptr; + using Ptr = shared_qobject_ptr; - explicit LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version); + explicit LocalResourceUpdateTask(QDir index_dir, ModPlatform::IndexedPack& project, ModPlatform::IndexedVersion& version); auto canAbort() const -> bool override { return true; } auto abort() -> bool override; @@ -38,10 +38,10 @@ class LocalModUpdateTask : public Task { void executeTask() override; signals: - void hasOldMod(QString name, QString filename); + void hasOldResource(QString name, QString filename); private: QDir m_index_dir; - ModPlatform::IndexedPack& m_mod; - ModPlatform::IndexedVersion& m_mod_version; + ModPlatform::IndexedPack& m_project; + ModPlatform::IndexedVersion& m_version; }; diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 05caa33c91..4adbebdb1c 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -6,7 +6,7 @@ #include "Json.h" #include "minecraft/mod/Mod.h" -#include "minecraft/mod/tasks/LocalModUpdateTask.h" +#include "minecraft/mod/tasks/LocalResourceUpdateTask.h" #include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameModIndex.h" @@ -19,52 +19,52 @@ static ModPlatform::ProviderCapabilities ProviderCaps; static ModrinthAPI modrinth_api; static FlameAPI flame_api; -EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::ResourceProvider prov) +EnsureMetadataTask::EnsureMetadataTask(Resource* resource, QDir dir, ModPlatform::ResourceProvider prov) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr) { - auto hash_task = createNewHash(mod); + auto hash_task = createNewHash(resource); if (!hash_task) return; - connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); }); - connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); }); + connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_resources.insert(hash, resource); }); + connect(hash_task.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); }); hash_task->start(); } -EnsureMetadataTask::EnsureMetadataTask(QList& mods, QDir dir, ModPlatform::ResourceProvider prov) +EnsureMetadataTask::EnsureMetadataTask(QList& resources, QDir dir, ModPlatform::ResourceProvider prov) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) { m_hashing_task.reset(new ConcurrentTask(this, "MakeHashesTask", 10)); - for (auto* mod : mods) { - auto hash_task = createNewHash(mod); + for (auto* resource : resources) { + auto hash_task = createNewHash(resource); if (!hash_task) continue; - connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); }); - connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); }); + connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_resources.insert(hash, resource); }); + connect(hash_task.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); }); m_hashing_task->addTask(hash_task); } } -Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Mod* mod) +Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Resource* resource) { - if (!mod || !mod->valid() || mod->type() == ResourceType::FOLDER) + if (!resource || !resource->valid() || resource->type() == ResourceType::FOLDER) return nullptr; - return Hashing::createHasher(mod->fileinfo().absoluteFilePath(), m_provider); + return Hashing::createHasher(resource->fileinfo().absoluteFilePath(), m_provider); } -QString EnsureMetadataTask::getExistingHash(Mod* mod) +QString EnsureMetadataTask::getExistingHash(Resource* resource) { // Check for already computed hashes // (linear on the number of mods vs. linear on the size of the mod's JAR) - auto it = m_mods.keyValueBegin(); - while (it != m_mods.keyValueEnd()) { - if ((*it).second == mod) + auto it = m_resources.keyValueBegin(); + while (it != m_resources.keyValueEnd()) { + if ((*it).second == resource) break; it++; } // We already have the hash computed - if (it != m_mods.keyValueEnd()) { + if (it != m_resources.keyValueEnd()) { return (*it).first; } @@ -84,25 +84,25 @@ bool EnsureMetadataTask::abort() void EnsureMetadataTask::executeTask() { - setStatus(tr("Checking if mods have metadata...")); + setStatus(tr("Checking if resources have metadata...")); - for (auto* mod : m_mods) { - if (!mod->valid()) { - qDebug() << "Mod" << mod->name() << "is invalid!"; - emitFail(mod); + for (auto* resource : m_resources) { + if (!resource->valid()) { + qDebug() << "Resource" << resource->name() << "is invalid!"; + emitFail(resource); continue; } // They already have the right metadata :o - if (mod->status() != ResourceStatus::NO_METADATA && mod->metadata() && mod->metadata()->provider == m_provider) { - qDebug() << "Mod" << mod->name() << "already has metadata!"; - emitReady(mod); + if (resource->status() != ResourceStatus::NO_METADATA && resource->metadata() && resource->metadata()->provider == m_provider) { + qDebug() << "Resource" << resource->name() << "already has metadata!"; + emitReady(resource); continue; } // Folders don't have metadata - if (mod->type() == ResourceType::FOLDER) { - emitReady(mod); + if (resource->type() == ResourceType::FOLDER) { + emitReady(resource); } } @@ -118,9 +118,9 @@ void EnsureMetadataTask::executeTask() } auto invalidade_leftover = [this] { - for (auto mod = m_mods.constBegin(); mod != m_mods.constEnd(); mod++) - emitFail(mod.value(), mod.key(), RemoveFromList::No); - m_mods.clear(); + for (auto resource = m_resources.constBegin(); resource != m_resources.constEnd(); resource++) + emitFail(resource.value(), resource.key(), RemoveFromList::No); + m_resources.clear(); emitSucceeded(); }; @@ -159,53 +159,53 @@ void EnsureMetadataTask::executeTask() m_current_task.reset(); }); - if (m_mods.size() > 1) + if (m_resources.size() > 1) setStatus(tr("Requesting metadata information from %1...").arg(ProviderCaps.readableName(m_provider))); - else if (!m_mods.empty()) + else if (!m_resources.empty()) setStatus(tr("Requesting metadata information from %1 for '%2'...") - .arg(ProviderCaps.readableName(m_provider), m_mods.begin().value()->name())); + .arg(ProviderCaps.readableName(m_provider), m_resources.begin().value()->name())); m_current_task = version_task; version_task->start(); } -void EnsureMetadataTask::emitReady(Mod* m, QString key, RemoveFromList remove) +void EnsureMetadataTask::emitReady(Resource* resource, QString key, RemoveFromList remove) { - if (!m) { - qCritical() << "Tried to mark a null mod as ready."; + if (!resource) { + qCritical() << "Tried to mark a null resource as ready."; if (!key.isEmpty()) - m_mods.remove(key); + m_resources.remove(key); return; } - qDebug() << QString("Generated metadata for %1").arg(m->name()); - emit metadataReady(m); + qDebug() << QString("Generated metadata for %1").arg(resource->name()); + emit metadataReady(resource); if (remove == RemoveFromList::Yes) { if (key.isEmpty()) - key = getExistingHash(m); - m_mods.remove(key); + key = getExistingHash(resource); + m_resources.remove(key); } } -void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove) +void EnsureMetadataTask::emitFail(Resource* resource, QString key, RemoveFromList remove) { - if (!m) { - qCritical() << "Tried to mark a null mod as failed."; + if (!resource) { + qCritical() << "Tried to mark a null resource as failed."; if (!key.isEmpty()) - m_mods.remove(key); + m_resources.remove(key); return; } - qDebug() << QString("Failed to generate metadata for %1").arg(m->name()); - emit metadataFailed(m); + qDebug() << QString("Failed to generate metadata for %1").arg(resource->name()); + emit metadataFailed(resource); if (remove == RemoveFromList::Yes) { if (key.isEmpty()) - key = getExistingHash(m); - m_mods.remove(key); + key = getExistingHash(resource); + m_resources.remove(key); } } @@ -216,7 +216,7 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask() auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); auto response = std::make_shared(); - auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response); + auto ver_task = modrinth_api.currentVersions(m_resources.keys(), hash_type, response); // Prevents unfortunate timings when aborting the task if (!ver_task) @@ -236,20 +236,20 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask() try { auto entries = Json::requireObject(doc); - for (auto& hash : m_mods.keys()) { - auto mod = m_mods.find(hash).value(); + for (auto& hash : m_resources.keys()) { + auto resource = m_resources.find(hash).value(); try { auto entry = Json::requireObject(entries, hash); - setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name())); - qDebug() << "Getting version for" << mod->name() << "from Modrinth"; + setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(resource->name())); + qDebug() << "Getting version for" << resource->name() << "from Modrinth"; m_temp_versions.insert(hash, Modrinth::loadIndexedPackVersion(entry)); } catch (Json::JsonException& e) { qDebug() << e.cause(); qDebug() << entries; - emitFail(mod); + emitFail(resource); } } } catch (Json::JsonException& e) { @@ -321,23 +321,23 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask() auto hash = addonIds.find(pack.addonId.toString()).value(); - auto mod_iter = m_mods.find(hash); - if (mod_iter == m_mods.end()) { + auto resource_iter = m_resources.find(hash); + if (resource_iter == m_resources.end()) { qWarning() << "Invalid project id from the API response."; continue; } - auto* mod = mod_iter.value(); + auto* resource = resource_iter.value(); try { - setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name())); + setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(resource->name())); - modrinthCallback(pack, m_temp_versions.find(hash).value(), mod); + modrinthCallback(pack, m_temp_versions.find(hash).value(), resource); } catch (Json::JsonException& e) { qDebug() << e.cause(); qDebug() << entries; - emitFail(mod); + emitFail(resource); } } }); @@ -351,7 +351,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask() auto response = std::make_shared(); QList fingerprints; - for (auto& murmur : m_mods.keys()) { + for (auto& murmur : m_resources.keys()) { fingerprints.push_back(murmur.toUInt()); } @@ -391,13 +391,13 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask() } auto fingerprint = QString::number(Json::ensureVariant(file_obj, "fileFingerprint").toUInt()); - auto mod = m_mods.find(fingerprint); - if (mod == m_mods.end()) { + auto resource = m_resources.find(fingerprint); + if (resource == m_resources.end()) { qWarning() << "Invalid fingerprint from the API response."; continue; } - setStatus(tr("Parsing API response from CurseForge for '%1'...").arg((*mod)->name())); + setStatus(tr("Parsing API response from CurseForge for '%1'...").arg((*resource)->name())); m_temp_versions.insert(fingerprint, FlameMod::loadIndexedPackVersion(file_obj)); } @@ -414,7 +414,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask() Task::Ptr EnsureMetadataTask::flameProjectsTask() { QHash addonIds; - for (auto const& hash : m_mods.keys()) { + for (auto const& hash : m_resources.keys()) { if (m_temp_versions.contains(hash)) { auto data = m_temp_versions.find(hash).value(); @@ -461,20 +461,20 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask() auto id = QString::number(Json::requireInteger(entry_obj, "id")); auto hash = addonIds.find(id).value(); - auto mod = m_mods.find(hash).value(); + auto resource = m_resources.find(hash).value(); try { - setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(mod->name())); + setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name())); ModPlatform::IndexedPack pack; FlameMod::loadIndexedPack(pack, entry_obj); - flameCallback(pack, m_temp_versions.find(hash).value(), mod); + flameCallback(pack, m_temp_versions.find(hash).value(), resource); } catch (Json::JsonException& e) { qDebug() << e.cause(); qDebug() << entries; - emitFail(mod); + emitFail(resource); } } } catch (Json::JsonException& e) { @@ -486,17 +486,17 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask() return proj_task; } -void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod* mod) +void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource* resource) { // Prevent file name mismatch - ver.fileName = mod->fileinfo().fileName(); + ver.fileName = resource->fileinfo().fileName(); if (ver.fileName.endsWith(".disabled")) ver.fileName.chop(9); QDir tmp_index_dir(m_index_dir); { - LocalModUpdateTask update_metadata(m_index_dir, pack, ver); + LocalResourceUpdateTask update_metadata(m_index_dir, pack, ver); QEventLoop loop; QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit); @@ -510,27 +510,27 @@ void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPla auto metadata = Metadata::get(tmp_index_dir, pack.slug); if (!metadata.isValid()) { qCritical() << "Failed to generate metadata at last step!"; - emitFail(mod); + emitFail(resource); return; } - mod->setMetadata(metadata); + resource->setMetadata(metadata); - emitReady(mod); + emitReady(resource); } -void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod* mod) +void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource* resource) { try { // Prevent file name mismatch - ver.fileName = mod->fileinfo().fileName(); + ver.fileName = resource->fileinfo().fileName(); if (ver.fileName.endsWith(".disabled")) ver.fileName.chop(9); QDir tmp_index_dir(m_index_dir); { - LocalModUpdateTask update_metadata(m_index_dir, pack, ver); + LocalResourceUpdateTask update_metadata(m_index_dir, pack, ver); QEventLoop loop; QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit); @@ -544,16 +544,16 @@ void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatfo auto metadata = Metadata::get(tmp_index_dir, pack.slug); if (!metadata.isValid()) { qCritical() << "Failed to generate metadata at last step!"; - emitFail(mod); + emitFail(resource); return; } - mod->setMetadata(metadata); + resource->setMetadata(metadata); - emitReady(mod); + emitReady(resource); } catch (Json::JsonException& e) { qDebug() << e.cause(); - emitFail(mod); + emitFail(resource); } } diff --git a/launcher/modplatform/EnsureMetadataTask.h b/launcher/modplatform/EnsureMetadataTask.h index 2f276e5a02..d82d9c26e9 100644 --- a/launcher/modplatform/EnsureMetadataTask.h +++ b/launcher/modplatform/EnsureMetadataTask.h @@ -5,6 +5,7 @@ #include "modplatform/helpers/HashUtils.h" +#include "minecraft/mod/Resource.h" #include "tasks/ConcurrentTask.h" class Mod; @@ -14,8 +15,8 @@ class EnsureMetadataTask : public Task { Q_OBJECT public: - EnsureMetadataTask(Mod*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); - EnsureMetadataTask(QList&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); + EnsureMetadataTask(Resource*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); + EnsureMetadataTask(QList&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); ~EnsureMetadataTask() = default; @@ -36,23 +37,23 @@ class EnsureMetadataTask : public Task { // Helpers enum class RemoveFromList { Yes, No }; - void emitReady(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes); - void emitFail(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes); + void emitReady(Resource*, QString key = {}, RemoveFromList = RemoveFromList::Yes); + void emitFail(Resource*, QString key = {}, RemoveFromList = RemoveFromList::Yes); // Hashes and stuff - auto createNewHash(Mod*) -> Hashing::Hasher::Ptr; - auto getExistingHash(Mod*) -> QString; + auto createNewHash(Resource*) -> Hashing::Hasher::Ptr; + auto getExistingHash(Resource*) -> QString; private slots: - void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*); - void flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*); + void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource*); + void flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource*); signals: - void metadataReady(Mod*); - void metadataFailed(Mod*); + void metadataReady(Resource*); + void metadataFailed(Resource*); private: - QHash m_mods; + QHash m_resources; QDir m_index_dir; ModPlatform::ResourceProvider m_provider; diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index c087e2be3e..648ff70505 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -48,6 +48,7 @@ #include "minecraft/MinecraftInstance.h" +#include "Application.h" #include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/pages/modplatform/ResourceModel.h" #include "ui/widgets/ProjectItem.h" @@ -332,7 +333,8 @@ void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, const std::shared_ptr base_model) { - m_model->addPack(pack, ver, base_model); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_model->addPack(pack, ver, base_model, is_indexed); } void ResourcePage::removeResourceFromPage(const QString& name) diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp index 586dffc556..6f4120289d 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp @@ -8,6 +8,7 @@ #include "ShaderPackModel.h" +#include "Application.h" #include "ui/dialogs/ResourceDownloadDialog.h" #include @@ -48,10 +49,11 @@ void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pac ModPlatform::IndexedVersion& version, const std::shared_ptr base_model) { + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); QString custom_target_folder; if (version.loaders & ModPlatform::Cauldron) custom_target_folder = QStringLiteral("resourcepacks"); - m_model->addPack(pack, version, base_model, false, custom_target_folder); + m_model->addPack(pack, version, base_model, is_indexed, custom_target_folder); } } // namespace ResourceDownload From 609eaa67abd9e9844f5cd52f81c70826689cd90a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 1 Sep 2023 21:23:51 +0300 Subject: [PATCH 0035/2054] refactored skin apis Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 1 + launcher/minecraft/services/CapeChange.cpp | 89 +++++--------------- launcher/minecraft/services/CapeChange.h | 30 +++---- launcher/minecraft/services/SkinDelete.cpp | 59 ++++--------- launcher/minecraft/services/SkinDelete.h | 24 ++---- launcher/minecraft/services/SkinUpload.cpp | 84 +++++++----------- launcher/minecraft/services/SkinUpload.h | 31 +++---- launcher/net/Logging.cpp | 1 + launcher/net/Logging.h | 1 + launcher/net/NetRequest.cpp | 2 + launcher/net/StaticHeaderProxy.h | 39 +++++++++ launcher/ui/dialogs/SkinUploadDialog.cpp | 5 +- launcher/ui/pages/global/AccountListPage.cpp | 2 +- 13 files changed, 144 insertions(+), 224 deletions(-) create mode 100644 launcher/net/StaticHeaderProxy.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 18e0acab10..3b6218b7fc 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -139,6 +139,7 @@ set(NET_SOURCES net/HeaderProxy.h net/RawHeaderProxy.h net/ApiHeaderProxy.h + net/StaticHeaderProxy.h net/ApiDownload.h net/ApiDownload.cpp net/ApiUpload.cpp diff --git a/launcher/minecraft/services/CapeChange.cpp b/launcher/minecraft/services/CapeChange.cpp index 2ba38a6af1..5a7820b548 100644 --- a/launcher/minecraft/services/CapeChange.cpp +++ b/launcher/minecraft/services/CapeChange.cpp @@ -35,87 +35,38 @@ #include "CapeChange.h" -#include -#include +#include -#include "Application.h" +#include "net/ByteArraySink.h" +#include "net/StaticHeaderProxy.h" -CapeChange::CapeChange(QObject* parent, QString token, QString cape) : Task(parent), m_capeId(cape), m_token(token) {} - -void CapeChange::setCape([[maybe_unused]] QString& cape) +CapeChange::CapeChange(QString token, QString cape) : NetRequest(), m_capeId(cape), m_token(token) { - QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active")); - auto requestString = QString("{\"capeId\":\"%1\"}").arg(m_capeId); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit()); - QNetworkReply* rep = APPLICATION->network()->put(request, requestString.toUtf8()); - - setStatus(tr("Equipping cape")); - - m_reply = shared_qobject_ptr(rep); - connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 - connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError); -#else - connect(rep, QOverload::of(&QNetworkReply::error), this, &CapeChange::downloadError); -#endif - connect(rep, &QNetworkReply::sslErrors, this, &CapeChange::sslErrors); - connect(rep, &QNetworkReply::finished, this, &CapeChange::downloadFinished); -} - -void CapeChange::clearCape() -{ - QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active")); - auto requestString = QString("{\"capeId\":\"%1\"}").arg(m_capeId); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit()); - QNetworkReply* rep = APPLICATION->network()->deleteResource(request); - - setStatus(tr("Removing cape")); + logCat = taskMCServicesLogC; +}; - m_reply = shared_qobject_ptr(rep); - connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 - connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError); -#else - connect(rep, QOverload::of(&QNetworkReply::error), this, &CapeChange::downloadError); -#endif - connect(rep, &QNetworkReply::sslErrors, this, &CapeChange::sslErrors); - connect(rep, &QNetworkReply::finished, this, &CapeChange::downloadFinished); -} - -void CapeChange::executeTask() +QNetworkReply* CapeChange::getReply(QNetworkRequest& request) { if (m_capeId.isEmpty()) { - clearCape(); + setStatus(tr("Removing cape")); + return m_network->deleteResource(request); } else { - setCape(m_capeId); + setStatus(tr("Equipping cape")); + return m_network->post(request, QString("{\"capeId\":\"%1\"}").arg(m_capeId).toUtf8()); } } -void CapeChange::downloadError(QNetworkReply::NetworkError error) +void CapeChange::init() { - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); + addHeaderProxy(new Net::StaticHeaderProxy(QList{ + { "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() }, + })); } -void CapeChange::sslErrors(const QList& errors) +CapeChange::Ptr CapeChange::make(QString token, QString capeId) { - int i = 1; - for (auto error : errors) { - qCritical() << "Cape change SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } -} - -void CapeChange::downloadFinished() -{ - // if the download failed - if (m_reply->error() != QNetworkReply::NetworkError::NoError) { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); + auto up = makeShared(token, capeId); + up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active"); + up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); + return up; } diff --git a/launcher/minecraft/services/CapeChange.h b/launcher/minecraft/services/CapeChange.h index d0c893c448..74805ef43c 100644 --- a/launcher/minecraft/services/CapeChange.h +++ b/launcher/minecraft/services/CapeChange.h @@ -1,31 +1,21 @@ #pragma once -#include -#include -#include -#include "QObjectPtr.h" -#include "tasks/Task.h" +#include "net/NetRequest.h" -class CapeChange : public Task { +class CapeChange : public Net::NetRequest { Q_OBJECT public: - CapeChange(QObject* parent, QString token, QString capeId); - virtual ~CapeChange() {} + using Ptr = shared_qobject_ptr; + CapeChange(QString token, QString capeId); + virtual ~CapeChange() = default; - private: - void setCape(QString& cape); - void clearCape(); + static CapeChange::Ptr make(QString token, QString capeId); + void init() override; + + protected: + virtual QNetworkReply* getReply(QNetworkRequest&) override; private: QString m_capeId; QString m_token; - shared_qobject_ptr m_reply; - - protected: - virtual void executeTask(); - - public slots: - void downloadError(QNetworkReply::NetworkError); - void sslErrors(const QList& errors); - void downloadFinished(); }; diff --git a/launcher/minecraft/services/SkinDelete.cpp b/launcher/minecraft/services/SkinDelete.cpp index 9e9020692d..7944637f67 100644 --- a/launcher/minecraft/services/SkinDelete.cpp +++ b/launcher/minecraft/services/SkinDelete.cpp @@ -35,56 +35,31 @@ #include "SkinDelete.h" -#include -#include +#include "net/ByteArraySink.h" +#include "net/StaticHeaderProxy.h" -#include "Application.h" - -SkinDelete::SkinDelete(QObject* parent, QString token) : Task(parent), m_token(token) {} - -void SkinDelete::executeTask() +SkinDelete::SkinDelete(QString token) : NetRequest(), m_token(token) { - QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active")); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit()); - QNetworkReply* rep = APPLICATION->network()->deleteResource(request); - m_reply = shared_qobject_ptr(rep); - - setStatus(tr("Deleting skin")); - connect(rep, &QNetworkReply::uploadProgress, this, &SkinDelete::setProgress); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 - connect(rep, &QNetworkReply::errorOccurred, this, &SkinDelete::downloadError); -#else - connect(rep, QOverload::of(&QNetworkReply::error), this, &SkinDelete::downloadError); -#endif - connect(rep, &QNetworkReply::sslErrors, this, &SkinDelete::sslErrors); - connect(rep, &QNetworkReply::finished, this, &SkinDelete::downloadFinished); -} + logCat = taskMCServicesLogC; +}; -void SkinDelete::downloadError(QNetworkReply::NetworkError error) +QNetworkReply* SkinDelete::getReply(QNetworkRequest& request) { - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); + setStatus(tr("Deleting skin")); + return m_network->deleteResource(request); } -void SkinDelete::sslErrors(const QList& errors) +void SkinDelete::init() { - int i = 1; - for (auto error : errors) { - qCritical() << "Skin Delete SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } + addHeaderProxy(new Net::StaticHeaderProxy(QList{ + { "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() }, + })); } -void SkinDelete::downloadFinished() +SkinDelete::Ptr SkinDelete::make(QString token) { - // if the download failed - if (m_reply->error() != QNetworkReply::NetworkError::NoError) { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); + auto up = makeShared(token); + up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active"); + up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); + return up; } diff --git a/launcher/minecraft/services/SkinDelete.h b/launcher/minecraft/services/SkinDelete.h index d5b2e63db3..b0fb866cd7 100644 --- a/launcher/minecraft/services/SkinDelete.h +++ b/launcher/minecraft/services/SkinDelete.h @@ -1,26 +1,20 @@ #pragma once -#include -#include -#include "tasks/Task.h" +#include "net/NetRequest.h" -typedef shared_qobject_ptr SkinDeletePtr; - -class SkinDelete : public Task { +class SkinDelete : public Net::NetRequest { Q_OBJECT public: - SkinDelete(QObject* parent, QString token); + using Ptr = shared_qobject_ptr; + SkinDelete(QString token); virtual ~SkinDelete() = default; - private: - QString m_token; - shared_qobject_ptr m_reply; + static SkinDelete::Ptr make(QString token); + void init() override; protected: - virtual void executeTask(); + virtual QNetworkReply* getReply(QNetworkRequest&) override; - public slots: - void downloadError(QNetworkReply::NetworkError); - void sslErrors(const QList& errors); - void downloadFinished(); + private: + QString m_token; }; diff --git a/launcher/minecraft/services/SkinUpload.cpp b/launcher/minecraft/services/SkinUpload.cpp index 163b481b19..0400fa0f4b 100644 --- a/launcher/minecraft/services/SkinUpload.cpp +++ b/launcher/minecraft/services/SkinUpload.cpp @@ -36,30 +36,17 @@ #include "SkinUpload.h" #include -#include -#include "Application.h" +#include "net/ByteArraySink.h" +#include "net/StaticHeaderProxy.h" -QByteArray getVariant(SkinUpload::Model model) +SkinUpload::SkinUpload(QString token, QByteArray skin, SkinUpload::Model model) : NetRequest(), m_model(model), m_skin(skin), m_token(token) { - switch (model) { - default: - qDebug() << "Unknown skin type!"; - case SkinUpload::STEVE: - return "CLASSIC"; - case SkinUpload::ALEX: - return "SLIM"; - } -} - -SkinUpload::SkinUpload(QObject* parent, QString token, QByteArray skin, SkinUpload::Model model) - : Task(parent), m_model(model), m_skin(skin), m_token(token) -{} + logCat = taskMCServicesLogC; +}; -void SkinUpload::executeTask() +QNetworkReply* SkinUpload::getReply(QNetworkRequest& request) { - QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/skins")); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit()); QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); QHttpPart skin; @@ -69,50 +56,37 @@ void SkinUpload::executeTask() QHttpPart model; model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"variant\"")); - model.setBody(getVariant(m_model)); + + switch (m_model) { + default: + qDebug() << "Unknown skin type!"; + emitFailed("Unknown skin type!"); + return nullptr; + case SkinUpload::STEVE: + model.setBody("CLASSIC"); + break; + case SkinUpload::ALEX: + model.setBody("SLIM"); + break; + } multiPart->append(skin); multiPart->append(model); - - QNetworkReply* rep = APPLICATION->network()->post(request, multiPart); - m_reply = shared_qobject_ptr(rep); - setStatus(tr("Uploading skin")); - connect(rep, &QNetworkReply::uploadProgress, this, &SkinUpload::setProgress); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 - connect(rep, &QNetworkReply::errorOccurred, this, &SkinUpload::downloadError); -#else - connect(rep, QOverload::of(&QNetworkReply::error), this, &SkinUpload::downloadError); -#endif - connect(rep, &QNetworkReply::sslErrors, this, &SkinUpload::sslErrors); - connect(rep, &QNetworkReply::finished, this, &SkinUpload::downloadFinished); + return m_network->post(request, multiPart); } -void SkinUpload::downloadError(QNetworkReply::NetworkError error) +void SkinUpload::init() { - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); + addHeaderProxy(new Net::StaticHeaderProxy(QList{ + { "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() }, + })); } -void SkinUpload::sslErrors(const QList& errors) +SkinUpload::Ptr SkinUpload::make(QString token, QByteArray skin, SkinUpload::Model model) { - int i = 1; - for (auto error : errors) { - qCritical() << "Skin Upload SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } -} - -void SkinUpload::downloadFinished() -{ - // if the download failed - if (m_reply->error() != QNetworkReply::NetworkError::NoError) { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); + auto up = makeShared(token, skin, model); + up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins"); + up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); + return up; } diff --git a/launcher/minecraft/services/SkinUpload.h b/launcher/minecraft/services/SkinUpload.h index 5716aa996e..2da836d526 100644 --- a/launcher/minecraft/services/SkinUpload.h +++ b/launcher/minecraft/services/SkinUpload.h @@ -1,34 +1,25 @@ #pragma once -#include -#include -#include -#include "tasks/Task.h" +#include "net/NetRequest.h" -typedef shared_qobject_ptr SkinUploadPtr; - -class SkinUpload : public Task { +class SkinUpload : public Net::NetRequest { Q_OBJECT public: + using Ptr = shared_qobject_ptr; enum Model { STEVE, ALEX }; // Note this class takes ownership of the file. - SkinUpload(QObject* parent, QString token, QByteArray skin, Model model = STEVE); - virtual ~SkinUpload() {} + SkinUpload(QString token, QByteArray skin, Model model = STEVE); + virtual ~SkinUpload() = default; + + static SkinUpload::Ptr make(QString token, QByteArray skin, Model model = STEVE); + void init() override; + + protected: + virtual QNetworkReply* getReply(QNetworkRequest&) override; private: Model m_model; QByteArray m_skin; QString m_token; - shared_qobject_ptr m_reply; - - protected: - virtual void executeTask(); - - public slots: - - void downloadError(QNetworkReply::NetworkError); - void sslErrors(const QList& errors); - - void downloadFinished(); }; diff --git a/launcher/net/Logging.cpp b/launcher/net/Logging.cpp index a9b9db7cfa..45d2dcc209 100644 --- a/launcher/net/Logging.cpp +++ b/launcher/net/Logging.cpp @@ -22,5 +22,6 @@ Q_LOGGING_CATEGORY(taskNetLogC, "launcher.task.net") Q_LOGGING_CATEGORY(taskDownloadLogC, "launcher.task.net.download") Q_LOGGING_CATEGORY(taskUploadLogC, "launcher.task.net.upload") +Q_LOGGING_CATEGORY(taskMCServicesLogC, "launcher.task.minecraft.servicies") Q_LOGGING_CATEGORY(taskMetaCacheLogC, "launcher.task.net.metacache") Q_LOGGING_CATEGORY(taskHttpMetaCacheLogC, "launcher.task.net.metacache.http") diff --git a/launcher/net/Logging.h b/launcher/net/Logging.h index 4deed2b493..d3a11cdce2 100644 --- a/launcher/net/Logging.h +++ b/launcher/net/Logging.h @@ -24,5 +24,6 @@ Q_DECLARE_LOGGING_CATEGORY(taskNetLogC) Q_DECLARE_LOGGING_CATEGORY(taskDownloadLogC) Q_DECLARE_LOGGING_CATEGORY(taskUploadLogC) +Q_DECLARE_LOGGING_CATEGORY(taskMCServicesLogC) Q_DECLARE_LOGGING_CATEGORY(taskMetaCacheLogC) Q_DECLARE_LOGGING_CATEGORY(taskHttpMetaCacheLogC) diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index ff59da18bf..eef550e15a 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -111,6 +111,8 @@ void NetRequest::executeTask() m_last_progress_bytes = 0; QNetworkReply* rep = getReply(request); + if (rep == nullptr) // it failed + return; m_reply.reset(rep); connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::downloadProgress); connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished); diff --git a/launcher/net/StaticHeaderProxy.h b/launcher/net/StaticHeaderProxy.h new file mode 100644 index 0000000000..0e62d80ffd --- /dev/null +++ b/launcher/net/StaticHeaderProxy.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +#include "net/HeaderProxy.h" + +namespace Net { + +class StaticHeaderProxy : public HeaderProxy { + public: + StaticHeaderProxy(QList hdrs = {}) : HeaderProxy(), m_hdrs(hdrs){}; + virtual ~StaticHeaderProxy() = default; + + public: + virtual QList headers(const QNetworkRequest&) const override { return m_hdrs; }; + void setHeaders(QList hdrs) { m_hdrs = hdrs; }; + + private: + QList m_hdrs; +}; + +} // namespace Net \ No newline at end of file diff --git a/launcher/ui/dialogs/SkinUploadDialog.cpp b/launcher/ui/dialogs/SkinUploadDialog.cpp index 5b3ebfa23f..70f1e67606 100644 --- a/launcher/ui/dialogs/SkinUploadDialog.cpp +++ b/launcher/ui/dialogs/SkinUploadDialog.cpp @@ -35,6 +35,7 @@ #include #include +#include #include #include @@ -101,12 +102,12 @@ void SkinUploadDialog::on_buttonBox_accepted() } else if (ui->alexBtn->isChecked()) { model = SkinUpload::ALEX; } - skinUpload.addTask(shared_qobject_ptr(new SkinUpload(this, m_acct->accessToken(), FS::read(fileName), model))); + skinUpload.addTask(SkinUpload::make(m_acct->accessToken(), FS::read(fileName), model)); } auto selectedCape = ui->capeCombo->currentData().toString(); if (selectedCape != m_acct->accountData()->minecraftProfile.currentCape) { - skinUpload.addTask(shared_qobject_ptr(new CapeChange(this, m_acct->accessToken(), selectedCape))); + skinUpload.addTask(CapeChange::make(m_acct->accessToken(), selectedCape)); } if (prog.execWithTask(&skinUpload) != QDialog::Accepted) { CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec(); diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index c95bfabddb..3dcf05e0a0 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -268,7 +268,7 @@ void AccountListPage::on_actionDeleteSkin_triggered() QModelIndex selected = selection.first(); MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value(); ProgressDialog prog(this); - auto deleteSkinTask = std::make_shared(this, account->accessToken()); + auto deleteSkinTask = SkinDelete::make(account->accessToken()); if (prog.execWithTask((Task*)deleteSkinTask.get()) != QDialog::Accepted) { CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to delete current skin!"), QMessageBox::Warning)->exec(); return; From 9ad029e0286ee18a6531d673c052a48a5f40d8d5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 1 Sep 2023 23:10:33 +0300 Subject: [PATCH 0036/2054] added skins directory Signed-off-by: Trial97 --- launcher/Application.cpp | 1 + launcher/ui/MainWindow.cpp | 5 ++++ launcher/ui/MainWindow.h | 2 ++ launcher/ui/MainWindow.ui | 13 +++++++++++ launcher/ui/pages/global/LauncherPage.cpp | 13 +++++++++++ launcher/ui/pages/global/LauncherPage.h | 1 + launcher/ui/pages/global/LauncherPage.ui | 28 +++++++++++++++++++---- 7 files changed, 59 insertions(+), 4 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 66044d9ac2..9cff11546d 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -536,6 +536,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("IconsDir", "icons"); m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); m_settings->registerSetting("DownloadsDirWatchRecursive", false); + m_settings->registerSetting("SkinsDir", "skins"); // Editors m_settings->registerSetting("JsonEditor", QString()); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 5e55a5abbb..a82932e081 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1186,6 +1186,11 @@ void MainWindow::on_actionViewCentralModsFolder_triggered() DesktopServices::openDirectory(APPLICATION->settings()->get("CentralModsDir").toString(), true); } +void MainWindow::on_actionViewSkinsFolder_triggered() +{ + DesktopServices::openDirectory(APPLICATION->settings()->get("SkinsDir").toString(), true); +} + void MainWindow::on_actionViewIconThemeFolder_triggered() { DesktopServices::openDirectory(APPLICATION->themeManager()->getIconThemesFolder().path()); diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 0b6144522b..5d816b5d04 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -118,6 +118,8 @@ class MainWindow : public QMainWindow { void on_actionViewWidgetThemeFolder_triggered(); void on_actionViewCatPackFolder_triggered(); + void on_actionViewSkinsFolder_triggered(); + void on_actionViewSelectedInstFolder_triggered(); void refreshInstances(); diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 91b2c27030..2665515883 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -190,6 +190,7 @@ + @@ -575,6 +576,18 @@ Open the central mods folder in a file browser. + + + + .. + + + View &Skins Folder + + + Open the skins folder in a file browser. + + Themes diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 7f22fdb50d..5576a03395 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -173,6 +173,17 @@ void LauncherPage::on_downloadsDirBrowseBtn_clicked() } } +void LauncherPage::on_skinsDirBrowseBtn_clicked() +{ + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Skins Folder"), ui->skinsDirTextBox->text()); + + // do not allow current dir - it's dirty. Do not allow dirs that don't exist + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { + QString cooked_dir = FS::NormalizePath(raw_dir); + ui->skinsDirTextBox->setText(cooked_dir); + } +} + void LauncherPage::on_metadataDisableBtn_clicked() { ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); @@ -205,6 +216,7 @@ void LauncherPage::applySettings() s->set("CentralModsDir", ui->modsDirTextBox->text()); s->set("IconsDir", ui->iconsDirTextBox->text()); s->set("DownloadsDir", ui->downloadsDirTextBox->text()); + s->set("SkinsDir", ui->skinsDirTextBox->text()); s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked()); auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId(); @@ -259,6 +271,7 @@ void LauncherPage::loadSettings() ui->modsDirTextBox->setText(s->get("CentralModsDir").toString()); ui->iconsDirTextBox->setText(s->get("IconsDir").toString()); ui->downloadsDirTextBox->setText(s->get("DownloadsDir").toString()); + ui->skinsDirTextBox->setText(s->get("SkinsDir").toString()); ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool()); QString sortMode = s->get("InstSortMode").toString(); diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h index e733224d24..f9aefb1716 100644 --- a/launcher/ui/pages/global/LauncherPage.h +++ b/launcher/ui/pages/global/LauncherPage.h @@ -74,6 +74,7 @@ class LauncherPage : public QWidget, public BasePage { void on_modsDirBrowseBtn_clicked(); void on_iconsDirBrowseBtn_clicked(); void on_downloadsDirBrowseBtn_clicked(); + void on_skinsDirBrowseBtn_clicked(); void on_metadataDisableBtn_clicked(); /*! diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index bc259a9b8d..87c4b5ef9a 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -67,7 +67,7 @@ Folders - + &Downloads: @@ -90,13 +90,16 @@ - + - + + + + Browse @@ -147,7 +150,24 @@ - + + + + Browse + + + + + + + &Skins: + + + skinsDirTextBox + + + + When enabled, in addition to the downloads folder, its sub folders will also be searched when looking for resources (e.g. when looking for blocked mods on CurseForge). From 179abfa03eff92404bce37388f994ba1692459a9 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 1 Sep 2023 22:05:44 +0100 Subject: [PATCH 0037/2054] Resource provider column Signed-off-by: TheKodeToad --- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- .../minecraft/mod/ResourceFolderModel.cpp | 9 ++++++--- launcher/minecraft/mod/ResourceFolderModel.h | 8 ++++---- .../minecraft/mod/ResourcePackFolderModel.cpp | 14 ++++++++----- .../minecraft/mod/ResourcePackFolderModel.h | 2 +- .../minecraft/mod/TexturePackFolderModel.cpp | 20 ++++++++++--------- .../minecraft/mod/TexturePackFolderModel.h | 2 +- 7 files changed, 33 insertions(+), 24 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 43c64a6671..370bddb22f 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -160,7 +160,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio case DateColumn: return tr("The date and time this mod was last changed (or added)."); case ProviderColumn: - return tr("Where the mod was downloaded from."); + return tr("The source provider of the mod."); default: return QVariant(); } diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 27eb4d2e56..0f155f6956 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -466,6 +466,8 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const return m_resources[row]->name(); case DATE_COLUMN: return m_resources[row]->dateTimeChanged(); + case PROVIDER_COLUMN: + return m_resources[row]->provider(); default: return {}; } @@ -535,21 +537,22 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien case ACTIVE_COLUMN: case NAME_COLUMN: case DATE_COLUMN: + case PROVIDER_COLUMN: return columnNames().at(section); default: return {}; } case Qt::ToolTipRole: { + //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. switch (section) { case ACTIVE_COLUMN: - //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("Is the resource enabled?"); case NAME_COLUMN: - //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("The name of the resource."); case DATE_COLUMN: - //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("The date and time this resource was last changed (or added)."); + case PROVIDER_COLUMN: + return tr("The source provider of the resource."); default: return {}; } diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index e8f8d2b9e9..7deff5fe9e 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -100,7 +100,7 @@ class ResourceFolderModel : public QAbstractListModel { /* Qt behavior */ /* Basic columns */ - enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS }; + enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, PROVIDER_COLUMN, NUM_COLUMNS }; QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; } [[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast(size()); } @@ -205,11 +205,11 @@ class ResourceFolderModel : public QAbstractListModel { // Represents the relationship between a column's index (represented by the list index), and it's sorting key. // As such, the order in with they appear is very important! QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE }; - QStringList m_column_names = { "Enable", "Name", "Last Modified" }; - QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified") }; + QStringList m_column_names = { "Enable", "Name", "Last Modified", "Provider" }; + QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider") }; QList m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch, QHeaderView::ResizeToContents }; - QList m_columnsHideable = { false, false, true }; + QList m_columnsHideable = { false, false, true, true }; QDir m_dir; BaseInstance* m_instance; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 2e72e02f28..11175e5a6f 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -50,12 +50,12 @@ ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) : ResourceFolderModel(dir, instance, is_indexed, create_dir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") }); + m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider" }); + m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider") }); m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; - m_columnsHideable = { false, true, false, true, true }; + m_columnsHideable = { false, true, false, true, true, true }; } QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const @@ -86,7 +86,8 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const } case DateColumn: return m_resources[row]->dateTimeChanged(); - + case ProviderColumn: + return m_resources[row]->provider(); default: return {}; } @@ -140,6 +141,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O case PackFormatColumn: case DateColumn: case ImageColumn: + case ProviderColumn: return columnNames().at(section); default: return {}; @@ -148,7 +150,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O case Qt::ToolTipRole: switch (section) { case ActiveColumn: - return tr("Is the resource pack enabled? (Only valid for ZIPs)"); + return tr("Is the resource pack enabled?"); case NameColumn: return tr("The name of the resource pack."); case PackFormatColumn: @@ -156,6 +158,8 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O return tr("The resource pack format ID, as well as the Minecraft versions it was designed for."); case DateColumn: return tr("The date and time this resource pack was last changed (or added)."); + case ProviderColumn: + return tr("The source provider of the resource pack."); default: return {}; } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index fd56c8c73f..f00acee29d 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -7,7 +7,7 @@ class ResourcePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, ProviderColumn, NUM_COLUMNS }; explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 23a3924214..a20e2b5dbf 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -45,12 +45,12 @@ TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified") }); + m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider" }); + m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider") }); m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE }; m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents }; - m_columnsHideable = { false, true, false, true }; + m_columnsHideable = { false, true, false, true, true }; } Task* TexturePackFolderModel::createParseTask(Resource& resource) @@ -73,6 +73,8 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const return m_resources[row]->name(); case DateColumn: return m_resources[row]->dateTimeChanged(); + case ProviderColumn: + return m_resources[row]->provider(); default: return {}; } @@ -119,6 +121,7 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or case NameColumn: case DateColumn: case ImageColumn: + case ProviderColumn: return columnNames().at(section); default: return {}; @@ -126,14 +129,13 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or case Qt::ToolTipRole: { switch (section) { case ActiveColumn: - //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. - return tr("Is the resource enabled?"); + return tr("Is the texture pack enabled?"); case NameColumn: - //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. - return tr("The name of the resource."); + return tr("The name of the texture pack."); case DateColumn: - //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. - return tr("The date and time this resource was last changed (or added)."); + return tr("The date and time this texture pack was last changed (or added)."); + case ProviderColumn: + return tr("The source provider of the texture pack."); default: return {}; } diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index 44b19b57a3..649842e23b 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -44,7 +44,7 @@ class TexturePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, ProviderColumn, NUM_COLUMNS }; explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); From c86b8b0f70894e447c8a08e136b919987c3dffae Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 5 Sep 2023 00:18:36 +0300 Subject: [PATCH 0038/2054] added skin manage dialog Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 28 +- launcher/SkinUtils.cpp | 52 --- launcher/SkinUtils.h | 22 - launcher/minecraft/services/CapeChange.h | 21 - launcher/minecraft/services/SkinDelete.h | 20 - launcher/minecraft/services/SkinUpload.h | 25 -- .../{services => skins}/CapeChange.cpp | 6 +- launcher/minecraft/skins/CapeChange.h | 39 ++ .../{services => skins}/SkinDelete.cpp | 3 +- launcher/minecraft/skins/SkinDelete.h | 38 ++ launcher/minecraft/skins/SkinList.cpp | 393 ++++++++++++++++++ launcher/minecraft/skins/SkinList.h | 80 ++++ launcher/minecraft/skins/SkinModel.cpp | 128 ++++++ launcher/minecraft/skins/SkinModel.h | 58 +++ .../{services => skins}/SkinUpload.cpp | 28 +- launcher/minecraft/skins/SkinUpload.h | 42 ++ launcher/net/Logging.cpp | 2 +- launcher/net/Logging.h | 2 +- launcher/net/NetJob.h | 4 +- launcher/net/NetRequest.cpp | 1 + launcher/net/NetRequest.h | 1 + launcher/net/StaticHeaderProxy.h | 2 +- launcher/ui/MainWindow.cpp | 1 - launcher/ui/dialogs/ProfileSelectDialog.cpp | 1 - launcher/ui/dialogs/SkinUploadDialog.cpp | 165 -------- launcher/ui/dialogs/SkinUploadDialog.h | 28 -- launcher/ui/dialogs/SkinUploadDialog.ui | 95 ----- .../ui/dialogs/skins/SkinManageDialog.cpp | 339 +++++++++++++++ launcher/ui/dialogs/skins/SkinManageDialog.h | 62 +++ launcher/ui/dialogs/skins/SkinManageDialog.ui | 193 +++++++++ launcher/ui/pages/global/AccountListPage.cpp | 34 +- launcher/ui/pages/global/AccountListPage.h | 3 +- launcher/ui/pages/global/AccountListPage.ui | 24 +- 33 files changed, 1425 insertions(+), 515 deletions(-) delete mode 100644 launcher/SkinUtils.cpp delete mode 100644 launcher/SkinUtils.h delete mode 100644 launcher/minecraft/services/CapeChange.h delete mode 100644 launcher/minecraft/services/SkinDelete.h delete mode 100644 launcher/minecraft/services/SkinUpload.h rename launcher/minecraft/{services => skins}/CapeChange.cpp (90%) create mode 100644 launcher/minecraft/skins/CapeChange.h rename launcher/minecraft/{services => skins}/SkinDelete.cpp (96%) create mode 100644 launcher/minecraft/skins/SkinDelete.h create mode 100644 launcher/minecraft/skins/SkinList.cpp create mode 100644 launcher/minecraft/skins/SkinList.h create mode 100644 launcher/minecraft/skins/SkinModel.cpp create mode 100644 launcher/minecraft/skins/SkinModel.h rename launcher/minecraft/{services => skins}/SkinUpload.cpp (79%) create mode 100644 launcher/minecraft/skins/SkinUpload.h delete mode 100644 launcher/ui/dialogs/SkinUploadDialog.cpp delete mode 100644 launcher/ui/dialogs/SkinUploadDialog.h delete mode 100644 launcher/ui/dialogs/SkinUploadDialog.ui create mode 100644 launcher/ui/dialogs/skins/SkinManageDialog.cpp create mode 100644 launcher/ui/dialogs/skins/SkinManageDialog.h create mode 100644 launcher/ui/dialogs/skins/SkinManageDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 3b6218b7fc..6914b3385d 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -377,13 +377,17 @@ set(MINECRAFT_SOURCES minecraft/AssetsUtils.h minecraft/AssetsUtils.cpp - # Minecraft services - minecraft/services/CapeChange.cpp - minecraft/services/CapeChange.h - minecraft/services/SkinUpload.cpp - minecraft/services/SkinUpload.h - minecraft/services/SkinDelete.cpp - minecraft/services/SkinDelete.h + # Minecraft skins + minecraft/skins/CapeChange.cpp + minecraft/skins/CapeChange.h + minecraft/skins/SkinUpload.cpp + minecraft/skins/SkinUpload.h + minecraft/skins/SkinDelete.cpp + minecraft/skins/SkinDelete.h + minecraft/skins/SkinModel.cpp + minecraft/skins/SkinModel.h + minecraft/skins/SkinList.cpp + minecraft/skins/SkinList.h minecraft/Agent.h) @@ -742,8 +746,6 @@ SET(LAUNCHER_SOURCES ui/InstanceWindow.cpp # FIXME: maybe find a better home for this. - SkinUtils.cpp - SkinUtils.h FileIgnoreProxy.cpp FileIgnoreProxy.h FastFileIconProvider.cpp @@ -965,8 +967,6 @@ SET(LAUNCHER_SOURCES ui/dialogs/ReviewMessageBox.h ui/dialogs/VersionSelectDialog.cpp ui/dialogs/VersionSelectDialog.h - ui/dialogs/SkinUploadDialog.cpp - ui/dialogs/SkinUploadDialog.h ui/dialogs/ResourceDownloadDialog.cpp ui/dialogs/ResourceDownloadDialog.h ui/dialogs/ScrollMessageBox.cpp @@ -980,6 +980,9 @@ SET(LAUNCHER_SOURCES ui/dialogs/InstallLoaderDialog.cpp ui/dialogs/InstallLoaderDialog.h + ui/dialogs/skins/SkinManageDialog.cpp + ui/dialogs/skins/SkinManageDialog.h + # GUI - widgets ui/widgets/Common.cpp ui/widgets/Common.h @@ -1096,7 +1099,6 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/NewComponentDialog.ui ui/dialogs/NewsDialog.ui ui/dialogs/ProfileSelectDialog.ui - ui/dialogs/SkinUploadDialog.ui ui/dialogs/ExportInstanceDialog.ui ui/dialogs/ExportPackDialog.ui ui/dialogs/ExportToModListDialog.ui @@ -1111,6 +1113,8 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/ScrollMessageBox.ui ui/dialogs/BlockedModsDialog.ui ui/dialogs/ChooseProviderDialog.ui + + ui/dialogs/skins/SkinManageDialog.ui ) qt_add_resources(LAUNCHER_RESOURCES diff --git a/launcher/SkinUtils.cpp b/launcher/SkinUtils.cpp deleted file mode 100644 index 989114ad57..0000000000 --- a/launcher/SkinUtils.cpp +++ /dev/null @@ -1,52 +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 "SkinUtils.h" -#include "Application.h" -#include "net/HttpMetaCache.h" - -#include -#include -#include -#include -#include - -namespace SkinUtils { -/* - * Given a username, return a pixmap of the cached skin (if it exists), QPixmap() otherwise - */ -QPixmap getFaceFromCache(QString username, int height, int width) -{ - QFile fskin(APPLICATION->metacache()->resolveEntry("skins", username + ".png")->getFullPath()); - - if (fskin.exists()) { - QPixmap skinTexture(fskin.fileName()); - if (!skinTexture.isNull()) { - QPixmap skin = QPixmap(8, 8); -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - skin.fill(QColorConstants::Transparent); -#else - skin.fill(QColor(0, 0, 0, 0)); -#endif - QPainter painter(&skin); - painter.drawPixmap(0, 0, skinTexture.copy(8, 8, 8, 8)); - painter.drawPixmap(0, 0, skinTexture.copy(40, 8, 8, 8)); - return skin.scaled(height, width, Qt::KeepAspectRatio); - } - } - - return QPixmap(); -} -} // namespace SkinUtils diff --git a/launcher/SkinUtils.h b/launcher/SkinUtils.h deleted file mode 100644 index 11bc8bc6f2..0000000000 --- a/launcher/SkinUtils.h +++ /dev/null @@ -1,22 +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 - -namespace SkinUtils { -QPixmap getFaceFromCache(QString id, int height = 64, int width = 64); -} diff --git a/launcher/minecraft/services/CapeChange.h b/launcher/minecraft/services/CapeChange.h deleted file mode 100644 index 74805ef43c..0000000000 --- a/launcher/minecraft/services/CapeChange.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "net/NetRequest.h" - -class CapeChange : public Net::NetRequest { - Q_OBJECT - public: - using Ptr = shared_qobject_ptr; - CapeChange(QString token, QString capeId); - virtual ~CapeChange() = default; - - static CapeChange::Ptr make(QString token, QString capeId); - void init() override; - - protected: - virtual QNetworkReply* getReply(QNetworkRequest&) override; - - private: - QString m_capeId; - QString m_token; -}; diff --git a/launcher/minecraft/services/SkinDelete.h b/launcher/minecraft/services/SkinDelete.h deleted file mode 100644 index b0fb866cd7..0000000000 --- a/launcher/minecraft/services/SkinDelete.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "net/NetRequest.h" - -class SkinDelete : public Net::NetRequest { - Q_OBJECT - public: - using Ptr = shared_qobject_ptr; - SkinDelete(QString token); - virtual ~SkinDelete() = default; - - static SkinDelete::Ptr make(QString token); - void init() override; - - protected: - virtual QNetworkReply* getReply(QNetworkRequest&) override; - - private: - QString m_token; -}; diff --git a/launcher/minecraft/services/SkinUpload.h b/launcher/minecraft/services/SkinUpload.h deleted file mode 100644 index 2da836d526..0000000000 --- a/launcher/minecraft/services/SkinUpload.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include "net/NetRequest.h" - -class SkinUpload : public Net::NetRequest { - Q_OBJECT - public: - using Ptr = shared_qobject_ptr; - enum Model { STEVE, ALEX }; - - // Note this class takes ownership of the file. - SkinUpload(QString token, QByteArray skin, Model model = STEVE); - virtual ~SkinUpload() = default; - - static SkinUpload::Ptr make(QString token, QByteArray skin, Model model = STEVE); - void init() override; - - protected: - virtual QNetworkReply* getReply(QNetworkRequest&) override; - - private: - Model m_model; - QByteArray m_skin; - QString m_token; -}; diff --git a/launcher/minecraft/services/CapeChange.cpp b/launcher/minecraft/skins/CapeChange.cpp similarity index 90% rename from launcher/minecraft/services/CapeChange.cpp rename to launcher/minecraft/skins/CapeChange.cpp index 5a7820b548..863e89844f 100644 --- a/launcher/minecraft/services/CapeChange.cpp +++ b/launcher/minecraft/skins/CapeChange.cpp @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,7 +43,7 @@ CapeChange::CapeChange(QString token, QString cape) : NetRequest(), m_capeId(cape), m_token(token) { - logCat = taskMCServicesLogC; + logCat = taskMCSkinsLogC; }; QNetworkReply* CapeChange::getReply(QNetworkRequest& request) @@ -52,7 +53,7 @@ QNetworkReply* CapeChange::getReply(QNetworkRequest& request) return m_network->deleteResource(request); } else { setStatus(tr("Equipping cape")); - return m_network->post(request, QString("{\"capeId\":\"%1\"}").arg(m_capeId).toUtf8()); + return m_network->put(request, QString("{\"capeId\":\"%1\"}").arg(m_capeId).toUtf8()); } } @@ -67,6 +68,7 @@ CapeChange::Ptr CapeChange::make(QString token, QString capeId) { auto up = makeShared(token, capeId); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active"); + up->setObjectName(QString("BYTES:") + up->m_url.toString()); up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); return up; } diff --git a/launcher/minecraft/skins/CapeChange.h b/launcher/minecraft/skins/CapeChange.h new file mode 100644 index 0000000000..bcafcde879 --- /dev/null +++ b/launcher/minecraft/skins/CapeChange.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "net/NetRequest.h" + +class CapeChange : public Net::NetRequest { + Q_OBJECT + public: + using Ptr = shared_qobject_ptr; + CapeChange(QString token, QString capeId); + virtual ~CapeChange() = default; + + static CapeChange::Ptr make(QString token, QString capeId); + void init() override; + + protected: + virtual QNetworkReply* getReply(QNetworkRequest&) override; + + private: + QString m_capeId; + QString m_token; +}; diff --git a/launcher/minecraft/services/SkinDelete.cpp b/launcher/minecraft/skins/SkinDelete.cpp similarity index 96% rename from launcher/minecraft/services/SkinDelete.cpp rename to launcher/minecraft/skins/SkinDelete.cpp index 7944637f67..982cac1b71 100644 --- a/launcher/minecraft/services/SkinDelete.cpp +++ b/launcher/minecraft/skins/SkinDelete.cpp @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,7 +41,7 @@ SkinDelete::SkinDelete(QString token) : NetRequest(), m_token(token) { - logCat = taskMCServicesLogC; + logCat = taskMCSkinsLogC; }; QNetworkReply* SkinDelete::getReply(QNetworkRequest& request) diff --git a/launcher/minecraft/skins/SkinDelete.h b/launcher/minecraft/skins/SkinDelete.h new file mode 100644 index 0000000000..5d02e0cc43 --- /dev/null +++ b/launcher/minecraft/skins/SkinDelete.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "net/NetRequest.h" + +class SkinDelete : public Net::NetRequest { + Q_OBJECT + public: + using Ptr = shared_qobject_ptr; + SkinDelete(QString token); + virtual ~SkinDelete() = default; + + static SkinDelete::Ptr make(QString token); + void init() override; + + protected: + virtual QNetworkReply* getReply(QNetworkRequest&) override; + + private: + QString m_token; +}; diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp new file mode 100644 index 0000000000..be329564bc --- /dev/null +++ b/launcher/minecraft/skins/SkinList.cpp @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "SkinList.h" + +#include +#include + +#include "FileSystem.h" +#include "Json.h" +#include "minecraft/skins/SkinModel.h" + +SkinList::SkinList(QObject* parent, QString path, MinecraftAccountPtr acct) : QAbstractListModel(parent), m_acct(acct) +{ + FS::ensureFolderPathExists(m_dir.absolutePath()); + m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); + m_watcher.reset(new QFileSystemWatcher(this)); + is_watching = false; + connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &SkinList::directoryChanged); + connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &SkinList::fileChanged); + directoryChanged(path); +} + +void SkinList::startWatching() +{ + if (is_watching) { + return; + } + update(); + is_watching = m_watcher->addPath(m_dir.absolutePath()); + if (is_watching) { + qDebug() << "Started watching " << m_dir.absolutePath(); + } else { + qDebug() << "Failed to start watching " << m_dir.absolutePath(); + } +} + +void SkinList::stopWatching() +{ + save(); + if (!is_watching) { + return; + } + is_watching = !m_watcher->removePath(m_dir.absolutePath()); + if (!is_watching) { + qDebug() << "Stopped watching " << m_dir.absolutePath(); + } else { + qDebug() << "Failed to stop watching " << m_dir.absolutePath(); + } +} + +bool SkinList::update() +{ + QVector newSkins; + m_dir.refresh(); + + auto manifestInfo = QFileInfo(m_dir.absoluteFilePath("index.json")); + if (manifestInfo.exists()) { + try { + auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "SkinList JSON file"); + const auto root = doc.object(); + auto skins = Json::ensureArray(root, "skins"); + for (auto jSkin : skins) { + SkinModel s(m_dir, Json::ensureObject(jSkin)); + if (s.isValid()) { + newSkins << s; + } + } + } catch (const Exception& e) { + qCritical() << "Couldn't load skins json:" << e.cause(); + } + } else { + newSkins = loadMinecraftSkins(); + } + + bool needsSave = false; + const auto& skin = m_acct->accountData()->minecraftProfile.skin; + if (!skin.url.isEmpty() && !skin.data.isEmpty()) { + QPixmap skinTexture; + SkinModel* nskin = nullptr; + for (auto i = 0; i < newSkins.size(); i++) { + if (newSkins[i].getURL() == skin.url) { + nskin = &newSkins[i]; + break; + } + } + if (!nskin) { + auto name = m_acct->profileName() + ".png"; + if (QFileInfo(m_dir.absoluteFilePath(name)).exists()) { + name = QUrl(skin.url).fileName() + ".png"; + } + auto path = m_dir.absoluteFilePath(name); + if (skinTexture.loadFromData(skin.data, "PNG") && skinTexture.save(path)) { + SkinModel s(path); + s.setModel(SkinModel::CLASSIC); // maybe better model detection + s.setCapeId(m_acct->accountData()->minecraftProfile.currentCape); + s.setURL(skin.url); + newSkins << s; + needsSave = true; + } + } else { + nskin->setCapeId(m_acct->accountData()->minecraftProfile.currentCape); + } + } + + auto folderContents = m_dir.entryInfoList(); + // if there are any untracked files... + for (QFileInfo entry : folderContents) { + if (!entry.isFile() && entry.suffix() != "png") + continue; + + SkinModel w(entry.absoluteFilePath()); + if (w.isValid()) { + auto add = true; + for (auto s : newSkins) { + if (s.name() == w.name()) { + add = false; + break; + } + } + if (add) { + newSkins.append(w); + needsSave = true; + } + } + } + std::sort(newSkins.begin(), newSkins.end(), + [](const SkinModel& a, const SkinModel& b) { return a.getPath().localeAwareCompare(b.getPath()) < 0; }); + beginResetModel(); + m_skin_list.swap(newSkins); + endResetModel(); + if (needsSave) + save(); + return true; +} + +void SkinList::directoryChanged(const QString& path) +{ + QDir new_dir(path); + if (!new_dir.exists()) + if (!FS::ensureFolderPathExists(new_dir.absolutePath())) + return; + if (m_dir.absolutePath() != new_dir.absolutePath()) { + m_dir.setPath(path); + m_dir.refresh(); + if (is_watching) + stopWatching(); + startWatching(); + } + update(); +} + +void SkinList::fileChanged(const QString& path) +{ + qDebug() << "Checking " << path; + QFileInfo checkfile(path); + if (!checkfile.exists()) + return; + + for (int i = 0; i < m_skin_list.count(); i++) { + if (m_skin_list[i].getPath() == checkfile.absoluteFilePath()) { + m_skin_list[i].refresh(); + dataChanged(index(i), index(i)); + break; + } + } +} + +QStringList SkinList::mimeTypes() const +{ + return { "text/uri-list" }; +} + +Qt::DropActions SkinList::supportedDropActions() const +{ + return Qt::CopyAction; +} + +bool SkinList::dropMimeData(const QMimeData* data, + Qt::DropAction action, + [[maybe_unused]] int row, + [[maybe_unused]] int column, + [[maybe_unused]] const QModelIndex& parent) +{ + if (action == Qt::IgnoreAction) + return true; + // check if the action is supported + if (!data || !(action & supportedDropActions())) + return false; + + // files dropped from outside? + if (data->hasUrls()) { + auto urls = data->urls(); + QStringList iconFiles; + for (auto url : urls) { + // only local files may be dropped... + if (!url.isLocalFile()) + continue; + iconFiles += url.toLocalFile(); + } + installSkins(iconFiles); + return true; + } + return false; +} + +Qt::ItemFlags SkinList::flags(const QModelIndex& index) const +{ + Qt::ItemFlags f = Qt::ItemIsDropEnabled | QAbstractListModel::flags(index); + if (index.isValid()) { + f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); + } + return f; +} + +QVariant SkinList::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + + if (row < 0 || row >= m_skin_list.size()) + return QVariant(); + auto skin = m_skin_list[row]; + switch (role) { + case Qt::DecorationRole: + return skin.getTexture(); + case Qt::DisplayRole: + return skin.name(); + case Qt::UserRole: + return skin.name(); + case Qt::EditRole: + return skin.name(); + default: + return QVariant(); + } +} + +int SkinList::rowCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : m_skin_list.size(); +} + +void SkinList::installSkins(const QStringList& iconFiles) +{ + for (QString file : iconFiles) + installSkin(file, {}); +} + +void SkinList::installSkin(const QString& file, const QString& name) +{ + QFileInfo fileinfo(file); + if (!fileinfo.isReadable() || !fileinfo.isFile()) + return; + + if (fileinfo.suffix() != "png" && !SkinModel(fileinfo.absoluteFilePath()).isValid()) + return; + + QString target = FS::PathCombine(m_dir.absolutePath(), name.isEmpty() ? fileinfo.fileName() : name); + QFile::copy(file, target); +} + +int SkinList::getSkinIndex(const QString& key) const +{ + for (int i = 0; i < m_skin_list.count(); i++) { + if (m_skin_list[i].name() == key) { + return i; + } + } + return -1; +} + +const SkinModel* SkinList::skin(const QString& key) const +{ + int idx = getSkinIndex(key); + if (idx == -1) + return nullptr; + return &m_skin_list[idx]; +} + +SkinModel* SkinList::skin(const QString& key) +{ + int idx = getSkinIndex(key); + if (idx == -1) + return nullptr; + return &m_skin_list[idx]; +} + +bool SkinList::deleteSkin(const QString& key, const bool trash) +{ + int idx = getSkinIndex(key); + if (idx != -1) { + auto s = m_skin_list[idx]; + if (trash) { + if (FS::trash(s.getPath(), nullptr)) { + m_skin_list.remove(idx); + return true; + } + } else if (QFile::remove(s.getPath())) { + m_skin_list.remove(idx); + return true; + } + } + return false; +} + +void SkinList::save() +{ + QJsonObject doc; + QJsonArray arr; + for (auto s : m_skin_list) { + arr << s.toJSON(); + } + doc["skins"] = arr; + Json::write(doc, m_dir.absoluteFilePath("index.json")); +} + +int SkinList::getSelectedAccountSkin() +{ + const auto& skin = m_acct->accountData()->minecraftProfile.skin; + for (int i = 0; i < m_skin_list.count(); i++) { + if (m_skin_list[i].getURL() == skin.url) { + return i; + } + } + return -1; +} + +bool SkinList::setData(const QModelIndex& idx, const QVariant& value, int role) +{ + if (!idx.isValid() || role != Qt::EditRole) { + return false; + } + + int row = idx.row(); + if (row < 0 || row >= m_skin_list.size()) + return false; + auto skin = m_skin_list[row]; + auto newName = value.toString(); + if (skin.name() != newName) { + skin.rename(newName); + save(); + } + return true; +} + +QVector SkinList::loadMinecraftSkins() +{ + QString partialPath; +#if defined(Q_OS_OSX) + partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support"); +#elif defined(Q_OS_WIN32) + partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", ""); +#else + partialPath = QDir::homePath(); +#endif + QVector newSkins; + auto path = FS::PathCombine(partialPath, ".minecraft", "launcher_custom_skins.json"); + auto manifestInfo = QFileInfo(path); + if (!manifestInfo.exists()) + return {}; + try { + auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "SkinList JSON file"); + const auto root = doc.object(); + auto skins = Json::ensureObject(root, "customSkins"); + for (auto key : skins.keys()) { + SkinModel s(m_dir, Json::ensureObject(skins, key)); + if (s.isValid()) { + newSkins << s; + } + } + } catch (const Exception& e) { + qCritical() << "Couldn't load minecraft skins json:" << e.cause(); + } + return newSkins; +} diff --git a/launcher/minecraft/skins/SkinList.h b/launcher/minecraft/skins/SkinList.h new file mode 100644 index 0000000000..8d8266d799 --- /dev/null +++ b/launcher/minecraft/skins/SkinList.h @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include + +#include "QObjectPtr.h" +#include "SkinModel.h" +#include "minecraft/auth/MinecraftAccount.h" + +class SkinList : public QAbstractListModel { + Q_OBJECT + public: + explicit SkinList(QObject* parent, QString path, MinecraftAccountPtr acct); + virtual ~SkinList() { save(); }; + + int getSkinIndex(const QString& key) const; + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex& idx, const QVariant& value, int role) override; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + + virtual QStringList mimeTypes() const override; + virtual Qt::DropActions supportedDropActions() const override; + virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; + virtual Qt::ItemFlags flags(const QModelIndex& index) const override; + + bool deleteSkin(const QString& key, const bool trash); + + void installSkins(const QStringList& iconFiles); + void installSkin(const QString& file, const QString& name); + + const SkinModel* skin(const QString& key) const; + SkinModel* skin(const QString& key); + + void startWatching(); + void stopWatching(); + + QString getDir() const { return m_dir.absolutePath(); } + void save(); + int getSelectedAccountSkin(); + + private: + // hide copy constructor + SkinList(const SkinList&) = delete; + // hide assign op + SkinList& operator=(const SkinList&) = delete; + + QVector loadMinecraftSkins(); + + protected slots: + void directoryChanged(const QString& path); + void fileChanged(const QString& path); + bool update(); + + private: + shared_qobject_ptr m_watcher; + bool is_watching; + QVector m_skin_list; + QDir m_dir; + MinecraftAccountPtr m_acct; +}; \ No newline at end of file diff --git a/launcher/minecraft/skins/SkinModel.cpp b/launcher/minecraft/skins/SkinModel.cpp new file mode 100644 index 0000000000..3b467019cd --- /dev/null +++ b/launcher/minecraft/skins/SkinModel.cpp @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "SkinModel.h" +#include +#include +#include +#include + +#include "FileSystem.h" +#include "Json.h" + +SkinModel::SkinModel(QString path) : m_path(path), m_texture(path), m_model(Model::CLASSIC) {} + +SkinModel::SkinModel(QDir skinDir, QJsonObject obj) + : m_cape_id(Json::ensureString(obj, "capeId")), m_model(Model::CLASSIC), m_url(Json::ensureString(obj, "url")) +{ + auto name = Json::ensureString(obj, "name"); + auto skinImage = Json::ensureString(obj, "skinImage"); + if (!skinImage.isEmpty()) { // minecraft skin model + skinImage = skinImage.mid(22); + m_texture.loadFromData(QByteArray::fromBase64(skinImage.toUtf8()), "PNG"); + auto textureId = Json::ensureString(obj, "textureId"); + if (name.isEmpty()) { + name = textureId; + } + if (Json::ensureBoolean(obj, "slim", false)) { + m_model = Model::SLIM; + } + } else { + if (auto model = Json::ensureString(obj, "model"); model == "SLIM") { + m_model = Model::SLIM; + } + } + m_path = skinDir.absoluteFilePath(name) + ".png"; + if (!QFileInfo(m_path).exists() && isValid()) { + m_texture.save(m_path, "PNG"); + } else { + m_texture = QPixmap(m_path); + } +} + +QString SkinModel::name() const +{ + return QFileInfo(m_path).baseName(); +} + +bool SkinModel::rename(QString newName) +{ + auto info = QFileInfo(m_path); + m_path = FS::PathCombine(info.absolutePath(), newName + ".png"); + return FS::move(info.absoluteFilePath(), m_path); +} + +QJsonObject SkinModel::toJSON() const +{ + QJsonObject obj; + obj["name"] = name(); + obj["capeId"] = m_cape_id; + obj["url"] = m_url; + obj["model"] = getModelString(); + return obj; +} + +QString SkinModel::getModelString() const +{ + switch (m_model) { + case CLASSIC: + return "CLASSIC"; + case SLIM: + return "SLIM"; + } + return {}; +} + +bool SkinModel::isValid() const +{ + return !m_texture.isNull() && (m_texture.size().height() == 32 || m_texture.size().height() == 64) && m_texture.size().width() == 64; +} + +QPixmap SkinModel::renderFrontBody() const +{ + auto isSlim = m_model == SLIM; + auto slimOffset = isSlim ? 1 : 0; + auto isOldSkin = m_texture.height() < 64; + + auto head = m_texture.copy(QRect(8, 8, 16, 16)); + auto torso = m_texture.copy(QRect(20, 20, 28, 32)); + auto rightArm = m_texture.copy(QRect(44, 20, 48 - slimOffset, 32)); + auto rightLeg = m_texture.copy(QRect(4, 20, 8, 32)); + QPixmap leftArm, leftLeg; + + if (isOldSkin) { + leftArm = rightArm.transformed(QTransform().scale(-1, 1)); + leftLeg = rightLeg.transformed(QTransform().scale(-1, 1)); + } else { + leftArm = m_texture.copy(QRect(36, 52, 40 - slimOffset, 64)); + leftLeg = m_texture.copy(QRect(20, 52, 24, 64)); + } + QPixmap output(16, 32); + output.fill(Qt::black); + QPainter p; + if (!p.begin(&output)) + return {}; + p.drawPixmap(QPoint(4, 0), head); + p.drawPixmap(QPoint(4, 8), torso); + p.drawPixmap(QPoint(12, 8), leftArm); + p.drawPixmap(QPoint(slimOffset, 8), rightArm); + p.drawPixmap(QPoint(8, 20), leftLeg); + p.drawPixmap(QPoint(4, 20), leftArm); + + return output.scaled(128, 128, Qt::KeepAspectRatioByExpanding, Qt::FastTransformation); +} \ No newline at end of file diff --git a/launcher/minecraft/skins/SkinModel.h b/launcher/minecraft/skins/SkinModel.h new file mode 100644 index 0000000000..6d135c7f7d --- /dev/null +++ b/launcher/minecraft/skins/SkinModel.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include + +class SkinModel { + public: + enum Model { CLASSIC, SLIM }; + + SkinModel(QString path); + SkinModel(QDir skinDir, QJsonObject obj); + virtual ~SkinModel() = default; + + QString name() const; + QString getModelString() const; + bool isValid() const; + QString getPath() const { return m_path; } + QPixmap getTexture() const { return m_texture; } + QString getCapeId() const { return m_cape_id; } + Model getModel() const { return m_model; } + QString getURL() const { return m_url; } + + bool rename(QString newName); + void setCapeId(QString capeID) { m_cape_id = capeID; } + void setModel(Model model) { m_model = model; } + void setURL(QString url) { m_url = url; } + void refresh() { m_texture = QPixmap(m_path); } + + QJsonObject toJSON() const; + + QPixmap renderFrontBody() const; + + private: + QString m_path; + QPixmap m_texture; + QString m_cape_id; + Model m_model; + QString m_url; +}; \ No newline at end of file diff --git a/launcher/minecraft/services/SkinUpload.cpp b/launcher/minecraft/skins/SkinUpload.cpp similarity index 79% rename from launcher/minecraft/services/SkinUpload.cpp rename to launcher/minecraft/skins/SkinUpload.cpp index 0400fa0f4b..4e56bd7e69 100644 --- a/launcher/minecraft/services/SkinUpload.cpp +++ b/launcher/minecraft/skins/SkinUpload.cpp @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,12 +38,13 @@ #include +#include "FileSystem.h" #include "net/ByteArraySink.h" #include "net/StaticHeaderProxy.h" -SkinUpload::SkinUpload(QString token, QByteArray skin, SkinUpload::Model model) : NetRequest(), m_model(model), m_skin(skin), m_token(token) +SkinUpload::SkinUpload(QString token, SkinModel* skin) : NetRequest(), m_skin(skin), m_token(token) { - logCat = taskMCServicesLogC; + logCat = taskUploadLogC; }; QNetworkReply* SkinUpload::getReply(QNetworkRequest& request) @@ -52,23 +54,12 @@ QNetworkReply* SkinUpload::getReply(QNetworkRequest& request) QHttpPart skin; skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png")); skin.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"skin.png\"")); - skin.setBody(m_skin); + + skin.setBody(FS::read(m_skin->getPath())); QHttpPart model; model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"variant\"")); - - switch (m_model) { - default: - qDebug() << "Unknown skin type!"; - emitFailed("Unknown skin type!"); - return nullptr; - case SkinUpload::STEVE: - model.setBody("CLASSIC"); - break; - case SkinUpload::ALEX: - model.setBody("SLIM"); - break; - } + model.setBody(m_skin->getModelString().toUtf8()); multiPart->append(skin); multiPart->append(model); @@ -83,10 +74,11 @@ void SkinUpload::init() })); } -SkinUpload::Ptr SkinUpload::make(QString token, QByteArray skin, SkinUpload::Model model) +SkinUpload::Ptr SkinUpload::make(QString token, SkinModel* skin) { - auto up = makeShared(token, skin, model); + auto up = makeShared(token, skin); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins"); + up->setObjectName(QString("BYTES:") + up->m_url.toString()); up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); return up; } diff --git a/launcher/minecraft/skins/SkinUpload.h b/launcher/minecraft/skins/SkinUpload.h new file mode 100644 index 0000000000..d070f301d3 --- /dev/null +++ b/launcher/minecraft/skins/SkinUpload.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "minecraft/skins/SkinModel.h" +#include "net/NetRequest.h" + +class SkinUpload : public Net::NetRequest { + Q_OBJECT + public: + using Ptr = shared_qobject_ptr; + + // Note this class takes ownership of the file. + SkinUpload(QString token, SkinModel* skin); + virtual ~SkinUpload() = default; + + static SkinUpload::Ptr make(QString token, SkinModel* skin); + void init() override; + + protected: + virtual QNetworkReply* getReply(QNetworkRequest&) override; + + private: + SkinModel* m_skin; + QString m_token; +}; diff --git a/launcher/net/Logging.cpp b/launcher/net/Logging.cpp index 45d2dcc209..cd0c88d3ca 100644 --- a/launcher/net/Logging.cpp +++ b/launcher/net/Logging.cpp @@ -22,6 +22,6 @@ Q_LOGGING_CATEGORY(taskNetLogC, "launcher.task.net") Q_LOGGING_CATEGORY(taskDownloadLogC, "launcher.task.net.download") Q_LOGGING_CATEGORY(taskUploadLogC, "launcher.task.net.upload") -Q_LOGGING_CATEGORY(taskMCServicesLogC, "launcher.task.minecraft.servicies") +Q_LOGGING_CATEGORY(taskMCSkinsLogC, "launcher.task.minecraft.skins") Q_LOGGING_CATEGORY(taskMetaCacheLogC, "launcher.task.net.metacache") Q_LOGGING_CATEGORY(taskHttpMetaCacheLogC, "launcher.task.net.metacache.http") diff --git a/launcher/net/Logging.h b/launcher/net/Logging.h index d3a11cdce2..2536f31aa8 100644 --- a/launcher/net/Logging.h +++ b/launcher/net/Logging.h @@ -24,6 +24,6 @@ Q_DECLARE_LOGGING_CATEGORY(taskNetLogC) Q_DECLARE_LOGGING_CATEGORY(taskDownloadLogC) Q_DECLARE_LOGGING_CATEGORY(taskUploadLogC) -Q_DECLARE_LOGGING_CATEGORY(taskMCServicesLogC) +Q_DECLARE_LOGGING_CATEGORY(taskMCSkinsLogC) Q_DECLARE_LOGGING_CATEGORY(taskMetaCacheLogC) Q_DECLARE_LOGGING_CATEGORY(taskHttpMetaCacheLogC) diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h index cc63f4497d..26791bc0d1 100644 --- a/launcher/net/NetJob.h +++ b/launcher/net/NetJob.h @@ -52,8 +52,8 @@ class NetJob : public ConcurrentTask { public: using Ptr = shared_qobject_ptr; - explicit NetJob(QString job_name, shared_qobject_ptr network) - : ConcurrentTask(nullptr, job_name), m_network(network) + explicit NetJob(QString job_name, shared_qobject_ptr network, int max_concurrent = 6) + : ConcurrentTask(nullptr, job_name, max_concurrent), m_network(network) {} ~NetJob() override = default; diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index eef550e15a..8538735286 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -5,6 +5,7 @@ * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2023 TheKodeToad * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/net/NetRequest.h b/launcher/net/NetRequest.h index ee47ab2a65..917495ed91 100644 --- a/launcher/net/NetRequest.h +++ b/launcher/net/NetRequest.h @@ -4,6 +4,7 @@ * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/net/StaticHeaderProxy.h b/launcher/net/StaticHeaderProxy.h index 0e62d80ffd..aabbc9c92d 100644 --- a/launcher/net/StaticHeaderProxy.h +++ b/launcher/net/StaticHeaderProxy.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index a82932e081..4858e7d466 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -77,7 +77,6 @@ #include #include #include -#include #include #include #include diff --git a/launcher/ui/dialogs/ProfileSelectDialog.cpp b/launcher/ui/dialogs/ProfileSelectDialog.cpp index a62238bdbd..fe03e1b6b3 100644 --- a/launcher/ui/dialogs/ProfileSelectDialog.cpp +++ b/launcher/ui/dialogs/ProfileSelectDialog.cpp @@ -20,7 +20,6 @@ #include #include "Application.h" -#include "SkinUtils.h" #include "ui/dialogs/ProgressDialog.h" diff --git a/launcher/ui/dialogs/SkinUploadDialog.cpp b/launcher/ui/dialogs/SkinUploadDialog.cpp deleted file mode 100644 index 70f1e67606..0000000000 --- a/launcher/ui/dialogs/SkinUploadDialog.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * 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 -#include -#include -#include - -#include - -#include -#include -#include - -#include "CustomMessageBox.h" -#include "ProgressDialog.h" -#include "SkinUploadDialog.h" -#include "ui_SkinUploadDialog.h" - -void SkinUploadDialog::on_buttonBox_rejected() -{ - close(); -} - -void SkinUploadDialog::on_buttonBox_accepted() -{ - QString fileName; - QString input = ui->skinPathTextBox->text(); - ProgressDialog prog(this); - SequentialTask skinUpload; - - if (!input.isEmpty()) { - QRegularExpression urlPrefixMatcher(QRegularExpression::anchoredPattern("^([a-z]+)://.+$")); - bool isLocalFile = false; - // it has an URL prefix -> it is an URL - if (urlPrefixMatcher.match(input).hasMatch()) { - QUrl fileURL = input; - if (fileURL.isValid()) { - // local? - if (fileURL.isLocalFile()) { - isLocalFile = true; - fileName = fileURL.toLocalFile(); - } else { - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Using remote URLs for setting skins is not implemented yet."), - QMessageBox::Warning) - ->exec(); - close(); - return; - } - } else { - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("You cannot use an invalid URL for uploading skins."), - QMessageBox::Warning) - ->exec(); - close(); - return; - } - } else { - // just assume it's a path then - isLocalFile = true; - fileName = ui->skinPathTextBox->text(); - } - if (isLocalFile && !QFile::exists(fileName)) { - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec(); - close(); - return; - } - SkinUpload::Model model = SkinUpload::STEVE; - if (ui->steveBtn->isChecked()) { - model = SkinUpload::STEVE; - } else if (ui->alexBtn->isChecked()) { - model = SkinUpload::ALEX; - } - skinUpload.addTask(SkinUpload::make(m_acct->accessToken(), FS::read(fileName), model)); - } - - auto selectedCape = ui->capeCombo->currentData().toString(); - if (selectedCape != m_acct->accountData()->minecraftProfile.currentCape) { - skinUpload.addTask(CapeChange::make(m_acct->accessToken(), selectedCape)); - } - if (prog.execWithTask(&skinUpload) != QDialog::Accepted) { - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec(); - close(); - return; - } - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Success"), QMessageBox::Information)->exec(); - close(); -} - -void SkinUploadDialog::on_skinBrowseBtn_clicked() -{ - auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString(); - QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter); - if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) { - return; - } - QString cooked_path = FS::NormalizePath(raw_path); - ui->skinPathTextBox->setText(cooked_path); -} - -SkinUploadDialog::SkinUploadDialog(MinecraftAccountPtr acct, QWidget* parent) : QDialog(parent), m_acct(acct), ui(new Ui::SkinUploadDialog) -{ - ui->setupUi(this); - - // FIXME: add a model for this, download/refresh the capes on demand - auto& accountData = *acct->accountData(); - int index = 0; - ui->capeCombo->addItem(tr("No Cape"), QVariant()); - auto currentCape = accountData.minecraftProfile.currentCape; - if (currentCape.isEmpty()) { - ui->capeCombo->setCurrentIndex(index); - } - - for (auto& cape : accountData.minecraftProfile.capes) { - index++; - if (cape.data.size()) { - QPixmap capeImage; - if (capeImage.loadFromData(cape.data, "PNG")) { - QPixmap preview = QPixmap(10, 16); - QPainter painter(&preview); - painter.drawPixmap(0, 0, capeImage.copy(1, 1, 10, 16)); - ui->capeCombo->addItem(capeImage, cape.alias, cape.id); - if (currentCape == cape.id) { - ui->capeCombo->setCurrentIndex(index); - } - continue; - } - } - ui->capeCombo->addItem(cape.alias, cape.id); - if (currentCape == cape.id) { - ui->capeCombo->setCurrentIndex(index); - } - } -} diff --git a/launcher/ui/dialogs/SkinUploadDialog.h b/launcher/ui/dialogs/SkinUploadDialog.h deleted file mode 100644 index 81d6140cc4..0000000000 --- a/launcher/ui/dialogs/SkinUploadDialog.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include - -namespace Ui { -class SkinUploadDialog; -} - -class SkinUploadDialog : public QDialog { - Q_OBJECT - public: - explicit SkinUploadDialog(MinecraftAccountPtr acct, QWidget* parent = 0); - virtual ~SkinUploadDialog(){}; - - public slots: - void on_buttonBox_accepted(); - - void on_buttonBox_rejected(); - - void on_skinBrowseBtn_clicked(); - - protected: - MinecraftAccountPtr m_acct; - - private: - Ui::SkinUploadDialog* ui; -}; diff --git a/launcher/ui/dialogs/SkinUploadDialog.ui b/launcher/ui/dialogs/SkinUploadDialog.ui deleted file mode 100644 index c6df92df37..0000000000 --- a/launcher/ui/dialogs/SkinUploadDialog.ui +++ /dev/null @@ -1,95 +0,0 @@ - - - SkinUploadDialog - - - - 0 - 0 - 394 - 360 - - - - Skin Upload - - - - - - Skin File - - - - - - Leave empty to keep current skin - - - - - - - - 0 - 0 - - - - Browse - - - - - - - - - - Player Model - - - - - - Steve Model - - - true - - - - - - - Alex Model - - - - - - - - - - Cape - - - - - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp new file mode 100644 index 0000000000..1ba7e7055d --- /dev/null +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "Application.h" +#include "DesktopServices.h" +#include "QObjectPtr.h" +#include "SkinManageDialog.h" + +#include "minecraft/auth/AccountTask.h" +#include "minecraft/skins/CapeChange.h" +#include "minecraft/skins/SkinDelete.h" +#include "minecraft/skins/SkinList.h" +#include "minecraft/skins/SkinModel.h" +#include "minecraft/skins/SkinUpload.h" + +#include "net/NetJob.h" +#include "tasks/Task.h" + +#include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/ProgressDialog.h" +#include "ui/instanceview/InstanceDelegate.h" +#include "ui_SkinManageDialog.h" + +SkinManageDialog::SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct) + : QDialog(parent), m_acct(acct), ui(new Ui::SkinManageDialog), m_list(this, APPLICATION->settings()->get("SkinsDir").toString(), acct) +{ + ui->setupUi(this); + + setWindowModality(Qt::WindowModal); + + auto contentsWidget = ui->listView; + contentsWidget->setViewMode(QListView::IconMode); + contentsWidget->setFlow(QListView::LeftToRight); + contentsWidget->setIconSize(QSize(48, 48)); + contentsWidget->setMovement(QListView::Static); + contentsWidget->setResizeMode(QListView::Adjust); + contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection); + contentsWidget->setSpacing(5); + contentsWidget->setWordWrap(false); + contentsWidget->setWrapping(true); + contentsWidget->setUniformItemSizes(true); + contentsWidget->setTextElideMode(Qt::ElideRight); + contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + contentsWidget->installEventFilter(this); + contentsWidget->setItemDelegate(new ListViewDelegate()); + + contentsWidget->setAcceptDrops(true); + contentsWidget->setDropIndicatorShown(true); + contentsWidget->viewport()->setAcceptDrops(true); + contentsWidget->setDragDropMode(QAbstractItemView::DropOnly); + contentsWidget->setDefaultDropAction(Qt::CopyAction); + + contentsWidget->installEventFilter(this); + contentsWidget->setModel(&m_list); + + connect(contentsWidget, SIGNAL(doubleClicked(QModelIndex)), SLOT(activated(QModelIndex))); + + connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + SLOT(selectionChanged(QItemSelection, QItemSelection))); + connect(ui->listView, &QListView::customContextMenuRequested, this, &SkinManageDialog::show_context_menu); + + setupCapes(); + + ui->listView->setCurrentIndex(m_list.index(m_list.getSelectedAccountSkin())); +} + +SkinManageDialog::~SkinManageDialog() +{ + delete ui; +} + +void SkinManageDialog::activated(QModelIndex index) +{ + m_selected_skin = index.data(Qt::UserRole).toString(); + accept(); +} + +void SkinManageDialog::selectionChanged(QItemSelection selected, QItemSelection deselected) +{ + if (selected.empty()) + return; + + QString key = selected.first().indexes().first().data(Qt::UserRole).toString(); + if (key.isEmpty()) + return; + m_selected_skin = key; + auto skin = m_list.skin(key); + if (!skin) + return; + ui->selectedModel->setPixmap(skin->getTexture().scaled(128, 128, Qt::KeepAspectRatio, Qt::FastTransformation)); + ui->capeCombo->setCurrentIndex(m_capes_idx.value(skin->getCapeId())); + ui->steveBtn->setChecked(skin->getModel() == SkinModel::CLASSIC); + ui->alexBtn->setChecked(skin->getModel() == SkinModel::SLIM); +} + +void SkinManageDialog::delayed_scroll(QModelIndex model_index) +{ + auto contentsWidget = ui->listView; + contentsWidget->scrollTo(model_index); +} + +void SkinManageDialog::on_openDirBtn_clicked() +{ + DesktopServices::openDirectory(m_list.getDir(), true); +} + +void SkinManageDialog::on_addBtn_clicked() +{ + auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString(); + QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter); + if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) { + return; + } + if (!SkinModel(raw_path).isValid()) { + CustomMessageBox::selectable(this, tr("Selected file is not a valid skin"), + tr("Skin images must be 64x64 or 64x32 pixel PNG files."), QMessageBox::Critical) + ->show(); + return; + } + m_list.installSkin(raw_path, {}); +} + +QPixmap previewCape(QPixmap capeImage) +{ + QPixmap preview = QPixmap(10, 16); + QPainter painter(&preview); + painter.drawPixmap(0, 0, capeImage.copy(1, 1, 10, 16)); + return preview.scaled(80, 128, Qt::IgnoreAspectRatio, Qt::FastTransformation); +} + +void SkinManageDialog::setupCapes() +{ + // FIXME: add a model for this, download/refresh the capes on demand + auto& accountData = *m_acct->accountData(); + int index = 0; + ui->capeCombo->addItem(tr("No Cape"), QVariant()); + auto currentCape = accountData.minecraftProfile.currentCape; + if (currentCape.isEmpty()) { + ui->capeCombo->setCurrentIndex(index); + } + + auto capesDir = FS::PathCombine(m_list.getDir(), "capes"); + NetJob::Ptr job{ new NetJob(tr("Download capes"), APPLICATION->network()) }; + bool needsToDownload = false; + for (auto& cape : accountData.minecraftProfile.capes) { + auto path = FS::PathCombine(capesDir, cape.id + ".png"); + if (cape.data.size()) { + QPixmap capeImage; + if (capeImage.loadFromData(cape.data, "PNG") && capeImage.save(path)) { + m_capes[cape.id] = previewCape(capeImage); + continue; + } + } + if (QFileInfo(path).exists()) { + continue; + } + if (!cape.url.isEmpty()) { + needsToDownload = true; + job->addNetAction(Net::Download::makeFile(cape.url, path)); + } + } + if (needsToDownload) { + ProgressDialog dlg(this); + dlg.execWithTask(job.get()); + } + for (auto& cape : accountData.minecraftProfile.capes) { + index++; + QPixmap capeImage; + if (!m_capes.contains(cape.id)) { + auto path = FS::PathCombine(capesDir, cape.id + ".png"); + if (QFileInfo(path).exists() && capeImage.load(path)) { + capeImage = previewCape(capeImage); + m_capes[cape.id] = capeImage; + } + } + if (!capeImage.isNull()) { + ui->capeCombo->addItem(capeImage, cape.alias, cape.id); + } else { + ui->capeCombo->addItem(cape.alias, cape.id); + } + + m_capes_idx[cape.id] = index; + } +} + +void SkinManageDialog::on_capeCombo_currentIndexChanged(int index) +{ + auto id = ui->capeCombo->currentData(); + ui->capeImage->setPixmap(m_capes.value(id.toString(), {})); + if (auto skin = m_list.skin(m_selected_skin); skin) { + skin->setCapeId(id.toString()); + } +} + +void SkinManageDialog::on_steveBtn_toggled(bool checked) +{ + if (auto skin = m_list.skin(m_selected_skin); skin) { + skin->setModel(checked ? SkinModel::CLASSIC : SkinModel::SLIM); + } +} + +void SkinManageDialog::accept() +{ + auto skin = m_list.skin(m_selected_skin); + if (!skin) + reject(); + auto path = skin->getPath(); + + ProgressDialog prog(this); + NetJob::Ptr skinUpload{ new NetJob(tr("Change skin"), APPLICATION->network(), 1) }; + + if (!QFile::exists(path)) { + CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec(); + reject(); + return; + } + + skinUpload->addNetAction(SkinUpload::make(m_acct->accessToken(), skin)); + + auto selectedCape = skin->getCapeId(); + if (selectedCape != m_acct->accountData()->minecraftProfile.currentCape) { + skinUpload->addNetAction(CapeChange::make(m_acct->accessToken(), selectedCape)); + } + + skinUpload->addTask(m_acct->refresh().staticCast()); + if (prog.execWithTask(skinUpload.get()) != QDialog::Accepted) { + CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec(); + reject(); + return; + } + skin->setURL(m_acct->accountData()->minecraftProfile.skin.url); + QDialog::accept(); +} + +void SkinManageDialog::on_resetBtn_clicked() +{ + ProgressDialog prog(this); + NetJob::Ptr skinReset{ new NetJob(tr("Reset skin"), APPLICATION->network(), 1) }; + skinReset->addNetAction(SkinDelete::make(m_acct->accessToken())); + skinReset->addTask(m_acct->refresh().staticCast()); + if (prog.execWithTask(skinReset.get()) != QDialog::Accepted) { + CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to delete current skin!"), QMessageBox::Warning)->exec(); + reject(); + return; + } + QDialog::accept(); +} + +void SkinManageDialog::show_context_menu(const QPoint& pos) +{ + QMenu myMenu(tr("Context menu"), this); + myMenu.addAction(ui->action_Rename_Skin); + myMenu.addAction(ui->action_Delete_Skin); + + myMenu.exec(ui->listView->mapToGlobal(pos)); +} + +bool SkinManageDialog::eventFilter(QObject* obj, QEvent* ev) +{ + if (obj == ui->listView) { + if (ev->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(ev); + switch (keyEvent->key()) { + case Qt::Key_Delete: + on_action_Delete_Skin_triggered(false); + return true; + case Qt::Key_F2: + on_action_Rename_Skin_triggered(false); + return true; + default: + break; + } + } + } + return QDialog::eventFilter(obj, ev); +} + +void SkinManageDialog::on_action_Rename_Skin_triggered(bool checked) +{ + if (!m_selected_skin.isEmpty()) { + ui->listView->edit(ui->listView->currentIndex()); + } +} + +void SkinManageDialog::on_action_Delete_Skin_triggered(bool checked) +{ + if (m_selected_skin.isEmpty()) + return; + + if (m_list.getSkinIndex(m_selected_skin) == m_list.getSelectedAccountSkin()) { + CustomMessageBox::selectable(this, tr("Delete error"), tr("Can not delete skin that is in use."), QMessageBox::Warning); + return; + } + + auto skin = m_list.skin(m_selected_skin); + if (!skin) + return; + + auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"), + tr("You are about to delete \"%1\".\n" + "Are you sure?") + .arg(skin->name()), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response == QMessageBox::Yes) { + if (!m_list.deleteSkin(m_selected_skin, true)) { + m_list.deleteSkin(m_selected_skin, false); + } + } +} diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.h b/launcher/ui/dialogs/skins/SkinManageDialog.h new file mode 100644 index 0000000000..8c55c33101 --- /dev/null +++ b/launcher/ui/dialogs/skins/SkinManageDialog.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include + +#include "minecraft/auth/MinecraftAccount.h" +#include "minecraft/skins/SkinList.h" + +namespace Ui { +class SkinManageDialog; +} + +class SkinManageDialog : public QDialog { + Q_OBJECT + public: + explicit SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct); + virtual ~SkinManageDialog(); + + public slots: + void selectionChanged(QItemSelection, QItemSelection); + void activated(QModelIndex); + void delayed_scroll(QModelIndex); + void on_openDirBtn_clicked(); + void on_addBtn_clicked(); + void accept() override; + void on_capeCombo_currentIndexChanged(int index); + void on_steveBtn_toggled(bool checked); + void on_resetBtn_clicked(); + void show_context_menu(const QPoint& pos); + bool eventFilter(QObject* obj, QEvent* ev) override; + void on_action_Rename_Skin_triggered(bool checked); + void on_action_Delete_Skin_triggered(bool checked); + + private: + void setupCapes(); + + MinecraftAccountPtr m_acct; + Ui::SkinManageDialog* ui; + SkinList m_list; + QString m_selected_skin; + QHash m_capes; + QHash m_capes_idx; +}; diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.ui b/launcher/ui/dialogs/skins/SkinManageDialog.ui new file mode 100644 index 0000000000..6ad826478f --- /dev/null +++ b/launcher/ui/dialogs/skins/SkinManageDialog.ui @@ -0,0 +1,193 @@ + + + SkinManageDialog + + + + 0 + 0 + 968 + 757 + + + + Skin Upload + + + + + + + + + + + + + true + + + + + + + + 0 + 0 + + + + Model + + + + + + Clasic + + + true + + + + + + + Slim + + + + + + + + + + Cape + + + + + + + + + + + + true + + + + + + + + + + + + Qt::CustomContextMenu + + + false + + + 0 + + + + + + + + + + + Open Folder + + + + + + + Import Skin + + + + + + + Reset Skin + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + &Delete Skin + + + Deletes selected skin + + + Del + + + + + &Rename Skin + + + Rename selected skin + + + F2 + + + + + + + buttonBox + rejected() + SkinManageDialog + reject() + + + 617 + 736 + + + 483 + 378 + + + + + buttonBox + accepted() + SkinManageDialog + accept() + + + 617 + 736 + + + 483 + 378 + + + + + diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index 3dcf05e0a0..25ccfd0d7e 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -35,6 +35,7 @@ */ #include "AccountListPage.h" +#include "ui/dialogs/skins/SkinManageDialog.h" #include "ui_AccountListPage.h" #include @@ -42,23 +43,13 @@ #include -#include "net/NetJob.h" - #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/LoginDialog.h" #include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/OfflineLoginDialog.h" -#include "ui/dialogs/ProgressDialog.h" -#include "ui/dialogs/SkinUploadDialog.h" - -#include "minecraft/auth/AccountTask.h" -#include "minecraft/services/SkinDelete.h" -#include "tasks/Task.h" #include "Application.h" -#include "BuildConfig.h" - AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new Ui::AccountListPage) { ui->setupUi(this); @@ -235,8 +226,7 @@ void AccountListPage::updateButtonStates() } ui->actionRemove->setEnabled(accountIsReady); ui->actionSetDefault->setEnabled(accountIsReady); - ui->actionUploadSkin->setEnabled(accountIsReady && accountIsOnline); - ui->actionDeleteSkin->setEnabled(accountIsReady && accountIsOnline); + ui->actionManageSkins->setEnabled(accountIsReady && accountIsOnline); ui->actionRefresh->setEnabled(accountIsReady && accountIsOnline); if (m_accounts->defaultAccount().get() == nullptr) { @@ -248,29 +238,13 @@ void AccountListPage::updateButtonStates() } } -void AccountListPage::on_actionUploadSkin_triggered() +void AccountListPage::on_actionManageSkins_triggered() { QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); if (selection.size() > 0) { QModelIndex selected = selection.first(); MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value(); - SkinUploadDialog dialog(account, this); + SkinManageDialog dialog(this, account); dialog.exec(); } } - -void AccountListPage::on_actionDeleteSkin_triggered() -{ - QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.size() <= 0) - return; - - QModelIndex selected = selection.first(); - MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value(); - ProgressDialog prog(this); - auto deleteSkinTask = SkinDelete::make(account->accessToken()); - if (prog.execWithTask((Task*)deleteSkinTask.get()) != QDialog::Accepted) { - CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to delete current skin!"), QMessageBox::Warning)->exec(); - return; - } -} diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h index add0f4aa00..64702cff7a 100644 --- a/launcher/ui/pages/global/AccountListPage.h +++ b/launcher/ui/pages/global/AccountListPage.h @@ -77,8 +77,7 @@ class AccountListPage : public QMainWindow, public BasePage { void on_actionRefresh_triggered(); void on_actionSetDefault_triggered(); void on_actionNoDefault_triggered(); - void on_actionUploadSkin_triggered(); - void on_actionDeleteSkin_triggered(); + void on_actionManageSkins_triggered(); void listChanged(); diff --git a/launcher/ui/pages/global/AccountListPage.ui b/launcher/ui/pages/global/AccountListPage.ui index 469955b51d..0e73f8c520 100644 --- a/launcher/ui/pages/global/AccountListPage.ui +++ b/launcher/ui/pages/global/AccountListPage.ui @@ -60,19 +60,13 @@ - - + Add &Mojang - - - Remo&ve - - &Set Default @@ -86,17 +80,12 @@ &No Default - + - &Upload Skin - - - - - &Delete Skin + &Manage Skins - Delete the currently active skin and go back to the default one + Manage Skins @@ -117,6 +106,11 @@ Refresh the account tokens + + + Remo&ve + + From 8bad255a9191cd76808a73942da366c981643d35 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 5 Sep 2023 20:13:16 +0300 Subject: [PATCH 0039/2054] added more import options Signed-off-by: Trial97 --- launcher/minecraft/skins/CapeChange.cpp | 2 +- launcher/minecraft/skins/SkinDelete.cpp | 2 +- launcher/minecraft/skins/SkinList.cpp | 74 ++++---- launcher/minecraft/skins/SkinList.h | 6 +- launcher/minecraft/skins/SkinModel.cpp | 58 +------ launcher/minecraft/skins/SkinModel.h | 3 +- launcher/minecraft/skins/SkinUpload.cpp | 4 +- launcher/net/NetAction.h | 1 + .../ui/dialogs/skins/SkinManageDialog.cpp | 158 ++++++++++++++++-- launcher/ui/dialogs/skins/SkinManageDialog.h | 4 +- launcher/ui/dialogs/skins/SkinManageDialog.ui | 37 +++- 11 files changed, 226 insertions(+), 123 deletions(-) diff --git a/launcher/minecraft/skins/CapeChange.cpp b/launcher/minecraft/skins/CapeChange.cpp index 863e89844f..4db28e2457 100644 --- a/launcher/minecraft/skins/CapeChange.cpp +++ b/launcher/minecraft/skins/CapeChange.cpp @@ -44,7 +44,7 @@ CapeChange::CapeChange(QString token, QString cape) : NetRequest(), m_capeId(cape), m_token(token) { logCat = taskMCSkinsLogC; -}; +} QNetworkReply* CapeChange::getReply(QNetworkRequest& request) { diff --git a/launcher/minecraft/skins/SkinDelete.cpp b/launcher/minecraft/skins/SkinDelete.cpp index 982cac1b71..3c50cf3136 100644 --- a/launcher/minecraft/skins/SkinDelete.cpp +++ b/launcher/minecraft/skins/SkinDelete.cpp @@ -42,7 +42,7 @@ SkinDelete::SkinDelete(QString token) : NetRequest(), m_token(token) { logCat = taskMCSkinsLogC; -}; +} QNetworkReply* SkinDelete::getReply(QNetworkRequest& request) { diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index be329564bc..15d0b0a8e7 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -85,8 +85,6 @@ bool SkinList::update() } catch (const Exception& e) { qCritical() << "Couldn't load skins json:" << e.cause(); } - } else { - newSkins = loadMinecraftSkins(); } bool needsSave = false; @@ -108,7 +106,7 @@ bool SkinList::update() auto path = m_dir.absoluteFilePath(name); if (skinTexture.loadFromData(skin.data, "PNG") && skinTexture.save(path)) { SkinModel s(path); - s.setModel(SkinModel::CLASSIC); // maybe better model detection + s.setModel(skin.variant == "slim" ? SkinModel::SLIM : SkinModel::CLASSIC); s.setCapeId(m_acct->accountData()->minecraftProfile.currentCape); s.setURL(skin.url); newSkins << s; @@ -116,6 +114,7 @@ bool SkinList::update() } } else { nskin->setCapeId(m_acct->accountData()->minecraftProfile.currentCape); + nskin->setModel(skin.variant == "slim" ? SkinModel::SLIM : SkinModel::CLASSIC); } } @@ -207,14 +206,14 @@ bool SkinList::dropMimeData(const QMimeData* data, // files dropped from outside? if (data->hasUrls()) { auto urls = data->urls(); - QStringList iconFiles; + QStringList skinFiles; for (auto url : urls) { // only local files may be dropped... if (!url.isLocalFile()) continue; - iconFiles += url.toLocalFile(); + skinFiles << url.toLocalFile(); } - installSkins(iconFiles); + installSkins(skinFiles); return true; } return false; @@ -261,20 +260,26 @@ int SkinList::rowCount(const QModelIndex& parent) const void SkinList::installSkins(const QStringList& iconFiles) { for (QString file : iconFiles) - installSkin(file, {}); + installSkin(file); } -void SkinList::installSkin(const QString& file, const QString& name) +QString SkinList::installSkin(const QString& file, const QString& name) { + if (file.isEmpty()) + return tr("Path is empty."); QFileInfo fileinfo(file); - if (!fileinfo.isReadable() || !fileinfo.isFile()) - return; - + if (!fileinfo.exists()) + return tr("File doesn't exist."); + if (!fileinfo.isFile()) + return tr("Not a file."); + if (!fileinfo.isReadable()) + return tr("File is not readable."); if (fileinfo.suffix() != "png" && !SkinModel(fileinfo.absoluteFilePath()).isValid()) - return; + return tr("Skin images must be 64x64 or 64x32 pixel PNG files."); QString target = FS::PathCombine(m_dir.absolutePath(), name.isEmpty() ? fileinfo.fileName() : name); - QFile::copy(file, target); + + return QFile::copy(file, target) ? "" : tr("Unable to copy file"); } int SkinList::getSkinIndex(const QString& key) const @@ -311,10 +316,12 @@ bool SkinList::deleteSkin(const QString& key, const bool trash) if (trash) { if (FS::trash(s.getPath(), nullptr)) { m_skin_list.remove(idx); + save(); return true; } } else if (QFile::remove(s.getPath())) { m_skin_list.remove(idx); + save(); return true; } } @@ -361,33 +368,22 @@ bool SkinList::setData(const QModelIndex& idx, const QVariant& value, int role) return true; } -QVector SkinList::loadMinecraftSkins() +void SkinList::updateSkin(SkinModel s) { - QString partialPath; -#if defined(Q_OS_OSX) - partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support"); -#elif defined(Q_OS_WIN32) - partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", ""); -#else - partialPath = QDir::homePath(); -#endif - QVector newSkins; - auto path = FS::PathCombine(partialPath, ".minecraft", "launcher_custom_skins.json"); - auto manifestInfo = QFileInfo(path); - if (!manifestInfo.exists()) - return {}; - try { - auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "SkinList JSON file"); - const auto root = doc.object(); - auto skins = Json::ensureObject(root, "customSkins"); - for (auto key : skins.keys()) { - SkinModel s(m_dir, Json::ensureObject(skins, key)); - if (s.isValid()) { - newSkins << s; - } + auto done = false; + for (auto i = 0; i < m_skin_list.size(); i++) { + if (m_skin_list[i].getPath() == s.getPath()) { + m_skin_list[i].setCapeId(s.getCapeId()); + m_skin_list[i].setModel(s.getModel()); + m_skin_list[i].setURL(s.getURL()); + done = true; + break; } - } catch (const Exception& e) { - qCritical() << "Couldn't load minecraft skins json:" << e.cause(); } - return newSkins; + if (!done) { + beginInsertRows(QModelIndex(), m_skin_list.count(), m_skin_list.count() + 1); + m_skin_list.append(s); + endInsertRows(); + } + save(); } diff --git a/launcher/minecraft/skins/SkinList.h b/launcher/minecraft/skins/SkinList.h index 8d8266d799..b6981e1b49 100644 --- a/launcher/minecraft/skins/SkinList.h +++ b/launcher/minecraft/skins/SkinList.h @@ -46,7 +46,7 @@ class SkinList : public QAbstractListModel { bool deleteSkin(const QString& key, const bool trash); void installSkins(const QStringList& iconFiles); - void installSkin(const QString& file, const QString& name); + QString installSkin(const QString& file, const QString& name = {}); const SkinModel* skin(const QString& key) const; SkinModel* skin(const QString& key); @@ -58,14 +58,14 @@ class SkinList : public QAbstractListModel { void save(); int getSelectedAccountSkin(); + void updateSkin(SkinModel s); + private: // hide copy constructor SkinList(const SkinList&) = delete; // hide assign op SkinList& operator=(const SkinList&) = delete; - QVector loadMinecraftSkins(); - protected slots: void directoryChanged(const QString& path); void fileChanged(const QString& path); diff --git a/launcher/minecraft/skins/SkinModel.cpp b/launcher/minecraft/skins/SkinModel.cpp index 3b467019cd..d53b9e762d 100644 --- a/launcher/minecraft/skins/SkinModel.cpp +++ b/launcher/minecraft/skins/SkinModel.cpp @@ -31,28 +31,12 @@ SkinModel::SkinModel(QDir skinDir, QJsonObject obj) : m_cape_id(Json::ensureString(obj, "capeId")), m_model(Model::CLASSIC), m_url(Json::ensureString(obj, "url")) { auto name = Json::ensureString(obj, "name"); - auto skinImage = Json::ensureString(obj, "skinImage"); - if (!skinImage.isEmpty()) { // minecraft skin model - skinImage = skinImage.mid(22); - m_texture.loadFromData(QByteArray::fromBase64(skinImage.toUtf8()), "PNG"); - auto textureId = Json::ensureString(obj, "textureId"); - if (name.isEmpty()) { - name = textureId; - } - if (Json::ensureBoolean(obj, "slim", false)) { - m_model = Model::SLIM; - } - } else { - if (auto model = Json::ensureString(obj, "model"); model == "SLIM") { - m_model = Model::SLIM; - } + + if (auto model = Json::ensureString(obj, "model"); model == "SLIM") { + m_model = Model::SLIM; } m_path = skinDir.absoluteFilePath(name) + ".png"; - if (!QFileInfo(m_path).exists() && isValid()) { - m_texture.save(m_path, "PNG"); - } else { - m_texture = QPixmap(m_path); - } + m_texture = QPixmap(m_path); } QString SkinModel::name() const @@ -92,37 +76,3 @@ bool SkinModel::isValid() const { return !m_texture.isNull() && (m_texture.size().height() == 32 || m_texture.size().height() == 64) && m_texture.size().width() == 64; } - -QPixmap SkinModel::renderFrontBody() const -{ - auto isSlim = m_model == SLIM; - auto slimOffset = isSlim ? 1 : 0; - auto isOldSkin = m_texture.height() < 64; - - auto head = m_texture.copy(QRect(8, 8, 16, 16)); - auto torso = m_texture.copy(QRect(20, 20, 28, 32)); - auto rightArm = m_texture.copy(QRect(44, 20, 48 - slimOffset, 32)); - auto rightLeg = m_texture.copy(QRect(4, 20, 8, 32)); - QPixmap leftArm, leftLeg; - - if (isOldSkin) { - leftArm = rightArm.transformed(QTransform().scale(-1, 1)); - leftLeg = rightLeg.transformed(QTransform().scale(-1, 1)); - } else { - leftArm = m_texture.copy(QRect(36, 52, 40 - slimOffset, 64)); - leftLeg = m_texture.copy(QRect(20, 52, 24, 64)); - } - QPixmap output(16, 32); - output.fill(Qt::black); - QPainter p; - if (!p.begin(&output)) - return {}; - p.drawPixmap(QPoint(4, 0), head); - p.drawPixmap(QPoint(4, 8), torso); - p.drawPixmap(QPoint(12, 8), leftArm); - p.drawPixmap(QPoint(slimOffset, 8), rightArm); - p.drawPixmap(QPoint(8, 20), leftLeg); - p.drawPixmap(QPoint(4, 20), leftArm); - - return output.scaled(128, 128, Qt::KeepAspectRatioByExpanding, Qt::FastTransformation); -} \ No newline at end of file diff --git a/launcher/minecraft/skins/SkinModel.h b/launcher/minecraft/skins/SkinModel.h index 6d135c7f7d..46e9d6cf15 100644 --- a/launcher/minecraft/skins/SkinModel.h +++ b/launcher/minecraft/skins/SkinModel.h @@ -26,6 +26,7 @@ class SkinModel { public: enum Model { CLASSIC, SLIM }; + SkinModel() = default; SkinModel(QString path); SkinModel(QDir skinDir, QJsonObject obj); virtual ~SkinModel() = default; @@ -47,8 +48,6 @@ class SkinModel { QJsonObject toJSON() const; - QPixmap renderFrontBody() const; - private: QString m_path; QPixmap m_texture; diff --git a/launcher/minecraft/skins/SkinUpload.cpp b/launcher/minecraft/skins/SkinUpload.cpp index 4e56bd7e69..4496f3f1c2 100644 --- a/launcher/minecraft/skins/SkinUpload.cpp +++ b/launcher/minecraft/skins/SkinUpload.cpp @@ -44,8 +44,8 @@ SkinUpload::SkinUpload(QString token, SkinModel* skin) : NetRequest(), m_skin(skin), m_token(token) { - logCat = taskUploadLogC; -}; + logCat = taskMCSkinsLogC; +} QNetworkReply* SkinUpload::getReply(QNetworkRequest& request) { diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index b66b91941f..6440d38b9b 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -55,6 +55,7 @@ class NetAction : public Task { virtual ~NetAction() = default; QUrl url() { return m_url; } + void setUrl(QUrl url) { m_url = url; } void setNetwork(shared_qobject_ptr network) { m_network = network; } diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 1ba7e7055d..0afb6cbc4b 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -16,37 +16,41 @@ * along with this program. If not, see . */ -#include -#include -#include -#include +#include "SkinManageDialog.h" +#include "ui_SkinManageDialog.h" #include #include #include +#include +#include #include #include #include +#include +#include +#include #include "Application.h" #include "DesktopServices.h" +#include "Json.h" #include "QObjectPtr.h" -#include "SkinManageDialog.h" #include "minecraft/auth/AccountTask.h" +#include "minecraft/auth/Parsers.h" #include "minecraft/skins/CapeChange.h" #include "minecraft/skins/SkinDelete.h" #include "minecraft/skins/SkinList.h" #include "minecraft/skins/SkinModel.h" #include "minecraft/skins/SkinUpload.h" +#include "net/Download.h" #include "net/NetJob.h" #include "tasks/Task.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui/instanceview/InstanceDelegate.h" -#include "ui_SkinManageDialog.h" SkinManageDialog::SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct) : QDialog(parent), m_acct(acct), ui(new Ui::SkinManageDialog), m_list(this, APPLICATION->settings()->get("SkinsDir").toString(), acct) @@ -132,20 +136,15 @@ void SkinManageDialog::on_openDirBtn_clicked() DesktopServices::openDirectory(m_list.getDir(), true); } -void SkinManageDialog::on_addBtn_clicked() +void SkinManageDialog::on_fileBtn_clicked() { auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString(); QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter); - if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) { + auto message = m_list.installSkin(raw_path, {}); + if (!message.isEmpty()) { + CustomMessageBox::selectable(this, tr("Selected file is not a valid skin"), message, QMessageBox::Critical)->show(); return; } - if (!SkinModel(raw_path).isValid()) { - CustomMessageBox::selectable(this, tr("Selected file is not a valid skin"), - tr("Skin images must be 64x64 or 64x32 pixel PNG files."), QMessageBox::Critical) - ->show(); - return; - } - m_list.installSkin(raw_path, {}); } QPixmap previewCape(QPixmap capeImage) @@ -337,3 +336,132 @@ void SkinManageDialog::on_action_Delete_Skin_triggered(bool checked) } } } + +void SkinManageDialog::on_urlBtn_clicked() +{ + auto url = QUrl(ui->urlLine->text()); + if (!url.isValid()) { + CustomMessageBox::selectable(this, tr("Invalid url"), tr("Invalid url"), QMessageBox::Critical)->show(); + return; + } + ui->urlLine->setText(""); + + NetJob::Ptr job{ new NetJob(tr("Download skin"), APPLICATION->network()) }; + + auto path = FS::PathCombine(m_list.getDir(), url.fileName()); + job->addNetAction(Net::Download::makeFile(url, path)); + ProgressDialog dlg(this); + dlg.execWithTask(job.get()); + SkinModel s(path); + if (!s.isValid()) { + CustomMessageBox::selectable(this, tr("URL is not a valid skin"), tr("Skin images must be 64x64 or 64x32 pixel PNG files."), + QMessageBox::Critical) + ->show(); + QFile::remove(path); + return; + } + if (QFileInfo(path).suffix().isEmpty()) { + QFile::rename(path, path + ".png"); + } +} + +class WaitTask : public Task { + public: + WaitTask() : m_loop(), m_done(false){}; + virtual ~WaitTask() = default; + + public slots: + void quit() + { + m_done = true; + m_loop.quit(); + } + + protected: + virtual void executeTask() + { + if (!m_done) + m_loop.exec(); + emitSucceeded(); + }; + + private: + QEventLoop m_loop; + bool m_done; +}; + +void SkinManageDialog::on_userBtn_clicked() +{ + auto user = ui->urlLine->text(); + if (user.isEmpty()) { + return; + } + ui->urlLine->setText(""); + MinecraftProfile mcProfile; + auto path = FS::PathCombine(m_list.getDir(), user + ".png"); + + NetJob::Ptr job{ new NetJob(tr("Download user skin"), APPLICATION->network(), 1) }; + + auto uuidOut = std::make_shared(); + auto profileOut = std::make_shared(); + + auto uuidLoop = makeShared(); + auto profileLoop = makeShared(); + + auto getUUID = Net::Download::makeByteArray("https://api.mojang.com/users/profiles/minecraft/" + user, uuidOut); + auto getProfile = Net::Download::makeByteArray(QUrl(), profileOut); + auto downloadSkin = Net::Download::makeFile(QUrl(), path); + + connect(getUUID.get(), &Task::aborted, uuidLoop.get(), &WaitTask::quit); + connect(getUUID.get(), &Task::failed, uuidLoop.get(), &WaitTask::quit); + connect(getProfile.get(), &Task::aborted, profileLoop.get(), &WaitTask::quit); + connect(getProfile.get(), &Task::failed, profileLoop.get(), &WaitTask::quit); + + connect(getUUID.get(), &Task::succeeded, this, [uuidLoop, uuidOut, job, getProfile] { + try { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*uuidOut, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Minecraft skin service at " << parse_error.offset + << " reason: " << parse_error.errorString(); + uuidLoop->quit(); + return; + } + const auto root = doc.object(); + auto id = Json::ensureString(root, "id"); + if (!id.isEmpty()) { + getProfile->setUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + id); + } else { + job->abort(); + } + } catch (const Exception& e) { + qCritical() << "Couldn't load skin json:" << e.cause(); + } + uuidLoop->quit(); + }); + + connect(getProfile.get(), &Task::succeeded, this, [profileLoop, profileOut, job, getProfile, &mcProfile, downloadSkin] { + if (Parsers::parseMinecraftProfileMojang(*profileOut, mcProfile)) { + downloadSkin->setUrl(mcProfile.skin.url); + } else { + job->abort(); + } + profileLoop->quit(); + }); + + job->addNetAction(getUUID); + job->addTask(uuidLoop); + job->addNetAction(getProfile); + job->addTask(profileLoop); + job->addNetAction(downloadSkin); + ProgressDialog dlg(this); + dlg.execWithTask(job.get()); + + SkinModel s(path); + s.setModel(mcProfile.skin.variant == "slim" ? SkinModel::SLIM : SkinModel::CLASSIC); + s.setURL(mcProfile.skin.url); + if (m_capes.contains(mcProfile.currentCape)) { + s.setCapeId(mcProfile.currentCape); + } + m_list.updateSkin(s); +} \ No newline at end of file diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.h b/launcher/ui/dialogs/skins/SkinManageDialog.h index 8c55c33101..ce8fc93480 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.h +++ b/launcher/ui/dialogs/skins/SkinManageDialog.h @@ -40,7 +40,9 @@ class SkinManageDialog : public QDialog { void activated(QModelIndex); void delayed_scroll(QModelIndex); void on_openDirBtn_clicked(); - void on_addBtn_clicked(); + void on_fileBtn_clicked(); + void on_urlBtn_clicked(); + void on_userBtn_clicked(); void accept() override; void on_capeCombo_currentIndexChanged(int index); void on_steveBtn_toggled(bool checked); diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.ui b/launcher/ui/dialogs/skins/SkinManageDialog.ui index 6ad826478f..c2ce9143c1 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.ui +++ b/launcher/ui/dialogs/skins/SkinManageDialog.ui @@ -100,7 +100,7 @@ - + @@ -109,21 +109,48 @@ - + - Import Skin + Reset Skin - + + + + + + + + - Reset Skin + Import URL + + + + + + + Import user + + + + + + + Import File + + + 0 + 0 + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok From 8c8e4329d72afb1b5141725127c883b52513761b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 5 Sep 2023 23:45:32 +0300 Subject: [PATCH 0040/2054] fix codeql Signed-off-by: Trial97 --- launcher/minecraft/skins/SkinList.cpp | 12 ++++++------ launcher/minecraft/skins/SkinList.h | 2 +- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index 15d0b0a8e7..b3a5934543 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -368,21 +368,21 @@ bool SkinList::setData(const QModelIndex& idx, const QVariant& value, int role) return true; } -void SkinList::updateSkin(SkinModel s) +void SkinList::updateSkin(SkinModel* s) { auto done = false; for (auto i = 0; i < m_skin_list.size(); i++) { - if (m_skin_list[i].getPath() == s.getPath()) { - m_skin_list[i].setCapeId(s.getCapeId()); - m_skin_list[i].setModel(s.getModel()); - m_skin_list[i].setURL(s.getURL()); + if (m_skin_list[i].getPath() == s->getPath()) { + m_skin_list[i].setCapeId(s->getCapeId()); + m_skin_list[i].setModel(s->getModel()); + m_skin_list[i].setURL(s->getURL()); done = true; break; } } if (!done) { beginInsertRows(QModelIndex(), m_skin_list.count(), m_skin_list.count() + 1); - m_skin_list.append(s); + m_skin_list.append(*s); endInsertRows(); } save(); diff --git a/launcher/minecraft/skins/SkinList.h b/launcher/minecraft/skins/SkinList.h index b6981e1b49..66af6a17bb 100644 --- a/launcher/minecraft/skins/SkinList.h +++ b/launcher/minecraft/skins/SkinList.h @@ -58,7 +58,7 @@ class SkinList : public QAbstractListModel { void save(); int getSelectedAccountSkin(); - void updateSkin(SkinModel s); + void updateSkin(SkinModel* s); private: // hide copy constructor diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 0afb6cbc4b..4ef91a2bf7 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -463,5 +463,5 @@ void SkinManageDialog::on_userBtn_clicked() if (m_capes.contains(mcProfile.currentCape)) { s.setCapeId(mcProfile.currentCape); } - m_list.updateSkin(s); + m_list.updateSkin(&s); } \ No newline at end of file From 6ec1cf6e4933013e04acb9086f3fcf4a844d181a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 8 Sep 2023 19:50:46 +0300 Subject: [PATCH 0041/2054] made skin upload more generic Signed-off-by: Trial97 --- launcher/minecraft/skins/SkinUpload.cpp | 10 +++++----- launcher/minecraft/skins/SkinUpload.h | 8 ++++---- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/launcher/minecraft/skins/SkinUpload.cpp b/launcher/minecraft/skins/SkinUpload.cpp index 4496f3f1c2..4a88faedf1 100644 --- a/launcher/minecraft/skins/SkinUpload.cpp +++ b/launcher/minecraft/skins/SkinUpload.cpp @@ -42,7 +42,7 @@ #include "net/ByteArraySink.h" #include "net/StaticHeaderProxy.h" -SkinUpload::SkinUpload(QString token, SkinModel* skin) : NetRequest(), m_skin(skin), m_token(token) +SkinUpload::SkinUpload(QString token, QString path, QString variant) : NetRequest(), m_token(token), m_path(path), m_variant(variant) { logCat = taskMCSkinsLogC; } @@ -55,11 +55,11 @@ QNetworkReply* SkinUpload::getReply(QNetworkRequest& request) skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png")); skin.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"skin.png\"")); - skin.setBody(FS::read(m_skin->getPath())); + skin.setBody(FS::read(m_path)); QHttpPart model; model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"variant\"")); - model.setBody(m_skin->getModelString().toUtf8()); + model.setBody(m_variant.toUtf8()); multiPart->append(skin); multiPart->append(model); @@ -74,9 +74,9 @@ void SkinUpload::init() })); } -SkinUpload::Ptr SkinUpload::make(QString token, SkinModel* skin) +SkinUpload::Ptr SkinUpload::make(QString token, QString path, QString variant) { - auto up = makeShared(token, skin); + auto up = makeShared(token, path, variant); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins"); up->setObjectName(QString("BYTES:") + up->m_url.toString()); up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); diff --git a/launcher/minecraft/skins/SkinUpload.h b/launcher/minecraft/skins/SkinUpload.h index d070f301d3..f24cef5a29 100644 --- a/launcher/minecraft/skins/SkinUpload.h +++ b/launcher/minecraft/skins/SkinUpload.h @@ -18,7 +18,6 @@ #pragma once -#include "minecraft/skins/SkinModel.h" #include "net/NetRequest.h" class SkinUpload : public Net::NetRequest { @@ -27,16 +26,17 @@ class SkinUpload : public Net::NetRequest { using Ptr = shared_qobject_ptr; // Note this class takes ownership of the file. - SkinUpload(QString token, SkinModel* skin); + SkinUpload(QString token, QString path, QString variant); virtual ~SkinUpload() = default; - static SkinUpload::Ptr make(QString token, SkinModel* skin); + static SkinUpload::Ptr make(QString token, QString path, QString variant); void init() override; protected: virtual QNetworkReply* getReply(QNetworkRequest&) override; private: - SkinModel* m_skin; QString m_token; + QString m_path; + QString m_variant; }; diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 4ef91a2bf7..24197baebc 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -242,7 +242,7 @@ void SkinManageDialog::accept() return; } - skinUpload->addNetAction(SkinUpload::make(m_acct->accessToken(), skin)); + skinUpload->addNetAction(SkinUpload::make(m_acct->accessToken(), skin->getPath(), skin->getModelString())); auto selectedCape = skin->getCapeId(); if (selectedCape != m_acct->accountData()->minecraftProfile.currentCape) { From d2e662ddbb518b147de76ca3f613e046d2912db5 Mon Sep 17 00:00:00 2001 From: cullvox Date: Sat, 9 Sep 2023 12:35:34 -0400 Subject: [PATCH 0042/2054] added support for components in resource pack descriptions. Signed-off-by: Caden Miller --- .../mod/tasks/LocalResourcePackParseTask.cpp | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 73cbf891c4..f78d3b7b3e 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -186,7 +186,35 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) auto pack_obj = Json::requireObject(json_doc.object(), "pack", {}); pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); - pack.setDescription(Json::ensureString(pack_obj, "description", "")); + + // description could either be string, or array of dictionaries + auto desc_val = pack_obj.value("description"); + + if (desc_val.isString()) { + pack.setDescription(Json::ensureString(pack_obj, "description", "")); + } else if (desc_val.isArray()) { + + // rebuild the description from the dictionaries without colors + QString build_desc; + auto arr = desc_val.toArray(); + + for(const QJsonValue& dict : arr) { + // must be an object, for a dictionary + if (!dict.isObject()) throw Json::JsonException("Invalid value description."); + + auto obj = dict.toObject(); + auto val = obj.value(obj.keys()[0]); + + // value must be a string type + if (!val.isString()) throw Json::JsonException("Invalid value description type."); + + build_desc.append(val.toString()); + }; + + pack.setDescription(build_desc); + } else { + throw Json::JsonException("Invalid description type."); + } } catch (Json::JsonException& e) { qWarning() << "JsonException: " << e.what() << e.cause(); return false; From 093d09efe36dbe1b18e4ae0ccb48150ef2f9f3ab Mon Sep 17 00:00:00 2001 From: cullvox Date: Sat, 9 Sep 2023 19:03:33 -0400 Subject: [PATCH 0043/2054] fix style, and use qWarning instead of throw. Signed-off-by: cullvox --- .../mod/tasks/LocalResourcePackParseTask.cpp | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index f78d3b7b3e..22f5fa163e 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -189,9 +189,13 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) // description could either be string, or array of dictionaries auto desc_val = pack_obj.value("description"); + if (desc_val.isUndefined()) { + qWarning() << "No resource pack description found."; + return false; + } if (desc_val.isString()) { - pack.setDescription(Json::ensureString(pack_obj, "description", "")); + pack.setDescription(desc_val.toString()); } else if (desc_val.isArray()) { // rebuild the description from the dictionaries without colors @@ -200,20 +204,27 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) for(const QJsonValue& dict : arr) { // must be an object, for a dictionary - if (!dict.isObject()) throw Json::JsonException("Invalid value description."); + if (!dict.isObject()) + { + qWarning() << "Invalid component object."; + continue; + } auto obj = dict.toObject(); auto val = obj.value(obj.keys()[0]); - // value must be a string type - if (!val.isString()) throw Json::JsonException("Invalid value description type."); + if (!val.isString()) + { + qWarning() << "Invalid text description type in components."; + continue; + } build_desc.append(val.toString()); }; pack.setDescription(build_desc); } else { - throw Json::JsonException("Invalid description type."); + qWarning() << "Invalid description type."; } } catch (Json::JsonException& e) { qWarning() << "JsonException: " << e.what() << e.cause(); From 04aa0155bf181dcd17236ef5baa3b91a4b9df449 Mon Sep 17 00:00:00 2001 From: cullvox Date: Sat, 9 Sep 2023 19:45:30 -0400 Subject: [PATCH 0044/2054] DCO Remediation Commit for cullvox I, cullvox , hereby add my Signed-off-by to this commit: d2e662ddbb518b147de76ca3f613e046d2912db5 Signed-off-by: cullvox --- launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 22f5fa163e..7a2fd7a86c 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -221,7 +221,6 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) build_desc.append(val.toString()); }; - pack.setDescription(build_desc); } else { qWarning() << "Invalid description type."; From 05f4214cc5d3a9005c0d259ca185303792aa2fa7 Mon Sep 17 00:00:00 2001 From: cullvox Date: Sat, 9 Sep 2023 19:50:13 -0400 Subject: [PATCH 0045/2054] fix clang-format failing --- .../minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 7a2fd7a86c..b696adea5f 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -202,10 +202,9 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) QString build_desc; auto arr = desc_val.toArray(); - for(const QJsonValue& dict : arr) { + for (const QJsonValue& dict : arr) { // must be an object, for a dictionary - if (!dict.isObject()) - { + if (!dict.isObject()) { qWarning() << "Invalid component object."; continue; } @@ -213,8 +212,7 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) auto obj = dict.toObject(); auto val = obj.value(obj.keys()[0]); - if (!val.isString()) - { + if (!val.isString()) { qWarning() << "Invalid text description type in components."; continue; } From ef1dc2afaccc681fdfd53513ac134b36a5d943cc Mon Sep 17 00:00:00 2001 From: cullvox Date: Sat, 9 Sep 2023 19:50:59 -0400 Subject: [PATCH 0046/2054] DCO Remediation Commit for cullvox I, cullvox , hereby add my Signed-off-by to this commit: 05f4214cc5d3a9005c0d259ca185303792aa2fa7 Signed-off-by: cullvox --- launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index b696adea5f..eaef76a6ca 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -197,7 +197,6 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) if (desc_val.isString()) { pack.setDescription(desc_val.toString()); } else if (desc_val.isArray()) { - // rebuild the description from the dictionaries without colors QString build_desc; auto arr = desc_val.toArray(); From 1261908ef7465e000390c9c12133171f4e73302f Mon Sep 17 00:00:00 2001 From: cullvox Date: Sat, 9 Sep 2023 19:55:55 -0400 Subject: [PATCH 0047/2054] remove space for clang-format Signed-off-by: cullvox --- launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index eaef76a6ca..1f535695bf 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -194,7 +194,7 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) return false; } - if (desc_val.isString()) { + if (desc_val.isString()) { pack.setDescription(desc_val.toString()); } else if (desc_val.isArray()) { // rebuild the description from the dictionaries without colors From 7a7c3015f47cba94eb34deba6748dc8555d8e457 Mon Sep 17 00:00:00 2001 From: cullvox Date: Sat, 9 Sep 2023 20:25:49 -0400 Subject: [PATCH 0048/2054] fix not properly reading json text. The text now displays properly in the GUI of Prism. Signed-off-by: cullvox --- .../minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 1f535695bf..4c30c12045 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -209,14 +209,8 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) } auto obj = dict.toObject(); - auto val = obj.value(obj.keys()[0]); - if (!val.isString()) { - qWarning() << "Invalid text description type in components."; - continue; - } - - build_desc.append(val.toString()); + build_desc.append(Json::ensureString(obj, "text", {})); }; pack.setDescription(build_desc); } else { From 58bd6d929f20e43c38cb372e431d1e1240d736b6 Mon Sep 17 00:00:00 2001 From: cullvox Date: Sun, 10 Sep 2023 23:37:26 -0400 Subject: [PATCH 0049/2054] added formatting with colors. This is somewhat dirty implementation and I will clean it up soon. Using the HTML rich text features of Qt, made it much easier to understand what was needed. Signed-off-by: cullvox --- .../mod/tasks/LocalResourcePackParseTask.cpp | 170 +++++++++++++++--- 1 file changed, 143 insertions(+), 27 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 4c30c12045..e6dde4ba02 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -178,9 +178,143 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level) return true; } -// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta -bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) +struct TextFormat { + QString color = "#000000"; + bool bold = false; + bool italic = false; + bool underlined = false; + bool strikethrough = false; + + TextFormat() = default; + ~TextFormat() = default; + + void clear() { + color = "#00000"; + bold = false; + italic = false; + underlined = false; + strikethrough = false; + } +}; + +QString textColorToHexColor(const QString& color) { + static const std::unordered_map str_to_hex = { + { "black", "#000000" }, + { "dark_blue", "#0000AA" }, + { "dark_green", "#00AA00" }, + { "dark_aqua", "#00AAAA" }, + { "dark_red", "#AA0000" }, + { "dark_purple", "#AA00AA" }, + { "gold", "#FFAA00" }, + { "gray", "#AAAAAA" }, + { "dark_gray", "#555555" }, + { "blue", "#5555FF" }, + { "green", "#55FF55" }, + { "aqua", "#55FFFF" }, + { "red", "#FF5555" }, + { "light_purple", "#FF55FF" }, + { "yellow", "#FFFF55" }, + { "white", "#FFFFFF" }, + }; + + auto it = str_to_hex.find(color); + return (it != str_to_hex.end()) ? it->second : QString("#000000"); // return black if color not found +} + +bool readFormat(const QJsonObject& obj, TextFormat& format) { + auto text = obj.value("text"); + auto color = obj.value("color"); + auto bold = obj.value("bold"); + auto italic = obj.value("italic"); + auto underlined = obj.value("underlined"); + auto strikethrough = obj.value("strikethrough"); + auto extra = obj.value("extra"); + + if (color.isString()) { + format.color = textColorToHexColor(color.toString()); + } + if (bold.isBool()) + format.bold = bold.toBool(); + if (italic.isBool()) + format.italic = italic.toBool(); + if (underlined.isBool()) + format.underlined = underlined.toBool(); + if (strikethrough.isBool()) + format.strikethrough = strikethrough.toBool(); + + return true; +} + +void appendBeginFormat(TextFormat& format, QString& toAppend) { + toAppend.append(""); + if (format.bold) + toAppend.append(""); + if (format.italic) + toAppend.append(""); + if (format.underlined) + toAppend.append(""); + if (format.strikethrough) + toAppend.append(""); +} + +void appendEndFormat(TextFormat& format, QString& toAppend) { + toAppend.append(""); + if (format.bold) + toAppend.append(""); + if (format.italic) + toAppend.append(""); + if (format.underlined) + toAppend.append(""); + if (format.strikethrough) + toAppend.append(""); +} + +bool processComponent(const QJsonValue& value, QString& result, TextFormat* parentFormat = nullptr); + +bool processComponentList(const QJsonArray& arr, QString& result, TextFormat* parentFormat = nullptr) { + + for (const QJsonValue& val : arr) { + processComponent(val, result, parentFormat); + } + + return true; +} + +bool processComponent(const QJsonValue& value, QString& result, TextFormat* parentFormat) { + if (value.isString()) { + result.append(value.toString()); + } else if (value.isObject()) { + auto obj = value.toObject(); + + TextFormat format{}; + if (parentFormat) + format = *parentFormat; + + if (!readFormat(obj, format)) + return false; + + appendBeginFormat(format, result); + + auto text = obj.value("text"); + if (text.isString()) + result.append(text.toString()); + + auto extra = obj.value("extra"); + if (extra.isArray()) + if (!processComponentList(extra.toArray(), result, &format)) + return false; + + appendEndFormat(format, result); + } + + return true; +} + +// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta +bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) { + + try { auto json_doc = QJsonDocument::fromJson(raw_data); auto pack_obj = Json::requireObject(json_doc.object(), "pack", {}); @@ -189,33 +323,15 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) // description could either be string, or array of dictionaries auto desc_val = pack_obj.value("description"); - if (desc_val.isUndefined()) { - qWarning() << "No resource pack description found."; - return false; - } - - if (desc_val.isString()) { + + if (desc_val.isString()) pack.setDescription(desc_val.toString()); - } else if (desc_val.isArray()) { - // rebuild the description from the dictionaries without colors - QString build_desc; - auto arr = desc_val.toArray(); - - for (const QJsonValue& dict : arr) { - // must be an object, for a dictionary - if (!dict.isObject()) { - qWarning() << "Invalid component object."; - continue; - } - - auto obj = dict.toObject(); - - build_desc.append(Json::ensureString(obj, "text", {})); - }; - pack.setDescription(build_desc); - } else { - qWarning() << "Invalid description type."; + else if (desc_val.isArray()) { + QString desc{}; + processComponentList(desc_val.toArray(), desc); + pack.setDescription(desc); } + } catch (Json::JsonException& e) { qWarning() << "JsonException: " << e.what() << e.cause(); return false; From fbe4043651bebaca399b9f4499a2097670e485b6 Mon Sep 17 00:00:00 2001 From: cullvox Date: Mon, 11 Sep 2023 01:34:53 -0400 Subject: [PATCH 0050/2054] fix formatting and add more proper errors. Signed-off-by: cullvox --- .../mod/tasks/LocalResourcePackParseTask.cpp | 125 +++++++++++------- 1 file changed, 78 insertions(+), 47 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index e6dde4ba02..ca1a5455c8 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -184,45 +184,60 @@ struct TextFormat { bool italic = false; bool underlined = false; bool strikethrough = false; +}; + +bool textColorToHexColor(const QString& text_color, QString& result) +{ + static const QHash text_to_hex = { + { "black", "#000000" }, { "dark_blue", "#0000AA" }, { "dark_green", "#00AA00" }, { "dark_aqua", "#00AAAA" }, + { "dark_red", "#AA0000" }, { "dark_purple", "#AA00AA" }, { "gold", "#FFAA00" }, { "gray", "#AAAAAA" }, + { "dark_gray", "#555555" }, { "blue", "#5555FF" }, { "green", "#55FF55" }, { "aqua", "#55FFFF" }, + { "red", "#FF5555" }, { "light_purple", "#FF55FF" }, { "yellow", "#FFFF55" }, { "white", "#FFFFFF" }, + }; - TextFormat() = default; - ~TextFormat() = default; + auto it = text_to_hex.find(text_color); - void clear() { - color = "#00000"; - bold = false; - italic = false; - underlined = false; - strikethrough = false; + if (it == text_to_hex.end()) { + qWarning() << "Invalid color string in component!"; + result = "#000000"; // return black if color not found + return false; } -}; -QString textColorToHexColor(const QString& color) + result = it.value(); + return true; +} + +bool getBoolOrFromText(const QJsonValue& val, bool& result) { - static const std::unordered_map str_to_hex = { - { "black", "#000000" }, - { "dark_blue", "#0000AA" }, - { "dark_green", "#00AA00" }, - { "dark_aqua", "#00AAAA" }, - { "dark_red", "#AA0000" }, - { "dark_purple", "#AA00AA" }, - { "gold", "#FFAA00" }, - { "gray", "#AAAAAA" }, - { "dark_gray", "#555555" }, - { "blue", "#5555FF" }, - { "green", "#55FF55" }, - { "aqua", "#55FFFF" }, - { "red", "#FF5555" }, - { "light_purple", "#FF55FF" }, - { "yellow", "#FFFF55" }, - { "white", "#FFFFFF" }, - }; + if (val.isUndefined()) { + result = false; + return true; + } - auto it = str_to_hex.find(color); - return (it != str_to_hex.end()) ? it->second : QString("#000000"); // return black if color not found + if (val.isBool()) { + result = val.toBool(); + return true; + } else if (val.isString()) { + auto bool_str = val.toString(); + + if (bool_str == "true") { + result = true; + return true; + } else if (bool_str == "false") { + result = false; + return true; + } else { + qWarning() << "Invalid bool value in component!"; + return false; + } + } else { + qWarning() << "Invalid type where bool expected!"; + return false; + } } -bool readFormat(const QJsonObject& obj, TextFormat& format) { +bool readFormat(const QJsonObject& obj, TextFormat& format) +{ auto text = obj.value("text"); auto color = obj.value("color"); auto bold = obj.value("bold"); @@ -232,21 +247,30 @@ bool readFormat(const QJsonObject& obj, TextFormat& format) { auto extra = obj.value("extra"); if (color.isString()) { - format.color = textColorToHexColor(color.toString()); + // colors can either be a hex code or one of a few text colors + auto col_str = color.toString(); + + const QRegularExpression hex_expression("^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$"); + const auto hex_match = hex_expression.match(col_str); + + if (hex_match.hasMatch()) { + format.color = color.toString(); + } else { + if (!textColorToHexColor(color.toString(), format.color)) { + qWarning() << "Invalid color type in component!"; + return false; + } + } } - if (bold.isBool()) - format.bold = bold.toBool(); - if (italic.isBool()) - format.italic = italic.toBool(); - if (underlined.isBool()) - format.underlined = underlined.toBool(); - if (strikethrough.isBool()) - format.strikethrough = strikethrough.toBool(); - return true; + return getBoolOrFromText(bold, format.bold) && + getBoolOrFromText(italic, format.italic) && + getBoolOrFromText(underlined, format.underlined) && + getBoolOrFromText(strikethrough, format.strikethrough); } -void appendBeginFormat(TextFormat& format, QString& toAppend) { +void appendBeginFormat(TextFormat& format, QString& toAppend) +{ toAppend.append(""); if (format.bold) toAppend.append(""); @@ -275,7 +299,8 @@ bool processComponent(const QJsonValue& value, QString& result, TextFormat* pare bool processComponentList(const QJsonArray& arr, QString& result, TextFormat* parentFormat = nullptr) { for (const QJsonValue& val : arr) { - processComponent(val, result, parentFormat); + if (!processComponent(val, result, parentFormat)) + return false; } return true; @@ -306,6 +331,9 @@ bool processComponent(const QJsonValue& value, QString& result, TextFormat* pare return false; appendEndFormat(format, result); + } else { + qWarning() << "Invalid component type!"; + return false; } return true; @@ -324,12 +352,15 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) { // description could either be string, or array of dictionaries auto desc_val = pack_obj.value("description"); - if (desc_val.isString()) + if (desc_val.isString()) { pack.setDescription(desc_val.toString()); - else if (desc_val.isArray()) { - QString desc{}; - processComponentList(desc_val.toArray(), desc); + } else if (desc_val.isArray()) { + QString desc; + if (!processComponentList(desc_val.toArray(), desc)) + return false; pack.setDescription(desc); + } else { + return false; } } catch (Json::JsonException& e) { From b16085f66c9083a1ec6d0feb08b96ce9173baccd Mon Sep 17 00:00:00 2001 From: cullvox Date: Mon, 11 Sep 2023 02:05:05 -0400 Subject: [PATCH 0051/2054] attempt to fix clang-format and ubuntu build. Signed-off-by: cullvox --- .../mod/tasks/LocalResourcePackParseTask.cpp | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index ca1a5455c8..c2aef06d53 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -26,6 +26,7 @@ #include #include +#include namespace ResourcePackUtils { @@ -236,7 +237,7 @@ bool getBoolOrFromText(const QJsonValue& val, bool& result) } } -bool readFormat(const QJsonObject& obj, TextFormat& format) +bool readFormat(const QJsonObject& obj, TextFormat& format) { auto text = obj.value("text"); auto color = obj.value("color"); @@ -247,7 +248,7 @@ bool readFormat(const QJsonObject& obj, TextFormat& format) auto extra = obj.value("extra"); if (color.isString()) { - // colors can either be a hex code or one of a few text colors + // colors can either be a hex code or one of a few text colors auto col_str = color.toString(); const QRegularExpression hex_expression("^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$"); @@ -263,13 +264,11 @@ bool readFormat(const QJsonObject& obj, TextFormat& format) } } - return getBoolOrFromText(bold, format.bold) && - getBoolOrFromText(italic, format.italic) && - getBoolOrFromText(underlined, format.underlined) && - getBoolOrFromText(strikethrough, format.strikethrough); + return getBoolOrFromText(bold, format.bold) && getBoolOrFromText(italic, format.italic) && + getBoolOrFromText(underlined, format.underlined) && getBoolOrFromText(strikethrough, format.strikethrough); } -void appendBeginFormat(TextFormat& format, QString& toAppend) +void appendBeginFormat(TextFormat& format, QString& toAppend) { toAppend.append(""); if (format.bold) @@ -282,7 +281,8 @@ void appendBeginFormat(TextFormat& format, QString& toAppend) toAppend.append(""); } -void appendEndFormat(TextFormat& format, QString& toAppend) { +void appendEndFormat(TextFormat& format, QString& toAppend) +{ toAppend.append(""); if (format.bold) toAppend.append(""); @@ -296,7 +296,8 @@ void appendEndFormat(TextFormat& format, QString& toAppend) { bool processComponent(const QJsonValue& value, QString& result, TextFormat* parentFormat = nullptr); -bool processComponentList(const QJsonArray& arr, QString& result, TextFormat* parentFormat = nullptr) { +bool processComponentList(const QJsonArray& arr, QString& result, TextFormat* parentFormat = nullptr) +{ for (const QJsonValue& val : arr) { if (!processComponent(val, result, parentFormat)) @@ -306,7 +307,8 @@ bool processComponentList(const QJsonArray& arr, QString& result, TextFormat* pa return true; } -bool processComponent(const QJsonValue& value, QString& result, TextFormat* parentFormat) { +bool processComponent(const QJsonValue& value, QString& result, TextFormat* parentFormat) +{ if (value.isString()) { result.append(value.toString()); } else if (value.isObject()) { @@ -340,9 +342,8 @@ bool processComponent(const QJsonValue& value, QString& result, TextFormat* pare } // https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta -bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) { - - +bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) +{ try { auto json_doc = QJsonDocument::fromJson(raw_data); auto pack_obj = Json::requireObject(json_doc.object(), "pack", {}); @@ -351,7 +352,7 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) { // description could either be string, or array of dictionaries auto desc_val = pack_obj.value("description"); - + if (desc_val.isString()) { pack.setDescription(desc_val.toString()); } else if (desc_val.isArray()) { From df88ccd4190ad2f605efe96b2b1503bc0ce0d5c8 Mon Sep 17 00:00:00 2001 From: cullvox Date: Mon, 11 Sep 2023 15:49:01 -0400 Subject: [PATCH 0052/2054] clean up and add review suggestions, links open Signed-off-by: cullvox --- .../mod/tasks/LocalResourcePackParseTask.cpp | 135 ++++++------------ .../mod/tasks/LocalResourcePackParseTask.h | 3 + 2 files changed, 44 insertions(+), 94 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index c2aef06d53..5b6f6fee08 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -185,87 +185,23 @@ struct TextFormat { bool italic = false; bool underlined = false; bool strikethrough = false; + bool is_linked = false; + QString link_url = ""; }; -bool textColorToHexColor(const QString& text_color, QString& result) -{ - static const QHash text_to_hex = { - { "black", "#000000" }, { "dark_blue", "#0000AA" }, { "dark_green", "#00AA00" }, { "dark_aqua", "#00AAAA" }, - { "dark_red", "#AA0000" }, { "dark_purple", "#AA00AA" }, { "gold", "#FFAA00" }, { "gray", "#AAAAAA" }, - { "dark_gray", "#555555" }, { "blue", "#5555FF" }, { "green", "#55FF55" }, { "aqua", "#55FFFF" }, - { "red", "#FF5555" }, { "light_purple", "#FF55FF" }, { "yellow", "#FFFF55" }, { "white", "#FFFFFF" }, - }; - - auto it = text_to_hex.find(text_color); - - if (it == text_to_hex.end()) { - qWarning() << "Invalid color string in component!"; - result = "#000000"; // return black if color not found - return false; - } - - result = it.value(); - return true; -} - -bool getBoolOrFromText(const QJsonValue& val, bool& result) -{ - if (val.isUndefined()) { - result = false; - return true; - } - - if (val.isBool()) { - result = val.toBool(); - return true; - } else if (val.isString()) { - auto bool_str = val.toString(); - - if (bool_str == "true") { - result = true; - return true; - } else if (bool_str == "false") { - result = false; - return true; - } else { - qWarning() << "Invalid bool value in component!"; - return false; - } - } else { - qWarning() << "Invalid type where bool expected!"; - return false; - } -} - bool readFormat(const QJsonObject& obj, TextFormat& format) { - auto text = obj.value("text"); - auto color = obj.value("color"); - auto bold = obj.value("bold"); - auto italic = obj.value("italic"); - auto underlined = obj.value("underlined"); - auto strikethrough = obj.value("strikethrough"); - auto extra = obj.value("extra"); - - if (color.isString()) { - // colors can either be a hex code or one of a few text colors - auto col_str = color.toString(); - - const QRegularExpression hex_expression("^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$"); - const auto hex_match = hex_expression.match(col_str); - - if (hex_match.hasMatch()) { - format.color = color.toString(); - } else { - if (!textColorToHexColor(color.toString(), format.color)) { - qWarning() << "Invalid color type in component!"; - return false; - } - } - } + format.color = Json::ensureString(obj, "color"); + format.bold = Json::ensureBoolean(obj, "bold", false); + format.italic = Json::ensureBoolean(obj, "italic", false); + format.underlined = Json::ensureBoolean(obj, "underlined", false); + format.strikethrough = Json::ensureBoolean(obj, "strikethrough", false); + + auto click_event = Json::ensureObject(obj, "clickEvent"); + format.is_linked = Json::ensureBoolean(click_event, "open_url", false); + format.link_url = Json::ensureString(click_event, "value"); - return getBoolOrFromText(bold, format.bold) && getBoolOrFromText(italic, format.italic) && - getBoolOrFromText(underlined, format.underlined) && getBoolOrFromText(strikethrough, format.strikethrough); + return true; } void appendBeginFormat(TextFormat& format, QString& toAppend) @@ -279,35 +215,36 @@ void appendBeginFormat(TextFormat& format, QString& toAppend) toAppend.append(""); if (format.strikethrough) toAppend.append(""); + if (format.is_linked) + toAppend.append(""); } void appendEndFormat(TextFormat& format, QString& toAppend) { - toAppend.append(""); - if (format.bold) - toAppend.append(""); + if (format.is_linked) + toAppend.append(""); + if (format.strikethrough) + toAppend.append(""); if (format.italic) toAppend.append(""); if (format.underlined) toAppend.append(""); - if (format.strikethrough) - toAppend.append(""); + if (format.bold) + toAppend.append(""); + toAppend.append(""); } -bool processComponent(const QJsonValue& value, QString& result, TextFormat* parentFormat = nullptr); - -bool processComponentList(const QJsonArray& arr, QString& result, TextFormat* parentFormat = nullptr) +bool processComponentList(const QJsonArray& arr, QString& result) { - for (const QJsonValue& val : arr) { - if (!processComponent(val, result, parentFormat)) + if (!processComponent(val, result)) return false; } return true; } -bool processComponent(const QJsonValue& value, QString& result, TextFormat* parentFormat) +bool processComponent(const QJsonValue& value, QString& result) { if (value.isString()) { result.append(value.toString()); @@ -315,8 +252,6 @@ bool processComponent(const QJsonValue& value, QString& result, TextFormat* pare auto obj = value.toObject(); TextFormat format{}; - if (parentFormat) - format = *parentFormat; if (!readFormat(obj, format)) return false; @@ -329,7 +264,7 @@ bool processComponent(const QJsonValue& value, QString& result, TextFormat* pare auto extra = obj.value("extra"); if (extra.isArray()) - if (!processComponentList(extra.toArray(), result, &format)) + if (!processComponentList(extra.toArray(), result)) return false; appendEndFormat(format, result); @@ -342,6 +277,7 @@ bool processComponent(const QJsonValue& value, QString& result, TextFormat* pare } // https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta +// https://minecraft.fandom.com/wiki/Raw_JSON_text_format#Plain_Text bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) { try { @@ -350,20 +286,31 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); - // description could either be string, or array of dictionaries + // description could be many things according to minecraft auto desc_val = pack_obj.value("description"); + QString desc; if (desc_val.isString()) { - pack.setDescription(desc_val.toString()); + desc = desc_val.toString(); } else if (desc_val.isArray()) { - QString desc; if (!processComponentList(desc_val.toArray(), desc)) return false; - pack.setDescription(desc); + } else if (desc_val.isObject()) { + if (!processComponent(desc_val, desc)) + return false; + } else if (desc_val.isBool()) { + desc = desc_val.toBool() ? "true" : "false"; + } else if (desc_val.isDouble()) { + desc = QString::number(desc_val.toDouble()); } else { + qWarning() << "Invalid description type!"; return false; } + qInfo() << desc; + + pack.setDescription(desc); + } catch (Json::JsonException& e) { qWarning() << "JsonException: " << e.what() << e.cause(); return false; diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h index 5199bf3f03..ed3317643b 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h @@ -34,6 +34,9 @@ bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); + +bool processComponent(const QJsonValue& value, QString& result); +bool processComponentList(const QJsonArray& value, QString& result); bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data); bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data); From a4e65305139dc5cdc7ad246b3203175a21262050 Mon Sep 17 00:00:00 2001 From: cullvox Date: Tue, 12 Sep 2023 21:34:42 -0400 Subject: [PATCH 0053/2054] added tests, fixed issues with overriding/format In the documentation it states that child values can override the parent values. Originally this code did not support that but now it does. Also added in testing inspired by the previous tests. Signed-off-by: cullvox --- .../mod/tasks/LocalResourcePackParseTask.cpp | 213 ++++++++++-------- .../mod/tasks/LocalResourcePackParseTask.h | 4 +- tests/CMakeLists.txt | 3 + tests/MetaComponentParse_test.cpp | 108 +++++++++ .../MetaComponentParse/component_basic.json | 4 + .../component_with_extra.json | 18 ++ .../component_with_format.json | 13 ++ .../component_with_link.json | 12 + .../component_with_mixed.json | 40 ++++ 9 files changed, 319 insertions(+), 96 deletions(-) create mode 100644 tests/MetaComponentParse_test.cpp create mode 100644 tests/testdata/MetaComponentParse/component_basic.json create mode 100644 tests/testdata/MetaComponentParse/component_with_extra.json create mode 100644 tests/testdata/MetaComponentParse/component_with_format.json create mode 100644 tests/testdata/MetaComponentParse/component_with_link.json create mode 100644 tests/testdata/MetaComponentParse/component_with_mixed.json diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 5b6f6fee08..31cfdca047 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -179,96 +179,138 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level) return true; } -struct TextFormat { - QString color = "#000000"; - bool bold = false; - bool italic = false; - bool underlined = false; - bool strikethrough = false; - bool is_linked = false; - QString link_url = ""; -}; - -bool readFormat(const QJsonObject& obj, TextFormat& format) -{ - format.color = Json::ensureString(obj, "color"); - format.bold = Json::ensureBoolean(obj, "bold", false); - format.italic = Json::ensureBoolean(obj, "italic", false); - format.underlined = Json::ensureBoolean(obj, "underlined", false); - format.strikethrough = Json::ensureBoolean(obj, "strikethrough", false); +struct TextFormatter { + // left is value, right is if the value was explicitly written + QPair color = { "#000000", false }; + QPair bold = { false, false }; + QPair italic = { false, false }; + QPair underlined = { false, false }; + QPair strikethrough = { false, false }; + QPair is_linked = { false, false }; + QPair link_url = { "", false }; + + void setColor(const QString& new_color, bool written) { color = { new_color, written}; } + void setBold(bool new_bold, bool written) { bold = { new_bold, written}; } + void setItalic(bool new_italic, bool written) { italic = { new_italic, written}; } + void setUnderlined(bool new_underlined, bool written) { underlined = { new_underlined, written}; } + void setStrikethrough(bool new_strikethrough, bool written) { strikethrough = { new_strikethrough, written}; } + void setIsLinked(bool new_is_linked, bool written) { is_linked = { new_is_linked, written}; } + void setLinkURL(const QString& new_url, bool written) { link_url = { new_url, written}; } + + void overrideFrom(const TextFormatter& child) + { + if (child.color.second) + color.first = child.color.first; + if (child.bold.second) + bold.first = child.bold.first; + if (child.italic.second) + italic.first = child.italic.first; + if (child.underlined.second) + underlined.first = child.underlined.first; + if (child.strikethrough.second) + strikethrough.first = child.strikethrough.first; + if (child.is_linked.second) + is_linked.first = child.is_linked.first; + if (child.link_url.second) + link_url.first = child.link_url.first; + } - auto click_event = Json::ensureObject(obj, "clickEvent"); - format.is_linked = Json::ensureBoolean(click_event, "open_url", false); - format.link_url = Json::ensureString(click_event, "value"); + QString format(QString text) + { + if (text.isEmpty()) + return QString(); + + QString result; + + if (color.first != "#000000") + result.append(""); + if (bold.first) + result.append(""); + if (italic.first) + result.append(""); + if (underlined.first) + result.append(""); + if (strikethrough.first) + result.append(""); + if (is_linked.first) + result.append(""); + + result.append(text); + + if (is_linked.first) + result.append(""); + if (strikethrough.first) + result.append(""); + if (underlined.first) + result.append(""); + if (italic.first) + result.append(""); + if (bold.first) + result.append(""); + if (color.first != "#000000") + result.append(""); + + return result; + } - return true; -} + bool readFormat(const QJsonObject& obj) + { + setColor(Json::ensureString(obj, "color", "#000000"), obj.contains("color")); + setBold(Json::ensureBoolean(obj, "bold", false), obj.contains("bold")); + setItalic(Json::ensureBoolean(obj, "italic", false), obj.contains("italic")); + setUnderlined(Json::ensureBoolean(obj, "underlined", false), obj.contains("underlined")); + setStrikethrough(Json::ensureBoolean(obj, "strikethrough", false), obj.contains("strikethrough")); -void appendBeginFormat(TextFormat& format, QString& toAppend) -{ - toAppend.append(""); - if (format.bold) - toAppend.append(""); - if (format.italic) - toAppend.append(""); - if (format.underlined) - toAppend.append(""); - if (format.strikethrough) - toAppend.append(""); - if (format.is_linked) - toAppend.append(""); -} + auto click_event = Json::ensureObject(obj, "clickEvent"); + setIsLinked(Json::ensureBoolean(click_event, "open_url", false), click_event.contains("open_url")); + setLinkURL(Json::ensureString(click_event, "value"), click_event.contains("value")); -void appendEndFormat(TextFormat& format, QString& toAppend) -{ - if (format.is_linked) - toAppend.append(""); - if (format.strikethrough) - toAppend.append(""); - if (format.italic) - toAppend.append(""); - if (format.underlined) - toAppend.append(""); - if (format.bold) - toAppend.append(""); - toAppend.append(""); -} + return true; + } +}; -bool processComponentList(const QJsonArray& arr, QString& result) +bool processComponent(const QJsonValue& value, QString& result, const TextFormatter* parentFormat) { - for (const QJsonValue& val : arr) { - if (!processComponent(val, result)) - return false; - } + TextFormatter formatter; - return true; -} + if (parentFormat) + formatter = *parentFormat; -bool processComponent(const QJsonValue& value, QString& result) -{ if (value.isString()) { - result.append(value.toString()); - } else if (value.isObject()) { + result.append(formatter.format(value.toString())); + } else if (value.isBool()) { + result.append(formatter.format(value.toBool() ? "true" : "false")); + } else if (value.isDouble()) { + result.append(formatter.format(QString::number(value.toDouble()))); + } else if (value.isObject()) { auto obj = value.toObject(); - TextFormat format{}; - - if (!readFormat(obj, format)) + if (not formatter.readFormat(obj)) return false; - - appendBeginFormat(format, result); - - auto text = obj.value("text"); - if (text.isString()) - result.append(text.toString()); + // override the parent format with our new one + TextFormatter mixed; + if (parentFormat) + mixed = *parentFormat; + + mixed.overrideFrom(formatter); + + result.append(mixed.format(Json::ensureString(obj, "text"))); + + // process any 'extra' children with this format auto extra = obj.value("extra"); - if (extra.isArray()) - if (!processComponentList(extra.toArray(), result)) - return false; + if (not extra.isUndefined()) + return processComponent(extra, result, &mixed); - appendEndFormat(format, result); - } else { + } else if (value.isArray()) { + auto array = value.toArray(); + + for (const QJsonValue& current : array) { + if (not processComponent(current, result, parentFormat)) { + return false; + } + } + } else { qWarning() << "Invalid component type!"; return false; } @@ -286,29 +328,12 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); - // description could be many things according to minecraft auto desc_val = pack_obj.value("description"); - - QString desc; - if (desc_val.isString()) { - desc = desc_val.toString(); - } else if (desc_val.isArray()) { - if (!processComponentList(desc_val.toArray(), desc)) - return false; - } else if (desc_val.isObject()) { - if (!processComponent(desc_val, desc)) - return false; - } else if (desc_val.isBool()) { - desc = desc_val.toBool() ? "true" : "false"; - } else if (desc_val.isDouble()) { - desc = QString::number(desc_val.toDouble()); - } else { - qWarning() << "Invalid description type!"; + QString desc{}; + if (not processComponent(desc_val, desc)) return false; - } qInfo() << desc; - pack.setDescription(desc); } catch (Json::JsonException& e) { diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h index ed3317643b..7e893979a9 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h @@ -35,8 +35,8 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Ful bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); -bool processComponent(const QJsonValue& value, QString& result); -bool processComponentList(const QJsonArray& value, QString& result); +struct TextFormatter; +bool processComponent(const QJsonValue& value, QString& result, const TextFormatter* parentFormat = nullptr); bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data); bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a26a49fec1..8690c078e5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -56,3 +56,6 @@ ecm_add_test(Index_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}: ecm_add_test(Version_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME Version) + +ecm_add_test(MetaComponentParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME MetaComponentParse) \ No newline at end of file diff --git a/tests/MetaComponentParse_test.cpp b/tests/MetaComponentParse_test.cpp new file mode 100644 index 0000000000..77b49b478c --- /dev/null +++ b/tests/MetaComponentParse_test.cpp @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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 +#include +#include +#include +#include + +#include + +#include + +class MetaComponentParseTest : public QObject { + Q_OBJECT + + void doTest(QString name) + { + QString source = QFINDTESTDATA("testdata/MetaComponentParse"); + + QString comp_rp = FS::PathCombine(source, name); + + QFile file; + file.setFileName(comp_rp); + QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text)); + QString data = file.readAll(); + file.close(); + + QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8()); + QJsonObject obj = doc.object(); + + QJsonValue description_json = obj.value("description"); + QJsonValue expected_json = obj.value("expected_output"); + + QVERIFY(description_json.isUndefined() == false); + QVERIFY(expected_json.isString() == true); + + QString expected = expected_json.toString(); + + QString processed; + bool valid = ResourcePackUtils::processComponent(description_json, processed); + + QVERIFY(processed == expected); + QVERIFY(valid == true); + } + +private slots: + void test_parseComponentBasic() + { + doTest("component_basic.json"); + } + + void test_parseComponentWithFormat() + { + doTest("component_with_format.json"); + } + + void test_parseComponentWithExtra() + { + doTest("component_with_extra.json"); + } + + void test_parseComponentWithLink() + { + doTest("component_with_link.json"); + } + + void test_parseComponentWithMixed() + { + doTest("component_with_mixed.json"); + } +}; + +QTEST_GUILESS_MAIN(MetaComponentParseTest) + +#include "MetaComponentParse_test.moc" diff --git a/tests/testdata/MetaComponentParse/component_basic.json b/tests/testdata/MetaComponentParse/component_basic.json new file mode 100644 index 0000000000..5cfdeeeabf --- /dev/null +++ b/tests/testdata/MetaComponentParse/component_basic.json @@ -0,0 +1,4 @@ +{ + "description": [{"text": "Hello, Component!"}], + "expected_output": "Hello, Component!" +} diff --git a/tests/testdata/MetaComponentParse/component_with_extra.json b/tests/testdata/MetaComponentParse/component_with_extra.json new file mode 100644 index 0000000000..49397556d3 --- /dev/null +++ b/tests/testdata/MetaComponentParse/component_with_extra.json @@ -0,0 +1,18 @@ +{ + "description": [ + { + "text": "Hello, ", + "color": "red", + "bold": true, + "italic": true, + "extra": [ + { + "extra": "Component!", + "bold": false, + "italic": false + } + ] + } + ], + "expected_output": "Hello, Component!" +} \ No newline at end of file diff --git a/tests/testdata/MetaComponentParse/component_with_format.json b/tests/testdata/MetaComponentParse/component_with_format.json new file mode 100644 index 0000000000..000de20cb0 --- /dev/null +++ b/tests/testdata/MetaComponentParse/component_with_format.json @@ -0,0 +1,13 @@ +{ + "description": [ + { + "text": "Hello, Component!", + "color": "blue", + "bold": true, + "italic": true, + "underlined": true, + "strikethrough": true + } + ], + "expected_output": "Hello, Component!" +} \ No newline at end of file diff --git a/tests/testdata/MetaComponentParse/component_with_link.json b/tests/testdata/MetaComponentParse/component_with_link.json new file mode 100644 index 0000000000..e190cc5e86 --- /dev/null +++ b/tests/testdata/MetaComponentParse/component_with_link.json @@ -0,0 +1,12 @@ +{ + "description": [ + { + "text": "Hello, Component!", + "clickEvent": { + "open_url": true, + "value": "https://google.com" + } + } + ], + "expected_output": "Hello, Component!" +} diff --git a/tests/testdata/MetaComponentParse/component_with_mixed.json b/tests/testdata/MetaComponentParse/component_with_mixed.json new file mode 100644 index 0000000000..7e65169af3 --- /dev/null +++ b/tests/testdata/MetaComponentParse/component_with_mixed.json @@ -0,0 +1,40 @@ +{ + "description": [ + { + "text": "The quick ", + "color": "blue", + "italic": true + }, + { + "text": "brown fox ", + "color": "#873600", + "bold": true, + "underlined": true, + "extra": { + "text": "jumped over ", + "color": "blue", + "bold": false, + "underlined": false, + "italic": true, + "strikethrough": true + } + }, + { + "text": "the lazy dog's back. ", + "color": "green", + "bold": true, + "italic": true, + "underlined": true, + "strikethrough": true, + "extra": [ + { + "text": "1234567890 ", + "color": "black", + "strikethrough": false, + "extra": "How vexingly quick daft zebras jump!" + } + ] + } + ], + "expected_output": "The quick brown fox jumped over the lazy dog's back. 1234567890 How vexingly quick daft zebras jump!" +} From ddf0c28b1b0b6617b0308405b4d5eefd20ff48b2 Mon Sep 17 00:00:00 2001 From: cullvox Date: Tue, 12 Sep 2023 21:45:29 -0400 Subject: [PATCH 0054/2054] clang-format fixes --- .../mod/tasks/LocalResourcePackParseTask.cpp | 22 ++++++------ .../mod/tasks/LocalResourcePackParseTask.h | 1 - tests/MetaComponentParse_test.cpp | 35 +++++-------------- 3 files changed, 19 insertions(+), 39 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 31cfdca047..8e209a4163 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -189,18 +189,18 @@ struct TextFormatter { QPair is_linked = { false, false }; QPair link_url = { "", false }; - void setColor(const QString& new_color, bool written) { color = { new_color, written}; } - void setBold(bool new_bold, bool written) { bold = { new_bold, written}; } - void setItalic(bool new_italic, bool written) { italic = { new_italic, written}; } - void setUnderlined(bool new_underlined, bool written) { underlined = { new_underlined, written}; } - void setStrikethrough(bool new_strikethrough, bool written) { strikethrough = { new_strikethrough, written}; } - void setIsLinked(bool new_is_linked, bool written) { is_linked = { new_is_linked, written}; } - void setLinkURL(const QString& new_url, bool written) { link_url = { new_url, written}; } + void setColor(const QString& new_color, bool written) { color = { new_color, written }; } + void setBold(bool new_bold, bool written) { bold = { new_bold, written }; } + void setItalic(bool new_italic, bool written) { italic = { new_italic, written }; } + void setUnderlined(bool new_underlined, bool written) { underlined = { new_underlined, written }; } + void setStrikethrough(bool new_strikethrough, bool written) { strikethrough = { new_strikethrough, written }; } + void setIsLinked(bool new_is_linked, bool written) { is_linked = { new_is_linked, written }; } + void setLinkURL(const QString& new_url, bool written) { link_url = { new_url, written }; } void overrideFrom(const TextFormatter& child) { if (child.color.second) - color.first = child.color.first; + color.first = child.color.first; if (child.bold.second) bold.first = child.bold.first; if (child.italic.second) @@ -282,7 +282,7 @@ bool processComponent(const QJsonValue& value, QString& result, const TextFormat result.append(formatter.format(value.toBool() ? "true" : "false")); } else if (value.isDouble()) { result.append(formatter.format(QString::number(value.toDouble()))); - } else if (value.isObject()) { + } else if (value.isObject()) { auto obj = value.toObject(); if (not formatter.readFormat(obj)) @@ -294,7 +294,7 @@ bool processComponent(const QJsonValue& value, QString& result, const TextFormat mixed = *parentFormat; mixed.overrideFrom(formatter); - + result.append(mixed.format(Json::ensureString(obj, "text"))); // process any 'extra' children with this format @@ -310,7 +310,7 @@ bool processComponent(const QJsonValue& value, QString& result, const TextFormat return false; } } - } else { + } else { qWarning() << "Invalid component type!"; return false; } diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h index 7e893979a9..945499a91b 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h @@ -34,7 +34,6 @@ bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); - struct TextFormatter; bool processComponent(const QJsonValue& value, QString& result, const TextFormatter* parentFormat = nullptr); bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data); diff --git a/tests/MetaComponentParse_test.cpp b/tests/MetaComponentParse_test.cpp index 77b49b478c..1b1e2ce3ee 100644 --- a/tests/MetaComponentParse_test.cpp +++ b/tests/MetaComponentParse_test.cpp @@ -33,11 +33,11 @@ * limitations under the License. */ -#include -#include #include #include #include +#include +#include #include @@ -76,31 +76,12 @@ class MetaComponentParseTest : public QObject { QVERIFY(valid == true); } -private slots: - void test_parseComponentBasic() - { - doTest("component_basic.json"); - } - - void test_parseComponentWithFormat() - { - doTest("component_with_format.json"); - } - - void test_parseComponentWithExtra() - { - doTest("component_with_extra.json"); - } - - void test_parseComponentWithLink() - { - doTest("component_with_link.json"); - } - - void test_parseComponentWithMixed() - { - doTest("component_with_mixed.json"); - } + private slots: + void test_parseComponentBasic() { doTest("component_basic.json"); } + void test_parseComponentWithFormat() { doTest("component_with_format.json"); } + void test_parseComponentWithExtra() { doTest("component_with_extra.json"); } + void test_parseComponentWithLink() { doTest("component_with_link.json"); } + void test_parseComponentWithMixed() { doTest("component_with_mixed.json"); } }; QTEST_GUILESS_MAIN(MetaComponentParseTest) From e1dda6c005dab379a5b93a2a9279205895b71060 Mon Sep 17 00:00:00 2001 From: cullvox Date: Tue, 12 Sep 2023 21:50:33 -0400 Subject: [PATCH 0055/2054] DCO Remediation Commit for cullvox I, cullvox , hereby add my Signed-off-by to this commit: ddf0c28b1b0b6617b0308405b4d5eefd20ff48b2 Signed-off-by: cullvox --- launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 8e209a4163..5e53244ff5 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -333,7 +333,6 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) if (not processComponent(desc_val, desc)) return false; - qInfo() << desc; pack.setDescription(desc); } catch (Json::JsonException& e) { From ee7016fa542e3ca92bdd691bd2e168d9d43a02c2 Mon Sep 17 00:00:00 2001 From: cullvox Date: Wed, 13 Sep 2023 14:28:56 -0400 Subject: [PATCH 0056/2054] use clang-format Signed-off-by: cullvox Hello, Component!" + ], + "expected_output": "Hello, Component!" } \ No newline at end of file diff --git a/tests/testdata/MetaComponentParse/component_with_format.json b/tests/testdata/MetaComponentParse/component_with_format.json index 000de20cb0..00dfc7daf4 100644 --- a/tests/testdata/MetaComponentParse/component_with_format.json +++ b/tests/testdata/MetaComponentParse/component_with_format.json @@ -1,13 +1,13 @@ { - "description": [ - { - "text": "Hello, Component!", - "color": "blue", - "bold": true, - "italic": true, - "underlined": true, - "strikethrough": true - } - ], - "expected_output": "Hello, Component!" + "description": [ + { + "text": "Hello, Component!", + "color": "blue", + "bold": true, + "italic": true, + "underlined": true, + "strikethrough": true + } + ], + "expected_output": "Hello, Component!" } \ No newline at end of file diff --git a/tests/testdata/MetaComponentParse/component_with_link.json b/tests/testdata/MetaComponentParse/component_with_link.json index e190cc5e86..b1e34c7d66 100644 --- a/tests/testdata/MetaComponentParse/component_with_link.json +++ b/tests/testdata/MetaComponentParse/component_with_link.json @@ -1,12 +1,12 @@ { - "description": [ - { - "text": "Hello, Component!", - "clickEvent": { - "open_url": true, - "value": "https://google.com" - } - } - ], - "expected_output": "Hello, Component!" + "description": [ + { + "text": "Hello, Component!", + "clickEvent": { + "open_url": true, + "value": "https://google.com" + } + } + ], + "expected_output": "Hello, Component!" } diff --git a/tests/testdata/MetaComponentParse/component_with_mixed.json b/tests/testdata/MetaComponentParse/component_with_mixed.json index 7e65169af3..7c8c5b032a 100644 --- a/tests/testdata/MetaComponentParse/component_with_mixed.json +++ b/tests/testdata/MetaComponentParse/component_with_mixed.json @@ -1,40 +1,41 @@ { - "description": [ - { - "text": "The quick ", - "color": "blue", - "italic": true - }, - { - "text": "brown fox ", - "color": "#873600", - "bold": true, - "underlined": true, - "extra": { - "text": "jumped over ", - "color": "blue", - "bold": false, - "underlined": false, - "italic": true, - "strikethrough": true - } - }, - { - "text": "the lazy dog's back. ", - "color": "green", - "bold": true, - "italic": true, - "underlined": true, - "strikethrough": true, - "extra": [ + "description": [ { - "text": "1234567890 ", - "color": "black", - "strikethrough": false, - "extra": "How vexingly quick daft zebras jump!" + "text": "The quick ", + "color": "blue", + "italic": true + }, + { + "text": "brown fox ", + "color": "#873600", + "bold": true, + "underlined": true, + "extra": { + "text": "jumped over ", + "color": "blue", + "bold": false, + "underlined": false, + "italic": true, + "strikethrough": true + } + }, + { + "text": "the lazy dog's back. ", + "color": "green", + "bold": true, + "italic": true, + "underlined": true, + "strikethrough": true, + "extra": [ + { + "text": "1234567890 ", + "color": "black", + "strikethrough": false, + "extra": "How vexingly quick daft zebras jump!" + } + ] } - ] - } - ], - "expected_output": "The quick brown fox jumped over the lazy dog's back. 1234567890 How vexingly quick daft zebras jump!" + ], + "expected_output": + "The quick brown fox jumped over the lazy dog's back. 1234567890 How vexingly quick daft zebras jump!" } From e96d0b6cafb0a0c17e7f5baef1bfbafe4a7fcb07 Mon Sep 17 00:00:00 2001 From: cullvox Date: Wed, 13 Sep 2023 14:31:04 -0400 Subject: [PATCH 0057/2054] DCO Remediation Commit for cullvox I, cullvox , hereby add my Signed-off-by to this commit: ee7016fa542e3ca92bdd691bd2e168d9d43a02c2 Signed-off-by: cullvox --- launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 5e53244ff5..fd28aa0b7b 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -272,7 +272,6 @@ struct TextFormatter { bool processComponent(const QJsonValue& value, QString& result, const TextFormatter* parentFormat) { TextFormatter formatter; - if (parentFormat) formatter = *parentFormat; From c81689d39322d5ea5c7ed0073d73e4d8d2446b33 Mon Sep 17 00:00:00 2001 From: cullvox Date: Fri, 15 Sep 2023 20:41:21 -0400 Subject: [PATCH 0058/2054] fixes html elide issue with InfoFrame --- launcher/ui/widgets/InfoFrame.cpp | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 1f03f9eaf7..0162b40006 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include "InfoFrame.h" #include "ui_InfoFrame.h" @@ -274,12 +275,31 @@ void InfoFrame::setDescription(QString text) } QString labeltext; labeltext.reserve(300); - if (finaltext.length() > 290) { + + + // elide rich text by getting characters without formatting + const int maxCharacterElide = 290; + QTextDocument doc; + doc.setHtml(text); + + if (doc.characterCount() > maxCharacterElide) { + ui->descriptionLabel->setOpenExternalLinks(false); - ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); + ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); // This allows injecting HTML here. m_description = text; - // This allows injecting HTML here. - labeltext.append("" + finaltext.left(287) + "..."); + + const QString elidedPostfix = "..."; + + // move the cursor to the character elide, doesn't see html + QTextCursor cursor(&doc); + cursor.movePosition(QTextCursor::End); + cursor.setPosition(maxCharacterElide, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + + // insert the post fix at the cursor + cursor.insertHtml(elidedPostfix); + + labeltext.append(doc.toHtml()); QObject::connect(ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler); } else { ui->descriptionLabel->setTextFormat(Qt::TextFormat::AutoText); From 4053229544ca2d80cd569f4d67365f81ed64353d Mon Sep 17 00:00:00 2001 From: cullvox Date: Fri, 15 Sep 2023 20:55:34 -0400 Subject: [PATCH 0059/2054] DCO Remediation Commit for cullvox I, cullvox , hereby add my Signed-off-by to this commit: c81689d39322d5ea5c7ed0073d73e4d8d2446b33 Signed-off-by: cullvox --- launcher/ui/widgets/InfoFrame.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 0162b40006..6423e88d6b 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -36,8 +36,9 @@ #include #include -#include #include +#include +#include #include "InfoFrame.h" #include "ui_InfoFrame.h" @@ -276,16 +277,14 @@ void InfoFrame::setDescription(QString text) QString labeltext; labeltext.reserve(300); - // elide rich text by getting characters without formatting const int maxCharacterElide = 290; QTextDocument doc; doc.setHtml(text); if (doc.characterCount() > maxCharacterElide) { - ui->descriptionLabel->setOpenExternalLinks(false); - ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); // This allows injecting HTML here. + ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); // This allows injecting HTML here. m_description = text; const QString elidedPostfix = "..."; @@ -298,7 +297,7 @@ void InfoFrame::setDescription(QString text) // insert the post fix at the cursor cursor.insertHtml(elidedPostfix); - + labeltext.append(doc.toHtml()); QObject::connect(ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler); } else { From 01e98a6ce810ed1e8169d2252dca5cec39f8d335 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 16 Sep 2023 10:20:24 +0300 Subject: [PATCH 0060/2054] simplify the raw json parsing Signed-off-by: Trial97 Fixed Tests Signed-off-by: Trial97 --- .../mod/tasks/LocalResourcePackParseTask.cpp | 202 ++++++------------ .../mod/tasks/LocalResourcePackParseTask.h | 3 +- launcher/ui/widgets/InfoFrame.cpp | 6 +- tests/MetaComponentParse_test.cpp | 10 +- .../component_with_extra.json | 7 +- .../component_with_format.json | 2 +- .../component_with_link.json | 2 +- .../component_with_mixed.json | 24 ++- 8 files changed, 98 insertions(+), 158 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index fd28aa0b7b..d9d26b9c2e 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -179,142 +179,85 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level) return true; } -struct TextFormatter { - // left is value, right is if the value was explicitly written - QPair color = { "#000000", false }; - QPair bold = { false, false }; - QPair italic = { false, false }; - QPair underlined = { false, false }; - QPair strikethrough = { false, false }; - QPair is_linked = { false, false }; - QPair link_url = { "", false }; - - void setColor(const QString& new_color, bool written) { color = { new_color, written }; } - void setBold(bool new_bold, bool written) { bold = { new_bold, written }; } - void setItalic(bool new_italic, bool written) { italic = { new_italic, written }; } - void setUnderlined(bool new_underlined, bool written) { underlined = { new_underlined, written }; } - void setStrikethrough(bool new_strikethrough, bool written) { strikethrough = { new_strikethrough, written }; } - void setIsLinked(bool new_is_linked, bool written) { is_linked = { new_is_linked, written }; } - void setLinkURL(const QString& new_url, bool written) { link_url = { new_url, written }; } - - void overrideFrom(const TextFormatter& child) - { - if (child.color.second) - color.first = child.color.first; - if (child.bold.second) - bold.first = child.bold.first; - if (child.italic.second) - italic.first = child.italic.first; - if (child.underlined.second) - underlined.first = child.underlined.first; - if (child.strikethrough.second) - strikethrough.first = child.strikethrough.first; - if (child.is_linked.second) - is_linked.first = child.is_linked.first; - if (child.link_url.second) - link_url.first = child.link_url.first; +QString buildStyle(const QJsonObject& obj) +{ + QStringList styles; + if (auto color = Json::ensureString(obj, "color"); !color.isEmpty()) { + styles << QString("color: %1;").arg(color); } - - QString format(QString text) - { - if (text.isEmpty()) - return QString(); - - QString result; - - if (color.first != "#000000") - result.append(""); - if (bold.first) - result.append(""); - if (italic.first) - result.append(""); - if (underlined.first) - result.append(""); - if (strikethrough.first) - result.append(""); - if (is_linked.first) - result.append(""); - - result.append(text); - - if (is_linked.first) - result.append(""); - if (strikethrough.first) - result.append(""); - if (underlined.first) - result.append(""); - if (italic.first) - result.append(""); - if (bold.first) - result.append(""); - if (color.first != "#000000") - result.append(""); - - return result; + if (obj.contains("bold")) { + QString weight = "normal"; + if (Json::ensureBoolean(obj, "bold", false)) { + weight = "bold"; + } + styles << QString("font-weight: %1;").arg(weight); } - - bool readFormat(const QJsonObject& obj) - { - setColor(Json::ensureString(obj, "color", "#000000"), obj.contains("color")); - setBold(Json::ensureBoolean(obj, "bold", false), obj.contains("bold")); - setItalic(Json::ensureBoolean(obj, "italic", false), obj.contains("italic")); - setUnderlined(Json::ensureBoolean(obj, "underlined", false), obj.contains("underlined")); - setStrikethrough(Json::ensureBoolean(obj, "strikethrough", false), obj.contains("strikethrough")); - - auto click_event = Json::ensureObject(obj, "clickEvent"); - setIsLinked(Json::ensureBoolean(click_event, "open_url", false), click_event.contains("open_url")); - setLinkURL(Json::ensureString(click_event, "value"), click_event.contains("value")); - - return true; + if (obj.contains("italic")) { + QString style = "normal"; + if (Json::ensureBoolean(obj, "italic", false)) { + style = "italic"; + } + styles << QString("font-style: %1;").arg(style); } -}; - -bool processComponent(const QJsonValue& value, QString& result, const TextFormatter* parentFormat) -{ - TextFormatter formatter; - if (parentFormat) - formatter = *parentFormat; - if (value.isString()) { - result.append(formatter.format(value.toString())); - } else if (value.isBool()) { - result.append(formatter.format(value.toBool() ? "true" : "false")); - } else if (value.isDouble()) { - result.append(formatter.format(QString::number(value.toDouble()))); - } else if (value.isObject()) { - auto obj = value.toObject(); - - if (not formatter.readFormat(obj)) - return false; - - // override the parent format with our new one - TextFormatter mixed; - if (parentFormat) - mixed = *parentFormat; - - mixed.overrideFrom(formatter); - - result.append(mixed.format(Json::ensureString(obj, "text"))); + return styles.isEmpty() ? "" : QString("style=\"%1\"").arg(styles.join(" ")); +} - // process any 'extra' children with this format - auto extra = obj.value("extra"); - if (not extra.isUndefined()) - return processComponent(extra, result, &mixed); +QString processComponent(const QJsonArray& value, bool strikethrough, bool underline) +{ + QString result; + for (auto current : value) + result += processComponent(current, strikethrough, underline); + return result; +} - } else if (value.isArray()) { - auto array = value.toArray(); +QString processComponent(const QJsonObject& obj, bool strikethrough, bool underline) +{ + underline = Json::ensureBoolean(obj, "underlined", underline); + strikethrough = Json::ensureBoolean(obj, "strikethrough", strikethrough); - for (const QJsonValue& current : array) { - if (not processComponent(current, result, parentFormat)) { - return false; - } + QString result = Json::ensureString(obj, "text"); + if (underline) { + result = QString("%1").arg(result); + } + if (strikethrough) { + result = QString("%1").arg(result); + } + // the extra needs to be a array + result += processComponent(Json::ensureArray(obj, "extra"), strikethrough, underline); + if (auto style = buildStyle(obj); !style.isEmpty()) { + result = QString("%2").arg(style, result); + } + if (obj.contains("clickEvent")) { + auto click_event = Json::ensureObject(obj, "clickEvent"); + auto action = Json::ensureString(click_event, "action"); + auto value = Json::ensureString(click_event, "value"); + if (action == "open_url" && !value.isEmpty()) { + result = QString("%2").arg(value, result); } - } else { - qWarning() << "Invalid component type!"; - return false; } + return result; +} - return true; +QString processComponent(const QJsonValue& value, bool strikethrough, bool underline) +{ + if (value.isString()) { + return value.toString(); + } + if (value.isBool()) { + return value.toBool() ? "true" : "false"; + } + if (value.isDouble()) { + return QString::number(value.toDouble()); + } + if (value.isArray()) { + return processComponent(value.toArray(), strikethrough, underline); + } + if (value.isObject()) { + return processComponent(value.toObject(), strikethrough, underline); + } + qWarning() << "Invalid component type!"; + return {}; } // https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta @@ -327,12 +270,7 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); - auto desc_val = pack_obj.value("description"); - QString desc{}; - if (not processComponent(desc_val, desc)) - return false; - - pack.setDescription(desc); + pack.setDescription(processComponent(pack_obj.value("description"))); } catch (Json::JsonException& e) { qWarning() << "JsonException: " << e.what() << e.cause(); diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h index 945499a91b..97bf7b2ba6 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h @@ -34,8 +34,7 @@ bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); -struct TextFormatter; -bool processComponent(const QJsonValue& value, QString& result, const TextFormatter* parentFormat = nullptr); +QString processComponent(const QJsonValue& value, bool strikethrough = false, bool underline = false); bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data); bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data); diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 6423e88d6b..2cf3e7a930 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -287,8 +287,6 @@ void InfoFrame::setDescription(QString text) ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); // This allows injecting HTML here. m_description = text; - const QString elidedPostfix = "..."; - // move the cursor to the character elide, doesn't see html QTextCursor cursor(&doc); cursor.movePosition(QTextCursor::End); @@ -296,7 +294,7 @@ void InfoFrame::setDescription(QString text) cursor.removeSelectedText(); // insert the post fix at the cursor - cursor.insertHtml(elidedPostfix); + cursor.insertHtml("..."); labeltext.append(doc.toHtml()); QObject::connect(ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler); @@ -335,7 +333,7 @@ void InfoFrame::setLicense(QString text) if (finaltext.length() > 290) { ui->licenseLabel->setOpenExternalLinks(false); ui->licenseLabel->setTextFormat(Qt::TextFormat::RichText); - m_description = text; + m_license = text; // This allows injecting HTML here. labeltext.append("" + finaltext.left(287) + "..."); QObject::connect(ui->licenseLabel, &QLabel::linkActivated, this, &InfoFrame::licenseEllipsisHandler); diff --git a/tests/MetaComponentParse_test.cpp b/tests/MetaComponentParse_test.cpp index 1b1e2ce3ee..9979a9fa68 100644 --- a/tests/MetaComponentParse_test.cpp +++ b/tests/MetaComponentParse_test.cpp @@ -64,16 +64,14 @@ class MetaComponentParseTest : public QObject { QJsonValue description_json = obj.value("description"); QJsonValue expected_json = obj.value("expected_output"); - QVERIFY(description_json.isUndefined() == false); - QVERIFY(expected_json.isString() == true); + QVERIFY(!description_json.isUndefined()); + QVERIFY(expected_json.isString()); QString expected = expected_json.toString(); - QString processed; - bool valid = ResourcePackUtils::processComponent(description_json, processed); + QString processed = ResourcePackUtils::processComponent(description_json); - QVERIFY(processed == expected); - QVERIFY(valid == true); + QCOMPARE(processed, expected); } private slots: diff --git a/tests/testdata/MetaComponentParse/component_with_extra.json b/tests/testdata/MetaComponentParse/component_with_extra.json index e26b2abffb..887becdbeb 100644 --- a/tests/testdata/MetaComponentParse/component_with_extra.json +++ b/tests/testdata/MetaComponentParse/component_with_extra.json @@ -7,12 +7,15 @@ "italic": true, "extra": [ { - "extra": "Component!", + "extra": [ + "Component!" + ], "bold": false, "italic": false } ] } ], - "expected_output": "Hello, Component!" + "expected_output": + "Hello, Component!" } \ No newline at end of file diff --git a/tests/testdata/MetaComponentParse/component_with_format.json b/tests/testdata/MetaComponentParse/component_with_format.json index 00dfc7daf4..1078886a6b 100644 --- a/tests/testdata/MetaComponentParse/component_with_format.json +++ b/tests/testdata/MetaComponentParse/component_with_format.json @@ -9,5 +9,5 @@ "strikethrough": true } ], - "expected_output": "Hello, Component!" + "expected_output": "Hello, Component!" } \ No newline at end of file diff --git a/tests/testdata/MetaComponentParse/component_with_link.json b/tests/testdata/MetaComponentParse/component_with_link.json index b1e34c7d66..188c004cd5 100644 --- a/tests/testdata/MetaComponentParse/component_with_link.json +++ b/tests/testdata/MetaComponentParse/component_with_link.json @@ -3,7 +3,7 @@ { "text": "Hello, Component!", "clickEvent": { - "open_url": true, + "action": "open_url", "value": "https://google.com" } } diff --git a/tests/testdata/MetaComponentParse/component_with_mixed.json b/tests/testdata/MetaComponentParse/component_with_mixed.json index 7c8c5b032a..661fc1a3e9 100644 --- a/tests/testdata/MetaComponentParse/component_with_mixed.json +++ b/tests/testdata/MetaComponentParse/component_with_mixed.json @@ -10,14 +10,16 @@ "color": "#873600", "bold": true, "underlined": true, - "extra": { - "text": "jumped over ", - "color": "blue", - "bold": false, - "underlined": false, - "italic": true, - "strikethrough": true - } + "extra": [ + { + "text": "jumped over ", + "color": "blue", + "bold": false, + "underlined": false, + "italic": true, + "strikethrough": true + } + ] }, { "text": "the lazy dog's back. ", @@ -31,11 +33,13 @@ "text": "1234567890 ", "color": "black", "strikethrough": false, - "extra": "How vexingly quick daft zebras jump!" + "extra": [ + "How vexingly quick daft zebras jump!" + ] } ] } ], "expected_output": - "The quick brown fox jumped over the lazy dog's back. 1234567890 How vexingly quick daft zebras jump!" + "The quick brown fox jumped over the lazy dog's back. 1234567890 How vexingly quick daft zebras jump!" } From 0e41ceffc4a2b8842e44a8bd1dc027944ce27471 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 16 Sep 2023 19:18:58 +0300 Subject: [PATCH 0061/2054] removed missed header Signed-off-by: Trial97 --- launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index d9d26b9c2e..c5d8991231 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -26,7 +26,6 @@ #include #include -#include namespace ResourcePackUtils { From ab725eeb1816aea428107dcc9ed9184136c7f766 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 15 Oct 2023 11:52:28 +0300 Subject: [PATCH 0062/2054] corected side and added loaders Signed-off-by: Trial97 --- launcher/modplatform/ModIndex.cpp | 19 ++++++++++++++++++- launcher/modplatform/ModIndex.h | 4 +++- launcher/modplatform/flame/FlameModIndex.cpp | 6 ++++++ launcher/modplatform/modrinth/ModrinthAPI.h | 2 +- launcher/modplatform/packwiz/Packwiz.cpp | 19 ++++++++++++++++++- launcher/modplatform/packwiz/Packwiz.h | 1 + 6 files changed, 47 insertions(+), 4 deletions(-) diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index fc79dff152..5b76245632 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -117,7 +117,7 @@ QString getMetaURL(ResourceProvider provider, QVariant projectID) projectID.toString(); } -auto getModLoaderString(ModLoaderType type) -> const QString +auto getModLoaderAsString(ModLoaderType type) -> const QString { switch (type) { case NeoForge: @@ -138,4 +138,21 @@ auto getModLoaderString(ModLoaderType type) -> const QString return ""; } +auto getModLoaderFromString(QString type) -> const ModLoaderType +{ + if (type == "neoforge") + return NeoForge; + if (type == "forge") + return Forge; + if (type == "cauldron") + return Cauldron; + if (type == "liteloader") + return LiteLoader; + if (type == "fabric") + return Fabric; + if (type == "quilt") + return Quilt; + return {}; +} + } // namespace ModPlatform diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 72294c3994..940dddea02 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -109,6 +109,7 @@ struct IndexedVersion { bool is_preferred = true; QString changelog; QList dependencies; + QString side; // this is for flame API // For internal use, not provided by APIs bool is_currently_selected = false; @@ -181,7 +182,8 @@ inline auto getOverrideDeps() -> QList QString getMetaURL(ResourceProvider provider, QVariant projectID); -auto getModLoaderString(ModLoaderType type) -> const QString; +auto getModLoaderAsString(ModLoaderType type) -> const QString; +auto getModLoaderFromString(QString type) -> const ModLoaderType; constexpr bool hasSingleModLoaderSelected(ModLoaderTypes l) noexcept { diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 345883c174..75c10d6f5d 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -130,6 +130,12 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> file.loaders |= ModPlatform::Fabric; if (loader == "quilt") file.loaders |= ModPlatform::Quilt; + if (loader == "server" || loader == "client") { + if (file.side.isEmpty()) + file.side = loader; + else if (file.side != loader) + file.side = "both"; + } } file.addonId = Json::requireInteger(obj, "modId"); diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index d0f0811b2d..8577199027 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -41,7 +41,7 @@ class ModrinthAPI : public NetworkResourceAPI { for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt, ModPlatform::LiteLoader }) { if (types & loader) { - l << getModLoaderString(loader); + l << getModLoaderAsString(loader); } } return l; diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index e35567f24a..90d7d0ed71 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -113,7 +113,8 @@ auto V1::createModFormat([[maybe_unused]] QDir& index_dir, ModPlatform::IndexedP mod.provider = mod_pack.provider; mod.file_id = mod_version.fileId; mod.project_id = mod_pack.addonId; - mod.side = stringToSide(mod_pack.side); + mod.side = stringToSide(mod_version.side.isEmpty() ? mod_pack.side : mod_version.side); + mod.loaders = mod_version.loaders; return mod; } @@ -181,6 +182,14 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) break; } + toml::array loaders; + for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Cauldron, ModPlatform::LiteLoader, ModPlatform::Fabric, + ModPlatform::Quilt }) { + if (mod.loaders & loader) { + loaders.push_back(getModLoaderAsString(loader)); + } + } + if (!index_file.open(QIODevice::ReadWrite)) { qCritical() << QString("Could not open file %1!").arg(normalized_fname); return; @@ -192,6 +201,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) auto tbl = toml::table{ { "name", mod.name.toStdString() }, { "filename", mod.filename.toStdString() }, { "side", sideToString(mod.side).toStdString() }, + { "loader", loaders }, { "download", toml::table{ { "mode", mod.mode.toStdString() }, @@ -276,6 +286,13 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod mod.name = stringEntry(table, "name"); mod.filename = stringEntry(table, "filename"); mod.side = stringToSide(stringEntry(table, "side")); + if (auto loaders = table["loaders"]; loaders && loaders.is_array()) { + for (auto&& loader : *loaders.as_array()) { + if (loader.is_string()) { + mod.loaders |= ModPlatform::getModLoaderFromString(QString::fromStdString(loader.as_string()->value_or(""))); + } + } + } } { // [download] info diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index dce198b0e7..a61bb05031 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -41,6 +41,7 @@ class V1 { QString name{}; QString filename{}; Side side{ Side::UniversalSide }; + ModPlatform::ModLoaderTypes loaders; // [download] QString mode{}; From 749975e8ef96ba780aa910217911ff0af6ecbe5b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 15 Oct 2023 12:46:23 +0300 Subject: [PATCH 0063/2054] made side and loaders visible to user Signed-off-by: Trial97 --- launcher/minecraft/mod/MetadataHandler.h | 2 ++ launcher/minecraft/mod/Mod.cpp | 29 +++++++++++++++ launcher/minecraft/mod/Mod.h | 3 ++ launcher/minecraft/mod/ModFolderModel.cpp | 35 ++++++++++++++++--- launcher/minecraft/mod/ModFolderModel.h | 12 ++++++- launcher/minecraft/mod/Resource.h | 2 +- .../modrinth/ModrinthCheckUpdate.cpp | 2 +- launcher/modplatform/packwiz/Packwiz.cpp | 4 +-- 8 files changed, 79 insertions(+), 10 deletions(-) diff --git a/launcher/minecraft/mod/MetadataHandler.h b/launcher/minecraft/mod/MetadataHandler.h index 3496da2a02..ccdd7d559d 100644 --- a/launcher/minecraft/mod/MetadataHandler.h +++ b/launcher/minecraft/mod/MetadataHandler.h @@ -52,4 +52,6 @@ class Metadata { static auto get(QDir& index_dir, QString mod_slug) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_slug); } static auto get(QDir& index_dir, QVariant& mod_id) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_id); } + + static auto modSideToString(ModSide side) -> QString { return Packwiz::V1::sideToString(side); } }; diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 310946379d..132f99f7d7 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -45,6 +45,7 @@ #include "MetadataHandler.h" #include "Version.h" #include "minecraft/mod/ModDetails.h" +#include "minecraft/mod/Resource.h" #include "minecraft/mod/tasks/LocalModParseTask.h" static ModPlatform::ProviderCapabilities ProviderCaps; @@ -109,6 +110,20 @@ std::pair Mod::compare(const Resource& other, SortType type) const return { compare_result, type == SortType::PROVIDER }; break; } + case SortType::SIDE: { + if (side() > cast_other->side()) + return { 1, type == SortType::SIDE }; + else if (side() < cast_other->side()) + return { -1, type == SortType::SIDE }; + break; + } + case SortType::LOADERS: { + if (loaders() > cast_other->loaders()) + return { 1, type == SortType::LOADERS }; + else if (loaders() < cast_other->loaders()) + return { -1, type == SortType::LOADERS }; + break; + } } return { 0, false }; } @@ -232,6 +247,20 @@ auto Mod::provider() const -> std::optional return {}; } +auto Mod::side() const -> Metadata::ModSide +{ + if (metadata()) + return metadata()->side; + return Metadata::ModSide::UniversalSide; +} + +auto Mod::loaders() const -> ModPlatform::ModLoaderTypes +{ + if (metadata()) + return metadata()->loaders; + return {}; +} + auto Mod::licenses() const -> const QList& { return details().licenses; diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index e97ee9d3b3..970f85a00e 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -47,6 +47,7 @@ #include "ModDetails.h" #include "Resource.h" +#include "modplatform/ModIndex.h" class Mod : public Resource { Q_OBJECT @@ -70,6 +71,8 @@ class Mod : public Resource { auto licenses() const -> const QList&; auto issueTracker() const -> QString; auto metaurl() const -> QString; + auto side() const -> Metadata::ModSide; + auto loaders() const -> ModPlatform::ModLoaderTypes; /** Get the intneral path to the mod's icon file*/ QString iconPath() const { return m_local_details.icon_file; } diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index a5f1489dd7..631425a724 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -52,6 +52,8 @@ #include "Application.h" #include "Json.h" +#include "minecraft/mod/MetadataHandler.h" +#include "minecraft/mod/Resource.h" #include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModUpdateTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h" @@ -62,12 +64,15 @@ ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir) : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER }; + m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Side", "Loaders" }); + m_column_names_translated = QStringList( + { tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), tr("Side"), tr("Loaders") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, + SortType::DATE, SortType::PROVIDER, SortType::SIDE, SortType::LOADERS }; m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, - QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; - m_columnsHideable = { false, true, false, true, true, true }; + QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, + QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; + m_columnsHideable = { false, true, false, true, true, true, true, true }; } QVariant ModFolderModel::data(const QModelIndex& index, int role) const @@ -105,6 +110,20 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const return provider.value(); } + case SideColumn: { + return Metadata::modSideToString(at(row)->side()); + } + case LoadersColumn: { + QStringList loaders; + auto modLoaders = at(row)->loaders(); + for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Cauldron, ModPlatform::LiteLoader, + ModPlatform::Fabric, ModPlatform::Quilt }) { + if (modLoaders & loader) { + loaders << getModLoaderAsString(loader); + } + } + return loaders.join(","); + } default: return QVariant(); } @@ -154,6 +173,8 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio case DateColumn: case ProviderColumn: case ImageColumn: + case SideColumn: + case LoadersColumn: return columnNames().at(section); default: return QVariant(); @@ -171,6 +192,10 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio return tr("The date and time this mod was last changed (or added)."); case ProviderColumn: return tr("Where the mod was downloaded from."); + case SideColumn: + return tr("On what environment the mod is running."); + case LoadersColumn: + return tr("The mod loader."); default: return QVariant(); } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 61d840f9bd..1623fadfe4 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -61,7 +61,17 @@ class QFileSystemWatcher; class ModFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, VersionColumn, DateColumn, ProviderColumn, NUM_COLUMNS }; + enum Columns { + ActiveColumn = 0, + ImageColumn, + NameColumn, + VersionColumn, + DateColumn, + ProviderColumn, + SideColumn, + LoadersColumn, + NUM_COLUMNS + }; enum ModStatusAction { Disable, Enable, Toggle }; ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true); diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index c1ed49461e..eefb5f8a54 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -15,7 +15,7 @@ enum class ResourceType { LITEMOD, //!< The resource is a litemod }; -enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER }; +enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIDE, LOADERS }; enum class EnableAction { ENABLE, DISABLE, TOGGLE }; diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index 9b7c53854e..7d491f4fe2 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -114,7 +114,7 @@ void ModrinthCheckUpdate::executeTask() ModPlatform::ModLoaderType::Fabric, ModPlatform::ModLoaderType::Quilt }; for (auto flag : flags) { if (m_loaders.value().testFlag(flag)) { - loader_filter = ModPlatform::getModLoaderString(flag); + loader_filter = ModPlatform::getModLoaderAsString(flag); break; } } diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 90d7d0ed71..f15ab9e7a2 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -186,7 +186,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Cauldron, ModPlatform::LiteLoader, ModPlatform::Fabric, ModPlatform::Quilt }) { if (mod.loaders & loader) { - loaders.push_back(getModLoaderAsString(loader)); + loaders.push_back(getModLoaderAsString(loader).toStdString()); } } @@ -201,7 +201,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) auto tbl = toml::table{ { "name", mod.name.toStdString() }, { "filename", mod.filename.toStdString() }, { "side", sideToString(mod.side).toStdString() }, - { "loader", loaders }, + { "loaders", loaders }, { "download", toml::table{ { "mode", mod.mode.toStdString() }, From 0ff98c5876f73556e328cba352fb12566f889e08 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 15 Oct 2023 21:22:59 +0300 Subject: [PATCH 0064/2054] allways enable dependencies Signed-off-by: Trial97 --- launcher/ui/dialogs/ModUpdateDialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index a0c2f2f441..9499378a5f 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -220,8 +220,8 @@ void ModUpdateDialog::checkCandidates() changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); auto download_task = makeShared(dep->pack, dep->version, m_mod_model); CheckUpdateTask::UpdatableMod updatable = { - dep->pack->name, dep->version.hash, "", dep->version.version, dep->version.version_type, - changelog, dep->pack->provider, download_task + dep->pack->name, dep->version.hash, "", dep->version.version, dep->version.version_type, changelog, true, + dep->pack->provider, download_task }; appendMod(updatable, getRequiredBy.value(dep->version.addonId.toString())); From b54376062e87b9f278b3953da5f7f1bb04695b49 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 17 Oct 2023 19:25:01 +0300 Subject: [PATCH 0065/2054] Added mcVersion column Signed-off-by: Trial97 --- launcher/minecraft/mod/Mod.cpp | 15 ++++++++++++ launcher/minecraft/mod/Mod.h | 1 + launcher/minecraft/mod/ModFolderModel.cpp | 23 ++++++++++++------- launcher/minecraft/mod/ModFolderModel.h | 1 + launcher/minecraft/mod/Resource.h | 2 +- launcher/modplatform/ModIndex.cpp | 2 +- launcher/modplatform/ModIndex.h | 2 +- launcher/modplatform/flame/FlameModIndex.cpp | 16 ++++++------- .../modrinth/ModrinthPackIndex.cpp | 10 ++++---- launcher/modplatform/packwiz/Packwiz.cpp | 18 +++++++++++++++ launcher/modplatform/packwiz/Packwiz.h | 1 + 11 files changed, 67 insertions(+), 24 deletions(-) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 132f99f7d7..22e652319f 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -124,6 +124,14 @@ std::pair Mod::compare(const Resource& other, SortType type) const return { -1, type == SortType::LOADERS }; break; } + case SortType::MC_VERSIONS: { + auto thisVersion = mcVersions().join(","); + auto otherVersion = cast_other->mcVersions().join(","); + auto compare_result = QString::compare(thisVersion, otherVersion, Qt::CaseInsensitive); + if (compare_result != 0) + return { compare_result, type == SortType::MC_VERSIONS }; + break; + } } return { 0, false }; } @@ -261,6 +269,13 @@ auto Mod::loaders() const -> ModPlatform::ModLoaderTypes return {}; } +auto Mod::mcVersions() const -> QStringList +{ + if (metadata()) + return metadata()->mcVersions; + return {}; +} + auto Mod::licenses() const -> const QList& { return details().licenses; diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 970f85a00e..92de65889e 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -73,6 +73,7 @@ class Mod : public Resource { auto metaurl() const -> QString; auto side() const -> Metadata::ModSide; auto loaders() const -> ModPlatform::ModLoaderTypes; + auto mcVersions() const -> QStringList; /** Get the intneral path to the mod's icon file*/ QString iconPath() const { return m_local_details.icon_file; } diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 631425a724..ef4ab957c2 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -64,15 +64,16 @@ ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir) : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Side", "Loaders" }); - m_column_names_translated = QStringList( - { tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), tr("Side"), tr("Loaders") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, - SortType::DATE, SortType::PROVIDER, SortType::SIDE, SortType::LOADERS }; + m_column_names = + QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Side", "Loaders", "Miecraft Versions" }); + m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), + tr("Side"), tr("Loaders"), tr("Miecraft Versions") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, + SortType::PROVIDER, SortType::SIDE, SortType::LOADERS, SortType::MC_VERSIONS }; m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, - QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; - m_columnsHideable = { false, true, false, true, true, true, true, true }; + QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; + m_columnsHideable = { false, true, false, true, true, true, true, true, true }; } QVariant ModFolderModel::data(const QModelIndex& index, int role) const @@ -122,7 +123,10 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const loaders << getModLoaderAsString(loader); } } - return loaders.join(","); + return loaders.join(", "); + } + case McVersionsColumn: { + return at(row)->mcVersions().join(", "); } default: return QVariant(); @@ -175,6 +179,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio case ImageColumn: case SideColumn: case LoadersColumn: + case McVersionsColumn: return columnNames().at(section); default: return QVariant(); @@ -196,6 +201,8 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio return tr("On what environment the mod is running."); case LoadersColumn: return tr("The mod loader."); + case McVersionsColumn: + return tr("The supported minecraft versions."); default: return QVariant(); } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 1623fadfe4..f9a87a7417 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -70,6 +70,7 @@ class ModFolderModel : public ResourceFolderModel { ProviderColumn, SideColumn, LoadersColumn, + McVersionsColumn, NUM_COLUMNS }; enum ModStatusAction { Disable, Enable, Toggle }; diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index eefb5f8a54..c469808372 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -15,7 +15,7 @@ enum class ResourceType { LITEMOD, //!< The resource is a litemod }; -enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIDE, LOADERS }; +enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIDE, LOADERS, MC_VERSIONS }; enum class EnableAction { ENABLE, DISABLE, TOGGLE }; diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index 5b76245632..2cd7710e39 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -138,7 +138,7 @@ auto getModLoaderAsString(ModLoaderType type) -> const QString return ""; } -auto getModLoaderFromString(QString type) -> const ModLoaderType +auto getModLoaderFromString(QString type) -> ModLoaderType { if (type == "neoforge") return NeoForge; diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 940dddea02..7c472ec9b4 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -183,7 +183,7 @@ inline auto getOverrideDeps() -> QList QString getMetaURL(ResourceProvider provider, QVariant projectID); auto getModLoaderAsString(ModLoaderType type) -> const QString; -auto getModLoaderFromString(QString type) -> const ModLoaderType; +auto getModLoaderFromString(QString type) -> ModLoaderType; constexpr bool hasSingleModLoaderSelected(ModLoaderTypes l) noexcept { diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 75c10d6f5d..d56e46b23b 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -117,20 +117,20 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> if (str.contains('.')) file.mcVersion.append(str); - auto loader = str.toLower(); - if (loader == "neoforge") + + if (auto loader = str.toLower(); loader == "neoforge") file.loaders |= ModPlatform::NeoForge; - if (loader == "forge") + else if (loader == "forge") file.loaders |= ModPlatform::Forge; - if (loader == "cauldron") + else if (loader == "cauldron") file.loaders |= ModPlatform::Cauldron; - if (loader == "liteloader") + else if (loader == "liteloader") file.loaders |= ModPlatform::LiteLoader; - if (loader == "fabric") + else if (loader == "fabric") file.loaders |= ModPlatform::Fabric; - if (loader == "quilt") + else if (loader == "quilt") file.loaders |= ModPlatform::Quilt; - if (loader == "server" || loader == "client") { + else if (loader == "server" || loader == "client") { if (file.side.isEmpty()) file.side = loader; else if (file.side != loader) diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 7d08932612..23770b7fb7 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -152,15 +152,15 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t for (auto loader : loaders) { if (loader == "neoforge") file.loaders |= ModPlatform::NeoForge; - if (loader == "forge") + else if (loader == "forge") file.loaders |= ModPlatform::Forge; - if (loader == "cauldron") + else if (loader == "cauldron") file.loaders |= ModPlatform::Cauldron; - if (loader == "liteloader") + else if (loader == "liteloader") file.loaders |= ModPlatform::LiteLoader; - if (loader == "fabric") + else if (loader == "fabric") file.loaders |= ModPlatform::Fabric; - if (loader == "quilt") + else if (loader == "quilt") file.loaders |= ModPlatform::Quilt; } file.version = Json::requireString(obj, "name"); diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index f15ab9e7a2..0692a627d8 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -115,6 +115,8 @@ auto V1::createModFormat([[maybe_unused]] QDir& index_dir, ModPlatform::IndexedP mod.project_id = mod_pack.addonId; mod.side = stringToSide(mod_version.side.isEmpty() ? mod_pack.side : mod_version.side); mod.loaders = mod_version.loaders; + mod.mcVersions = mod_version.mcVersion; + mod.mcVersions.sort(); return mod; } @@ -189,6 +191,10 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) loaders.push_back(getModLoaderAsString(loader).toStdString()); } } + toml::array mcVersions; + for (auto version : mod.mcVersions) { + mcVersions.push_back(version.toStdString()); + } if (!index_file.open(QIODevice::ReadWrite)) { qCritical() << QString("Could not open file %1!").arg(normalized_fname); @@ -202,6 +208,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) { "filename", mod.filename.toStdString() }, { "side", sideToString(mod.side).toStdString() }, { "loaders", loaders }, + { "mcVersions", mcVersions }, { "download", toml::table{ { "mode", mod.mode.toStdString() }, @@ -293,6 +300,17 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod } } } + if (auto versions = table["mcVersions"]; versions && versions.is_array()) { + for (auto&& version : *versions.as_array()) { + if (version.is_string()) { + auto ver = QString::fromStdString(version.as_string()->value_or("")); + if (!ver.isEmpty()) { + mod.mcVersions << ver; + } + } + } + mod.mcVersions.sort(); + } } { // [download] info diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index a61bb05031..5cf99e74a1 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -42,6 +42,7 @@ class V1 { QString filename{}; Side side{ Side::UniversalSide }; ModPlatform::ModLoaderTypes loaders; + QStringList mcVersions; // [download] QString mode{}; From fea4c4eba8bd6d919e1fd2cb96a25b2bc0968894 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 18 Oct 2023 09:00:45 +0300 Subject: [PATCH 0066/2054] Updated filters Signed-off-by: Trial97 --- .../mod/tasks/GetModDependenciesTask.cpp | 1 + launcher/modplatform/ModIndex.cpp | 3 +- .../ui/dialogs/ResourceDownloadDialog.cpp | 1 + launcher/ui/pages/modplatform/ModModel.cpp | 10 +- launcher/ui/pages/modplatform/ModPage.cpp | 9 +- launcher/ui/pages/modplatform/ModPage.h | 3 +- .../modplatform/flame/FlameResourceModels.cpp | 1 + launcher/ui/widgets/ModFilterWidget.cpp | 242 ++++++++++-------- launcher/ui/widgets/ModFilterWidget.h | 57 ++--- launcher/ui/widgets/ModFilterWidget.ui | 156 +++++++++-- 10 files changed, 308 insertions(+), 175 deletions(-) diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp index df8c690aff..deec0db7c5 100644 --- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp +++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp @@ -23,6 +23,7 @@ #include #include "Json.h" #include "QObjectPtr.h" +#include "minecraft/PackProfile.h" #include "minecraft/mod/MetadataHandler.h" #include "modplatform/ModIndex.h" #include "modplatform/ResourceAPI.h" diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index 2cd7710e39..d2113320a4 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -94,10 +94,9 @@ auto ProviderCapabilities::hash(ResourceProvider p, QIODevice* device, QString t { QCryptographicHash::Algorithm algo = QCryptographicHash::Sha1; switch (p) { - case ResourceProvider::MODRINTH: { + case ResourceProvider::MODRINTH: algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Sha512; break; - } case ResourceProvider::FLAME: algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Md5; break; diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index dc7cfff06f..f21502365b 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -27,6 +27,7 @@ #include "Application.h" #include "ResourceDownloadTask.h" +#include "minecraft/PackProfile.h" #include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ResourcePackFolderModel.h" #include "minecraft/mod/ShaderPackFolderModel.h" diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index c628f74acd..3b53d13484 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -25,15 +25,18 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments() Q_ASSERT(m_filter); std::optional> versions{}; + auto loaders = profile->getSupportedModLoaders(); { // Version filter if (!m_filter->versions.empty()) versions = m_filter->versions; + if (m_filter->loaders) + loaders = m_filter->loaders; } auto sort = getCurrentSortingMethodByIndex(); - return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getSupportedModLoaders(), versions }; + return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, loaders, versions }; } ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) @@ -45,10 +48,13 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en Q_ASSERT(m_filter); std::optional> versions{}; + auto loaders = profile->getSupportedModLoaders(); if (!m_filter->versions.empty()) versions = m_filter->versions; + if (m_filter->loaders) + loaders = m_filter->loaders; - return { pack, versions, profile->getSupportedModLoaders() }; + return { pack, versions, loaders }; } ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index d6cc1fdcc7..838cf21c83 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -71,7 +71,6 @@ void ModPage::setFilterWidget(unique_qobject_ptr& widget) m_ui->gridLayout_3->addWidget(m_filter_widget.get(), 0, 0, 1, m_ui->gridLayout_3->columnCount()); - m_filter_widget->setInstance(&static_cast(m_base_instance)); m_filter = m_filter_widget->getFilter(); connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, @@ -89,13 +88,14 @@ void ModPage::filterMods() void ModPage::triggerSearch() { + auto changed = m_filter_widget->changed(); m_filter = m_filter_widget->getFilter(); m_ui->packView->clearSelection(); m_ui->packDescription->clear(); m_ui->versionSelectionBox->clear(); updateSelectionButton(); - static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt(), m_filter_widget->changed()); + static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt(), changed); m_fetch_progress.watch(m_model->activeSearchJob().get()); } @@ -116,6 +116,9 @@ void ModPage::updateVersionList() auto packProfile = (dynamic_cast(m_base_instance)).getPackProfile(); QString mcVersion = packProfile->getComponentVersion("net.minecraft"); + auto loaders = packProfile->getSupportedModLoaders(); + if (m_filter->loaders) + loaders = m_filter->loaders; auto current_pack = getCurrentPack(); if (!current_pack) @@ -124,7 +127,7 @@ void ModPage::updateVersionList() auto version = current_pack->versions[i]; bool valid = false; for (auto& mcVer : m_filter->versions) { - if (validateVersion(version, mcVer.toString(), packProfile->getSupportedModLoaders())) { + if (validateVersion(version, mcVer.toString(), loaders)) { valid = true; break; } diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 5a43e49a60..6efb318eaf 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -31,8 +31,7 @@ class ModPage : public ResourcePage { auto page = new T(dialog, instance); auto model = static_cast(page->getModel()); - auto filter_widget = - ModFilterWidget::create(static_cast(instance).getPackProfile()->getComponentVersion("net.minecraft"), page); + auto filter_widget = ModFilterWidget::create(&static_cast(instance), page); page->setFilterWidget(filter_widget); model->setFilter(page->getFilter()); diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index 7d18e72a6e..39a2a0d6a9 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -6,6 +6,7 @@ #include "Json.h" +#include "minecraft/PackProfile.h" #include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameModIndex.h" diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index c2c099eeb5..62a8eb1543 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -1,13 +1,70 @@ #include "ModFilterWidget.h" +#include +#include +#include "BaseVersionList.h" +#include "meta/Index.h" +#include "modplatform/ModIndex.h" #include "ui_ModFilterWidget.h" #include "Application.h" +#include "minecraft/PackProfile.h" -unique_qobject_ptr ModFilterWidget::create(Version default_version, QWidget* parent) +unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, QWidget* parent) { - auto filter_widget = new ModFilterWidget(default_version, parent); + return unique_qobject_ptr(new ModFilterWidget(instance, parent)); +} + +ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, QWidget* parent) + : QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter()) +{ + ui->setupUi(this); + + m_versions_proxy = new VersionProxyModel(this); + + ui->versionsCb->setModel(m_versions_proxy); + + m_versions_proxy->setFilter(BaseVersionList::TypeRole, new RegexpFilter("(release)", false)); + + ui->versionsCb->setStyleSheet("combobox-popup: 0;"); + connect(ui->snapshotsCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onIncludeSnapshotsChanged); + connect(ui->versionsCb, &QComboBox::currentIndexChanged, this, &ModFilterWidget::onVersionFilterChanged); - if (!filter_widget->versionList()->isLoaded()) { + connect(ui->neoForgeCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->forgeCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->fabricCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->quiltCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->liteLoaderCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->cauldronCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + + ui->liteLoaderCb->hide(); + ui->cauldronCb->hide(); + + connect(ui->serverEnv, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged); + connect(ui->clientEnv, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged); + + connect(ui->hide_installed, &QCheckBox::stateChanged, this, &ModFilterWidget::onHideInstalledFilterChanged); + + setHidden(true); + loadVersionList(); + prepareBasicFilter(); +} + +auto ModFilterWidget::getFilter() -> std::shared_ptr +{ + m_filter_changed = false; + emit filterUnchanged(); + return m_filter; +} + +ModFilterWidget::~ModFilterWidget() +{ + delete ui; +} + +void ModFilterWidget::loadVersionList() +{ + m_version_list = APPLICATION->metadataIndex()->get("net.minecraft"); + if (!m_version_list->isLoaded()) { QEventLoop load_version_list_loop; QTimer time_limit_for_list_load; @@ -16,10 +73,12 @@ unique_qobject_ptr ModFilterWidget::create(Version default_vers time_limit_for_list_load.callOnTimeout(&load_version_list_loop, &QEventLoop::quit); time_limit_for_list_load.start(4000); - auto task = filter_widget->versionList()->getLoadTask(); + auto task = m_version_list->getLoadTask(); - connect(task.get(), &Task::failed, - [filter_widget] { filter_widget->disableVersionButton(VersionButtonID::Major, tr("failed to get version index")); }); + connect(task.get(), &Task::failed, [this] { + ui->versionsCb->setEnabled(false); + ui->snapshotsCb->setEnabled(false); + }); connect(task.get(), &Task::finished, &load_version_list_loop, &QEventLoop::quit); if (!task->isRunning()) @@ -29,128 +88,93 @@ unique_qobject_ptr ModFilterWidget::create(Version default_vers if (time_limit_for_list_load.isActive()) time_limit_for_list_load.stop(); } - - return unique_qobject_ptr(filter_widget); + m_versions_proxy->setSourceModel(m_version_list.get()); } -ModFilterWidget::ModFilterWidget(Version def, QWidget* parent) : QTabWidget(parent), m_filter(new Filter()), ui(new Ui::ModFilterWidget) +void ModFilterWidget::prepareBasicFilter() { - ui->setupUi(this); - - m_mcVersion_buttons.addButton(ui->strictVersionButton, VersionButtonID::Strict); - ui->strictVersionButton->click(); - m_mcVersion_buttons.addButton(ui->majorVersionButton, VersionButtonID::Major); - m_mcVersion_buttons.addButton(ui->allVersionsButton, VersionButtonID::All); - // m_mcVersion_buttons.addButton(ui->betweenVersionsButton, VersionButtonID::Between); - - connect(&m_mcVersion_buttons, SIGNAL(idClicked(int)), this, SLOT(onVersionFilterChanged(int))); - - m_filter->versions.push_front(def); - - m_version_list = APPLICATION->metadataIndex()->get("net.minecraft"); - setHidden(true); + m_filter->hideInstalled = false; + m_filter->side = ""; // or "both"t + auto loaders = m_instance->getPackProfile()->getSupportedModLoaders().value(); + ui->neoForgeCb->setChecked(loaders & ModPlatform::NeoForge); + ui->forgeCb->setChecked(loaders & ModPlatform::Forge); + ui->fabricCb->setChecked(loaders & ModPlatform::Fabric); + ui->quiltCb->setChecked(loaders & ModPlatform::Quilt); + ui->liteLoaderCb->setChecked(loaders & ModPlatform::LiteLoader); + ui->cauldronCb->setChecked(loaders & ModPlatform::Cauldron); + m_filter->loaders = loaders; + auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft"); + m_filter->versions.push_front(Version{ def }); + m_versions_proxy->setCurrentVersion(def); + ui->versionsCb->setCurrentIndex(m_versions_proxy->getVersion(def).row()); } -void ModFilterWidget::setInstance(MinecraftInstance* instance) +void ModFilterWidget::onIncludeSnapshotsChanged() { - m_instance = instance; - - ui->strictVersionButton->setText(tr("Strict match (= %1)").arg(mcVersionStr())); - - // we can't do this for snapshots sadly - if (mcVersionStr().contains('.')) { - auto mcVersionSplit = mcVersionStr().split("."); - ui->majorVersionButton->setText(tr("Major version match (= %1.%2.x)").arg(mcVersionSplit[0], mcVersionSplit[1])); - } else { - ui->majorVersionButton->setText(tr("Major version match (unsupported)")); - disableVersionButton(Major); - } - ui->allVersionsButton->setText(tr("Any version")); - // ui->betweenVersionsButton->setText( - // tr("Between two versions")); + QString filter = "(release)"; + if (ui->snapshotsCb->isChecked()) + filter += "|(snapshot)"; + m_versions_proxy->setFilter(BaseVersionList::TypeRole, new RegexpFilter(filter, false)); } -auto ModFilterWidget::getFilter() -> std::shared_ptr +void ModFilterWidget::onVersionFilterChanged() { - m_last_version_id = m_version_id; - emit filterUnchanged(); - return m_filter; + auto version = ui->versionsCb->currentData(BaseVersionList::VersionIdRole).toString(); + m_filter->versions.clear(); + m_filter->versions.push_front(version); + m_filter_changed = true; + emit filterChanged(); } -void ModFilterWidget::disableVersionButton(VersionButtonID id, QString reason) +void ModFilterWidget::onLoadersFilterChanged() { - QAbstractButton* btn = nullptr; - - switch (id) { - case (VersionButtonID::Strict): - btn = ui->strictVersionButton; - break; - case (VersionButtonID::Major): - btn = ui->majorVersionButton; - break; - case (VersionButtonID::All): - btn = ui->allVersionsButton; - break; - case (VersionButtonID::Between): - default: - break; - } - - if (btn) { - btn->setEnabled(false); - if (!reason.isEmpty()) - btn->setText(btn->text() + QString(" (%1)").arg(reason)); - } + ModPlatform::ModLoaderTypes loaders; + if (ui->neoForgeCb->isChecked()) + loaders |= ModPlatform::NeoForge; + if (ui->forgeCb->isChecked()) + loaders |= ModPlatform::Forge; + if (ui->fabricCb->isChecked()) + loaders |= ModPlatform::Fabric; + if (ui->quiltCb->isChecked()) + loaders |= ModPlatform::Quilt; + if (ui->cauldronCb->isChecked()) + loaders |= ModPlatform::Cauldron; + if (ui->liteLoaderCb->isChecked()) + loaders |= ModPlatform::LiteLoader; + m_filter_changed = loaders != m_filter->loaders; + m_filter->loaders = loaders; + if (m_filter_changed) + emit filterChanged(); + else + emit filterUnchanged(); } -void ModFilterWidget::onVersionFilterChanged(int id) +void ModFilterWidget::onSideFilterChanged() { - // ui->lowerVersionComboBox->setEnabled(id == VersionButtonID::Between); - // ui->upperVersionComboBox->setEnabled(id == VersionButtonID::Between); - - int index = 1; - - auto cast_id = (VersionButtonID)id; - if (cast_id != m_version_id) { - m_version_id = cast_id; - } else { - return; - } - - m_filter->versions.clear(); - - switch (cast_id) { - case (VersionButtonID::Strict): - m_filter->versions.push_front(mcVersion()); - break; - case (VersionButtonID::Major): { - auto versionSplit = mcVersionStr().split("."); - - auto major_version = QString("%1.%2").arg(versionSplit[0], versionSplit[1]); - QString version_str = major_version; - - while (m_version_list->hasVersion(version_str)) { - m_filter->versions.emplace_back(version_str); - version_str = QString("%1.%2").arg(major_version, QString::number(index++)); - } - - break; - } - case (VersionButtonID::All): - // Empty list to avoid enumerating all versions :P - break; - case (VersionButtonID::Between): - // TODO - break; + QString side; + if (ui->serverEnv->isChecked()) + side = "server"; + if (ui->clientEnv->isChecked()) { + if (side.isEmpty()) + side = "client"; + else + side = ""; // or both } - - if (changed()) + m_filter_changed = side != m_filter->side; + m_filter->side = side; + if (m_filter_changed) emit filterChanged(); else emit filterUnchanged(); } -ModFilterWidget::~ModFilterWidget() +void ModFilterWidget::onHideInstalledFilterChanged() { - delete ui; -} + auto hide = ui->hide_installed->isChecked(); + m_filter_changed = hide != m_filter->hideInstalled; + m_filter->hideInstalled = hide; + if (m_filter_changed) + emit filterChanged(); + else + emit filterUnchanged(); +} \ No newline at end of file diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index ed6cd0ea75..b92437a4f1 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -5,11 +5,11 @@ #include "Version.h" -#include "meta/Index.h" +#include "VersionProxyModel.h" #include "meta/VersionList.h" #include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" +#include "modplatform/ModIndex.h" class MinecraftInstance; @@ -20,42 +20,37 @@ class ModFilterWidget; class ModFilterWidget : public QTabWidget { Q_OBJECT public: - enum VersionButtonID { Strict = 0, Major = 1, All = 2, Between = 3 }; - struct Filter { std::list versions; - - bool operator==(const Filter& other) const { return versions == other.versions; } + ModPlatform::ModLoaderTypes loaders; + QString side; + bool hideInstalled; + + bool operator==(const Filter& other) const + { + return hideInstalled == other.hideInstalled && side == other.side && loaders == other.loaders && versions == other.versions; + } bool operator!=(const Filter& other) const { return !(*this == other); } }; - std::shared_ptr m_filter; - - public: - static unique_qobject_ptr create(Version default_version, QWidget* parent = nullptr); - ~ModFilterWidget(); - - void setInstance(MinecraftInstance* instance); - - /// By default all buttons are enabled - void disableVersionButton(VersionButtonID, QString reason = {}); + static unique_qobject_ptr create(MinecraftInstance* instance, QWidget* parent = nullptr); + virtual ~ModFilterWidget(); auto getFilter() -> std::shared_ptr; - auto changed() const -> bool { return m_last_version_id != m_version_id; } - - Meta::VersionList::Ptr versionList() { return m_version_list; } + auto changed() const -> bool { return m_filter_changed; } private: - ModFilterWidget(Version def, QWidget* parent = nullptr); + ModFilterWidget(MinecraftInstance* instance, QWidget* parent = nullptr); - inline auto mcVersionStr() const -> QString - { - return m_instance ? m_instance->getPackProfile()->getComponentVersion("net.minecraft") : ""; - } - inline auto mcVersion() const -> Version { return { mcVersionStr() }; } + void loadVersionList(); + void prepareBasicFilter(); private slots: - void onVersionFilterChanged(int id); + void onVersionFilterChanged(); + void onLoadersFilterChanged(); + void onSideFilterChanged(); + void onHideInstalledFilterChanged(); + void onIncludeSnapshotsChanged(); public: signals: @@ -66,13 +61,9 @@ class ModFilterWidget : public QTabWidget { Ui::ModFilterWidget* ui; MinecraftInstance* m_instance = nullptr; - - /* Version stuff */ - QButtonGroup m_mcVersion_buttons; + std::shared_ptr m_filter; + bool m_filter_changed = false; Meta::VersionList::Ptr m_version_list; - - /* Used to tell if the filter was changed since the last getFilter() call */ - VersionButtonID m_last_version_id = VersionButtonID::Strict; - VersionButtonID m_version_id = VersionButtonID::Strict; + VersionProxyModel* m_versions_proxy = nullptr; }; diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui index ebe5d2be17..74e27e5f73 100644 --- a/launcher/ui/widgets/ModFilterWidget.ui +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -7,7 +7,7 @@ 0 0 400 - 300 + 127 @@ -16,35 +16,143 @@ 0 + + 0 + Minecraft versions + + + + + 0 + 0 + + + + 5 + + + QComboBox::AdjustToContentsOnFirstShow + + + + + + + Include Snapshots + + + + + + + + Loaders + + + + QLayout::SetMinimumSize + + + + + false + + + LiteLoader + + + + + + + false + + + Cauldron + + + - - - - - allVersions - - - - - - - strictVersion - - - - - - - majorVersion - - - - + + + NeoForge + + + + + + + Forge + + + + + + + Quilt + + + + + + + Fabric + + + + + + + + Others + + + + + + Environments + + + + QLayout::SetDefaultConstraint + + + + + Client + + + + + + + Server + + + + + + + + + + Instaled status + + + + + + Hide already installed + + + + + From 55946c8923bf0e1b804944934aec2a42478f84a4 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 19 Oct 2023 23:53:26 +0300 Subject: [PATCH 0067/2054] first attempt at a combobox Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 2 + launcher/ui/widgets/CheckComboBox.cpp | 205 ++++++++++++++++++++++++ launcher/ui/widgets/CheckComboBox.h | 61 +++++++ launcher/ui/widgets/ModFilterWidget.cpp | 8 +- launcher/ui/widgets/ModFilterWidget.ui | 9 +- 5 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 launcher/ui/widgets/CheckComboBox.cpp create mode 100644 launcher/ui/widgets/CheckComboBox.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index d15dc85de8..a6c3a3e08a 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1035,6 +1035,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/InstallLoaderDialog.h # GUI - widgets + ui/widgets/CheckComboBox.cpp + ui/widgets/CheckComboBox.h ui/widgets/Common.cpp ui/widgets/Common.h ui/widgets/CustomCommands.cpp diff --git a/launcher/ui/widgets/CheckComboBox.cpp b/launcher/ui/widgets/CheckComboBox.cpp new file mode 100644 index 0000000000..9c9006c744 --- /dev/null +++ b/launcher/ui/widgets/CheckComboBox.cpp @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "CheckComboBox.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "BaseVersionList.h" + +class CheckComboModel : public QIdentityProxyModel { + Q_OBJECT + + public: + explicit CheckComboModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) {} + + virtual Qt::ItemFlags flags(const QModelIndex& index) const { return QIdentityProxyModel::flags(index) | Qt::ItemIsUserCheckable; } + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const + { + if (role == Qt::CheckStateRole) { + auto txt = QIdentityProxyModel::data(index, BaseVersionList::VersionIdRole).toString(); + return checked.contains(txt) ? Qt::Checked : Qt::Unchecked; + } + if (role == Qt::DisplayRole) + return QIdentityProxyModel::data(index, BaseVersionList::VersionIdRole); + return {}; + } + virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) + { + if (role == Qt::CheckStateRole) { + auto txt = QIdentityProxyModel::data(index, BaseVersionList::VersionIdRole).toString(); + if (checked.contains(txt)) { + checked.removeOne(txt); + } else { + checked.push_back(txt); + } + emit dataChanged(index, index); + emit checkStateChanged(); + return true; + } + return QIdentityProxyModel::setData(index, value, role); + } + QStringList getChecked() { return checked; } + + signals: + void checkStateChanged(); + + private: + QStringList checked; +}; + +CheckComboBox::CheckComboBox(QWidget* parent) : QComboBox(parent), m_separator(",") +{ + // read-only contents + // QLineEdit* lineEdit = new QLineEdit(this); + // lineEdit->setReadOnly(false); + // setLineEdit(lineEdit); + // lineEdit->disconnect(this); + setInsertPolicy(QComboBox::NoInsert); + + view()->installEventFilter(this); + view()->window()->installEventFilter(this); + view()->viewport()->installEventFilter(this); + this->installEventFilter(this); +} + +void CheckComboBox::setModel(QAbstractItemModel* new_model) +{ + auto proxy = new CheckComboModel(this); + proxy->setSourceModel(new_model); + model()->disconnect(this); + QComboBox::setModel(proxy); + connect(this, QOverload::of(&QComboBox::activated), this, &CheckComboBox::toggleCheckState); + connect(proxy, &CheckComboModel::checkStateChanged, this, &CheckComboBox::updateCheckedItems); + connect(model(), &CheckComboModel::rowsInserted, this, &CheckComboBox::updateCheckedItems); + connect(model(), &CheckComboModel::rowsRemoved, this, &CheckComboBox::updateCheckedItems); +} + +void CheckComboBox::hidePopup() +{ + if (containerMousePress) + QComboBox::hidePopup(); +} + +void CheckComboBox::updateCheckedItems() +{ + QStringList items = checkedItems(); + if (items.isEmpty()) + setEditText(defaultText()); + else + setEditText(items.join(separator())); + + emit checkedItemsChanged(items); +} + +QString CheckComboBox::defaultText() const +{ + return m_default_text; +} + +void CheckComboBox::setDefaultText(const QString& text) +{ + if (m_default_text != text) { + m_default_text = text; + updateCheckedItems(); + } +} + +QString CheckComboBox::separator() const +{ + return m_separator; +} + +void CheckComboBox::setSeparator(const QString& separator) +{ + if (m_separator != separator) { + m_separator = separator; + updateCheckedItems(); + } +} + +bool CheckComboBox::eventFilter(QObject* receiver, QEvent* event) +{ + switch (event->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: { + QKeyEvent* keyEvent = static_cast(event); + if (receiver == this && (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)) { + showPopup(); + return true; + } else if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Escape) { + // it is important to call QComboBox implementation + QComboBox::hidePopup(); + return (keyEvent->key() != Qt::Key_Escape); + } + } + case QEvent::MouseButtonPress: + containerMousePress = (receiver == view()->window()); + break; + case QEvent::MouseButtonRelease: + containerMousePress = false; + break; + default: + break; + } + return false; +} + +void CheckComboBox::toggleCheckState(int index) +{ + QVariant value = itemData(index, Qt::CheckStateRole); + if (value.isValid()) { + Qt::CheckState state = static_cast(value.toInt()); + setItemData(index, (state == Qt::Unchecked ? Qt::Checked : Qt::Unchecked), Qt::CheckStateRole); + } + updateCheckedItems(); +} + +Qt::CheckState CheckComboBox::itemCheckState(int index) const +{ + return static_cast(itemData(index, Qt::CheckStateRole).toInt()); +} + +void CheckComboBox::setItemCheckState(int index, Qt::CheckState state) +{ + setItemData(index, state, Qt::CheckStateRole); +} + +QStringList CheckComboBox::checkedItems() const +{ + if (model()) + return dynamic_cast(model())->getChecked(); + return {}; +} + +void CheckComboBox::setCheckedItems(const QStringList& items) +{ + foreach (auto text, items) { + auto index = findText(text); + setItemCheckState(index, index != -1 ? Qt::Checked : Qt::Unchecked); + } +} + +#include "CheckComboBox.moc" \ No newline at end of file diff --git a/launcher/ui/widgets/CheckComboBox.h b/launcher/ui/widgets/CheckComboBox.h new file mode 100644 index 0000000000..277dd5fb84 --- /dev/null +++ b/launcher/ui/widgets/CheckComboBox.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +class CheckComboBox : public QComboBox { + Q_OBJECT + + public: + explicit CheckComboBox(QWidget* parent = nullptr); + virtual ~CheckComboBox() = default; + + virtual void hidePopup() override; + + QString defaultText() const; + void setDefaultText(const QString& text); + + Qt::CheckState itemCheckState(int index) const; + void setItemCheckState(int index, Qt::CheckState state); + + QString separator() const; + void setSeparator(const QString& separator); + + QStringList checkedItems() const; + + virtual void setModel(QAbstractItemModel* model) override; + + public slots: + void setCheckedItems(const QStringList& items); + + signals: + void checkedItemsChanged(const QStringList& items); + + private: + void updateCheckedItems(); + bool eventFilter(QObject* receiver, QEvent* event) override; + void toggleCheckState(int index); + + private: + QString m_default_text; + QString m_separator; + bool containerMousePress; +}; \ No newline at end of file diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 62a8eb1543..5ed2e34f62 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -105,8 +105,7 @@ void ModFilterWidget::prepareBasicFilter() m_filter->loaders = loaders; auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft"); m_filter->versions.push_front(Version{ def }); - m_versions_proxy->setCurrentVersion(def); - ui->versionsCb->setCurrentIndex(m_versions_proxy->getVersion(def).row()); + ui->versionsCb->setCheckedItems({ def }); } void ModFilterWidget::onIncludeSnapshotsChanged() @@ -119,9 +118,10 @@ void ModFilterWidget::onIncludeSnapshotsChanged() void ModFilterWidget::onVersionFilterChanged() { - auto version = ui->versionsCb->currentData(BaseVersionList::VersionIdRole).toString(); + auto versions = ui->versionsCb->checkedItems(); m_filter->versions.clear(); - m_filter->versions.push_front(version); + for (auto version : versions) + m_filter->versions.push_back(version); m_filter_changed = true; emit filterChanged(); } diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui index 74e27e5f73..e27de6f1a5 100644 --- a/launcher/ui/widgets/ModFilterWidget.ui +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -25,7 +25,7 @@ - + 0 @@ -157,6 +157,13 @@ + + + CheckComboBox + QComboBox +
ui/widgets/CheckComboBox.h
+
+
From 6883c195795a8bb61a7ab50869d60265ddebc3d8 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 20 Oct 2023 00:44:36 +0300 Subject: [PATCH 0068/2054] Fixed the version combobox Signed-off-by: Trial97 --- launcher/ui/widgets/CheckComboBox.cpp | 16 +++++++--------- launcher/ui/widgets/ModFilterWidget.cpp | 23 +++++++++++++++++++++-- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/launcher/ui/widgets/CheckComboBox.cpp b/launcher/ui/widgets/CheckComboBox.cpp index 9c9006c744..f826c1afe8 100644 --- a/launcher/ui/widgets/CheckComboBox.cpp +++ b/launcher/ui/widgets/CheckComboBox.cpp @@ -39,17 +39,17 @@ class CheckComboModel : public QIdentityProxyModel { virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const { if (role == Qt::CheckStateRole) { - auto txt = QIdentityProxyModel::data(index, BaseVersionList::VersionIdRole).toString(); + auto txt = QIdentityProxyModel::data(index, Qt::DisplayRole).toString(); return checked.contains(txt) ? Qt::Checked : Qt::Unchecked; } if (role == Qt::DisplayRole) - return QIdentityProxyModel::data(index, BaseVersionList::VersionIdRole); + return QIdentityProxyModel::data(index, Qt::DisplayRole); return {}; } virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) { if (role == Qt::CheckStateRole) { - auto txt = QIdentityProxyModel::data(index, BaseVersionList::VersionIdRole).toString(); + auto txt = QIdentityProxyModel::data(index, Qt::DisplayRole).toString(); if (checked.contains(txt)) { checked.removeOne(txt); } else { @@ -72,11 +72,10 @@ class CheckComboModel : public QIdentityProxyModel { CheckComboBox::CheckComboBox(QWidget* parent) : QComboBox(parent), m_separator(",") { - // read-only contents - // QLineEdit* lineEdit = new QLineEdit(this); - // lineEdit->setReadOnly(false); - // setLineEdit(lineEdit); - // lineEdit->disconnect(this); + QLineEdit* lineEdit = new QLineEdit(this); + lineEdit->setReadOnly(false); + setLineEdit(lineEdit); + lineEdit->disconnect(this); setInsertPolicy(QComboBox::NoInsert); view()->installEventFilter(this); @@ -150,7 +149,6 @@ bool CheckComboBox::eventFilter(QObject* receiver, QEvent* event) showPopup(); return true; } else if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Escape) { - // it is important to call QComboBox implementation QComboBox::hidePopup(); return (keyEvent->key() != Qt::Key_Escape); } diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 5ed2e34f62..5a67757b54 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -14,6 +14,20 @@ unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* i return unique_qobject_ptr(new ModFilterWidget(instance, parent)); } +class VersionBasicModel : public QIdentityProxyModel { + Q_OBJECT + + public: + explicit VersionBasicModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) {} + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override + { + if (role == Qt::DisplayRole) + return QIdentityProxyModel::data(index, BaseVersionList::VersionIdRole); + return {}; + } +}; + ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, QWidget* parent) : QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter()) { @@ -21,7 +35,10 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, QWidget* parent) m_versions_proxy = new VersionProxyModel(this); - ui->versionsCb->setModel(m_versions_proxy); + auto proxy = new VersionBasicModel(this); + proxy->setSourceModel(m_versions_proxy); + ui->versionsCb->setModel(proxy); + ui->versionsCb->setSeparator("| "); m_versions_proxy->setFilter(BaseVersionList::TypeRole, new RegexpFilter("(release)", false)); @@ -177,4 +194,6 @@ void ModFilterWidget::onHideInstalledFilterChanged() emit filterChanged(); else emit filterUnchanged(); -} \ No newline at end of file +} + +#include "ModFilterWidget.moc" From 4850434c67017622cf7b082728cf56a3b42d7920 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 21 Oct 2023 12:16:02 +0300 Subject: [PATCH 0069/2054] added releaseType? Signed-off-by: Trial97 --- launcher/ui/widgets/ModFilterWidget.ui | 73 +++++++++++++++++++------- 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui index e27de6f1a5..3113f2df23 100644 --- a/launcher/ui/widgets/ModFilterWidget.ui +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -17,7 +17,7 @@ - 0 + 2 @@ -57,17 +57,28 @@ QLayout::SetMinimumSize - - - - false + + + + Fabric + + + + - LiteLoader + NeoForge - + + + + Forge + + + + false @@ -77,31 +88,55 @@ - - + + - NeoForge + Quilt - - + + + + false + - Forge + LiteLoader - - +
+ + + + Release type + + + + - Quilt + Release - - + + - Fabric + Beta + + + + + + + Alpha + + + + + + + Unknown From 9e85297f7af495ecf8c34ea62a3559b712ff8e14 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 21 Oct 2023 18:28:33 +0300 Subject: [PATCH 0070/2054] Connected filters Signed-off-by: Trial97 --- launcher/modplatform/ResourceAPI.h | 1 + launcher/modplatform/flame/FlameModIndex.cpp | 7 +-- launcher/modplatform/modrinth/ModrinthAPI.h | 18 +++++++ .../modrinth/ModrinthPackIndex.cpp | 7 +-- launcher/ui/pages/modplatform/ModModel.cpp | 53 +++++++++++++++--- launcher/ui/pages/modplatform/ModModel.h | 3 ++ launcher/ui/pages/modplatform/ModPage.cpp | 38 +------------ launcher/ui/pages/modplatform/ModPage.h | 9 +--- .../ui/pages/modplatform/ResourceModel.cpp | 15 ++++-- launcher/ui/pages/modplatform/ResourceModel.h | 11 ++++ .../ui/pages/modplatform/ResourcePage.cpp | 2 +- launcher/ui/pages/modplatform/ResourcePage.h | 7 --- .../modplatform/flame/FlameResourceModels.cpp | 25 +++++++++ .../modplatform/flame/FlameResourceModels.h | 8 +++ .../modplatform/flame/FlameResourcePages.cpp | 38 ++----------- .../modplatform/flame/FlameResourcePages.h | 12 +---- .../modrinth/ModrinthResourcePages.cpp | 11 ++-- .../modrinth/ModrinthResourcePages.h | 3 +- launcher/ui/widgets/CheckComboBox.cpp | 2 +- launcher/ui/widgets/CheckComboBox.h | 4 +- launcher/ui/widgets/ModFilterWidget.cpp | 54 ++++++++++++++++--- launcher/ui/widgets/ModFilterWidget.h | 19 ++++--- launcher/ui/widgets/ModFilterWidget.ui | 19 ++++--- 23 files changed, 214 insertions(+), 152 deletions(-) diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index 3b19593842..732c666216 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -73,6 +73,7 @@ class ResourceAPI { std::optional sorting; std::optional loaders; std::optional > versions; + std::optional side; }; struct SearchCallbacks { std::function on_succeed; diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index d56e46b23b..f95b17683c 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -79,10 +79,6 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, const BaseInstance* inst) { QVector unsortedVersions; - auto profile = (dynamic_cast(inst))->getPackProfile(); - QString mcVersion = profile->getComponentVersion("net.minecraft"); - auto loaders = profile->getSupportedModLoaders(); - for (auto versionIter : arr) { auto obj = versionIter.toObject(); @@ -90,8 +86,7 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, if (!file.addonId.isValid()) file.addonId = pack.addonId; - if (file.fileId.isValid() && - (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid + if (file.fileId.isValid()) // Heuristic to check if the returned value is valid unsortedVersions.append(file); } diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 8577199027..8e0f9fe22a 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -56,6 +56,18 @@ class ModrinthAPI : public NetworkResourceAPI { return l.join(','); } + static auto getSideFilters(QString side) -> const QString + { + if (side.isEmpty() || side == "both") { + return {}; + } + if (side == "client") + return QString("\"client_side:required\",\"client_side:optional\""); + if (side == "server") + return QString("\"server_side:required\",\"server_side:optional\""); + return {}; + } + private: [[nodiscard]] static QString resourceTypeParameter(ModPlatform::ResourceType type) { @@ -73,6 +85,7 @@ class ModrinthAPI : public NetworkResourceAPI { return ""; } + [[nodiscard]] QString createFacets(SearchArgs const& args) const { QStringList facets_list; @@ -81,6 +94,11 @@ class ModrinthAPI : public NetworkResourceAPI { facets_list.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value()))); if (args.versions.has_value()) facets_list.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value()))); + if (args.side.has_value()) { + auto side = getSideFilters(args.side.value()); + if (!side.isEmpty()) + facets_list.append(QString("[%1]").arg(side)); + } facets_list.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type))); return QString("[%1]").arg(facets_list.join(',')); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 23770b7fb7..5de6452201 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -112,16 +112,11 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst) { QVector unsortedVersions; - auto profile = (dynamic_cast(inst))->getPackProfile(); - QString mcVersion = profile->getComponentVersion("net.minecraft"); - auto loaders = profile->getSupportedModLoaders(); - for (auto versionIter : arr) { auto obj = versionIter.toObject(); auto file = loadIndexedPackVersion(obj); - if (file.fileId.isValid() && - (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid + if (file.fileId.isValid()) // Heuristic to check if the returned value is valid unsortedVersions.append(file); } auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 3b53d13484..64ae7de367 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -27,16 +27,16 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments() std::optional> versions{}; auto loaders = profile->getSupportedModLoaders(); - { // Version filter - if (!m_filter->versions.empty()) - versions = m_filter->versions; - if (m_filter->loaders) - loaders = m_filter->loaders; - } + // Version filter + if (!m_filter->versions.empty()) + versions = m_filter->versions; + if (m_filter->loaders) + loaders = m_filter->loaders; + auto side = m_filter->side; auto sort = getCurrentSortingMethodByIndex(); - return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, loaders, versions }; + return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, loaders, versions, side }; } ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) @@ -85,4 +85,43 @@ bool ModModel::isPackInstalled(ModPlatform::IndexedPack::Ptr pack) const }); } +bool checkSide(QString filter, QString value) +{ + return filter.isEmpty() || value.isEmpty() || filter == "both" || value == "both" || filter == value; +} + +bool checkMcVersions(std::list filter, QStringList value) +{ + bool valid = false; + for (auto mcVersion : filter) { + if (value.contains(mcVersion.toString())) { + valid = true; + break; + } + } + return filter.empty() || valid; +} + +bool ModModel::checkFilters(ModPlatform::IndexedPack::Ptr pack) +{ + if (!m_filter) + return true; + return !(m_filter->hideInstalled && isPackInstalled(pack)) && checkSide(m_filter->side, pack->side); +} + +bool ModModel::checkVersionFilters(const ModPlatform::IndexedVersion& v) +{ + if (!m_filter) + return true; + auto loaders = static_cast(m_base_instance).getPackProfile()->getSupportedModLoaders(); + if (m_filter->loaders) + loaders = m_filter->loaders; + return (!optedOut(v) && // is opted out(aka curseforge download link) + (!loaders.has_value() || !v.loaders || loaders.value() & v.loaders) && // loaders + checkSide(m_filter->side, v.side) && // side + (m_filter->releases.empty() || // releases + std::find(m_filter->releases.cbegin(), m_filter->releases.cend(), v.version_type) != m_filter->releases.cend()) && + checkMcVersions(m_filter->versions, v.mcVersion)); // mcVersions +} + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index dd187aa8db..9101e07ba8 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -45,6 +45,9 @@ class ModModel : public ResourceModel { auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0; virtual bool isPackInstalled(ModPlatform::IndexedPack::Ptr) const override; + virtual bool checkFilters(ModPlatform::IndexedPack::Ptr) override; + virtual bool checkVersionFilters(const ModPlatform::IndexedVersion&) override; + protected: BaseInstance& m_base_instance; diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 838cf21c83..e6106a0de8 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -73,6 +73,7 @@ void ModPage::setFilterWidget(unique_qobject_ptr& widget) m_filter = m_filter_widget->getFilter(); + connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, &ResourcePage::updateVersionList); connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, [&] { m_ui->searchButton->setStyleSheet("text-decoration: underline"); }); connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this, @@ -110,43 +111,6 @@ QMap ModPage::urlHandlers() const /******** Make changes to the UI ********/ -void ModPage::updateVersionList() -{ - m_ui->versionSelectionBox->clear(); - auto packProfile = (dynamic_cast(m_base_instance)).getPackProfile(); - - QString mcVersion = packProfile->getComponentVersion("net.minecraft"); - auto loaders = packProfile->getSupportedModLoaders(); - if (m_filter->loaders) - loaders = m_filter->loaders; - - auto current_pack = getCurrentPack(); - if (!current_pack) - return; - for (int i = 0; i < current_pack->versions.size(); i++) { - auto version = current_pack->versions[i]; - bool valid = false; - for (auto& mcVer : m_filter->versions) { - if (validateVersion(version, mcVer.toString(), loaders)) { - valid = true; - break; - } - } - - // Only add the version if it's valid or using the 'Any' filter, but never if the version is opted out - if ((valid || m_filter->versions.empty()) && !optedOut(version)) { - auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : ""; - m_ui->versionSelectionBox->addItem(QString("%1%2").arg(version.version, release_type), QVariant(i)); - } - } - if (m_ui->versionSelectionBox->count() == 0) { - m_ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); - m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :(")); - } - - updateSelectionButton(); -} - void ModPage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, const std::shared_ptr base_model) diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 6efb318eaf..c878bc0045 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -31,7 +31,7 @@ class ModPage : public ResourcePage { auto page = new T(dialog, instance); auto model = static_cast(page->getModel()); - auto filter_widget = ModFilterWidget::create(&static_cast(instance), page); + auto filter_widget = page->createFilterWidget(); page->setFilterWidget(filter_widget); model->setFilter(page->getFilter()); @@ -52,17 +52,12 @@ class ModPage : public ResourcePage { ModPlatform::IndexedVersion&, const std::shared_ptr) override; - virtual auto validateVersion(ModPlatform::IndexedVersion& ver, - QString mineVer, - std::optional loaders = {}) const -> bool = 0; + virtual unique_qobject_ptr createFilterWidget() = 0; [[nodiscard]] bool supportsFiltering() const override { return true; }; auto getFilter() const -> const std::shared_ptr { return m_filter; } void setFilterWidget(unique_qobject_ptr&); - public slots: - void updateVersionList() override; - protected: ModPage(ModDownloadDialog* dialog, BaseInstance& instance); diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 48e66efca2..776765d641 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -399,12 +399,17 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc) m_search_state = SearchState::CanFetchMore; } + QList filteredNewList; + for (auto p : newList) + if (checkFilters(p)) + filteredNewList << p; + // When you have a Qt build with assertions turned on, proceeding here will abort the application - if (newList.size() == 0) + if (filteredNewList.size() == 0) return; - beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + newList.size() - 1); - m_packs.append(newList); + beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + filteredNewList.size() - 1); + m_packs.append(filteredNewList); endInsertRows(); } @@ -547,4 +552,8 @@ void ResourceModel::removePack(const QString& rem) ver.is_currently_selected = false; } +bool ResourceModel::checkVersionFilters(const ModPlatform::IndexedVersion& v) +{ + return (!optedOut(v)); +} } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index ecf4f8f793..1956e54ac1 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -11,6 +11,7 @@ #include "QObjectPtr.h" #include "ResourceDownloadTask.h" +#include "modplatform/ModIndex.h" #include "modplatform/ResourceAPI.h" #include "tasks/ConcurrentTask.h" @@ -55,6 +56,16 @@ class ResourceModel : public QAbstractListModel { [[nodiscard]] auto getSortingMethods() const { return m_api->getSortingMethods(); } + /** Whether the version is opted out or not. Currently only makes sense in CF. */ + virtual bool optedOut(const ModPlatform::IndexedVersion& ver) const + { + Q_UNUSED(ver); + return false; + }; + + virtual bool checkFilters(ModPlatform::IndexedPack::Ptr) { return true; } + virtual bool checkVersionFilters(const ModPlatform::IndexedVersion&); + public slots: void fetchMore(const QModelIndex& parent) override; // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 44a91003d6..158b13c965 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -263,7 +263,7 @@ void ResourcePage::updateVersionList() if (current_pack) for (int i = 0; i < current_pack->versions.size(); i++) { auto& version = current_pack->versions[i]; - if (optedOut(version)) + if (!m_model->checkVersionFilters(version)) continue; auto release_type = current_pack->versions[i].version_type.isValid() diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 7bec0a3751..84b37f50b8 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -96,13 +96,6 @@ class ResourcePage : public QWidget, public BasePage { virtual QMap urlHandlers() const = 0; virtual void openUrl(const QUrl&); - /** Whether the version is opted out or not. Currently only makes sense in CF. */ - virtual bool optedOut(ModPlatform::IndexedVersion& ver) const - { - Q_UNUSED(ver); - return false; - }; - public: BaseInstance& m_base_instance; diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index 39a2a0d6a9..ae4562be4a 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -12,6 +12,11 @@ namespace ResourceDownload { +static bool isOptedOut(const ModPlatform::IndexedVersion& ver) +{ + return ver.downloadUrl.isEmpty(); +} + FlameModModel::FlameModModel(BaseInstance& base) : ModModel(base, new FlameAPI) {} void FlameModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) @@ -35,6 +40,11 @@ auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJs return FlameMod::loadDependencyVersions(m, arr, &m_base_instance); } +bool FlameModModel::optedOut(const ModPlatform::IndexedVersion& ver) const +{ + return isOptedOut(ver); +} + auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray { return Json::ensureArray(obj.object(), "data"); @@ -58,6 +68,11 @@ void FlameResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); } +bool FlameResourcePackModel::optedOut(const ModPlatform::IndexedVersion& ver) const +{ + return isOptedOut(ver); +} + auto FlameResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray { return Json::ensureArray(obj.object(), "data"); @@ -117,6 +132,11 @@ ResourceAPI::VersionSearchArgs FlameTexturePackModel::createVersionsArguments(QM return args; } +bool FlameTexturePackModel::optedOut(const ModPlatform::IndexedVersion& ver) const +{ + return isOptedOut(ver); +} + auto FlameTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray { return Json::ensureArray(obj.object(), "data"); @@ -140,6 +160,11 @@ void FlameShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); } +bool FlameShaderPackModel::optedOut(const ModPlatform::IndexedVersion& ver) const +{ + return isOptedOut(ver); +} + auto FlameShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray { return Json::ensureArray(obj.object(), "data"); diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h index 76dbd7b3d0..458fd85d03 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -17,6 +17,8 @@ class FlameModModel : public ModModel { FlameModModel(BaseInstance&); ~FlameModModel() override = default; + bool optedOut(const ModPlatform::IndexedVersion& ver) const override; + private: [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } @@ -36,6 +38,8 @@ class FlameResourcePackModel : public ResourcePackResourceModel { FlameResourcePackModel(const BaseInstance&); ~FlameResourcePackModel() override = default; + bool optedOut(const ModPlatform::IndexedVersion& ver) const override; + private: [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } @@ -54,6 +58,8 @@ class FlameTexturePackModel : public TexturePackResourceModel { FlameTexturePackModel(const BaseInstance&); ~FlameTexturePackModel() override = default; + bool optedOut(const ModPlatform::IndexedVersion& ver) const override; + private: [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } @@ -75,6 +81,8 @@ class FlameShaderPackModel : public ShaderPackResourceModel { FlameShaderPackModel(const BaseInstance&); ~FlameShaderPackModel() override = default; + bool optedOut(const ModPlatform::IndexedVersion& ver) const override; + private: [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 23373ec9d1..66a51decd6 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -44,11 +44,6 @@ namespace ResourceDownload { -static bool isOptedOut(ModPlatform::IndexedVersion const& ver) -{ - return ver.downloadUrl.isEmpty(); -} - FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ModPage(dialog, instance) { m_model = new FlameModModel(instance); @@ -66,19 +61,6 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) : m_ui->packDescription->setMetaEntry(metaEntryBase()); } -auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, - QString mineVer, - std::optional loaders) const -> bool -{ - return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty() && - (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders); -} - -bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const -{ - return isOptedOut(ver); -} - void FlameModPage::openUrl(const QUrl& url) { if (url.scheme().isEmpty()) { @@ -113,11 +95,6 @@ FlameResourcePackPage::FlameResourcePackPage(ResourcePackDownloadDialog* dialog, m_ui->packDescription->setMetaEntry(metaEntryBase()); } -bool FlameResourcePackPage::optedOut(ModPlatform::IndexedVersion& ver) const -{ - return isOptedOut(ver); -} - void FlameResourcePackPage::openUrl(const QUrl& url) { if (url.scheme().isEmpty()) { @@ -152,11 +129,6 @@ FlameTexturePackPage::FlameTexturePackPage(TexturePackDownloadDialog* dialog, Ba m_ui->packDescription->setMetaEntry(metaEntryBase()); } -bool FlameTexturePackPage::optedOut(ModPlatform::IndexedVersion& ver) const -{ - return isOptedOut(ver); -} - void FlameTexturePackPage::openUrl(const QUrl& url) { if (url.scheme().isEmpty()) { @@ -191,11 +163,6 @@ FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseI m_ui->packDescription->setMetaEntry(metaEntryBase()); } -bool FlameShaderPackPage::optedOut(ModPlatform::IndexedVersion& ver) const -{ - return isOptedOut(ver); -} - void FlameShaderPackPage::openUrl(const QUrl& url) { if (url.scheme().isEmpty()) { @@ -232,4 +199,9 @@ auto FlameShaderPackPage::shouldDisplay() const -> bool return true; } +unique_qobject_ptr FlameModPage::createFilterWidget() +{ + return ModFilterWidget::create(&static_cast(m_base_instance), false, this); +} + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index f2f5cecad0..88ef404fbc 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -94,12 +94,8 @@ class FlameModPage : public ModPage { [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } - bool validateVersion(ModPlatform::IndexedVersion& ver, - QString mineVer, - std::optional loaders = {}) const override; - bool optedOut(ModPlatform::IndexedVersion& ver) const override; - void openUrl(const QUrl& url) override; + unique_qobject_ptr createFilterWidget() override; }; class FlameResourcePackPage : public ResourcePackResourcePage { @@ -124,8 +120,6 @@ class FlameResourcePackPage : public ResourcePackResourcePage { [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } - bool optedOut(ModPlatform::IndexedVersion& ver) const override; - void openUrl(const QUrl& url) override; }; @@ -151,8 +145,6 @@ class FlameTexturePackPage : public TexturePackResourcePage { [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } - bool optedOut(ModPlatform::IndexedVersion& ver) const override; - void openUrl(const QUrl& url) override; }; @@ -178,8 +170,6 @@ class FlameShaderPackPage : public ShaderPackResourcePage { [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } - bool optedOut(ModPlatform::IndexedVersion& ver) const override; - void openUrl(const QUrl& url) override; }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index a4197b2255..ab015ff0e2 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -63,13 +63,6 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instan m_ui->packDescription->setMetaEntry(metaEntryBase()); } -auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, - QString mineVer, - std::optional loaders) const -> bool -{ - return ver.mcVersion.contains(mineVer) && (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders); -} - ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance) : ResourcePackResourcePage(dialog, instance) { @@ -144,4 +137,8 @@ auto ModrinthShaderPackPage::shouldDisplay() const -> bool return true; } +unique_qobject_ptr ModrinthModPage::createFilterWidget() +{ + return ModFilterWidget::create(&static_cast(m_base_instance), true, this); +} } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index 311bcfe321..0a715d747a 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -93,8 +93,7 @@ class ModrinthModPage : public ModPage { [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } - auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const - -> bool override; + unique_qobject_ptr createFilterWidget() override; }; class ModrinthResourcePackPage : public ResourcePackResourcePage { diff --git a/launcher/ui/widgets/CheckComboBox.cpp b/launcher/ui/widgets/CheckComboBox.cpp index f826c1afe8..af45943357 100644 --- a/launcher/ui/widgets/CheckComboBox.cpp +++ b/launcher/ui/widgets/CheckComboBox.cpp @@ -84,7 +84,7 @@ CheckComboBox::CheckComboBox(QWidget* parent) : QComboBox(parent), m_separator(" this->installEventFilter(this); } -void CheckComboBox::setModel(QAbstractItemModel* new_model) +void CheckComboBox::setSourceModel(QAbstractItemModel* new_model) { auto proxy = new CheckComboModel(this); proxy->setSourceModel(new_model); diff --git a/launcher/ui/widgets/CheckComboBox.h b/launcher/ui/widgets/CheckComboBox.h index 277dd5fb84..f1d9771cc8 100644 --- a/launcher/ui/widgets/CheckComboBox.h +++ b/launcher/ui/widgets/CheckComboBox.h @@ -28,7 +28,7 @@ class CheckComboBox : public QComboBox { explicit CheckComboBox(QWidget* parent = nullptr); virtual ~CheckComboBox() = default; - virtual void hidePopup() override; + void hidePopup() override; QString defaultText() const; void setDefaultText(const QString& text); @@ -41,7 +41,7 @@ class CheckComboBox : public QComboBox { QStringList checkedItems() const; - virtual void setModel(QAbstractItemModel* model) override; + void setSourceModel(QAbstractItemModel* model); public slots: void setCheckedItems(const QStringList& items); diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 5a67757b54..dde5d75025 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -9,9 +9,9 @@ #include "Application.h" #include "minecraft/PackProfile.h" -unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, QWidget* parent) +unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, bool extendedSupport, QWidget* parent) { - return unique_qobject_ptr(new ModFilterWidget(instance, parent)); + return unique_qobject_ptr(new ModFilterWidget(instance, extendedSupport, parent)); } class VersionBasicModel : public QIdentityProxyModel { @@ -28,23 +28,33 @@ class VersionBasicModel : public QIdentityProxyModel { } }; -ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, QWidget* parent) +ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent) : QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter()) { ui->setupUi(this); m_versions_proxy = new VersionProxyModel(this); + m_versions_proxy->setFilter(BaseVersionList::TypeRole, new RegexpFilter("(release)", false)); auto proxy = new VersionBasicModel(this); proxy->setSourceModel(m_versions_proxy); - ui->versionsCb->setModel(proxy); - ui->versionsCb->setSeparator("| "); - m_versions_proxy->setFilter(BaseVersionList::TypeRole, new RegexpFilter("(release)", false)); + if (!extendedSupport) { + ui->versionsSimpleCb->setModel(proxy); + ui->versionsCb->hide(); + ui->snapshotsCb->hide(); + ui->envBox->hide(); + } else { + ui->versionsCb->setSourceModel(proxy); + ui->versionsCb->setSeparator("| "); + ui->versionsSimpleCb->hide(); + } ui->versionsCb->setStyleSheet("combobox-popup: 0;"); + ui->versionsSimpleCb->setStyleSheet("combobox-popup: 0;"); connect(ui->snapshotsCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onIncludeSnapshotsChanged); connect(ui->versionsCb, &QComboBox::currentIndexChanged, this, &ModFilterWidget::onVersionFilterChanged); + connect(ui->versionsSimpleCb, &QComboBox::currentTextChanged, this, &ModFilterWidget::onVersionFilterTextChanged); connect(ui->neoForgeCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->forgeCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); @@ -61,6 +71,11 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, QWidget* parent) connect(ui->hide_installed, &QCheckBox::stateChanged, this, &ModFilterWidget::onHideInstalledFilterChanged); + connect(ui->releaseCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onReleaseFilterChanged); + connect(ui->betaCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onReleaseFilterChanged); + connect(ui->alphaCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onReleaseFilterChanged); + connect(ui->unknownCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onReleaseFilterChanged); + setHidden(true); loadVersionList(); prepareBasicFilter(); @@ -196,4 +211,31 @@ void ModFilterWidget::onHideInstalledFilterChanged() emit filterUnchanged(); } +void ModFilterWidget::onReleaseFilterChanged() +{ + std::list releases; + if (ui->releaseCb->isChecked()) + releases.push_back(ModPlatform::IndexedVersionType(ModPlatform::IndexedVersionType::VersionType::Release)); + if (ui->betaCb->isChecked()) + releases.push_back(ModPlatform::IndexedVersionType(ModPlatform::IndexedVersionType::VersionType::Beta)); + if (ui->alphaCb->isChecked()) + releases.push_back(ModPlatform::IndexedVersionType(ModPlatform::IndexedVersionType::VersionType::Alpha)); + if (ui->unknownCb->isChecked()) + releases.push_back(ModPlatform::IndexedVersionType(ModPlatform::IndexedVersionType::VersionType::Unknown)); + m_filter_changed = releases != m_filter->releases; + m_filter->releases = releases; + if (m_filter_changed) + emit filterChanged(); + else + emit filterUnchanged(); +} + +void ModFilterWidget::onVersionFilterTextChanged(QString version) +{ + m_filter->versions.clear(); + m_filter->versions.push_front(version); + m_filter_changed = true; + emit filterChanged(); +} + #include "ModFilterWidget.moc" diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index b92437a4f1..50e6b37626 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -22,41 +22,44 @@ class ModFilterWidget : public QTabWidget { public: struct Filter { std::list versions; + std::list releases; ModPlatform::ModLoaderTypes loaders; QString side; bool hideInstalled; bool operator==(const Filter& other) const { - return hideInstalled == other.hideInstalled && side == other.side && loaders == other.loaders && versions == other.versions; + return hideInstalled == other.hideInstalled && side == other.side && loaders == other.loaders && versions == other.versions && + releases == other.releases; } bool operator!=(const Filter& other) const { return !(*this == other); } }; - static unique_qobject_ptr create(MinecraftInstance* instance, QWidget* parent = nullptr); + static unique_qobject_ptr create(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); virtual ~ModFilterWidget(); auto getFilter() -> std::shared_ptr; auto changed() const -> bool { return m_filter_changed; } + signals: + void filterChanged(); + void filterUnchanged(); + private: - ModFilterWidget(MinecraftInstance* instance, QWidget* parent = nullptr); + ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); void loadVersionList(); void prepareBasicFilter(); private slots: void onVersionFilterChanged(); + void onVersionFilterTextChanged(QString version); + void onReleaseFilterChanged(); void onLoadersFilterChanged(); void onSideFilterChanged(); void onHideInstalledFilterChanged(); void onIncludeSnapshotsChanged(); - public: - signals: - void filterChanged(); - void filterUnchanged(); - private: Ui::ModFilterWidget* ui; diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui index 3113f2df23..d962ea44ab 100644 --- a/launcher/ui/widgets/ModFilterWidget.ui +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -6,7 +6,7 @@ 0 0 - 400 + 460 127 @@ -17,13 +17,20 @@ - 2 + 0 Minecraft versions + + + + Include Snapshots + + + @@ -40,12 +47,8 @@ - - - - Include Snapshots - - + + From 9aac8e389f4e5daaf05826691b6bdb21bfb06e09 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 21 Oct 2023 18:48:55 +0300 Subject: [PATCH 0071/2054] made release type visible Signed-off-by: Trial97 --- launcher/minecraft/mod/Mod.cpp | 15 +++++++++++++++ launcher/minecraft/mod/Mod.h | 1 + launcher/minecraft/mod/ModFolderModel.cpp | 21 ++++++++++++++------- launcher/minecraft/mod/ModFolderModel.h | 1 + launcher/minecraft/mod/Resource.h | 2 +- launcher/modplatform/packwiz/Packwiz.cpp | 3 +++ launcher/modplatform/packwiz/Packwiz.h | 1 + launcher/ui/widgets/ModFilterWidget.cpp | 2 +- launcher/ui/widgets/ModFilterWidget.h | 2 +- 9 files changed, 38 insertions(+), 10 deletions(-) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 22e652319f..f301cc9495 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -47,6 +47,7 @@ #include "minecraft/mod/ModDetails.h" #include "minecraft/mod/Resource.h" #include "minecraft/mod/tasks/LocalModParseTask.h" +#include "modplatform/ModIndex.h" static ModPlatform::ProviderCapabilities ProviderCaps; @@ -132,6 +133,13 @@ std::pair Mod::compare(const Resource& other, SortType type) const return { compare_result, type == SortType::MC_VERSIONS }; break; } + case SortType::RELEASE_TYPE: { + if (releaseType() > cast_other->releaseType()) + return { 1, type == SortType::RELEASE_TYPE }; + else if (releaseType() < cast_other->releaseType()) + return { -1, type == SortType::RELEASE_TYPE }; + break; + } } return { 0, false }; } @@ -262,6 +270,13 @@ auto Mod::side() const -> Metadata::ModSide return Metadata::ModSide::UniversalSide; } +auto Mod::releaseType() const -> ModPlatform::IndexedVersionType +{ + if (metadata()) + return metadata()->releaseType; + return ModPlatform::IndexedVersionType::VersionType::Unknown; +} + auto Mod::loaders() const -> ModPlatform::ModLoaderTypes { if (metadata()) diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 92de65889e..bf503bc80b 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -74,6 +74,7 @@ class Mod : public Resource { auto side() const -> Metadata::ModSide; auto loaders() const -> ModPlatform::ModLoaderTypes; auto mcVersions() const -> QStringList; + auto releaseType() const -> ModPlatform::IndexedVersionType; /** Get the intneral path to the mod's icon file*/ QString iconPath() const { return m_local_details.icon_file; } diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index ef4ab957c2..fb3688d5d3 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -64,16 +64,17 @@ ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir) : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) { - m_column_names = - QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Side", "Loaders", "Miecraft Versions" }); + m_column_names = QStringList( + { "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Side", "Loaders", "Miecraft Versions", "Release Type" }); m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), - tr("Side"), tr("Loaders"), tr("Miecraft Versions") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, - SortType::PROVIDER, SortType::SIDE, SortType::LOADERS, SortType::MC_VERSIONS }; + tr("Side"), tr("Loaders"), tr("Miecraft Versions"), tr("Release Type") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, + SortType::PROVIDER, SortType::SIDE, SortType::LOADERS, SortType::MC_VERSIONS, SortType::RELEASE_TYPE }; m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, - QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; - m_columnsHideable = { false, true, false, true, true, true, true, true, true }; + QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, + QHeaderView::ResizeToContents }; + m_columnsHideable = { false, true, false, true, true, true, true, true, true, true }; } QVariant ModFolderModel::data(const QModelIndex& index, int role) const @@ -128,6 +129,9 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const case McVersionsColumn: { return at(row)->mcVersions().join(", "); } + case ReleaseTypeColumn: { + return at(row)->releaseType().toString(); + } default: return QVariant(); } @@ -180,6 +184,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio case SideColumn: case LoadersColumn: case McVersionsColumn: + case ReleaseTypeColumn: return columnNames().at(section); default: return QVariant(); @@ -203,6 +208,8 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio return tr("The mod loader."); case McVersionsColumn: return tr("The supported minecraft versions."); + case ReleaseTypeColumn: + return tr("The release type."); default: return QVariant(); } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index f9a87a7417..a243a3c927 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -71,6 +71,7 @@ class ModFolderModel : public ResourceFolderModel { SideColumn, LoadersColumn, McVersionsColumn, + ReleaseTypeColumn, NUM_COLUMNS }; enum ModStatusAction { Disable, Enable, Toggle }; diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index c469808372..101f652715 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -15,7 +15,7 @@ enum class ResourceType { LITEMOD, //!< The resource is a litemod }; -enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIDE, LOADERS, MC_VERSIONS }; +enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIDE, LOADERS, MC_VERSIONS, RELEASE_TYPE }; enum class EnableAction { ENABLE, DISABLE, TOGGLE }; diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 0692a627d8..1672cdf3d7 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -117,6 +117,7 @@ auto V1::createModFormat([[maybe_unused]] QDir& index_dir, ModPlatform::IndexedP mod.loaders = mod_version.loaders; mod.mcVersions = mod_version.mcVersion; mod.mcVersions.sort(); + mod.releaseType = mod_version.version_type; return mod; } @@ -209,6 +210,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) { "side", sideToString(mod.side).toStdString() }, { "loaders", loaders }, { "mcVersions", mcVersions }, + { "releaseType", mod.releaseType.toString().toStdString() }, { "download", toml::table{ { "mode", mod.mode.toStdString() }, @@ -293,6 +295,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod mod.name = stringEntry(table, "name"); mod.filename = stringEntry(table, "filename"); mod.side = stringToSide(stringEntry(table, "side")); + mod.releaseType = ModPlatform::IndexedVersionType(stringEntry(table, "releaseType")); if (auto loaders = table["loaders"]; loaders && loaders.is_array()) { for (auto&& loader : *loaders.as_array()) { if (loader.is_string()) { diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 5cf99e74a1..50cce9c66e 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -43,6 +43,7 @@ class V1 { Side side{ Side::UniversalSide }; ModPlatform::ModLoaderTypes loaders; QStringList mcVersions; + ModPlatform::IndexedVersionType releaseType; // [download] QString mode{}; diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index dde5d75025..d702173f13 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -148,7 +148,7 @@ void ModFilterWidget::onIncludeSnapshotsChanged() m_versions_proxy->setFilter(BaseVersionList::TypeRole, new RegexpFilter(filter, false)); } -void ModFilterWidget::onVersionFilterChanged() +void ModFilterWidget::onVersionFilterChanged(int) { auto versions = ui->versionsCb->checkedItems(); m_filter->versions.clear(); diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index 50e6b37626..b6edcb21e8 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -52,7 +52,7 @@ class ModFilterWidget : public QTabWidget { void prepareBasicFilter(); private slots: - void onVersionFilterChanged(); + void onVersionFilterChanged(int); void onVersionFilterTextChanged(QString version); void onReleaseFilterChanged(); void onLoadersFilterChanged(); From 35d62cc5f2d79e499791a20a3997bf91137aadc0 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 21 Oct 2023 19:05:31 +0300 Subject: [PATCH 0072/2054] Updated dependencies Signed-off-by: Trial97 --- .../mod/tasks/GetModDependenciesTask.cpp | 15 ++++++++++++--- launcher/ui/widgets/ModFilterWidget.cpp | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp index deec0db7c5..625ab23bb5 100644 --- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp +++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp @@ -45,6 +45,14 @@ static ModPlatform::ModLoaderTypes mcLoaders(BaseInstance* inst) return static_cast(inst)->getPackProfile()->getSupportedModLoaders().value(); } +static bool checkDependencies(std::shared_ptr sel, + Version mcVersion, + ModPlatform::ModLoaderTypes loaders) +{ + return (sel->pack->versions.isEmpty() || sel->version.mcVersion.contains(mcVersion.toString())) && + (!loaders || !sel->version.loaders || sel->version.loaders & loaders); +} + GetModDependenciesTask::GetModDependenciesTask(QObject* parent, BaseInstance* instance, ModFolderModel* folder, @@ -67,9 +75,10 @@ GetModDependenciesTask::GetModDependenciesTask(QObject* parent, void GetModDependenciesTask::prepare() { for (auto sel : m_selected) { - for (auto dep : getDependenciesForVersion(sel->version, sel->pack->provider)) { - addTask(prepareDependencyTask(dep, sel->pack->provider, 20)); - } + if (checkDependencies(sel, m_version, m_loaderType)) + for (auto dep : getDependenciesForVersion(sel->version, sel->pack->provider)) { + addTask(prepareDependencyTask(dep, sel->pack->provider, 20)); + } } } diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index d702173f13..0dff975109 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -53,7 +53,7 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extendedSuppo ui->versionsCb->setStyleSheet("combobox-popup: 0;"); ui->versionsSimpleCb->setStyleSheet("combobox-popup: 0;"); connect(ui->snapshotsCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onIncludeSnapshotsChanged); - connect(ui->versionsCb, &QComboBox::currentIndexChanged, this, &ModFilterWidget::onVersionFilterChanged); + connect(ui->versionsCb, QOverload::of(&QComboBox::currentIndexChanged), this, &ModFilterWidget::onVersionFilterChanged); connect(ui->versionsSimpleCb, &QComboBox::currentTextChanged, this, &ModFilterWidget::onVersionFilterTextChanged); connect(ui->neoForgeCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); From 41c9ca4f8ae0d18c53a5523b81e31d5d4e42072f Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 21 Oct 2023 22:15:07 +0300 Subject: [PATCH 0073/2054] Added categories filter Signed-off-by: Trial97 --- launcher/modplatform/ModIndex.h | 5 +++ launcher/modplatform/ResourceAPI.h | 1 + launcher/modplatform/flame/FlameAPI.cpp | 41 +++++++++++++++++++ launcher/modplatform/flame/FlameAPI.h | 7 ++++ launcher/modplatform/modrinth/ModrinthAPI.cpp | 38 +++++++++++++++++ launcher/modplatform/modrinth/ModrinthAPI.h | 15 +++++++ launcher/ui/pages/modplatform/ModModel.cpp | 5 ++- launcher/ui/pages/modplatform/ModPage.cpp | 1 + launcher/ui/pages/modplatform/ModPage.h | 2 + .../modplatform/flame/FlameResourcePages.cpp | 14 +++++++ .../modplatform/flame/FlameResourcePages.h | 3 ++ .../modrinth/ModrinthResourcePages.cpp | 11 +++++ .../modrinth/ModrinthResourcePages.h | 3 ++ launcher/ui/widgets/ModFilterWidget.cpp | 39 ++++++++++++++++-- launcher/ui/widgets/ModFilterWidget.h | 11 ++++- launcher/ui/widgets/ModFilterWidget.ui | 40 +++++++++++++++++- 16 files changed, 230 insertions(+), 6 deletions(-) diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 7c472ec9b4..a8ec6e7b31 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -191,6 +191,11 @@ constexpr bool hasSingleModLoaderSelected(ModLoaderTypes l) noexcept return x && !(x & (x - 1)); } +struct Category { + QString name; + QString id; +}; + } // namespace ModPlatform Q_DECLARE_METATYPE(ModPlatform::IndexedPack) diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index 732c666216..97a38ca698 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -74,6 +74,7 @@ class ResourceAPI { std::optional loaders; std::optional > versions; std::optional side; + std::optional categoryIds; }; struct SearchCallbacks { std::function on_succeed; diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index e99ce3a56c..cc3f31e329 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -3,10 +3,12 @@ // SPDX-License-Identifier: GPL-3.0-only #include "FlameAPI.h" +#include #include "FlameModIndex.h" #include "Application.h" #include "Json.h" +#include "modplatform/ModIndex.h" #include "net/ApiDownload.h" #include "net/ApiUpload.h" #include "net/NetJob.h" @@ -222,3 +224,42 @@ QList FlameAPI::getSortingMethods() const { return s_sorts; } + +Task::Ptr FlameAPI::getModCategories(std::shared_ptr response) +{ + auto netJob = makeShared(QString("Flame::GetCategories"), APPLICATION->network()); + netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl("https://api.curseforge.com/v1/categories?gameId=432&classId=6"), response)); + QObject::connect(netJob.get(), &Task::failed, [](QString msg) { qDebug() << "Flame failed to get categories:" << msg; }); + return netJob; +} + +QList FlameAPI::loadModCategories(std::shared_ptr response) +{ + QList categories; + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from categories at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return categories; + } + + try { + auto obj = Json::requireObject(doc); + auto arr = Json::requireArray(obj, "data"); + + for (auto val : arr) { + auto cat = Json::requireObject(val); + auto id = Json::requireInteger(cat, "id"); + auto name = Json::requireString(cat, "name"); + categories.push_back({ name, QString::number(id) }); + } + + } catch (Json::JsonException& e) { + qCritical() << "Failed to parse response from a version request."; + qCritical() << e.what(); + qDebug() << doc; + } + return categories; +}; \ No newline at end of file diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index e22d8f0d8f..dfe76f9d5b 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include "modplatform/ModIndex.h" @@ -22,6 +23,9 @@ class FlameAPI : public NetworkResourceAPI { Task::Ptr getFiles(const QStringList& fileIds, std::shared_ptr response) const; Task::Ptr getFile(const QString& addonId, const QString& fileId, std::shared_ptr response) const; + static Task::Ptr getModCategories(std::shared_ptr response); + static QList loadModCategories(std::shared_ptr response); + [[nodiscard]] auto getSortingMethods() const -> QList override; static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool @@ -96,6 +100,9 @@ class FlameAPI : public NetworkResourceAPI { get_arguments.append("sortOrder=desc"); if (args.loaders.has_value()) get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(args.loaders.value()))); + if (args.categoryIds.has_value() && !args.categoryIds->empty()) + get_arguments.append(QString("categoryIds=[%1]").arg(args.categoryIds->join(","))); + get_arguments.append(gameVersionStr); return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index f453f5cb9c..d3f006e6f4 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -122,3 +122,41 @@ QList ModrinthAPI::getSortingMethods() const { return s_sorts; } + +Task::Ptr ModrinthAPI::getModCategories(std::shared_ptr response) +{ + auto netJob = makeShared(QString("Modrinth::GetCategories"), APPLICATION->network()); + netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(BuildConfig.MODRINTH_PROD_URL + "/tag/category"), response)); + QObject::connect(netJob.get(), &Task::failed, [](QString msg) { qDebug() << "Modrinth failed to get categories:" << msg; }); + return netJob; +} + +QList ModrinthAPI::loadModCategories(std::shared_ptr response) +{ + QList categories; + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from categories at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return categories; + } + + try { + auto arr = Json::requireArray(doc); + + for (auto val : arr) { + auto cat = Json::requireObject(val); + auto name = Json::requireString(cat, "name"); + if (Json::ensureString(cat, "project_type", "") == "mod") + categories.push_back({ name, name }); + } + + } catch (Json::JsonException& e) { + qCritical() << "Failed to parse response from a version request."; + qCritical() << e.what(); + qDebug() << doc; + } + return categories; +}; \ No newline at end of file diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 8e0f9fe22a..d1f8f712af 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -30,6 +30,9 @@ class ModrinthAPI : public NetworkResourceAPI { Task::Ptr getProjects(QStringList addonIds, std::shared_ptr response) const override; + static Task::Ptr getModCategories(std::shared_ptr response); + static QList loadModCategories(std::shared_ptr response); + public: [[nodiscard]] auto getSortingMethods() const -> QList override; @@ -56,6 +59,15 @@ class ModrinthAPI : public NetworkResourceAPI { return l.join(','); } + static auto getCategoriesFilters(QStringList categories) -> const QString + { + QStringList l; + for (auto cat : categories) { + l << QString("\"categories:%1\"").arg(cat); + } + return l.join(','); + } + static auto getSideFilters(QString side) -> const QString { if (side.isEmpty() || side == "both") { @@ -99,6 +111,9 @@ class ModrinthAPI : public NetworkResourceAPI { if (!side.isEmpty()) facets_list.append(QString("[%1]").arg(side)); } + if (args.categoryIds.has_value() && !args.categoryIds->empty()) + facets_list.append(QString("[%1]").arg(getCategoriesFilters(args.categoryIds.value()))); + facets_list.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type))); return QString("[%1]").arg(facets_list.join(',')); diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 64ae7de367..61a01ddd2d 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -25,6 +25,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments() Q_ASSERT(m_filter); std::optional> versions{}; + std::optional categories{}; auto loaders = profile->getSupportedModLoaders(); // Version filter @@ -32,11 +33,13 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments() versions = m_filter->versions; if (m_filter->loaders) loaders = m_filter->loaders; + if (!m_filter->categoryIds.empty()) + categories = m_filter->categoryIds; auto side = m_filter->side; auto sort = getCurrentSortingMethodByIndex(); - return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, loaders, versions, side }; + return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, loaders, versions, side, categories }; } ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index e6106a0de8..e4da1a9623 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -78,6 +78,7 @@ void ModPage::setFilterWidget(unique_qobject_ptr& widget) [&] { m_ui->searchButton->setStyleSheet("text-decoration: underline"); }); connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this, [&] { m_ui->searchButton->setStyleSheet("text-decoration: none"); }); + prepareProviderCategories(); } /******** Callbacks to events in the UI (set up in the derived classes) ********/ diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index c878bc0045..d6a7d64e4f 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -61,6 +61,8 @@ class ModPage : public ResourcePage { protected: ModPage(ModDownloadDialog* dialog, BaseInstance& instance); + virtual void prepareProviderCategories(){}; + protected slots: virtual void filterMods(); void triggerSearch() override; diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 66a51decd6..7a60468248 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -37,6 +37,10 @@ */ #include "FlameResourcePages.h" +#include +#include +#include "modplatform/ModIndex.h" +#include "modplatform/flame/FlameAPI.h" #include "ui_ResourcePage.h" #include "FlameResourceModels.h" @@ -204,4 +208,14 @@ unique_qobject_ptr FlameModPage::createFilterWidget() return ModFilterWidget::create(&static_cast(m_base_instance), false, this); } +void FlameModPage::prepareProviderCategories() +{ + auto response = std::make_shared(); + auto task = FlameAPI::getModCategories(response); + QObject::connect(task.get(), &Task::succeeded, [this, response]() { + auto categories = FlameAPI::loadModCategories(response); + m_filter_widget->setCategories(categories); + }); + task->start(); +}; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index 88ef404fbc..0288023cae 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -96,6 +96,9 @@ class FlameModPage : public ModPage { void openUrl(const QUrl& url) override; unique_qobject_ptr createFilterWidget() override; + + protected: + virtual void prepareProviderCategories() override; }; class FlameResourcePackPage : public ResourcePackResourcePage { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index ab015ff0e2..5b5099863a 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -141,4 +141,15 @@ unique_qobject_ptr ModrinthModPage::createFilterWidget() { return ModFilterWidget::create(&static_cast(m_base_instance), true, this); } + +void ModrinthModPage::prepareProviderCategories() +{ + auto response = std::make_shared(); + auto task = ModrinthAPI::getModCategories(response); + QObject::connect(task.get(), &Task::succeeded, [this, response]() { + auto categories = ModrinthAPI::loadModCategories(response); + m_filter_widget->setCategories(categories); + }); + task->start(); +}; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index 0a715d747a..a8dcd1de50 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -94,6 +94,9 @@ class ModrinthModPage : public ModPage { [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } unique_qobject_ptr createFilterWidget() override; + + protected: + virtual void prepareProviderCategories() override; }; class ModrinthResourcePackPage : public ResourcePackResourcePage { diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 0dff975109..a3b0a0cc04 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -1,6 +1,8 @@ #include "ModFilterWidget.h" -#include -#include +#include +#include +#include +#include #include "BaseVersionList.h" #include "meta/Index.h" #include "modplatform/ModIndex.h" @@ -76,6 +78,8 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extendedSuppo connect(ui->alphaCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onReleaseFilterChanged); connect(ui->unknownCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onReleaseFilterChanged); + connect(ui->categoriesList, &QListWidget::itemClicked, this, &ModFilterWidget::onCategoryClicked); + setHidden(true); loadVersionList(); prepareBasicFilter(); @@ -238,4 +242,33 @@ void ModFilterWidget::onVersionFilterTextChanged(QString version) emit filterChanged(); } -#include "ModFilterWidget.moc" +void ModFilterWidget::setCategories(QList categories) +{ + ui->categoriesList->clear(); + m_categories = categories; + for (auto cat : categories) { + auto item = new QListWidgetItem(cat.name, ui->categoriesList); + item->setFlags(item->flags() & (~Qt::ItemIsUserCheckable)); + item->setCheckState(Qt::Unchecked); + ui->categoriesList->addItem(item); + } +} + +void ModFilterWidget::onCategoryClicked(QListWidgetItem* item) +{ + if (item) + item->setCheckState(item->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked); + m_filter->categoryIds.clear(); + for (auto i = 0; i < ui->categoriesList->count(); i++) { + auto item = ui->categoriesList->item(i); + if (item->checkState() == Qt::Checked) { + auto c = std::find_if(m_categories.cbegin(), m_categories.cend(), [item](auto v) { return v.name == item->text(); }); + if (c != m_categories.cend()) + m_filter->categoryIds << c->id; + } + } + m_filter_changed = true; + emit filterChanged(); +}; + +#include "ModFilterWidget.moc" \ No newline at end of file diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index b6edcb21e8..402e11ae40 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include "Version.h" @@ -26,11 +28,12 @@ class ModFilterWidget : public QTabWidget { ModPlatform::ModLoaderTypes loaders; QString side; bool hideInstalled; + QStringList categoryIds; bool operator==(const Filter& other) const { return hideInstalled == other.hideInstalled && side == other.side && loaders == other.loaders && versions == other.versions && - releases == other.releases; + releases == other.releases && categoryIds == other.categoryIds; } bool operator!=(const Filter& other) const { return !(*this == other); } }; @@ -45,6 +48,9 @@ class ModFilterWidget : public QTabWidget { void filterChanged(); void filterUnchanged(); + public slots: + void setCategories(QList); + private: ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); @@ -59,6 +65,7 @@ class ModFilterWidget : public QTabWidget { void onSideFilterChanged(); void onHideInstalledFilterChanged(); void onIncludeSnapshotsChanged(); + void onCategoryClicked(QListWidgetItem* item); private: Ui::ModFilterWidget* ui; @@ -69,4 +76,6 @@ class ModFilterWidget : public QTabWidget { Meta::VersionList::Ptr m_version_list; VersionProxyModel* m_versions_proxy = nullptr; + + QList m_categories; }; diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui index d962ea44ab..56cd33fa6f 100644 --- a/launcher/ui/widgets/ModFilterWidget.ui +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -7,7 +7,7 @@ 0 0 460 - 127 + 132 @@ -145,6 +145,44 @@ + + + Categories + + + + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerItem + + + QListView::LeftToRight + + + true + + + QListView::ListMode + + + 0 + + + + + Others From d151e97e3a24667063ad4fe9cd6f5aca6cdf7b21 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 21 Oct 2023 22:40:37 +0300 Subject: [PATCH 0074/2054] Fixed some headers Signed-off-by: Trial97 --- launcher/minecraft/mod/MetadataHandler.h | 1 + launcher/minecraft/mod/Mod.cpp | 1 + launcher/minecraft/mod/Mod.h | 1 + launcher/minecraft/mod/ModFolderModel.cpp | 1 + launcher/minecraft/mod/ModFolderModel.h | 1 + launcher/minecraft/mod/Resource.h | 35 +++++++++++++++++++ launcher/modplatform/ModIndex.cpp | 1 + launcher/modplatform/ResourceAPI.h | 1 + .../modrinth/ModrinthPackIndex.cpp | 1 + launcher/modplatform/packwiz/Packwiz.cpp | 1 + launcher/modplatform/packwiz/Packwiz.h | 1 + launcher/ui/pages/modplatform/ModPage.cpp | 1 + .../ui/pages/modplatform/ResourcePage.cpp | 1 + .../modplatform/flame/FlameResourcePages.cpp | 1 + .../modplatform/flame/FlameResourcePages.h | 1 + .../modrinth/ModrinthResourcePages.cpp | 1 + .../modrinth/ModrinthResourcePages.h | 1 + launcher/ui/widgets/CheckComboBox.cpp | 6 ++-- launcher/ui/widgets/CheckComboBox.h | 2 +- launcher/ui/widgets/ModFilterWidget.cpp | 35 +++++++++++++++++++ launcher/ui/widgets/ModFilterWidget.h | 35 +++++++++++++++++++ 21 files changed, 124 insertions(+), 5 deletions(-) diff --git a/launcher/minecraft/mod/MetadataHandler.h b/launcher/minecraft/mod/MetadataHandler.h index ccdd7d559d..fb3a101336 100644 --- a/launcher/minecraft/mod/MetadataHandler.h +++ b/launcher/minecraft/mod/MetadataHandler.h @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index f301cc9495..e891310340 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -3,6 +3,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index bf503bc80b..e9baeed585 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index fb3688d5d3..8e31fa34bf 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -3,6 +3,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index a243a3c927..31c739e5b8 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -3,6 +3,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 101f652715..0710a400b4 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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 diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index d2113320a4..20bd3f8272 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index 97a38ca698..a306dcc525 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -4,6 +4,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 5de6452201..a2bda2ec53 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 1672cdf3d7..c8899345ee 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 50cce9c66e..95362bbfef 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index e4da1a9623..8fd42b427d 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -5,6 +5,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2022 TheKodeToad + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 158b13c965..35c4afc0d2 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -5,6 +5,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2023 TheKodeToad + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 7a60468248..d82c76c3a8 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -5,6 +5,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2022 TheKodeToad + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index 0288023cae..6eef3e435a 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -5,6 +5,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2022 TheKodeToad + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 5b5099863a..26fe46a545 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -4,6 +4,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index a8dcd1de50..eaf6129a5a 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -4,6 +4,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/ui/widgets/CheckComboBox.cpp b/launcher/ui/widgets/CheckComboBox.cpp index af45943357..8117baa584 100644 --- a/launcher/ui/widgets/CheckComboBox.cpp +++ b/launcher/ui/widgets/CheckComboBox.cpp @@ -17,17 +17,15 @@ */ #include "CheckComboBox.h" -#include -#include -#include #include +#include #include #include #include #include +#include #include -#include "BaseVersionList.h" class CheckComboModel : public QIdentityProxyModel { Q_OBJECT diff --git a/launcher/ui/widgets/CheckComboBox.h b/launcher/ui/widgets/CheckComboBox.h index f1d9771cc8..4d6daf5a48 100644 --- a/launcher/ui/widgets/CheckComboBox.h +++ b/launcher/ui/widgets/CheckComboBox.h @@ -18,8 +18,8 @@ #pragma once -#include #include +#include class CheckComboBox : public QComboBox { Q_OBJECT diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index a3b0a0cc04..b7da6878ad 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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 "ModFilterWidget.h" #include #include diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index 402e11ae40..dd98d9afaf 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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 From 10ae808da90c819df03c9ea78a3248ddb66179d1 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 22 Oct 2023 00:06:08 +0300 Subject: [PATCH 0075/2054] fixed codeql Signed-off-by: Trial97 --- launcher/ui/widgets/ModFilterWidget.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index b7da6878ad..341823244a 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -289,10 +289,10 @@ void ModFilterWidget::setCategories(QList categories) } } -void ModFilterWidget::onCategoryClicked(QListWidgetItem* item) +void ModFilterWidget::onCategoryClicked(QListWidgetItem* itm) { - if (item) - item->setCheckState(item->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked); + if (itm) + itm->setCheckState(itm->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked); m_filter->categoryIds.clear(); for (auto i = 0; i < ui->categoriesList->count(); i++) { auto item = ui->categoriesList->item(i); From 6a19f2dae82ecda7790e8fcf6cd8cf16f3bcdaf0 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 24 Oct 2023 11:19:19 +0300 Subject: [PATCH 0076/2054] fixed icon import Signed-off-by: Trial97 --- launcher/InstanceImportTask.cpp | 4 +++- launcher/icons/IconUtils.cpp | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 713787902f..21ad834b41 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -334,13 +334,15 @@ void InstanceImportTask::processMultiMC() m_instIcon = instance.iconKey(); auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon); + if (importIconPath.isNull() || !QFile::exists(importIconPath)) + importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), "icon.png"); if (!importIconPath.isNull() && QFile::exists(importIconPath)) { // import icon auto iconList = APPLICATION->icons(); if (iconList->iconFileExists(m_instIcon)) { iconList->deleteIcon(m_instIcon); } - iconList->installIcons({ importIconPath }); + iconList->installIcon(importIconPath, m_instIcon); } } emitSucceeded(); diff --git a/launcher/icons/IconUtils.cpp b/launcher/icons/IconUtils.cpp index 99c38f47af..6825dd6da1 100644 --- a/launcher/icons/IconUtils.cpp +++ b/launcher/icons/IconUtils.cpp @@ -52,8 +52,7 @@ QString findBestIconIn(const QString& folder, const QString& iconKey) while (it.hasNext()) { it.next(); auto fileInfo = it.fileInfo(); - - if (fileInfo.completeBaseName() == iconKey && isIconSuffix(fileInfo.suffix())) + if ((fileInfo.completeBaseName() == iconKey || fileInfo.fileName() == iconKey) && isIconSuffix(fileInfo.suffix())) return fileInfo.absoluteFilePath(); } return {}; From 263dc5af67b21719a690c3577c3e9d028fc1106a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 25 Oct 2023 19:56:26 +0300 Subject: [PATCH 0077/2054] Fixed remane and delete of selected skin Signed-off-by: Trial97 --- launcher/minecraft/skins/SkinList.cpp | 2 +- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index b3a5934543..1b046a7819 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -359,7 +359,7 @@ bool SkinList::setData(const QModelIndex& idx, const QVariant& value, int role) int row = idx.row(); if (row < 0 || row >= m_skin_list.size()) return false; - auto skin = m_skin_list[row]; + auto& skin = m_skin_list[row]; auto newName = value.toString(); if (skin.name() != newName) { skin.rename(newName); diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 24197baebc..5419d3eed7 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -229,8 +229,10 @@ void SkinManageDialog::on_steveBtn_toggled(bool checked) void SkinManageDialog::accept() { auto skin = m_list.skin(m_selected_skin); - if (!skin) + if (!skin) { reject(); + return; + } auto path = skin->getPath(); ProgressDialog prog(this); @@ -315,7 +317,7 @@ void SkinManageDialog::on_action_Delete_Skin_triggered(bool checked) return; if (m_list.getSkinIndex(m_selected_skin) == m_list.getSelectedAccountSkin()) { - CustomMessageBox::selectable(this, tr("Delete error"), tr("Can not delete skin that is in use."), QMessageBox::Warning); + CustomMessageBox::selectable(this, tr("Delete error"), tr("Can not delete skin that is in use."), QMessageBox::Warning)->exec(); return; } From 44cdf3f6979969c2de7953b58fd08836049fe5d5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 26 Oct 2023 13:33:00 +0300 Subject: [PATCH 0078/2054] Fixed skin variant Signed-off-by: Trial97 --- launcher/minecraft/auth/Parsers.cpp | 2 +- launcher/minecraft/skins/SkinList.cpp | 4 ++-- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp index f6179a93eb..a2b002c787 100644 --- a/launcher/minecraft/auth/Parsers.cpp +++ b/launcher/minecraft/auth/Parsers.cpp @@ -347,7 +347,7 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output) Skin skinOut; // fill in default skin info ourselves, as this endpoint doesn't provide it bool steve = isDefaultModelSteve(output.id); - skinOut.variant = steve ? "classic" : "slim"; + skinOut.variant = steve ? "CLASSIC" : "SLIM"; skinOut.url = steve ? SKIN_URL_STEVE : SKIN_URL_ALEX; // sadly we can't figure this out, but I don't think it really matters... skinOut.id = "00000000-0000-0000-0000-000000000000"; diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index 1b046a7819..0505e4ced1 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -106,7 +106,7 @@ bool SkinList::update() auto path = m_dir.absoluteFilePath(name); if (skinTexture.loadFromData(skin.data, "PNG") && skinTexture.save(path)) { SkinModel s(path); - s.setModel(skin.variant == "slim" ? SkinModel::SLIM : SkinModel::CLASSIC); + s.setModel(skin.variant == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); s.setCapeId(m_acct->accountData()->minecraftProfile.currentCape); s.setURL(skin.url); newSkins << s; @@ -114,7 +114,7 @@ bool SkinList::update() } } else { nskin->setCapeId(m_acct->accountData()->minecraftProfile.currentCape); - nskin->setModel(skin.variant == "slim" ? SkinModel::SLIM : SkinModel::CLASSIC); + nskin->setModel(skin.variant == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); } } diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 5419d3eed7..5c69be0ba4 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -460,7 +460,7 @@ void SkinManageDialog::on_userBtn_clicked() dlg.execWithTask(job.get()); SkinModel s(path); - s.setModel(mcProfile.skin.variant == "slim" ? SkinModel::SLIM : SkinModel::CLASSIC); + s.setModel(mcProfile.skin.variant == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); s.setURL(mcProfile.skin.url); if (m_capes.contains(mcProfile.currentCape)) { s.setCapeId(mcProfile.currentCape); From 8b8ea2d27057dd9cebe20586f2ab5fc8426d1c99 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 26 Oct 2023 13:37:25 +0300 Subject: [PATCH 0079/2054] ensured that the variant is allways uppercase Signed-off-by: Trial97 --- launcher/minecraft/skins/SkinList.cpp | 4 ++-- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index 0505e4ced1..fd883ad524 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -106,7 +106,7 @@ bool SkinList::update() auto path = m_dir.absoluteFilePath(name); if (skinTexture.loadFromData(skin.data, "PNG") && skinTexture.save(path)) { SkinModel s(path); - s.setModel(skin.variant == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); + s.setModel(skin.variant.toUpper() == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); s.setCapeId(m_acct->accountData()->minecraftProfile.currentCape); s.setURL(skin.url); newSkins << s; @@ -114,7 +114,7 @@ bool SkinList::update() } } else { nskin->setCapeId(m_acct->accountData()->minecraftProfile.currentCape); - nskin->setModel(skin.variant == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); + nskin->setModel(skin.variant.toUpper() == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); } } diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 5c69be0ba4..9320101c26 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -460,7 +460,7 @@ void SkinManageDialog::on_userBtn_clicked() dlg.execWithTask(job.get()); SkinModel s(path); - s.setModel(mcProfile.skin.variant == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); + s.setModel(mcProfile.skin.variant.toUpper() == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); s.setURL(mcProfile.skin.url); if (m_capes.contains(mcProfile.currentCape)) { s.setCapeId(mcProfile.currentCape); From 321bbf1fa8bbdf37a5b81aa8c69512e1b04028eb Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 26 Oct 2023 19:06:38 +0300 Subject: [PATCH 0080/2054] fixed asan stuff Signed-off-by: Trial97 --- launcher/minecraft/skins/SkinUpload.cpp | 2 +- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/skins/SkinUpload.cpp b/launcher/minecraft/skins/SkinUpload.cpp index 4a88faedf1..dc1bc0bf91 100644 --- a/launcher/minecraft/skins/SkinUpload.cpp +++ b/launcher/minecraft/skins/SkinUpload.cpp @@ -49,7 +49,7 @@ SkinUpload::SkinUpload(QString token, QString path, QString variant) : NetReques QNetworkReply* SkinUpload::getReply(QNetworkRequest& request) { - QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this); QHttpPart skin; skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png")); diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 9320101c26..8028a719c7 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -74,7 +74,7 @@ SkinManageDialog::SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct) contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); contentsWidget->installEventFilter(this); - contentsWidget->setItemDelegate(new ListViewDelegate()); + contentsWidget->setItemDelegate(new ListViewDelegate(this)); contentsWidget->setAcceptDrops(true); contentsWidget->setDropIndicatorShown(true); From 3d84635b24e52c35b70e044bed67060adb1dcfd6 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 26 Oct 2023 20:09:12 +0300 Subject: [PATCH 0081/2054] Remove file on mod duplication Signed-off-by: Trial97 --- launcher/InstanceList.cpp | 12 ++++++------ launcher/InstanceList.h | 2 +- launcher/minecraft/mod/Resource.cpp | 10 +++++----- launcher/minecraft/mod/ResourceFolderModel.cpp | 3 --- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 756ff93ddc..e245fc2c69 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -371,13 +371,13 @@ void InstanceList::undoTrashInstance() auto top = m_trashHistory.pop(); - while (QDir(top.polyPath).exists()) { + while (QDir(top.rootPath).exists()) { top.id += "1"; - top.polyPath += "1"; + top.rootPath += "1"; } - qDebug() << "Moving" << top.trashPath << "back to" << top.polyPath; - QFile(top.trashPath).rename(top.polyPath); + qDebug() << "Moving" << top.trashPath << "back to" << top.rootPath; + QFile(top.trashPath).rename(top.rootPath); m_instanceGroupIndex[top.id] = top.groupName; increaseGroupCount(top.groupName); @@ -634,8 +634,8 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id) QString inst_type = instanceSettings->get("InstanceType").toString(); - // NOTE: Some PolyMC versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a OneSix - // instance + // NOTE: Some PrismLauncher versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a + // OneSix instance if (inst_type == "OneSix" || inst_type.isEmpty()) { inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot)); } else { diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index 6b0bcd8109..1a70b57b96 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -58,7 +58,7 @@ enum class GroupsState { NotLoaded, Steady, Dirty }; struct TrashHistoryItem { QString id; - QString polyPath; + QString rootPath; QString trashPath; QString groupName; }; diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index da806f0f46..05940fcb45 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -130,15 +130,15 @@ bool Resource::enable(EnableAction action) if (!path.endsWith(".disabled")) return false; path.chop(9); - - if (!file.rename(path)) - return false; } else { path += ".disabled"; - - if (!file.rename(path)) + } + if (QFileInfo::exists(path)) { // the path exists so just remove the file at path + if (!QFile::remove(path)) return false; } + if (!file.rename(path)) + return false; setFile(QFileInfo(path)); diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 0503b660bb..8f2c87747b 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -213,9 +213,6 @@ bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, Ena } auto new_id = resource->internal_id(); - if (m_resources_index.contains(new_id)) { - // FIXME: https://github.com/PolyMC/PolyMC/issues/550 - } m_resources_index.remove(old_id); m_resources_index[new_id] = row; From 60a7628dbbf21c27cdc8021b01278b173f56dedb Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 26 Oct 2023 20:43:26 +0300 Subject: [PATCH 0082/2054] delete duplicate mods Signed-off-by: Trial97 --- launcher/minecraft/mod/tasks/BasicFolderLoadTask.h | 10 ++++++++-- launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h index 23a2b649ab..6e8b8ed43c 100644 --- a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h @@ -49,10 +49,16 @@ class BasicFolderLoadTask : public Task { connect(this, &Task::finished, this->thread(), &QThread::quit); m_dir.refresh(); + QStringList names; for (auto entry : m_dir.entryInfoList()) { auto resource = m_create_func(entry); - resource->moveToThread(m_thread_to_spawn_into); - m_result->resources.insert(resource->internal_id(), resource); + if (names.contains(resource->name())) { + resource->destroy(); + } else { + names << resource->name(); + resource->moveToThread(m_thread_to_spawn_into); + m_result->resources.insert(resource->internal_id(), resource); + } } if (m_aborted) diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 2094df4fcf..c11759dd2f 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -62,9 +62,15 @@ void ModFolderLoadTask::executeTask() // Read JAR files that don't have metadata m_mods_dir.refresh(); + QStringList names; for (auto entry : m_mods_dir.entryInfoList()) { Mod* mod(new Mod(entry)); + if (names.contains(mod->name())) { + mod->destroy(m_index_dir, true); + continue; + } + names << mod->name(); if (mod->enabled()) { if (m_result->mods.contains(mod->internal_id())) { m_result->mods[mod->internal_id()]->setStatus(ModStatus::Installed); From 33cf7066b403e1438fd3c9dd36e33bf5e84c5f29 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 27 Oct 2023 21:51:35 +0300 Subject: [PATCH 0083/2054] formated the code Signed-off-by: Trial97 --- launcher/ui/widgets/JavaSettingsWidget.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 3c0352b13b..63d7aa57ca 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -40,7 +40,6 @@ JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent) connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaSettingsWidget::javaPathEdited); connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaSettingsWidget::on_javaStatusBtn_clicked); connect(m_javaDownloadBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaDownloadBtn_clicked); - } void JavaSettingsWidget::setupUi() @@ -274,7 +273,6 @@ void JavaSettingsWidget::on_javaBrowseBtn_clicked() void JavaSettingsWidget::on_javaDownloadBtn_clicked() { JavaDownloader::showPrompts(this); - } void JavaSettingsWidget::on_javaStatusBtn_clicked() { From 3cbc63bb9f5265a55294e546f3812b52d4ef7346 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 2 Nov 2023 12:18:24 +0200 Subject: [PATCH 0084/2054] added size column Signed-off-by: Trial97 --- launcher/minecraft/mod/Mod.cpp | 4 ++- launcher/minecraft/mod/ModFolderModel.cpp | 24 +++++++++++----- launcher/minecraft/mod/ModFolderModel.h | 2 +- launcher/minecraft/mod/Resource.cpp | 7 +++++ launcher/minecraft/mod/Resource.h | 2 +- .../minecraft/mod/ResourceFolderModel.cpp | 28 +++++++++++-------- launcher/minecraft/mod/ResourceFolderModel.h | 12 ++++---- .../minecraft/mod/ResourcePackFolderModel.cpp | 19 +++++++++---- .../minecraft/mod/ResourcePackFolderModel.h | 2 +- .../minecraft/mod/TexturePackFolderModel.cpp | 16 +++++++---- .../minecraft/mod/TexturePackFolderModel.h | 2 +- 11 files changed, 78 insertions(+), 40 deletions(-) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 310946379d..374e60d567 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -45,6 +45,7 @@ #include "MetadataHandler.h" #include "Version.h" #include "minecraft/mod/ModDetails.h" +#include "minecraft/mod/Resource.h" #include "minecraft/mod/tasks/LocalModParseTask.h" static ModPlatform::ProviderCapabilities ProviderCaps; @@ -87,7 +88,8 @@ std::pair Mod::compare(const Resource& other, SortType type) const default: case SortType::ENABLED: case SortType::NAME: - case SortType::DATE: { + case SortType::DATE: + case SortType::SIZE: { auto res = Resource::compare(other, type); if (res.first != 0) return res; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index a5f1489dd7..5c6cdb00e5 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -52,6 +52,8 @@ #include "Application.h" #include "Json.h" +#include "StringUtils.h" +#include "minecraft/mod/Resource.h" #include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModUpdateTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h" @@ -62,12 +64,15 @@ ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir) : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER }; + m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size" }); + m_column_names_translated = + QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), tr("Size") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, + SortType::DATE, SortType::PROVIDER, SortType::SIZE }; m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, - QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; - m_columnsHideable = { false, true, false, true, true, true }; + QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, + QHeaderView::ResizeToContents }; + m_columnsHideable = { false, true, false, true, true, true, true }; } QVariant ModFolderModel::data(const QModelIndex& index, int role) const @@ -105,12 +110,14 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const return provider.value(); } + case SizeColumn: + return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); default: return QVariant(); } case Qt::ToolTipRole: - if (column == NAME_COLUMN) { + if (column == NameColumn) { if (at(row)->isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." @@ -124,7 +131,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const } return m_resources[row]->internal_id(); case Qt::DecorationRole: { - if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) + if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) return APPLICATION->getThemedIcon("status-yellow"); if (column == ImageColumn) { return at(row)->icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); @@ -154,6 +161,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio case DateColumn: case ProviderColumn: case ImageColumn: + case SizeColumn: return columnNames().at(section); default: return QVariant(); @@ -171,6 +179,8 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio return tr("The date and time this mod was last changed (or added)."); case ProviderColumn: return tr("Where the mod was downloaded from."); + case SizeColumn: + return tr("The size of the resource."); default: return QVariant(); } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 61d840f9bd..e830556aba 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -61,7 +61,7 @@ class QFileSystemWatcher; class ModFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, VersionColumn, DateColumn, ProviderColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, VersionColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS }; enum ModStatusAction { Disable, Enable, Toggle }; ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true); diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index da806f0f46..79e52a8818 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -89,6 +89,13 @@ std::pair Resource::compare(const Resource& other, SortType type) con if (dateTimeChanged() < other.dateTimeChanged()) return { -1, type == SortType::DATE }; break; + case SortType::SIZE: { + if (fileinfo().size() > other.fileinfo().size()) + return { 1, type == SortType::SIZE }; + if (fileinfo().size() < other.fileinfo().size()) + return { -1, type == SortType::SIZE }; + break; + } } return { 0, false }; diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index c1ed49461e..d94e4b3684 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -15,7 +15,7 @@ enum class ResourceType { LITEMOD, //!< The resource is a litemod }; -enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER }; +enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE }; enum class EnableAction { ENABLE, DISABLE, TOGGLE }; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 0503b660bb..16ff012271 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -15,6 +15,7 @@ #include "FileSystem.h" #include "QVariantUtils.h" +#include "StringUtils.h" #include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "settings/Setting.h" @@ -410,15 +411,17 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const switch (role) { case Qt::DisplayRole: switch (column) { - case NAME_COLUMN: + case NameColumn: return m_resources[row]->name(); - case DATE_COLUMN: + case DateColumn: return m_resources[row]->dateTimeChanged(); + case SizeColumn: + return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); default: return {}; } case Qt::ToolTipRole: - if (column == NAME_COLUMN) { + if (column == NameColumn) { if (at(row).isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." @@ -434,14 +437,14 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const return m_resources[row]->internal_id(); case Qt::DecorationRole: { - if (column == NAME_COLUMN && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) + if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) return APPLICATION->getThemedIcon("status-yellow"); return {}; } case Qt::CheckStateRole: switch (column) { - case ACTIVE_COLUMN: + case ActiveColumn: return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked; default: return {}; @@ -480,24 +483,27 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien switch (role) { case Qt::DisplayRole: switch (section) { - case ACTIVE_COLUMN: - case NAME_COLUMN: - case DATE_COLUMN: + case ActiveColumn: + case NameColumn: + case DateColumn: + case SizeColumn: return columnNames().at(section); default: return {}; } case Qt::ToolTipRole: { switch (section) { - case ACTIVE_COLUMN: + case ActiveColumn: //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("Is the resource enabled?"); - case NAME_COLUMN: + case NameColumn: //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("The name of the resource."); - case DATE_COLUMN: + case DateColumn: //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("The date and time this resource was last changed (or added)."); + case SizeColumn: + return tr("The size of the resource."); default: return {}; } diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 60b8879c01..c282b8b80e 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -96,7 +96,7 @@ class ResourceFolderModel : public QAbstractListModel { /* Qt behavior */ /* Basic columns */ - enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS }; QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; } [[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast(size()); } @@ -198,12 +198,12 @@ class ResourceFolderModel : public QAbstractListModel { protected: // Represents the relationship between a column's index (represented by the list index), and it's sorting key. // As such, the order in with they appear is very important! - QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE }; - QStringList m_column_names = { "Enable", "Name", "Last Modified" }; - QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified") }; + QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::SIZE }; + QStringList m_column_names = { "Enable", "Name", "Last Modified", "Size" }; + QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Size") }; QList m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch, - QHeaderView::ResizeToContents }; - QList m_columnsHideable = { false, false, true }; + QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; + QList m_columnsHideable = { false, false, true, true }; QDir m_dir; BaseInstance* m_instance; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index f27431576f..d94751629a 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -42,19 +42,21 @@ #include #include "Application.h" +#include "StringUtils.h" #include "Version.h" +#include "minecraft/mod/Resource.h" #include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/LocalResourcePackParseTask.h" ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; - m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, - QHeaderView::ResizeToContents }; - m_columnsHideable = { false, true, false, true, true }; + m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Size" }); + m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Size") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE, SortType::SIZE }; + m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, + QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; + m_columnsHideable = { false, true, false, true, true, true }; } QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const @@ -85,6 +87,8 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const } case DateColumn: return m_resources[row]->dateTimeChanged(); + case SizeColumn: + return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); default: return {}; @@ -139,6 +143,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O case PackFormatColumn: case DateColumn: case ImageColumn: + case SizeColumn: return columnNames().at(section); default: return {}; @@ -155,6 +160,8 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O return tr("The resource pack format ID, as well as the Minecraft versions it was designed for."); case DateColumn: return tr("The date and time this resource pack was last changed (or added)."); + case SizeColumn: + return tr("The size of the resource."); default: return {}; } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index 29c2c59954..755b9c4c63 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -7,7 +7,7 @@ class ResourcePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, SizeColumn, NUM_COLUMNS }; explicit ResourcePackFolderModel(const QString& dir, BaseInstance* instance); diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 5c5f2b7c1c..ef241351f4 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -37,6 +37,7 @@ #include "Application.h" +#include "StringUtils.h" #include "TexturePackFolderModel.h" #include "minecraft/mod/tasks/BasicFolderLoadTask.h" @@ -44,12 +45,12 @@ TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE }; - m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, + m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Size" }); + m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Size") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::SIZE }; + m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; - m_columnsHideable = { false, true, false, true }; + m_columnsHideable = { false, true, false, true, true }; } Task* TexturePackFolderModel::createUpdateTask() @@ -77,6 +78,8 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const return m_resources[row]->name(); case DateColumn: return m_resources[row]->dateTimeChanged(); + case SizeColumn: + return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); default: return {}; } @@ -123,6 +126,7 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or case NameColumn: case DateColumn: case ImageColumn: + case SizeColumn: return columnNames().at(section); default: return {}; @@ -138,6 +142,8 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or case DateColumn: //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("The date and time this resource was last changed (or added)."); + case SizeColumn: + return tr("The size of the resource."); default: return {}; } diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index b975d86417..de90f879f0 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -44,7 +44,7 @@ class TexturePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS }; explicit TexturePackFolderModel(const QString& dir, std::shared_ptr instance); From cbb453a0edf5bde883627247b8284f02d18c4493 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 3 Nov 2023 22:55:10 +0200 Subject: [PATCH 0085/2054] minimize the permisions for extracted files Signed-off-by: Trial97 --- launcher/MMCZip.cpp | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 6172fe911a..c3acd8eb66 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -42,6 +42,7 @@ #include #include +#include #include #if defined(LAUNCHER_APPLICATION) @@ -327,9 +328,20 @@ std::optional extractSubDir(QuaZip* zip, const QString& subdir, con } extracted.append(target_file_path); - QFile::setPermissions(target_file_path, - QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser); - + auto fileInfo = QFileInfo(target_file_path); + if (fileInfo.isFile()) { + auto permissions = fileInfo.permissions(); + auto maxPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser | + QFileDevice::Permission::ReadGroup | QFileDevice::Permission::ReadOther; + auto minPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; + + auto newPermisions = (permissions & maxPermisions) | minPermisions; + if (newPermisions != permissions) { + if (!QFile::setPermissions(target_file_path, permissions)) { + qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path)); + } + } + } qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; } while (zip->goToNextFile()); @@ -582,8 +594,20 @@ auto ExtractZipTask::extractZip() -> ZipResult } extracted.append(target_file_path); - QFile::setPermissions(target_file_path, - QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser); + auto fileInfo = QFileInfo(target_file_path); + if (fileInfo.isFile()) { + auto permissions = fileInfo.permissions(); + auto maxPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser | + QFileDevice::Permission::ReadGroup | QFileDevice::Permission::ReadOther; + auto minPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; + + auto newPermisions = (permissions & maxPermisions) | minPermisions; + if (newPermisions != permissions) { + if (!QFile::setPermissions(target_file_path, permissions)) { + logWarning(tr("Could not fix permissions for %1").arg(target_file_path)); + } + } + } qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; } while (m_input->goToNextFile()); From 2b6d8b8a2d7aff89b7677a22980254115293dbd5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 3 Nov 2023 23:02:17 +0200 Subject: [PATCH 0086/2054] updated tooltip messages Signed-off-by: Trial97 --- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- launcher/minecraft/mod/ResourcePackFolderModel.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 5c6cdb00e5..5ba1795fbd 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -180,7 +180,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio case ProviderColumn: return tr("Where the mod was downloaded from."); case SizeColumn: - return tr("The size of the resource."); + return tr("The size of the mod."); default: return QVariant(); } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index d94751629a..e9c3d3043a 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -161,7 +161,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O case DateColumn: return tr("The date and time this resource pack was last changed (or added)."); case SizeColumn: - return tr("The size of the resource."); + return tr("The size of the resource pack."); default: return {}; } From f828ea8929f9ba23f5f2d6a852c9634eebdf7786 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 4 Nov 2023 11:49:05 +0200 Subject: [PATCH 0087/2054] updated the texture pack tooltips Signed-off-by: Trial97 --- launcher/minecraft/mod/TexturePackFolderModel.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index ef241351f4..e3f369bb88 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -135,15 +135,15 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or switch (section) { case ActiveColumn: //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. - return tr("Is the resource enabled?"); + return tr("Is the texture pack enabled?"); case NameColumn: //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. - return tr("The name of the resource."); + return tr("The name of the texture pack."); case DateColumn: //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. - return tr("The date and time this resource was last changed (or added)."); + return tr("The date and time this texture pack was last changed (or added)."); case SizeColumn: - return tr("The size of the resource."); + return tr("The size of the texture pack."); default: return {}; } From 5afe6600eea7d569c6f47dcd98226ca10c40aa62 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 4 Nov 2023 16:49:35 +0200 Subject: [PATCH 0088/2054] use fs::move instead of qt rename Signed-off-by: Trial97 --- launcher/FileSystem.cpp | 3 +-- launcher/InstanceList.cpp | 3 +-- launcher/modplatform/flame/FlameInstanceCreationTask.cpp | 4 ++-- launcher/modplatform/legacy_ftb/PackInstallTask.cpp | 2 +- .../modplatform/modrinth/ModrinthInstanceCreationTask.cpp | 4 ++-- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c7d5f85fa5..794804985c 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -652,8 +652,7 @@ bool move(const QString& source, const QString& dest) if (err) { qWarning() << "Failed to move file:" << QString::fromStdString(err.message()); - qDebug() << "Source file:" << source; - qDebug() << "Destination file:" << dest; + qDebug() << "Source file:" << source << ";Destination file:" << dest; } return err.value() == 0; diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 2b8f342939..8f0cbba79c 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -953,7 +953,6 @@ bool InstanceList::commitStagedInstance(const QString& path, if (groupName.isEmpty() && !groupName.isNull()) groupName = QString(); - QDir dir; QString instID; InstancePtr inst; @@ -977,7 +976,7 @@ bool InstanceList::commitStagedInstance(const QString& path, return false; } } else { - if (!dir.rename(path, destination)) { + if (!FS::move(path, destination)) { qWarning() << "Failed to move" << path << "to" << destination; return false; } diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 2a26ce944b..7e107f2d2d 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -321,7 +321,7 @@ bool FlameCreationTask::createInstance() // Keep index file in case we need it some other time (like when changing versions) QString new_index_place(FS::PathCombine(parent_folder, "manifest.json")); FS::ensureFilePathExists(new_index_place); - QFile::rename(index_path, new_index_place); + FS::move(index_path, new_index_place); } catch (const JSONValidationError& e) { setError(tr("Could not understand pack manifest:\n") + e.cause()); @@ -335,7 +335,7 @@ bool FlameCreationTask::createInstance() Override::createOverrides("overrides", parent_folder, overridePath); QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); - if (!QFile::rename(overridePath, mcPath)) { + if (!FS::move(overridePath, mcPath)) { setError(tr("Could not rename the overrides folder:\n") + m_pack.overrides); return false; } diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 0912967511..867773f58f 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -137,7 +137,7 @@ void PackInstallTask::install() QDir unzipMcDir(m_stagingPath + "/unzip/minecraft"); if (unzipMcDir.exists()) { // ok, found minecraft dir, move contents to instance dir - if (!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) { + if (!FS::move(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) { emitFailed(tr("Failed to move unzipped Minecraft!")); return; } diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index e732ad39cc..f01c52d218 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -171,7 +171,7 @@ bool ModrinthCreationTask::createInstance() // Keep index file in case we need it some other time (like when changing versions) QString new_index_place(FS::PathCombine(parent_folder, "modrinth.index.json")); FS::ensureFilePathExists(new_index_place); - QFile::rename(index_path, new_index_place); + FS::move(index_path, new_index_place); auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); @@ -181,7 +181,7 @@ bool ModrinthCreationTask::createInstance() Override::createOverrides("overrides", parent_folder, override_path); // Apply the overrides - if (!QFile::rename(override_path, mcPath)) { + if (!FS::move(override_path, mcPath)) { setError(tr("Could not rename the overrides folder:\n") + "overrides"); return false; } From 1d67fc66466566955719d4ab599d8b5482182f46 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 5 Nov 2023 16:47:33 +0200 Subject: [PATCH 0089/2054] added special case for windows Signed-off-by: Trial97 --- launcher/FileSystem.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 794804985c..5bb10b0691 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -643,6 +643,19 @@ void ExternalLinkFileProcess::runLinkFile() qDebug() << "Process exited"; } +bool moveByCopy(const QString& source, const QString& dest) +{ + if (!copy(source, dest)()) { // copy + qDebug() << "Copy of" << source << "to" << dest << "failed!"; + return false; + } + if (!deletePath(source)) { // remove original + qDebug() << "Deletion of" << source << "failed!"; + return false; + }; + return true; +} + bool move(const QString& source, const QString& dest) { std::error_code err; @@ -650,12 +663,14 @@ bool move(const QString& source, const QString& dest) ensureFilePathExists(dest); fs::rename(StringUtils::toStdString(source), StringUtils::toStdString(dest), err); - if (err) { - qWarning() << "Failed to move file:" << QString::fromStdString(err.message()); - qDebug() << "Source file:" << source << ";Destination file:" << dest; + if (err.value() != 0) { + if (moveByCopy(source, dest)) + return true; + qDebug() << "Move of" << source << "to" << dest << "failed!"; + qWarning() << "Failed to move file:" << QString::fromStdString(err.message()) << QString::number(err.value()); + return false; } - - return err.value() == 0; + return true; } bool deletePath(QString path) From b4bfc03e8b25d8eeea250560c1fc5e1c055677b2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 9 Nov 2023 21:48:46 +0200 Subject: [PATCH 0090/2054] Added button to refresh themes and catpacks Signed-off-by: Trial97 --- launcher/ui/themes/ThemeManager.cpp | 10 +++++++++ launcher/ui/themes/ThemeManager.h | 2 ++ .../ui/widgets/ThemeCustomizationWidget.cpp | 21 +++++++++++++++++++ .../ui/widgets/ThemeCustomizationWidget.h | 1 + .../ui/widgets/ThemeCustomizationWidget.ui | 7 +++++++ 5 files changed, 41 insertions(+) diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index 0bcac100c7..cb90bc826b 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -313,3 +313,13 @@ void ThemeManager::initializeCatPacks() } } } + +void ThemeManager::refresh() +{ + m_themes.clear(); + m_icons.clear(); + m_catPacks.clear(); + + initializeThemes(); + initializeCatPacks(); +}; \ No newline at end of file diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index b5c66677b2..8ed5878065 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -55,6 +55,8 @@ class ThemeManager { QString getCatPack(QString catName = ""); QList getValidCatPacks(); + void refresh(); + private: std::map> m_themes; std::map m_icons; diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.cpp b/launcher/ui/widgets/ThemeCustomizationWidget.cpp index 0de97441f5..a0e682bb93 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.cpp +++ b/launcher/ui/widgets/ThemeCustomizationWidget.cpp @@ -39,6 +39,8 @@ ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget* parent) : QWidget(pa [] { DesktopServices::openDirectory(APPLICATION->themeManager()->getApplicationThemesFolder().path()); }); connect(ui->catPackFolder, &QPushButton::clicked, this, [] { DesktopServices::openDirectory(APPLICATION->themeManager()->getCatPacksFolder().path()); }); + + connect(ui->refreshButton, &QPushButton::clicked, this, &ThemeCustomizationWidget::refresh); } ThemeCustomizationWidget::~ThemeCustomizationWidget() @@ -169,3 +171,22 @@ void ThemeCustomizationWidget::retranslate() { ui->retranslateUi(this); } + +void ThemeCustomizationWidget::refresh() +{ + applySettings(); + disconnect(ui->iconsComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyIconTheme); + disconnect(ui->widgetStyleComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &ThemeCustomizationWidget::applyWidgetTheme); + disconnect(ui->backgroundCatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &ThemeCustomizationWidget::applyCatTheme); + APPLICATION->themeManager()->refresh(); + ui->iconsComboBox->clear(); + ui->widgetStyleComboBox->clear(); + ui->backgroundCatComboBox->clear(); + loadSettings(); + connect(ui->iconsComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyIconTheme); + connect(ui->widgetStyleComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &ThemeCustomizationWidget::applyWidgetTheme); + connect(ui->backgroundCatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyCatTheme); +}; \ No newline at end of file diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.h b/launcher/ui/widgets/ThemeCustomizationWidget.h index cef5fb6c66..6977b8495e 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.h +++ b/launcher/ui/widgets/ThemeCustomizationWidget.h @@ -44,6 +44,7 @@ class ThemeCustomizationWidget : public QWidget { void applyIconTheme(int index); void applyWidgetTheme(int index); void applyCatTheme(int index); + void refresh(); signals: int currentIconThemeChanged(int index); diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.ui b/launcher/ui/widgets/ThemeCustomizationWidget.ui index 4503181c21..322f9d6a7f 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.ui +++ b/launcher/ui/widgets/ThemeCustomizationWidget.ui @@ -167,6 +167,13 @@
+ + + + Refresh + + + From 463608b289eeffc3e20e2af396875ee084db11d1 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 10 Nov 2023 19:37:11 +0200 Subject: [PATCH 0091/2054] fixed shader packs detection Signed-off-by: Trial97 --- .../minecraft/mod/ResourceFolderModel.cpp | 30 ++++++++++++++++++- launcher/minecraft/mod/ResourceFolderModel.h | 7 ++--- launcher/minecraft/mod/ShaderPack.cpp | 5 ---- launcher/minecraft/mod/ShaderPack.h | 1 - .../minecraft/mod/tasks/LocalModParseTask.cpp | 2 +- .../mod/tasks/LocalResourcePackParseTask.cpp | 4 ++- .../mod/tasks/LocalShaderPackParseTask.cpp | 4 ++- .../mod/tasks/LocalTexturePackParseTask.cpp | 4 ++- .../pages/instance/ExternalResourcesPage.cpp | 1 + 9 files changed, 42 insertions(+), 16 deletions(-) diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 0503b660bb..de638681ea 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -283,7 +283,12 @@ void ResourceFolderModel::resolveResource(Resource* res) connect( task.get(), &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); connect( - task.get(), &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection); + task.get(), &Task::finished, this, + [=] { + m_active_parse_tasks.remove(ticket); + emit parseFinished(); + }, + Qt::ConnectionType::QueuedConnection); m_helper_thread_task.addTask(task); @@ -630,3 +635,26 @@ QString ResourceFolderModel::instDirPath() const { return QFileInfo(m_instance->instanceRoot()).absoluteFilePath(); } + +void ResourceFolderModel::onParseFailed(int ticket, QString resource_id) +{ + auto iter = m_active_parse_tasks.constFind(ticket); + if (iter == m_active_parse_tasks.constEnd()) + return; + + auto removed_index = m_resources_index[resource_id]; + auto removed_it = m_resources.begin() + removed_index; + Q_ASSERT(removed_it != m_resources.end()); + + beginRemoveRows(QModelIndex(), removed_index, removed_index); + m_resources.erase(removed_it); + + // update index + m_resources_index.clear(); + int idx = 0; + for (auto const& mod : qAsConst(m_resources)) { + m_resources_index[mod->internal_id()] = idx; + idx++; + } + endRemoveRows(); +} diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 60b8879c01..d6ac6e0dfd 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -143,6 +143,7 @@ class ResourceFolderModel : public QAbstractListModel { signals: void updateFinished(); + void parseFinished(); protected: /** This creates a new update task to be executed by update(). @@ -189,11 +190,7 @@ class ResourceFolderModel : public QAbstractListModel { * if the resource is complex and has more stuff to parse. */ virtual void onParseSucceeded(int ticket, QString resource_id); - virtual void onParseFailed(int ticket, QString resource_id) - { - Q_UNUSED(ticket); - Q_UNUSED(resource_id); - } + virtual void onParseFailed(int ticket, QString resource_id); protected: // Represents the relationship between a column's index (represented by the list index), and it's sorting key. diff --git a/launcher/minecraft/mod/ShaderPack.cpp b/launcher/minecraft/mod/ShaderPack.cpp index 2c094f26ab..ccb344cb57 100644 --- a/launcher/minecraft/mod/ShaderPack.cpp +++ b/launcher/minecraft/mod/ShaderPack.cpp @@ -35,8 +35,3 @@ bool ShaderPack::valid() const { return m_pack_format != ShaderPackFormat::INVALID; } - -bool ShaderPack::applyFilter(QRegularExpression filter) const -{ - return valid() && Resource::applyFilter(filter); -} diff --git a/launcher/minecraft/mod/ShaderPack.h b/launcher/minecraft/mod/ShaderPack.h index d07c124bec..ec0f9404ee 100644 --- a/launcher/minecraft/mod/ShaderPack.h +++ b/launcher/minecraft/mod/ShaderPack.h @@ -54,7 +54,6 @@ class ShaderPack : public Resource { void setPackFormat(ShaderPackFormat new_format); bool valid() const override; - [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; protected: mutable QMutex m_data_lock; diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index e9e12d86aa..0594351cb5 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -746,7 +746,7 @@ void LocalModParseTask::executeTask() m_result->details = mod.details(); if (m_aborted) - emit finished(); + emitAborted(); else emitSucceeded(); } diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 26bc07637d..f495c9b8d9 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -286,8 +286,10 @@ bool LocalResourcePackParseTask::abort() void LocalResourcePackParseTask::executeTask() { - if (!ResourcePackUtils::process(m_resource_pack)) + if (!ResourcePackUtils::process(m_resource_pack)) { + emitFailed("this is not a resource pack"); return; + } if (m_aborted) emitAborted(); diff --git a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp index a9949735b0..4deebcd1dd 100644 --- a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp @@ -103,8 +103,10 @@ bool LocalShaderPackParseTask::abort() void LocalShaderPackParseTask::executeTask() { - if (!ShaderPackUtils::process(m_shader_pack)) + if (!ShaderPackUtils::process(m_shader_pack)) { + emitFailed("this is not a shader pack"); return; + } if (m_aborted) emitAborted(); diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp index d7e61ca907..00cc2def2d 100644 --- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp @@ -241,8 +241,10 @@ bool LocalTexturePackParseTask::abort() void LocalTexturePackParseTask::executeTask() { - if (!TexturePackUtils::process(m_texture_pack)) + if (!TexturePackUtils::process(m_texture_pack)) { + emitFailed("this is not a texture pack"); return; + } if (m_aborted) emitAborted(); diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index 1a8fafa9ba..f46f8af5f6 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -87,6 +87,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared }; connect(selection_model, &QItemSelectionModel::selectionChanged, this, updateExtra); connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra); + connect(model.get(), &ResourceFolderModel::parseFinished, this, updateExtra); connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged); From 50a4d61ded300c47c727f1104d17b2960ff5d0c6 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 10 Nov 2023 23:26:41 +0200 Subject: [PATCH 0092/2054] Fixed demo mode Signed-off-by: Trial97 --- launcher/LaunchController.cpp | 87 +++++++++++++------- launcher/LaunchController.h | 2 + launcher/minecraft/auth/AuthSession.cpp | 11 ++- launcher/minecraft/auth/AuthSession.h | 2 +- launcher/minecraft/auth/MinecraftAccount.cpp | 2 + 5 files changed, 71 insertions(+), 33 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index f59cdeda80..6d5a9e93ef 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -129,12 +129,63 @@ void LaunchController::decideAccount() } } +bool LaunchController::askPlayDemo() +{ + QMessageBox box(m_parentWidget); + box.setWindowTitle(tr("Play demo?")); + box.setText( + tr("This account does not own Minecraft.\nYou need to purchase the game first to play it.\n\nDo you want to play " + "the demo?")); + box.setIcon(QMessageBox::Warning); + auto demoButton = box.addButton(tr("Play Demo"), QMessageBox::ButtonRole::YesRole); + auto cancelButton = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::NoRole); + box.setDefaultButton(cancelButton); + + box.exec(); + return box.clickedButton() == demoButton; +} + +QString LaunchController::askOfflineName(QString playerName, bool demo, bool& ok) +{ + // we ask the user for a player name + QString message = tr("Choose your offline mode player name."); + if (demo) { + message = tr("Choose your demo mode player name."); + } + + QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString(); + QString usedname = lastOfflinePlayerName.isEmpty() ? playerName : lastOfflinePlayerName; + QString name = QInputDialog::getText(m_parentWidget, tr("Player name"), message, QLineEdit::Normal, usedname, &ok); + if (!ok) + return {}; + if (name.length()) { + usedname = name; + APPLICATION->settings()->set("LastOfflinePlayerName", usedname); + } + return usedname; +} + void LaunchController::login() { decideAccount(); - // if no account is selected, we bail if (!m_accountToUse) { + // if no account is selected, ask about demo + if (!m_demo) { + m_demo = askPlayDemo(); + } + if (m_demo) { + // we ask the user for a player name + bool ok = false; + auto name = askOfflineName("Player", m_demo, ok); + if (ok) { + m_session = std::make_shared(); + m_session->MakeDemo(name, MinecraftAccount::uuidFromUsername(name).toString().remove(QRegularExpression("[{}-]"))); + launchInstance(); + return; + } + } + // if no account is selected, we bail emitFailed(tr("No account selected for launch.")); return; } @@ -175,24 +226,12 @@ void LaunchController::login() if (!m_session->wants_online) { // we ask the user for a player name bool ok = false; - - QString message = tr("Choose your offline mode player name."); - if (m_session->demo) { - message = tr("Choose your demo mode player name."); - } - - QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString(); - QString usedname = lastOfflinePlayerName.isEmpty() ? m_session->player_name : lastOfflinePlayerName; - QString name = QInputDialog::getText(m_parentWidget, tr("Player name"), message, QLineEdit::Normal, usedname, &ok); + auto name = askOfflineName(m_session->player_name, m_session->demo, ok); if (!ok) { tryagain = false; break; } - if (name.length()) { - usedname = name; - APPLICATION->settings()->set("LastOfflinePlayerName", usedname); - } - m_session->MakeOffline(usedname); + m_session->MakeOffline(name); // offline flavored game from here :3 } if (m_accountToUse->ownsMinecraft()) { @@ -212,20 +251,10 @@ void LaunchController::login() return; } else { // play demo ? - QMessageBox box(m_parentWidget); - box.setWindowTitle(tr("Play demo?")); - box.setText( - tr("This account does not own Minecraft.\nYou need to purchase the game first to play it.\n\nDo you want to play " - "the demo?")); - box.setIcon(QMessageBox::Warning); - auto demoButton = box.addButton(tr("Play Demo"), QMessageBox::ButtonRole::YesRole); - auto cancelButton = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::NoRole); - box.setDefaultButton(cancelButton); - - box.exec(); - if (box.clickedButton() == demoButton) { - // play demo here - m_session->MakeDemo(); + if (!m_session->demo) { + m_session->demo = askPlayDemo(); + } + if (m_session->demo) { // play demo here launchInstance(); } else { emitFailed(tr("Launch cancelled - account does not own Minecraft.")); diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h index f1c88afb7a..02b1e0d331 100644 --- a/launcher/LaunchController.h +++ b/launcher/LaunchController.h @@ -74,6 +74,8 @@ class LaunchController : public Task { void login(); void launchInstance(); void decideAccount(); + bool askPlayDemo(); + QString askOfflineName(QString playerName, bool demo, bool& ok); private slots: void readyForLaunch(); diff --git a/launcher/minecraft/auth/AuthSession.cpp b/launcher/minecraft/auth/AuthSession.cpp index 37534f9830..3657befec2 100644 --- a/launcher/minecraft/auth/AuthSession.cpp +++ b/launcher/minecraft/auth/AuthSession.cpp @@ -30,8 +30,13 @@ bool AuthSession::MakeOffline(QString offline_playername) return true; } -void AuthSession::MakeDemo() +void AuthSession::MakeDemo(QString name, QString u) { - player_name = "Player"; + wants_online = false; demo = true; -} + uuid = u; + session = "-"; + access_token = "0"; + player_name = name; + status = PlayableOnline; // needs online to download the assets +}; \ No newline at end of file diff --git a/launcher/minecraft/auth/AuthSession.h b/launcher/minecraft/auth/AuthSession.h index cec238033a..54e7d69e02 100644 --- a/launcher/minecraft/auth/AuthSession.h +++ b/launcher/minecraft/auth/AuthSession.h @@ -10,7 +10,7 @@ class QNetworkAccessManager; struct AuthSession { bool MakeOffline(QString offline_playername); - void MakeDemo(); + void MakeDemo(QString name, QString uuid); QString serializeUserProperties(); diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 545d06aed0..c3c29f5983 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -269,6 +269,8 @@ void MinecraftAccount::fillSession(AuthSessionPtr session) session->player_name = data.profileName(); // profile ID session->uuid = data.profileId(); + if (session->uuid.isEmpty()) + session->uuid = uuidFromUsername(session->player_name).toString().remove(QRegularExpression("[{}-]")); // 'legacy' or 'mojang', depending on account type session->user_type = typeString(); if (!session->access_token.isEmpty()) { From 6804e2ba59a45f8ec504e392829bacb51a30c86d Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 14 Nov 2023 18:40:45 +0200 Subject: [PATCH 0093/2054] moved export to list to the mods page Signed-off-by: Trial97 --- launcher/ui/MainWindow.cpp | 10 ---- launcher/ui/MainWindow.h | 1 - launcher/ui/MainWindow.ui | 9 ---- launcher/ui/dialogs/ExportToModListDialog.cpp | 48 ++++++++----------- launcher/ui/dialogs/ExportToModListDialog.h | 10 ++-- launcher/ui/pages/instance/ModFolderPage.cpp | 18 ++++++- launcher/ui/pages/instance/ModFolderPage.h | 1 + 7 files changed, 42 insertions(+), 55 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 1da982dadd..3ae80ebb68 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -96,7 +96,6 @@ #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ExportInstanceDialog.h" #include "ui/dialogs/ExportPackDialog.h" -#include "ui/dialogs/ExportToModListDialog.h" #include "ui/dialogs/IconPickerDialog.h" #include "ui/dialogs/ImportResourceDialog.h" #include "ui/dialogs/NewInstanceDialog.h" @@ -208,7 +207,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi exportInstanceMenu->addAction(ui->actionExportInstanceZip); exportInstanceMenu->addAction(ui->actionExportInstanceMrPack); exportInstanceMenu->addAction(ui->actionExportInstanceFlamePack); - exportInstanceMenu->addAction(ui->actionExportInstanceToModList); ui->actionExportInstance->setMenu(exportInstanceMenu); } @@ -1390,14 +1388,6 @@ void MainWindow::on_actionExportInstanceMrPack_triggered() } } -void MainWindow::on_actionExportInstanceToModList_triggered() -{ - if (m_selectedInstance) { - ExportToModListDialog dlg(m_selectedInstance, this); - dlg.exec(); - } -} - void MainWindow::on_actionExportInstanceFlamePack_triggered() { if (m_selectedInstance) { diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 0b72874044..7935ddf562 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -156,7 +156,6 @@ class MainWindow : public QMainWindow { void on_actionExportInstanceZip_triggered(); void on_actionExportInstanceMrPack_triggered(); void on_actionExportInstanceFlamePack_triggered(); - void on_actionExportInstanceToModList_triggered(); void on_actionRenameInstance_triggered(); diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 91b2c27030..ee90eeba1f 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -479,15 +479,6 @@ CurseForge (zip) - - - - .. - - - Mod List - - diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp index a343f555ac..f767727d7a 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.cpp +++ b/launcher/ui/dialogs/ExportToModListDialog.cpp @@ -22,8 +22,6 @@ #include #include "FileSystem.h" #include "Markdown.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/mod/ModFolderModel.h" #include "modplatform/helpers/ExportToModList.h" #include "ui_ExportToModListDialog.h" @@ -41,21 +39,12 @@ const QHash ExportToModListDialog::exampleLin { ExportToModList::CSV, "{name},{url},{version},\"{authors}\"" }, }; -ExportToModListDialog::ExportToModListDialog(InstancePtr instance, QWidget* parent) - : QDialog(parent), m_template_changed(false), name(instance->name()), ui(new Ui::ExportToModListDialog) +ExportToModListDialog::ExportToModListDialog(QString name, QList mods, QWidget* parent) + : QDialog(parent), m_mods(mods), m_template_changed(false), m_name(name), ui(new Ui::ExportToModListDialog) { ui->setupUi(this); enableCustom(false); - MinecraftInstance* mcInstance = dynamic_cast(instance.get()); - if (mcInstance) { - mcInstance->loaderModList()->update(); - connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, [this, mcInstance]() { - m_allMods = mcInstance->loaderModList()->allMods(); - triggerImp(); - }); - } - connect(ui->formatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ExportToModListDialog::formatChanged); connect(ui->authorsCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); connect(ui->versionCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); @@ -64,7 +53,7 @@ ExportToModListDialog::ExportToModListDialog(InstancePtr instance, QWidget* pare connect(ui->versionButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Version); }); connect(ui->urlButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Url); }); connect(ui->templateText, &QTextEdit::textChanged, this, [this] { - if (ui->templateText->toPlainText() != exampleLines[format]) + if (ui->templateText->toPlainText() != exampleLines[m_format]) ui->formatComboBox->setCurrentIndex(5); else triggerImp(); @@ -73,6 +62,7 @@ ExportToModListDialog::ExportToModListDialog(InstancePtr instance, QWidget* pare this->ui->finalText->selectAll(); this->ui->finalText->copy(); }); + triggerImp(); } ExportToModListDialog::~ExportToModListDialog() @@ -86,38 +76,38 @@ void ExportToModListDialog::formatChanged(int index) case 0: { enableCustom(false); ui->resultText->show(); - format = ExportToModList::HTML; + m_format = ExportToModList::HTML; break; } case 1: { enableCustom(false); ui->resultText->show(); - format = ExportToModList::MARKDOWN; + m_format = ExportToModList::MARKDOWN; break; } case 2: { enableCustom(false); ui->resultText->hide(); - format = ExportToModList::PLAINTXT; + m_format = ExportToModList::PLAINTXT; break; } case 3: { enableCustom(false); ui->resultText->hide(); - format = ExportToModList::JSON; + m_format = ExportToModList::JSON; break; } case 4: { enableCustom(false); ui->resultText->hide(); - format = ExportToModList::CSV; + m_format = ExportToModList::CSV; break; } case 5: { m_template_changed = true; enableCustom(true); ui->resultText->hide(); - format = ExportToModList::CUSTOM; + m_format = ExportToModList::CUSTOM; break; } } @@ -126,8 +116,8 @@ void ExportToModListDialog::formatChanged(int index) void ExportToModListDialog::triggerImp() { - if (format == ExportToModList::CUSTOM) { - ui->finalText->setPlainText(ExportToModList::exportToModList(m_allMods, ui->templateText->toPlainText())); + if (m_format == ExportToModList::CUSTOM) { + ui->finalText->setPlainText(ExportToModList::exportToModList(m_mods, ui->templateText->toPlainText())); return; } auto opt = 0; @@ -137,9 +127,9 @@ void ExportToModListDialog::triggerImp() opt |= ExportToModList::Version; if (ui->urlCheckBox->isChecked()) opt |= ExportToModList::Url; - auto txt = ExportToModList::exportToModList(m_allMods, format, static_cast(opt)); + auto txt = ExportToModList::exportToModList(m_mods, m_format, static_cast(opt)); ui->finalText->setPlainText(txt); - switch (format) { + switch (m_format) { case ExportToModList::CUSTOM: return; case ExportToModList::HTML: @@ -155,7 +145,7 @@ void ExportToModListDialog::triggerImp() case ExportToModList::CSV: break; } - auto exampleLine = exampleLines[format]; + auto exampleLine = exampleLines[m_format]; if (!m_template_changed && ui->templateText->toPlainText() != exampleLine) ui->templateText->setPlainText(exampleLine); } @@ -163,9 +153,9 @@ void ExportToModListDialog::triggerImp() void ExportToModListDialog::done(int result) { if (result == Accepted) { - const QString filename = FS::RemoveInvalidFilenameChars(name); + const QString filename = FS::RemoveInvalidFilenameChars(m_name); const QString output = - QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + extension()), + QFileDialog::getSaveFileName(this, tr("Export %1").arg(m_name), FS::PathCombine(QDir::homePath(), filename + extension()), "File (*.txt *.html *.md *.json *.csv)", nullptr); if (output.isEmpty()) @@ -178,7 +168,7 @@ void ExportToModListDialog::done(int result) QString ExportToModListDialog::extension() { - switch (format) { + switch (m_format) { case ExportToModList::HTML: return ".html"; case ExportToModList::MARKDOWN: @@ -197,7 +187,7 @@ QString ExportToModListDialog::extension() void ExportToModListDialog::addExtra(ExportToModList::OptionalData option) { - if (format != ExportToModList::CUSTOM) + if (m_format != ExportToModList::CUSTOM) return; switch (option) { case ExportToModList::Authors: diff --git a/launcher/ui/dialogs/ExportToModListDialog.h b/launcher/ui/dialogs/ExportToModListDialog.h index 9886ae5a0c..4ebe203f7b 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.h +++ b/launcher/ui/dialogs/ExportToModListDialog.h @@ -20,7 +20,6 @@ #include #include -#include "BaseInstance.h" #include "minecraft/mod/Mod.h" #include "modplatform/helpers/ExportToModList.h" @@ -32,7 +31,7 @@ class ExportToModListDialog : public QDialog { Q_OBJECT public: - explicit ExportToModListDialog(InstancePtr instance, QWidget* parent = nullptr); + explicit ExportToModListDialog(QString name, QList mods, QWidget* parent = nullptr); ~ExportToModListDialog(); void done(int result) override; @@ -46,10 +45,11 @@ class ExportToModListDialog : public QDialog { private: QString extension(); void enableCustom(bool enabled); - QList m_allMods; + + QList m_mods; bool m_template_changed; - QString name; - ExportToModList::Formats format = ExportToModList::Formats::HTML; + QString m_name; + ExportToModList::Formats m_format = ExportToModList::Formats::HTML; Ui::ExportToModListDialog* ui; static const QHash exampleLines; }; diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index a38e608f2f..31fb49d0cc 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -37,6 +37,7 @@ */ #include "ModFolderPage.h" +#include "ui/dialogs/ExportToModListDialog.h" #include "ui_ExternalResourcesPage.h" #include @@ -111,6 +112,10 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr connect(actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata); actionRemoveItemMetadata->setEnabled(false); + auto actionExportMetadata = updateMenu->addAction(tr("Export metadata")); + actionExportMetadata->setToolTip(tr("Export mod's metadata to text")); + connect(actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata); + ui->actionUpdateItem->setMenu(updateMenu); ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)")); @@ -124,7 +129,7 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); }; connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, - [this, check_allow_update, actionRemoveItemMetadata] { + [this, check_allow_update, actionRemoveItemMetadata, actionExportMetadata] { ui->actionUpdateItem->setEnabled(check_allow_update()); auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); @@ -360,3 +365,14 @@ void ModFolderPage::deleteModMetadata() m_model->deleteModsMetadata(selection); } + +void ModFolderPage::exportModMetadata() +{ + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto selectedMods = m_model->selectedMods(selection); + if (selectedMods.length() == 0) + selectedMods = m_model->allMods(); + + ExportToModListDialog dlg(m_instance->name(), selectedMods, this); + dlg.exec(); +} diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 4672350c66..928f5ca7e3 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -62,6 +62,7 @@ class ModFolderPage : public ExternalResourcesPage { private slots: void removeItems(const QItemSelection& selection) override; void deleteModMetadata(); + void exportModMetadata(); void installMods(); void updateMods(bool includeDeps = false); From bedb4da8699f6fc8e23d8e9b5644f6dd92251b7b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 14 Nov 2023 19:37:23 +0200 Subject: [PATCH 0094/2054] Added filename to the modlist export Signed-off-by: Trial97 --- .../modplatform/helpers/ExportToModList.cpp | 32 ++++++++++++++++--- .../modplatform/helpers/ExportToModList.h | 6 +--- launcher/ui/dialogs/ExportToModListDialog.cpp | 13 ++++++-- launcher/ui/dialogs/ExportToModListDialog.ui | 14 ++++++++ 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/launcher/modplatform/helpers/ExportToModList.cpp b/launcher/modplatform/helpers/ExportToModList.cpp index 1f01c4a891..86d051c752 100644 --- a/launcher/modplatform/helpers/ExportToModList.cpp +++ b/launcher/modplatform/helpers/ExportToModList.cpp @@ -16,6 +16,7 @@ * along with this program. If not, see . */ #include "ExportToModList.h" +#include #include #include #include @@ -42,17 +43,28 @@ QString toHTML(QList mods, OptionalData extraData) } if (extraData & Authors && !mod->authors().isEmpty()) line += " by " + mod->authors().join(", ").toHtmlEscaped(); + if (extraData & FileName) + line += QString(" (%1)").arg(mod->fileinfo().fileName().toHtmlEscaped()); + lines.append(QString("
  • %1
  • ").arg(line)); } return QString("
      \n\t%1\n
    ").arg(lines.join("\n\t")); } +QString toMarkdownEscaped(QString src) +{ + for (auto ch : "\\`*_{}[]<>()#+-.!|") + src.replace(ch, QString("\\%1").arg(ch)); + return src; +} + QString toMarkdown(QList mods, OptionalData extraData) { QStringList lines; + for (auto mod : mods) { auto meta = mod->metadata(); - auto modName = mod->name(); + auto modName = toMarkdownEscaped(mod->name()); if (extraData & Url) { auto url = mod->metaurl(); if (!url.isEmpty()) @@ -60,14 +72,16 @@ QString toMarkdown(QList mods, OptionalData extraData) } auto line = modName; if (extraData & Version) { - auto ver = mod->version(); + auto ver = toMarkdownEscaped(mod->version()); if (ver.isEmpty() && meta != nullptr) - ver = meta->version().toString(); + ver = toMarkdownEscaped(meta->version().toString()); if (!ver.isEmpty()) line += QString(" [%1]").arg(ver); } if (extraData & Authors && !mod->authors().isEmpty()) - line += " by " + mod->authors().join(", "); + line += " by " + toMarkdownEscaped(mod->authors().join(", ")); + if (extraData & FileName) + line += QString(" (%1)").arg(toMarkdownEscaped(mod->fileinfo().fileName())); lines << "- " + line; } return lines.join("\n"); @@ -95,6 +109,8 @@ QString toPlainTXT(QList mods, OptionalData extraData) } if (extraData & Authors && !mod->authors().isEmpty()) line += " by " + mod->authors().join(", "); + if (extraData & FileName) + line += QString(" (%1)").arg(mod->fileinfo().fileName()); lines << line; } return lines.join("\n"); @@ -122,6 +138,8 @@ QString toJSON(QList mods, OptionalData extraData) } if (extraData & Authors && !mod->authors().isEmpty()) line["authors"] = QJsonArray::fromStringList(mod->authors()); + if (extraData & FileName) + line["filename"] = mod->fileinfo().fileName(); lines << line; } QJsonDocument doc; @@ -154,6 +172,8 @@ QString toCSV(QList mods, OptionalData extraData) authors = QString("\"%1\"").arg(mod->authors().join(",")); data << authors; } + if (extraData & FileName) + data << mod->fileinfo().fileName(); lines << data.join(","); } return lines.join("\n"); @@ -189,11 +209,13 @@ QString exportToModList(QList mods, QString lineTemplate) if (ver.isEmpty() && meta != nullptr) ver = meta->version().toString(); auto authors = mod->authors().join(", "); + auto filename = mod->fileinfo().fileName(); lines << QString(lineTemplate) .replace("{name}", modName) .replace("{url}", url) .replace("{version}", ver) - .replace("{authors}", authors); + .replace("{authors}", authors) + .replace("{filename}", filename); } return lines.join("\n"); } diff --git a/launcher/modplatform/helpers/ExportToModList.h b/launcher/modplatform/helpers/ExportToModList.h index 7ea4ba9c2a..ab7797fe6d 100644 --- a/launcher/modplatform/helpers/ExportToModList.h +++ b/launcher/modplatform/helpers/ExportToModList.h @@ -23,11 +23,7 @@ namespace ExportToModList { enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM }; -enum OptionalData { - Authors = 1 << 0, - Url = 1 << 1, - Version = 1 << 2, -}; +enum OptionalData { Authors = 1 << 0, Url = 1 << 1, Version = 1 << 2, FileName = 1 << 3 }; QString exportToModList(QList mods, Formats format, OptionalData extraData); QString exportToModList(QList mods, QString lineTemplate); } // namespace ExportToModList diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp index f767727d7a..95916421db 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.cpp +++ b/launcher/ui/dialogs/ExportToModListDialog.cpp @@ -49,14 +49,15 @@ ExportToModListDialog::ExportToModListDialog(QString name, QList mods, QWi connect(ui->authorsCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); connect(ui->versionCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); connect(ui->urlCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); + connect(ui->filenameCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); connect(ui->authorsButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Authors); }); connect(ui->versionButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Version); }); connect(ui->urlButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Url); }); + connect(ui->filenameButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::FileName); }); connect(ui->templateText, &QTextEdit::textChanged, this, [this] { if (ui->templateText->toPlainText() != exampleLines[m_format]) ui->formatComboBox->setCurrentIndex(5); - else - triggerImp(); + triggerImp(); }); connect(ui->copyButton, &QPushButton::clicked, this, [this](bool) { this->ui->finalText->selectAll(); @@ -127,6 +128,8 @@ void ExportToModListDialog::triggerImp() opt |= ExportToModList::Version; if (ui->urlCheckBox->isChecked()) opt |= ExportToModList::Url; + if (ui->filenameCheckBox->isChecked()) + opt |= ExportToModList::FileName; auto txt = ExportToModList::exportToModList(m_mods, m_format, static_cast(opt)); ui->finalText->setPlainText(txt); switch (m_format) { @@ -199,6 +202,9 @@ void ExportToModListDialog::addExtra(ExportToModList::OptionalData option) case ExportToModList::Version: ui->templateText->insertPlainText("{version}"); break; + case ExportToModList::FileName: + ui->templateText->insertPlainText("{filename}"); + break; } } void ExportToModListDialog::enableCustom(bool enabled) @@ -211,4 +217,7 @@ void ExportToModListDialog::enableCustom(bool enabled) ui->urlCheckBox->setHidden(enabled); ui->urlButton->setHidden(!enabled); + + ui->filenameCheckBox->setHidden(enabled); + ui->filenameButton->setHidden(!enabled); } diff --git a/launcher/ui/dialogs/ExportToModListDialog.ui b/launcher/ui/dialogs/ExportToModListDialog.ui index 4f8ab52b59..3afda2fa8c 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.ui +++ b/launcher/ui/dialogs/ExportToModListDialog.ui @@ -117,6 +117,13 @@
    + + + + Filename + + + @@ -138,6 +145,13 @@ + + + + Filename + + + From 4d93f4adb144079449c05edf939226a6bb215921 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 15 Nov 2023 12:10:47 +0200 Subject: [PATCH 0095/2054] Fixed folder size Signed-off-by: Trial97 --- launcher/minecraft/World.cpp | 2 +- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- launcher/minecraft/mod/Resource.cpp | 20 +++++++++++++++++-- launcher/minecraft/mod/Resource.h | 2 ++ .../minecraft/mod/ResourceFolderModel.cpp | 2 +- .../minecraft/mod/ResourcePackFolderModel.cpp | 2 +- .../minecraft/mod/TexturePackFolderModel.cpp | 2 +- 7 files changed, 25 insertions(+), 7 deletions(-) diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index 1a680ac567..1eba148a50 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -206,8 +206,8 @@ int64_t calculateWorldSize(const QFileInfo& file) QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories); int64_t total = 0; while (it.hasNext()) { - total += it.fileInfo().size(); it.next(); + total += it.fileInfo().size(); } return total; } diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 5ba1795fbd..8f79a4e002 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -111,7 +111,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const return provider.value(); } case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); + return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); default: return QVariant(); } diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 79e52a8818..0115cf7caf 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -1,5 +1,6 @@ #include "Resource.h" +#include #include #include @@ -18,6 +19,20 @@ void Resource::setFile(QFileInfo file_info) parseFile(); } +qint64 calculateFileSize(const QFileInfo& file) +{ + if (file.isDir()) { + QDirIterator it(file.absoluteFilePath(), QDir::Files); + qint64 total = 0; + while (it.hasNext()) { + it.next(); + total += it.fileInfo().size(); + } + return total; + } + return file.size(); +} + void Resource::parseFile() { QString file_name{ m_file_info.fileName() }; @@ -26,6 +41,7 @@ void Resource::parseFile() m_internal_id = file_name; + m_size = calculateFileSize(m_file_info); if (m_file_info.isDir()) { m_type = ResourceType::FOLDER; m_name = file_name; @@ -90,9 +106,9 @@ std::pair Resource::compare(const Resource& other, SortType type) con return { -1, type == SortType::DATE }; break; case SortType::SIZE: { - if (fileinfo().size() > other.fileinfo().size()) + if (size() > other.size()) return { 1, type == SortType::SIZE }; - if (fileinfo().size() < other.fileinfo().size()) + if (size() < other.size()) return { -1, type == SortType::SIZE }; break; } diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index d94e4b3684..61949f917b 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -45,6 +45,7 @@ class Resource : public QObject { [[nodiscard]] auto internal_id() const -> QString { return m_internal_id; } [[nodiscard]] auto type() const -> ResourceType { return m_type; } [[nodiscard]] bool enabled() const { return m_enabled; } + [[nodiscard]] qint64 size() const { return m_size; } [[nodiscard]] virtual auto name() const -> QString { return m_name; } [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; } @@ -117,4 +118,5 @@ class Resource : public QObject { bool m_is_resolving = false; bool m_is_resolved = false; int m_resolution_ticket = 0; + qint64 m_size = 0; }; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 16ff012271..648bf84dba 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -416,7 +416,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const case DateColumn: return m_resources[row]->dateTimeChanged(); case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); + return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); default: return {}; } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index e9c3d3043a..cf2eb50dfb 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -88,7 +88,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const case DateColumn: return m_resources[row]->dateTimeChanged(); case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); + return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); default: return {}; diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index e3f369bb88..63fb1f8b3f 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -79,7 +79,7 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const case DateColumn: return m_resources[row]->dateTimeChanged(); case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); + return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); default: return {}; } From 215465e833e6faa17053bbaaf24b20ed99655d01 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 15 Nov 2023 14:11:34 +0200 Subject: [PATCH 0096/2054] added subdirectories to iteration Signed-off-by: Trial97 --- launcher/minecraft/mod/Resource.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 0115cf7caf..72652cefb0 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -22,7 +22,7 @@ void Resource::setFile(QFileInfo file_info) qint64 calculateFileSize(const QFileInfo& file) { if (file.isDir()) { - QDirIterator it(file.absoluteFilePath(), QDir::Files); + QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories); qint64 total = 0; while (it.hasNext()) { it.next(); From 26931475aeb0ed4f0e6e3d935095d9b1fa69b651 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 19 Nov 2023 15:58:30 +0200 Subject: [PATCH 0097/2054] made folder size reflect the number of elements Signed-off-by: Trial97 --- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- launcher/minecraft/mod/Resource.cpp | 25 ++++++++++--------- launcher/minecraft/mod/Resource.h | 4 +-- .../minecraft/mod/ResourceFolderModel.cpp | 2 +- .../minecraft/mod/ResourcePackFolderModel.cpp | 2 +- .../minecraft/mod/TexturePackFolderModel.cpp | 2 +- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 369ad39364..4e98d15205 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -110,7 +110,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const return provider.value(); } case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); + return m_resources[row]->sizeStr(); default: return QVariant(); } diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 72652cefb0..d8727db0af 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -5,6 +5,7 @@ #include #include "FileSystem.h" +#include "StringUtils.h" Resource::Resource(QObject* parent) : QObject(parent) {} @@ -19,18 +20,18 @@ void Resource::setFile(QFileInfo file_info) parseFile(); } -qint64 calculateFileSize(const QFileInfo& file) +QString calculateFileSize(const QFileInfo& file) { if (file.isDir()) { - QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories); - qint64 total = 0; - while (it.hasNext()) { - it.next(); - total += it.fileInfo().size(); - } - return total; + auto dir = QDir(file.absoluteFilePath()); + dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot); + auto count = dir.count(); + auto str = QObject::tr("item"); + if (count != 1) + str = QObject::tr("items"); + return QString("%1 %2").arg(QString::number(count), str); } - return file.size(); + return StringUtils::humanReadableFileSize(file.size(), true); } void Resource::parseFile() @@ -41,7 +42,7 @@ void Resource::parseFile() m_internal_id = file_name; - m_size = calculateFileSize(m_file_info); + m_size_str = calculateFileSize(m_file_info); if (m_file_info.isDir()) { m_type = ResourceType::FOLDER; m_name = file_name; @@ -106,9 +107,9 @@ std::pair Resource::compare(const Resource& other, SortType type) con return { -1, type == SortType::DATE }; break; case SortType::SIZE: { - if (size() > other.size()) + if (fileinfo().size() > other.fileinfo().size()) return { 1, type == SortType::SIZE }; - if (size() < other.size()) + if (fileinfo().size() < other.fileinfo().size()) return { -1, type == SortType::SIZE }; break; } diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 61949f917b..029afbd5bc 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -45,7 +45,7 @@ class Resource : public QObject { [[nodiscard]] auto internal_id() const -> QString { return m_internal_id; } [[nodiscard]] auto type() const -> ResourceType { return m_type; } [[nodiscard]] bool enabled() const { return m_enabled; } - [[nodiscard]] qint64 size() const { return m_size; } + [[nodiscard]] QString sizeStr() const { return m_size_str; } [[nodiscard]] virtual auto name() const -> QString { return m_name; } [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; } @@ -118,5 +118,5 @@ class Resource : public QObject { bool m_is_resolving = false; bool m_is_resolved = false; int m_resolution_ticket = 0; - qint64 m_size = 0; + QString m_size_str; }; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 6a9c5b7ddd..8e14f4e80c 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -417,7 +417,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const case DateColumn: return m_resources[row]->dateTimeChanged(); case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); + return m_resources[row]->sizeStr(); default: return {}; } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 7a61388331..7ad7b30380 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -88,7 +88,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const case DateColumn: return m_resources[row]->dateTimeChanged(); case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); + return m_resources[row]->sizeStr(); default: return {}; diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 38db9d8ffe..a042c91138 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -79,7 +79,7 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const case DateColumn: return m_resources[row]->dateTimeChanged(); case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); + return m_resources[row]->sizeStr(); default: return {}; } From 85b4e1f24f444627f48fe2e13b6cb34c45aa7361 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 22 Nov 2023 19:23:24 +0200 Subject: [PATCH 0098/2054] Made column hidden by default Signed-off-by: Trial97 --- launcher/minecraft/mod/ModFolderModel.cpp | 1 + launcher/minecraft/mod/ResourceFolderModel.cpp | 4 ++++ launcher/minecraft/mod/ResourceFolderModel.h | 1 + launcher/minecraft/mod/ResourcePackFolderModel.cpp | 1 + launcher/minecraft/mod/TexturePackFolderModel.cpp | 1 + 5 files changed, 8 insertions(+) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 98e21b9f05..6a948e2ec5 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -75,6 +75,7 @@ ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; m_columnsHideable = { false, true, false, true, true, true, true, true, true, true }; + m_columnsHiddenByDefault = { false, false, false, false, false, false, true, true, true, true }; } QVariant ModFolderModel::data(const QModelIndex& index, int role) const diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 4a3ee9922e..8a0797c583 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -528,6 +528,10 @@ void ResourceFolderModel::saveColumns(QTreeView* tree) void ResourceFolderModel::loadColumns(QTreeView* tree) { + for (auto i = 0; i < m_columnsHiddenByDefault.size(); ++i) { + tree->setColumnHidden(i, m_columnsHiddenByDefault[i]); + } + auto const setting_name = QString("UI/%1_Page/Columns").arg(id()); auto setting = (m_instance->settings()->contains(setting_name)) ? m_instance->settings()->getSetting(setting_name) : m_instance->settings()->registerSetting(setting_name); diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index b3f6d9a589..77bece6366 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -203,6 +203,7 @@ class ResourceFolderModel : public QAbstractListModel { QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified") }; QList m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive }; QList m_columnsHideable = { false, false, true }; + QList m_columnsHiddenByDefault = { false, false, false }; QDir m_dir; BaseInstance* m_instance; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 693b8af058..3fcbca4ce2 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -55,6 +55,7 @@ ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstanc m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, QHeaderView::Interactive }; m_columnsHideable = { false, true, false, true, true }; + m_columnsHiddenByDefault = { false, false, false, false, false }; } QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index f210501c76..1a35a3795a 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -49,6 +49,7 @@ TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE }; m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive }; m_columnsHideable = { false, true, false, true }; + m_columnsHiddenByDefault = { false, false, false, false }; } Task* TexturePackFolderModel::createUpdateTask() From e3af4f9a7892d90f3da9432c686b2e4f211fd499 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 22 Nov 2023 20:34:12 +0200 Subject: [PATCH 0099/2054] Fixed playtime recording Signed-off-by: Trial97 --- launcher/BaseInstance.cpp | 11 +++++++---- launcher/BaseInstance.h | 1 + launcher/minecraft/launch/LauncherPartLaunch.cpp | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 33dc3f7416..bb9e5223a8 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -267,13 +267,18 @@ void BaseInstance::setRunning(bool running) m_isRunning = running; - if (!m_settings->get("RecordGameTime").toBool()) { - emit runningStatusChanged(running); + emit runningStatusChanged(running); +} + +void BaseInstance::setMinecraftRunning(bool running) +{ + if (!settings()->get("RecordGameTime").toBool()) { return; } if (running) { m_timeStarted = QDateTime::currentDateTime(); + setLastLaunch(m_timeStarted.toMSecsSinceEpoch()); } else { QDateTime timeEnded = QDateTime::currentDateTime(); @@ -283,8 +288,6 @@ void BaseInstance::setRunning(bool running) emit propertiesChanged(this); } - - emit runningStatusChanged(running); } int64_t BaseInstance::totalTimePlayed() const diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index f4ed9113ce..499ec7866e 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -104,6 +104,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_thisshowMainWindow(); m_parent->setPid(-1); + m_parent->instance()->setMinecraftRunning(false); // if the exit code wasn't 0, report this as a crash auto exitCode = m_process.exitCode(); if (exitCode != 0) { @@ -228,7 +229,6 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state) case LoggedProcess::Running: emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::Launcher); m_parent->setPid(m_process.processId()); - m_parent->instance()->setLastLaunch(); // send the launch script to the launcher part m_process.write(m_launchScript.toUtf8()); @@ -248,6 +248,7 @@ void LauncherPartLaunch::setWorkingDirectory(const QString& wd) void LauncherPartLaunch::proceed() { if (mayProceed) { + m_parent->instance()->setMinecraftRunning(true); QString launchString("launch\n"); m_process.write(launchString.toUtf8()); mayProceed = false; From ac223a29ef61643a2313914d180801ce6dcae139 Mon Sep 17 00:00:00 2001 From: deadmeu Date: Sun, 23 Apr 2023 00:29:49 +1000 Subject: [PATCH 0100/2054] refactor: shorten desktop entry comment field Signed-off-by: deadmeu --- program_info/org.prismlauncher.PrismLauncher.desktop.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program_info/org.prismlauncher.PrismLauncher.desktop.in b/program_info/org.prismlauncher.PrismLauncher.desktop.in index f08f2ba430..0995c7c2ab 100644 --- a/program_info/org.prismlauncher.PrismLauncher.desktop.in +++ b/program_info/org.prismlauncher.PrismLauncher.desktop.in @@ -1,7 +1,7 @@ [Desktop Entry] Version=1.0 Name=Prism Launcher -Comment=A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once. +Comment=Discover, manage, and play Minecraft instances Type=Application Terminal=false Exec=@Launcher_APP_BINARY_NAME@ From 1a0fd5f9932574c95d0591d4d6d1d64093613f83 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 25 Nov 2023 00:18:10 +0200 Subject: [PATCH 0101/2054] Added mod reinstall dialog Signed-off-by: Trial97 --- .../ui/dialogs/ResourceDownloadDialog.cpp | 15 +++++ launcher/ui/dialogs/ResourceDownloadDialog.h | 2 + launcher/ui/pages/instance/ModFolderPage.cpp | 63 ++++++++++++++++++- launcher/ui/pages/instance/ModFolderPage.h | 1 + launcher/ui/pages/modplatform/ModModel.cpp | 10 +++ launcher/ui/pages/modplatform/ModModel.h | 1 + launcher/ui/pages/modplatform/ModPage.cpp | 12 +++- launcher/ui/pages/modplatform/ResourceModel.h | 2 + .../ui/pages/modplatform/ResourcePage.cpp | 60 ++++++++++++++++-- launcher/ui/pages/modplatform/ResourcePage.h | 4 ++ launcher/ui/widgets/PageContainer.h | 3 + 11 files changed, 165 insertions(+), 8 deletions(-) diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 1431ea92ce..5d49c0dfa5 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -356,4 +356,19 @@ QList ShaderPackDownloadDialog::getPages() return pages; } +void ModDownloadDialog::setModMetadata(std::shared_ptr meta) +{ + switch (meta->provider) { + case ModPlatform::ResourceProvider::MODRINTH: + selectPage(Modrinth::id()); + break; + case ModPlatform::ResourceProvider::FLAME: + selectPage(Flame::id()); + break; + } + m_container->hidePageList(); + m_buttons.hide(); + auto page = selectedPage(); + page->openProject(meta->project_id); +} } // namespace ResourceDownload diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index a6efca1381..7a0d6e895e 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -107,6 +107,8 @@ class ModDownloadDialog final : public ResourceDownloadDialog { QList getPages() override; GetModDependenciesTask::Ptr getModDependenciesTask() override; + void setModMetadata(std::shared_ptr); + private: BaseInstance* m_instance; }; diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index ce1cc3a90c..e33bcfc36e 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -40,6 +40,7 @@ #include "ui_ExternalResourcesPage.h" #include +#include #include #include #include @@ -121,10 +122,16 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr ui->actionsToolbar->addAction(ui->actionVisitItemPage); connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages); + auto changeVersion = new QAction(tr("Reinstall")); + changeVersion->setToolTip(tr("Reinstall mod")); + changeVersion->setEnabled(false); + ui->actionsToolbar->insertActionAfter(ui->actionVisitItemPage, changeVersion); + connect(changeVersion, &QAction::triggered, this, &ModFolderPage::changeModVersion); + auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); }; connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, - [this, check_allow_update, actionRemoveItemMetadata] { + [this, check_allow_update, actionRemoveItemMetadata, changeVersion] { ui->actionUpdateItem->setEnabled(check_allow_update()); auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); @@ -134,11 +141,12 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr if (selected <= 1) { ui->actionVisitItemPage->setText(tr("Visit mod's page")); ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page")); - } else { ui->actionVisitItemPage->setText(tr("Visit mods' pages")); ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods")); } + + changeVersion->setEnabled(mods_list.length() == 1 && mods_list[0]->metadata() != nullptr); ui->actionVisitItemPage->setEnabled(selected != 0); actionRemoveItemMetadata->setEnabled(selected != 0); }); @@ -372,3 +380,54 @@ void ModFolderPage::deleteModMetadata() m_model->deleteModsMetadata(selection); } + +void ModFolderPage::changeModVersion() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + auto profile = static_cast(m_instance)->getPackProfile(); + if (!profile->getModLoaders().has_value()) { + QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); + return; + } + if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!")); + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto mods_list = m_model->selectedMods(selection); + if (mods_list.length() != 1 || mods_list[0]->metadata() == nullptr) + return; + + ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance); + mdownload.setModMetadata((*mods_list.begin())->metadata()); + if (mdownload.exec()) { + auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + + tasks->deleteLater(); + }); + + for (auto& task : mdownload.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 4672350c66..9aa2fdbb12 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -66,6 +66,7 @@ class ModFolderPage : public ExternalResourcesPage { void installMods(); void updateMods(bool includeDeps = false); void visitModPages(); + void changeModVersion(); protected: std::shared_ptr m_model; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index c628f74acd..e7012c095a 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -79,4 +79,14 @@ bool ModModel::isPackInstalled(ModPlatform::IndexedPack::Ptr pack) const }); } +QVariant ModModel:: getInstalledPackVersion(ModPlatform::IndexedPack::Ptr pack) const +{ + auto allMods = static_cast(m_base_instance).loaderModList()->allMods(); + for (auto mod : allMods) { + if (auto meta = mod->metadata(); meta && meta->provider == pack->provider && meta->project_id == pack->addonId) { + return meta->version(); + } + } + return {}; +} } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index dd187aa8db..36540bb030 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -35,6 +35,7 @@ class ModModel : public ResourceModel { virtual ModPlatform::IndexedVersion loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) = 0; void setFilter(std::shared_ptr filter) { m_filter = filter; } + virtual QVariant getInstalledPackVersion(ModPlatform::IndexedPack::Ptr) const override; public slots: ResourceAPI::SearchArgs createSearchArguments() override; diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index d6cc1fdcc7..0ac08d4dd8 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -120,6 +120,8 @@ void ModPage::updateVersionList() auto current_pack = getCurrentPack(); if (!current_pack) return; + auto installedVersion = m_model->getInstalledPackVersion(current_pack); + auto installedIndex = -1; for (int i = 0; i < current_pack->versions.size(); i++) { auto version = current_pack->versions[i]; bool valid = false; @@ -129,13 +131,19 @@ void ModPage::updateVersionList() break; } } - + QString flag; + if (installedIndex == -1 && installedVersion.isValid() && installedVersion == version.fileId) { + flag = tr("[installed]"); + installedIndex = i; + } // Only add the version if it's valid or using the 'Any' filter, but never if the version is opted out if ((valid || m_filter->versions.empty()) && !optedOut(version)) { auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : ""; - m_ui->versionSelectionBox->addItem(QString("%1%2").arg(version.version, release_type), QVariant(i)); + m_ui->versionSelectionBox->addItem(QString("%1%2%3").arg(version.version, release_type, flag), QVariant(i)); } } + if (installedIndex != -1) + m_ui->versionSelectionBox->setCurrentIndex(installedIndex); if (m_ui->versionSelectionBox->count() == 0) { m_ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :(")); diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 12db49080f..97f9e36b47 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -55,6 +55,8 @@ class ResourceModel : public QAbstractListModel { [[nodiscard]] auto getSortingMethods() const { return m_api->getSortingMethods(); } + virtual QVariant getInstalledPackVersion(ModPlatform::IndexedPack::Ptr) const { return {}; } + public slots: void fetchMore(const QModelIndex& parent) override; // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 44a91003d6..00bd7548a4 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -260,7 +260,9 @@ void ResourcePage::updateVersionList() m_ui->versionSelectionBox->clear(); m_ui->versionSelectionBox->blockSignals(false); - if (current_pack) + if (current_pack) { + auto installedVersion = m_model->getInstalledPackVersion(current_pack); + for (int i = 0; i < current_pack->versions.size(); i++) { auto& version = current_pack->versions[i]; if (optedOut(version)) @@ -269,9 +271,10 @@ void ResourcePage::updateVersionList() auto release_type = current_pack->versions[i].version_type.isValid() ? QString(" [%1]").arg(current_pack->versions[i].version_type.toString()) : ""; - m_ui->versionSelectionBox->addItem(current_pack->versions[i].version, QVariant(i)); - } + m_ui->versionSelectionBox->addItem(QString("%1%2").arg(version.version, release_type), QVariant(i)); + } + } if (m_ui->versionSelectionBox->count() == 0) { m_ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1)); m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :(")); @@ -388,7 +391,7 @@ void ResourcePage::openUrl(const QUrl& url) } } - if (!page.isNull()) { + if (!page.isNull() && !m_do_not_jump_to_mod) { const QString slug = match.captured(1); // ensure the user isn't opening the same mod @@ -432,4 +435,53 @@ void ResourcePage::openUrl(const QUrl& url) QDesktopServices::openUrl(url); } +void ResourcePage::openProject(QVariant projectID) +{ + m_ui->sortByBox->hide(); + m_ui->searchButton->hide(); + m_ui->searchEdit->hide(); + m_ui->resourceFilterButton->hide(); + m_ui->packView->hide(); + m_ui->resourceSelectionButton->hide(); + m_do_not_jump_to_mod = true; + + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + + auto okBtn = buttonBox->button(QDialogButtonBox::Ok); + okBtn->setDefault(true); + okBtn->setAutoDefault(true); + okBtn->setText(tr("Reinstall")); + okBtn->setShortcut(tr("Ctrl+Return")); + okBtn->setEnabled(false); + + auto cancelBtn = buttonBox->button(QDialogButtonBox::Cancel); + cancelBtn->setDefault(false); + cancelBtn->setAutoDefault(false); + + connect(okBtn, &QPushButton::clicked, this, [this] { + onResourceSelected(); + m_parent_dialog->accept(); + }); + + connect(cancelBtn, &QPushButton::clicked, m_parent_dialog, &ResourceDownloadDialog::reject); + m_ui->gridLayout_4->addWidget(buttonBox, 1, 2); + + auto jump = [this, okBtn] { + for (int row = 0; row < m_model->rowCount({}); row++) { + const QModelIndex index = m_model->index(row); + m_ui->packView->setCurrentIndex(index); + okBtn->setEnabled(true); + return; + } + m_ui->packDescription->setText(tr("The resource was not found")); + }; + + m_ui->searchEdit->setText("#" + projectID.toString()); + triggerSearch(); + + if (m_model->hasActiveSearchJob()) + connect(m_model->activeSearchJob().get(), &Task::finished, jump); + else + jump(); +} } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 235b44412c..212c83ea53 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -83,6 +83,8 @@ class ResourcePage : public QWidget, public BasePage { QList selectedPacks() { return m_model->selectedPacks(); } bool hasSelectedPacks() { return !(m_model->selectedPacks().isEmpty()); } + virtual void openProject(QVariant projectID); + protected slots: virtual void triggerSearch() {} @@ -118,6 +120,8 @@ class ResourcePage : public QWidget, public BasePage { // Used to do instant searching with a delay to cache quick changes QTimer m_search_timer; + + bool m_do_not_jump_to_mod = false; }; } // namespace ResourceDownload diff --git a/launcher/ui/widgets/PageContainer.h b/launcher/ui/widgets/PageContainer.h index 05be1c3a5c..ab4444c99a 100644 --- a/launcher/ui/widgets/PageContainer.h +++ b/launcher/ui/widgets/PageContainer.h @@ -36,6 +36,7 @@ #pragma once +#include #include #include @@ -86,6 +87,8 @@ class PageContainer : public QWidget, public BasePageContainer { void changeEvent(QEvent*) override; + void hidePageList() { m_pageList->hide(); } + private: void createUI(); void retranslate(); From f4e098f7f542186c907aae2f9e634e36a1aae333 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 25 Nov 2023 09:36:55 +0200 Subject: [PATCH 0102/2054] Formated file Signed-off-by: Trial97 --- launcher/ui/pages/modplatform/ModModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index e7012c095a..02fc8a3dde 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -79,7 +79,7 @@ bool ModModel::isPackInstalled(ModPlatform::IndexedPack::Ptr pack) const }); } -QVariant ModModel:: getInstalledPackVersion(ModPlatform::IndexedPack::Ptr pack) const +QVariant ModModel::getInstalledPackVersion(ModPlatform::IndexedPack::Ptr pack) const { auto allMods = static_cast(m_base_instance).loaderModList()->allMods(); for (auto mod : allMods) { From 6f6b6312a55d970c6d7d809305151e874a910f79 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 27 Nov 2023 09:39:31 +0200 Subject: [PATCH 0103/2054] Moved the button up Signed-off-by: Trial97 --- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 1 + launcher/ui/pages/instance/ModFolderPage.cpp | 6 +++--- launcher/ui/pages/modplatform/ModPage.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 5d49c0dfa5..47b58a6ed6 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -366,6 +366,7 @@ void ModDownloadDialog::setModMetadata(std::shared_ptr meta selectPage(Flame::id()); break; } + setWindowTitle(tr("Change %1 version").arg(meta->name)); m_container->hidePageList(); m_buttons.hide(); auto page = selectedPage(); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index e33bcfc36e..d46bc5652d 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -122,10 +122,10 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr ui->actionsToolbar->addAction(ui->actionVisitItemPage); connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages); - auto changeVersion = new QAction(tr("Reinstall")); - changeVersion->setToolTip(tr("Reinstall mod")); + auto changeVersion = new QAction(tr("Change Version")); + changeVersion->setToolTip(tr("Change mod version")); changeVersion->setEnabled(false); - ui->actionsToolbar->insertActionAfter(ui->actionVisitItemPage, changeVersion); + ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, changeVersion); connect(changeVersion, &QAction::triggered, this, &ModFolderPage::changeModVersion); auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); }; diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 0ac08d4dd8..ccbe5b377d 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -133,7 +133,7 @@ void ModPage::updateVersionList() } QString flag; if (installedIndex == -1 && installedVersion.isValid() && installedVersion == version.fileId) { - flag = tr("[installed]"); + flag = QString(" [%1]").arg(tr("installed", "Mod version select box")); installedIndex = i; } // Only add the version if it's valid or using the 'Any' filter, but never if the version is opted out From 284e536e8144b3fc7ff4842c39e0d7231f8e4b93 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 28 Nov 2023 12:30:13 +0000 Subject: [PATCH 0104/2054] Data pack management Signed-off-by: TheKodeToad --- launcher/Application.cpp | 5 + launcher/CMakeLists.txt | 7 + launcher/minecraft/mod/DataPack.cpp | 47 +++++ launcher/minecraft/mod/DataPack.h | 17 ++ .../minecraft/mod/DataPackFolderModel.cpp | 191 ++++++++++++++++++ launcher/minecraft/mod/DataPackFolderModel.h | 62 ++++++ .../mod/tasks/LocalDataPackParseTask.cpp | 121 +++++++++++ .../mod/tasks/LocalDataPackParseTask.h | 4 + launcher/modplatform/ModIndex.h | 2 +- launcher/modplatform/flame/FlameAPI.h | 1 + launcher/modplatform/modrinth/ModrinthAPI.h | 3 + .../ui/dialogs/ResourceDownloadDialog.cpp | 23 ++- launcher/ui/dialogs/ResourceDownloadDialog.h | 18 ++ launcher/ui/pages/instance/DataPackPage.cpp | 82 ++++++++ launcher/ui/pages/instance/DataPackPage.h | 39 ++++ .../ui/pages/instance/ExternalResourcesPage.h | 1 + launcher/ui/pages/instance/WorldListPage.cpp | 34 +++- launcher/ui/pages/instance/WorldListPage.h | 6 +- launcher/ui/pages/instance/WorldListPage.ui | 8 +- .../ui/pages/modplatform/DataPackModel.cpp | 48 +++++ launcher/ui/pages/modplatform/DataPackModel.h | 44 ++++ .../ui/pages/modplatform/DataPackPage.cpp | 48 +++++ launcher/ui/pages/modplatform/DataPackPage.h | 51 +++++ .../modrinth/ModrinthResourceModels.cpp | 23 +++ .../modrinth/ModrinthResourceModels.h | 19 ++ .../modrinth/ModrinthResourcePages.cpp | 22 ++ .../modrinth/ModrinthResourcePages.h | 24 +++ launcher/ui/widgets/InfoFrame.cpp | 6 + launcher/ui/widgets/InfoFrame.h | 2 + 29 files changed, 943 insertions(+), 15 deletions(-) create mode 100644 launcher/minecraft/mod/DataPackFolderModel.cpp create mode 100644 launcher/minecraft/mod/DataPackFolderModel.h create mode 100644 launcher/ui/pages/instance/DataPackPage.cpp create mode 100644 launcher/ui/pages/instance/DataPackPage.h create mode 100644 launcher/ui/pages/modplatform/DataPackModel.cpp create mode 100644 launcher/ui/pages/modplatform/DataPackModel.h create mode 100644 launcher/ui/pages/modplatform/DataPackPage.cpp create mode 100644 launcher/ui/pages/modplatform/DataPackPage.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index be252f1c5a..2c3189a900 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -683,6 +683,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("RPDownloadGeometry", ""); m_settings->registerSetting("TPDownloadGeometry", ""); m_settings->registerSetting("ShaderDownloadGeometry", ""); + m_settings->registerSetting("DataPackDownloadGeometry", ""); + + // data pack window + // in future, more pages may be added - so this name is chosen to avoid needing migration + m_settings->registerSetting("WorldManagementGeometry", ""); // HACK: This code feels so stupid is there a less stupid way of doing this? { diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 99acf8fc57..36451ab8a3 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -333,6 +333,8 @@ set(MINECRAFT_SOURCES minecraft/mod/ResourceFolderModel.cpp minecraft/mod/DataPack.h minecraft/mod/DataPack.cpp + minecraft/mod/DataPackFolderModel.h + minecraft/mod/DataPackFolderModel.cpp minecraft/mod/ResourcePack.h minecraft/mod/ResourcePack.cpp minecraft/mod/ResourcePackFolderModel.h @@ -861,6 +863,8 @@ SET(LAUNCHER_SOURCES ui/pages/instance/VersionPage.h ui/pages/instance/ManagedPackPage.cpp ui/pages/instance/ManagedPackPage.h + ui/pages/instance/DataPackPage.h + ui/pages/instance/DataPackPage.cpp ui/pages/instance/TexturePackPage.h ui/pages/instance/TexturePackPage.cpp ui/pages/instance/ResourcePackPage.h @@ -930,6 +934,9 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/ShaderPackPage.cpp ui/pages/modplatform/ShaderPackModel.cpp + ui/pages/modplatform/DataPackPage.cpp + ui/pages/modplatform/DataPackModel.cpp + ui/pages/modplatform/atlauncher/AtlFilterModel.cpp ui/pages/modplatform/atlauncher/AtlFilterModel.h ui/pages/modplatform/atlauncher/AtlListModel.cpp diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index fc2d3f68b6..8c7d1b086a 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -25,7 +25,9 @@ #include #include +#include "MTPixmapCache.h" #include "Version.h" +#include "minecraft/mod/tasks/LocalDataPackParseTask.h" // Values taken from: // https://minecraft.wiki/w/Tutorials/Creating_a_data_pack#%22pack_format%22 @@ -56,6 +58,51 @@ void DataPack::setDescription(QString new_description) m_description = new_description; } +void DataPack::setImage(QImage new_image) const +{ + QMutexLocker locker(&m_data_lock); + + Q_ASSERT(!new_image.isNull()); + + if (m_pack_image_cache_key.key.isValid()) + PixmapCache::instance().remove(m_pack_image_cache_key.key); + + // scale the image to avoid flooding the pixmapcache + auto pixmap = + QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); + + m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap); + m_pack_image_cache_key.was_ever_used = true; + + // This can happen if the pixmap is too big to fit in the cache :c + if (!m_pack_image_cache_key.key.isValid()) { + qWarning() << "Could not insert a image cache entry! Ignoring it."; + m_pack_image_cache_key.was_ever_used = false; + } +} + +QPixmap DataPack::image(QSize size, Qt::AspectRatioMode mode) const +{ + QPixmap cached_image; + if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) { + if (size.isNull()) + return cached_image; + return cached_image.scaled(size, mode, Qt::SmoothTransformation); + } + + // No valid image we can get + if (!m_pack_image_cache_key.was_ever_used) { + return {}; + } else { + qDebug() << "Resource Pack" << name() << "Had it's image evicted from the cache. reloading..."; + PixmapCache::markCacheMissByEviciton(); + } + + // Imaged got evicted from the cache. Re-process it and retry. + DataPackUtils::processPackPNG(*this); + return image(size); +} + std::pair DataPack::compatibleVersions() const { if (!s_pack_format_versions.contains(m_pack_format)) { diff --git a/launcher/minecraft/mod/DataPack.h b/launcher/minecraft/mod/DataPack.h index b3787b238d..3eec4e0e09 100644 --- a/launcher/minecraft/mod/DataPack.h +++ b/launcher/minecraft/mod/DataPack.h @@ -24,6 +24,7 @@ #include "Resource.h" #include +#include class Version; @@ -48,12 +49,18 @@ class DataPack : public Resource { /** Gets the description of the data pack. */ [[nodiscard]] QString description() const { return m_description; } + /** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */ + [[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const; + /** Thread-safe. */ void setPackFormat(int new_format_id); /** Thread-safe. */ void setDescription(QString new_description); + /** Thread-safe. */ + void setImage(QImage new_image) const; + bool valid() const override; [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair override; @@ -70,4 +77,14 @@ class DataPack : public Resource { /** The data pack's description, as defined in the pack.mcmeta file. */ QString m_description; + + /** The data pack's image file cache key, for access in the QPixmapCache global instance. + * + * The 'was_ever_used' state simply identifies whether the key was never inserted on the cache (true), + * so as to tell whether a cache entry is inexistent or if it was just evicted from the cache. + */ + struct { + QPixmapCache::Key key; + bool was_ever_used = false; + } mutable m_pack_image_cache_key; }; diff --git a/launcher/minecraft/mod/DataPackFolderModel.cpp b/launcher/minecraft/mod/DataPackFolderModel.cpp new file mode 100644 index 0000000000..9efb372943 --- /dev/null +++ b/launcher/minecraft/mod/DataPackFolderModel.cpp @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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 "DataPackFolderModel.h" +#include +#include + +#include +#include + +#include "Application.h" +#include "Version.h" + +#include "minecraft/mod/tasks/BasicFolderLoadTask.h" +#include "minecraft/mod/tasks/LocalDataPackParseTask.h" +#include "minecraft/mod/tasks/LocalResourcePackParseTask.h" + +DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) +{ + m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" }); + m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; + m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive }; + m_columnsHideable = { false, true, false, true, true }; +} + +QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const +{ + if (!validateIndex(index)) + return {}; + + int row = index.row(); + int column = index.column(); + + switch (role) { + case Qt::DisplayRole: + switch (column) { + case NameColumn: + return m_resources[row]->name(); + case PackFormatColumn: { + auto resource = at(row); + auto pack_format = resource->packFormat(); + if (pack_format == 0) + return tr("Unrecognized"); + + auto version_bounds = resource->compatibleVersions(); + if (version_bounds.first.toString().isEmpty()) + return QString::number(pack_format); + + return QString("%1 (%2 - %3)") + .arg(QString::number(pack_format), version_bounds.first.toString(), version_bounds.second.toString()); + } + case DateColumn: + return m_resources[row]->dateTimeChanged(); + + default: + return {}; + } + case Qt::DecorationRole: { + if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) + return APPLICATION->getThemedIcon("status-yellow"); + if (column == ImageColumn) { + return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); + } + return {}; + } + case Qt::ToolTipRole: { + if (column == PackFormatColumn) { + //: The string being explained by this is in the format: ID (Lower version - Upper version) + return tr("The data pack format ID, as well as the Minecraft versions it was designed for."); + } + if (column == NameColumn) { + if (at(row)->isSymLinkUnder(instDirPath())) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." + "\nCanonical Path: %1") + .arg(at(row)->fileinfo().canonicalFilePath()); + ; + } + if (at(row)->isMoreThanOneHardLink()) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); + } + } + return m_resources[row]->internal_id(); + } + case Qt::SizeHintRole: + if (column == ImageColumn) { + return QSize(32, 32); + } + return {}; + case Qt::CheckStateRole: + switch (column) { + case ActiveColumn: + return at(row)->enabled() ? Qt::Checked : Qt::Unchecked; + default: + return {}; + } + default: + return {}; + } +} + +QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const +{ + switch (role) { + case Qt::DisplayRole: + switch (section) { + case ActiveColumn: + case NameColumn: + case PackFormatColumn: + case DateColumn: + case ImageColumn: + return columnNames().at(section); + default: + return {}; + } + + case Qt::ToolTipRole: + switch (section) { + case ActiveColumn: + return tr("Is the data pack enabled? (Only valid for ZIPs)"); + case NameColumn: + return tr("The name of the data pack."); + case PackFormatColumn: + //: The string being explained by this is in the format: ID (Lower version - Upper version) + return tr("The data pack format ID, as well as the Minecraft versions it was designed for."); + case DateColumn: + return tr("The date and time this data pack was last changed (or added)."); + default: + return {}; + } + case Qt::SizeHintRole: + if (section == ImageColumn) { + return QSize(64, 0); + } + return {}; + default: + return {}; + } +} + +int DataPackFolderModel::columnCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : NUM_COLUMNS; +} + +Task* DataPackFolderModel::createUpdateTask() +{ + return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared(entry); }); +} + +Task* DataPackFolderModel::createParseTask(Resource& resource) +{ + return new LocalDataPackParseTask(m_next_resolution_ticket, static_cast(resource)); +} diff --git a/launcher/minecraft/mod/DataPackFolderModel.h b/launcher/minecraft/mod/DataPackFolderModel.h new file mode 100644 index 0000000000..1c2204af96 --- /dev/null +++ b/launcher/minecraft/mod/DataPackFolderModel.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022 flowln + * Copyright (C) 2023 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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 "ResourceFolderModel.h" + +#include "DataPack.h" +#include "ResourcePack.h" + +class DataPackFolderModel : public ResourceFolderModel { + Q_OBJECT + public: + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, NUM_COLUMNS }; + + explicit DataPackFolderModel(const QString& dir, BaseInstance* instance); + + virtual QString id() const override { return "datapacks"; } + + [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + + [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + [[nodiscard]] int columnCount(const QModelIndex& parent) const override; + + [[nodiscard]] Task* createUpdateTask() override; + [[nodiscard]] Task* createParseTask(Resource&) override; + + RESOURCE_HELPERS(DataPack) +}; diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp index 82f6b9df90..e5148e5be7 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp @@ -81,6 +81,29 @@ bool processFolder(DataPack& pack, ProcessingLevel level) return true; // only need basic info already checked } + auto png_invalid = [&pack]() { + qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png"; + return true; // the png is optional + }; + + QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); + if (image_file_info.exists() && image_file_info.isFile()) { + QFile pack_png_file(image_file_info.filePath()); + if (!pack_png_file.open(QIODevice::ReadOnly)) + return png_invalid(); // can't open pack.png file + + auto data = pack_png_file.readAll(); + + bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); + + pack_png_file.close(); + if (!pack_png_result) { + return png_invalid(); // pack.png invalid + } + } else { + return png_invalid(); // pack.png does not exists or is not a valid file. + } + return true; // all tests passed } @@ -128,6 +151,32 @@ bool processZIP(DataPack& pack, ProcessingLevel level) return true; // only need basic info already checked } + auto png_invalid = [&pack]() { + qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png"; + return true; // the png is optional + }; + + if (zip.setCurrentFile("pack.png")) { + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open file in zip."; + zip.close(); + return png_invalid(); + } + + auto data = file.readAll(); + + bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); + + file.close(); + zip.close(); + if (!pack_png_result) { + return png_invalid(); // pack.png invalid + } + } else { + zip.close(); + return png_invalid(); // could not set pack.mcmeta as current file. + } + zip.close(); return true; @@ -149,6 +198,78 @@ bool processMCMeta(DataPack& pack, QByteArray&& raw_data) return true; } +bool processPackPNG(const DataPack& pack, QByteArray&& raw_data) +{ + auto img = QImage::fromData(raw_data); + if (!img.isNull()) { + pack.setImage(img); + } else { + qWarning() << "Failed to parse pack.png."; + return false; + } + return true; +} + +bool processPackPNG(const DataPack& pack) +{ + auto png_invalid = [&pack]() { + qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png"; + return false; + }; + + switch (pack.type()) { + case ResourceType::FOLDER: { + QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); + if (image_file_info.exists() && image_file_info.isFile()) { + QFile pack_png_file(image_file_info.filePath()); + if (!pack_png_file.open(QIODevice::ReadOnly)) + return png_invalid(); // can't open pack.png file + + auto data = pack_png_file.readAll(); + + bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); + + pack_png_file.close(); + if (!pack_png_result) { + return png_invalid(); // pack.png invalid + } + } else { + return png_invalid(); // pack.png does not exists or is not a valid file. + } + return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740 + } + case ResourceType::ZIPFILE: { + QuaZip zip(pack.fileinfo().filePath()); + if (!zip.open(QuaZip::mdUnzip)) + return false; // can't open zip file + + QuaZipFile file(&zip); + if (zip.setCurrentFile("pack.png")) { + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open file in zip."; + zip.close(); + return png_invalid(); + } + + auto data = file.readAll(); + + bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); + + file.close(); + if (!pack_png_result) { + return png_invalid(); // pack.png invalid + } + } else { + return png_invalid(); // could not set pack.mcmeta as current file. + } + return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740 + } + default: + qWarning() << "Invalid type for data pack parse task!"; + return false; + } +} + bool validate(QFileInfo file) { DataPack dp{ file }; diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h index 12fd8c82c6..4a83437ca4 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h @@ -38,6 +38,10 @@ bool processZIP(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processMCMeta(DataPack& pack, QByteArray&& raw_data); +bool processPackPNG(const DataPack& pack, QByteArray&& raw_data); + +/// processes ONLY the pack.png (rest of the pack may be invalid) +bool processPackPNG(const DataPack& pack); /** Checks whether a file is valid as a data pack or not. */ bool validate(QFileInfo file); diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 72294c3994..8b3fd15f84 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -36,7 +36,7 @@ Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) enum class ResourceProvider { MODRINTH, FLAME }; -enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK }; +enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK, DATA_PACK }; enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN }; diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index e22d8f0d8f..449a1625a8 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -37,6 +37,7 @@ class FlameAPI : public NetworkResourceAPI { case ModPlatform::ResourceType::MOD: return 6; case ModPlatform::ResourceType::RESOURCE_PACK: + case ModPlatform::ResourceType::DATA_PACK: return 12; case ModPlatform::ResourceType::SHADER_PACK: return 6552; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index d0f0811b2d..d21d37d7ef 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -61,6 +61,7 @@ class ModrinthAPI : public NetworkResourceAPI { { switch (type) { case ModPlatform::ResourceType::MOD: + case ModPlatform::ResourceType::DATA_PACK: return "mod"; case ModPlatform::ResourceType::RESOURCE_PACK: return "resourcepack"; @@ -81,6 +82,8 @@ class ModrinthAPI : public NetworkResourceAPI { facets_list.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value()))); if (args.versions.has_value()) facets_list.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value()))); + if (args.type == ModPlatform::ResourceType::DATA_PACK) + facets_list.append("[\"categories:datapack\"]"); facets_list.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type))); return QString("[%1]").arg(facets_list.join(',')); diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 1431ea92ce..53c740d94a 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -57,7 +57,7 @@ ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, const std::share { setObjectName(QStringLiteral("ResourceDownloadDialog")); - resize(std::max(0.5 * parent->width(), 400.0), std::max(0.75 * parent->height(), 400.0)); + resize(static_cast(std::max(0.5 * parent->width(), 400.0)), static_cast(std::max(0.75 * parent->height(), 400.0))); setWindowIcon(APPLICATION->getThemedIcon("new")); @@ -356,4 +356,25 @@ QList ShaderPackDownloadDialog::getPages() return pages; } +DataPackDownloadDialog::DataPackDownloadDialog(QWidget* parent, + const std::shared_ptr& data_packs, + BaseInstance* instance) + : ResourceDownloadDialog(parent, data_packs), m_instance(instance) +{ + setWindowTitle(dialogTitle()); + + initializeContainer(); + connectButtons(); + + if (!geometrySaveKey().isEmpty()) + restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toByteArray())); +} + +QList DataPackDownloadDialog::getPages() +{ + QList pages; + pages.append(ModrinthDataPackPage::create(this, *m_instance)); + return pages; +} + } // namespace ResourceDownload diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index e9d2cfbe62..f5599041da 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -25,6 +25,7 @@ #include #include "QObjectPtr.h" +#include "minecraft/mod/DataPackFolderModel.h" #include "minecraft/mod/tasks/GetModDependenciesTask.h" #include "modplatform/ModIndex.h" #include "ui/pages/BasePageProvider.h" @@ -166,4 +167,21 @@ class ShaderPackDownloadDialog final : public ResourceDownloadDialog { BaseInstance* m_instance; }; +class DataPackDownloadDialog final : public ResourceDownloadDialog { + Q_OBJECT + + public: + explicit DataPackDownloadDialog(QWidget* parent, const std::shared_ptr& data_packs, BaseInstance* instance); + ~DataPackDownloadDialog() override = default; + + //: String that gets appended to the data pack download dialog title ("Download " + resourcesString()) + [[nodiscard]] QString resourcesString() const override { return tr("data packs"); } + [[nodiscard]] QString geometrySaveKey() const override { return "DataPackDownloadGeometry"; } + + QList getPages() override; + + private: + BaseInstance* m_instance; +}; + } // namespace ResourceDownload diff --git a/launcher/ui/pages/instance/DataPackPage.cpp b/launcher/ui/pages/instance/DataPackPage.cpp new file mode 100644 index 0000000000..f46e7528f6 --- /dev/null +++ b/launcher/ui/pages/instance/DataPackPage.cpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "DataPackPage.h" + +#include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/ProgressDialog.h" +#include "ui/dialogs/ResourceDownloadDialog.h" + +DataPackPage::DataPackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) + : ExternalResourcesPage(instance, model, parent) +{ + ui->actionDownloadItem->setText(tr("Download packs")); + ui->actionDownloadItem->setToolTip(tr("Download data packs from online platforms")); + ui->actionDownloadItem->setEnabled(true); + connect(ui->actionDownloadItem, &QAction::triggered, this, &DataPackPage::downloadDataPacks); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); + + ui->actionViewConfigs->setVisible(false); +} + +bool DataPackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) +{ + auto sourceCurrent = m_filterModel->mapToSource(current); + int row = sourceCurrent.row(); + auto& dp = static_cast(m_model->at(row)); + ui->frame->updateWithDataPack(dp); + + return true; +} + +void DataPackPage::downloadDataPacks() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + ResourceDownload::DataPackDownloadDialog mdownload(this, std::static_pointer_cast(m_model), m_instance); + if (mdownload.exec()) { + auto tasks = + new ConcurrentTask(this, "Download Data Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + + tasks->deleteLater(); + }); + + for (auto& task : mdownload.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} diff --git a/launcher/ui/pages/instance/DataPackPage.h b/launcher/ui/pages/instance/DataPackPage.h new file mode 100644 index 0000000000..039a9c40f4 --- /dev/null +++ b/launcher/ui/pages/instance/DataPackPage.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "ExternalResourcesPage.h" +#include "minecraft/mod/DataPackFolderModel.h" +#include "ui_ExternalResourcesPage.h" + +class DataPackPage : public ExternalResourcesPage { + Q_OBJECT + public: + explicit DataPackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent = 0); + + QString displayName() const override { return tr("Data packs"); } + QIcon icon() const override { return APPLICATION->getThemedIcon("datapacks"); } + QString id() const override { return "datapacks"; } + QString helpPage() const override { return "Data-packs"; } + bool shouldDisplay() const override { return true; } + + public slots: + bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; + void downloadDataPacks(); +}; diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h index d29be0fc3a..031935544b 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.h +++ b/launcher/ui/pages/instance/ExternalResourcesPage.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 587bb6ce6d..133957328b 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -57,6 +57,7 @@ #include "ui/GuiUtil.h" #include "Application.h" +#include "DataPackPage.h" class WorldListProxyModel : public QSortFilterProxyModel { Q_OBJECT @@ -82,7 +83,7 @@ class WorldListProxyModel : public QSortFilterProxyModel { } }; -WorldListPage::WorldListPage(BaseInstance* inst, std::shared_ptr worlds, QWidget* parent) +WorldListPage::WorldListPage(MinecraftInstance* inst, std::shared_ptr worlds, QWidget* parent) : QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds) { ui->setupUi(this); @@ -210,7 +211,7 @@ void WorldListPage::on_actionView_Folder_triggered() DesktopServices::openDirectory(m_worlds->dir().absolutePath(), true); } -void WorldListPage::on_actionDatapacks_triggered() +void WorldListPage::on_actionData_Packs_triggered() { QModelIndex index = getSelectedWorld(); @@ -218,12 +219,33 @@ void WorldListPage::on_actionDatapacks_triggered() return; } - if (!worldSafetyNagQuestion(tr("Open World Datapacks Folder"))) + if (!worldSafetyNagQuestion(tr("Manage Data Packs"))) return; - auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); + const QString fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); + const QString folder = FS::PathCombine(fullPath, "datapacks"); + + auto dialog = new QDialog(window()); + dialog->setWindowTitle(tr("Data packs for %1").arg(m_worlds->data(index, WorldList::NameRole).toString())); + dialog->setWindowModality(Qt::WindowModal); + + dialog->resize(static_cast(std::max(0.5 * window()->width(), 400.0)), + static_cast(std::max(0.75 * window()->height(), 400.0))); + dialog->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("DataPackDownloadGeometry").toByteArray())); + + auto layout = new QHBoxLayout(dialog); + auto page = new DataPackPage(m_inst, std::make_shared(folder, m_inst)); + page->setParent(dialog); // HACK: many pages extend QMainWindow; setting the parent manually prevents them from creating a window. + layout->addWidget(page); + dialog->setLayout(layout); + + connect(dialog, &QDialog::finished, this, [dialog, page] { + APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64()); + page->closed(); + }); - DesktopServices::openDirectory(FS::PathCombine(fullPath, "datapacks"), true); + dialog->show(); + page->opened(); } void WorldListPage::on_actionReset_Icon_triggered() @@ -336,7 +358,7 @@ void WorldListPage::worldChanged([[maybe_unused]] const QModelIndex& current, [[ ui->actionRemove->setEnabled(enable); ui->actionCopy->setEnabled(enable); ui->actionRename->setEnabled(enable); - ui->actionDatapacks->setEnabled(enable); + ui->actionData_Packs->setEnabled(enable); bool hasIcon = !index.data(WorldList::IconFileRole).isNull(); ui->actionReset_Icon->setEnabled(enable && hasIcon); } diff --git a/launcher/ui/pages/instance/WorldListPage.h b/launcher/ui/pages/instance/WorldListPage.h index 4f83002f4e..50c5d20f64 100644 --- a/launcher/ui/pages/instance/WorldListPage.h +++ b/launcher/ui/pages/instance/WorldListPage.h @@ -53,7 +53,7 @@ class WorldListPage : public QMainWindow, public BasePage { Q_OBJECT public: - explicit WorldListPage(BaseInstance* inst, std::shared_ptr worlds, QWidget* parent = 0); + explicit WorldListPage(MinecraftInstance* inst, std::shared_ptr worlds, QWidget* parent = 0); virtual ~WorldListPage(); virtual QString displayName() const override { return tr("Worlds"); } @@ -72,7 +72,7 @@ class WorldListPage : public QMainWindow, public BasePage { QMenu* createPopupMenu() override; protected: - BaseInstance* m_inst; + MinecraftInstance* m_inst; private: QModelIndex getSelectedWorld(); @@ -97,7 +97,7 @@ class WorldListPage : public QMainWindow, public BasePage { void on_actionRename_triggered(); void on_actionRefresh_triggered(); void on_actionView_Folder_triggered(); - void on_actionDatapacks_triggered(); + void on_actionData_Packs_triggered(); void on_actionReset_Icon_triggered(); void worldChanged(const QModelIndex& current, const QModelIndex& previous); void mceditState(LoggedProcess::State state); diff --git a/launcher/ui/pages/instance/WorldListPage.ui b/launcher/ui/pages/instance/WorldListPage.ui index d74dd07968..b30b691d35 100644 --- a/launcher/ui/pages/instance/WorldListPage.ui +++ b/launcher/ui/pages/instance/WorldListPage.ui @@ -85,7 +85,7 @@ - + @@ -140,12 +140,12 @@ Remove world icon to make the game re-generate it on next load.
    - + - Datapacks + Data Packs - Manage datapacks inside the world. + Manage data packs inside the world. diff --git a/launcher/ui/pages/modplatform/DataPackModel.cpp b/launcher/ui/pages/modplatform/DataPackModel.cpp new file mode 100644 index 0000000000..c17703d3cf --- /dev/null +++ b/launcher/ui/pages/modplatform/DataPackModel.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 flowln +// SPDX-FileCopyrightText: 2023 TheKodeToad +// +// SPDX-License-Identifier: GPL-3.0-only + +#include "DataPackModel.h" + +#include + +namespace ResourceDownload { + +DataPackResourceModel::DataPackResourceModel(BaseInstance const& base_inst, ResourceAPI* api) + : ResourceModel(api), m_base_instance(base_inst) +{} + +/******** Make data requests ********/ + +ResourceAPI::SearchArgs DataPackResourceModel::createSearchArguments() +{ + auto sort = getCurrentSortingMethodByIndex(); + return { ModPlatform::ResourceType::DATA_PACK, m_next_search_offset, m_search_term, sort }; +} + +ResourceAPI::VersionSearchArgs DataPackResourceModel::createVersionsArguments(QModelIndex& entry) +{ + auto& pack = m_packs[entry.row()]; + return { *pack }; +} + +ResourceAPI::ProjectInfoArgs DataPackResourceModel::createInfoArguments(QModelIndex& entry) +{ + auto& pack = m_packs[entry.row()]; + return { *pack }; +} + +void DataPackResourceModel::searchWithTerm(const QString& term, unsigned int sort) +{ + if (m_search_term == term && m_search_term.isNull() == term.isNull() && m_current_sort_index == sort) { + return; + } + + setSearchTerm(term); + m_current_sort_index = sort; + + refresh(); +} + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/DataPackModel.h b/launcher/ui/pages/modplatform/DataPackModel.h new file mode 100644 index 0000000000..4954b7350b --- /dev/null +++ b/launcher/ui/pages/modplatform/DataPackModel.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2023 flowln +// SPDX-FileCopyrightText: 2023 TheKodeToad +// +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include + +#include "BaseInstance.h" + +#include "modplatform/ModIndex.h" + +#include "ui/pages/modplatform/ResourceModel.h" + +class Version; + +namespace ResourceDownload { + +class DataPackResourceModel : public ResourceModel { + Q_OBJECT + + public: + DataPackResourceModel(BaseInstance const&, ResourceAPI*); + + /* Ask the API for more information */ + void searchWithTerm(const QString& term, unsigned int sort); + + void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&) override = 0; + void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) override = 0; + void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&) override = 0; + + public slots: + ResourceAPI::SearchArgs createSearchArguments() override; + ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; + ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; + + protected: + const BaseInstance& m_base_instance; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0; +}; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/DataPackPage.cpp b/launcher/ui/pages/modplatform/DataPackPage.cpp new file mode 100644 index 0000000000..84a777f3c6 --- /dev/null +++ b/launcher/ui/pages/modplatform/DataPackPage.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 flowln +// SPDX-FileCopyrightText: 2023 TheKodeToad +// +// SPDX-License-Identifier: GPL-3.0-only + +#include "DataPackPage.h" +#include "modplatform/ModIndex.h" +#include "ui_ResourcePage.h" + +#include "DataPackModel.h" + +#include "ui/dialogs/ResourceDownloadDialog.h" + +#include + +namespace ResourceDownload { + +DataPackResourcePage::DataPackResourcePage(DataPackDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) +{ + connect(m_ui->searchButton, &QPushButton::clicked, this, &DataPackResourcePage::triggerSearch); + connect(m_ui->packView, &QListView::doubleClicked, this, &DataPackResourcePage::onResourceSelected); +} + +/******** Callbacks to events in the UI (set up in the derived classes) ********/ + +void DataPackResourcePage::triggerSearch() +{ + m_ui->packView->clearSelection(); + m_ui->packDescription->clear(); + m_ui->versionSelectionBox->clear(); + + updateSelectionButton(); + + static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt()); + m_fetch_progress.watch(m_model->activeSearchJob().get()); +} + +QMap DataPackResourcePage::urlHandlers() const +{ + QMap map; + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/resourcepack\\/([^\\/]+)\\/?"), "modrinth"); + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/texture-packs\\/([^\\/]+)\\/?"), + "curseforge"); + map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge"); + return map; +} + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/DataPackPage.h b/launcher/ui/pages/modplatform/DataPackPage.h new file mode 100644 index 0000000000..55ed205f88 --- /dev/null +++ b/launcher/ui/pages/modplatform/DataPackPage.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2023 flowln +// SPDX-FileCopyrightText: 2023 TheKodeToad +// +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include "ui/pages/modplatform/ResourcePage.h" +#include "ui/pages/modplatform/DataPackModel.h" + +namespace Ui { +class ResourcePage; +} + +namespace ResourceDownload { + +class DataPackDownloadDialog; + +class DataPackResourcePage : public ResourcePage { + Q_OBJECT + + public: + template + static T* create(DataPackDownloadDialog* dialog, BaseInstance& instance) + { + auto page = new T(dialog, instance); + auto model = static_cast(page->getModel()); + + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); + + return page; + } + + //: The plural version of 'data pack' + [[nodiscard]] inline QString resourcesString() const override { return tr("data packs"); } + //: The singular version of 'data packs' + [[nodiscard]] inline QString resourceString() const override { return tr("data pack"); } + + [[nodiscard]] bool supportsFiltering() const override { return false; }; + + [[nodiscard]] QMap urlHandlers() const override; + + protected: + DataPackResourcePage(DataPackDownloadDialog* dialog, BaseInstance& instance); + + protected slots: + void triggerSearch() override; +}; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index 856018294c..a2185233db 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -118,4 +118,27 @@ auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJson return obj.object().value("hits").toArray(); } +ModrinthDataPackModel::ModrinthDataPackModel(const BaseInstance& base) : DataPackResourceModel(base, new ModrinthAPI) {} + +void ModrinthDataPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + ::Modrinth::loadIndexedPack(m, obj); +} + +void ModrinthDataPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + ::Modrinth::loadExtraPackData(m, obj); +} + +void ModrinthDataPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance); +} + +auto ModrinthDataPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return obj.object().value("hits").toArray(); +} + + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h index 15cd585444..6a5ba03825 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h @@ -20,6 +20,7 @@ #pragma once +#include "ui/pages/modplatform/DataPackModel.h" #include "ui/pages/modplatform/ModModel.h" #include "ui/pages/modplatform/ResourcePackModel.h" #include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" @@ -99,4 +100,22 @@ class ModrinthShaderPackModel : public ShaderPackResourceModel { auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; }; +class ModrinthDataPackModel : public DataPackResourceModel { + Q_OBJECT + + public: + ModrinthDataPackModel(const BaseInstance&); + ~ModrinthDataPackModel() override = default; + + private: + [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; } + [[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); } + + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; +}; + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index a4197b2255..d4adf07d92 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -124,6 +124,24 @@ ModrinthShaderPackPage::ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog, m_ui->packDescription->setMetaEntry(metaEntryBase()); } +ModrinthDataPackPage::ModrinthDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance) + : DataPackResourcePage(dialog, instance) +{ + m_model = new ModrinthDataPackModel(instance); + m_ui->packView->setModel(m_model); + + addSortings(); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's constructor... + connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthDataPackPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthDataPackPage::onVersionSelectionChanged); + connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthDataPackPage::onResourceSelected); + + m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + // I don't know why, but doing this on the parent class makes it so that // other mod providers start loading before being selected, at least with // my Qt, so we need to implement this in every derived class... @@ -143,5 +161,9 @@ auto ModrinthShaderPackPage::shouldDisplay() const -> bool { return true; } +auto ModrinthDataPackPage::shouldDisplay() const -> bool +{ + return true; +} } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index 311bcfe321..e9cb33a60a 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -41,6 +41,7 @@ #include "modplatform/ResourceAPI.h" +#include "ui/pages/modplatform/DataPackPage.h" #include "ui/pages/modplatform/ModPage.h" #include "ui/pages/modplatform/ResourcePackPage.h" #include "ui/pages/modplatform/ShaderPackPage.h" @@ -166,4 +167,27 @@ class ModrinthShaderPackPage : public ShaderPackResourcePage { [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } }; +class ModrinthDataPackPage : public DataPackResourcePage { + Q_OBJECT + + public: + static ModrinthDataPackPage* create(DataPackDownloadDialog* dialog, BaseInstance& instance) + { + return DataPackResourcePage::create(dialog, instance); + } + + ModrinthDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance); + ~ModrinthDataPackPage() override = default; + + [[nodiscard]] bool shouldDisplay() const override; + + [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); } + [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); } + [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); } + [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); } + [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } + + [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } +}; + } // namespace ResourceDownload diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 69f72fea21..f44e1e3ffa 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -212,6 +212,12 @@ void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) setImage(resource_pack.image({ 64, 64 })); } +void InfoFrame::updateWithDataPack(DataPack& data_pack) { + setName(renderColorCodes(data_pack.name())); + setDescription(renderColorCodes(data_pack.description())); + setImage(data_pack.image({ 64, 64 })); +} + void InfoFrame::updateWithTexturePack(TexturePack& texture_pack) { setName(renderColorCodes(texture_pack.name())); diff --git a/launcher/ui/widgets/InfoFrame.h b/launcher/ui/widgets/InfoFrame.h index d6764baa24..20c54e2e5d 100644 --- a/launcher/ui/widgets/InfoFrame.h +++ b/launcher/ui/widgets/InfoFrame.h @@ -37,6 +37,7 @@ #include +#include "minecraft/mod/DataPack.h" #include "minecraft/mod/Mod.h" #include "minecraft/mod/ResourcePack.h" #include "minecraft/mod/TexturePack.h" @@ -63,6 +64,7 @@ class InfoFrame : public QFrame { void updateWithMod(Mod const& m); void updateWithResource(Resource const& resource); void updateWithResourcePack(ResourcePack& rp); + void updateWithDataPack(DataPack& rp); void updateWithTexturePack(TexturePack& tp); static QString renderColorCodes(QString input); From 657416fe30c96a7bfb3978e8f84c5d0c7951ba27 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 3 Dec 2023 21:29:02 +0200 Subject: [PATCH 0105/2054] Removed header Signed-off-by: Trial97 --- launcher/modplatform/helpers/ExportToModList.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/modplatform/helpers/ExportToModList.cpp b/launcher/modplatform/helpers/ExportToModList.cpp index 86d051c752..aea16ab503 100644 --- a/launcher/modplatform/helpers/ExportToModList.cpp +++ b/launcher/modplatform/helpers/ExportToModList.cpp @@ -16,7 +16,6 @@ * along with this program. If not, see . */ #include "ExportToModList.h" -#include #include #include #include From 648aa18e6f84be7fbabc330de09beff9f170afa3 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 12 Dec 2023 14:22:23 -0800 Subject: [PATCH 0106/2054] add: refresh on load Signed-off-by: theMackabu --- launcher/ui/widgets/ThemeCustomizationWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.cpp b/launcher/ui/widgets/ThemeCustomizationWidget.cpp index a0e682bb93..79932e20bd 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.cpp +++ b/launcher/ui/widgets/ThemeCustomizationWidget.cpp @@ -27,6 +27,7 @@ ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget* parent) : QWidget(pa { ui->setupUi(this); loadSettings(); + ThemeCustomizationWidget::refresh(); connect(ui->iconsComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyIconTheme); connect(ui->widgetStyleComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, From d10b07567797a9e05fcc9996aefb7f1ba99a13b6 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 13 Dec 2023 14:36:58 +0200 Subject: [PATCH 0107/2054] Sort mods before export Signed-off-by: Trial97 --- launcher/ui/pages/instance/ModFolderPage.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 8dc32a8ff4..8746513beb 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -129,7 +129,7 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); }; connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, - [this, check_allow_update, actionRemoveItemMetadata, actionExportMetadata] { + [this, check_allow_update, actionRemoveItemMetadata] { ui->actionUpdateItem->setEnabled(check_allow_update()); auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); @@ -385,6 +385,7 @@ void ModFolderPage::exportModMetadata() if (selectedMods.length() == 0) selectedMods = m_model->allMods(); + std::sort(selectedMods.begin(), selectedMods.end(), [](const Mod* a, const Mod* b) { return a->name() < b->name(); }); ExportToModListDialog dlg(m_instance->name(), selectedMods, this); dlg.exec(); } From 6a619003288a06c0f664c5454dc4b69a65b4235b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 13 Dec 2023 17:48:11 +0200 Subject: [PATCH 0108/2054] Fixed curseforge version filter Signed-off-by: Trial97 --- launcher/ui/widgets/ModFilterWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 341823244a..f419537c7e 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -177,6 +177,7 @@ void ModFilterWidget::prepareBasicFilter() auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft"); m_filter->versions.push_front(Version{ def }); ui->versionsCb->setCheckedItems({ def }); + ui->versionsSimpleCb->setCurrentIndex(ui->versionsSimpleCb->findText(def)); } void ModFilterWidget::onIncludeSnapshotsChanged() From f33110ee0cf39596d893aae3b5e40de3f282d44d Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 14 Dec 2023 00:19:22 +0200 Subject: [PATCH 0109/2054] Renamed function Signed-off-by: Trial97 --- launcher/net/NetJob.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index faceba0580..6740bb8c5c 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -155,7 +155,7 @@ void NetJob::emitFailed(QString reason) if (response == QMessageBox::Yes) { m_try = 0; - startNext(); + executeNextSubTask(); return; } #endif From 13d29ac6f466776f13b01f90ba805af0490a41ca Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 22 Dec 2023 23:35:48 +0200 Subject: [PATCH 0110/2054] Updated the size sort code Signed-off-by: Trial97 --- launcher/minecraft/mod/Resource.cpp | 13 +++++++------ launcher/minecraft/mod/Resource.h | 2 ++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index d8727db0af..2fe7e87d4f 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "FileSystem.h" #include "StringUtils.h" @@ -20,7 +21,7 @@ void Resource::setFile(QFileInfo file_info) parseFile(); } -QString calculateFileSize(const QFileInfo& file) +std::tuple calculateFileSize(const QFileInfo& file) { if (file.isDir()) { auto dir = QDir(file.absoluteFilePath()); @@ -29,9 +30,9 @@ QString calculateFileSize(const QFileInfo& file) auto str = QObject::tr("item"); if (count != 1) str = QObject::tr("items"); - return QString("%1 %2").arg(QString::number(count), str); + return { QString("%1 %2").arg(QString::number(count), str), -count }; } - return StringUtils::humanReadableFileSize(file.size(), true); + return { StringUtils::humanReadableFileSize(file.size(), true), file.size() }; } void Resource::parseFile() @@ -42,7 +43,7 @@ void Resource::parseFile() m_internal_id = file_name; - m_size_str = calculateFileSize(m_file_info); + std::tie(m_size_str, m_size_info) = calculateFileSize(m_file_info); if (m_file_info.isDir()) { m_type = ResourceType::FOLDER; m_name = file_name; @@ -107,9 +108,9 @@ std::pair Resource::compare(const Resource& other, SortType type) con return { -1, type == SortType::DATE }; break; case SortType::SIZE: { - if (fileinfo().size() > other.fileinfo().size()) + if (sizeInfo() > other.sizeInfo()) return { 1, type == SortType::SIZE }; - if (fileinfo().size() < other.fileinfo().size()) + if (sizeInfo() < other.sizeInfo()) return { -1, type == SortType::SIZE }; break; } diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 029afbd5bc..74f4d006e4 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -46,6 +46,7 @@ class Resource : public QObject { [[nodiscard]] auto type() const -> ResourceType { return m_type; } [[nodiscard]] bool enabled() const { return m_enabled; } [[nodiscard]] QString sizeStr() const { return m_size_str; } + [[nodiscard]] qint64 sizeInfo() const { return m_size_info; } [[nodiscard]] virtual auto name() const -> QString { return m_name; } [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; } @@ -119,4 +120,5 @@ class Resource : public QObject { bool m_is_resolved = false; int m_resolution_ticket = 0; QString m_size_str; + qint64 m_size_info; }; From d94e641900db85cab74df95fa5bf100247b83f59 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 7 Jan 2024 22:00:31 +0200 Subject: [PATCH 0111/2054] Load versions on first edit Signed-off-by: Trial97 --- launcher/ui/pages/instance/ModFolderPage.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 313fef2b62..aba87942a7 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -321,6 +321,12 @@ bool CoreModFolderPage::shouldDisplay() const auto version = inst->getPackProfile(); + ProgressDialog loadDialog(parentWidget()); + auto update = inst->createUpdateTask(Net::Mode::Offline); + if (update) { + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(update.get()); + } if (!version) return true; if (!version->getComponent("net.minecraftforge")) From f77749e4486f3a19a75b74f933e6d9de9c0c0a44 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 21 Jan 2024 21:24:47 +0200 Subject: [PATCH 0112/2054] Resized Refresh theme button Signed-off-by: Trial97 --- .../ui/widgets/ThemeCustomizationWidget.ui | 261 ++++++++++-------- 1 file changed, 146 insertions(+), 115 deletions(-) diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.ui b/launcher/ui/widgets/ThemeCustomizationWidget.ui index 322f9d6a7f..1faa45c4f0 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.ui +++ b/launcher/ui/widgets/ThemeCustomizationWidget.ui @@ -13,7 +13,7 @@ Form - + QLayout::SetMinimumSize @@ -29,151 +29,182 @@ 0 - - - - &Icons - - - iconsComboBox - - - - - - - - - - 0 - 0 - + + + + + + &Icons - - Qt::StrongFocus + + iconsComboBox - - - - View icon themes folder. - + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + + + + + View icon themes folder. + + + + + + + + + true + + + + + + + - - - - - .. + &Widgets - - true + + widgetStyleComboBox - - - - - - &Widgets - - - widgetStyleComboBox - - - - - - - - - - 0 - 0 - - - - Qt::StrongFocus - - + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + + + + + View widget themes folder. + + + + + + + + + true + + + + - - + + - View widget themes folder. + The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar. - - - - - .. + C&at - - true + + backgroundCatComboBox + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar. + + + + + + + View cat packs folder. + + + + + + + + + true + + + + + - - - - The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar. - - - C&at - - - backgroundCatComboBox - - - - - + + - - - - 0 - 0 - + + + Qt::Horizontal - - Qt::StrongFocus + + + 40 + 20 + - - The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar. - - + - - - View cat packs folder. - + - + Refresh all - - - .. + + + + + + Qt::Horizontal - - true + + + 40 + 20 + - + - - - - Refresh - - - From 9bbeb5ef3ae389113bc7219b61600ebadcb4ce7b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 22 Jan 2024 21:26:46 +0200 Subject: [PATCH 0113/2054] Made updater interval configurable Signed-off-by: Trial97 --- launcher/ui/pages/global/LauncherPage.cpp | 2 ++ launcher/ui/pages/global/LauncherPage.ui | 27 +++++++++++++++++++++++ launcher/updater/PrismExternalUpdater.cpp | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 78c44380a0..fc062023c5 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -185,6 +185,7 @@ void LauncherPage::applySettings() // Updates if (APPLICATION->updater()) { APPLICATION->updater()->setAutomaticallyChecksForUpdates(ui->autoUpdateCheckBox->isChecked()); + APPLICATION->updater()->setUpdateCheckInterval(ui->updateIntervalSpinBox->value() * 3600); } s->set("MenuBarInsteadOfToolBar", ui->preferMenuBarCheckBox->isChecked()); @@ -234,6 +235,7 @@ void LauncherPage::loadSettings() // Updates if (APPLICATION->updater()) { ui->autoUpdateCheckBox->setChecked(APPLICATION->updater()->getAutomaticallyChecksForUpdates()); + ui->updateIntervalSpinBox->setValue(APPLICATION->updater()->getUpdateCheckInterval() / 3600); } // Toolbar/menu bar settings (not applicable if native menu bar is present) diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 18b52e1b86..68f4e74369 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -58,6 +58,33 @@ + + + + + + Update interval + + + + + + + Set it to 0 to only check on launch + + + h + + + 0 + + + 99999999 + + + + + diff --git a/launcher/updater/PrismExternalUpdater.cpp b/launcher/updater/PrismExternalUpdater.cpp index bee72e3a0c..dc1aae8724 100644 --- a/launcher/updater/PrismExternalUpdater.cpp +++ b/launcher/updater/PrismExternalUpdater.cpp @@ -257,7 +257,7 @@ void PrismExternalUpdater::setBetaAllowed(bool allowed) void PrismExternalUpdater::resetAutoCheckTimer() { - if (priv->autoCheck) { + if (priv->autoCheck && priv->updateInterval > 0) { int timeoutDuration = 0; auto now = QDateTime::currentDateTime(); if (priv->lastCheck.isValid()) { From 6b637a8797dad184d5ed73a93f00e9f86da22ee0 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 22 Jan 2024 22:32:42 +0200 Subject: [PATCH 0114/2054] Display minecraft version if not mentioned for modrinth packs Signed-off-by: Trial97 --- .../modplatform/modrinth/ModrinthPackManifest.cpp | 4 ++++ launcher/modplatform/modrinth/ModrinthPackManifest.h | 1 + .../ui/pages/modplatform/modrinth/ModrinthPage.cpp | 11 ++++++----- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index 7846e966dd..f360df43ae 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -131,6 +131,10 @@ auto loadIndexedVersion(QJsonObject& obj) -> ModpackVersion file.name = Json::requireString(obj, "name"); file.version = Json::requireString(obj, "version_number"); + auto gameVersions = Json::ensureArray(obj, "game_versions"); + if (!gameVersions.isEmpty()) { + file.gameVersion = Json::ensureString(gameVersions[0]); + } file.version_type = ModPlatform::IndexedVersionType(Json::requireString(obj, "version_type")); file.changelog = Json::ensureString(obj, "changelog"); diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h index 1ffd31d837..2bd61c5d90 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.h +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h @@ -84,6 +84,7 @@ struct ModpackExtra { struct ModpackVersion { QString name; QString version; + QString gameVersion; ModPlatform::IndexedVersionType version_type; QString changelog; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index fffa21940a..452416edd1 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -222,11 +222,12 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI } for (auto version : current.versions) { auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : ""; - if (!version.name.contains(version.version)) - ui->versionSelectionBox->addItem(QString("%1 — %2%3").arg(version.name, version.version, release_type), - QVariant(version.id)); - else - ui->versionSelectionBox->addItem(QString("%1%2").arg(version.name, release_type), QVariant(version.id)); + auto mcVersion = !version.gameVersion.isEmpty() && !version.name.contains(version.gameVersion) + ? QString(" for %1").arg(version.gameVersion) + : ""; + auto versionStr = !version.name.contains(version.version) ? version.version : ""; + ui->versionSelectionBox->addItem(QString("%1%2 — %3%4").arg(version.name, mcVersion, versionStr, release_type), + QVariant(version.id)); } QVariant current_updated; From a0e7729aa62a5bb6a83e401dd863e69cbd886edc Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 24 Jan 2024 18:26:43 +0200 Subject: [PATCH 0115/2054] Started workin on stuff Signed-off-by: Trial97 --- launcher/Application.cpp | 18 +- launcher/Application.h | 2 - launcher/CMakeLists.txt | 2 - launcher/JavaCommon.cpp | 30 ++- launcher/JavaCommon.h | 12 +- launcher/MMCZip.cpp | 106 ++++++++++ launcher/MMCZip.h | 28 +++ launcher/SysInfo.cpp | 18 +- launcher/SysInfo.h | 1 + launcher/java/JavaChecker.cpp | 39 ++-- launcher/java/JavaChecker.h | 64 +++--- launcher/java/JavaCheckerJob.cpp | 41 ---- launcher/java/JavaCheckerJob.h | 56 ------ launcher/java/JavaInstallList.cpp | 37 ++-- launcher/java/JavaInstallList.h | 12 +- launcher/java/JavaUtils.h | 5 +- .../java/providers/AdoptiumJavaDownloader.cpp | 119 +++++++++++ .../java/providers/AdoptiumJavaDownloader.h | 36 ++++ .../java/providers/AzulJavaDownloader.cpp | 159 +++++++++++++++ launcher/java/providers/AzulJavaDownloader.h | 37 ++++ .../java/providers/BasicJavaDownloader.cpp | 29 +++ launcher/java/providers/BasicJavaDownloader.h | 41 ++++ .../java/providers/MojangJavaDownloader.cpp | 185 ++++++++++++++++++ .../java/providers/MojanglJavaDownloader.h | 37 ++++ launcher/launch/steps/CheckJava.cpp | 14 +- launcher/launch/steps/CheckJava.h | 4 +- launcher/ui/widgets/JavaSettingsWidget.cpp | 21 +- launcher/ui/widgets/JavaSettingsWidget.h | 4 +- 28 files changed, 909 insertions(+), 248 deletions(-) delete mode 100644 launcher/java/JavaCheckerJob.cpp delete mode 100644 launcher/java/JavaCheckerJob.h create mode 100644 launcher/java/providers/AdoptiumJavaDownloader.cpp create mode 100644 launcher/java/providers/AdoptiumJavaDownloader.h create mode 100644 launcher/java/providers/AzulJavaDownloader.cpp create mode 100644 launcher/java/providers/AzulJavaDownloader.h create mode 100644 launcher/java/providers/BasicJavaDownloader.cpp create mode 100644 launcher/java/providers/BasicJavaDownloader.h create mode 100644 launcher/java/providers/MojangJavaDownloader.cpp create mode 100644 launcher/java/providers/MojanglJavaDownloader.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 661c6c5be3..aa702aa7ea 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -44,6 +44,7 @@ #include "BuildConfig.h" #include "DataMigrationTask.h" +#include "java/JavaInstallList.h" #include "net/PasteUpload.h" #include "pathmatcher/MultiMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h" @@ -125,6 +126,7 @@ #include #include +#include "SysInfo.h" #ifdef Q_OS_LINUX #include @@ -607,7 +609,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // Memory m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512); - m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, suitableMaxMem()); + m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::suitableMaxMem()); m_settings->registerSetting("PermGen", 128); // Java Settings @@ -1667,20 +1669,6 @@ QString Application::getUserAgentUncached() return BuildConfig.USER_AGENT_UNCACHED; } -int Application::suitableMaxMem() -{ - float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte; - int maxMemoryAlloc; - - // If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB - if (totalRAM < (4096 * 1.5)) - maxMemoryAlloc = (int)(totalRAM / 1.5); - else - maxMemoryAlloc = 4096; - - return maxMemoryAlloc; -} - bool Application::handleDataMigration(const QString& currentData, const QString& oldData, const QString& name, diff --git a/launcher/Application.h b/launcher/Application.h index 7669e08ec3..85bf2dff44 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -180,8 +180,6 @@ class Application : public QApplication { void ShowGlobalSettings(class QWidget* parent, QString open_page = QString()); - int suitableMaxMem(); - bool updaterEnabled(); QString updaterBinaryName(); diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 48ca8f0851..e735f20817 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -422,8 +422,6 @@ set(SETTINGS_SOURCES set(JAVA_SOURCES java/JavaChecker.h java/JavaChecker.cpp - java/JavaCheckerJob.h - java/JavaCheckerJob.cpp java/JavaInstall.h java/JavaInstall.cpp java/JavaInstallList.h diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp index e16ac92556..cfc3cfe424 100644 --- a/launcher/JavaCommon.cpp +++ b/launcher/JavaCommon.cpp @@ -63,7 +63,7 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent) return true; } -void JavaCommon::javaWasOk(QWidget* parent, const JavaCheckResult& result) +void JavaCommon::javaWasOk(QWidget* parent, const JavaChecker::Result& result) { QString text; text += QObject::tr( @@ -79,7 +79,7 @@ void JavaCommon::javaWasOk(QWidget* parent, const JavaCheckResult& result) CustomMessageBox::selectable(parent, QObject::tr("Java test success"), text, QMessageBox::Information)->show(); } -void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaCheckResult& result) +void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaChecker::Result& result) { auto htmlError = result.errorLog; QString text; @@ -89,7 +89,7 @@ void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaCheckResult& result) CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); } -void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaCheckResult& result) +void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaChecker::Result& result) { QString text; text += QObject::tr( @@ -116,34 +116,26 @@ void JavaCommon::TestCheck::run() emit finished(); return; } - checker.reset(new JavaChecker()); + checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0, this)); connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished); - checker->m_path = m_path; - checker->performCheck(); + checker->start(); } -void JavaCommon::TestCheck::checkFinished(JavaCheckResult result) +void JavaCommon::TestCheck::checkFinished(JavaChecker::Result result) { - if (result.validity != JavaCheckResult::Validity::Valid) { + if (result.validity != JavaChecker::Result::Validity::Valid) { javaBinaryWasBad(m_parent, result); emit finished(); return; } - checker.reset(new JavaChecker()); + checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0, this)); connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs); - checker->m_path = m_path; - checker->m_args = m_args; - checker->m_minMem = m_minMem; - checker->m_maxMem = m_maxMem; - if (result.javaVersion.requiresPermGen()) { - checker->m_permGen = m_permGen; - } - checker->performCheck(); + checker->start(); } -void JavaCommon::TestCheck::checkFinishedWithArgs(JavaCheckResult result) +void JavaCommon::TestCheck::checkFinishedWithArgs(JavaChecker::Result result) { - if (result.validity == JavaCheckResult::Validity::Valid) { + if (result.validity == JavaChecker::Result::Validity::Valid) { javaWasOk(m_parent, result); emit finished(); return; diff --git a/launcher/JavaCommon.h b/launcher/JavaCommon.h index c96f7a9858..7c5510efc3 100644 --- a/launcher/JavaCommon.h +++ b/launcher/JavaCommon.h @@ -10,11 +10,11 @@ namespace JavaCommon { bool checkJVMArgs(QString args, QWidget* parent); // Show a dialog saying that the Java binary was usable -void javaWasOk(QWidget* parent, const JavaCheckResult& result); +void javaWasOk(QWidget* parent, const JavaChecker::Result& result); // Show a dialog saying that the Java binary was not usable because of bad options -void javaArgsWereBad(QWidget* parent, const JavaCheckResult& result); +void javaArgsWereBad(QWidget* parent, const JavaChecker::Result& result); // Show a dialog saying that the Java binary was not usable -void javaBinaryWasBad(QWidget* parent, const JavaCheckResult& result); +void javaBinaryWasBad(QWidget* parent, const JavaChecker::Result& result); // Show a dialog if we couldn't find Java Checker void javaCheckNotFound(QWidget* parent); @@ -32,11 +32,11 @@ class TestCheck : public QObject { void finished(); private slots: - void checkFinished(JavaCheckResult result); - void checkFinishedWithArgs(JavaCheckResult result); + void checkFinished(JavaChecker::Result result); + void checkFinishedWithArgs(JavaChecker::Result result); private: - std::shared_ptr checker; + JavaChecker::Ptr checker; QWidget* m_parent = nullptr; QString m_path; QString m_args; diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 3bfe16ab5c..78a3b290de 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -507,6 +507,112 @@ bool ExportToZipTask::abort() } return false; } + +void ExtractZipTask::executeTask() +{ + m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); }); + connect(&m_zip_watcher, &QFutureWatcher::finished, this, &ExtractZipTask::finish); + m_zip_watcher.setFuture(m_zip_future); +} + +auto ExtractZipTask::extractZip() -> ZipResult +{ + auto target = m_output_dir.absolutePath(); + auto target_top_dir = QUrl::fromLocalFile(target); + + QStringList extracted; + + qDebug() << "Extracting subdir" << m_subdirectory << "from" << m_input->getZipName() << "to" << target; + auto numEntries = m_input->getEntriesCount(); + if (numEntries < 0) { + return ZipResult(tr("Failed to enumerate files in archive")); + } + if (numEntries == 0) { + logWarning(tr("Extracting empty archives seems odd...")); + return ZipResult(); + } + if (!m_input->goToFirstFile()) { + return ZipResult(tr("Failed to seek to first file in zip")); + } + + setStatus("Extracting files..."); + setProgress(0, numEntries); + do { + if (m_zip_future.isCanceled()) + return ZipResult(); + setProgress(m_progress + 1, m_progressTotal); + QString file_name = m_input->getCurrentFileName(); + if (!file_name.startsWith(m_subdirectory)) + continue; + + auto relative_file_name = QDir::fromNativeSeparators(file_name.remove(0, m_subdirectory.size())); + auto original_name = relative_file_name; + setStatus("Unziping: " + relative_file_name); + + // Fix subdirs/files ending with a / getting transformed into absolute paths + if (relative_file_name.startsWith('/')) + relative_file_name = relative_file_name.mid(1); + + // Fix weird "folders with a single file get squashed" thing + QString sub_path; + if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) { + sub_path = relative_file_name.section('/', 0, -2) + '/'; + FS::ensureFolderPathExists(FS::PathCombine(target, sub_path)); + + relative_file_name = relative_file_name.split('/').last(); + } + + QString target_file_path; + if (relative_file_name.isEmpty()) { + target_file_path = target + '/'; + } else { + target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name); + if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/')) + target_file_path += '/'; + } + + if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) { + return ZipResult(tr("Extracting %1 was cancelled, because it was effectively outside of the target path %2") + .arg(relative_file_name, target)); + } + + if (!JlCompress::extractFile(m_input.get(), "", target_file_path)) { + JlCompress::removeFile(extracted); + return ZipResult(tr("Failed to extract file %1 to %2").arg(original_name, target_file_path)); + } + + extracted.append(target_file_path); + QFile::setPermissions(target_file_path, + QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser); + + qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; + } while (m_input->goToNextFile()); + + return ZipResult(); +} + +void ExtractZipTask::finish() +{ + if (m_zip_future.isCanceled()) { + emitAborted(); + } else if (auto result = m_zip_future.result(); result.has_value()) { + emitFailed(result.value()); + } else { + emitSucceeded(); + } +} + +bool ExtractZipTask::abort() +{ + if (m_zip_future.isRunning()) { + m_zip_future.cancel(); + // NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur + // immediately. + return true; + } + return false; +} + #endif } // namespace MMCZip diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index 45b1df0b33..2b396eb9c8 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -194,5 +194,33 @@ class ExportToZipTask : public Task { QFuture m_build_zip_future; QFutureWatcher m_build_zip_watcher; }; + +class ExtractZipTask : public Task { + public: + ExtractZipTask(QString input, QDir outputDir, QString subdirectory = "") + : ExtractZipTask(std::make_shared(input), outputDir, subdirectory) + {} + ExtractZipTask(std::shared_ptr input, QDir outputDir, QString subdirectory = "") + : m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory) + {} + virtual ~ExtractZipTask() = default; + + typedef std::optional ZipResult; + + protected: + virtual void executeTask() override; + bool abort() override; + + ZipResult extractZip(); + void finish(); + + private: + std::shared_ptr m_input; + QDir m_output_dir; + QString m_subdirectory; + + QFuture m_zip_future; + QFutureWatcher m_zip_watcher; +}; #endif } // namespace MMCZip diff --git a/launcher/SysInfo.cpp b/launcher/SysInfo.cpp index ad251e29ff..f15dde0e4f 100644 --- a/launcher/SysInfo.cpp +++ b/launcher/SysInfo.cpp @@ -1,5 +1,6 @@ #include #include +#include "sys.h" #ifdef Q_OS_MACOS #include #endif @@ -7,9 +8,6 @@ #include #include #include -#include "Application.h" -#include "Commandline.h" -#include "java/JavaUtils.h" #ifdef Q_OS_MACOS bool rosettaDetect() @@ -59,4 +57,18 @@ QString useQTForArch() #endif return qtArch; } + +int suitableMaxMem() +{ + float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte; + int maxMemoryAlloc; + + // If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB + if (totalRAM < (4096 * 1.5)) + maxMemoryAlloc = (int)(totalRAM / 1.5); + else + maxMemoryAlloc = 4096; + + return maxMemoryAlloc; +} } // namespace SysInfo diff --git a/launcher/SysInfo.h b/launcher/SysInfo.h index 3650bf008e..499c3b1dd1 100644 --- a/launcher/SysInfo.h +++ b/launcher/SysInfo.h @@ -3,4 +3,5 @@ namespace SysInfo { QString currentSystem(); QString useQTForArch(); +int suitableMaxMem(); } // namespace SysInfo diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 20caba189e..7271c0d093 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -40,14 +40,13 @@ #include #include -#include "Application.h" #include "Commandline.h" -#include "FileSystem.h" -#include "JavaUtils.h" +#include "java/JavaUtils.h" -JavaChecker::JavaChecker(QObject* parent) : QObject(parent) {} +JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id, QObject* parent) + : Task(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id){}; -void JavaChecker::performCheck() +void JavaChecker::executeTask() { QString checkerJar = JavaUtils::getJavaCheckPath(); @@ -69,7 +68,7 @@ void JavaChecker::performCheck() if (m_maxMem != 0) { args << QString("-Xmx%1m").arg(m_maxMem); } - if (m_permGen != 64) { + if (m_permGen != 64 && m_permGen != 0) { args << QString("-XX:PermSize=%1m").arg(m_permGen); } @@ -112,11 +111,10 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) QProcessPtr _process = process; process.reset(); - JavaCheckResult result; - { - result.path = m_path; - result.id = m_id; - } + Result result = { + m_path, + m_id, + }; result.errorLog = m_stderr; result.outLog = m_stdout; qDebug() << "STDOUT" << m_stdout; @@ -124,8 +122,9 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) qDebug() << "Java checker finished with status" << status << "exit code" << exitcode; if (status == QProcess::CrashExit || exitcode == 1) { - result.validity = JavaCheckResult::Validity::Errored; + result.validity = Result::Validity::Errored; emit checkFinished(result); + emitSucceeded(); return; } @@ -158,8 +157,9 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) } if (!results.contains("os.arch") || !results.contains("java.version") || !results.contains("java.vendor") || !success) { - result.validity = JavaCheckResult::Validity::ReturnedInvalidData; + result.validity = Result::Validity::ReturnedInvalidData; emit checkFinished(result); + emitSucceeded(); return; } @@ -168,7 +168,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) auto java_vendor = results["java.vendor"]; bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64"; - result.validity = JavaCheckResult::Validity::Valid; + result.validity = Result::Validity::Valid; result.is_64bit = is_64; result.mojangPlatform = is_64 ? "64" : "32"; result.realPlatform = os_arch; @@ -176,6 +176,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) result.javaVendor = java_vendor; qDebug() << "Java checker succeeded."; emit checkFinished(result); + emitSucceeded(); } void JavaChecker::error(QProcess::ProcessError err) @@ -187,15 +188,9 @@ void JavaChecker::error(QProcess::ProcessError err) qDebug() << "Native environment:"; qDebug() << QProcessEnvironment::systemEnvironment().toStringList(); killTimer.stop(); - JavaCheckResult result; - { - result.path = m_path; - result.id = m_id; - } - - emit checkFinished(result); - return; + emit checkFinished({ m_path, m_id }); } + emitSucceeded(); } void JavaChecker::timeout() diff --git a/launcher/java/JavaChecker.h b/launcher/java/JavaChecker.h index 7111f85227..0c6191c21b 100644 --- a/launcher/java/JavaChecker.h +++ b/launcher/java/JavaChecker.h @@ -3,49 +3,51 @@ #include #include -#include "QObjectPtr.h" - #include "JavaVersion.h" +#include "QObjectPtr.h" +#include "tasks/Task.h" -class JavaChecker; - -struct JavaCheckResult { - QString path; - QString mojangPlatform; - QString realPlatform; - JavaVersion javaVersion; - QString javaVendor; - QString outLog; - QString errorLog; - bool is_64bit = false; - int id; - enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored; -}; - -using QProcessPtr = shared_qobject_ptr; -using JavaCheckerPtr = shared_qobject_ptr; -class JavaChecker : public QObject { +class JavaChecker : public Task { Q_OBJECT public: - explicit JavaChecker(QObject* parent = 0); - void performCheck(); - - QString m_path; - QString m_args; - int m_id = 0; - int m_minMem = 0; - int m_maxMem = 0; - int m_permGen = 64; + using QProcessPtr = shared_qobject_ptr; + using Ptr = shared_qobject_ptr; + + struct Result { + QString path; + int id; + QString mojangPlatform; + QString realPlatform; + JavaVersion javaVersion; + QString javaVendor; + QString outLog; + QString errorLog; + bool is_64bit = false; + enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored; + }; + + explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0, QObject* parent = 0); signals: - void checkFinished(JavaCheckResult result); + void checkFinished(Result result); + + protected: + virtual void executeTask() override; private: QProcessPtr process; QTimer killTimer; QString m_stdout; QString m_stderr; - public slots: + + QString m_path; + QString m_args; + int m_minMem = 0; + int m_maxMem = 0; + int m_permGen = 64; + int m_id = 0; + + private slots: void timeout(); void finished(int exitcode, QProcess::ExitStatus); void error(QProcess::ProcessError); diff --git a/launcher/java/JavaCheckerJob.cpp b/launcher/java/JavaCheckerJob.cpp deleted file mode 100644 index 870e2a09ad..0000000000 --- a/launcher/java/JavaCheckerJob.cpp +++ /dev/null @@ -1,41 +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 "JavaCheckerJob.h" - -#include - -void JavaCheckerJob::partFinished(JavaCheckResult result) -{ - num_finished++; - qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" << javacheckers.size(); - setProgress(num_finished, javacheckers.size()); - - javaresults.replace(result.id, result); - - if (num_finished == javacheckers.size()) { - emitSucceeded(); - } -} - -void JavaCheckerJob::executeTask() -{ - qDebug() << m_job_name.toLocal8Bit() << " started."; - for (auto iter : javacheckers) { - javaresults.append(JavaCheckResult()); - connect(iter.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished); - iter->performCheck(); - } -} diff --git a/launcher/java/JavaCheckerJob.h b/launcher/java/JavaCheckerJob.h deleted file mode 100644 index ddf8279683..0000000000 --- a/launcher/java/JavaCheckerJob.h +++ /dev/null @@ -1,56 +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 -#include "JavaChecker.h" -#include "tasks/Task.h" - -class JavaCheckerJob; -using JavaCheckerJobPtr = shared_qobject_ptr; - -// FIXME: this just seems horribly redundant -class JavaCheckerJob : public Task { - Q_OBJECT - public: - explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name){}; - virtual ~JavaCheckerJob(){}; - - bool addJavaCheckerAction(JavaCheckerPtr base) - { - javacheckers.append(base); - // if this is already running, the action needs to be started right away! - if (isRunning()) { - setProgress(num_finished, javacheckers.size()); - connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished); - base->performCheck(); - } - return true; - } - QList getResults() { return javaresults; } - - private slots: - void partFinished(JavaCheckResult result); - - protected: - virtual void executeTask() override; - - private: - QString m_job_name; - QList javacheckers; - QList javaresults; - int num_finished = 0; -}; diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index d8be4963f5..ef99d6853e 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -38,11 +38,13 @@ #include #include +#include -#include "java/JavaCheckerJob.h" +#include "Application.h" +#include "java/JavaChecker.h" #include "java/JavaInstallList.h" #include "java/JavaUtils.h" -#include "minecraft/VersionFilterData.h" +#include "tasks/ConcurrentTask.h" JavaInstallList::JavaInstallList(QObject* parent) : BaseVersionList(parent) {} @@ -55,7 +57,7 @@ Task::Ptr JavaInstallList::getLoadTask() Task::Ptr JavaInstallList::getCurrentTask() { if (m_status == Status::InProgress) { - return m_loadTask; + return m_load_task; } return nullptr; } @@ -64,8 +66,8 @@ void JavaInstallList::load() { if (m_status != Status::InProgress) { m_status = Status::InProgress; - m_loadTask.reset(new JavaListLoadTask(this)); - m_loadTask->start(); + m_load_task.reset(new JavaListLoadTask(this)); + m_load_task->start(); } } @@ -129,7 +131,7 @@ void JavaInstallList::updateListData(QList versions) } endResetModel(); m_status = Status::Done; - m_loadTask.reset(); + m_load_task.reset(); } bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right) @@ -149,11 +151,9 @@ void JavaInstallList::sortVersions() JavaListLoadTask::JavaListLoadTask(JavaInstallList* vlist) : Task() { m_list = vlist; - m_currentRecommended = NULL; + m_current_recommended = NULL; } -JavaListLoadTask::~JavaListLoadTask() {} - void JavaListLoadTask::executeTask() { setStatus(tr("Detecting Java installations...")); @@ -161,20 +161,17 @@ void JavaListLoadTask::executeTask() JavaUtils ju; QList candidate_paths = ju.FindJavaPaths(); - m_job.reset(new JavaCheckerJob("Java detection")); + ConcurrentTask::Ptr job(new ConcurrentTask(this, "Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); + m_job.reset(job); connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); connect(m_job.get(), &Task::progress, this, &Task::setProgress); qDebug() << "Probing the following Java paths: "; int id = 0; for (QString candidate : candidate_paths) { - qDebug() << " " << candidate; - - auto candidate_checker = new JavaChecker(); - candidate_checker->m_path = candidate; - candidate_checker->m_id = id; - m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker)); - + auto checker = new JavaChecker(candidate, "", 0, 0, 0, id, this); + connect(checker, &JavaChecker::checkFinished, [this](JavaChecker::Result result) { m_results << result; }); + job->addTask(Task::Ptr(checker)); id++; } @@ -184,11 +181,11 @@ void JavaListLoadTask::executeTask() void JavaListLoadTask::javaCheckerFinished() { QList candidates; - auto results = m_job->getResults(); + std::sort(m_results.begin(), m_results.end(), [](JavaChecker::Result a, JavaChecker::Result b) { return a.id < b.id; }); qDebug() << "Found the following valid Java installations:"; - for (JavaCheckResult result : results) { - if (result.validity == JavaCheckResult::Validity::Valid) { + for (auto result : m_results) { + if (result.validity == JavaChecker::Result::Validity::Valid) { JavaInstallPtr javaVersion(new JavaInstall()); javaVersion->id = result.javaVersion; diff --git a/launcher/java/JavaInstallList.h b/launcher/java/JavaInstallList.h index 1eebadf234..08f0b310df 100644 --- a/launcher/java/JavaInstallList.h +++ b/launcher/java/JavaInstallList.h @@ -19,9 +19,9 @@ #include #include "BaseVersionList.h" +#include "java/JavaChecker.h" #include "tasks/Task.h" -#include "JavaCheckerJob.h" #include "JavaInstall.h" #include "QObjectPtr.h" @@ -53,7 +53,7 @@ class JavaInstallList : public BaseVersionList { protected: Status m_status = Status::NotDone; - shared_qobject_ptr m_loadTask; + shared_qobject_ptr m_load_task; QList m_vlist; }; @@ -62,14 +62,16 @@ class JavaListLoadTask : public Task { public: explicit JavaListLoadTask(JavaInstallList* vlist); - virtual ~JavaListLoadTask(); + virtual ~JavaListLoadTask() = default; + protected: void executeTask() override; public slots: void javaCheckerFinished(); protected: - shared_qobject_ptr m_job; + Task::Ptr m_job; JavaInstallList* m_list; - JavaInstall* m_currentRecommended; + JavaInstall* m_current_recommended; + QList m_results; }; diff --git a/launcher/java/JavaUtils.h b/launcher/java/JavaUtils.h index 6161797068..c052ca325c 100644 --- a/launcher/java/JavaUtils.h +++ b/launcher/java/JavaUtils.h @@ -15,10 +15,9 @@ #pragma once +#include #include - -#include "JavaChecker.h" -#include "JavaInstallList.h" +#include "java/JavaInstall.h" #ifdef Q_OS_WIN #include diff --git a/launcher/java/providers/AdoptiumJavaDownloader.cpp b/launcher/java/providers/AdoptiumJavaDownloader.cpp new file mode 100644 index 0000000000..4f8499d4a8 --- /dev/null +++ b/launcher/java/providers/AdoptiumJavaDownloader.cpp @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "java/providers/AdoptiumJavaDownloader.h" +#include +#include +#include "MMCZip.h" + +#include "Application.h" +#include "net/NetJob.h" +#include "tasks/Task.h" + +void AdoptiumJavaDownloader::executeTask() +{ + downloadJava(); +}; + +QString AdoptiumJavaDownloader::getArch() const +{ + if (m_os_arch == "arm64") + return "aarch64"; + if (m_os_arch.isEmpty()) + return "x86"; + return m_os_arch; +} + +void AdoptiumJavaDownloader::downloadJava() +{ + // JRE found ! download the zip + setStatus(tr("Downloading Java from Adoptium")); + + auto javaVersion = m_is_legacy ? QString("8") : QString("17"); + auto azulOS = m_os_name == "osx" ? "mac" : m_os_name; + auto arch = getArch(); + MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("java", "adoptiumJRE.zip"); + + auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); + download->addNetAction(Net::Download::makeCached( + QString("https://api.adoptium.net/v3/binary/latest/%1/ga/%2/%3/jre/hotspot/normal/eclipse").arg(javaVersion, azulOS, arch), entry)); + auto fullPath = entry->getFullPath(); + + connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); + // connect(download.get(), &NetJob::aborted, [path] { APPLICATION->instances()->destroyStagingPath(path); }); + connect(download.get(), &NetJob::progress, this, &AdoptiumJavaDownloader::progress); + connect(download.get(), &NetJob::failed, this, &AdoptiumJavaDownloader::emitFailed); + connect(this, &Task::aborted, download.get(), &NetJob::abort); + connect(download.get(), &NetJob::succeeded, [this, fullPath] { + // This should do all of the extracting and creating folders + extractJava(fullPath); + }); + download->start(); +}; + +void AdoptiumJavaDownloader::extractJava(QString input) +{ + setStatus(tr("Extracting java")); + auto zip = std::make_shared(input); + auto files = zip->getFileNameList(); + if (files.isEmpty()) { + emitFailed("Empty archive"); + return; + } + auto zipTask = makeShared(input, m_final_path, files[0]); + + auto progressStep = std::make_shared(); + connect(zipTask.get(), &Task::finished, this, [this, progressStep] { + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + }); + + connect(this, &Task::aborted, zipTask.get(), &Task::abort); + connect(zipTask.get(), &Task::finished, [zipTask, this] { disconnect(this, &Task::aborted, zipTask.get(), &Task::abort); }); + + connect(zipTask.get(), &Task::succeeded, this, &AdoptiumJavaDownloader::emitSucceeded); + connect(zipTask.get(), &Task::aborted, this, &AdoptiumJavaDownloader::emitAborted); + connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) { + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + emitFailed(reason); + }); + connect(zipTask.get(), &Task::stepProgress, this, &AdoptiumJavaDownloader::propagateStepProgress); + + connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { + progressStep->update(current, total); + stepProgress(*progressStep); + }); + connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) { + progressStep->status = status; + stepProgress(*progressStep); + }); + zipTask->start(); +}; + +static const QStringList supportedOs = { + "linux", "windows", "mac", "solaris", "aix", "alpine-linux", +}; + +static const QStringList supportedArch = { + "x64", "x86", "x32", "ppc64", "ppc64le", "s390x", "aarch64", "arm", "sparcv9", "riscv64", +}; + +bool AdoptiumJavaDownloader::isSupported() const +{ + return supportedOs.contains(m_os_name == "osx" ? "mac" : m_os_name) && supportedArch.contains(getArch()); +}; \ No newline at end of file diff --git a/launcher/java/providers/AdoptiumJavaDownloader.h b/launcher/java/providers/AdoptiumJavaDownloader.h new file mode 100644 index 0000000000..f0ae6239b3 --- /dev/null +++ b/launcher/java/providers/AdoptiumJavaDownloader.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "java/providers/BasicJavaDownloader.h" + +class AdoptiumJavaDownloader : public BasicJavaDownloader { + Q_OBJECT + public: + void executeTask() override; + + virtual QString name() const override { return "Adoptium"; }; + virtual bool isSupported() const override; + private slots: + void downloadJava(); + void extractJava(QString input); + + private: + QString getArch() const; +}; \ No newline at end of file diff --git a/launcher/java/providers/AzulJavaDownloader.cpp b/launcher/java/providers/AzulJavaDownloader.cpp new file mode 100644 index 0000000000..674da592ec --- /dev/null +++ b/launcher/java/providers/AzulJavaDownloader.cpp @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "java/providers/AzulJavaDownloader.h" +#include +#include "MMCZip.h" + +#include "Application.h" +#include "Json.h" +#include "net/NetJob.h" +#include "tasks/Task.h" + +void AzulJavaDownloader::executeTask() +{ + downloadJavaList(); +}; + +void AzulJavaDownloader::downloadJavaList() +{ + setStatus(tr("Querying Azul meta")); + + auto javaVersion = m_is_legacy ? QString("8.0") : QString("17.0"); + auto azulOS = m_os_name == "osx" ? "macos" : m_os_name; + auto arch = getArch(); + auto metaResponse = std::make_shared(); + auto downloadJob = makeShared(QString("JRE::QueryAzulMeta"), APPLICATION->network()); + downloadJob->addNetAction(Net::Download::makeByteArray(QString("https://api.azul.com/metadata/v1/zulu/packages/?" + "java_version=%1" + "&os=%2" + "&arch=%3" + "&archive_type=zip" + "&java_package_type=jre" + "&support_term=lts" + "&latest=true" + "status=ga" + "&availability_types=CA" + "&page=1" + "&page_size=1") + .arg(javaVersion, azulOS, arch), + metaResponse)); + connect(downloadJob.get(), &NetJob::finished, + [downloadJob, metaResponse, this] { disconnect(this, &Task::aborted, downloadJob.get(), &NetJob::abort); }); + connect(this, &Task::aborted, downloadJob.get(), &NetJob::abort); + connect(downloadJob.get(), &NetJob::failed, this, &AzulJavaDownloader::emitFailed); + connect(downloadJob.get(), &NetJob::progress, this, &AzulJavaDownloader::progress); + connect(downloadJob.get(), &NetJob::succeeded, [metaResponse, this] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*metaResponse, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << *metaResponse; + return; + } + auto array = Json::ensureArray(doc.array()); + if (!array.empty()) { + downloadJava(array); + } else { + emitFailed(tr("No suitable JRE found")); + } + }); + downloadJob->start(); +}; + +QString AzulJavaDownloader::getArch() const +{ + if (m_os_arch == "arm64") + return "aarch64"; + if (m_os_arch == "arm") + return "aarch32"; + if (m_os_arch.isEmpty()) + return "x86"; + return m_os_arch; +} + +void AzulJavaDownloader::downloadJava(const QJsonArray& array) +{ + // JRE found ! download the zip + setStatus(tr("Downloading Java from Azul")); + + MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("java", "azulJRE.zip"); + + auto downloadURL = QUrl(array[0].toObject()["url"].toString()); + auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); + download->addNetAction(Net::Download::makeCached(downloadURL, entry)); + auto fullPath = entry->getFullPath(); + + connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); + // connect(download.get(), &NetJob::aborted, [path] { APPLICATION->instances()->destroyStagingPath(path); }); + connect(download.get(), &NetJob::progress, this, &AzulJavaDownloader::progress); + connect(download.get(), &NetJob::failed, this, &AzulJavaDownloader::emitFailed); + connect(this, &Task::aborted, download.get(), &NetJob::abort); + connect(download.get(), &NetJob::succeeded, [downloadURL, this, fullPath] { + // This should do all of the extracting and creating folders + extractJava(fullPath, downloadURL.fileName().chopped(4)); + }); + download->start(); +}; + +void AzulJavaDownloader::extractJava(QString input, QString subdirectory) +{ + setStatus(tr("Extracting java")); + auto zipTask = makeShared(input, m_final_path, subdirectory); + + auto progressStep = std::make_shared(); + connect(zipTask.get(), &Task::finished, this, [this, progressStep] { + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + }); + + connect(this, &Task::aborted, zipTask.get(), &Task::abort); + connect(zipTask.get(), &Task::finished, [zipTask, this] { disconnect(this, &Task::aborted, zipTask.get(), &Task::abort); }); + + connect(zipTask.get(), &Task::succeeded, this, &AzulJavaDownloader::emitSucceeded); + connect(zipTask.get(), &Task::aborted, this, &AzulJavaDownloader::emitAborted); + connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) { + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + emitFailed(reason); + }); + connect(zipTask.get(), &Task::stepProgress, this, &AzulJavaDownloader::propagateStepProgress); + + connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { + progressStep->update(current, total); + stepProgress(*progressStep); + }); + connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) { + progressStep->status = status; + stepProgress(*progressStep); + }); + zipTask->start(); +}; + +static const QStringList supportedOs = { + "macos", "linux", "windows", "linux-musl", "linux-glibc", "qnx", "solaris", "aix", +}; + +static const QStringList supportedArch = { + "x86", "x64", "amd64", "i686", "arm", "aarch64", "aarch32", "aarch32sf", "aarch32hf", "ppc", + "ppc64", "ppc32", "ppc32hf", "ppc32spe", "sparc", "sparc64", "sparc32", "sparcv9", "sparcv9-64", "sparcv9-32", +}; + +bool AzulJavaDownloader::isSupported() const +{ + return supportedOs.contains(m_os_name == "osx" ? "macos" : m_os_name) && supportedArch.contains(getArch()); +}; \ No newline at end of file diff --git a/launcher/java/providers/AzulJavaDownloader.h b/launcher/java/providers/AzulJavaDownloader.h new file mode 100644 index 0000000000..c4bc8c7613 --- /dev/null +++ b/launcher/java/providers/AzulJavaDownloader.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "java/providers/BasicJavaDownloader.h" + +class AzulJavaDownloader : public BasicJavaDownloader { + Q_OBJECT + public: + void executeTask() override; + + virtual QString name() const override { return "Azul"; }; + virtual bool isSupported() const override; + private slots: + void downloadJavaList(); + void downloadJava(const QJsonArray& doc); + void extractJava(QString input, QString subdirectory); + + private: + QString getArch() const; +}; \ No newline at end of file diff --git a/launcher/java/providers/BasicJavaDownloader.cpp b/launcher/java/providers/BasicJavaDownloader.cpp new file mode 100644 index 0000000000..9f353d9c19 --- /dev/null +++ b/launcher/java/providers/BasicJavaDownloader.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "java/providers/BasicJavaDownloader.h" + +#include "SysInfo.h" +#include "tasks/Task.h" + +BasicJavaDownloader::BasicJavaDownloader(QString final_path, bool m_is_legacy, QObject* parent) + : Task(parent) + , m_os_name(SysInfo::currentSystem()) + , m_os_arch(SysInfo::useQTForArch()) + , m_final_path(final_path) + , m_is_legacy(m_is_legacy) +{} diff --git a/launcher/java/providers/BasicJavaDownloader.h b/launcher/java/providers/BasicJavaDownloader.h new file mode 100644 index 0000000000..34c944ec89 --- /dev/null +++ b/launcher/java/providers/BasicJavaDownloader.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "tasks/Task.h" + +class BasicJavaDownloader : public Task { + Q_OBJECT + public: + BasicJavaDownloader(QString final_path, bool m_is_legacy = false, QObject* parent = nullptr); + virtual ~BasicJavaDownloader() = default; + + [[nodiscard]] bool canAbort() const override { return true; } + + virtual QString name() const = 0; + virtual bool isSupported() const = 0; + + protected: + QString m_os_name; + QString m_os_arch; + QString m_final_path; + bool m_is_legacy; + + Task::Ptr m_current_task; +}; \ No newline at end of file diff --git a/launcher/java/providers/MojangJavaDownloader.cpp b/launcher/java/providers/MojangJavaDownloader.cpp new file mode 100644 index 0000000000..fc2a17e94a --- /dev/null +++ b/launcher/java/providers/MojangJavaDownloader.cpp @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "java/providers/MojanglJavaDownloader.h" + +#include "Application.h" +#include "FileSystem.h" +#include "Json.h" +#include "net/ChecksumValidator.h" +#include "net/NetJob.h" + +struct File { + QString path; + QString url; + QByteArray hash; + bool isExec; +}; + +void MojangJavaDownloader::executeTask() +{ + downloadJavaList(); +}; + +void MojangJavaDownloader::downloadJavaList() +{ + auto netJob = makeShared(QString("JRE::QueryVersions"), APPLICATION->network()); + auto response = std::make_shared(); + setStatus(tr("Querying mojang meta")); + netJob->addNetAction(Net::Download::makeByteArray( + QUrl("https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"), response)); + + connect(netJob.get(), &NetJob::finished, [netJob, this] { + // delete so that it's not called on a deleted job + // FIXME: is this needed? qt should handle this + disconnect(this, &Task::aborted, netJob.get(), &NetJob::abort); + }); + connect(this, &Task::aborted, netJob.get(), &NetJob::abort); + + connect(netJob.get(), &NetJob::progress, this, &MojangJavaDownloader::progress); + connect(netJob.get(), &NetJob::failed, this, &MojangJavaDownloader::emitFailed); + connect(netJob.get(), &NetJob::succeeded, [response, this, netJob] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << *response; + emitFailed(parse_error.errorString()); + return; + } + auto versionArray = Json::ensureArray(Json::ensureObject(doc.object(), getOS()), m_is_legacy ? "jre-legacy" : "java-runtime-gamma"); + if (!versionArray.empty()) { + parseManifest(versionArray); + + } else { + // mojang does not have a JRE for us, so fail + emitFailed("No suitable JRE found"); + } + }); + + netJob->start(); +}; + +QString MojangJavaDownloader::getOS() const +{ + if (m_os_name == "windows") { + if (m_os_arch == "x86_64") { + return "windows-x64"; + } + if (m_os_arch == "i386") { + return "windows-x86"; + } + // Unknown, maybe arm, appending arch for downloader + return "windows-" + m_os_arch; + } + if (m_os_name == "osx") { + if (m_os_arch == "arm64") { + return "mac-os-arm64"; + } + return "mac-os"; + } + if (m_os_name == "linux") { + if (m_os_arch == "x86_64") { + return "linux"; + } + // will work for i386, and arm(64) + return "linux-" + m_os_arch; + } + return {}; +} +void MojangJavaDownloader::parseManifest(const QJsonArray& versionArray) +{ + setStatus(tr("Downloading Java from Mojang")); + auto url = Json::ensureString(Json::ensureObject(Json::ensureObject(versionArray[0]), "manifest"), "url"); + auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); + auto files = std::make_shared(); + + download->addNetAction(Net::Download::makeByteArray(QUrl(url), files)); + + connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); + connect(download.get(), &NetJob::progress, this, &MojangJavaDownloader::progress); + connect(download.get(), &NetJob::failed, this, &MojangJavaDownloader::emitFailed); + connect(this, &Task::aborted, download.get(), &NetJob::abort); + + connect(download.get(), &NetJob::succeeded, [files, this] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << *files; + emitFailed(parse_error.errorString()); + return; + } + downloadJava(doc); + }); + download->start(); +}; + +void MojangJavaDownloader::downloadJava(const QJsonDocument& doc) +{ + // valid json doc, begin making jre spot + FS::ensureFolderPathExists(m_final_path); + std::vector toDownload; + auto list = Json::ensureObject(Json::ensureObject(doc.object()), "files"); + for (const auto& paths : list.keys()) { + auto file = FS::PathCombine(m_final_path, paths); + + const QJsonObject& meta = Json::ensureObject(list, paths); + auto type = Json::ensureString(meta, "type"); + if (type == "directory") { + FS::ensureFolderPathExists(file); + } else if (type == "link") { + // this is linux only ! + auto path = Json::ensureString(meta, "target"); + if (!path.isEmpty()) { + auto target = FS::PathCombine(file, "../" + path); + QFile(target).link(file); + } + } else if (type == "file") { + // TODO download compressed version if it exists ? + auto raw = Json::ensureObject(Json::ensureObject(meta, "downloads"), "raw"); + auto isExec = Json::ensureBoolean(meta, "executable", false); + auto url = Json::ensureString(raw, "url"); + if (!url.isEmpty() && QUrl(url).isValid()) { + auto f = File{ file, url, QByteArray::fromHex(Json::ensureString(raw, "sha1").toLatin1()), isExec }; + toDownload.push_back(f); + } + } + } + auto elementDownload = new NetJob("JRE::FileDownload", APPLICATION->network()); + for (const auto& file : toDownload) { + auto dl = Net::Download::makeFile(file.url, file.path); + if (!file.hash.isEmpty()) { + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.hash)); + } + if (file.isExec) { + connect(dl.get(), &Net::Download::succeeded, + [file] { QFile(file.path).setPermissions(QFile(file.path).permissions() | QFileDevice::Permissions(0x1111)); }); + } + elementDownload->addNetAction(dl); + } + connect(elementDownload, &NetJob::finished, [elementDownload, this] { + disconnect(this, &Task::aborted, elementDownload, &NetJob::abort); + elementDownload->deleteLater(); + }); + connect(elementDownload, &NetJob::progress, this, &MojangJavaDownloader::progress); + connect(elementDownload, &NetJob::failed, this, &MojangJavaDownloader::emitFailed); + + connect(this, &Task::aborted, elementDownload, &NetJob::abort); + connect(elementDownload, &NetJob::succeeded, [this] { emitSucceeded(); }); + elementDownload->start(); +}; diff --git a/launcher/java/providers/MojanglJavaDownloader.h b/launcher/java/providers/MojanglJavaDownloader.h new file mode 100644 index 0000000000..a1b5b17657 --- /dev/null +++ b/launcher/java/providers/MojanglJavaDownloader.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "java/providers/BasicJavaDownloader.h" + +class MojangJavaDownloader : public BasicJavaDownloader { + Q_OBJECT + public: + void executeTask() override; + + virtual QString name() const override { return "Mojang"; }; + virtual bool isSupported() const override { return !getOS().isEmpty(); }; + private slots: + void downloadJavaList(); + void parseManifest(const QJsonArray& versionArray); + void downloadJava(const QJsonDocument& doc); + + private: + QString getOS() const; +}; \ No newline at end of file diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp index 81337a88e2..c776092fe6 100644 --- a/launcher/launch/steps/CheckJava.cpp +++ b/launcher/launch/steps/CheckJava.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include "java/JavaUtils.h" @@ -90,11 +91,10 @@ void CheckJava::executeTask() // if timestamps are not the same, or something is missing, check! if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 || storedRealArchitecture.size() == 0 || storedVendor.size() == 0) { - m_JavaChecker.reset(new JavaChecker); + m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0, this)); emit logLine(QString("Checking Java version..."), MessageLevel::Launcher); connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished); - m_JavaChecker->m_path = realJavaPath; - m_JavaChecker->performCheck(); + m_JavaChecker->start(); return; } else { auto verString = instance->settings()->get("JavaVersion").toString(); @@ -106,10 +106,10 @@ void CheckJava::executeTask() emitSucceeded(); } -void CheckJava::checkJavaFinished(JavaCheckResult result) +void CheckJava::checkJavaFinished(JavaChecker::Result result) { switch (result.validity) { - case JavaCheckResult::Validity::Errored: { + case JavaChecker::Result::Validity::Errored: { // Error message displayed if java can't start emit logLine(QString("Could not start java:"), MessageLevel::Error); emit logLines(result.errorLog.split('\n'), MessageLevel::Error); @@ -117,14 +117,14 @@ void CheckJava::checkJavaFinished(JavaCheckResult result) emitFailed(QString("Could not start java!")); return; } - case JavaCheckResult::Validity::ReturnedInvalidData: { + case JavaChecker::Result::Validity::ReturnedInvalidData: { emit logLine(QString("Java checker returned some invalid data we don't understand:"), MessageLevel::Error); emit logLines(result.outLog.split('\n'), MessageLevel::Warning); emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher); emitSucceeded(); return; } - case JavaCheckResult::Validity::Valid: { + case JavaChecker::Result::Validity::Valid: { auto instance = m_parent->instance(); printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor); instance->settings()->set("JavaVersion", result.javaVersion.toString()); diff --git a/launcher/launch/steps/CheckJava.h b/launcher/launch/steps/CheckJava.h index 4436e2a551..5ba9e4018a 100644 --- a/launcher/launch/steps/CheckJava.h +++ b/launcher/launch/steps/CheckJava.h @@ -28,7 +28,7 @@ class CheckJava : public LaunchStep { virtual void executeTask(); virtual bool canAbort() const { return false; } private slots: - void checkJavaFinished(JavaCheckResult result); + void checkJavaFinished(JavaChecker::Result result); private: void printJavaInfo(const QString& version, const QString& architecture, const QString& realArchitecture, const QString& vendor); @@ -37,5 +37,5 @@ class CheckJava : public LaunchStep { private: QString m_javaPath; QString m_javaSignature; - JavaCheckerPtr m_JavaChecker; + JavaChecker::Ptr m_JavaChecker; }; diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 63d7aa57ca..be6b195dcc 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -14,7 +14,9 @@ #include "FileSystem.h" #include "JavaCommon.h" #include "JavaDownloader.h" +#include "java/JavaChecker.h" #include "java/JavaInstall.h" +#include "java/JavaInstallList.h" #include "java/JavaUtils.h" #include "ui/dialogs/CustomMessageBox.h" @@ -368,30 +370,25 @@ void JavaSettingsWidget::checkJavaPath(const QString& path) return; } setJavaStatus(JavaStatus::Pending); - m_checker.reset(new JavaChecker()); - m_checker->m_path = path; - m_checker->m_minMem = minHeapSize(); - m_checker->m_maxMem = maxHeapSize(); - if (m_permGenSpinBox->isVisible()) { - m_checker->m_permGen = m_permGenSpinBox->value(); - } + m_checker.reset( + new JavaChecker(path, "", minHeapSize(), maxHeapSize(), m_permGenSpinBox->isVisible() ? m_permGenSpinBox->value() : 0, 0, this)); connect(m_checker.get(), &JavaChecker::checkFinished, this, &JavaSettingsWidget::checkFinished); - m_checker->performCheck(); + m_checker->start(); } -void JavaSettingsWidget::checkFinished(JavaCheckResult result) +void JavaSettingsWidget::checkFinished(JavaChecker::Result result) { m_result = result; switch (result.validity) { - case JavaCheckResult::Validity::Valid: { + case JavaChecker::Result::Validity::Valid: { setJavaStatus(JavaStatus::Good); break; } - case JavaCheckResult::Validity::ReturnedInvalidData: { + case JavaChecker::Result::Validity::ReturnedInvalidData: { setJavaStatus(JavaStatus::ReturnedInvalidData); break; } - case JavaCheckResult::Validity::Errored: { + case JavaChecker::Result::Validity::Errored: { setJavaStatus(JavaStatus::DoesNotStart); break; } diff --git a/launcher/ui/widgets/JavaSettingsWidget.h b/launcher/ui/widgets/JavaSettingsWidget.h index 0e05bf3414..d3cd2c5a70 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.h +++ b/launcher/ui/widgets/JavaSettingsWidget.h @@ -51,7 +51,7 @@ class JavaSettingsWidget : public QWidget { void on_javaBrowseBtn_clicked(); void on_javaStatusBtn_clicked(); void on_javaDownloadBtn_clicked(); - void checkFinished(JavaCheckResult result); + void checkFinished(JavaChecker::Result result); protected: /* methods */ void checkJavaPathOnEdit(const QString& path); @@ -89,5 +89,5 @@ class JavaSettingsWidget : public QWidget { QString queuedCheck; uint64_t m_availableMemory = 0ull; shared_qobject_ptr m_checker; - JavaCheckResult m_result; + JavaChecker::Result m_result; }; From 2c18d0f1a511c23898bb8f82d57c99458712bc30 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 25 Jan 2024 15:41:34 +0000 Subject: [PATCH 0116/2054] Store current version in packwiz metadata (temporarily using `x-prismlauncher-version-number`) Signed-off-by: TheKodeToad --- launcher/modplatform/packwiz/Packwiz.cpp | 9 ++++++--- launcher/modplatform/packwiz/Packwiz.h | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index d8d496af7a..f68624c1a3 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -91,8 +91,9 @@ auto intEntry(toml::table table, QString entry_name) -> int return node.value_or(0); } -auto V1::createModFormat([[maybe_unused]] const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) - -> Mod +auto V1::createModFormat([[maybe_unused]] const QDir& index_dir, + ModPlatform::IndexedPack& mod_pack, + ModPlatform::IndexedVersion& mod_version) -> Mod { Mod mod; @@ -111,6 +112,7 @@ auto V1::createModFormat([[maybe_unused]] const QDir& index_dir, ModPlatform::In mod.hash = mod_version.hash; mod.provider = mod_pack.provider; + mod.version_number = mod_version.version_number; mod.file_id = mod_version.fileId; mod.project_id = mod_pack.addonId; mod.side = stringToSide(mod_pack.side); @@ -199,7 +201,8 @@ void V1::updateModIndex(const QDir& index_dir, Mod& mod) { "hash-format", mod.hash_format.toStdString() }, { "hash", mod.hash.toStdString() }, } }, - { "update", toml::table{ { ProviderCaps.name(mod.provider), update } } } }; + { "update", toml::table{ { ProviderCaps.name(mod.provider), update }, + { "x-prismlauncher-version-number", mod.version_number.toStdString() } } } }; std::stringstream ss; ss << tbl; in_stream << QString::fromStdString(ss.str()); diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 97c8f4b165..07bb4248d1 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -52,6 +52,7 @@ class V1 { // [update] ModPlatform::ResourceProvider provider{}; + QString version_number{}; QVariant file_id{}; QVariant project_id{}; From e89703238365cbcca8cce19b923a28bbb2057516 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 25 Jan 2024 22:22:21 +0200 Subject: [PATCH 0117/2054] Added java metadata Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 3 +++ launcher/java/JavaVersion.cpp | 20 +++++++++++++++++++ launcher/java/JavaVersion.h | 4 ++++ launcher/minecraft/MojangVersionFormat.cpp | 6 ++++++ launcher/minecraft/OneSixVersionFormat.cpp | 14 +++++++++++++ launcher/minecraft/VersionFile.h | 8 ++++++++ .../ui/pages/instance/InstanceSettingsPage.ui | 2 -- 7 files changed, 55 insertions(+), 2 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e735f20817..2af33178d8 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -430,6 +430,9 @@ set(JAVA_SOURCES java/JavaUtils.cpp java/JavaVersion.h java/JavaVersion.cpp + + java/JavaRuntime.h + java/JavaRuntime.cpp ) set(TRANSLATIONS_SOURCES diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp index b77bf2adfe..3de6f5ad6a 100644 --- a/launcher/java/JavaVersion.cpp +++ b/launcher/java/JavaVersion.cpp @@ -109,3 +109,23 @@ bool JavaVersion::operator>(const JavaVersion& rhs) { return (!operator<(rhs)) && (!operator==(rhs)); } + +JavaVersion::JavaVersion(int major, int minor, int security, int build, QString name) + : m_major(major), m_minor(minor), m_security(security), m_name(name), m_parseable(true) +{ + if (build != 0) { + m_prerelease = QString::number(build); + m_string = m_prerelease; + } + if (m_security != 0) + m_string = QString::number(m_security) + "." + m_string; + else if (!m_string.isEmpty()) { + m_string = "0." + m_string; + } + if (m_minor != 0) + m_string = QString::number(m_minor) + "." + m_string; + else if (!m_string.isEmpty()) { + m_string = "0." + m_string; + } + m_string = QString::number(m_major) + "." + m_string; +} diff --git a/launcher/java/JavaVersion.h b/launcher/java/JavaVersion.h index 421578ea1a..eee992346d 100644 --- a/launcher/java/JavaVersion.h +++ b/launcher/java/JavaVersion.h @@ -16,6 +16,7 @@ class JavaVersion { public: JavaVersion() {} JavaVersion(const QString& rhs); + JavaVersion(int major, int minor, int security, int build = 0, QString name = ""); JavaVersion& operator=(const QString& rhs); @@ -32,12 +33,15 @@ class JavaVersion { int major() { return m_major; } int minor() { return m_minor; } int security() { return m_security; } + QString build() { return m_prerelease; } + QString name() { return m_name; } private: QString m_string; int m_major = 0; int m_minor = 0; int m_security = 0; + QString m_name = ""; bool m_parseable = false; QString m_prerelease; }; diff --git a/launcher/minecraft/MojangVersionFormat.cpp b/launcher/minecraft/MojangVersionFormat.cpp index bb782e47fe..d17a3a21f0 100644 --- a/launcher/minecraft/MojangVersionFormat.cpp +++ b/launcher/minecraft/MojangVersionFormat.cpp @@ -185,6 +185,9 @@ void MojangVersionFormat::readVersionProperties(const QJsonObject& in, VersionFi out->compatibleJavaMajors.append(requireInteger(compatible)); } } + if (in.contains("compatibleJavaName")) { + out->compatibleJavaName = requireString(in.value("compatibleJavaName")); + } if (in.contains("downloads")) { auto downloadsObj = requireObject(in, "downloads"); @@ -259,6 +262,9 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj } out.insert("compatibleJavaMajors", compatibleJavaMajorsOut); } + if (!in->compatibleJavaName.isEmpty()) { + writeString(out, "compatibleJavaName", in->compatibleJavaName); + } } QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr& patch) diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp index 306c95a6ae..5f3b4f2a2f 100644 --- a/launcher/minecraft/OneSixVersionFormat.cpp +++ b/launcher/minecraft/OneSixVersionFormat.cpp @@ -36,6 +36,8 @@ #include "OneSixVersionFormat.h" #include #include +#include +#include "java/JavaRuntime.h" #include "minecraft/Agent.h" #include "minecraft/ParseUtils.h" @@ -255,6 +257,18 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc out->m_volatile = requireBoolean(root, "volatile"); } + if (root.contains("runtimes")) { + auto runtimes = requireObject(root, "runtimes"); + out->runtimes = {}; + for (auto key : runtimes.keys()) { + QList list; + for (auto runtime : ensureArray(runtimes, key)) { + list.append(JavaRuntime::parseJavaMeta(ensureObject(runtime))); + } + out->runtimes[key] = list; + } + } + /* removed features that shouldn't be used */ if (root.contains("tweakers")) { out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element 'tweakers'")); diff --git a/launcher/minecraft/VersionFile.h b/launcher/minecraft/VersionFile.h index 280e35ee34..297c19709b 100644 --- a/launcher/minecraft/VersionFile.h +++ b/launcher/minecraft/VersionFile.h @@ -36,6 +36,8 @@ #pragma once #include +#include +#include #include #include #include @@ -45,6 +47,7 @@ #include "Agent.h" #include "Library.h" #include "ProblemProvider.h" +#include "java/JavaRuntime.h" #include "minecraft/Rule.h" class PackProfile; @@ -98,6 +101,9 @@ class VersionFile : public ProblemContainer { /// Mojang: list of compatible java majors QList compatibleJavaMajors; + /// Mojang: the name of recomended java version + QString compatibleJavaName; + /// Mojang: type of the Minecraft version QString type; @@ -149,6 +155,8 @@ class VersionFile : public ProblemContainer { /// is volatile -- may be removed as soon as it is no longer needed by something else bool m_volatile = false; + QHash> runtimes; + public: // Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more. QMap> mojangDownloads; diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index cb37366ccb..7762ca8a7e 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -796,8 +796,6 @@ enableFeralGamemodeCheck enableMangoHud useDiscreteGpuCheck - modLoaderSettingsGroupBox - disableQuiltBeaconCheckBox gameTimeGroupBox serverJoinGroupBox serverJoinAddress From f36be3f0e3a32cb5f5711a727ecd07cf1c28b8dd Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 25 Jan 2024 22:58:12 +0200 Subject: [PATCH 0118/2054] Cleanup downloaders Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 4 + launcher/java/JavaRuntime.cpp | 56 ++++++ launcher/java/JavaRuntime.h | 30 ++++ .../ArchiveJavaDownloader.cpp} | 53 ++---- .../ArchiveJavaDownloader.h} | 16 +- .../ManifestJavaDownloader.cpp} | 93 ++-------- .../ManifestJavaDownloader.h} | 20 ++- .../java/providers/AdoptiumJavaDownloader.h | 36 ---- .../java/providers/AzulJavaDownloader.cpp | 159 ------------------ launcher/java/providers/AzulJavaDownloader.h | 37 ---- .../java/providers/BasicJavaDownloader.cpp | 29 ---- 11 files changed, 132 insertions(+), 401 deletions(-) create mode 100644 launcher/java/JavaRuntime.cpp create mode 100644 launcher/java/JavaRuntime.h rename launcher/java/{providers/AdoptiumJavaDownloader.cpp => download/ArchiveJavaDownloader.cpp} (63%) rename launcher/java/{providers/BasicJavaDownloader.h => download/ArchiveJavaDownloader.h} (74%) rename launcher/java/{providers/MojangJavaDownloader.cpp => download/ManifestJavaDownloader.cpp} (55%) rename launcher/java/{providers/MojanglJavaDownloader.h => download/ManifestJavaDownloader.h} (70%) delete mode 100644 launcher/java/providers/AdoptiumJavaDownloader.h delete mode 100644 launcher/java/providers/AzulJavaDownloader.cpp delete mode 100644 launcher/java/providers/AzulJavaDownloader.h delete mode 100644 launcher/java/providers/BasicJavaDownloader.cpp diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 2af33178d8..ac3f8eb68a 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -433,6 +433,10 @@ set(JAVA_SOURCES java/JavaRuntime.h java/JavaRuntime.cpp + java/download/ArchiveJavaDownloader.cpp + java/download/ArchiveJavaDownloader.h + java/download/ManifestJavaDownloader.cpp + java/download/ManifestJavaDownloader.h ) set(TRANSLATIONS_SOURCES diff --git a/launcher/java/JavaRuntime.cpp b/launcher/java/JavaRuntime.cpp new file mode 100644 index 0000000000..0de8c839bf --- /dev/null +++ b/launcher/java/JavaRuntime.cpp @@ -0,0 +1,56 @@ +#include "java/JavaRuntime.h" + +#include + +#include "Json.h" +#include "java/JavaVersion.h" +#include "minecraft/ParseUtils.h" + +namespace JavaRuntime { + +DownloadType parseDownloadType(QString javaDownload) +{ + if (javaDownload == "manifest") + return DownloadType::Manifest; + // if (javaDownload == "archive") + return DownloadType::Archive; +} +QString downloadTypeToString(DownloadType javaDownload) +{ + switch (javaDownload) { + case DownloadType::Manifest: + return "manifest"; + case DownloadType::Archive: + return "archive"; + } +} +MetaPtr parseJavaMeta(const QJsonObject& in) +{ + auto meta = std::make_shared(); + + meta->name = Json::ensureString(in, "name", ""); + meta->vendor = Json::ensureString(in, "vendor", ""); + meta->url = Json::ensureString(in, "url", ""); + meta->releaseTime = timeFromS3Time(Json::ensureString(in, "releaseTime", "")); + meta->recommended = Json::ensureBoolean(in, "recommended", false); + meta->downloadType = parseDownloadType(Json::ensureString(in, "downloadType", "")); + meta->packageType = Json::ensureString(in, "packageType", ""); + + if (in.contains("checksum")) { + auto obj = Json::requireObject(in, "checksum"); + meta->checksumHash = Json::ensureString(obj, "hash", ""); + meta->checksumType = Json::ensureString(obj, "type", ""); + } + + if (in.contains("version")) { + auto obj = Json::requireObject(in, "checksum"); + auto name = Json::ensureString(obj, "name", ""); + auto major = Json::ensureInteger(obj, "major", 0); + auto minor = Json::ensureInteger(obj, "minor", 0); + auto security = Json::ensureInteger(obj, "security", 0); + auto build = Json::ensureInteger(obj, "build", 0); + meta->version = JavaVersion(major, minor, security, build, name); + } + return meta; +} +} // namespace JavaRuntime diff --git a/launcher/java/JavaRuntime.h b/launcher/java/JavaRuntime.h new file mode 100644 index 0000000000..1ea5d2e48c --- /dev/null +++ b/launcher/java/JavaRuntime.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "java/JavaVersion.h" + +namespace JavaRuntime { + +enum class DownloadType { Manifest, Archive }; + +struct Meta { + QString name; + QString vendor; + QString url; + QDateTime releaseTime; + QString checksumType; + QString checksumHash; + bool recommended; + DownloadType downloadType; + QString packageType; + JavaVersion version; +}; +using MetaPtr = std::shared_ptr; + +DownloadType parseDownloadType(QString javaDownload); +QString downloadTypeToString(DownloadType javaDownload); +MetaPtr parseJavaMeta(const QJsonObject& libObj); + +} // namespace JavaRuntime \ No newline at end of file diff --git a/launcher/java/providers/AdoptiumJavaDownloader.cpp b/launcher/java/download/ArchiveJavaDownloader.cpp similarity index 63% rename from launcher/java/providers/AdoptiumJavaDownloader.cpp rename to launcher/java/download/ArchiveJavaDownloader.cpp index 4f8499d4a8..844be1dce9 100644 --- a/launcher/java/providers/AdoptiumJavaDownloader.cpp +++ b/launcher/java/download/ArchiveJavaDownloader.cpp @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "java/providers/AdoptiumJavaDownloader.h" +#include "java/download/ArchiveJavaDownloader.h" #include #include #include "MMCZip.h" @@ -24,39 +24,21 @@ #include "net/NetJob.h" #include "tasks/Task.h" -void AdoptiumJavaDownloader::executeTask() -{ - downloadJava(); -}; - -QString AdoptiumJavaDownloader::getArch() const -{ - if (m_os_arch == "arm64") - return "aarch64"; - if (m_os_arch.isEmpty()) - return "x86"; - return m_os_arch; -} - -void AdoptiumJavaDownloader::downloadJava() +void ArchiveJavaDownloader::executeTask() { // JRE found ! download the zip - setStatus(tr("Downloading Java from Adoptium")); + setStatus(tr("Downloading Java")); - auto javaVersion = m_is_legacy ? QString("8") : QString("17"); - auto azulOS = m_os_name == "osx" ? "mac" : m_os_name; - auto arch = getArch(); - MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("java", "adoptiumJRE.zip"); + MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("java", m_url.toLocalFile()); auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); - download->addNetAction(Net::Download::makeCached( - QString("https://api.adoptium.net/v3/binary/latest/%1/ga/%2/%3/jre/hotspot/normal/eclipse").arg(javaVersion, azulOS, arch), entry)); + download->addNetAction(Net::Download::makeCached(m_url, entry)); auto fullPath = entry->getFullPath(); connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); // connect(download.get(), &NetJob::aborted, [path] { APPLICATION->instances()->destroyStagingPath(path); }); - connect(download.get(), &NetJob::progress, this, &AdoptiumJavaDownloader::progress); - connect(download.get(), &NetJob::failed, this, &AdoptiumJavaDownloader::emitFailed); + connect(download.get(), &NetJob::progress, this, &ArchiveJavaDownloader::progress); + connect(download.get(), &NetJob::failed, this, &ArchiveJavaDownloader::emitFailed); connect(this, &Task::aborted, download.get(), &NetJob::abort); connect(download.get(), &NetJob::succeeded, [this, fullPath] { // This should do all of the extracting and creating folders @@ -65,7 +47,7 @@ void AdoptiumJavaDownloader::downloadJava() download->start(); }; -void AdoptiumJavaDownloader::extractJava(QString input) +void ArchiveJavaDownloader::extractJava(QString input) { setStatus(tr("Extracting java")); auto zip = std::make_shared(input); @@ -85,14 +67,14 @@ void AdoptiumJavaDownloader::extractJava(QString input) connect(this, &Task::aborted, zipTask.get(), &Task::abort); connect(zipTask.get(), &Task::finished, [zipTask, this] { disconnect(this, &Task::aborted, zipTask.get(), &Task::abort); }); - connect(zipTask.get(), &Task::succeeded, this, &AdoptiumJavaDownloader::emitSucceeded); - connect(zipTask.get(), &Task::aborted, this, &AdoptiumJavaDownloader::emitAborted); + connect(zipTask.get(), &Task::succeeded, this, &ArchiveJavaDownloader::emitSucceeded); + connect(zipTask.get(), &Task::aborted, this, &ArchiveJavaDownloader::emitAborted); connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) { progressStep->state = TaskStepState::Failed; stepProgress(*progressStep); emitFailed(reason); }); - connect(zipTask.get(), &Task::stepProgress, this, &AdoptiumJavaDownloader::propagateStepProgress); + connect(zipTask.get(), &Task::stepProgress, this, &ArchiveJavaDownloader::propagateStepProgress); connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { progressStep->update(current, total); @@ -104,16 +86,3 @@ void AdoptiumJavaDownloader::extractJava(QString input) }); zipTask->start(); }; - -static const QStringList supportedOs = { - "linux", "windows", "mac", "solaris", "aix", "alpine-linux", -}; - -static const QStringList supportedArch = { - "x64", "x86", "x32", "ppc64", "ppc64le", "s390x", "aarch64", "arm", "sparcv9", "riscv64", -}; - -bool AdoptiumJavaDownloader::isSupported() const -{ - return supportedOs.contains(m_os_name == "osx" ? "mac" : m_os_name) && supportedArch.contains(getArch()); -}; \ No newline at end of file diff --git a/launcher/java/providers/BasicJavaDownloader.h b/launcher/java/download/ArchiveJavaDownloader.h similarity index 74% rename from launcher/java/providers/BasicJavaDownloader.h rename to launcher/java/download/ArchiveJavaDownloader.h index 34c944ec89..d176570864 100644 --- a/launcher/java/providers/BasicJavaDownloader.h +++ b/launcher/java/download/ArchiveJavaDownloader.h @@ -18,24 +18,24 @@ #pragma once +#include #include "tasks/Task.h" -class BasicJavaDownloader : public Task { +class ArchiveJavaDownloader : public Task { Q_OBJECT public: - BasicJavaDownloader(QString final_path, bool m_is_legacy = false, QObject* parent = nullptr); - virtual ~BasicJavaDownloader() = default; + ArchiveJavaDownloader(QUrl url, QString final_path); + virtual ~ArchiveJavaDownloader() = default; [[nodiscard]] bool canAbort() const override { return true; } + void executeTask() override; - virtual QString name() const = 0; - virtual bool isSupported() const = 0; + private slots: + void extractJava(QString input); protected: - QString m_os_name; - QString m_os_arch; + QUrl m_url; QString m_final_path; - bool m_is_legacy; Task::Ptr m_current_task; }; \ No newline at end of file diff --git a/launcher/java/providers/MojangJavaDownloader.cpp b/launcher/java/download/ManifestJavaDownloader.cpp similarity index 55% rename from launcher/java/providers/MojangJavaDownloader.cpp rename to launcher/java/download/ManifestJavaDownloader.cpp index fc2a17e94a..4f7dab94e5 100644 --- a/launcher/java/providers/MojangJavaDownloader.cpp +++ b/launcher/java/download/ManifestJavaDownloader.cpp @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "java/providers/MojanglJavaDownloader.h" +#include "java/download/ManifestJavaDownloader.h" #include "Application.h" #include "FileSystem.h" @@ -30,89 +30,18 @@ struct File { bool isExec; }; -void MojangJavaDownloader::executeTask() +ManifestJavaDownloader::ManifestJavaDownloader(QUrl url, QString final_path) : m_url(url), m_final_path(final_path){}; +void ManifestJavaDownloader::executeTask() { - downloadJavaList(); -}; - -void MojangJavaDownloader::downloadJavaList() -{ - auto netJob = makeShared(QString("JRE::QueryVersions"), APPLICATION->network()); - auto response = std::make_shared(); - setStatus(tr("Querying mojang meta")); - netJob->addNetAction(Net::Download::makeByteArray( - QUrl("https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"), response)); - - connect(netJob.get(), &NetJob::finished, [netJob, this] { - // delete so that it's not called on a deleted job - // FIXME: is this needed? qt should handle this - disconnect(this, &Task::aborted, netJob.get(), &NetJob::abort); - }); - connect(this, &Task::aborted, netJob.get(), &NetJob::abort); - - connect(netJob.get(), &NetJob::progress, this, &MojangJavaDownloader::progress); - connect(netJob.get(), &NetJob::failed, this, &MojangJavaDownloader::emitFailed); - connect(netJob.get(), &NetJob::succeeded, [response, this, netJob] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << *response; - emitFailed(parse_error.errorString()); - return; - } - auto versionArray = Json::ensureArray(Json::ensureObject(doc.object(), getOS()), m_is_legacy ? "jre-legacy" : "java-runtime-gamma"); - if (!versionArray.empty()) { - parseManifest(versionArray); - - } else { - // mojang does not have a JRE for us, so fail - emitFailed("No suitable JRE found"); - } - }); - - netJob->start(); -}; - -QString MojangJavaDownloader::getOS() const -{ - if (m_os_name == "windows") { - if (m_os_arch == "x86_64") { - return "windows-x64"; - } - if (m_os_arch == "i386") { - return "windows-x86"; - } - // Unknown, maybe arm, appending arch for downloader - return "windows-" + m_os_arch; - } - if (m_os_name == "osx") { - if (m_os_arch == "arm64") { - return "mac-os-arm64"; - } - return "mac-os"; - } - if (m_os_name == "linux") { - if (m_os_arch == "x86_64") { - return "linux"; - } - // will work for i386, and arm(64) - return "linux-" + m_os_arch; - } - return {}; -} -void MojangJavaDownloader::parseManifest(const QJsonArray& versionArray) -{ - setStatus(tr("Downloading Java from Mojang")); - auto url = Json::ensureString(Json::ensureObject(Json::ensureObject(versionArray[0]), "manifest"), "url"); + setStatus(tr("Downloading Java")); auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); auto files = std::make_shared(); - download->addNetAction(Net::Download::makeByteArray(QUrl(url), files)); + download->addNetAction(Net::Download::makeByteArray(m_url, files)); connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); - connect(download.get(), &NetJob::progress, this, &MojangJavaDownloader::progress); - connect(download.get(), &NetJob::failed, this, &MojangJavaDownloader::emitFailed); + connect(download.get(), &NetJob::progress, this, &ManifestJavaDownloader::progress); + connect(download.get(), &NetJob::failed, this, &ManifestJavaDownloader::emitFailed); connect(this, &Task::aborted, download.get(), &NetJob::abort); connect(download.get(), &NetJob::succeeded, [files, this] { @@ -129,7 +58,7 @@ void MojangJavaDownloader::parseManifest(const QJsonArray& versionArray) download->start(); }; -void MojangJavaDownloader::downloadJava(const QJsonDocument& doc) +void ManifestJavaDownloader::downloadJava(const QJsonDocument& doc) { // valid json doc, begin making jre spot FS::ensureFolderPathExists(m_final_path); @@ -176,10 +105,10 @@ void MojangJavaDownloader::downloadJava(const QJsonDocument& doc) disconnect(this, &Task::aborted, elementDownload, &NetJob::abort); elementDownload->deleteLater(); }); - connect(elementDownload, &NetJob::progress, this, &MojangJavaDownloader::progress); - connect(elementDownload, &NetJob::failed, this, &MojangJavaDownloader::emitFailed); + connect(elementDownload, &NetJob::progress, this, &ManifestJavaDownloader::progress); + connect(elementDownload, &NetJob::failed, this, &ManifestJavaDownloader::emitFailed); connect(this, &Task::aborted, elementDownload, &NetJob::abort); connect(elementDownload, &NetJob::succeeded, [this] { emitSucceeded(); }); elementDownload->start(); -}; +}; \ No newline at end of file diff --git a/launcher/java/providers/MojanglJavaDownloader.h b/launcher/java/download/ManifestJavaDownloader.h similarity index 70% rename from launcher/java/providers/MojanglJavaDownloader.h rename to launcher/java/download/ManifestJavaDownloader.h index a1b5b17657..44e9ed9de4 100644 --- a/launcher/java/providers/MojanglJavaDownloader.h +++ b/launcher/java/download/ManifestJavaDownloader.h @@ -18,20 +18,24 @@ #pragma once -#include "java/providers/BasicJavaDownloader.h" +#include +#include "tasks/Task.h" -class MojangJavaDownloader : public BasicJavaDownloader { +class ManifestJavaDownloader : public Task { Q_OBJECT public: + ManifestJavaDownloader(QUrl url, QString final_path); + virtual ~ManifestJavaDownloader() = default; + + [[nodiscard]] bool canAbort() const override { return true; } void executeTask() override; - virtual QString name() const override { return "Mojang"; }; - virtual bool isSupported() const override { return !getOS().isEmpty(); }; private slots: - void downloadJavaList(); - void parseManifest(const QJsonArray& versionArray); void downloadJava(const QJsonDocument& doc); - private: - QString getOS() const; + protected: + QUrl m_url; + QString m_final_path; + + Task::Ptr m_current_task; }; \ No newline at end of file diff --git a/launcher/java/providers/AdoptiumJavaDownloader.h b/launcher/java/providers/AdoptiumJavaDownloader.h deleted file mode 100644 index f0ae6239b3..0000000000 --- a/launcher/java/providers/AdoptiumJavaDownloader.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023 Trial97 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include "java/providers/BasicJavaDownloader.h" - -class AdoptiumJavaDownloader : public BasicJavaDownloader { - Q_OBJECT - public: - void executeTask() override; - - virtual QString name() const override { return "Adoptium"; }; - virtual bool isSupported() const override; - private slots: - void downloadJava(); - void extractJava(QString input); - - private: - QString getArch() const; -}; \ No newline at end of file diff --git a/launcher/java/providers/AzulJavaDownloader.cpp b/launcher/java/providers/AzulJavaDownloader.cpp deleted file mode 100644 index 674da592ec..0000000000 --- a/launcher/java/providers/AzulJavaDownloader.cpp +++ /dev/null @@ -1,159 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023 Trial97 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "java/providers/AzulJavaDownloader.h" -#include -#include "MMCZip.h" - -#include "Application.h" -#include "Json.h" -#include "net/NetJob.h" -#include "tasks/Task.h" - -void AzulJavaDownloader::executeTask() -{ - downloadJavaList(); -}; - -void AzulJavaDownloader::downloadJavaList() -{ - setStatus(tr("Querying Azul meta")); - - auto javaVersion = m_is_legacy ? QString("8.0") : QString("17.0"); - auto azulOS = m_os_name == "osx" ? "macos" : m_os_name; - auto arch = getArch(); - auto metaResponse = std::make_shared(); - auto downloadJob = makeShared(QString("JRE::QueryAzulMeta"), APPLICATION->network()); - downloadJob->addNetAction(Net::Download::makeByteArray(QString("https://api.azul.com/metadata/v1/zulu/packages/?" - "java_version=%1" - "&os=%2" - "&arch=%3" - "&archive_type=zip" - "&java_package_type=jre" - "&support_term=lts" - "&latest=true" - "status=ga" - "&availability_types=CA" - "&page=1" - "&page_size=1") - .arg(javaVersion, azulOS, arch), - metaResponse)); - connect(downloadJob.get(), &NetJob::finished, - [downloadJob, metaResponse, this] { disconnect(this, &Task::aborted, downloadJob.get(), &NetJob::abort); }); - connect(this, &Task::aborted, downloadJob.get(), &NetJob::abort); - connect(downloadJob.get(), &NetJob::failed, this, &AzulJavaDownloader::emitFailed); - connect(downloadJob.get(), &NetJob::progress, this, &AzulJavaDownloader::progress); - connect(downloadJob.get(), &NetJob::succeeded, [metaResponse, this] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*metaResponse, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << *metaResponse; - return; - } - auto array = Json::ensureArray(doc.array()); - if (!array.empty()) { - downloadJava(array); - } else { - emitFailed(tr("No suitable JRE found")); - } - }); - downloadJob->start(); -}; - -QString AzulJavaDownloader::getArch() const -{ - if (m_os_arch == "arm64") - return "aarch64"; - if (m_os_arch == "arm") - return "aarch32"; - if (m_os_arch.isEmpty()) - return "x86"; - return m_os_arch; -} - -void AzulJavaDownloader::downloadJava(const QJsonArray& array) -{ - // JRE found ! download the zip - setStatus(tr("Downloading Java from Azul")); - - MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("java", "azulJRE.zip"); - - auto downloadURL = QUrl(array[0].toObject()["url"].toString()); - auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); - download->addNetAction(Net::Download::makeCached(downloadURL, entry)); - auto fullPath = entry->getFullPath(); - - connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); - // connect(download.get(), &NetJob::aborted, [path] { APPLICATION->instances()->destroyStagingPath(path); }); - connect(download.get(), &NetJob::progress, this, &AzulJavaDownloader::progress); - connect(download.get(), &NetJob::failed, this, &AzulJavaDownloader::emitFailed); - connect(this, &Task::aborted, download.get(), &NetJob::abort); - connect(download.get(), &NetJob::succeeded, [downloadURL, this, fullPath] { - // This should do all of the extracting and creating folders - extractJava(fullPath, downloadURL.fileName().chopped(4)); - }); - download->start(); -}; - -void AzulJavaDownloader::extractJava(QString input, QString subdirectory) -{ - setStatus(tr("Extracting java")); - auto zipTask = makeShared(input, m_final_path, subdirectory); - - auto progressStep = std::make_shared(); - connect(zipTask.get(), &Task::finished, this, [this, progressStep] { - progressStep->state = TaskStepState::Succeeded; - stepProgress(*progressStep); - }); - - connect(this, &Task::aborted, zipTask.get(), &Task::abort); - connect(zipTask.get(), &Task::finished, [zipTask, this] { disconnect(this, &Task::aborted, zipTask.get(), &Task::abort); }); - - connect(zipTask.get(), &Task::succeeded, this, &AzulJavaDownloader::emitSucceeded); - connect(zipTask.get(), &Task::aborted, this, &AzulJavaDownloader::emitAborted); - connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) { - progressStep->state = TaskStepState::Failed; - stepProgress(*progressStep); - emitFailed(reason); - }); - connect(zipTask.get(), &Task::stepProgress, this, &AzulJavaDownloader::propagateStepProgress); - - connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { - progressStep->update(current, total); - stepProgress(*progressStep); - }); - connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) { - progressStep->status = status; - stepProgress(*progressStep); - }); - zipTask->start(); -}; - -static const QStringList supportedOs = { - "macos", "linux", "windows", "linux-musl", "linux-glibc", "qnx", "solaris", "aix", -}; - -static const QStringList supportedArch = { - "x86", "x64", "amd64", "i686", "arm", "aarch64", "aarch32", "aarch32sf", "aarch32hf", "ppc", - "ppc64", "ppc32", "ppc32hf", "ppc32spe", "sparc", "sparc64", "sparc32", "sparcv9", "sparcv9-64", "sparcv9-32", -}; - -bool AzulJavaDownloader::isSupported() const -{ - return supportedOs.contains(m_os_name == "osx" ? "macos" : m_os_name) && supportedArch.contains(getArch()); -}; \ No newline at end of file diff --git a/launcher/java/providers/AzulJavaDownloader.h b/launcher/java/providers/AzulJavaDownloader.h deleted file mode 100644 index c4bc8c7613..0000000000 --- a/launcher/java/providers/AzulJavaDownloader.h +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023 Trial97 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include "java/providers/BasicJavaDownloader.h" - -class AzulJavaDownloader : public BasicJavaDownloader { - Q_OBJECT - public: - void executeTask() override; - - virtual QString name() const override { return "Azul"; }; - virtual bool isSupported() const override; - private slots: - void downloadJavaList(); - void downloadJava(const QJsonArray& doc); - void extractJava(QString input, QString subdirectory); - - private: - QString getArch() const; -}; \ No newline at end of file diff --git a/launcher/java/providers/BasicJavaDownloader.cpp b/launcher/java/providers/BasicJavaDownloader.cpp deleted file mode 100644 index 9f353d9c19..0000000000 --- a/launcher/java/providers/BasicJavaDownloader.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023 Trial97 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "java/providers/BasicJavaDownloader.h" - -#include "SysInfo.h" -#include "tasks/Task.h" - -BasicJavaDownloader::BasicJavaDownloader(QString final_path, bool m_is_legacy, QObject* parent) - : Task(parent) - , m_os_name(SysInfo::currentSystem()) - , m_os_arch(SysInfo::useQTForArch()) - , m_final_path(final_path) - , m_is_legacy(m_is_legacy) -{} From 81282bf7e0189ede29d2db14f85ef2c886d8b583 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 26 Jan 2024 00:22:05 +0200 Subject: [PATCH 0119/2054] Autodetect Java? Signed-off-by: Trial97 --- launcher/java/JavaRuntime.cpp | 19 +++++++ launcher/java/JavaRuntime.h | 19 +++++++ launcher/java/JavaUtils.cpp | 1 + .../java/download/ArchiveJavaDownloader.cpp | 18 +++++-- .../java/download/ArchiveJavaDownloader.h | 4 +- .../java/download/ManifestJavaDownloader.cpp | 18 +++++-- .../java/download/ManifestJavaDownloader.h | 4 +- .../minecraft/launch/VerifyJavaInstall.cpp | 49 ++++++++++++++----- 8 files changed, 112 insertions(+), 20 deletions(-) diff --git a/launcher/java/JavaRuntime.cpp b/launcher/java/JavaRuntime.cpp index 0de8c839bf..78651e9911 100644 --- a/launcher/java/JavaRuntime.cpp +++ b/launcher/java/JavaRuntime.cpp @@ -1,3 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "java/JavaRuntime.h" #include @@ -23,6 +41,7 @@ QString downloadTypeToString(DownloadType javaDownload) case DownloadType::Archive: return "archive"; } + return ""; } MetaPtr parseJavaMeta(const QJsonObject& in) { diff --git a/launcher/java/JavaRuntime.h b/launcher/java/JavaRuntime.h index 1ea5d2e48c..b44b546b6d 100644 --- a/launcher/java/JavaRuntime.h +++ b/launcher/java/JavaRuntime.h @@ -1,8 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #pragma once #include #include +#include + #include "java/JavaVersion.h" namespace JavaRuntime { diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 074bf54df1..67f1fd3a77 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -393,6 +393,7 @@ QList JavaUtils::FindJavaPaths() scanJavaDir(snap + dirPath); } }; + scanJavaDir(FS::PathCombine(APPLICATION->dataRoot(), "java")); // oracle RPMs scanJavaDirs("/usr/java"); // general locations used by distro packaging diff --git a/launcher/java/download/ArchiveJavaDownloader.cpp b/launcher/java/download/ArchiveJavaDownloader.cpp index 844be1dce9..32a3c39d27 100644 --- a/launcher/java/download/ArchiveJavaDownloader.cpp +++ b/launcher/java/download/ArchiveJavaDownloader.cpp @@ -21,9 +21,13 @@ #include "MMCZip.h" #include "Application.h" +#include "net/ChecksumValidator.h" #include "net/NetJob.h" #include "tasks/Task.h" +ArchiveJavaDownloader::ArchiveJavaDownloader(QUrl url, QString final_path, QString checksumType, QString checksumHash) + : m_url(url), m_final_path(final_path), m_checksum_type(checksumType), m_checksum_hash(checksumHash){}; + void ArchiveJavaDownloader::executeTask() { // JRE found ! download the zip @@ -32,7 +36,15 @@ void ArchiveJavaDownloader::executeTask() MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("java", m_url.toLocalFile()); auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); - download->addNetAction(Net::Download::makeCached(m_url, entry)); + auto action = Net::Download::makeCached(m_url, entry); + if (!m_checksum_hash.isEmpty() && !m_checksum_type.isEmpty()) { + auto hashType = QCryptographicHash::Algorithm::Sha1; + if (m_checksum_type == "sha256") { + hashType = QCryptographicHash::Algorithm::Sha256; + } + action->addValidator(new Net::ChecksumValidator(hashType, m_checksum_hash.toLatin1())); + } + download->addNetAction(action); auto fullPath = entry->getFullPath(); connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); @@ -45,7 +57,7 @@ void ArchiveJavaDownloader::executeTask() extractJava(fullPath); }); download->start(); -}; +} void ArchiveJavaDownloader::extractJava(QString input) { @@ -85,4 +97,4 @@ void ArchiveJavaDownloader::extractJava(QString input) stepProgress(*progressStep); }); zipTask->start(); -}; +} \ No newline at end of file diff --git a/launcher/java/download/ArchiveJavaDownloader.h b/launcher/java/download/ArchiveJavaDownloader.h index d176570864..230f62b4d2 100644 --- a/launcher/java/download/ArchiveJavaDownloader.h +++ b/launcher/java/download/ArchiveJavaDownloader.h @@ -24,7 +24,7 @@ class ArchiveJavaDownloader : public Task { Q_OBJECT public: - ArchiveJavaDownloader(QUrl url, QString final_path); + ArchiveJavaDownloader(QUrl url, QString final_path, QString checksumType = "", QString checksumHash = ""); virtual ~ArchiveJavaDownloader() = default; [[nodiscard]] bool canAbort() const override { return true; } @@ -36,6 +36,8 @@ class ArchiveJavaDownloader : public Task { protected: QUrl m_url; QString m_final_path; + QString m_checksum_type; + QString m_checksum_hash; Task::Ptr m_current_task; }; \ No newline at end of file diff --git a/launcher/java/download/ManifestJavaDownloader.cpp b/launcher/java/download/ManifestJavaDownloader.cpp index 4f7dab94e5..08321ca7f6 100644 --- a/launcher/java/download/ManifestJavaDownloader.cpp +++ b/launcher/java/download/ManifestJavaDownloader.cpp @@ -30,14 +30,24 @@ struct File { bool isExec; }; -ManifestJavaDownloader::ManifestJavaDownloader(QUrl url, QString final_path) : m_url(url), m_final_path(final_path){}; +ManifestJavaDownloader::ManifestJavaDownloader(QUrl url, QString final_path, QString checksumType, QString checksumHash) + : m_url(url), m_final_path(final_path), m_checksum_type(checksumType), m_checksum_hash(checksumHash){}; + void ManifestJavaDownloader::executeTask() { setStatus(tr("Downloading Java")); auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); auto files = std::make_shared(); - download->addNetAction(Net::Download::makeByteArray(m_url, files)); + auto action = Net::Download::makeByteArray(m_url, files); + if (!m_checksum_hash.isEmpty() && !m_checksum_type.isEmpty()) { + auto hashType = QCryptographicHash::Algorithm::Sha1; + if (m_checksum_type == "sha256") { + hashType = QCryptographicHash::Algorithm::Sha256; + } + action->addValidator(new Net::ChecksumValidator(hashType, m_checksum_hash.toLatin1())); + } + download->addNetAction(action); connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); connect(download.get(), &NetJob::progress, this, &ManifestJavaDownloader::progress); @@ -56,7 +66,7 @@ void ManifestJavaDownloader::executeTask() downloadJava(doc); }); download->start(); -}; +} void ManifestJavaDownloader::downloadJava(const QJsonDocument& doc) { @@ -111,4 +121,4 @@ void ManifestJavaDownloader::downloadJava(const QJsonDocument& doc) connect(this, &Task::aborted, elementDownload, &NetJob::abort); connect(elementDownload, &NetJob::succeeded, [this] { emitSucceeded(); }); elementDownload->start(); -}; \ No newline at end of file +} \ No newline at end of file diff --git a/launcher/java/download/ManifestJavaDownloader.h b/launcher/java/download/ManifestJavaDownloader.h index 44e9ed9de4..d7114f4bd5 100644 --- a/launcher/java/download/ManifestJavaDownloader.h +++ b/launcher/java/download/ManifestJavaDownloader.h @@ -24,7 +24,7 @@ class ManifestJavaDownloader : public Task { Q_OBJECT public: - ManifestJavaDownloader(QUrl url, QString final_path); + ManifestJavaDownloader(QUrl url, QString final_path, QString checksumType = "", QString checksumHash = ""); virtual ~ManifestJavaDownloader() = default; [[nodiscard]] bool canAbort() const override { return true; } @@ -36,6 +36,8 @@ class ManifestJavaDownloader : public Task { protected: QUrl m_url; QString m_final_path; + QString m_checksum_type; + QString m_checksum_hash; Task::Ptr m_current_task; }; \ No newline at end of file diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp index cdd1f7fd1f..2188112902 100644 --- a/launcher/minecraft/launch/VerifyJavaInstall.cpp +++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp @@ -34,7 +34,11 @@ */ #include "VerifyJavaInstall.h" +#include +#include "Application.h" +#include "java/JavaInstall.h" +#include "java/JavaInstallList.h" #include "java/JavaVersion.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" @@ -46,6 +50,7 @@ void VerifyJavaInstall::executeTask() auto settings = instance->settings(); auto storedVersion = settings->get("JavaVersion").toString(); auto ignoreCompatibility = settings->get("IgnoreJavaCompatibility").toBool(); + auto automaticJavaSwitch = settings->get("AutomaticJavaSwitch").toBool(); auto compatibleMajors = packProfile->getProfile()->getCompatibleJavaMajors(); @@ -62,16 +67,38 @@ void VerifyJavaInstall::executeTask() return; } - emit logLine(tr("This instance is not compatible with Java version %1.\n" - "Please switch to one of the following Java versions for this instance:") - .arg(javaVersion.major()), - MessageLevel::Error); - for (auto major : compatibleMajors) { - emit logLine(tr("Java version %1").arg(major), MessageLevel::Error); - } - emit logLine(tr("Go to instance Java settings to change your Java version or disable the Java compatibility check if you know what " - "you're doing."), - MessageLevel::Error); + auto logFail = [this, &javaVersion, compatibleMajors] { + emit logLine(tr("This instance is not compatible with Java version %1.\n" + "Please switch to one of the following Java versions for this instance:") + .arg(javaVersion.major()), + MessageLevel::Error); + for (auto major : compatibleMajors) { + emit logLine(tr("Java version %1").arg(major), MessageLevel::Error); + } + emit logLine(tr("Go to instance Java settings to change your Java version or disable the Java compatibility check if you know what " + "you're doing."), + MessageLevel::Error); + + emitFailed(QString("Incompatible Java major version")); + }; - emitFailed(QString("Incompatible Java major version")); + if (automaticJavaSwitch || true) { + settings->set("OverrideJava", true); + auto javas = APPLICATION->javalist().get(); + auto task = javas->getLoadTask(); + connect(task.get(), &Task::finished, this, [this, javas, compatibleMajors, settings, &logFail] { + for (auto i = 0; i < javas->count(); i++) { + auto java = std::dynamic_pointer_cast(javas->at(i)); + if (java && compatibleMajors.contains(java->id.major())) { + settings->set("OverrideJavaLocation", true); + settings->set("JavaPath", java->path); + emitSucceeded(); + return; + } + } + logFail(); + }); + } else { + logFail(); + } } From 4cd236ed807e44666e6775126d27fef237eef168 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 26 Jan 2024 00:31:43 +0200 Subject: [PATCH 0120/2054] missing header Signed-off-by: Trial97 --- launcher/java/JavaChecker.cpp | 3 ++- launcher/java/JavaRuntime.h | 1 + launcher/java/download/ArchiveJavaDownloader.cpp | 3 ++- launcher/java/download/ManifestJavaDownloader.cpp | 3 ++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 7271c0d093..18f93d256a 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -44,7 +44,8 @@ #include "java/JavaUtils.h" JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id, QObject* parent) - : Task(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id){}; + : Task(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id) +{} void JavaChecker::executeTask() { diff --git a/launcher/java/JavaRuntime.h b/launcher/java/JavaRuntime.h index b44b546b6d..4c4efa2886 100644 --- a/launcher/java/JavaRuntime.h +++ b/launcher/java/JavaRuntime.h @@ -18,6 +18,7 @@ #pragma once #include +#include #include #include diff --git a/launcher/java/download/ArchiveJavaDownloader.cpp b/launcher/java/download/ArchiveJavaDownloader.cpp index 32a3c39d27..5350fc4e6e 100644 --- a/launcher/java/download/ArchiveJavaDownloader.cpp +++ b/launcher/java/download/ArchiveJavaDownloader.cpp @@ -26,7 +26,8 @@ #include "tasks/Task.h" ArchiveJavaDownloader::ArchiveJavaDownloader(QUrl url, QString final_path, QString checksumType, QString checksumHash) - : m_url(url), m_final_path(final_path), m_checksum_type(checksumType), m_checksum_hash(checksumHash){}; + : m_url(url), m_final_path(final_path), m_checksum_type(checksumType), m_checksum_hash(checksumHash) +{} void ArchiveJavaDownloader::executeTask() { diff --git a/launcher/java/download/ManifestJavaDownloader.cpp b/launcher/java/download/ManifestJavaDownloader.cpp index 08321ca7f6..368d6431c3 100644 --- a/launcher/java/download/ManifestJavaDownloader.cpp +++ b/launcher/java/download/ManifestJavaDownloader.cpp @@ -31,7 +31,8 @@ struct File { }; ManifestJavaDownloader::ManifestJavaDownloader(QUrl url, QString final_path, QString checksumType, QString checksumHash) - : m_url(url), m_final_path(final_path), m_checksum_type(checksumType), m_checksum_hash(checksumHash){}; + : m_url(url), m_final_path(final_path), m_checksum_type(checksumType), m_checksum_hash(checksumHash) +{} void ManifestJavaDownloader::executeTask() { From 97ee0a19b5caf1714d033b282b5fca1bc70d6bff Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 26 Jan 2024 02:53:30 +0000 Subject: [PATCH 0121/2054] Refactor updating mechanisms to work with all resources Summary: - It compiles - I need to go to bed Signed-off-by: TheKodeToad --- launcher/CMakeLists.txt | 4 +- launcher/minecraft/mod/ModFolderModel.cpp | 39 +--- launcher/minecraft/mod/ModFolderModel.h | 3 - launcher/minecraft/mod/Resource.cpp | 2 +- launcher/minecraft/mod/Resource.h | 2 +- .../minecraft/mod/ResourceFolderModel.cpp | 6 +- launcher/minecraft/mod/ResourceFolderModel.h | 89 +++++---- .../minecraft/mod/ResourcePackFolderModel.cpp | 18 +- .../minecraft/mod/TexturePackFolderModel.cpp | 15 +- launcher/modplatform/CheckUpdateTask.h | 34 ++-- .../modplatform/flame/FlameCheckUpdate.cpp | 63 ++++--- launcher/modplatform/flame/FlameCheckUpdate.h | 6 +- .../modrinth/ModrinthCheckUpdate.cpp | 86 +++++---- .../modrinth/ModrinthCheckUpdate.h | 6 +- launcher/modplatform/packwiz/Packwiz.cpp | 10 +- ...ateDialog.cpp => ResourceUpdateDialog.cpp} | 174 +++++++++--------- ...dUpdateDialog.h => ResourceUpdateDialog.h} | 33 ++-- launcher/ui/pages/instance/ModFolderPage.cpp | 13 +- 18 files changed, 304 insertions(+), 299 deletions(-) rename launcher/ui/dialogs/{ModUpdateDialog.cpp => ResourceUpdateDialog.cpp} (74%) rename launcher/ui/dialogs/{ModUpdateDialog.h => ResourceUpdateDialog.h} (53%) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b341840df3..bc48abdef7 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1031,8 +1031,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/BlockedModsDialog.h ui/dialogs/ChooseProviderDialog.h ui/dialogs/ChooseProviderDialog.cpp - ui/dialogs/ModUpdateDialog.cpp - ui/dialogs/ModUpdateDialog.h + ui/dialogs/ResourceUpdateDialog.cpp + ui/dialogs/ResourceUpdateDialog.h ui/dialogs/InstallLoaderDialog.cpp ui/dialogs/InstallLoaderDialog.h diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 1ba09c8ecf..d5735dcb8e 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -89,35 +89,35 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const default: break; } - return at(row)->version(); + return at(row).version(); } case DateColumn: return m_resources[row]->dateTimeChanged(); case ProviderColumn: - return at(row)->provider(); + return at(row).provider(); default: return QVariant(); } case Qt::ToolTipRole: if (column == NAME_COLUMN) { - if (at(row)->isSymLinkUnder(instDirPath())) { + if (at(row).isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." "\nCanonical Path: %1") - .arg(at(row)->fileinfo().canonicalFilePath()); + .arg(at(row).fileinfo().canonicalFilePath()); } - if (at(row)->isMoreThanOneHardLink()) { + if (at(row).isMoreThanOneHardLink()) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); } } return m_resources[row]->internal_id(); case Qt::DecorationRole: { - if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) + if (column == NAME_COLUMN && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) return APPLICATION->getThemedIcon("status-yellow"); if (column == ImageColumn) { - return at(row)->icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); + return at(row).icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); } return {}; } @@ -129,7 +129,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const case Qt::CheckStateRole: switch (column) { case ActiveColumn: - return at(row)->enabled() ? Qt::Checked : Qt::Unchecked; + return at(row).enabled() ? Qt::Checked : Qt::Unchecked; default: return QVariant(); } @@ -190,29 +190,6 @@ bool ModFolderModel::isValid() return m_dir.exists() && m_dir.isReadable(); } -auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList -{ - QList selected_resources; - for (auto i : indexes) { - if (i.column() != 0) - continue; - - selected_resources.push_back(at(i.row())); - } - return selected_resources; -} - -auto ModFolderModel::allMods() -> QList -{ - QList mods; - - for (auto& res : qAsConst(m_resources)) { - mods.append(static_cast(res.get())); - } - - return mods; -} - void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) { auto iter = m_active_parse_tasks.constFind(ticket); diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index c1db33c0cd..cd5e99b119 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -76,9 +76,6 @@ class ModFolderModel : public ResourceFolderModel { bool isValid(); - auto selectedMods(QModelIndexList& indexes) -> QList; - auto allMods() -> QList; - RESOURCE_HELPERS(Mod) private slots: diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index ac81681cfd..30b453812a 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -184,7 +184,7 @@ auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attem return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath()); } -auto Resource::destroyMetadata(const QDir& index_dir) -> bool +auto Resource::destroyMetadata(const QDir& index_dir) -> void { if (metadata()) { Metadata::remove(index_dir, metadata()->slug); diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 0d77be7c92..772f456f1d 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -101,7 +101,7 @@ class Resource : public QObject { // Delete all files of this resource. auto destroy(const QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool; // Delete the metadata only. - auto destroyMetadata(const QDir& index_dir) -> bool; + auto destroyMetadata(const QDir& index_dir) -> void; [[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); } diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index ff5e95983a..a7a80db052 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -242,10 +242,10 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) return true; } -bool ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes) +void ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes) { if (indexes.isEmpty()) - return true; + return; for (auto i : indexes) { if (i.column() != 0) @@ -256,8 +256,6 @@ bool ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes) } update(); - - return true; } bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action) diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 8b5ac68ad6..3de3c54763 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -19,6 +19,58 @@ class QSortFilterProxyModel; +/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */ +#define RESOURCE_HELPERS(T) \ + [[nodiscard]] T& operator[](int index) \ + { \ + return *static_cast(m_resources[index].get()); \ + } \ + [[nodiscard]] T& at(int index) \ + { \ + return *static_cast(m_resources[index].get()); \ + } \ + [[nodiscard]] const T& at(int index) const \ + { \ + return *static_cast(m_resources.at(index).get()); \ + } \ + [[nodiscard]] T& first() \ + { \ + return *static_cast(m_resources.first().get()); \ + } \ + [[nodiscard]] T& last() \ + { \ + return *static_cast(m_resources.last().get()); \ + } \ + [[nodiscard]] T* find(QString id) \ + { \ + auto iter = std::find_if(m_resources.constBegin(), m_resources.constEnd(), \ + [&](Resource::Ptr const& r) { return r->internal_id() == id; }); \ + if (iter == m_resources.constEnd()) \ + return nullptr; \ + return static_cast((*iter).get()); \ + } \ + QList selected##T##s(const QModelIndexList& indexes) \ + { \ + QList result; \ + for (const QModelIndex& index : indexes) { \ + if (index.column() != 0) \ + continue; \ + \ + result.append(&at(index.row())); \ + } \ + return result; \ + } \ + QList all##T##s() \ + { \ + QList result; \ + result.reserve(m_resources.size()); \ + \ + for (const Resource::Ptr& resource : m_resources) \ + result.append(static_cast(resource.get())); \ + \ + return result; \ + } + /** A basic model for external resources. * * This model manages a list of resources. As such, external users of such resources do not own them, @@ -69,7 +121,7 @@ class ResourceFolderModel : public QAbstractListModel { */ virtual bool uninstallResource(QString file_name, bool preserve_metadata = false); virtual bool deleteResources(const QModelIndexList&); - virtual bool deleteMetadata(const QModelIndexList&); + virtual void deleteMetadata(const QModelIndexList&); /** Applies the given 'action' to the resources in 'indexes'. * @@ -85,9 +137,7 @@ class ResourceFolderModel : public QAbstractListModel { [[nodiscard]] qsizetype size() const { return m_resources.size(); } [[nodiscard]] bool empty() const { return size() == 0; } - [[nodiscard]] Resource& at(int index) { return *m_resources.at(index); } - [[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); } - [[nodiscard]] QList const& all() const { return m_resources; } + RESOURCE_HELPERS(Resource) [[nodiscard]] QDir const& dir() const { return m_dir; } @@ -232,37 +282,6 @@ class ResourceFolderModel : public QAbstractListModel { std::atomic m_next_resolution_ticket = 0; }; -/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */ -#define RESOURCE_HELPERS(T) \ - [[nodiscard]] T* operator[](int index) \ - { \ - return static_cast(m_resources[index].get()); \ - } \ - [[nodiscard]] T* at(int index) \ - { \ - return static_cast(m_resources[index].get()); \ - } \ - [[nodiscard]] const T* at(int index) const \ - { \ - return static_cast(m_resources.at(index).get()); \ - } \ - [[nodiscard]] T* first() \ - { \ - return static_cast(m_resources.first().get()); \ - } \ - [[nodiscard]] T* last() \ - { \ - return static_cast(m_resources.last().get()); \ - } \ - [[nodiscard]] T* find(QString id) \ - { \ - auto iter = std::find_if(m_resources.constBegin(), m_resources.constEnd(), \ - [&](Resource::Ptr const& r) { return r->internal_id() == id; }); \ - if (iter == m_resources.constEnd()) \ - return nullptr; \ - return static_cast((*iter).get()); \ - } - /* Template definition to avoid some code duplication */ template void ResourceFolderModel::applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources) diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 8d0cd5fcd6..b777dbafd9 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -72,12 +72,12 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const case NameColumn: return m_resources[row]->name(); case PackFormatColumn: { - auto resource = at(row); - auto pack_format = resource->packFormat(); + auto& resource = at(row); + auto pack_format = resource.packFormat(); if (pack_format == 0) return tr("Unrecognized"); - auto version_bounds = resource->compatibleVersions(); + auto version_bounds = resource.compatibleVersions(); if (version_bounds.first.toString().isEmpty()) return QString::number(pack_format); @@ -92,10 +92,10 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const return {}; } case Qt::DecorationRole: { - if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) + if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) return APPLICATION->getThemedIcon("status-yellow"); if (column == ImageColumn) { - return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); + return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); } return {}; } @@ -105,14 +105,14 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const return tr("The resource pack format ID, as well as the Minecraft versions it was designed for."); } if (column == NameColumn) { - if (at(row)->isSymLinkUnder(instDirPath())) { + if (at(row).isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." "\nCanonical Path: %1") - .arg(at(row)->fileinfo().canonicalFilePath()); + .arg(at(row).fileinfo().canonicalFilePath()); ; } - if (at(row)->isMoreThanOneHardLink()) { + if (at(row).isMoreThanOneHardLink()) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); } @@ -127,7 +127,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const case Qt::CheckStateRole: switch (column) { case ActiveColumn: - return at(row)->enabled() ? Qt::Checked : Qt::Unchecked; + return at(row).enabled() ? Qt::Checked : Qt::Unchecked; default: return {}; } diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index b4b06a62a5..3795795a27 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -52,11 +52,6 @@ TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* in m_columnsHideable = { false, true, false, true, true }; } -Task* TexturePackFolderModel::createUpdateTask() -{ - return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared(entry); }); -} - Task* TexturePackFolderModel::createParseTask(Resource& resource) { return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast(resource)); @@ -84,14 +79,14 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const } case Qt::ToolTipRole: if (column == NameColumn) { - if (at(row)->isSymLinkUnder(instDirPath())) { + if (at(row).isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." "\nCanonical Path: %1") - .arg(at(row)->fileinfo().canonicalFilePath()); + .arg(at(row).fileinfo().canonicalFilePath()); ; } - if (at(row)->isMoreThanOneHardLink()) { + if (at(row).isMoreThanOneHardLink()) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); } @@ -99,10 +94,10 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const return m_resources[row]->internal_id(); case Qt::DecorationRole: { - if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) + if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) return APPLICATION->getThemedIcon("status-yellow"); if (column == ImageColumn) { - return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); + return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); } return {}; } diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index 8bd83d988e..aaa997c166 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -13,13 +13,13 @@ class CheckUpdateTask : public Task { Q_OBJECT public: - CheckUpdateTask(QList& mods, + CheckUpdateTask(QList& resources, std::list& mcVersions, std::optional loaders, - std::shared_ptr mods_folder) - : Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder){}; + std::shared_ptr resource_model) + : Task(nullptr), m_resources(resources), m_game_versions(mcVersions), m_loaders(loaders), m_resource_model(resource_model){}; - struct UpdatableMod { + struct Update { QString name; QString old_hash; QString old_version; @@ -30,14 +30,14 @@ class CheckUpdateTask : public Task { shared_qobject_ptr download; public: - UpdatableMod(QString name, - QString old_h, - QString old_v, - QString new_v, - std::optional new_v_type, - QString changelog, - ModPlatform::ResourceProvider p, - shared_qobject_ptr t) + Update(QString name, + QString old_h, + QString old_v, + QString new_v, + std::optional new_v_type, + QString changelog, + ModPlatform::ResourceProvider p, + shared_qobject_ptr t) : name(name) , old_hash(old_h) , old_version(old_v) @@ -49,7 +49,7 @@ class CheckUpdateTask : public Task { {} }; - auto getUpdatable() -> std::vector&& { return std::move(m_updatable); } + auto getUpdates() -> std::vector&& { return std::move(m_updates); } auto getDependencies() -> QList>&& { return std::move(m_deps); } public slots: @@ -59,14 +59,14 @@ class CheckUpdateTask : public Task { void executeTask() override = 0; signals: - void checkFailed(Mod* failed, QString reason, QUrl recover_url = {}); + void checkFailed(Resource* failed, QString reason, QUrl recover_url = {}); protected: - QList& m_mods; + QList& m_resources; std::list& m_game_versions; std::optional m_loaders; - std::shared_ptr m_mods_folder; + std::shared_ptr m_resource_model; - std::vector m_updatable; + std::vector m_updates; QList> m_deps; }; diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index a6a76adb63..9a3249bc74 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -120,19 +120,19 @@ ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileI * */ void FlameCheckUpdate::executeTask() { - setStatus(tr("Preparing mods for CurseForge...")); + setStatus(tr("Preparing resources for CurseForge...")); int i = 0; - for (auto* mod : m_mods) { - if (!mod->enabled()) { - emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!")); + for (auto* resource : m_resources) { + if (!resource->enabled()) { + emit checkFailed(resource, tr("Disabled resources won't be updated, to prevent resource duplication issues!")); continue; } - setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name())); - setProgress(i++, m_mods.size()); + setStatus(tr("Getting API response from CurseForge for '%1'...").arg(resource->name())); + setProgress(i++, m_resources.size()); - auto latest_ver = api.getLatestVersion({ { mod->metadata()->project_id.toString() }, m_game_versions, m_loaders }); + auto latest_ver = api.getLatestVersion({ { resource->metadata()->project_id.toString() }, m_game_versions, m_loaders }); // Check if we were aborted while getting the latest version if (m_was_aborted) { @@ -140,43 +140,50 @@ void FlameCheckUpdate::executeTask() return; } - setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(mod->name())); + setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name())); if (!latest_ver.addonId.isValid()) { - emit checkFailed(mod, tr("No valid version found for this mod. It's probably unavailable for the current game " - "version / mod loader.")); + QString reason; + if (dynamic_cast(resource) != nullptr) + reason = + tr("No valid version found for this resource. It's probably unavailable for the current game " + "version / mod loader."); + else + reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); + + emit checkFailed(resource, reason); continue; } - if (latest_ver.downloadUrl.isEmpty() && latest_ver.fileId != mod->metadata()->file_id) { + if (latest_ver.downloadUrl.isEmpty() && latest_ver.fileId != resource->metadata()->file_id) { auto pack = getProjectInfo(latest_ver); auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver.fileId.toString()); - emit checkFailed(mod, tr("Mod has a new update available, but is not downloadable using CurseForge."), recover_url); + emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."), recover_url); continue; } // Fake pack with the necessary info to pass to the download task :) auto pack = std::make_shared(); - pack->name = mod->name(); - pack->slug = mod->metadata()->slug; - pack->addonId = mod->metadata()->project_id; - pack->websiteUrl = mod->homeurl(); - for (auto& author : mod->authors()) - pack->authors.append({ author }); - pack->description = mod->description(); + pack->name = resource->name(); + pack->slug = resource->metadata()->slug; + pack->addonId = resource->metadata()->project_id; pack->provider = ModPlatform::ResourceProvider::FLAME; - if (!latest_ver.hash.isEmpty() && (mod->metadata()->hash != latest_ver.hash || mod->status() == ResourceStatus::NOT_INSTALLED)) { - auto old_version = mod->version(); - if (old_version.isEmpty() && mod->status() != ResourceStatus::NOT_INSTALLED) { - auto current_ver = getFileInfo(latest_ver.addonId.toInt(), mod->metadata()->file_id.toInt()); - old_version = current_ver.version; + if (!latest_ver.hash.isEmpty() && + (resource->metadata()->hash != latest_ver.hash || resource->status() == ResourceStatus::NOT_INSTALLED)) { + auto download_task = makeShared(pack, latest_ver, m_resource_model); + + QString old_version = resource->metadata()->version_number; + if (old_version.isEmpty()) { + if (resource->status() == ResourceStatus::NOT_INSTALLED) + old_version = tr("Not installed"); + else + old_version = tr("Unknown"); } - auto download_task = makeShared(pack, latest_ver, m_mods_folder); - m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver.version, latest_ver.version_type, - api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), - ModPlatform::ResourceProvider::FLAME, download_task); + m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver.version, latest_ver.version_type, + api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), + ModPlatform::ResourceProvider::FLAME, download_task); } m_deps.append(std::make_shared(pack, latest_ver)); } diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h index f5bb1653d5..9ae9441532 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.h +++ b/launcher/modplatform/flame/FlameCheckUpdate.h @@ -8,11 +8,11 @@ class FlameCheckUpdate : public CheckUpdateTask { Q_OBJECT public: - FlameCheckUpdate(QList& mods, + FlameCheckUpdate(QList& resources, std::list& mcVersions, std::optional loaders, - std::shared_ptr mods_folder) - : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) + std::shared_ptr resource_model) + : CheckUpdateTask(resources, mcVersions, loaders, resource_model) {} public slots: diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index 13696d8a24..f7e9fb8a9e 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -29,38 +29,38 @@ bool ModrinthCheckUpdate::abort() * */ void ModrinthCheckUpdate::executeTask() { - setStatus(tr("Preparing mods for Modrinth...")); + setStatus(tr("Preparing resources for Modrinth...")); setProgress(0, 3); - QHash mappings; + QHash mappings; // Create all hashes QStringList hashes; auto best_hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); - for (auto* mod : m_mods) { - if (!mod->enabled()) { - emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!")); + for (auto* resource : m_resources) { + if (!resource->enabled()) { + emit checkFailed(resource, tr("Disabled resources won't be updated, to prevent resource duplication issues!")); continue; } - auto hash = mod->metadata()->hash; + auto hash = resource->metadata()->hash; // Sadly the API can only handle one hash type per call, se we // need to generate a new hash if the current one is innadequate // (though it will rarely happen, if at all) - if (mod->metadata()->hash_format != best_hash_type) { - auto hash_task = Hashing::createModrinthHasher(mod->fileinfo().absoluteFilePath()); - connect(hash_task.get(), &Hashing::Hasher::resultsReady, [&hashes, &mappings, mod](QString hash) { + if (resource->metadata()->hash_format != best_hash_type) { + auto hash_task = Hashing::createModrinthHasher(resource->fileinfo().absoluteFilePath()); + connect(hash_task.get(), &Hashing::Hasher::resultsReady, [&hashes, &mappings, resource](QString hash) { hashes.append(hash); - mappings.insert(hash, mod); + mappings.insert(hash, resource); }); connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); }); hashing_task.addTask(hash_task); } else { hashes.append(hash); - mappings.insert(hash, mod); + mappings.insert(hash, resource); } } @@ -88,19 +88,26 @@ void ModrinthCheckUpdate::executeTask() setProgress(2, 3); try { - for (auto hash : mappings.keys()) { + for (auto iter = mappings.begin(); iter != mappings.end(); iter++) { + const QString& hash = iter.key(); + Resource* resource = iter.value(); auto project_obj = doc[hash].toObject(); // If the returned project is empty, but we have Modrinth metadata, // it means this specific version is not available if (project_obj.isEmpty()) { - qDebug() << "Mod " << mappings.find(hash).value()->name() << " got an empty response."; + qDebug() << "Resource " << resource->name() << " got an empty response."; qDebug() << "Hash: " << hash; - emit checkFailed( - mappings.find(hash).value(), - tr("No valid version found for this mod. It's probably unavailable for the current game version / mod loader.")); + QString reason; + if (dynamic_cast(resource) != nullptr) + reason = + tr("No valid version found for this resource. It's probably unavailable for the current game " + "version / mod loader."); + else + reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); + emit checkFailed(resource, reason); continue; } @@ -109,7 +116,8 @@ void ModrinthCheckUpdate::executeTask() QString loader_filter; if (m_loaders.has_value()) { static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge, - ModPlatform::ModLoaderType::Fabric, ModPlatform::ModLoaderType::Quilt }; + ModPlatform::ModLoaderType::Fabric, ModPlatform::ModLoaderType::Quilt, + ModPlatform::ModLoaderType::LiteLoader }; for (auto flag : flags) { if (m_loaders.value().testFlag(flag)) { loader_filter = ModPlatform::getModLoaderString(flag); @@ -126,46 +134,44 @@ void ModrinthCheckUpdate::executeTask() auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, best_hash_type, loader_filter); if (project_ver.downloadUrl.isEmpty()) { - qCritical() << "Modrinth mod without download url!"; + qCritical() << "Modrinth resource without download url!"; qCritical() << project_ver.fileName; - emit checkFailed(mappings.find(hash).value(), tr("Mod has an empty download URL")); + emit checkFailed(mappings.find(hash).value(), tr("Resource has an empty download URL")); continue; } - auto mod_iter = mappings.find(hash); - if (mod_iter == mappings.end()) { - qCritical() << "Failed to remap mod from Modrinth!"; + auto resource_iter = mappings.find(hash); + if (resource_iter == mappings.end()) { + qCritical() << "Failed to remap resource from Modrinth!"; continue; } - auto mod = *mod_iter; - - auto key = project_ver.hash; // Fake pack with the necessary info to pass to the download task :) auto pack = std::make_shared(); - pack->name = mod->name(); - pack->slug = mod->metadata()->slug; - pack->addonId = mod->metadata()->project_id; - pack->websiteUrl = mod->homeurl(); - for (auto& author : mod->authors()) - pack->authors.append({ author }); - pack->description = mod->description(); + pack->name = resource->name(); + pack->slug = resource->metadata()->slug; + pack->addonId = resource->metadata()->project_id; pack->provider = ModPlatform::ResourceProvider::MODRINTH; - if ((key != hash && project_ver.is_preferred) || (mod->status() == ResourceStatus::NOT_INSTALLED)) { - if (mod->version() == project_ver.version_number) - continue; - - auto download_task = makeShared(pack, project_ver, m_mods_folder); + if ((project_ver.hash != hash && project_ver.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) { + auto download_task = makeShared(pack, project_ver, m_resource_model); + + QString old_version = resource->metadata()->version_number; + if (old_version.isEmpty()) { + if (resource->status() == ResourceStatus::NOT_INSTALLED) + old_version = tr("Not installed"); + else + old_version = tr("Unknown"); + } - m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.version_type, - project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task); + m_updates.emplace_back(pack->name, hash, old_version, project_ver.version_number, project_ver.version_type, + project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task); } m_deps.append(std::make_shared(pack, project_ver)); } } catch (Json::JsonException& e) { - emitFailed(e.cause() + " : " + e.what()); + emitFailed(e.cause() + ": " + e.what()); return; } emitSucceeded(); diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h index f2f2c7e92c..5aa3c8cb68 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h @@ -8,11 +8,11 @@ class ModrinthCheckUpdate : public CheckUpdateTask { Q_OBJECT public: - ModrinthCheckUpdate(QList& mods, + ModrinthCheckUpdate(QList& resources, std::list& mcVersions, std::optional loaders, - std::shared_ptr mods_folder) - : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) + std::shared_ptr resource_model) + : CheckUpdateTask(resources, mcVersions, loaders, resource_model) {} public slots: diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index f68624c1a3..81c0a1efbb 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -297,18 +297,20 @@ auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod { // [update] info using Provider = ModPlatform::ResourceProvider; - auto update_table = table["update"]; - if (!update_table || !update_table.is_table()) { + auto update_table = table["update"].as_table(); + if (!update_table) { qCritical() << QString("No [update] section found on mod metadata!"); return {}; } + mod.version_number = stringEntry(*update_table, "x-prismlauncher-version-number"); + toml::table* mod_provider_table = nullptr; - if ((mod_provider_table = update_table[ProviderCaps.name(Provider::FLAME)].as_table())) { + if ((mod_provider_table = (*update_table)[ProviderCaps.name(Provider::FLAME)].as_table())) { mod.provider = Provider::FLAME; mod.file_id = intEntry(*mod_provider_table, "file-id"); mod.project_id = intEntry(*mod_provider_table, "project-id"); - } else if ((mod_provider_table = update_table[ProviderCaps.name(Provider::MODRINTH)].as_table())) { + } else if ((mod_provider_table = (*update_table)[ProviderCaps.name(Provider::MODRINTH)].as_table())) { mod.provider = Provider::MODRINTH; mod.mod_id() = stringEntry(*mod_provider_table, "mod-id"); mod.version() = stringEntry(*mod_provider_table, "version"); diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp similarity index 74% rename from launcher/ui/dialogs/ModUpdateDialog.cpp rename to launcher/ui/dialogs/ResourceUpdateDialog.cpp index eef690d570..cad5b9f7f6 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -1,4 +1,4 @@ -#include "ModUpdateDialog.h" +#include "ResourceUpdateDialog.h" #include "ChooseProviderDialog.h" #include "CustomMessageBox.h" #include "ProgressDialog.h" @@ -36,14 +36,14 @@ static std::optional mcLoaders(BaseInstance* inst) return { static_cast(inst)->getPackProfile()->getSupportedModLoaders() }; } -ModUpdateDialog::ModUpdateDialog(QWidget* parent, - BaseInstance* instance, - const std::shared_ptr mods, - QList& search_for, - bool includeDeps) +ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, + BaseInstance* instance, + const std::shared_ptr resource_model, + QList& search_for, + bool includeDeps) : ReviewMessageBox(parent, tr("Confirm mods to update"), "") , m_parent(parent) - , m_mod_model(mods) + , m_resource_model(resource_model) , m_candidates(search_for) , m_second_try_metadata( new ConcurrentTask(nullptr, "Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())) @@ -56,7 +56,7 @@ ModUpdateDialog::ModUpdateDialog(QWidget* parent, ui->onlyCheckedLabel->setText(tr("Only mods with a check will be updated!")); } -void ModUpdateDialog::checkCandidates() +void ResourceUpdateDialog::checkCandidates() { // Ensure mods have valid metadata auto went_well = ensureMetadata(); @@ -92,18 +92,20 @@ void ModUpdateDialog::checkCandidates() SequentialTask check_task(m_parent, tr("Checking for updates")); if (!m_modrinth_to_update.empty()) { - m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loaders, m_mod_model)); - connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this, [this](Mod* mod, QString reason, QUrl recover_url) { - m_failed_check_update.append({ mod, reason, recover_url }); - }); + m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loaders, m_resource_model)); + connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this, + [this](Resource* resource, QString reason, QUrl recover_url) { + m_failed_check_update.append({ resource, reason, recover_url }); + }); check_task.addTask(m_modrinth_check_task); } if (!m_flame_to_update.empty()) { - m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loaders, m_mod_model)); - connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this, [this](Mod* mod, QString reason, QUrl recover_url) { - m_failed_check_update.append({ mod, reason, recover_url }); - }); + m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loaders, m_resource_model)); + connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this, + [this](Resource* resource, QString reason, QUrl recover_url) { + m_failed_check_update.append({ resource, reason, recover_url }); + }); check_task.addTask(m_flame_check_task); } @@ -134,11 +136,11 @@ void ModUpdateDialog::checkCandidates() // Add found updates for Modrinth if (m_modrinth_check_task) { - auto modrinth_updates = m_modrinth_check_task->getUpdatable(); + auto modrinth_updates = m_modrinth_check_task->getUpdates(); for (auto& updatable : modrinth_updates) { qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); - appendMod(updatable); + appendResource(updatable); m_tasks.insert(updatable.name, updatable.download); } selectedVers.append(m_modrinth_check_task->getDependencies()); @@ -146,11 +148,11 @@ void ModUpdateDialog::checkCandidates() // Add found updated for Flame if (m_flame_check_task) { - auto flame_updates = m_flame_check_task->getUpdatable(); + auto flame_updates = m_flame_check_task->getUpdates(); for (auto& updatable : flame_updates) { qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); - appendMod(updatable); + appendResource(updatable); m_tasks.insert(updatable.name, updatable.download); } selectedVers.append(m_flame_check_task->getDependencies()); @@ -189,49 +191,53 @@ void ModUpdateDialog::checkCandidates() } if (m_include_deps && !APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies - auto depTask = makeShared(this, m_instance, m_mod_model.get(), selectedVers); - - connect(depTask.get(), &Task::failed, this, - [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - - connect(depTask.get(), &Task::succeeded, this, [&]() { - QStringList warnings = depTask->warnings(); - if (warnings.count()) { - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec(); + auto* mod_model = dynamic_cast(m_resource_model.get()); + + if (mod_model != nullptr) { + auto depTask = makeShared(this, m_instance, mod_model, selectedVers); + + connect(depTask.get(), &Task::failed, this, + [&](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + + connect(depTask.get(), &Task::succeeded, this, [&]() { + QStringList warnings = depTask->warnings(); + if (warnings.count()) { + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec(); + } + }); + + ProgressDialog progress_dialog_deps(m_parent); + progress_dialog_deps.setSkipButton(true, tr("Abort")); + progress_dialog_deps.setWindowTitle(tr("Checking for dependencies...")); + auto dret = progress_dialog_deps.execWithTask(depTask.get()); + + // If the dialog was skipped / some download error happened + if (dret == QDialog::DialogCode::Rejected) { + m_aborted = true; + QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); + return; + } + static FlameAPI api; + + auto getRequiredBy = depTask->getRequiredBy(); + + for (const auto& dep : depTask->getDependecies()) { + auto changelog = dep->version.changelog; + if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) + changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); + auto download_task = makeShared(dep->pack, dep->version, m_resource_model); + CheckUpdateTask::Update updatable = { + dep->pack->name, dep->version.hash, tr("Not installed"), dep->version.version, dep->version.version_type, + changelog, dep->pack->provider, download_task + }; + + appendResource(updatable, getRequiredBy.value(dep->version.addonId.toString())); + m_tasks.insert(updatable.name, updatable.download); } - }); - - ProgressDialog progress_dialog_deps(m_parent); - progress_dialog_deps.setSkipButton(true, tr("Abort")); - progress_dialog_deps.setWindowTitle(tr("Checking for dependencies...")); - auto dret = progress_dialog_deps.execWithTask(depTask.get()); - - // If the dialog was skipped / some download error happened - if (dret == QDialog::DialogCode::Rejected) { - m_aborted = true; - QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); - return; - } - static FlameAPI api; - - auto getRequiredBy = depTask->getRequiredBy(); - - for (auto dep : depTask->getDependecies()) { - auto changelog = dep->version.changelog; - if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) - changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); - auto download_task = makeShared(dep->pack, dep->version, m_mod_model); - CheckUpdateTask::UpdatableMod updatable = { - dep->pack->name, dep->version.hash, "", dep->version.version, dep->version.version_type, - changelog, dep->pack->provider, download_task - }; - - appendMod(updatable, getRequiredBy.value(dep->version.addonId.toString())); - m_tasks.insert(updatable.name, updatable.download); } } - // If there's no mod to be updated + // If there's no resource to be updated if (ui->modTreeWidget->topLevelItemCount() == 0) { m_no_updates = true; } else { @@ -253,7 +259,7 @@ void ModUpdateDialog::checkCandidates() } // Part 1: Ensure we have a valid metadata -auto ModUpdateDialog::ensureMetadata() -> bool +auto ResourceUpdateDialog::ensureMetadata() -> bool { auto index_dir = indexDir(); @@ -261,21 +267,21 @@ auto ModUpdateDialog::ensureMetadata() -> bool // A better use of data structures here could remove the need for this QHash QHash should_try_others; - QList modrinth_tmp; - QList flame_tmp; + QList modrinth_tmp; + QList flame_tmp; bool confirm_rest = false; bool try_others_rest = false; bool skip_rest = false; ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH; - auto addToTmp = [&](Mod* m, ModPlatform::ResourceProvider p) { + auto addToTmp = [&](Resource* resource, ModPlatform::ResourceProvider p) { switch (p) { case ModPlatform::ResourceProvider::MODRINTH: - modrinth_tmp.push_back(m); + modrinth_tmp.push_back(resource); break; case ModPlatform::ResourceProvider::FLAME: - flame_tmp.push_back(m); + flame_tmp.push_back(resource); break; } }; @@ -300,7 +306,7 @@ auto ModUpdateDialog::ensureMetadata() -> bool } ChooseProviderDialog chooser(this); - chooser.setDescription(tr("The mod '%1' does not have a metadata yet. We need to generate it in order to track relevant " + chooser.setDescription(tr("The resource '%1' does not have a metadata yet. We need to generate it in order to track relevant " "information on how to update this mod. " "To do this, please select a mod provider which we can use to check for updates for this mod.") .arg(candidate->name())); @@ -324,8 +330,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool if (!modrinth_tmp.empty()) { auto modrinth_task = makeShared(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH); - connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); - connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { + connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); + connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) { onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); }); connect(modrinth_task.get(), &EnsureMetadataTask::failed, @@ -339,8 +345,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool if (!flame_tmp.empty()) { auto flame_task = makeShared(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME); - connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); - connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { + connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); + connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) { onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); }); connect(flame_task.get(), &EnsureMetadataTask::failed, @@ -362,18 +368,18 @@ auto ModUpdateDialog::ensureMetadata() -> bool return (ret_metadata != QDialog::DialogCode::Rejected); } -void ModUpdateDialog::onMetadataEnsured(Mod* mod) +void ResourceUpdateDialog::onMetadataEnsured(Resource* resource) { // When the mod is a folder, for instance - if (!mod->metadata()) + if (!resource->metadata()) return; - switch (mod->metadata()->provider) { + switch (resource->metadata()->provider) { case ModPlatform::ResourceProvider::MODRINTH: - m_modrinth_to_update.push_back(mod); + m_modrinth_to_update.push_back(resource); break; case ModPlatform::ResourceProvider::FLAME: - m_flame_to_update.push_back(mod); + m_flame_to_update.push_back(resource); break; } } @@ -390,26 +396,26 @@ ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p) return ModPlatform::ResourceProvider::FLAME; } -void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::ResourceProvider first_choice) +void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others, ModPlatform::ResourceProvider first_choice) { if (try_others) { auto index_dir = indexDir(); - auto task = makeShared(mod, index_dir, next(first_choice)); - connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); - connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Mod* candidate) { onMetadataFailed(candidate, false); }); + auto task = makeShared(resource, index_dir, next(first_choice)); + connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); + connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Resource* candidate) { onMetadataFailed(candidate, false); }); connect(task.get(), &EnsureMetadataTask::failed, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); m_second_try_metadata->addTask(task); } else { QString reason{ tr("Couldn't find a valid version on the selected mod provider(s)") }; - m_failed_metadata.append({ mod, reason }); + m_failed_metadata.append({ resource, reason }); } } -void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStringList requiredBy) +void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, QStringList requiredBy) { auto item_top = new QTreeWidgetItem(ui->modTreeWidget); item_top->setCheckState(0, Qt::CheckState::Checked); @@ -420,7 +426,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri provider_item->setText(0, tr("Provider: %1").arg(ProviderCaps.readableName(info.provider))); auto old_version_item = new QTreeWidgetItem(item_top); - old_version_item->setText(0, tr("Old version: %1").arg(info.old_version.isEmpty() ? tr("Not installed") : info.old_version)); + old_version_item->setText(0, tr("Old version: %1").arg(info.old_version)); auto new_version_item = new QTreeWidgetItem(item_top); new_version_item->setText(0, tr("New version: %1").arg(info.new_version)); @@ -474,7 +480,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri ui->modTreeWidget->addTopLevelItem(item_top); } -auto ModUpdateDialog::getTasks() -> const QList +auto ResourceUpdateDialog::getTasks() -> const QList { QList list; diff --git a/launcher/ui/dialogs/ModUpdateDialog.h b/launcher/ui/dialogs/ResourceUpdateDialog.h similarity index 53% rename from launcher/ui/dialogs/ModUpdateDialog.h rename to launcher/ui/dialogs/ResourceUpdateDialog.h index de5ab46a56..31aab8f5db 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.h +++ b/launcher/ui/dialogs/ResourceUpdateDialog.h @@ -13,22 +13,21 @@ class ModrinthCheckUpdate; class FlameCheckUpdate; class ConcurrentTask; -class ModUpdateDialog final : public ReviewMessageBox { +class ResourceUpdateDialog final : public ReviewMessageBox { Q_OBJECT public: - explicit ModUpdateDialog(QWidget* parent, BaseInstance* instance, std::shared_ptr mod_model, QList& search_for); - explicit ModUpdateDialog(QWidget* parent, - BaseInstance* instance, - std::shared_ptr mod_model, - QList& search_for, - bool includeDeps); + explicit ResourceUpdateDialog(QWidget* parent, + BaseInstance* instance, + std::shared_ptr resource_model, + QList& search_for, + bool includeDeps); void checkCandidates(); - void appendMod(const CheckUpdateTask::UpdatableMod& info, QStringList requiredBy = {}); + void appendResource(const CheckUpdateTask::Update& info, QStringList requiredBy = {}); const QList getTasks(); - auto indexDir() const -> QDir { return m_mod_model->indexDir(); } + auto indexDir() const -> QDir { return m_resource_model->indexDir(); } auto noUpdates() const -> bool { return m_no_updates; }; auto aborted() const -> bool { return m_aborted; }; @@ -37,8 +36,8 @@ class ModUpdateDialog final : public ReviewMessageBox { auto ensureMetadata() -> bool; private slots: - void onMetadataEnsured(Mod*); - void onMetadataFailed(Mod*, + void onMetadataEnsured(Resource* resource); + void onMetadataFailed(Resource* resource, bool try_others = false, ModPlatform::ResourceProvider first_choice = ModPlatform::ResourceProvider::MODRINTH); @@ -48,15 +47,15 @@ class ModUpdateDialog final : public ReviewMessageBox { shared_qobject_ptr m_modrinth_check_task; shared_qobject_ptr m_flame_check_task; - const std::shared_ptr m_mod_model; + const std::shared_ptr m_resource_model; - QList& m_candidates; - QList m_modrinth_to_update; - QList m_flame_to_update; + QList& m_candidates; + QList m_modrinth_to_update; + QList m_flame_to_update; ConcurrentTask::Ptr m_second_try_metadata; - QList> m_failed_metadata; - QList> m_failed_check_update; + QList> m_failed_metadata; + QList> m_failed_check_update; QHash m_tasks; BaseInstance* m_instance; diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 0cc60205d0..e513cb1fad 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -51,8 +51,8 @@ #include "ui/GuiUtil.h" #include "ui/dialogs/CustomMessageBox.h" -#include "ui/dialogs/ModUpdateDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h" +#include "ui/dialogs/ResourceUpdateDialog.h" #include "DesktopServices.h" @@ -161,9 +161,8 @@ bool ModFolderPage::onSelectionChanged(const QModelIndex& current, [[maybe_unuse { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); - Mod const* m = m_model->at(row); - if (m) - ui->frame->updateWithMod(*m); + const Mod& mod = m_model->at(row); + ui->frame->updateWithMod(mod); return true; } @@ -253,12 +252,12 @@ void ModFolderPage::updateMods(bool includeDeps) } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedMods(selection); + auto mods_list = m_model->selectedResources(selection); bool use_all = mods_list.empty(); if (use_all) - mods_list = m_model->allMods(); + mods_list = m_model->allResources(); - ModUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps); + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps); update_dialog.checkCandidates(); if (update_dialog.aborted()) { From 82d0f204e2293e8969cc6604c1dff0d601a47b4d Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 26 Jan 2024 03:01:02 +0000 Subject: [PATCH 0122/2054] De-OOP ProviderCapabilities There was no reason for it to be a class, and imo it created quite a code-smell needing to initialise it everywhere. Signed-off-by: TheKodeToad --- launcher/minecraft/mod/Resource.cpp | 4 +--- launcher/modplatform/EnsureMetadataTask.cpp | 8 +++----- launcher/modplatform/ModIndex.h | 3 +-- launcher/modplatform/flame/FlameModIndex.cpp | 3 +-- launcher/modplatform/helpers/HashUtils.cpp | 14 ++++++-------- .../modplatform/modrinth/ModrinthCheckUpdate.cpp | 3 +-- .../modplatform/modrinth/ModrinthPackIndex.cpp | 3 +-- launcher/modplatform/packwiz/Packwiz.cpp | 8 +++----- launcher/ui/dialogs/ChooseProviderDialog.cpp | 4 +--- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 4 +--- launcher/ui/dialogs/ResourceUpdateDialog.cpp | 4 +--- 11 files changed, 20 insertions(+), 38 deletions(-) diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 30b453812a..7c1fb91080 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -5,8 +5,6 @@ #include "FileSystem.h" -static ModPlatform::ProviderCapabilities ProviderCaps; - Resource::Resource(QObject* parent) : QObject(parent) {} Resource::Resource(QFileInfo file_info) : QObject() @@ -74,7 +72,7 @@ static void removeThePrefix(QString& string) auto Resource::provider() const -> QString { if (metadata()) - return ProviderCaps.readableName(metadata()->provider); + return ModPlatform::ProviderCapabilities::readableName(metadata()->provider); return tr("Unknown"); } diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index b71bb1e0e1..277cd07646 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -15,8 +15,6 @@ #include "modplatform/modrinth/ModrinthAPI.h" #include "modplatform/modrinth/ModrinthPackIndex.h" -static ModPlatform::ProviderCapabilities ProviderCaps; - static ModrinthAPI modrinth_api; static FlameAPI flame_api; @@ -162,10 +160,10 @@ void EnsureMetadataTask::executeTask() }); if (m_resources.size() > 1) - setStatus(tr("Requesting metadata information from %1...").arg(ProviderCaps.readableName(m_provider))); + setStatus(tr("Requesting metadata information from %1...").arg(ModPlatform::ProviderCapabilities::readableName(m_provider))); else if (!m_resources.empty()) setStatus(tr("Requesting metadata information from %1 for '%2'...") - .arg(ProviderCaps.readableName(m_provider), m_resources.begin().value()->name())); + .arg(ModPlatform::ProviderCapabilities::readableName(m_provider), m_resources.begin().value()->name())); m_current_task = version_task; version_task->start(); @@ -215,7 +213,7 @@ void EnsureMetadataTask::emitFail(Resource* resource, QString key, RemoveFromLis Task::Ptr EnsureMetadataTask::modrinthVersionsTask() { - auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); + auto hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first(); auto response = std::make_shared(); auto ver_task = modrinth_api.currentVersions(m_resources.keys(), hash_type, response); diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index eff7e7f9f9..aeae87235a 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -40,8 +40,7 @@ enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK }; enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN }; -class ProviderCapabilities { - public: +namespace ProviderCapabilities { auto name(ResourceProvider) -> const char*; auto readableName(ResourceProvider) -> QString; auto hashType(ResourceProvider) -> QStringList; diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 345883c174..16cbbade42 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -6,7 +6,6 @@ #include "modplatform/flame/FlameAPI.h" static FlameAPI api; -static ModPlatform::ProviderCapabilities ProviderCaps; void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { @@ -158,7 +157,7 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> auto hash_list = Json::ensureArray(obj, "hashes"); for (auto h : hash_list) { auto hash_entry = Json::ensureObject(h); - auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::FLAME); + auto hash_types = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::FLAME); auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm")); if (hash_types.contains(hash_algo)) { file.hash = Json::requireString(hash_entry, "value"); diff --git a/launcher/modplatform/helpers/HashUtils.cpp b/launcher/modplatform/helpers/HashUtils.cpp index 6ff1d17106..19e5b447a7 100644 --- a/launcher/modplatform/helpers/HashUtils.cpp +++ b/launcher/modplatform/helpers/HashUtils.cpp @@ -10,8 +10,6 @@ namespace Hashing { -static ModPlatform::ProviderCapabilities ProviderCaps; - Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider) { switch (provider) { @@ -62,8 +60,8 @@ void ModrinthHasher::executeTask() return; } - auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); - m_hash = ProviderCaps.hash(ModPlatform::ResourceProvider::MODRINTH, &file, hash_type); + auto hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first(); + m_hash = ModPlatform::ProviderCapabilities::hash(ModPlatform::ResourceProvider::MODRINTH, &file, hash_type); file.close(); @@ -96,7 +94,7 @@ void FlameHasher::executeTask() BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) : Hasher(file_path), provider(provider) { setObjectName(QString("BlockedModHasher: %1").arg(file_path)); - hash_type = ProviderCaps.hashType(provider).first(); + hash_type = ModPlatform::ProviderCapabilities::hashType(provider).first(); } void BlockedModHasher::executeTask() @@ -113,7 +111,7 @@ void BlockedModHasher::executeTask() return; } - m_hash = ProviderCaps.hash(provider, &file, hash_type); + m_hash = ModPlatform::ProviderCapabilities::hash(provider, &file, hash_type); file.close(); @@ -127,12 +125,12 @@ void BlockedModHasher::executeTask() QStringList BlockedModHasher::getHashTypes() { - return ProviderCaps.hashType(provider); + return ModPlatform::ProviderCapabilities::hashType(provider); } bool BlockedModHasher::useHashType(QString type) { - auto types = ProviderCaps.hashType(provider); + auto types = ModPlatform::ProviderCapabilities::hashType(provider); if (types.contains(type)) { hash_type = type; return true; diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index f7e9fb8a9e..881f5499cb 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -13,7 +13,6 @@ #include "minecraft/mod/ModFolderModel.h" static ModrinthAPI api; -static ModPlatform::ProviderCapabilities ProviderCaps; bool ModrinthCheckUpdate::abort() { @@ -36,7 +35,7 @@ void ModrinthCheckUpdate::executeTask() // Create all hashes QStringList hashes; - auto best_hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); + auto best_hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first(); ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); for (auto* resource : m_resources) { diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index c1c30ab5fb..7a74619e5c 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -25,7 +25,6 @@ #include "modplatform/ModIndex.h" static ModrinthAPI api; -static ModPlatform::ProviderCapabilities ProviderCaps; bool shouldDownloadOnSide(QString side) { @@ -233,7 +232,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t file.hash = Json::requireString(hash_list, preferred_hash_type); file.hash_type = preferred_hash_type; } else { - auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH); + auto hash_types = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH); for (auto& hash_type : hash_types) { if (hash_list.contains(hash_type)) { file.hash = Json::requireString(hash_list, hash_type); diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 81c0a1efbb..c609e78fc5 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -66,8 +66,6 @@ static inline auto indexFileName(QString const& mod_slug) -> QString return QString("%1.pw.toml").arg(mod_slug); } -static ModPlatform::ProviderCapabilities ProviderCaps; - // Helper functions for extracting data from the TOML file auto stringEntry(toml::table table, QString entry_name) -> QString { @@ -201,7 +199,7 @@ void V1::updateModIndex(const QDir& index_dir, Mod& mod) { "hash-format", mod.hash_format.toStdString() }, { "hash", mod.hash.toStdString() }, } }, - { "update", toml::table{ { ProviderCaps.name(mod.provider), update }, + { "update", toml::table{ { ModPlatform::ProviderCapabilities::name(mod.provider), update }, { "x-prismlauncher-version-number", mod.version_number.toStdString() } } } }; std::stringstream ss; ss << tbl; @@ -306,11 +304,11 @@ auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod mod.version_number = stringEntry(*update_table, "x-prismlauncher-version-number"); toml::table* mod_provider_table = nullptr; - if ((mod_provider_table = (*update_table)[ProviderCaps.name(Provider::FLAME)].as_table())) { + if ((mod_provider_table = (*update_table)[ModPlatform::ProviderCapabilities::name(Provider::FLAME)].as_table())) { mod.provider = Provider::FLAME; mod.file_id = intEntry(*mod_provider_table, "file-id"); mod.project_id = intEntry(*mod_provider_table, "project-id"); - } else if ((mod_provider_table = (*update_table)[ProviderCaps.name(Provider::MODRINTH)].as_table())) { + } else if ((mod_provider_table = (*update_table)[ModPlatform::ProviderCapabilities::name(Provider::MODRINTH)].as_table())) { mod.provider = Provider::MODRINTH; mod.mod_id() = stringEntry(*mod_provider_table, "mod-id"); mod.version() = stringEntry(*mod_provider_table, "version"); diff --git a/launcher/ui/dialogs/ChooseProviderDialog.cpp b/launcher/ui/dialogs/ChooseProviderDialog.cpp index 83748e1e29..68457802d0 100644 --- a/launcher/ui/dialogs/ChooseProviderDialog.cpp +++ b/launcher/ui/dialogs/ChooseProviderDialog.cpp @@ -6,8 +6,6 @@ #include "modplatform/ModIndex.h" -static ModPlatform::ProviderCapabilities ProviderCaps; - ChooseProviderDialog::ChooseProviderDialog(QWidget* parent, bool single_choice, bool allow_skipping) : QDialog(parent), ui(new Ui::ChooseProviderDialog) { @@ -78,7 +76,7 @@ void ChooseProviderDialog::addProviders() QRadioButton* btn; for (auto& provider : { ModPlatform::ResourceProvider::MODRINTH, ModPlatform::ResourceProvider::FLAME }) { - btn = new QRadioButton(ProviderCaps.readableName(provider), this); + btn = new QRadioButton(ModPlatform::ProviderCapabilities::readableName(provider), this); m_providers.addButton(btn, btn_index++); ui->providersLayout->addWidget(btn); } diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 1431ea92ce..3f2be760a4 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -125,8 +125,6 @@ void ResourceDownloadDialog::connectButtons() connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); } -static ModPlatform::ProviderCapabilities ProviderCaps; - void ResourceDownloadDialog::confirm() { auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); @@ -167,7 +165,7 @@ void ResourceDownloadDialog::confirm() }); for (auto& task : selected) { confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(), - ProviderCaps.name(task->getProvider()), getRequiredBy.value(task->getPack()->addonId.toString()), + ModPlatform::ProviderCapabilities::name(task->getProvider()), getRequiredBy.value(task->getPack()->addonId.toString()), task->getVersion().version_type.toString() }); } diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index cad5b9f7f6..459ba7f232 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -24,8 +24,6 @@ #include -static ModPlatform::ProviderCapabilities ProviderCaps; - static std::list mcVersions(BaseInstance* inst) { return { static_cast(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; @@ -423,7 +421,7 @@ void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, Q item_top->setExpanded(true); auto provider_item = new QTreeWidgetItem(item_top); - provider_item->setText(0, tr("Provider: %1").arg(ProviderCaps.readableName(info.provider))); + provider_item->setText(0, tr("Provider: %1").arg(ModPlatform::ProviderCapabilities::readableName(info.provider))); auto old_version_item = new QTreeWidgetItem(item_top); old_version_item->setText(0, tr("Old version: %1").arg(info.old_version)); From ad98f600d5666396577295a3915e039314767f60 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 26 Jan 2024 18:36:31 +0200 Subject: [PATCH 0123/2054] corecly display core mods tab Signed-off-by: Trial97 --- launcher/ui/pages/instance/ModFolderPage.cpp | 29 +++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index aba87942a7..4b96338ae0 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -66,6 +66,7 @@ #include "Version.h" #include "tasks/ConcurrentTask.h" +#include "tasks/Task.h" #include "ui/dialogs/ProgressDialog.h" ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent) @@ -320,21 +321,23 @@ bool CoreModFolderPage::shouldDisplay() const return true; auto version = inst->getPackProfile(); - - ProgressDialog loadDialog(parentWidget()); - auto update = inst->createUpdateTask(Net::Mode::Offline); - if (update) { - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(update.get()); - } - if (!version) - return true; - if (!version->getComponent("net.minecraftforge")) + if (!version || !version->getComponent("net.minecraftforge") || !version->getComponent("net.minecraft")) return false; - if (!version->getComponent("net.minecraft")) + auto minecraftCmp = version->getComponent("net.minecraft"); + if (!minecraftCmp->m_loaded) { + version->reload(Net::Mode::Offline); + auto update = version->getCurrentTask(); + if (update) { + connect(update.get(), &Task::finished, this, [this] { + if (m_container) { + m_container->refreshContainer(); + } + }); + update->start(); + } return false; - if (version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate) - return true; + } + return minecraftCmp->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate; } return false; } From b54410b48cf10821fc3229d5d6989645e95c66f0 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 26 Jan 2024 19:10:39 +0200 Subject: [PATCH 0124/2054] Added gameVersion for curseforge mod packs Signed-off-by: Trial97 --- launcher/ui/pages/modplatform/flame/FlamePage.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index f1fd9b5d89..0074a92254 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -178,7 +178,11 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde for (auto version : current.versions) { auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : ""; - ui->versionSelectionBox->addItem(QString("%1%2").arg(version.version, release_type), QVariant(version.downloadUrl)); + auto mcVersion = !version.mcVersion.isEmpty() && !version.version.contains(version.mcVersion) + ? QString(" for %1").arg(version.mcVersion) + : ""; + ui->versionSelectionBox->addItem(QString("%1%2%3").arg(version.version, mcVersion, release_type), + QVariant(version.downloadUrl)); } QVariant current_updated; From 7317105e4d8c7a65f4a722dbebf8d5e3247f80dc Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 27 Jan 2024 22:47:28 +0200 Subject: [PATCH 0125/2054] Code spaghetti and more chaos Signed-off-by: Trial97 --- launcher/BaseVersionList.cpp | 1 + launcher/BaseVersionList.h | 1 + launcher/CMakeLists.txt | 8 +- launcher/JavaDownloader.cpp | 332 ------------------ launcher/JavaDownloader.h | 28 -- launcher/SysInfo.cpp | 43 ++- launcher/SysInfo.h | 1 + launcher/VersionProxyModel.cpp | 9 + launcher/VersionProxyModel.h | 2 +- launcher/java/JavaRuntime.cpp | 2 +- launcher/java/JavaVersion.cpp | 21 +- .../java/download/ArchiveJavaDownloader.cpp | 2 +- .../java/download/ManifestJavaDownloader.cpp | 2 +- launcher/ui/java/JavaDownload.cpp | 103 ++++++ launcher/ui/java/JavaDownload.h | 48 +++ launcher/ui/java/JavaDownload.ui | 100 ++++++ launcher/ui/java/ListModel.cpp | 154 ++++++++ launcher/ui/java/ListModel.h | 132 +++++++ launcher/ui/pages/global/JavaPage.cpp | 5 +- .../pages/instance/InstanceSettingsPage.cpp | 5 +- launcher/ui/widgets/JavaSettingsWidget.cpp | 5 +- 21 files changed, 613 insertions(+), 391 deletions(-) delete mode 100644 launcher/JavaDownloader.cpp delete mode 100644 launcher/JavaDownloader.h create mode 100644 launcher/ui/java/JavaDownload.cpp create mode 100644 launcher/ui/java/JavaDownload.h create mode 100644 launcher/ui/java/JavaDownload.ui create mode 100644 launcher/ui/java/ListModel.cpp create mode 100644 launcher/ui/java/ListModel.h diff --git a/launcher/BaseVersionList.cpp b/launcher/BaseVersionList.cpp index e11560d5ec..576c3a413d 100644 --- a/launcher/BaseVersionList.cpp +++ b/launcher/BaseVersionList.cpp @@ -110,6 +110,7 @@ QHash BaseVersionList::roleNames() const roles.insert(TypeRole, "type"); roles.insert(BranchRole, "branch"); roles.insert(PathRole, "path"); + roles.insert(AliasRole, "alias"); roles.insert(ArchitectureRole, "architecture"); return roles; } diff --git a/launcher/BaseVersionList.h b/launcher/BaseVersionList.h index 231887c4ea..c59d705707 100644 --- a/launcher/BaseVersionList.h +++ b/launcher/BaseVersionList.h @@ -48,6 +48,7 @@ class BaseVersionList : public QAbstractListModel { TypeRole, BranchRole, PathRole, + AliasRole, ArchitectureRole, SortRole }; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index ac3f8eb68a..d703b82637 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -437,6 +437,11 @@ set(JAVA_SOURCES java/download/ArchiveJavaDownloader.h java/download/ManifestJavaDownloader.cpp java/download/ManifestJavaDownloader.h + + ui/java/JavaDownload.h + ui/java/JavaDownload.cpp + ui/java/ListModel.h + ui/java/ListModel.cpp ) set(TRANSLATIONS_SOURCES @@ -1107,8 +1112,6 @@ SET(LAUNCHER_SOURCES ui/instanceview/InstanceDelegate.h ui/instanceview/VisualGroup.cpp ui/instanceview/VisualGroup.h - JavaDownloader.cpp - JavaDownloader.h ) if (NOT Apple) @@ -1189,6 +1192,7 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/ScrollMessageBox.ui ui/dialogs/BlockedModsDialog.ui ui/dialogs/ChooseProviderDialog.ui + ui/java/JavaDownload.ui ) qt_wrap_ui(PRISM_UPDATE_UI diff --git a/launcher/JavaDownloader.cpp b/launcher/JavaDownloader.cpp deleted file mode 100644 index e4f4b5f09f..0000000000 --- a/launcher/JavaDownloader.cpp +++ /dev/null @@ -1,332 +0,0 @@ -#include "JavaDownloader.h" -#include -#include -#include -#include -#include "Application.h" -#include "FileSystem.h" -#include "InstanceList.h" -#include "Json.h" -#include "MMCZip.h" -#include "SysInfo.h" -#include "net/ChecksumValidator.h" -#include "net/NetJob.h" -#include "ui/dialogs/ProgressDialog.h" - -// Quick & dirty struct to store files -struct File { - QString path; - QString url; - QByteArray hash; - bool isExec; -}; - -void JavaDownloader::executeTask() -{ - auto OS = m_OS; - auto isLegacy = m_isLegacy; - - downloadMojangJavaList(OS, isLegacy); -} -void JavaDownloader::downloadMojangJavaList(const QString& OS, bool isLegacy) -{ - auto netJob = makeShared(QString("JRE::QueryVersions"), APPLICATION->network()); - auto response = std::make_shared(); - setStatus(tr("Querying mojang meta")); - netJob->addNetAction(Net::Download::makeByteArray( - QUrl("https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"), response)); - - connect(this, &Task::aborted, [isLegacy] { - QDir(FS::PathCombine(QCoreApplication::applicationDirPath(), "java", (isLegacy ? "java-legacy" : "java-current"))) - .removeRecursively(); - }); - - connect(netJob.get(), &NetJob::finished, [netJob, response, this] { - // delete so that it's not called on a deleted job - // FIXME: is this needed? qt should handle this - disconnect(this, &Task::aborted, netJob.get(), &NetJob::abort); - }); - connect(netJob.get(), &NetJob::progress, this, &JavaDownloader::progress); - connect(netJob.get(), &NetJob::failed, this, &JavaDownloader::emitFailed); - - connect(this, &Task::aborted, netJob.get(), &NetJob::abort); - - connect(netJob.get(), &NetJob::succeeded, [response, OS, isLegacy, this, netJob] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - auto versionArray = Json::ensureArray(Json::ensureObject(doc.object(), OS), isLegacy ? "jre-legacy" : "java-runtime-gamma"); - if (!versionArray.empty()) { - parseMojangManifest(isLegacy, versionArray); - - } else { - // mojang does not have a JRE for us, let's get azul zulu - downloadAzulMeta(OS, isLegacy, netJob.get()); - } - }); - - netJob->start(); -} -void JavaDownloader::parseMojangManifest(bool isLegacy, const QJsonArray& versionArray) -{ - setStatus(tr("Downloading Java from Mojang")); - auto url = Json::ensureString(Json::ensureObject(Json::ensureObject(versionArray[0]), "manifest"), "url"); - auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); - auto files = std::make_shared(); - - download->addNetAction(Net::Download::makeByteArray(QUrl(url), files)); - - connect(download.get(), &NetJob::finished, - [download, files, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); - connect(download.get(), &NetJob::progress, this, &JavaDownloader::progress); - connect(download.get(), &NetJob::failed, this, &JavaDownloader::emitFailed); - connect(this, &Task::aborted, download.get(), &NetJob::abort); - - connect(download.get(), &NetJob::succeeded, [files, isLegacy, this] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << *files; - return; - } - downloadMojangJava(isLegacy, doc); - }); - download->start(); -} -void JavaDownloader::downloadMojangJava(bool isLegacy, const QJsonDocument& doc) -{ // valid json doc, begin making jre spot - auto output = FS::PathCombine(QCoreApplication::applicationDirPath(), QString("java"), (isLegacy ? "java-legacy" : "java-current")); - FS::ensureFolderPathExists(output); - std::vector toDownload; - auto list = Json::ensureObject(Json::ensureObject(doc.object()), "files"); - for (const auto& paths : list.keys()) { - auto file = FS::PathCombine(output, paths); - - const QJsonObject& meta = Json::ensureObject(list, paths); - auto type = Json::ensureString(meta, "type"); - if (type == "directory") { - FS::ensureFolderPathExists(file); - } else if (type == "link") { - // this is linux only ! - auto path = Json::ensureString(meta, "target"); - if (!path.isEmpty()) { - auto target = FS::PathCombine(file, "../" + path); - QFile(target).link(file); - } - } else if (type == "file") { - // TODO download compressed version if it exists ? - auto raw = Json::ensureObject(Json::ensureObject(meta, "downloads"), "raw"); - auto isExec = Json::ensureBoolean(meta, "executable", false); - auto url = Json::ensureString(raw, "url"); - if (!url.isEmpty() && QUrl(url).isValid()) { - auto f = File{ file, url, QByteArray::fromHex(Json::ensureString(raw, "sha1").toLatin1()), isExec }; - toDownload.push_back(f); - } - } - } - auto elementDownload = new NetJob("JRE::FileDownload", APPLICATION->network()); - for (const auto& file : toDownload) { - auto dl = Net::Download::makeFile(file.url, file.path); - if (!file.hash.isEmpty()) { - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.hash)); - } - if (file.isExec) { - connect(dl.get(), &Net::Download::succeeded, - [file] { QFile(file.path).setPermissions(QFile(file.path).permissions() | QFileDevice::Permissions(0x1111)); }); - } - elementDownload->addNetAction(dl); - } - connect(elementDownload, &NetJob::finished, [elementDownload, this] { - disconnect(this, &Task::aborted, elementDownload, &NetJob::abort); - elementDownload->deleteLater(); - }); - connect(elementDownload, &NetJob::progress, this, &JavaDownloader::progress); - connect(elementDownload, &NetJob::failed, this, &JavaDownloader::emitFailed); - - connect(this, &Task::aborted, elementDownload, &NetJob::abort); - connect(elementDownload, &NetJob::succeeded, [this] { emitSucceeded(); }); - elementDownload->start(); -} -void JavaDownloader::downloadAzulMeta(const QString& OS, bool isLegacy, const NetJob* netJob) -{ - setStatus(tr("Querying Azul meta")); - QString javaVersion = isLegacy ? QString("8.0") : QString("17.0"); - - QString azulOS; - QString arch; - QString bitness; - - mojangOStoAzul(OS, azulOS, arch, bitness); - auto metaResponse = std::make_shared(); - auto downloadJob = makeShared(QString("JRE::QueryAzulMeta"), APPLICATION->network()); - downloadJob->addNetAction( - Net::Download::makeByteArray(QString("https://api.azul.com/zulu/download/community/v1.0/bundles/?" - "java_version=%1" - "&os=%2" - "&arch=%3" - "&hw_bitness=%4" - "&ext=zip" // as a zip for all os, even linux NOTE !! Linux ARM is .deb or .tar.gz only !! - "&bundle_type=jre" // jre only - "&latest=true" // only get the one latest entry - ) - .arg(javaVersion, azulOS, arch, bitness), - metaResponse)); - connect(downloadJob.get(), &NetJob::finished, - [downloadJob, metaResponse, this] { disconnect(this, &Task::aborted, downloadJob.get(), &NetJob::abort); }); - connect(this, &Task::aborted, downloadJob.get(), &NetJob::abort); - connect(netJob, &NetJob::failed, this, &JavaDownloader::emitFailed); - connect(downloadJob.get(), &NetJob::progress, this, &JavaDownloader::progress); - connect(downloadJob.get(), &NetJob::succeeded, [metaResponse, isLegacy, this] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*metaResponse, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << *metaResponse; - return; - } - auto array = Json::ensureArray(doc.array()); - if (!array.empty()) { - downloadAzulJava(isLegacy, array); - } else { - emitFailed(tr("No suitable JRE found")); - } - }); - downloadJob->start(); -} -void JavaDownloader::mojangOStoAzul(const QString& OS, QString& azulOS, QString& arch, QString& bitness) -{ - if (OS == "mac-os-arm64") { - // macos arm64 - azulOS = "macos"; - arch = "arm"; - bitness = "64"; - } else if (OS == "linux-arm64") { - // linux arm64 - azulOS = "linux"; - arch = "arm"; - bitness = "64"; - } else if (OS == "linux-arm") { - // linux arm (32) - azulOS = "linux"; - arch = "arm"; - bitness = "32"; - } else if (OS == "linux") { - // linux x86 64 (used for debugging, should never reach here) - azulOS = "linux"; - arch = "x86"; - bitness = "64"; - } -} -void JavaDownloader::downloadAzulJava(bool isLegacy, const QJsonArray& array) -{ // JRE found ! download the zip - setStatus(tr("Downloading Java from Azul")); - auto downloadURL = QUrl(array[0].toObject()["url"].toString()); - auto download = new NetJob(QString("JRE::DownloadJava"), APPLICATION->network()); - auto path = APPLICATION->instances()->getStagedInstancePath(); - auto temp = FS::PathCombine(path, "azulJRE.zip"); - - download->addNetAction(Net::Download::makeFile(downloadURL, temp)); - connect(download, &NetJob::finished, [download, this] { - disconnect(this, &Task::aborted, download, &NetJob::abort); - download->deleteLater(); - }); - connect(download, &NetJob::aborted, [path] { APPLICATION->instances()->destroyStagingPath(path); }); - connect(download, &NetJob::progress, this, &JavaDownloader::progress); - connect(download, &NetJob::failed, this, [this, path](QString reason) { - APPLICATION->instances()->destroyStagingPath(path); - emitFailed(std::move(reason)); - }); - connect(this, &Task::aborted, download, &NetJob::abort); - connect(download, &NetJob::succeeded, [isLegacy, temp, downloadURL, path, this] { - setStatus(tr("Extracting java")); - auto output = FS::PathCombine(QCoreApplication::applicationDirPath(), "java", isLegacy ? "java-legacy" : "java-current"); - // This should do all of the extracting and creating folders - MMCZip::extractDir(temp, downloadURL.fileName().chopped(4), output); - APPLICATION->instances()->destroyStagingPath(path); - emitSucceeded(); - }); - download->start(); -} -void JavaDownloader::showPrompts(QWidget* parent) -{ - QString sys = SysInfo::currentSystem(); - if (sys == "osx") { - sys = "mac-os"; - } - QString arch = SysInfo::useQTForArch(); - QString version; - if (sys == "windows") { - if (arch == "x86_64") { - version = "windows-x64"; - } else if (arch == "i386") { - version = "windows-x86"; - } else { - // Unknown, maybe arm, appending arch for downloader - version = "windows-" + arch; - } - } else if (sys == "mac-os") { - if (arch == "arm64") { - version = "mac-os-arm64"; - } else { - version = "mac-os"; - } - } else if (sys == "linux") { - if (arch == "x86_64") { - version = "linux"; - } else { - // will work for i386, and arm(64) - version = "linux-" + arch; - } - } else { - // ? ? ? ? ? unknown os, at least it won't have a java version on mojang or azul, display warning - QMessageBox::warning(parent, tr("Unknown OS"), - tr("The OS you are running is not supported by Mojang or Azul. Please install Java manually.")); - return; - } - // Selection using QMessageBox for java 8 or 17 - QMessageBox box( - QMessageBox::Icon::Question, tr("Java version"), - tr("Do you want to download Java version 8 or 17?\n Java 8 is recommended for older Minecraft versions, below 1.17\n Java 17 " - "is recommended for newer Minecraft versions, starting from 1.17"), - QMessageBox::NoButton, parent); - auto yes = box.addButton("Java 17", QMessageBox::AcceptRole); - auto no = box.addButton("Java 8", QMessageBox::AcceptRole); - auto both = box.addButton(tr("Download both"), QMessageBox::AcceptRole); - auto cancel = box.addButton(QMessageBox::Cancel); - - if (QFileInfo::exists(FS::PathCombine(QCoreApplication::applicationDirPath(), QString("java"), "java-legacy"))) { - no->setEnabled(false); - } - if (QFileInfo::exists(FS::PathCombine(QCoreApplication::applicationDirPath(), QString("java"), "java-current"))) { - yes->setEnabled(false); - } - if (!yes->isEnabled() || !no->isEnabled()) { - both->setEnabled(false); - } - if (!yes->isEnabled() && !no->isEnabled()) { - QMessageBox::information(parent, tr("Already installed!"), tr("Both versions of Java are already installed!")); - return; - } - box.exec(); - if (box.clickedButton() == nullptr || box.clickedButton() == cancel) { - return; - } - bool isLegacy = box.clickedButton() == no; - - auto down = new JavaDownloader(isLegacy, version); - ProgressDialog dialog(parent); - dialog.setSkipButton(true, tr("Abort")); - bool finished_successfully = dialog.execWithTask(down); - // Run another download task for the other option as well! - if (finished_successfully && box.clickedButton() == both) { - auto dwn = new JavaDownloader(false, version); - ProgressDialog dg(parent); - dg.setSkipButton(true, tr("Abort")); - dg.execWithTask(dwn); - } -} diff --git a/launcher/JavaDownloader.h b/launcher/JavaDownloader.h deleted file mode 100644 index 54f058970d..0000000000 --- a/launcher/JavaDownloader.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include "net/NetJob.h" -#include "tasks/Task.h" - -class JavaDownloader : public Task { - Q_OBJECT - public: - /*Downloads the java to the runtimes folder*/ - explicit JavaDownloader(bool isLegacy, const QString& OS) : m_isLegacy(isLegacy), m_OS(OS) {} - - void executeTask() override; - [[nodiscard]] bool canAbort() const override { return true; } - static void showPrompts(QWidget* parent = nullptr); - - private: - bool m_isLegacy; - const QString& m_OS; - - void downloadMojangJavaList(const QString& OS, bool isLegacy); - void parseMojangManifest(bool isLegacy, const QJsonArray& versionArray); - void downloadMojangJava(bool isLegacy, const QJsonDocument& doc); - - static void mojangOStoAzul(const QString& OS, QString& azulOS, QString& arch, QString& bitness); - void downloadAzulMeta(const QString& OS, bool isLegacy, const NetJob* netJob); - void downloadAzulJava(bool isLegacy, const QJsonArray& array); -}; diff --git a/launcher/SysInfo.cpp b/launcher/SysInfo.cpp index f15dde0e4f..0dfa74de7f 100644 --- a/launcher/SysInfo.cpp +++ b/launcher/SysInfo.cpp @@ -17,13 +17,7 @@ bool rosettaDetect() if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) { return false; } - if (ret == 0) { - return false; - } - if (ret == 1) { - return true; - } - return false; + return ret == 1; } #endif @@ -47,7 +41,6 @@ QString currentSystem() QString useQTForArch() { - auto qtArch = QSysInfo::currentCpuArchitecture(); #if defined(Q_OS_MACOS) && !defined(Q_PROCESSOR_ARM) if (rosettaDetect()) { return "arm64"; @@ -55,7 +48,7 @@ QString useQTForArch() return "x86_64"; } #endif - return qtArch; + return QSysInfo::currentCpuArchitecture(); } int suitableMaxMem() @@ -71,4 +64,36 @@ int suitableMaxMem() return maxMemoryAlloc; } + +QString getSupportedJavaArchitecture() +{ + auto sys = currentSystem(); + auto arch = useQTForArch(); + if (sys == "windows") { + if (arch == "x86_64") + return "windows-x64"; + if (arch == "i386") + return "windows-x86"; + // Unknown, maybe arm, appending arch + return "windows-" + arch; + } + if (sys == "osx") { + if (arch == "arm64") + return "mac-os-arm64"; + if (arch.contains("64")) + return "mac-os-64"; + if (arch.contains("86")) + return "mac-os-86"; + // Unknown, maybe something new, appending arch + return "mac-os-" + arch; + } else if (sys == "linux") { + if (arch == "x86_64") + return "linux-x64"; + if (arch == "i386") + return "linux-x86"; + // will work for arm32 arm(64) + return "linux-" + arch; + } + return {}; +} } // namespace SysInfo diff --git a/launcher/SysInfo.h b/launcher/SysInfo.h index 499c3b1dd1..f3688d60d6 100644 --- a/launcher/SysInfo.h +++ b/launcher/SysInfo.h @@ -3,5 +3,6 @@ namespace SysInfo { QString currentSystem(); QString useQTForArch(); +QString getSupportedJavaArchitecture(); int suitableMaxMem(); } // namespace SysInfo diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index 0ab9ae2c3b..f1218f162d 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -118,6 +118,8 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation, return tr("Architecture"); case Path: return tr("Path"); + case Alias: + return tr("Alias"); case Time: return tr("Released"); } @@ -135,6 +137,8 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation, return tr("CPU Architecture"); case Path: return tr("Filesystem path to this version"); + case Alias: + return tr("The alternative name of the java version"); case Time: return tr("Release date of this version"); } @@ -169,6 +173,8 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const return sourceModel()->data(parentIndex, BaseVersionList::ArchitectureRole); case Path: return sourceModel()->data(parentIndex, BaseVersionList::PathRole); + case Alias: + return sourceModel()->data(parentIndex, BaseVersionList::AliasRole); case Time: return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate(); default: @@ -314,6 +320,9 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw) if (roles.contains(BaseVersionList::PathRole)) { m_columns.push_back(Path); } + if (roles.contains(BaseVersionList::AliasRole)) { + m_columns.push_back(Alias); + } if (roles.contains(Meta::VersionList::TimeRole)) { m_columns.push_back(Time); } diff --git a/launcher/VersionProxyModel.h b/launcher/VersionProxyModel.h index 0863a7c800..2dc35a6253 100644 --- a/launcher/VersionProxyModel.h +++ b/launcher/VersionProxyModel.h @@ -9,7 +9,7 @@ class VersionFilterModel; class VersionProxyModel : public QAbstractProxyModel { Q_OBJECT public: - enum Column { Name, ParentVersion, Branch, Type, Architecture, Path, Time }; + enum Column { Name, ParentVersion, Branch, Type, Architecture, Path, Time, Alias }; using FilterMap = QHash>; public: diff --git a/launcher/java/JavaRuntime.cpp b/launcher/java/JavaRuntime.cpp index 78651e9911..be13f47e43 100644 --- a/launcher/java/JavaRuntime.cpp +++ b/launcher/java/JavaRuntime.cpp @@ -62,7 +62,7 @@ MetaPtr parseJavaMeta(const QJsonObject& in) } if (in.contains("version")) { - auto obj = Json::requireObject(in, "checksum"); + auto obj = Json::requireObject(in, "version"); auto name = Json::ensureString(obj, "name", ""); auto major = Json::ensureInteger(obj, "major", 0); auto minor = Json::ensureInteger(obj, "minor", 0); diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp index 3de6f5ad6a..f3ded97099 100644 --- a/launcher/java/JavaVersion.cpp +++ b/launcher/java/JavaVersion.cpp @@ -113,19 +113,20 @@ bool JavaVersion::operator>(const JavaVersion& rhs) JavaVersion::JavaVersion(int major, int minor, int security, int build, QString name) : m_major(major), m_minor(minor), m_security(security), m_name(name), m_parseable(true) { + QStringList versions; if (build != 0) { m_prerelease = QString::number(build); - m_string = m_prerelease; + versions.push_front(m_prerelease); } if (m_security != 0) - m_string = QString::number(m_security) + "." + m_string; - else if (!m_string.isEmpty()) { - m_string = "0." + m_string; - } + versions.push_front(QString::number(m_security)); + else if (!versions.isEmpty()) + versions.push_front("0"); + if (m_minor != 0) - m_string = QString::number(m_minor) + "." + m_string; - else if (!m_string.isEmpty()) { - m_string = "0." + m_string; - } - m_string = QString::number(m_major) + "." + m_string; + versions.push_front(QString::number(m_minor)); + else if (!versions.isEmpty()) + versions.push_front("0"); + versions.push_front(QString::number(m_major)); + m_string = versions.join("."); } diff --git a/launcher/java/download/ArchiveJavaDownloader.cpp b/launcher/java/download/ArchiveJavaDownloader.cpp index 5350fc4e6e..bee65cf913 100644 --- a/launcher/java/download/ArchiveJavaDownloader.cpp +++ b/launcher/java/download/ArchiveJavaDownloader.cpp @@ -43,7 +43,7 @@ void ArchiveJavaDownloader::executeTask() if (m_checksum_type == "sha256") { hashType = QCryptographicHash::Algorithm::Sha256; } - action->addValidator(new Net::ChecksumValidator(hashType, m_checksum_hash.toLatin1())); + action->addValidator(new Net::ChecksumValidator(hashType, QByteArray::fromHex(m_checksum_hash.toUtf8()))); } download->addNetAction(action); auto fullPath = entry->getFullPath(); diff --git a/launcher/java/download/ManifestJavaDownloader.cpp b/launcher/java/download/ManifestJavaDownloader.cpp index 368d6431c3..c0b7942a7c 100644 --- a/launcher/java/download/ManifestJavaDownloader.cpp +++ b/launcher/java/download/ManifestJavaDownloader.cpp @@ -46,7 +46,7 @@ void ManifestJavaDownloader::executeTask() if (m_checksum_type == "sha256") { hashType = QCryptographicHash::Algorithm::Sha256; } - action->addValidator(new Net::ChecksumValidator(hashType, m_checksum_hash.toLatin1())); + action->addValidator(new Net::ChecksumValidator(hashType, QByteArray::fromHex(m_checksum_hash.toUtf8()))); } download->addNetAction(action); diff --git a/launcher/ui/java/JavaDownload.cpp b/launcher/ui/java/JavaDownload.cpp new file mode 100644 index 0000000000..c03fb10e81 --- /dev/null +++ b/launcher/ui/java/JavaDownload.cpp @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2024 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "JavaDownload.h" +#include +#include +#include +#include +#include +#include "Application.h" +#include "BaseVersionList.h" +#include "FileSystem.h" +#include "QObjectPtr.h" +#include "SysInfo.h" +#include "java/JavaInstallList.h" +#include "java/download/ArchiveJavaDownloader.h" +#include "java/download/ManifestJavaDownloader.h" +#include "meta/Index.h" +#include "meta/Version.h" +#include "ui/dialogs/ProgressDialog.h" +#include "ui/java/ListModel.h" +#include "ui_JavaDownload.h" + +JavaDownload::JavaDownload(QWidget* parent) : QDialog(parent), ui(new Ui::JavaDownload) +{ + ui->setupUi(this); + ui->widget->initialize(new Java::JavaBaseVersionList("net.minecraft.java")); + ui->widget->selectCurrent(); + connect(ui->widget, &VersionSelectWidget::selectedVersionChanged, this, &JavaDownload::setSelectedVersion); + auto reset = ui->buttonBox->button(QDialogButtonBox::Reset); + connect(reset, &QPushButton::clicked, this, &JavaDownload::refresh); + connect(ui->widget_2, &VersionSelectWidget::selectedVersionChanged, this, &JavaDownload::setSelectedVersion2); +} + +JavaDownload::~JavaDownload() +{ + delete ui; +} + +void JavaDownload::setSelectedVersion(BaseVersion::Ptr version) +{ + if (!version) + return; + auto dcast = std::dynamic_pointer_cast(version); + if (!dcast) { + return; + } + ui->widget_2->initialize(new Java::InstallList(dcast, this)); + ui->widget_2->selectCurrent(); +} + +void JavaDownload::setSelectedVersion2(BaseVersion::Ptr version) +{ + if (!version) + return; + m_selectedVersion = std::dynamic_pointer_cast(ui->widget_2->selectedVersion()); +} +void JavaDownload::accept() +{ + if (!m_selectedVersion) { + m_selectedVersion = std::dynamic_pointer_cast(ui->widget_2->selectedVersion()); + qDebug() << "=========?" << (ui->widget_2->selectedVersion() != nullptr); + } + if (!m_selectedVersion) { + qDebug() << "faillllllllllllllllllllllllllll"; + return; + } + auto meta = m_selectedVersion->meta; + Task::Ptr task; + auto final_path = FS::PathCombine(APPLICATION->dataRoot(), "java", meta->name); + qDebug() << "===============>>=>>" << meta->checksumType << meta->checksumHash; + switch (meta->downloadType) { + case JavaRuntime::DownloadType::Manifest: + task = makeShared(meta->url, final_path, meta->checksumType, meta->checksumHash); + break; + case JavaRuntime::DownloadType::Archive: + task = makeShared(meta->url, final_path, meta->checksumType, meta->checksumHash); + break; + } + ProgressDialog pg(this); + pg.execWithTask(task.get()); + QDialog::accept(); +} + +void JavaDownload::refresh() +{ + ui->widget->loadList(); +} diff --git a/launcher/ui/java/JavaDownload.h b/launcher/ui/java/JavaDownload.h new file mode 100644 index 0000000000..916d6b4952 --- /dev/null +++ b/launcher/ui/java/JavaDownload.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2024 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include "BaseVersion.h" +#include "ui/java/ListModel.h" + +namespace Ui { +class JavaDownload; +} + +class JavaDownload : public QDialog { + Q_OBJECT + + public: + explicit JavaDownload(QWidget* parent = 0); + ~JavaDownload(); + + void accept(); + + public slots: + void refresh(); + + protected slots: + void setSelectedVersion(BaseVersion::Ptr version); + void setSelectedVersion2(BaseVersion::Ptr version); + + private: + Ui::JavaDownload* ui; + Java::JavaRuntimePtr m_selectedVersion; +}; diff --git a/launcher/ui/java/JavaDownload.ui b/launcher/ui/java/JavaDownload.ui new file mode 100644 index 0000000000..bbc638e9bb --- /dev/null +++ b/launcher/ui/java/JavaDownload.ui @@ -0,0 +1,100 @@ + + + JavaDownload + + + + 0 + 0 + 821 + 593 + + + + Dialog + + + + + + + + Major + + + + + + + + + + + + Runtime + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Retry + + + + + + + + VersionSelectWidget + QWidget +
    ui/widgets/VersionSelectWidget.h
    + 1 +
    +
    + + + + buttonBox + accepted() + JavaDownload + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + JavaDownload + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
    diff --git a/launcher/ui/java/ListModel.cpp b/launcher/ui/java/ListModel.cpp new file mode 100644 index 0000000000..71f8765eb0 --- /dev/null +++ b/launcher/ui/java/ListModel.cpp @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2024 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ListModel.h" +#include +#include +#include "BaseVersionList.h" +#include "StringUtils.h" +#include "SysInfo.h" + +namespace Java { + +InstallList::InstallList(Meta::Version::Ptr version, QObject* parent) : BaseVersionList(parent), m_version(version) +{ + if (version->isLoaded()) + sortVersions(); +} + +Task::Ptr InstallList::getLoadTask() +{ + m_version->load(Net::Mode::Online); + auto task = m_version->getCurrentTask(); + connect(task.get(), &Task::finished, this, &InstallList::sortVersions); + return task; +} + +const BaseVersion::Ptr InstallList::at(int i) const +{ + return m_vlist.at(i); +} + +bool InstallList::isLoaded() +{ + return m_version->isLoaded(); +} + +int InstallList::count() const +{ + return m_vlist.count(); +} + +QVariant InstallList::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() > count()) + return QVariant(); + + auto version = (m_vlist[index.row()]); + switch (role) { + case SortRole: + return -index.row(); + case VersionPointerRole: + return QVariant::fromValue(std::dynamic_pointer_cast(m_vlist[index.row()])); + case VersionIdRole: + return version->descriptor(); + case VersionRole: + return version->meta->version.toString(); + case RecommendedRole: + return version->meta->recommended; + case AliasRole: + return version->meta->name; + case ArchitectureRole: + return version->meta->vendor; + default: + return QVariant(); + } +} + +BaseVersionList::RoleList InstallList::providesRoles() const +{ + return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, AliasRole, ArchitectureRole }; +} + +bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right) +{ + auto rleft = std::dynamic_pointer_cast(right); + auto rright = std::dynamic_pointer_cast(left); + return (*rleft) > (*rright); +} + +void InstallList::sortVersions() +{ + QString versionStr = SysInfo::getSupportedJavaArchitecture(); + beginResetModel(); + auto runtimes = m_version->data()->runtimes; + if (versionStr.isEmpty() || !runtimes.contains(versionStr)) { + return; + } + auto javaruntimes = runtimes.value(versionStr); + for (auto v : javaruntimes) { + m_vlist.append(std::make_shared(v)); + } + std::sort(m_vlist.begin(), m_vlist.end(), sortJavas); + endResetModel(); +} + +bool JavaRuntime2::operator<(const JavaRuntime2& rhs) +{ + auto id = meta->version; + if (id < rhs.meta->version) { + return true; + } + if (id > rhs.meta->version) { + return false; + } + return StringUtils::naturalCompare(meta->name, rhs.meta->name, Qt::CaseInsensitive) < 0; +} + +bool JavaRuntime2::operator==(const JavaRuntime2& rhs) +{ + return meta->version == rhs.meta->version && meta->name == rhs.meta->name; +} + +bool JavaRuntime2::operator>(const JavaRuntime2& rhs) +{ + return (!operator<(rhs)) && (!operator==(rhs)); +} + +bool JavaRuntime2::operator<(BaseVersion& a) +{ + try { + return operator<(dynamic_cast(a)); + } catch (const std::bad_cast& e) { + return BaseVersion::operator<(a); + } +} + +bool JavaRuntime2::operator>(BaseVersion& a) +{ + try { + return operator>(dynamic_cast(a)); + } catch (const std::bad_cast& e) { + return BaseVersion::operator>(a); + } +} + +} // namespace Java diff --git a/launcher/ui/java/ListModel.h b/launcher/ui/java/ListModel.h new file mode 100644 index 0000000000..3685d611d8 --- /dev/null +++ b/launcher/ui/java/ListModel.h @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2024 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include +#include "java/JavaRuntime.h" +#include "meta/VersionList.h" + +namespace Java { + +class JavaBaseVersionList : public Meta::VersionList { + Q_OBJECT + public: + explicit JavaBaseVersionList(const QString& uid, QObject* parent = nullptr) : VersionList(uid, parent) {} + BaseVersionList::RoleList providesRoles() const { return { VersionRole, RecommendedRole, VersionPointerRole }; } +}; + +struct JavaRuntime2 : public BaseVersion { + JavaRuntime2() {} + JavaRuntime2(JavaRuntime::MetaPtr m) : meta(m) {} + virtual QString descriptor() override { return meta->version.toString(); } + + virtual QString name() override { return meta->name; } + + virtual QString typeString() const override { return meta->vendor; } + + virtual bool operator<(BaseVersion& a) override; + virtual bool operator>(BaseVersion& a) override; + bool operator<(const JavaRuntime2& rhs); + bool operator==(const JavaRuntime2& rhs); + bool operator>(const JavaRuntime2& rhs); + + JavaRuntime::MetaPtr meta; +}; + +using JavaRuntimePtr = std::shared_ptr; + +class InstallList : public BaseVersionList { + Q_OBJECT + + public: + explicit InstallList(Meta::Version::Ptr m_version, QObject* parent = 0); + + Task::Ptr getLoadTask() override; + bool isLoaded() override; + const BaseVersion::Ptr at(int i) const override; + int count() const override; + void sortVersions() override; + + QVariant data(const QModelIndex& index, int role) const override; + RoleList providesRoles() const override; + + protected slots: + void updateListData(QList) override {} + + protected: + Meta::Version::Ptr m_version; + QList m_vlist; +}; + +} // namespace Java +// class FilterModel : public QSortFilterProxyModel { +// Q_OBJECT +// public: +// FilterModel(QObject* parent = Q_NULLPTR); +// enum Sorting { ByName, ByGameVersion }; +// const QMap getAvailableSortings(); +// QString translateCurrentSorting(); +// void setSorting(Sorting sorting); +// Sorting getCurrentSorting(); +// void setSearchTerm(QString term); + +// protected: +// bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; +// bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; + +// private: +// QMap sortings; +// Sorting currentSorting; +// QString searchTerm; +// }; + +// class ListModel : public QAbstractListModel { +// Q_OBJECT +// private: +// ModpackList modpacks; +// QStringList m_failedLogos; +// QStringList m_loadingLogos; +// FTBLogoMap m_logoMap; +// QMap waitingCallbacks; + +// void requestLogo(QString file); +// QString translatePackType(PackType type) const; + +// private slots: +// void logoFailed(QString logo); +// void logoLoaded(QString logo, QIcon out); + +// public: +// ListModel(QObject* parent); +// ~ListModel(); +// int rowCount(const QModelIndex& parent) const override; +// int columnCount(const QModelIndex& parent) const override; +// QVariant data(const QModelIndex& index, int role) const override; +// Qt::ItemFlags flags(const QModelIndex& index) const override; + +// void fill(ModpackList modpacks); +// void addPack(Modpack modpack); +// void clear(); +// void remove(int row); + +// Modpack at(int row); +// void getLogo(const QString& logo, LogoCallback callback); +// }; diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 0f05b6c8f0..09c951a3cc 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -36,6 +36,7 @@ #include "JavaPage.h" #include "JavaCommon.h" +#include "ui/java/JavaDownload.h" #include "ui_JavaPage.h" #include @@ -51,7 +52,6 @@ #include #include #include "Application.h" -#include "JavaDownloader.h" #include "settings/SettingsObject.h" JavaPage::JavaPage(QWidget* parent) : QWidget(parent), ui(new Ui::JavaPage) @@ -169,7 +169,8 @@ void JavaPage::on_javaTestBtn_clicked() void JavaPage::on_javaDownloadBtn_clicked() { - JavaDownloader::showPrompts(this); + auto jdialog = new JavaDownload(this); + jdialog->exec(); } void JavaPage::on_maxMemSpinBox_valueChanged([[maybe_unused]] int i) diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index a386a3bfe3..df380bfb3e 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -36,6 +36,7 @@ */ #include "InstanceSettingsPage.h" +#include "ui/java/JavaDownload.h" #include "ui_InstanceSettingsPage.h" #include @@ -53,7 +54,6 @@ #include "minecraft/auth/AccountList.h" #include "FileSystem.h" -#include "JavaDownloader.h" #include "java/JavaInstallList.h" #include "java/JavaUtils.h" @@ -387,7 +387,8 @@ void InstanceSettingsPage::loadSettings() void InstanceSettingsPage::on_javaDownloadBtn_clicked() { - JavaDownloader::showPrompts(this); + auto jdialog = new JavaDownload(this); + jdialog->exec(); } void InstanceSettingsPage::on_javaDetectBtn_clicked() diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 909ad7a017..7405ad1147 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -13,13 +13,13 @@ #include "FileSystem.h" #include "JavaCommon.h" -#include "JavaDownloader.h" #include "java/JavaChecker.h" #include "java/JavaInstall.h" #include "java/JavaInstallList.h" #include "java/JavaUtils.h" #include "ui/dialogs/CustomMessageBox.h" +#include "ui/java/JavaDownload.h" #include "ui/widgets/VersionSelectWidget.h" #include "Application.h" @@ -274,7 +274,8 @@ void JavaSettingsWidget::on_javaBrowseBtn_clicked() } void JavaSettingsWidget::on_javaDownloadBtn_clicked() { - JavaDownloader::showPrompts(this); + auto jdialog = new JavaDownload(this); + jdialog->exec(); } void JavaSettingsWidget::on_javaStatusBtn_clicked() { From 5afe75e821516dbbde60d1b2d3a627a0d243b1fe Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 30 Jan 2024 12:37:34 +0200 Subject: [PATCH 0126/2054] Fixed some codeql warnings Signed-off-by: Trial97 --- launcher/JavaCommon.cpp | 4 ++-- launcher/JavaCommon.h | 4 ++-- launcher/java/JavaChecker.h | 2 +- launcher/java/JavaInstallList.cpp | 4 ++-- launcher/java/JavaVersion.cpp | 4 ++-- launcher/java/JavaVersion.h | 4 ++-- launcher/java/download/ArchiveJavaDownloader.cpp | 1 - launcher/launch/steps/CheckJava.cpp | 2 +- launcher/launch/steps/CheckJava.h | 2 +- launcher/ui/widgets/JavaSettingsWidget.cpp | 2 +- launcher/ui/widgets/JavaSettingsWidget.h | 2 +- 11 files changed, 15 insertions(+), 16 deletions(-) diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp index cfc3cfe424..3cbf9f9d54 100644 --- a/launcher/JavaCommon.cpp +++ b/launcher/JavaCommon.cpp @@ -121,7 +121,7 @@ void JavaCommon::TestCheck::run() checker->start(); } -void JavaCommon::TestCheck::checkFinished(JavaChecker::Result result) +void JavaCommon::TestCheck::checkFinished(const JavaChecker::Result& result) { if (result.validity != JavaChecker::Result::Validity::Valid) { javaBinaryWasBad(m_parent, result); @@ -133,7 +133,7 @@ void JavaCommon::TestCheck::checkFinished(JavaChecker::Result result) checker->start(); } -void JavaCommon::TestCheck::checkFinishedWithArgs(JavaChecker::Result result) +void JavaCommon::TestCheck::checkFinishedWithArgs(const JavaChecker::Result& result) { if (result.validity == JavaChecker::Result::Validity::Valid) { javaWasOk(m_parent, result); diff --git a/launcher/JavaCommon.h b/launcher/JavaCommon.h index 7c5510efc3..ca208b5933 100644 --- a/launcher/JavaCommon.h +++ b/launcher/JavaCommon.h @@ -32,8 +32,8 @@ class TestCheck : public QObject { void finished(); private slots: - void checkFinished(JavaChecker::Result result); - void checkFinishedWithArgs(JavaChecker::Result result); + void checkFinished(const JavaChecker::Result& result); + void checkFinishedWithArgs(const JavaChecker::Result& result); private: JavaChecker::Ptr checker; diff --git a/launcher/java/JavaChecker.h b/launcher/java/JavaChecker.h index 0c6191c21b..171a18b763 100644 --- a/launcher/java/JavaChecker.h +++ b/launcher/java/JavaChecker.h @@ -29,7 +29,7 @@ class JavaChecker : public Task { explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0, QObject* parent = 0); signals: - void checkFinished(Result result); + void checkFinished(const Result& result); protected: virtual void executeTask() override; diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index ef99d6853e..c140569646 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -170,7 +170,7 @@ void JavaListLoadTask::executeTask() int id = 0; for (QString candidate : candidate_paths) { auto checker = new JavaChecker(candidate, "", 0, 0, 0, id, this); - connect(checker, &JavaChecker::checkFinished, [this](JavaChecker::Result result) { m_results << result; }); + connect(checker, &JavaChecker::checkFinished, [this](const JavaChecker::Result& result) { m_results << result; }); job->addTask(Task::Ptr(checker)); id++; } @@ -181,7 +181,7 @@ void JavaListLoadTask::executeTask() void JavaListLoadTask::javaCheckerFinished() { QList candidates; - std::sort(m_results.begin(), m_results.end(), [](JavaChecker::Result a, JavaChecker::Result b) { return a.id < b.id; }); + std::sort(m_results.begin(), m_results.end(), [](const JavaChecker::Result& a, const JavaChecker::Result& b) { return a.id < b.id; }); qDebug() << "Found the following valid Java installations:"; for (auto result : m_results) { diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp index f3ded97099..2dc23472e2 100644 --- a/launcher/java/JavaVersion.cpp +++ b/launcher/java/JavaVersion.cpp @@ -43,12 +43,12 @@ QString JavaVersion::toString() const return m_string; } -bool JavaVersion::requiresPermGen() +bool JavaVersion::requiresPermGen() const { return !m_parseable || m_major < 8; } -bool JavaVersion::isModular() +bool JavaVersion::isModular() const { return m_parseable && m_major >= 9; } diff --git a/launcher/java/JavaVersion.h b/launcher/java/JavaVersion.h index eee992346d..92c743bfb8 100644 --- a/launcher/java/JavaVersion.h +++ b/launcher/java/JavaVersion.h @@ -24,9 +24,9 @@ class JavaVersion { bool operator==(const JavaVersion& rhs); bool operator>(const JavaVersion& rhs); - bool requiresPermGen(); + bool requiresPermGen() const; - bool isModular(); + bool isModular() const; QString toString() const; diff --git a/launcher/java/download/ArchiveJavaDownloader.cpp b/launcher/java/download/ArchiveJavaDownloader.cpp index bee65cf913..cb642357a0 100644 --- a/launcher/java/download/ArchiveJavaDownloader.cpp +++ b/launcher/java/download/ArchiveJavaDownloader.cpp @@ -49,7 +49,6 @@ void ArchiveJavaDownloader::executeTask() auto fullPath = entry->getFullPath(); connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); - // connect(download.get(), &NetJob::aborted, [path] { APPLICATION->instances()->destroyStagingPath(path); }); connect(download.get(), &NetJob::progress, this, &ArchiveJavaDownloader::progress); connect(download.get(), &NetJob::failed, this, &ArchiveJavaDownloader::emitFailed); connect(this, &Task::aborted, download.get(), &NetJob::abort); diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp index c776092fe6..a8c7305abc 100644 --- a/launcher/launch/steps/CheckJava.cpp +++ b/launcher/launch/steps/CheckJava.cpp @@ -106,7 +106,7 @@ void CheckJava::executeTask() emitSucceeded(); } -void CheckJava::checkJavaFinished(JavaChecker::Result result) +void CheckJava::checkJavaFinished(const JavaChecker::Result& result) { switch (result.validity) { case JavaChecker::Result::Validity::Errored: { diff --git a/launcher/launch/steps/CheckJava.h b/launcher/launch/steps/CheckJava.h index 5ba9e4018a..62b0c6bbbd 100644 --- a/launcher/launch/steps/CheckJava.h +++ b/launcher/launch/steps/CheckJava.h @@ -28,7 +28,7 @@ class CheckJava : public LaunchStep { virtual void executeTask(); virtual bool canAbort() const { return false; } private slots: - void checkJavaFinished(JavaChecker::Result result); + void checkJavaFinished(const JavaChecker::Result& result); private: void printJavaInfo(const QString& version, const QString& architecture, const QString& realArchitecture, const QString& vendor); diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 7405ad1147..d6fbf00a3c 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -377,7 +377,7 @@ void JavaSettingsWidget::checkJavaPath(const QString& path) m_checker->start(); } -void JavaSettingsWidget::checkFinished(JavaChecker::Result result) +void JavaSettingsWidget::checkFinished(const JavaChecker::Result& result) { m_result = result; switch (result.validity) { diff --git a/launcher/ui/widgets/JavaSettingsWidget.h b/launcher/ui/widgets/JavaSettingsWidget.h index d3cd2c5a70..b7db955a4d 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.h +++ b/launcher/ui/widgets/JavaSettingsWidget.h @@ -51,7 +51,7 @@ class JavaSettingsWidget : public QWidget { void on_javaBrowseBtn_clicked(); void on_javaStatusBtn_clicked(); void on_javaDownloadBtn_clicked(); - void checkFinished(JavaChecker::Result result); + void checkFinished(const JavaChecker::Result& result); protected: /* methods */ void checkJavaPathOnEdit(const QString& path); From 6c5bb3817bfe1a51924127f77965ea72506452aa Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 31 Jan 2024 18:07:28 +0200 Subject: [PATCH 0127/2054] Cleaned some code Signed-off-by: Trial97 --- launcher/java/JavaRuntime.cpp | 44 ++++++++++++++++- launcher/java/JavaRuntime.h | 18 ++++++- launcher/ui/java/JavaDownload.cpp | 35 +++++--------- launcher/ui/java/JavaDownload.h | 3 -- launcher/ui/java/ListModel.cpp | 69 ++++++--------------------- launcher/ui/java/ListModel.h | 78 +------------------------------ 6 files changed, 85 insertions(+), 162 deletions(-) diff --git a/launcher/java/JavaRuntime.cpp b/launcher/java/JavaRuntime.cpp index be13f47e43..e889557723 100644 --- a/launcher/java/JavaRuntime.cpp +++ b/launcher/java/JavaRuntime.cpp @@ -21,6 +21,7 @@ #include #include "Json.h" +#include "StringUtils.h" #include "java/JavaVersion.h" #include "minecraft/ParseUtils.h" @@ -47,7 +48,7 @@ MetaPtr parseJavaMeta(const QJsonObject& in) { auto meta = std::make_shared(); - meta->name = Json::ensureString(in, "name", ""); + meta->m_name = Json::ensureString(in, "name", ""); meta->vendor = Json::ensureString(in, "vendor", ""); meta->url = Json::ensureString(in, "url", ""); meta->releaseTime = timeFromS3Time(Json::ensureString(in, "releaseTime", "")); @@ -72,4 +73,45 @@ MetaPtr parseJavaMeta(const QJsonObject& in) } return meta; } + +bool Meta::operator<(const Meta& rhs) +{ + auto id = version; + if (id < rhs.version) { + return true; + } + if (id > rhs.version) { + return false; + } + return StringUtils::naturalCompare(m_name, rhs.m_name, Qt::CaseInsensitive) < 0; +} + +bool Meta::operator==(const Meta& rhs) +{ + return version == rhs.version && m_name == rhs.m_name; +} + +bool Meta::operator>(const Meta& rhs) +{ + return (!operator<(rhs)) && (!operator==(rhs)); +} + +bool Meta::operator<(BaseVersion& a) +{ + try { + return operator<(dynamic_cast(a)); + } catch (const std::bad_cast& e) { + return BaseVersion::operator<(a); + } +} + +bool Meta::operator>(BaseVersion& a) +{ + try { + return operator>(dynamic_cast(a)); + } catch (const std::bad_cast& e) { + return BaseVersion::operator>(a); + } +} + } // namespace JavaRuntime diff --git a/launcher/java/JavaRuntime.h b/launcher/java/JavaRuntime.h index 4c4efa2886..654ba030ae 100644 --- a/launcher/java/JavaRuntime.h +++ b/launcher/java/JavaRuntime.h @@ -23,14 +23,28 @@ #include +#include "BaseVersion.h" #include "java/JavaVersion.h" namespace JavaRuntime { enum class DownloadType { Manifest, Archive }; -struct Meta { - QString name; +class Meta : public BaseVersion { + public: + virtual QString descriptor() override { return version.toString(); } + + virtual QString name() override { return m_name; } + + virtual QString typeString() const override { return vendor; } + + virtual bool operator<(BaseVersion& a) override; + virtual bool operator>(BaseVersion& a) override; + bool operator<(const Meta& rhs); + bool operator==(const Meta& rhs); + bool operator>(const Meta& rhs); + + QString m_name; QString vendor; QString url; QDateTime releaseTime; diff --git a/launcher/ui/java/JavaDownload.cpp b/launcher/ui/java/JavaDownload.cpp index c03fb10e81..bae61a0605 100644 --- a/launcher/ui/java/JavaDownload.cpp +++ b/launcher/ui/java/JavaDownload.cpp @@ -17,21 +17,23 @@ */ #include "JavaDownload.h" -#include -#include -#include -#include + +#include + #include + #include "Application.h" -#include "BaseVersionList.h" #include "FileSystem.h" #include "QObjectPtr.h" #include "SysInfo.h" -#include "java/JavaInstallList.h" + +#include "java/JavaRuntime.h" #include "java/download/ArchiveJavaDownloader.h" #include "java/download/ManifestJavaDownloader.h" + #include "meta/Index.h" #include "meta/Version.h" + #include "ui/dialogs/ProgressDialog.h" #include "ui/java/ListModel.h" #include "ui_JavaDownload.h" @@ -44,7 +46,6 @@ JavaDownload::JavaDownload(QWidget* parent) : QDialog(parent), ui(new Ui::JavaDo connect(ui->widget, &VersionSelectWidget::selectedVersionChanged, this, &JavaDownload::setSelectedVersion); auto reset = ui->buttonBox->button(QDialogButtonBox::Reset); connect(reset, &QPushButton::clicked, this, &JavaDownload::refresh); - connect(ui->widget_2, &VersionSelectWidget::selectedVersionChanged, this, &JavaDownload::setSelectedVersion2); } JavaDownload::~JavaDownload() @@ -54,8 +55,6 @@ JavaDownload::~JavaDownload() void JavaDownload::setSelectedVersion(BaseVersion::Ptr version) { - if (!version) - return; auto dcast = std::dynamic_pointer_cast(version); if (!dcast) { return; @@ -64,26 +63,14 @@ void JavaDownload::setSelectedVersion(BaseVersion::Ptr version) ui->widget_2->selectCurrent(); } -void JavaDownload::setSelectedVersion2(BaseVersion::Ptr version) -{ - if (!version) - return; - m_selectedVersion = std::dynamic_pointer_cast(ui->widget_2->selectedVersion()); -} void JavaDownload::accept() { - if (!m_selectedVersion) { - m_selectedVersion = std::dynamic_pointer_cast(ui->widget_2->selectedVersion()); - qDebug() << "=========?" << (ui->widget_2->selectedVersion() != nullptr); - } - if (!m_selectedVersion) { - qDebug() << "faillllllllllllllllllllllllllll"; + auto meta = std::dynamic_pointer_cast(ui->widget_2->selectedVersion()); + if (!meta) { return; } - auto meta = m_selectedVersion->meta; Task::Ptr task; - auto final_path = FS::PathCombine(APPLICATION->dataRoot(), "java", meta->name); - qDebug() << "===============>>=>>" << meta->checksumType << meta->checksumHash; + auto final_path = FS::PathCombine(APPLICATION->dataRoot(), "java", meta->m_name); switch (meta->downloadType) { case JavaRuntime::DownloadType::Manifest: task = makeShared(meta->url, final_path, meta->checksumType, meta->checksumHash); diff --git a/launcher/ui/java/JavaDownload.h b/launcher/ui/java/JavaDownload.h index 916d6b4952..de0916ae5e 100644 --- a/launcher/ui/java/JavaDownload.h +++ b/launcher/ui/java/JavaDownload.h @@ -20,7 +20,6 @@ #include #include "BaseVersion.h" -#include "ui/java/ListModel.h" namespace Ui { class JavaDownload; @@ -40,9 +39,7 @@ class JavaDownload : public QDialog { protected slots: void setSelectedVersion(BaseVersion::Ptr version); - void setSelectedVersion2(BaseVersion::Ptr version); private: Ui::JavaDownload* ui; - Java::JavaRuntimePtr m_selectedVersion; }; diff --git a/launcher/ui/java/ListModel.cpp b/launcher/ui/java/ListModel.cpp index 71f8765eb0..11cd01130d 100644 --- a/launcher/ui/java/ListModel.cpp +++ b/launcher/ui/java/ListModel.cpp @@ -17,11 +17,12 @@ */ #include "ListModel.h" -#include + #include + #include "BaseVersionList.h" -#include "StringUtils.h" #include "SysInfo.h" +#include "java/JavaRuntime.h" namespace Java { @@ -71,13 +72,13 @@ QVariant InstallList::data(const QModelIndex& index, int role) const case VersionIdRole: return version->descriptor(); case VersionRole: - return version->meta->version.toString(); + return version->version.toString(); case RecommendedRole: - return version->meta->recommended; + return version->recommended; case AliasRole: - return version->meta->name; + return version->name(); case ArchitectureRole: - return version->meta->vendor; + return version->vendor; default: return QVariant(); } @@ -90,8 +91,8 @@ BaseVersionList::RoleList InstallList::providesRoles() const bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right) { - auto rleft = std::dynamic_pointer_cast(right); - auto rright = std::dynamic_pointer_cast(left); + auto rleft = std::dynamic_pointer_cast(right); + auto rright = std::dynamic_pointer_cast(left); return (*rleft) > (*rright); } @@ -100,55 +101,13 @@ void InstallList::sortVersions() QString versionStr = SysInfo::getSupportedJavaArchitecture(); beginResetModel(); auto runtimes = m_version->data()->runtimes; - if (versionStr.isEmpty() || !runtimes.contains(versionStr)) { - return; - } - auto javaruntimes = runtimes.value(versionStr); - for (auto v : javaruntimes) { - m_vlist.append(std::make_shared(v)); + if (!versionStr.isEmpty() && runtimes.contains(versionStr)) { + m_vlist = runtimes.value(versionStr); + std::sort(m_vlist.begin(), m_vlist.end(), sortJavas); + } else { + m_vlist = {}; } - std::sort(m_vlist.begin(), m_vlist.end(), sortJavas); endResetModel(); } -bool JavaRuntime2::operator<(const JavaRuntime2& rhs) -{ - auto id = meta->version; - if (id < rhs.meta->version) { - return true; - } - if (id > rhs.meta->version) { - return false; - } - return StringUtils::naturalCompare(meta->name, rhs.meta->name, Qt::CaseInsensitive) < 0; -} - -bool JavaRuntime2::operator==(const JavaRuntime2& rhs) -{ - return meta->version == rhs.meta->version && meta->name == rhs.meta->name; -} - -bool JavaRuntime2::operator>(const JavaRuntime2& rhs) -{ - return (!operator<(rhs)) && (!operator==(rhs)); -} - -bool JavaRuntime2::operator<(BaseVersion& a) -{ - try { - return operator<(dynamic_cast(a)); - } catch (const std::bad_cast& e) { - return BaseVersion::operator<(a); - } -} - -bool JavaRuntime2::operator>(BaseVersion& a) -{ - try { - return operator>(dynamic_cast(a)); - } catch (const std::bad_cast& e) { - return BaseVersion::operator>(a); - } -} - } // namespace Java diff --git a/launcher/ui/java/ListModel.h b/launcher/ui/java/ListModel.h index 3685d611d8..a157e0e8d3 100644 --- a/launcher/ui/java/ListModel.h +++ b/launcher/ui/java/ListModel.h @@ -18,9 +18,6 @@ #pragma once -#include -#include -#include #include "java/JavaRuntime.h" #include "meta/VersionList.h" @@ -33,26 +30,6 @@ class JavaBaseVersionList : public Meta::VersionList { BaseVersionList::RoleList providesRoles() const { return { VersionRole, RecommendedRole, VersionPointerRole }; } }; -struct JavaRuntime2 : public BaseVersion { - JavaRuntime2() {} - JavaRuntime2(JavaRuntime::MetaPtr m) : meta(m) {} - virtual QString descriptor() override { return meta->version.toString(); } - - virtual QString name() override { return meta->name; } - - virtual QString typeString() const override { return meta->vendor; } - - virtual bool operator<(BaseVersion& a) override; - virtual bool operator>(BaseVersion& a) override; - bool operator<(const JavaRuntime2& rhs); - bool operator==(const JavaRuntime2& rhs); - bool operator>(const JavaRuntime2& rhs); - - JavaRuntime::MetaPtr meta; -}; - -using JavaRuntimePtr = std::shared_ptr; - class InstallList : public BaseVersionList { Q_OBJECT @@ -73,60 +50,7 @@ class InstallList : public BaseVersionList { protected: Meta::Version::Ptr m_version; - QList m_vlist; + QList m_vlist; }; } // namespace Java -// class FilterModel : public QSortFilterProxyModel { -// Q_OBJECT -// public: -// FilterModel(QObject* parent = Q_NULLPTR); -// enum Sorting { ByName, ByGameVersion }; -// const QMap getAvailableSortings(); -// QString translateCurrentSorting(); -// void setSorting(Sorting sorting); -// Sorting getCurrentSorting(); -// void setSearchTerm(QString term); - -// protected: -// bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; -// bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; - -// private: -// QMap sortings; -// Sorting currentSorting; -// QString searchTerm; -// }; - -// class ListModel : public QAbstractListModel { -// Q_OBJECT -// private: -// ModpackList modpacks; -// QStringList m_failedLogos; -// QStringList m_loadingLogos; -// FTBLogoMap m_logoMap; -// QMap waitingCallbacks; - -// void requestLogo(QString file); -// QString translatePackType(PackType type) const; - -// private slots: -// void logoFailed(QString logo); -// void logoLoaded(QString logo, QIcon out); - -// public: -// ListModel(QObject* parent); -// ~ListModel(); -// int rowCount(const QModelIndex& parent) const override; -// int columnCount(const QModelIndex& parent) const override; -// QVariant data(const QModelIndex& index, int role) const override; -// Qt::ItemFlags flags(const QModelIndex& index) const override; - -// void fill(ModpackList modpacks); -// void addPack(Modpack modpack); -// void clear(); -// void remove(int row); - -// Modpack at(int row); -// void getLogo(const QString& logo, LogoCallback callback); -// }; From 3c58fb06776000808029e8cc4d5207bfd0f57321 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 2 Feb 2024 15:51:32 +0200 Subject: [PATCH 0128/2054] Moving files around Signed-off-by: Trial97 --- launcher/BaseVersionList.cpp | 4 +- launcher/BaseVersionList.h | 4 +- launcher/CMakeLists.txt | 24 ++-- launcher/VersionProxyModel.cpp | 26 ++--- launcher/VersionProxyModel.h | 2 +- launcher/java/JavaInstallList.cpp | 4 +- .../{JavaRuntime.cpp => JavaMetadata.cpp} | 24 ++-- .../java/{JavaRuntime.h => JavaMetadata.h} | 16 +-- ...Downloader.cpp => ArchiveDownloadTask.cpp} | 22 ++-- ...JavaDownloader.h => ArchiveDownloadTask.h} | 12 +- ...ownloader.cpp => ManifestDownloadTask.cpp} | 20 ++-- ...avaDownloader.h => ManifestDownloadTask.h} | 13 ++- launcher/meta/VersionList.cpp | 8 +- launcher/meta/VersionList.h | 5 + launcher/minecraft/OneSixVersionFormat.cpp | 6 +- launcher/minecraft/VersionFile.h | 4 +- launcher/ui/java/JavaDownload.cpp | 90 --------------- launcher/ui/java/JavaDownloader.cpp | 109 ++++++++++++++++++ .../java/{JavaDownload.h => JavaDownloader.h} | 13 ++- .../{JavaDownload.ui => JavaDownloader.ui} | 12 +- .../java/{ListModel.cpp => VersionList.cpp} | 32 ++--- .../ui/java/{ListModel.h => VersionList.h} | 18 +-- launcher/ui/pages/global/JavaPage.cpp | 4 +- .../pages/instance/InstanceSettingsPage.cpp | 4 +- launcher/ui/widgets/JavaSettingsWidget.cpp | 4 +- 25 files changed, 255 insertions(+), 225 deletions(-) rename launcher/java/{JavaRuntime.cpp => JavaMetadata.cpp} (86%) rename launcher/java/{JavaRuntime.h => JavaMetadata.h} (84%) rename launcher/java/download/{ArchiveJavaDownloader.cpp => ArchiveDownloadTask.cpp} (82%) rename launcher/java/download/{ArchiveJavaDownloader.h => ArchiveDownloadTask.h} (82%) rename launcher/java/download/{ManifestJavaDownloader.cpp => ManifestDownloadTask.cpp} (87%) rename launcher/java/download/{ManifestJavaDownloader.h => ManifestDownloadTask.h} (82%) delete mode 100644 launcher/ui/java/JavaDownload.cpp create mode 100644 launcher/ui/java/JavaDownloader.cpp rename launcher/ui/java/{JavaDownload.h => JavaDownloader.h} (84%) rename launcher/ui/java/{JavaDownload.ui => JavaDownloader.ui} (86%) rename launcher/ui/java/{ListModel.cpp => VersionList.cpp} (76%) rename launcher/ui/java/{ListModel.h => VersionList.h} (71%) diff --git a/launcher/BaseVersionList.cpp b/launcher/BaseVersionList.cpp index 576c3a413d..afee8388a1 100644 --- a/launcher/BaseVersionList.cpp +++ b/launcher/BaseVersionList.cpp @@ -110,7 +110,7 @@ QHash BaseVersionList::roleNames() const roles.insert(TypeRole, "type"); roles.insert(BranchRole, "branch"); roles.insert(PathRole, "path"); - roles.insert(AliasRole, "alias"); - roles.insert(ArchitectureRole, "architecture"); + roles.insert(JavaNameRole, "javaName"); + roles.insert(CPUArchitectureRole, "architecture"); return roles; } diff --git a/launcher/BaseVersionList.h b/launcher/BaseVersionList.h index c59d705707..bc37e9e53e 100644 --- a/launcher/BaseVersionList.h +++ b/launcher/BaseVersionList.h @@ -48,8 +48,8 @@ class BaseVersionList : public QAbstractListModel { TypeRole, BranchRole, PathRole, - AliasRole, - ArchitectureRole, + JavaNameRole, + CPUArchitectureRole, SortRole }; using RoleList = QList; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index d703b82637..c7945d1b84 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -431,17 +431,17 @@ set(JAVA_SOURCES java/JavaVersion.h java/JavaVersion.cpp - java/JavaRuntime.h - java/JavaRuntime.cpp - java/download/ArchiveJavaDownloader.cpp - java/download/ArchiveJavaDownloader.h - java/download/ManifestJavaDownloader.cpp - java/download/ManifestJavaDownloader.h - - ui/java/JavaDownload.h - ui/java/JavaDownload.cpp - ui/java/ListModel.h - ui/java/ListModel.cpp + java/JavaMetadata.h + java/JavaMetadata.cpp + java/download/ArchiveDownloadTask.cpp + java/download/ArchiveDownloadTask.h + java/download/ManifestDownloadTask.cpp + java/download/ManifestDownloadTask.h + + ui/java/JavaDownloader.h + ui/java/JavaDownloader.cpp + ui/java/VersionList.h + ui/java/VersionList.cpp ) set(TRANSLATIONS_SOURCES @@ -1192,7 +1192,7 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/ScrollMessageBox.ui ui/dialogs/BlockedModsDialog.ui ui/dialogs/ChooseProviderDialog.ui - ui/java/JavaDownload.ui + ui/java/JavaDownloader.ui ) qt_wrap_ui(PRISM_UPDATE_UI diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index f1218f162d..070e952a4a 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -114,12 +114,12 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation, return tr("Branch"); case Type: return tr("Type"); - case Architecture: + case CPUArchitecture: return tr("Architecture"); case Path: return tr("Path"); - case Alias: - return tr("Alias"); + case JavaName: + return tr("Java Name"); case Time: return tr("Released"); } @@ -133,11 +133,11 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation, return tr("The version's branch"); case Type: return tr("The version's type"); - case Architecture: + case CPUArchitecture: return tr("CPU Architecture"); case Path: return tr("Filesystem path to this version"); - case Alias: + case JavaName: return tr("The alternative name of the java version"); case Time: return tr("Release date of this version"); @@ -169,12 +169,12 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const return sourceModel()->data(parentIndex, BaseVersionList::BranchRole); case Type: return sourceModel()->data(parentIndex, BaseVersionList::TypeRole); - case Architecture: - return sourceModel()->data(parentIndex, BaseVersionList::ArchitectureRole); + case CPUArchitecture: + return sourceModel()->data(parentIndex, BaseVersionList::CPUArchitectureRole); case Path: return sourceModel()->data(parentIndex, BaseVersionList::PathRole); - case Alias: - return sourceModel()->data(parentIndex, BaseVersionList::AliasRole); + case JavaName: + return sourceModel()->data(parentIndex, BaseVersionList::JavaNameRole); case Time: return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate(); default: @@ -314,14 +314,14 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw) m_columns.push_back(ParentVersion); } */ - if (roles.contains(BaseVersionList::ArchitectureRole)) { - m_columns.push_back(Architecture); + if (roles.contains(BaseVersionList::CPUArchitectureRole)) { + m_columns.push_back(CPUArchitecture); } if (roles.contains(BaseVersionList::PathRole)) { m_columns.push_back(Path); } - if (roles.contains(BaseVersionList::AliasRole)) { - m_columns.push_back(Alias); + if (roles.contains(BaseVersionList::JavaNameRole)) { + m_columns.push_back(JavaName); } if (roles.contains(Meta::VersionList::TimeRole)) { m_columns.push_back(Time); diff --git a/launcher/VersionProxyModel.h b/launcher/VersionProxyModel.h index 2dc35a6253..cb55b7f14a 100644 --- a/launcher/VersionProxyModel.h +++ b/launcher/VersionProxyModel.h @@ -9,7 +9,7 @@ class VersionFilterModel; class VersionProxyModel : public QAbstractProxyModel { Q_OBJECT public: - enum Column { Name, ParentVersion, Branch, Type, Architecture, Path, Time, Alias }; + enum Column { Name, ParentVersion, Branch, Type, CPUArchitecture, Path, Time, JavaName }; using FilterMap = QHash>; public: diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index c140569646..d99201f24d 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -108,7 +108,7 @@ QVariant JavaInstallList::data(const QModelIndex& index, int role) const return version->recommended; case PathRole: return version->path; - case ArchitectureRole: + case CPUArchitectureRole: return version->arch; default: return QVariant(); @@ -117,7 +117,7 @@ QVariant JavaInstallList::data(const QModelIndex& index, int role) const BaseVersionList::RoleList JavaInstallList::providesRoles() const { - return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole }; + return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, CPUArchitectureRole }; } void JavaInstallList::updateListData(QList versions) diff --git a/launcher/java/JavaRuntime.cpp b/launcher/java/JavaMetadata.cpp similarity index 86% rename from launcher/java/JavaRuntime.cpp rename to launcher/java/JavaMetadata.cpp index e889557723..b261122dc0 100644 --- a/launcher/java/JavaRuntime.cpp +++ b/launcher/java/JavaMetadata.cpp @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -#include "java/JavaRuntime.h" +#include "java/JavaMetadata.h" #include @@ -25,7 +25,7 @@ #include "java/JavaVersion.h" #include "minecraft/ParseUtils.h" -namespace JavaRuntime { +namespace Java { DownloadType parseDownloadType(QString javaDownload) { @@ -44,9 +44,9 @@ QString downloadTypeToString(DownloadType javaDownload) } return ""; } -MetaPtr parseJavaMeta(const QJsonObject& in) +MetadataPtr parseJavaMeta(const QJsonObject& in) { - auto meta = std::make_shared(); + auto meta = std::make_shared(); meta->m_name = Json::ensureString(in, "name", ""); meta->vendor = Json::ensureString(in, "vendor", ""); @@ -74,7 +74,7 @@ MetaPtr parseJavaMeta(const QJsonObject& in) return meta; } -bool Meta::operator<(const Meta& rhs) +bool Metadata::operator<(const Metadata& rhs) { auto id = version; if (id < rhs.version) { @@ -86,32 +86,32 @@ bool Meta::operator<(const Meta& rhs) return StringUtils::naturalCompare(m_name, rhs.m_name, Qt::CaseInsensitive) < 0; } -bool Meta::operator==(const Meta& rhs) +bool Metadata::operator==(const Metadata& rhs) { return version == rhs.version && m_name == rhs.m_name; } -bool Meta::operator>(const Meta& rhs) +bool Metadata::operator>(const Metadata& rhs) { return (!operator<(rhs)) && (!operator==(rhs)); } -bool Meta::operator<(BaseVersion& a) +bool Metadata::operator<(BaseVersion& a) { try { - return operator<(dynamic_cast(a)); + return operator<(dynamic_cast(a)); } catch (const std::bad_cast& e) { return BaseVersion::operator<(a); } } -bool Meta::operator>(BaseVersion& a) +bool Metadata::operator>(BaseVersion& a) { try { - return operator>(dynamic_cast(a)); + return operator>(dynamic_cast(a)); } catch (const std::bad_cast& e) { return BaseVersion::operator>(a); } } -} // namespace JavaRuntime +} // namespace Java diff --git a/launcher/java/JavaRuntime.h b/launcher/java/JavaMetadata.h similarity index 84% rename from launcher/java/JavaRuntime.h rename to launcher/java/JavaMetadata.h index 654ba030ae..dd3ae865c4 100644 --- a/launcher/java/JavaRuntime.h +++ b/launcher/java/JavaMetadata.h @@ -26,11 +26,11 @@ #include "BaseVersion.h" #include "java/JavaVersion.h" -namespace JavaRuntime { +namespace Java { enum class DownloadType { Manifest, Archive }; -class Meta : public BaseVersion { +class Metadata : public BaseVersion { public: virtual QString descriptor() override { return version.toString(); } @@ -40,9 +40,9 @@ class Meta : public BaseVersion { virtual bool operator<(BaseVersion& a) override; virtual bool operator>(BaseVersion& a) override; - bool operator<(const Meta& rhs); - bool operator==(const Meta& rhs); - bool operator>(const Meta& rhs); + bool operator<(const Metadata& rhs); + bool operator==(const Metadata& rhs); + bool operator>(const Metadata& rhs); QString m_name; QString vendor; @@ -55,10 +55,10 @@ class Meta : public BaseVersion { QString packageType; JavaVersion version; }; -using MetaPtr = std::shared_ptr; +using MetadataPtr = std::shared_ptr; DownloadType parseDownloadType(QString javaDownload); QString downloadTypeToString(DownloadType javaDownload); -MetaPtr parseJavaMeta(const QJsonObject& libObj); +MetadataPtr parseJavaMeta(const QJsonObject& libObj); -} // namespace JavaRuntime \ No newline at end of file +} // namespace Java \ No newline at end of file diff --git a/launcher/java/download/ArchiveJavaDownloader.cpp b/launcher/java/download/ArchiveDownloadTask.cpp similarity index 82% rename from launcher/java/download/ArchiveJavaDownloader.cpp rename to launcher/java/download/ArchiveDownloadTask.cpp index cb642357a0..847f3f3649 100644 --- a/launcher/java/download/ArchiveJavaDownloader.cpp +++ b/launcher/java/download/ArchiveDownloadTask.cpp @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "java/download/ArchiveJavaDownloader.h" +#include "java/download/ArchiveDownloadTask.h" #include #include #include "MMCZip.h" @@ -25,11 +25,12 @@ #include "net/NetJob.h" #include "tasks/Task.h" -ArchiveJavaDownloader::ArchiveJavaDownloader(QUrl url, QString final_path, QString checksumType, QString checksumHash) +namespace Java { +ArchiveDownloadTask::ArchiveDownloadTask(QUrl url, QString final_path, QString checksumType, QString checksumHash) : m_url(url), m_final_path(final_path), m_checksum_type(checksumType), m_checksum_hash(checksumHash) {} -void ArchiveJavaDownloader::executeTask() +void ArchiveDownloadTask::executeTask() { // JRE found ! download the zip setStatus(tr("Downloading Java")); @@ -49,8 +50,8 @@ void ArchiveJavaDownloader::executeTask() auto fullPath = entry->getFullPath(); connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); - connect(download.get(), &NetJob::progress, this, &ArchiveJavaDownloader::progress); - connect(download.get(), &NetJob::failed, this, &ArchiveJavaDownloader::emitFailed); + connect(download.get(), &NetJob::progress, this, &ArchiveDownloadTask::progress); + connect(download.get(), &NetJob::failed, this, &ArchiveDownloadTask::emitFailed); connect(this, &Task::aborted, download.get(), &NetJob::abort); connect(download.get(), &NetJob::succeeded, [this, fullPath] { // This should do all of the extracting and creating folders @@ -59,7 +60,7 @@ void ArchiveJavaDownloader::executeTask() download->start(); } -void ArchiveJavaDownloader::extractJava(QString input) +void ArchiveDownloadTask::extractJava(QString input) { setStatus(tr("Extracting java")); auto zip = std::make_shared(input); @@ -79,14 +80,14 @@ void ArchiveJavaDownloader::extractJava(QString input) connect(this, &Task::aborted, zipTask.get(), &Task::abort); connect(zipTask.get(), &Task::finished, [zipTask, this] { disconnect(this, &Task::aborted, zipTask.get(), &Task::abort); }); - connect(zipTask.get(), &Task::succeeded, this, &ArchiveJavaDownloader::emitSucceeded); - connect(zipTask.get(), &Task::aborted, this, &ArchiveJavaDownloader::emitAborted); + connect(zipTask.get(), &Task::succeeded, this, &ArchiveDownloadTask::emitSucceeded); + connect(zipTask.get(), &Task::aborted, this, &ArchiveDownloadTask::emitAborted); connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) { progressStep->state = TaskStepState::Failed; stepProgress(*progressStep); emitFailed(reason); }); - connect(zipTask.get(), &Task::stepProgress, this, &ArchiveJavaDownloader::propagateStepProgress); + connect(zipTask.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress); connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { progressStep->update(current, total); @@ -97,4 +98,5 @@ void ArchiveJavaDownloader::extractJava(QString input) stepProgress(*progressStep); }); zipTask->start(); -} \ No newline at end of file +} +} // namespace Java \ No newline at end of file diff --git a/launcher/java/download/ArchiveJavaDownloader.h b/launcher/java/download/ArchiveDownloadTask.h similarity index 82% rename from launcher/java/download/ArchiveJavaDownloader.h rename to launcher/java/download/ArchiveDownloadTask.h index 230f62b4d2..88c0223d5a 100644 --- a/launcher/java/download/ArchiveJavaDownloader.h +++ b/launcher/java/download/ArchiveDownloadTask.h @@ -21,11 +21,12 @@ #include #include "tasks/Task.h" -class ArchiveJavaDownloader : public Task { +namespace Java { +class ArchiveDownloadTask : public Task { Q_OBJECT public: - ArchiveJavaDownloader(QUrl url, QString final_path, QString checksumType = "", QString checksumHash = ""); - virtual ~ArchiveJavaDownloader() = default; + ArchiveDownloadTask(QUrl url, QString final_path, QString checksumType = "", QString checksumHash = ""); + virtual ~ArchiveDownloadTask() = default; [[nodiscard]] bool canAbort() const override { return true; } void executeTask() override; @@ -38,6 +39,5 @@ class ArchiveJavaDownloader : public Task { QString m_final_path; QString m_checksum_type; QString m_checksum_hash; - - Task::Ptr m_current_task; -}; \ No newline at end of file +}; +} // namespace Java \ No newline at end of file diff --git a/launcher/java/download/ManifestJavaDownloader.cpp b/launcher/java/download/ManifestDownloadTask.cpp similarity index 87% rename from launcher/java/download/ManifestJavaDownloader.cpp rename to launcher/java/download/ManifestDownloadTask.cpp index c0b7942a7c..1a30715301 100644 --- a/launcher/java/download/ManifestJavaDownloader.cpp +++ b/launcher/java/download/ManifestDownloadTask.cpp @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "java/download/ManifestJavaDownloader.h" +#include "java/download/ManifestDownloadTask.h" #include "Application.h" #include "FileSystem.h" @@ -30,11 +30,12 @@ struct File { bool isExec; }; -ManifestJavaDownloader::ManifestJavaDownloader(QUrl url, QString final_path, QString checksumType, QString checksumHash) +namespace Java { +ManifestDownloadTask::ManifestDownloadTask(QUrl url, QString final_path, QString checksumType, QString checksumHash) : m_url(url), m_final_path(final_path), m_checksum_type(checksumType), m_checksum_hash(checksumHash) {} -void ManifestJavaDownloader::executeTask() +void ManifestDownloadTask::executeTask() { setStatus(tr("Downloading Java")); auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); @@ -51,8 +52,8 @@ void ManifestJavaDownloader::executeTask() download->addNetAction(action); connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); - connect(download.get(), &NetJob::progress, this, &ManifestJavaDownloader::progress); - connect(download.get(), &NetJob::failed, this, &ManifestJavaDownloader::emitFailed); + connect(download.get(), &NetJob::progress, this, &ManifestDownloadTask::progress); + connect(download.get(), &NetJob::failed, this, &ManifestDownloadTask::emitFailed); connect(this, &Task::aborted, download.get(), &NetJob::abort); connect(download.get(), &NetJob::succeeded, [files, this] { @@ -69,7 +70,7 @@ void ManifestJavaDownloader::executeTask() download->start(); } -void ManifestJavaDownloader::downloadJava(const QJsonDocument& doc) +void ManifestDownloadTask::downloadJava(const QJsonDocument& doc) { // valid json doc, begin making jre spot FS::ensureFolderPathExists(m_final_path); @@ -116,10 +117,11 @@ void ManifestJavaDownloader::downloadJava(const QJsonDocument& doc) disconnect(this, &Task::aborted, elementDownload, &NetJob::abort); elementDownload->deleteLater(); }); - connect(elementDownload, &NetJob::progress, this, &ManifestJavaDownloader::progress); - connect(elementDownload, &NetJob::failed, this, &ManifestJavaDownloader::emitFailed); + connect(elementDownload, &NetJob::progress, this, &ManifestDownloadTask::progress); + connect(elementDownload, &NetJob::failed, this, &ManifestDownloadTask::emitFailed); connect(this, &Task::aborted, elementDownload, &NetJob::abort); connect(elementDownload, &NetJob::succeeded, [this] { emitSucceeded(); }); elementDownload->start(); -} \ No newline at end of file +} +} // namespace Java \ No newline at end of file diff --git a/launcher/java/download/ManifestJavaDownloader.h b/launcher/java/download/ManifestDownloadTask.h similarity index 82% rename from launcher/java/download/ManifestJavaDownloader.h rename to launcher/java/download/ManifestDownloadTask.h index d7114f4bd5..dcfee4d4da 100644 --- a/launcher/java/download/ManifestJavaDownloader.h +++ b/launcher/java/download/ManifestDownloadTask.h @@ -21,11 +21,13 @@ #include #include "tasks/Task.h" -class ManifestJavaDownloader : public Task { +namespace Java { + +class ManifestDownloadTask : public Task { Q_OBJECT public: - ManifestJavaDownloader(QUrl url, QString final_path, QString checksumType = "", QString checksumHash = ""); - virtual ~ManifestJavaDownloader() = default; + ManifestDownloadTask(QUrl url, QString final_path, QString checksumType = "", QString checksumHash = ""); + virtual ~ManifestDownloadTask() = default; [[nodiscard]] bool canAbort() const override { return true; } void executeTask() override; @@ -38,6 +40,5 @@ class ManifestJavaDownloader : public Task { QString m_final_path; QString m_checksum_type; QString m_checksum_hash; - - Task::Ptr m_current_task; -}; \ No newline at end of file +}; +} // namespace Java \ No newline at end of file diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp index 7b7ae1fa32..76b914b6a3 100644 --- a/launcher/meta/VersionList.cpp +++ b/launcher/meta/VersionList.cpp @@ -101,10 +101,14 @@ QVariant VersionList::data(const QModelIndex& index, int role) const BaseVersionList::RoleList VersionList::providesRoles() const { - return { VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole, TypeRole, UidRole, - TimeRole, RequiresRole, SortRole, RecommendedRole, LatestRole, VersionPtrRole }; + return m_provided_roles; } +void VersionList::setProvidedRoles(RoleList roles) +{ + m_provided_roles = roles; +}; + QHash VersionList::roleNames() const { QHash roles = BaseVersionList::roleNames(); diff --git a/launcher/meta/VersionList.h b/launcher/meta/VersionList.h index 2c5624701b..0890caf60a 100644 --- a/launcher/meta/VersionList.h +++ b/launcher/meta/VersionList.h @@ -47,6 +47,8 @@ class VersionList : public BaseVersionList, public BaseEntity { RoleList providesRoles() const override; QHash roleNames() const override; + void setProvidedRoles(RoleList roles); + QString localFilename() const override; QString uid() const { return m_uid; } @@ -79,6 +81,9 @@ class VersionList : public BaseVersionList, public BaseEntity { Version::Ptr m_recommended; + RoleList m_provided_roles = { VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole, TypeRole, UidRole, + TimeRole, RequiresRole, SortRole, RecommendedRole, LatestRole, VersionPtrRole }; + void setupAddedVersion(int row, const Version::Ptr& version); }; } // namespace Meta diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp index 5f3b4f2a2f..56e9c8ca25 100644 --- a/launcher/minecraft/OneSixVersionFormat.cpp +++ b/launcher/minecraft/OneSixVersionFormat.cpp @@ -37,7 +37,7 @@ #include #include #include -#include "java/JavaRuntime.h" +#include "java/JavaMetadata.h" #include "minecraft/Agent.h" #include "minecraft/ParseUtils.h" @@ -261,9 +261,9 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc auto runtimes = requireObject(root, "runtimes"); out->runtimes = {}; for (auto key : runtimes.keys()) { - QList list; + QList list; for (auto runtime : ensureArray(runtimes, key)) { - list.append(JavaRuntime::parseJavaMeta(ensureObject(runtime))); + list.append(Java::parseJavaMeta(ensureObject(runtime))); } out->runtimes[key] = list; } diff --git a/launcher/minecraft/VersionFile.h b/launcher/minecraft/VersionFile.h index 297c19709b..a9c1473d20 100644 --- a/launcher/minecraft/VersionFile.h +++ b/launcher/minecraft/VersionFile.h @@ -47,7 +47,7 @@ #include "Agent.h" #include "Library.h" #include "ProblemProvider.h" -#include "java/JavaRuntime.h" +#include "java/JavaMetadata.h" #include "minecraft/Rule.h" class PackProfile; @@ -155,7 +155,7 @@ class VersionFile : public ProblemContainer { /// is volatile -- may be removed as soon as it is no longer needed by something else bool m_volatile = false; - QHash> runtimes; + QHash> runtimes; public: // Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more. diff --git a/launcher/ui/java/JavaDownload.cpp b/launcher/ui/java/JavaDownload.cpp deleted file mode 100644 index bae61a0605..0000000000 --- a/launcher/ui/java/JavaDownload.cpp +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2024 Trial97 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "JavaDownload.h" - -#include - -#include - -#include "Application.h" -#include "FileSystem.h" -#include "QObjectPtr.h" -#include "SysInfo.h" - -#include "java/JavaRuntime.h" -#include "java/download/ArchiveJavaDownloader.h" -#include "java/download/ManifestJavaDownloader.h" - -#include "meta/Index.h" -#include "meta/Version.h" - -#include "ui/dialogs/ProgressDialog.h" -#include "ui/java/ListModel.h" -#include "ui_JavaDownload.h" - -JavaDownload::JavaDownload(QWidget* parent) : QDialog(parent), ui(new Ui::JavaDownload) -{ - ui->setupUi(this); - ui->widget->initialize(new Java::JavaBaseVersionList("net.minecraft.java")); - ui->widget->selectCurrent(); - connect(ui->widget, &VersionSelectWidget::selectedVersionChanged, this, &JavaDownload::setSelectedVersion); - auto reset = ui->buttonBox->button(QDialogButtonBox::Reset); - connect(reset, &QPushButton::clicked, this, &JavaDownload::refresh); -} - -JavaDownload::~JavaDownload() -{ - delete ui; -} - -void JavaDownload::setSelectedVersion(BaseVersion::Ptr version) -{ - auto dcast = std::dynamic_pointer_cast(version); - if (!dcast) { - return; - } - ui->widget_2->initialize(new Java::InstallList(dcast, this)); - ui->widget_2->selectCurrent(); -} - -void JavaDownload::accept() -{ - auto meta = std::dynamic_pointer_cast(ui->widget_2->selectedVersion()); - if (!meta) { - return; - } - Task::Ptr task; - auto final_path = FS::PathCombine(APPLICATION->dataRoot(), "java", meta->m_name); - switch (meta->downloadType) { - case JavaRuntime::DownloadType::Manifest: - task = makeShared(meta->url, final_path, meta->checksumType, meta->checksumHash); - break; - case JavaRuntime::DownloadType::Archive: - task = makeShared(meta->url, final_path, meta->checksumType, meta->checksumHash); - break; - } - ProgressDialog pg(this); - pg.execWithTask(task.get()); - QDialog::accept(); -} - -void JavaDownload::refresh() -{ - ui->widget->loadList(); -} diff --git a/launcher/ui/java/JavaDownloader.cpp b/launcher/ui/java/JavaDownloader.cpp new file mode 100644 index 0000000000..9121988da9 --- /dev/null +++ b/launcher/ui/java/JavaDownloader.cpp @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2024 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "JavaDownloader.h" + +#include + +#include + +#include "Application.h" +#include "BaseVersionList.h" +#include "FileSystem.h" +#include "QObjectPtr.h" +#include "SysInfo.h" + +#include "java/JavaMetadata.h" +#include "java/download/ArchiveDownloadTask.h" +#include "java/download/ManifestDownloadTask.h" + +#include "meta/Index.h" +#include "meta/Version.h" + +#include "meta/VersionList.h" +#include "ui/dialogs/ProgressDialog.h" +#include "ui/java/VersionList.h" +#include "ui_JavaDownloader.h" + +namespace Java { + +Downloader::Downloader(QWidget* parent) : QDialog(parent), ui(new Ui::JavaDownloader) +{ + ui->setupUi(this); + auto versionList = new Meta::VersionList("net.minecraft.java", this); + versionList->setProvidedRoles({ BaseVersionList::VersionRole, BaseVersionList::RecommendedRole, BaseVersionList::VersionPointerRole }); + ui->majorVersionSelect->initialize(versionList); + ui->majorVersionSelect->selectCurrent(); + ui->majorVersionSelect->setEmptyString(tr("No java versions are currently available in the meta")); + ui->majorVersionSelect->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); + + ui->javaVersionSelect->setEmptyString(tr("No java versions are currently available for your OS.")); + ui->javaVersionSelect->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); + + ui->buttonBox->button(QDialogButtonBox::Retry)->setText(tr("Refresh")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Download")); + + connect(ui->majorVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &Downloader::setSelectedVersion); + auto reset = ui->buttonBox->button(QDialogButtonBox::Reset); + connect(reset, &QPushButton::clicked, this, &Downloader::refresh); +} + +Downloader::~Downloader() +{ + delete ui; +} + +void Downloader::setSelectedVersion(BaseVersion::Ptr version) +{ + auto dcast = std::dynamic_pointer_cast(version); + if (!dcast) { + return; + } + ui->javaVersionSelect->initialize(new Java::VersionList(dcast, this)); + ui->javaVersionSelect->selectCurrent(); +} + +void Downloader::accept() +{ + auto meta = std::dynamic_pointer_cast(ui->javaVersionSelect->selectedVersion()); + if (!meta) { + return; + } + Task::Ptr task; + auto final_path = FS::PathCombine(APPLICATION->dataRoot(), "java", meta->m_name); + switch (meta->downloadType) { + case Java::DownloadType::Manifest: + task = makeShared(meta->url, final_path, meta->checksumType, meta->checksumHash); + break; + case Java::DownloadType::Archive: + task = makeShared(meta->url, final_path, meta->checksumType, meta->checksumHash); + break; + } + auto deletePath = [final_path] { FS::deletePath(final_path); }; + connect(task.get(), &Task::failed, this, deletePath); + connect(task.get(), &Task::aborted, this, deletePath); + ProgressDialog pg(this); + pg.execWithTask(task.get()); + QDialog::accept(); +} + +void Downloader::refresh() +{ + ui->majorVersionSelect->loadList(); +} +} // namespace Java diff --git a/launcher/ui/java/JavaDownload.h b/launcher/ui/java/JavaDownloader.h similarity index 84% rename from launcher/ui/java/JavaDownload.h rename to launcher/ui/java/JavaDownloader.h index de0916ae5e..b8bdde41a4 100644 --- a/launcher/ui/java/JavaDownload.h +++ b/launcher/ui/java/JavaDownloader.h @@ -22,15 +22,17 @@ #include "BaseVersion.h" namespace Ui { -class JavaDownload; +class JavaDownloader; } -class JavaDownload : public QDialog { +namespace Java { + +class Downloader : public QDialog { Q_OBJECT public: - explicit JavaDownload(QWidget* parent = 0); - ~JavaDownload(); + explicit Downloader(QWidget* parent = 0); + ~Downloader(); void accept(); @@ -41,5 +43,6 @@ class JavaDownload : public QDialog { void setSelectedVersion(BaseVersion::Ptr version); private: - Ui::JavaDownload* ui; + Ui::JavaDownloader* ui; }; +} // namespace Java diff --git a/launcher/ui/java/JavaDownload.ui b/launcher/ui/java/JavaDownloader.ui similarity index 86% rename from launcher/ui/java/JavaDownload.ui rename to launcher/ui/java/JavaDownloader.ui index bbc638e9bb..0eeabb26db 100644 --- a/launcher/ui/java/JavaDownload.ui +++ b/launcher/ui/java/JavaDownloader.ui @@ -1,7 +1,7 @@ - JavaDownload - + JavaDownloader + 0 @@ -23,7 +23,7 @@ - + @@ -35,7 +35,7 @@ - + @@ -67,7 +67,7 @@ buttonBox accepted() - JavaDownload + JavaDownloader accept() @@ -83,7 +83,7 @@ buttonBox rejected() - JavaDownload + JavaDownloader reject() diff --git a/launcher/ui/java/ListModel.cpp b/launcher/ui/java/VersionList.cpp similarity index 76% rename from launcher/ui/java/ListModel.cpp rename to launcher/ui/java/VersionList.cpp index 11cd01130d..31353f2f70 100644 --- a/launcher/ui/java/ListModel.cpp +++ b/launcher/ui/java/VersionList.cpp @@ -16,46 +16,46 @@ * along with this program. If not, see . */ -#include "ListModel.h" +#include "VersionList.h" #include #include "BaseVersionList.h" #include "SysInfo.h" -#include "java/JavaRuntime.h" +#include "java/JavaMetadata.h" namespace Java { -InstallList::InstallList(Meta::Version::Ptr version, QObject* parent) : BaseVersionList(parent), m_version(version) +VersionList::VersionList(Meta::Version::Ptr version, QObject* parent) : BaseVersionList(parent), m_version(version) { if (version->isLoaded()) sortVersions(); } -Task::Ptr InstallList::getLoadTask() +Task::Ptr VersionList::getLoadTask() { m_version->load(Net::Mode::Online); auto task = m_version->getCurrentTask(); - connect(task.get(), &Task::finished, this, &InstallList::sortVersions); + connect(task.get(), &Task::finished, this, &VersionList::sortVersions); return task; } -const BaseVersion::Ptr InstallList::at(int i) const +const BaseVersion::Ptr VersionList::at(int i) const { return m_vlist.at(i); } -bool InstallList::isLoaded() +bool VersionList::isLoaded() { return m_version->isLoaded(); } -int InstallList::count() const +int VersionList::count() const { return m_vlist.count(); } -QVariant InstallList::data(const QModelIndex& index, int role) const +QVariant VersionList::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); @@ -75,28 +75,28 @@ QVariant InstallList::data(const QModelIndex& index, int role) const return version->version.toString(); case RecommendedRole: return version->recommended; - case AliasRole: + case JavaNameRole: return version->name(); - case ArchitectureRole: + case CPUArchitectureRole: return version->vendor; default: return QVariant(); } } -BaseVersionList::RoleList InstallList::providesRoles() const +BaseVersionList::RoleList VersionList::providesRoles() const { - return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, AliasRole, ArchitectureRole }; + return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, JavaNameRole, CPUArchitectureRole }; } bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right) { - auto rleft = std::dynamic_pointer_cast(right); - auto rright = std::dynamic_pointer_cast(left); + auto rleft = std::dynamic_pointer_cast(right); + auto rright = std::dynamic_pointer_cast(left); return (*rleft) > (*rright); } -void InstallList::sortVersions() +void VersionList::sortVersions() { QString versionStr = SysInfo::getSupportedJavaArchitecture(); beginResetModel(); diff --git a/launcher/ui/java/ListModel.h b/launcher/ui/java/VersionList.h similarity index 71% rename from launcher/ui/java/ListModel.h rename to launcher/ui/java/VersionList.h index a157e0e8d3..d334ed5648 100644 --- a/launcher/ui/java/ListModel.h +++ b/launcher/ui/java/VersionList.h @@ -18,23 +18,17 @@ #pragma once -#include "java/JavaRuntime.h" -#include "meta/VersionList.h" +#include "BaseVersionList.h" +#include "java/JavaMetadata.h" +#include "meta/Version.h" namespace Java { -class JavaBaseVersionList : public Meta::VersionList { - Q_OBJECT - public: - explicit JavaBaseVersionList(const QString& uid, QObject* parent = nullptr) : VersionList(uid, parent) {} - BaseVersionList::RoleList providesRoles() const { return { VersionRole, RecommendedRole, VersionPointerRole }; } -}; - -class InstallList : public BaseVersionList { +class VersionList : public BaseVersionList { Q_OBJECT public: - explicit InstallList(Meta::Version::Ptr m_version, QObject* parent = 0); + explicit VersionList(Meta::Version::Ptr m_version, QObject* parent = 0); Task::Ptr getLoadTask() override; bool isLoaded() override; @@ -50,7 +44,7 @@ class InstallList : public BaseVersionList { protected: Meta::Version::Ptr m_version; - QList m_vlist; + QList m_vlist; }; } // namespace Java diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 09c951a3cc..9b21aad724 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -36,7 +36,7 @@ #include "JavaPage.h" #include "JavaCommon.h" -#include "ui/java/JavaDownload.h" +#include "ui/java/JavaDownloader.h" #include "ui_JavaPage.h" #include @@ -169,7 +169,7 @@ void JavaPage::on_javaTestBtn_clicked() void JavaPage::on_javaDownloadBtn_clicked() { - auto jdialog = new JavaDownload(this); + auto jdialog = new Java::Downloader(this); jdialog->exec(); } diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index df380bfb3e..b962406d1a 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -36,7 +36,7 @@ */ #include "InstanceSettingsPage.h" -#include "ui/java/JavaDownload.h" +#include "ui/java/JavaDownloader.h" #include "ui_InstanceSettingsPage.h" #include @@ -387,7 +387,7 @@ void InstanceSettingsPage::loadSettings() void InstanceSettingsPage::on_javaDownloadBtn_clicked() { - auto jdialog = new JavaDownload(this); + auto jdialog = new Java::Downloader(this); jdialog->exec(); } diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index d6fbf00a3c..3f4fe08ea4 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -19,7 +19,7 @@ #include "java/JavaUtils.h" #include "ui/dialogs/CustomMessageBox.h" -#include "ui/java/JavaDownload.h" +#include "ui/java/JavaDownloader.h" #include "ui/widgets/VersionSelectWidget.h" #include "Application.h" @@ -274,7 +274,7 @@ void JavaSettingsWidget::on_javaBrowseBtn_clicked() } void JavaSettingsWidget::on_javaDownloadBtn_clicked() { - auto jdialog = new JavaDownload(this); + auto jdialog = new Java::Downloader(this); jdialog->exec(); } void JavaSettingsWidget::on_javaStatusBtn_clicked() From 0384e652fb42ea18526a168a2370f8431cdcbc0f Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 2 Feb 2024 22:42:42 +0200 Subject: [PATCH 0129/2054] Finished up the download dialog Signed-off-by: Trial97 --- launcher/BaseVersionList.cpp | 1 + launcher/BaseVersionList.h | 1 + launcher/VersionProxyModel.cpp | 9 +++++++++ launcher/VersionProxyModel.h | 2 +- launcher/ui/java/VersionList.cpp | 11 +++++++++-- 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/launcher/BaseVersionList.cpp b/launcher/BaseVersionList.cpp index afee8388a1..1456a17bd9 100644 --- a/launcher/BaseVersionList.cpp +++ b/launcher/BaseVersionList.cpp @@ -112,5 +112,6 @@ QHash BaseVersionList::roleNames() const roles.insert(PathRole, "path"); roles.insert(JavaNameRole, "javaName"); roles.insert(CPUArchitectureRole, "architecture"); + roles.insert(JavaVendorRole, "javaVendor"); return roles; } diff --git a/launcher/BaseVersionList.h b/launcher/BaseVersionList.h index bc37e9e53e..7517c71b47 100644 --- a/launcher/BaseVersionList.h +++ b/launcher/BaseVersionList.h @@ -50,6 +50,7 @@ class BaseVersionList : public QAbstractListModel { PathRole, JavaNameRole, CPUArchitectureRole, + JavaVendorRole, SortRole }; using RoleList = QList; diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index 070e952a4a..ed4c0767d6 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -116,6 +116,8 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation, return tr("Type"); case CPUArchitecture: return tr("Architecture"); + case JavaVendor: + return tr("Vendor"); case Path: return tr("Path"); case JavaName: @@ -135,6 +137,8 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation, return tr("The version's type"); case CPUArchitecture: return tr("CPU Architecture"); + case JavaVendor: + return tr("Java vendor"); case Path: return tr("Filesystem path to this version"); case JavaName: @@ -171,6 +175,8 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const return sourceModel()->data(parentIndex, BaseVersionList::TypeRole); case CPUArchitecture: return sourceModel()->data(parentIndex, BaseVersionList::CPUArchitectureRole); + case JavaVendor: + return sourceModel()->data(parentIndex, BaseVersionList::JavaVendorRole); case Path: return sourceModel()->data(parentIndex, BaseVersionList::PathRole); case JavaName: @@ -317,6 +323,9 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw) if (roles.contains(BaseVersionList::CPUArchitectureRole)) { m_columns.push_back(CPUArchitecture); } + if (roles.contains(BaseVersionList::JavaVendorRole)) { + m_columns.push_back(JavaVendor); + } if (roles.contains(BaseVersionList::PathRole)) { m_columns.push_back(Path); } diff --git a/launcher/VersionProxyModel.h b/launcher/VersionProxyModel.h index cb55b7f14a..5a1017bee3 100644 --- a/launcher/VersionProxyModel.h +++ b/launcher/VersionProxyModel.h @@ -9,7 +9,7 @@ class VersionFilterModel; class VersionProxyModel : public QAbstractProxyModel { Q_OBJECT public: - enum Column { Name, ParentVersion, Branch, Type, CPUArchitecture, Path, Time, JavaName }; + enum Column { Name, ParentVersion, Branch, Type, CPUArchitecture, Path, Time, JavaName, JavaVendor }; using FilterMap = QHash>; public: diff --git a/launcher/ui/java/VersionList.cpp b/launcher/ui/java/VersionList.cpp index 31353f2f70..7c5668d206 100644 --- a/launcher/ui/java/VersionList.cpp +++ b/launcher/ui/java/VersionList.cpp @@ -23,6 +23,7 @@ #include "BaseVersionList.h" #include "SysInfo.h" #include "java/JavaMetadata.h" +#include "meta/VersionList.h" namespace Java { @@ -77,8 +78,12 @@ QVariant VersionList::data(const QModelIndex& index, int role) const return version->recommended; case JavaNameRole: return version->name(); - case CPUArchitectureRole: + case JavaVendorRole: return version->vendor; + case TypeRole: + return version->packageType; + case Meta::VersionList::TimeRole: + return version->releaseTime; default: return QVariant(); } @@ -86,7 +91,8 @@ QVariant VersionList::data(const QModelIndex& index, int role) const BaseVersionList::RoleList VersionList::providesRoles() const { - return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, JavaNameRole, CPUArchitectureRole }; + return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, + JavaNameRole, JavaVendorRole, TypeRole, Meta::VersionList::TimeRole }; } bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right) @@ -106,6 +112,7 @@ void VersionList::sortVersions() std::sort(m_vlist.begin(), m_vlist.end(), sortJavas); } else { m_vlist = {}; + qWarning() << "Your operating system is not yet supported: " << SysInfo::currentSystem() << " " << SysInfo::useQTForArch(); } endResetModel(); } From 82b15268bc8566be4b320c24b41113f8f25c2204 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 3 Feb 2024 00:11:05 +0200 Subject: [PATCH 0130/2054] preparing java autodownload Signed-off-by: Trial97 --- launcher/java/JavaVersion.h | 10 +++++----- launcher/minecraft/LaunchProfile.cpp | 9 +++++++++ launcher/minecraft/LaunchProfile.h | 4 ++++ launcher/minecraft/VersionFile.cpp | 1 + launcher/ui/java/JavaDownloader.cpp | 6 +++--- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/launcher/java/JavaVersion.h b/launcher/java/JavaVersion.h index 92c743bfb8..dfb4770da5 100644 --- a/launcher/java/JavaVersion.h +++ b/launcher/java/JavaVersion.h @@ -30,11 +30,11 @@ class JavaVersion { QString toString() const; - int major() { return m_major; } - int minor() { return m_minor; } - int security() { return m_security; } - QString build() { return m_prerelease; } - QString name() { return m_name; } + int major() const { return m_major; } + int minor() const { return m_minor; } + int security() const { return m_security; } + QString build() const { return m_prerelease; } + QString name() const { return m_name; } private: QString m_string; diff --git a/launcher/minecraft/LaunchProfile.cpp b/launcher/minecraft/LaunchProfile.cpp index cf819b411c..77072472c2 100644 --- a/launcher/minecraft/LaunchProfile.cpp +++ b/launcher/minecraft/LaunchProfile.cpp @@ -164,6 +164,10 @@ void LaunchProfile::applyCompatibleJavaMajors(QList& javaMajor) { m_compatibleJavaMajors.append(javaMajor); } +void LaunchProfile::applyCompatibleJavaName(QString javaName) +{ + m_compatibleJavaName = javaName; +} void LaunchProfile::applyLibrary(LibraryPtr library, const RuntimeContext& runtimeContext) { @@ -334,6 +338,11 @@ const QList& LaunchProfile::getCompatibleJavaMajors() const return m_compatibleJavaMajors; } +const QString LaunchProfile::getCompatibleJavaName() const +{ + return m_compatibleJavaName; +} + void LaunchProfile::getLibraryFiles(const RuntimeContext& runtimeContext, QStringList& jars, QStringList& nativeJars, diff --git a/launcher/minecraft/LaunchProfile.h b/launcher/minecraft/LaunchProfile.h index 12b312383a..f1be6fee0b 100644 --- a/launcher/minecraft/LaunchProfile.h +++ b/launcher/minecraft/LaunchProfile.h @@ -59,6 +59,7 @@ class LaunchProfile : public ProblemProvider { void applyMavenFile(LibraryPtr library, const RuntimeContext& runtimeContext); void applyAgent(AgentPtr agent, const RuntimeContext& runtimeContext); void applyCompatibleJavaMajors(QList& javaMajor); + void applyCompatibleJavaName(QString javaName); void applyMainJar(LibraryPtr jar); void applyProblemSeverity(ProblemSeverity severity); /// clear the profile @@ -80,6 +81,7 @@ class LaunchProfile : public ProblemProvider { const QList& getMavenFiles() const; const QList& getAgents() const; const QList& getCompatibleJavaMajors() const; + const QString getCompatibleJavaName() const; const LibraryPtr getMainJar() const; void getLibraryFiles(const RuntimeContext& runtimeContext, QStringList& jars, @@ -150,5 +152,7 @@ class LaunchProfile : public ProblemProvider { /// compatible java major versions QList m_compatibleJavaMajors; + QString m_compatibleJavaName; + ProblemSeverity m_problemSeverity = ProblemSeverity::None; }; diff --git a/launcher/minecraft/VersionFile.cpp b/launcher/minecraft/VersionFile.cpp index 6632bb8bf6..8ee61128f4 100644 --- a/launcher/minecraft/VersionFile.cpp +++ b/launcher/minecraft/VersionFile.cpp @@ -73,6 +73,7 @@ void VersionFile::applyTo(LaunchProfile* profile, const RuntimeContext& runtimeC profile->applyMods(mods); profile->applyTraits(traits); profile->applyCompatibleJavaMajors(compatibleJavaMajors); + profile->applyCompatibleJavaName(compatibleJavaName); for (auto library : libraries) { profile->applyLibrary(library, runtimeContext); diff --git a/launcher/ui/java/JavaDownloader.cpp b/launcher/ui/java/JavaDownloader.cpp index 9121988da9..3552f0ef5f 100644 --- a/launcher/ui/java/JavaDownloader.cpp +++ b/launcher/ui/java/JavaDownloader.cpp @@ -45,9 +45,9 @@ namespace Java { Downloader::Downloader(QWidget* parent) : QDialog(parent), ui(new Ui::JavaDownloader) { ui->setupUi(this); - auto versionList = new Meta::VersionList("net.minecraft.java", this); + auto versionList = APPLICATION->metadataIndex()->get("net.minecraft.java"); versionList->setProvidedRoles({ BaseVersionList::VersionRole, BaseVersionList::RecommendedRole, BaseVersionList::VersionPointerRole }); - ui->majorVersionSelect->initialize(versionList); + ui->majorVersionSelect->initialize(versionList.get()); ui->majorVersionSelect->selectCurrent(); ui->majorVersionSelect->setEmptyString(tr("No java versions are currently available in the meta")); ui->majorVersionSelect->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); @@ -85,7 +85,7 @@ void Downloader::accept() return; } Task::Ptr task; - auto final_path = FS::PathCombine(APPLICATION->dataRoot(), "java", meta->m_name); + auto final_path = FS::PathCombine(APPLICATION->dataRoot(), "java", meta->vendor, meta->m_name); switch (meta->downloadType) { case Java::DownloadType::Manifest: task = makeShared(meta->url, final_path, meta->checksumType, meta->checksumHash); From ba990e075bc9bea8b03857a643005f6fe9301a34 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 8 Feb 2024 19:45:46 +0200 Subject: [PATCH 0131/2054] Added JavaPath function Signed-off-by: Trial97 --- launcher/Application.cpp | 7 ++++--- launcher/Application.h | 3 +++ launcher/java/JavaUtils.cpp | 28 +++++++++++++++++++++++++++- launcher/java/JavaUtils.h | 1 + launcher/ui/java/JavaDownloader.cpp | 2 +- 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 679de711e3..20da461878 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -48,7 +48,6 @@ #include "net/PasteUpload.h" #include "pathmatcher/MultiMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h" -#include "settings/INIFile.h" #include "ui/InstanceWindow.h" #include "ui/MainWindow.h" @@ -106,8 +105,6 @@ #include "icons/IconList.h" #include "net/HttpMetaCache.h" -#include "java/JavaUtils.h" - #include "updater/ExternalUpdater.h" #include "tools/JProfiler.h" @@ -1833,3 +1830,7 @@ QUrl Application::normalizeImportUrl(QString const& url) return QUrl::fromUserInput(url); } } +const QString Application::javaPath() +{ + return FS::PathCombine(m_dataPath, "java"); +} diff --git a/launcher/Application.h b/launcher/Application.h index 85bf2dff44..ba65edd821 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -162,6 +162,9 @@ class Application : public QApplication { /// the data path the application is using const QString& dataRoot() { return m_dataPath; } + /// the java installed path the application is using + const QString javaPath(); + bool isPortable() { return m_portable; } const Capabilities capabilities() { return m_capabilities; } diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 67f1fd3a77..ff5ba5efe5 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -337,6 +337,7 @@ QList JavaUtils::FindJavaPaths() } candidates.append(getMinecraftJavaBundle()); + candidates.append(getPrismJavaBundle()); candidates = addJavasFromEnv(candidates); candidates.removeDuplicates(); return candidates; @@ -363,6 +364,7 @@ QList JavaUtils::FindJavaPaths() javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java"); } javas.append(getMinecraftJavaBundle()); + javas.append(getPrismJavaBundle()); javas = addJavasFromEnv(javas); javas.removeDuplicates(); return javas; @@ -393,7 +395,6 @@ QList JavaUtils::FindJavaPaths() scanJavaDir(snap + dirPath); } }; - scanJavaDir(FS::PathCombine(APPLICATION->dataRoot(), "java")); // oracle RPMs scanJavaDirs("/usr/java"); // general locations used by distro packaging @@ -416,6 +417,7 @@ QList JavaUtils::FindJavaPaths() scanJavaDirs(FS::PathCombine(home, ".sdkman/candidates/java")); javas.append(getMinecraftJavaBundle()); + javas.append(getPrismJavaBundle()); javas = addJavasFromEnv(javas); javas.removeDuplicates(); return javas; @@ -429,6 +431,8 @@ QList JavaUtils::FindJavaPaths() javas.append(this->GetDefaultJava()->path); javas.append(getMinecraftJavaBundle()); + javas.append(getPrismJavaBundle()); + javas.removeDuplicates(); return addJavasFromEnv(javas); } #endif @@ -484,3 +488,25 @@ QStringList getMinecraftJavaBundle() } return javas; } + +QStringList getPrismJavaBundle() +{ + QList javas; + QDir dir(APPLICATION->javaPath()); + if (!dir.exists()) + return javas; + + QString executable = "java"; +#if defined(Q_OS_WIN32) + executable += "w.exe"; +#endif + + auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (auto& entry : entries) { + QString prefix; + prefix = entry.canonicalFilePath(); + javas.append(FS::PathCombine(prefix, "jre", "bin", executable)); + javas.append(FS::PathCombine(prefix, "bin", executable)); + } + return javas; +} diff --git a/launcher/java/JavaUtils.h b/launcher/java/JavaUtils.h index 0beb8c67a7..aa5315a194 100644 --- a/launcher/java/JavaUtils.h +++ b/launcher/java/JavaUtils.h @@ -26,6 +26,7 @@ QString stripVariableEntries(QString name, QString target, QString remove); QProcessEnvironment CleanEnviroment(); QStringList getMinecraftJavaBundle(); +QStringList getPrismJavaBundle(); class JavaUtils : public QObject { Q_OBJECT diff --git a/launcher/ui/java/JavaDownloader.cpp b/launcher/ui/java/JavaDownloader.cpp index 3552f0ef5f..4495210c37 100644 --- a/launcher/ui/java/JavaDownloader.cpp +++ b/launcher/ui/java/JavaDownloader.cpp @@ -85,7 +85,7 @@ void Downloader::accept() return; } Task::Ptr task; - auto final_path = FS::PathCombine(APPLICATION->dataRoot(), "java", meta->vendor, meta->m_name); + auto final_path = FS::PathCombine(APPLICATION->javaPath(), meta->m_name); switch (meta->downloadType) { case Java::DownloadType::Manifest: task = makeShared(meta->url, final_path, meta->checksumType, meta->checksumHash); From 3d29da916ded28e320eda2654ba42f529e682019 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 8 Feb 2024 20:15:19 +0200 Subject: [PATCH 0132/2054] Made Java path configurable Signed-off-by: Trial97 --- launcher/Application.cpp | 3 +- launcher/ui/pages/global/LauncherPage.cpp | 12 +++ launcher/ui/pages/global/LauncherPage.h | 1 + launcher/ui/pages/global/LauncherPage.ui | 89 ++++++++++++++--------- 4 files changed, 68 insertions(+), 37 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 20da461878..cc4fc6b1c8 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -586,6 +586,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("IconsDir", "icons"); m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); m_settings->registerSetting("DownloadsDirWatchRecursive", false); + m_settings->registerSetting("JavaDir", "java"); // Editors m_settings->registerSetting("JsonEditor", QString()); @@ -1832,5 +1833,5 @@ QUrl Application::normalizeImportUrl(QString const& url) } const QString Application::javaPath() { - return FS::PathCombine(m_dataPath, "java"); + return m_settings->get("JavaDir").toString(); } diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 78c44380a0..99a80d98c5 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -173,6 +173,16 @@ void LauncherPage::on_downloadsDirBrowseBtn_clicked() } } +void LauncherPage::on_javaDirBrowseBtn_clicked() +{ + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Java Folder"), ui->javaDirTextBox->text()); + + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { + QString cooked_dir = FS::NormalizePath(raw_dir); + ui->javaDirTextBox->setText(cooked_dir); + } +} + void LauncherPage::on_metadataDisableBtn_clicked() { ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); @@ -208,6 +218,7 @@ void LauncherPage::applySettings() s->set("CentralModsDir", ui->modsDirTextBox->text()); s->set("IconsDir", ui->iconsDirTextBox->text()); s->set("DownloadsDir", ui->downloadsDirTextBox->text()); + s->set("JavaDir", ui->javaDirTextBox->text()); s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked()); auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId(); @@ -269,6 +280,7 @@ void LauncherPage::loadSettings() ui->modsDirTextBox->setText(s->get("CentralModsDir").toString()); ui->iconsDirTextBox->setText(s->get("IconsDir").toString()); ui->downloadsDirTextBox->setText(s->get("DownloadsDir").toString()); + ui->javaDirTextBox->setText(s->get("JavaDir").toString()); ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool()); QString sortMode = s->get("InstSortMode").toString(); diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h index e733224d24..32945626fb 100644 --- a/launcher/ui/pages/global/LauncherPage.h +++ b/launcher/ui/pages/global/LauncherPage.h @@ -74,6 +74,7 @@ class LauncherPage : public QWidget, public BasePage { void on_modsDirBrowseBtn_clicked(); void on_iconsDirBrowseBtn_clicked(); void on_downloadsDirBrowseBtn_clicked(); + void on_javaDirBrowseBtn_clicked(); void on_metadataDisableBtn_clicked(); /*! diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 18b52e1b86..81acb0a177 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -7,7 +7,7 @@ 0 0 511 - 629 + 654 @@ -67,13 +67,20 @@ Folders - - + + - &Downloads: + Browse - - downloadsDirTextBox + + + + + + When enabled, in addition to the downloads folder, its sub folders will also be searched when looking for resources (e.g. when looking for blocked mods on CurseForge). + + + Check downloads folder recursively @@ -87,73 +94,83 @@ - - - - - - - - - - + Browse + + + - - + + Browse - - + + + + + + + + - &Mods: + &Icons: - modsDirTextBox + iconsDirTextBox - - + + - Browse + &Downloads: + + + downloadsDirTextBox - - + + Browse - - + + - &Icons: + &Mods: - iconsDirTextBox + modsDirTextBox - - - - When enabled, in addition to the downloads folder, its sub folders will also be searched when looking for resources (e.g. when looking for blocked mods on CurseForge). + + + + Java: + + + + + + + - Check downloads folder recursively + Browse From b3fc07d44478aa7fc841c88fe9160c462b05577a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 8 Feb 2024 21:47:59 +0200 Subject: [PATCH 0133/2054] Added extra java search paths Signed-off-by: Trial97 --- launcher/Application.cpp | 1 + launcher/java/JavaUtils.cpp | 27 ++++++++--- launcher/ui/pages/global/JavaPage.cpp | 29 ++++++++++- launcher/ui/pages/global/JavaPage.h | 5 +- launcher/ui/pages/global/JavaPage.ui | 69 +++++++++++++++++++++++++-- 5 files changed, 119 insertions(+), 12 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index cc4fc6b1c8..d9abccfdc1 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -629,6 +629,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("JvmArgs", ""); m_settings->registerSetting("IgnoreJavaCompatibility", false); m_settings->registerSetting("IgnoreJavaWizard", false); + m_settings->registerSetting("JavaExtraSearchPaths", QStringList()); // Legacy settings m_settings->registerSetting("OnlineFixes", false); diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index ff5ba5efe5..cd95939809 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -492,21 +492,34 @@ QStringList getMinecraftJavaBundle() QStringList getPrismJavaBundle() { QList javas; - QDir dir(APPLICATION->javaPath()); - if (!dir.exists()) - return javas; QString executable = "java"; #if defined(Q_OS_WIN32) executable += "w.exe"; #endif - auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); - for (auto& entry : entries) { - QString prefix; - prefix = entry.canonicalFilePath(); + auto scanDir = [&](QString prefix) { javas.append(FS::PathCombine(prefix, "jre", "bin", executable)); javas.append(FS::PathCombine(prefix, "bin", executable)); + javas.append(FS::PathCombine(prefix, executable)); + }; + auto scanJavaDir = [&](const QString& dirPath) { + QDir dir(dirPath); + if (!dir.exists()) + return; + auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (auto& entry : entries) { + scanDir(entry.canonicalFilePath()); + } + }; + + scanJavaDir(APPLICATION->javaPath()); + + auto extra_paths = APPLICATION->settings()->get("JavaExtraSearchPaths").toStringList(); + for (auto& entry : extra_paths) { + scanDir(entry); + scanJavaDir(entry); } + return javas; } diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 9b21aad724..6d278983de 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include "ui/dialogs/VersionSelectDialog.h" @@ -57,7 +58,6 @@ JavaPage::JavaPage(QWidget* parent) : QWidget(parent), ui(new Ui::JavaPage) { ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); loadSettings(); updateThresholds(); @@ -95,6 +95,7 @@ void JavaPage::applySettings() s->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " ")); s->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked()); s->set("IgnoreJavaWizard", ui->skipJavaWizardCheckbox->isChecked()); + s->set("JavaExtraSearchPaths", m_extra_paths->stringList()); JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget()); } void JavaPage::loadSettings() @@ -117,6 +118,8 @@ void JavaPage::loadSettings() ui->jvmArgsTextBox->setPlainText(s->get("JvmArgs").toString()); ui->skipCompatibilityCheckbox->setChecked(s->get("IgnoreJavaCompatibility").toBool()); ui->skipJavaWizardCheckbox->setChecked(s->get("IgnoreJavaWizard").toBool()); + m_extra_paths = new QStringListModel(s->get("JavaExtraSearchPaths").toStringList()); + ui->extraJavaPathsList->setModel(m_extra_paths); } void JavaPage::on_javaDetectBtn_clicked() @@ -217,3 +220,27 @@ void JavaPage::updateThresholds() ui->labelMaxMemIcon->setPixmap(pix); } } + +void JavaPage::on_addExtraPathButton_clicked() +{ + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Add Extra Java Folder")); + + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { + QString cooked_dir = FS::NormalizePath(raw_dir); + auto currentList = m_extra_paths->stringList(); + if (!currentList.contains(cooked_dir)) { + currentList << cooked_dir; + m_extra_paths->setStringList(currentList); + } + } + APPLICATION->settings()->set("JavaExtraSearchPaths", m_extra_paths->stringList()); +} + +void JavaPage::on_removeExtraPathButton_clicked() +{ + auto indexes = ui->extraJavaPathsList->selectionModel()->selectedIndexes(); + if (indexes.size()) { + m_extra_paths->removeRow(indexes.first().row()); + } + APPLICATION->settings()->set("JavaExtraSearchPaths", m_extra_paths->stringList()); +} diff --git a/launcher/ui/pages/global/JavaPage.h b/launcher/ui/pages/global/JavaPage.h index 2bbfdf6bf8..d1315db64d 100644 --- a/launcher/ui/pages/global/JavaPage.h +++ b/launcher/ui/pages/global/JavaPage.h @@ -38,7 +38,7 @@ #include #include #include -#include +#include #include "JavaCommon.h" #include "ui/pages/BasePage.h" @@ -73,10 +73,13 @@ class JavaPage : public QWidget, public BasePage { void on_javaTestBtn_clicked(); void on_javaBrowseBtn_clicked(); void on_javaDownloadBtn_clicked(); + void on_addExtraPathButton_clicked(); + void on_removeExtraPathButton_clicked(); void on_maxMemSpinBox_valueChanged(int i); void checkerFinished(); private: Ui::JavaPage* ui; unique_qobject_ptr checker; + QStringListModel* m_extra_paths; }; diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index 382c183c96..d57e2e5488 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -32,11 +32,11 @@ - 0 + 1 - + - Tab 1 + General @@ -312,6 +312,69 @@ + + + Management + + + + + + Java extra paths + + + + + + + + + + + Add + + + + + + + Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + From 379f1a70e99a9a629c3cb174a1ad596fcb79a3cb Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 8 Feb 2024 22:37:57 +0200 Subject: [PATCH 0134/2054] Leave headers alone Signed-off-by: Trial97 --- launcher/Application.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index d9abccfdc1..4984278562 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -48,6 +48,7 @@ #include "net/PasteUpload.h" #include "pathmatcher/MultiMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h" +#include "settings/INIFile.h" #include "ui/InstanceWindow.h" #include "ui/MainWindow.h" @@ -105,6 +106,8 @@ #include "icons/IconList.h" #include "net/HttpMetaCache.h" +#include "java/JavaUtils.h" + #include "updater/ExternalUpdater.h" #include "tools/JProfiler.h" From 0a3303bcbdabbc2e5e1338dabfb3b8cf518d29e4 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 9 Feb 2024 21:07:12 +0200 Subject: [PATCH 0135/2054] Added button to add extra java path to the Java Wizzard Signed-off-by: Trial97 --- launcher/ui/dialogs/VersionSelectDialog.h | 4 ---- launcher/ui/widgets/JavaSettingsWidget.cpp | 25 +++++++++++++++++++++- launcher/ui/widgets/JavaSettingsWidget.h | 5 ++++- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/launcher/ui/dialogs/VersionSelectDialog.h b/launcher/ui/dialogs/VersionSelectDialog.h index 0ccd45e745..17efc1b93f 100644 --- a/launcher/ui/dialogs/VersionSelectDialog.h +++ b/launcher/ui/dialogs/VersionSelectDialog.h @@ -26,10 +26,6 @@ class QDialogButtonBox; class VersionSelectWidget; class QPushButton; -namespace Ui { -class VersionSelectDialog; -} - class VersionProxyModel; class VersionSelectDialog : public QDialog { diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 3f4fe08ea4..bb57e5da74 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -42,6 +42,7 @@ JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent) connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaSettingsWidget::javaPathEdited); connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaSettingsWidget::on_javaStatusBtn_clicked); connect(m_javaDownloadBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaDownloadBtn_clicked); + connect(m_addJavaPathBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_addJavaPathBtn_clicked); } void JavaSettingsWidget::setupUi() @@ -124,9 +125,16 @@ void JavaSettingsWidget::setupUi() m_verticalLayout->addWidget(m_memoryGroupBox); + m_horizontalBtnLayout = new QHBoxLayout(); + m_horizontalBtnLayout->setObjectName(QStringLiteral("horizontalBtnLayout")); + m_javaDownloadBtn = new QPushButton(tr("Download Java"), this); + m_horizontalBtnLayout->addWidget(m_javaDownloadBtn); + + m_addJavaPathBtn = new QPushButton(tr("Add extra Java path"), this); + m_horizontalBtnLayout->addWidget(m_addJavaPathBtn); - m_verticalLayout->addWidget(m_javaDownloadBtn); + m_verticalLayout->addLayout(m_horizontalBtnLayout); retranslate(); } @@ -437,3 +445,18 @@ void JavaSettingsWidget::updateThresholds() m_labelMaxMemIcon->setPixmap(pix); } } + +void JavaSettingsWidget::on_addJavaPathBtn_clicked() +{ + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Add Extra Java Folder")); + + auto currentList = APPLICATION->settings()->get("JavaExtraSearchPaths").toStringList(); + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { + QString cooked_dir = FS::NormalizePath(raw_dir); + if (!currentList.contains(cooked_dir)) { + currentList << cooked_dir; + } + } + APPLICATION->settings()->set("JavaExtraSearchPaths", currentList); + refresh(); +} diff --git a/launcher/ui/widgets/JavaSettingsWidget.h b/launcher/ui/widgets/JavaSettingsWidget.h index b7db955a4d..385a9256a3 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.h +++ b/launcher/ui/widgets/JavaSettingsWidget.h @@ -25,7 +25,7 @@ class JavaSettingsWidget : public QWidget { public: explicit JavaSettingsWidget(QWidget* parent); - virtual ~JavaSettingsWidget(){}; + virtual ~JavaSettingsWidget() = default; enum class JavaStatus { NotSet, Pending, Good, DoesNotExist, DoesNotStart, ReturnedInvalidData } javaStatus = JavaStatus::NotSet; @@ -51,6 +51,7 @@ class JavaSettingsWidget : public QWidget { void on_javaBrowseBtn_clicked(); void on_javaStatusBtn_clicked(); void on_javaDownloadBtn_clicked(); + void on_addJavaPathBtn_clicked(); void checkFinished(const JavaChecker::Result& result); protected: /* methods */ @@ -78,7 +79,9 @@ class JavaSettingsWidget : public QWidget { QLabel* m_labelPermGen = nullptr; QSpinBox* m_permGenSpinBox = nullptr; + QHBoxLayout* m_horizontalBtnLayout = nullptr; QPushButton* m_javaDownloadBtn = nullptr; + QPushButton* m_addJavaPathBtn = nullptr; QIcon goodIcon; QIcon yellowIcon; QIcon badIcon; From 27d662e64241a75eee963048a669398a5083f7ed Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 9 Feb 2024 22:47:39 +0200 Subject: [PATCH 0136/2054] Added management for downloaded javas from prism Signed-off-by: Trial97 --- launcher/java/JavaInstallList.cpp | 10 +++-- launcher/java/JavaInstallList.h | 6 ++- launcher/java/JavaUtils.cpp | 31 ++++++++++++++ launcher/java/JavaUtils.h | 1 + launcher/ui/pages/global/JavaPage.cpp | 40 ++++++++++++++++++ launcher/ui/pages/global/JavaPage.h | 2 + launcher/ui/pages/global/JavaPage.ui | 60 ++++++++++++++++++++++++++- 7 files changed, 143 insertions(+), 7 deletions(-) diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index d99201f24d..4172ba2cfa 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -46,7 +46,9 @@ #include "java/JavaUtils.h" #include "tasks/ConcurrentTask.h" -JavaInstallList::JavaInstallList(QObject* parent) : BaseVersionList(parent) {} +JavaInstallList::JavaInstallList(QObject* parent, bool onlyManagedVersions) + : BaseVersionList(parent), m_only_managed_versions(onlyManagedVersions) +{} Task::Ptr JavaInstallList::getLoadTask() { @@ -66,7 +68,7 @@ void JavaInstallList::load() { if (m_status != Status::InProgress) { m_status = Status::InProgress; - m_load_task.reset(new JavaListLoadTask(this)); + m_load_task.reset(new JavaListLoadTask(this, m_only_managed_versions)); m_load_task->start(); } } @@ -148,7 +150,7 @@ void JavaInstallList::sortVersions() endResetModel(); } -JavaListLoadTask::JavaListLoadTask(JavaInstallList* vlist) : Task() +JavaListLoadTask::JavaListLoadTask(JavaInstallList* vlist, bool onlyManagedVersions) : Task(), m_only_managed_versions(onlyManagedVersions) { m_list = vlist; m_current_recommended = NULL; @@ -159,7 +161,7 @@ void JavaListLoadTask::executeTask() setStatus(tr("Detecting Java installations...")); JavaUtils ju; - QList candidate_paths = ju.FindJavaPaths(); + QList candidate_paths = m_only_managed_versions ? getPrismJavaBundle() : ju.FindJavaPaths(); ConcurrentTask::Ptr job(new ConcurrentTask(this, "Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); m_job.reset(job); diff --git a/launcher/java/JavaInstallList.h b/launcher/java/JavaInstallList.h index 08f0b310df..c68c2a3be0 100644 --- a/launcher/java/JavaInstallList.h +++ b/launcher/java/JavaInstallList.h @@ -33,7 +33,7 @@ class JavaInstallList : public BaseVersionList { enum class Status { NotDone, InProgress, Done }; public: - explicit JavaInstallList(QObject* parent = 0); + explicit JavaInstallList(QObject* parent = 0, bool onlyManagedVersions = false); Task::Ptr getLoadTask() override; bool isLoaded() override; @@ -55,13 +55,14 @@ class JavaInstallList : public BaseVersionList { Status m_status = Status::NotDone; shared_qobject_ptr m_load_task; QList m_vlist; + bool m_only_managed_versions; }; class JavaListLoadTask : public Task { Q_OBJECT public: - explicit JavaListLoadTask(JavaInstallList* vlist); + explicit JavaListLoadTask(JavaInstallList* vlist, bool onlyManagedVersions = false); virtual ~JavaListLoadTask() = default; protected: @@ -74,4 +75,5 @@ class JavaListLoadTask : public Task { JavaInstallList* m_list; JavaInstall* m_current_recommended; QList m_results; + bool m_only_managed_versions; }; diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index cd95939809..1d254e4057 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -338,6 +338,7 @@ QList JavaUtils::FindJavaPaths() candidates.append(getMinecraftJavaBundle()); candidates.append(getPrismJavaBundle()); + candidates.append(getPrismExtraJavaPaths()); candidates = addJavasFromEnv(candidates); candidates.removeDuplicates(); return candidates; @@ -365,6 +366,7 @@ QList JavaUtils::FindJavaPaths() } javas.append(getMinecraftJavaBundle()); javas.append(getPrismJavaBundle()); + javas.append(getPrismExtraJavaPaths()); javas = addJavasFromEnv(javas); javas.removeDuplicates(); return javas; @@ -418,6 +420,7 @@ QList JavaUtils::FindJavaPaths() javas.append(getMinecraftJavaBundle()); javas.append(getPrismJavaBundle()); + javas.append(getPrismExtraJavaPaths()); javas = addJavasFromEnv(javas); javas.removeDuplicates(); return javas; @@ -432,6 +435,7 @@ QList JavaUtils::FindJavaPaths() javas.append(getMinecraftJavaBundle()); javas.append(getPrismJavaBundle()); + javas.append(getPrismExtraJavaPaths()); javas.removeDuplicates(); return addJavasFromEnv(javas); } @@ -515,6 +519,33 @@ QStringList getPrismJavaBundle() scanJavaDir(APPLICATION->javaPath()); + return javas; +} + +QStringList getPrismExtraJavaPaths() +{ + QList javas; + + QString executable = "java"; +#if defined(Q_OS_WIN32) + executable += "w.exe"; +#endif + + auto scanDir = [&](QString prefix) { + javas.append(FS::PathCombine(prefix, "jre", "bin", executable)); + javas.append(FS::PathCombine(prefix, "bin", executable)); + javas.append(FS::PathCombine(prefix, executable)); + }; + auto scanJavaDir = [&](const QString& dirPath) { + QDir dir(dirPath); + if (!dir.exists()) + return; + auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (auto& entry : entries) { + scanDir(entry.canonicalFilePath()); + } + }; + auto extra_paths = APPLICATION->settings()->get("JavaExtraSearchPaths").toStringList(); for (auto& entry : extra_paths) { scanDir(entry); diff --git a/launcher/java/JavaUtils.h b/launcher/java/JavaUtils.h index aa5315a194..66e4398340 100644 --- a/launcher/java/JavaUtils.h +++ b/launcher/java/JavaUtils.h @@ -27,6 +27,7 @@ QString stripVariableEntries(QString name, QString target, QString remove); QProcessEnvironment CleanEnviroment(); QStringList getMinecraftJavaBundle(); QStringList getPrismJavaBundle(); +QStringList getPrismExtraJavaPaths(); class JavaUtils : public QObject { Q_OBJECT diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 6d278983de..7d7530e353 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -36,6 +36,8 @@ #include "JavaPage.h" #include "JavaCommon.h" +#include "java/JavaInstall.h" +#include "ui/dialogs/CustomMessageBox.h" #include "ui/java/JavaDownloader.h" #include "ui_JavaPage.h" @@ -59,6 +61,11 @@ JavaPage::JavaPage(QWidget* parent) : QWidget(parent), ui(new Ui::JavaPage) { ui->setupUi(this); + ui->managedJavaList->initialize(new JavaInstallList(this, true)); + ui->managedJavaList->selectCurrent(); + ui->managedJavaList->setEmptyString(tr("No java versions are currently available in the meta")); + ui->managedJavaList->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); + loadSettings(); updateThresholds(); } @@ -244,3 +251,36 @@ void JavaPage::on_removeExtraPathButton_clicked() } APPLICATION->settings()->set("JavaExtraSearchPaths", m_extra_paths->stringList()); } + +void JavaPage::on_downloadJavaButton_clicked() +{ + on_javaDownloadBtn_clicked(); +} + +void JavaPage::on_removeJavaButton_clicked() +{ + auto version = ui->managedJavaList->selectedVersion(); + auto dcast = std::dynamic_pointer_cast(version); + if (!dcast) { + return; + } + QDir dir(APPLICATION->javaPath()); + + auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (auto& entry : entries) { + if (dcast->path.startsWith(entry.canonicalFilePath())) { + auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"), + tr("You are about to remove \"%1\" java version.\n" + "Are you sure?") + .arg(entry.fileName()), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response == QMessageBox::Yes) { + FS::deletePath(entry.canonicalFilePath()); + ui->managedJavaList->loadList(); + } + break; + } + } +} diff --git a/launcher/ui/pages/global/JavaPage.h b/launcher/ui/pages/global/JavaPage.h index d1315db64d..03d14b8246 100644 --- a/launcher/ui/pages/global/JavaPage.h +++ b/launcher/ui/pages/global/JavaPage.h @@ -75,6 +75,8 @@ class JavaPage : public QWidget, public BasePage { void on_javaDownloadBtn_clicked(); void on_addExtraPathButton_clicked(); void on_removeExtraPathButton_clicked(); + void on_downloadJavaButton_clicked(); + void on_removeJavaButton_clicked(); void on_maxMemSpinBox_valueChanged(int i); void checkerFinished(); diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index d57e2e5488..a518b0b149 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -32,7 +32,7 @@ - 1 + 0 @@ -317,6 +317,56 @@ Management + + + + Downloaded Java Versions + + + + + + + 0 + 0 + + + + + + + + + + Download + + + + + + + Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + @@ -379,6 +429,14 @@ + + + VersionSelectWidget + QWidget +
    ui/widgets/VersionSelectWidget.h
    + 1 +
    +
    minMemSpinBox maxMemSpinBox From c0fb053ccc966045df42e8b218a994cfcfdef986 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 9 Feb 2024 23:14:49 +0200 Subject: [PATCH 0137/2054] Added warning for x86 java Signed-off-by: Trial97 --- launcher/java/JavaInstall.h | 1 + launcher/java/JavaInstallList.cpp | 1 + launcher/ui/pages/global/JavaPage.cpp | 8 ++++++++ launcher/ui/pages/instance/InstanceSettingsPage.cpp | 10 ++++++++++ launcher/ui/widgets/JavaSettingsWidget.cpp | 4 ++++ 5 files changed, 24 insertions(+) diff --git a/launcher/java/JavaInstall.h b/launcher/java/JavaInstall.h index 8c2743a00e..6890264f33 100644 --- a/launcher/java/JavaInstall.h +++ b/launcher/java/JavaInstall.h @@ -40,6 +40,7 @@ struct JavaInstall : public BaseVersion { QString arch; QString path; bool recommended = false; + bool is_64bit = false; }; using JavaInstallPtr = std::shared_ptr; diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index 4172ba2cfa..569fda306b 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -193,6 +193,7 @@ void JavaListLoadTask::javaCheckerFinished() javaVersion->id = result.javaVersion; javaVersion->arch = result.realPlatform; javaVersion->path = result.path; + javaVersion->is_64bit = result.is_64bit; candidates.append(javaVersion); qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path; diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 7d7530e353..9b2aa7637a 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -145,6 +145,14 @@ void JavaPage::on_javaDetectBtn_clicked() if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) { java = std::dynamic_pointer_cast(vselect.selectedVersion()); ui->javaPathTextBox->setText(java->path); + if (!java->is_64bit && APPLICATION->settings()->get("MaxMemAlloc").toInt() > 2048) { + CustomMessageBox::selectable(this, tr("Confirm Selection"), + tr("You selected an x86 java version.\n" + "This means that will not support more than 2Gb(2048Mb) of ram.\n" + "Please make sure that the maximum memory value is lower."), + QMessageBox::Warning, QMessageBox::Ok, QMessageBox::Ok) + ->exec(); + } } } diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index b962406d1a..926e48952d 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -36,6 +36,7 @@ */ #include "InstanceSettingsPage.h" +#include "ui/dialogs/CustomMessageBox.h" #include "ui/java/JavaDownloader.h" #include "ui_InstanceSettingsPage.h" @@ -412,6 +413,15 @@ void InstanceSettingsPage::on_javaDetectBtn_clicked() ui->labelPermGen->setVisible(visible); ui->labelPermgenNote->setVisible(visible); m_settings->set("PermGenVisible", visible); + + if (!java->is_64bit && m_settings->get("MaxMemAlloc").toInt() > 2048) { + CustomMessageBox::selectable(this, tr("Confirm Selection"), + tr("You selected an x86 java version.\n" + "This means that will not support more than 2Gb(2048Mb) of ram.\n" + "Please make sure that the maximum memory value is lower."), + QMessageBox::Warning, QMessageBox::Ok, QMessageBox::Ok) + ->exec(); + } } } diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index bb57e5da74..507c632d53 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -433,6 +433,10 @@ void JavaSettingsWidget::updateThresholds() } else if (observedMaxMemory < observedMinMemory) { iconName = "status-yellow"; m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value")); + } else if (observedMaxMemory > 2048 && m_result.is_64bit) { + iconName = "status-bad"; + m_labelMaxMemIcon->setToolTip( + tr("Your maximum memory allocation exceeds selected java posible memory(due to x86 applicatiion limitations).")); } else { iconName = "status-good"; m_labelMaxMemIcon->setToolTip(""); From 031a9f4738df8977d8273968c2b3def6cc425fa2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 10 Feb 2024 11:03:51 +0200 Subject: [PATCH 0138/2054] Replaced QFile::remove with FS::deletePath Signed-off-by: Trial97 --- launcher/Application.cpp | 9 ++------ launcher/BaseInstaller.cpp | 3 ++- launcher/InstanceCreationTask.cpp | 3 ++- launcher/MMCZip.cpp | 22 +++++++++---------- launcher/icons/IconList.cpp | 2 +- launcher/meta/BaseEntity.cpp | 4 ++-- launcher/minecraft/Component.cpp | 2 +- launcher/minecraft/PackProfile.cpp | 2 +- .../minecraft/mod/ResourceFolderModel.cpp | 2 +- .../atlauncher/ATLPackInstallTask.cpp | 4 ++-- .../modplatform/helpers/OverrideUtils.cpp | 2 +- .../updater/prismupdater/PrismUpdater.cpp | 11 +++------- 12 files changed, 29 insertions(+), 37 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 42343ff8ff..25ee8fa692 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -389,20 +389,15 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) { static const QString baseLogFile = BuildConfig.LAUNCHER_NAME + "-%0.log"; static const QString logBase = FS::PathCombine("logs", baseLogFile); - auto moveFile = [](const QString& oldName, const QString& newName) { - QFile::remove(newName); - QFile::copy(oldName, newName); - QFile::remove(oldName); - }; if (FS::ensureFolderPathExists("logs")) { // if this did not fail for (auto i = 0; i <= 4; i++) if (auto oldName = baseLogFile.arg(i); QFile::exists(oldName)) // do not pointlessly delete new files if the old ones are not there - moveFile(oldName, logBase.arg(i)); + FS::move(oldName, logBase.arg(i)); } for (auto i = 4; i > 0; i--) - moveFile(logBase.arg(i - 1), logBase.arg(i)); + FS::move(logBase.arg(i - 1), logBase.arg(i)); logFile = std::unique_ptr(new QFile(logBase.arg(0))); if (!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { diff --git a/launcher/BaseInstaller.cpp b/launcher/BaseInstaller.cpp index 1ff86ed403..96a3b5ebe8 100644 --- a/launcher/BaseInstaller.cpp +++ b/launcher/BaseInstaller.cpp @@ -16,6 +16,7 @@ #include #include "BaseInstaller.h" +#include "FileSystem.h" #include "minecraft/MinecraftInstance.h" BaseInstaller::BaseInstaller() {} @@ -42,7 +43,7 @@ bool BaseInstaller::add(MinecraftInstance* to) bool BaseInstaller::remove(MinecraftInstance* from) { - return QFile::remove(filename(from->instanceRoot())); + return FS::deletePath(filename(from->instanceRoot())); } QString BaseInstaller::filename(const QString& root) const diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index 73dc17891d..9688fa1126 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -2,6 +2,7 @@ #include #include +#include "FileSystem.h" InstanceCreationTask::InstanceCreationTask() = default; @@ -47,7 +48,7 @@ void InstanceCreationTask::executeTask() if (!QFile::exists(path)) continue; qDebug() << "Removing" << path; - if (!QFile::remove(path)) { + if (!FS::deletePath(path)) { qCritical() << "Couldn't remove the old conflicting files."; emitFailed(tr("Failed to remove old conflicting files.")); return; diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index b81106a594..b6bfac25d7 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -122,7 +122,7 @@ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, QuaZip zip(fileCompressed); QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); if (!zip.open(QuaZip::mdCreate)) { - QFile::remove(fileCompressed); + FS::deletePath(fileCompressed); return false; } @@ -130,7 +130,7 @@ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, zip.close(); if (zip.getZipError() != 0) { - QFile::remove(fileCompressed); + FS::deletePath(fileCompressed); return false; } @@ -143,7 +143,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListtype() == ResourceType::ZIPFILE) { if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) { zipOut.close(); - QFile::remove(targetJarPath); + FS::deletePath(targetJarPath); qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; return false; } @@ -170,7 +170,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListfileinfo(); if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) { zipOut.close(); - QFile::remove(targetJarPath); + FS::deletePath(targetJarPath); qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; return false; } @@ -193,7 +193,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListfileinfo().fileName() << "to the jar."; return false; } @@ -201,7 +201,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListfileinfo().fileName() << "to the jar."; return false; } @@ -209,7 +209,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList ZipResult void ExportToZipTask::finish() { if (m_build_zip_future.isCanceled()) { - QFile::remove(m_output_path); + FS::deletePath(m_output_path); emitAborted(); } else if (auto result = m_build_zip_future.result(); result.has_value()) { - QFile::remove(m_output_path); + FS::deletePath(m_output_path); emitFailed(result.value()); } else { emitSucceeded(); diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index 5576b9745a..e4157ea2d9 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -322,7 +322,7 @@ const MMCIcon* IconList::icon(const QString& key) const bool IconList::deleteIcon(const QString& key) { - return iconFileExists(key) && QFile::remove(icon(key)->getFilePath()); + return iconFileExists(key) && FS::deletePath(icon(key)->getFilePath()); } bool IconList::trashIcon(const QString& key) diff --git a/launcher/meta/BaseEntity.cpp b/launcher/meta/BaseEntity.cpp index 5f9804e482..8a99e33034 100644 --- a/launcher/meta/BaseEntity.cpp +++ b/launcher/meta/BaseEntity.cpp @@ -15,6 +15,7 @@ #include "BaseEntity.h" +#include "FileSystem.h" #include "Json.h" #include "net/ApiDownload.h" #include "net/HttpMetaCache.h" @@ -83,8 +84,7 @@ bool Meta::BaseEntity::loadLocalFile() } catch (const Exception& e) { qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause()); // just make sure it's gone and we never consider it again. - QFile::remove(fname); - return false; + return !FS::deletePath(fname); } } diff --git a/launcher/minecraft/Component.cpp b/launcher/minecraft/Component.cpp index 79ea7a06d9..ad2e4023cf 100644 --- a/launcher/minecraft/Component.cpp +++ b/launcher/minecraft/Component.cpp @@ -336,7 +336,7 @@ bool Component::revert() bool result = true; // just kill the file and reload if (QFile::exists(filename)) { - result = QFile::remove(filename); + result = FS::deletePath(filename); } if (result) { // file gone... diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 180f8aa30b..4b17cdf070 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -839,7 +839,7 @@ bool PackProfile::installCustomJar_internal(QString filepath) QFileInfo jarInfo(finalPath); if (jarInfo.exists()) { - if (!QFile::remove(finalPath)) { + if (!FS::deletePath(finalPath)) { return false; } } diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 9157f35f0a..5bea720d0e 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -111,7 +111,7 @@ bool ResourceFolderModel::installResource(QString original_path) case ResourceType::ZIPFILE: case ResourceType::LITEMOD: { if (QFile::exists(new_path) || QFile::exists(new_path + QString(".disabled"))) { - if (!QFile::remove(new_path)) { + if (!FS::deletePath(new_path)) { qCritical() << "Cleaning up new location (" << new_path << ") was unsuccessful!"; return false; } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 8ae8145de7..57660aa6df 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -282,7 +282,7 @@ void PackInstallTask::deleteExistingFiles() // Delete the files for (const auto& item : filesToDelete) { - QFile::remove(item); + FS::deletePath(item); } } @@ -987,7 +987,7 @@ bool PackInstallTask::extractMods(const QMap& toExtract, // the copy from the Configs.zip QFileInfo fileInfo(to); if (fileInfo.exists()) { - if (!QFile::remove(to)) { + if (!FS::deletePath(to)) { qWarning() << "Failed to delete" << to; return false; } diff --git a/launcher/modplatform/helpers/OverrideUtils.cpp b/launcher/modplatform/helpers/OverrideUtils.cpp index 65b5f7603f..60983a5cfe 100644 --- a/launcher/modplatform/helpers/OverrideUtils.cpp +++ b/launcher/modplatform/helpers/OverrideUtils.cpp @@ -10,7 +10,7 @@ void createOverrides(const QString& name, const QString& parent_folder, const QS { QString file_path(FS::PathCombine(parent_folder, name + ".txt")); if (QFile::exists(file_path)) - QFile::remove(file_path); + FS::deletePath(file_path); FS::ensureFilePathExists(file_path); diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 5fe22bdd0d..8948b3e826 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -352,15 +352,10 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar FS::ensureFolderPathExists(FS::PathCombine(m_dataPath, "logs")); static const QString baseLogFile = BuildConfig.LAUNCHER_NAME + "Updater" + (m_checkOnly ? "-CheckOnly" : "") + "-%0.log"; static const QString logBase = FS::PathCombine(m_dataPath, "logs", baseLogFile); - auto moveFile = [](const QString& oldName, const QString& newName) { - QFile::remove(newName); - QFile::copy(oldName, newName); - QFile::remove(oldName); - }; if (FS::ensureFolderPathExists("logs")) { // enough history to track both launches of the updater during a portable install - moveFile(logBase.arg(1), logBase.arg(2)); - moveFile(logBase.arg(0), logBase.arg(1)); + FS::move(logBase.arg(1), logBase.arg(2)); + FS::move(logBase.arg(0), logBase.arg(1)); } logFile = std::unique_ptr(new QFile(logBase.arg(0))); @@ -924,7 +919,7 @@ bool PrismUpdaterApp::callAppImageUpdate() void PrismUpdaterApp::clearUpdateLog() { - QFile::remove(m_updateLogPath); + FS::deletePath(m_updateLogPath); } void PrismUpdaterApp::logUpdate(const QString& msg) From 2941307581114c435a2950df59e8ddc2c3c7c0c4 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 16 Feb 2024 23:42:39 +0200 Subject: [PATCH 0139/2054] Added basic java auto-detect and auto-download Signed-off-by: Trial97 --- launcher/Application.cpp | 2 + launcher/CMakeLists.txt | 2 + launcher/minecraft/MinecraftInstance.cpp | 12 +- launcher/minecraft/launch/AutoInstallJava.cpp | 192 ++++++++++++++++++ launcher/minecraft/launch/AutoInstallJava.h | 67 ++++++ .../minecraft/launch/VerifyJavaInstall.cpp | 45 +--- launcher/ui/pages/global/JavaPage.cpp | 4 + launcher/ui/pages/global/JavaPage.ui | 66 +++--- 8 files changed, 325 insertions(+), 65 deletions(-) create mode 100644 launcher/minecraft/launch/AutoInstallJava.cpp create mode 100644 launcher/minecraft/launch/AutoInstallJava.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 4984278562..6b7333972f 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -633,6 +633,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("IgnoreJavaCompatibility", false); m_settings->registerSetting("IgnoreJavaWizard", false); m_settings->registerSetting("JavaExtraSearchPaths", QStringList()); + m_settings->registerSetting("AutomaticJavaSwitch", false); + m_settings->registerSetting("AutomaticJavaDownload", false); // Legacy settings m_settings->registerSetting("OnlineFixes", false); diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c7945d1b84..fcc3c2f3a5 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -281,6 +281,8 @@ set(MINECRAFT_SOURCES minecraft/launch/ScanModFolders.h minecraft/launch/VerifyJavaInstall.cpp minecraft/launch/VerifyJavaInstall.h + minecraft/launch/AutoInstallJava.cpp + minecraft/launch/AutoInstallJava.h minecraft/GradleSpecifier.h minecraft/MinecraftInstance.cpp diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 4229f73eb8..00ce405aa8 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -38,6 +38,8 @@ #include "MinecraftInstance.h" #include "Application.h" #include "BuildConfig.h" +#include "QObjectPtr.h" +#include "minecraft/launch/AutoInstallJava.h" #include "minecraft/launch/CreateGameFolders.h" #include "minecraft/launch/ExtractNatives.h" #include "minecraft/launch/PrintInstanceInfo.h" @@ -1041,11 +1043,6 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt process->appendStep(makeShared(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::Launcher)); } - // check java - { - process->appendStep(makeShared(pptr)); - } - // create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732) { process->appendStep(makeShared(pptr)); @@ -1105,6 +1102,11 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt { process->appendStep(makeShared(pptr)); } + // check java + { + process->appendStep(makeShared(pptr)); + process->appendStep(makeShared(pptr)); + } // verify that minimum Java requirements are met { diff --git a/launcher/minecraft/launch/AutoInstallJava.cpp b/launcher/minecraft/launch/AutoInstallJava.cpp new file mode 100644 index 0000000000..0d3740c932 --- /dev/null +++ b/launcher/minecraft/launch/AutoInstallJava.cpp @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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 "AutoInstallJava.h" +#include +#include +#include + +#include "Application.h" +#include "FileSystem.h" +#include "MessageLevel.h" +#include "SysInfo.h" +#include "java/JavaInstall.h" +#include "java/JavaInstallList.h" +#include "java/JavaVersion.h" +#include "java/download/ArchiveDownloadTask.h" +#include "java/download/ManifestDownloadTask.h" +#include "meta/Index.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" +#include "net/Mode.h" + +AutoInstallJava::AutoInstallJava(LaunchTask* parent) + : LaunchStep(parent) + , m_instance(std::dynamic_pointer_cast(m_parent->instance())) + , m_supported_arch(SysInfo::getSupportedJavaArchitecture()){}; + +void AutoInstallJava::executeTask() +{ + auto settings = m_instance->settings(); + if (!APPLICATION->settings()->get("AutomaticJavaSwitch").toBool() || + (settings->get("OverrideJava").toBool() && settings->get("OverrideJavaLocation").toBool())) { + emitSucceeded(); + return; + } + auto packProfile = m_instance->getPackProfile(); + if (!APPLICATION->settings()->get("AutomaticJavaDownload").toBool()) { + auto javas = APPLICATION->javalist().get(); + m_current_task = javas->getLoadTask(); + connect(m_current_task.get(), &Task::finished, this, [this, javas, packProfile] { + for (auto i = 0; i < javas->count(); i++) { + auto java = std::dynamic_pointer_cast(javas->at(i)); + if (java && packProfile->getProfile()->getCompatibleJavaMajors().contains(java->id.major())) { + setJavaPath(java->path); + return; + } + } + emit logLine(tr("No comptatible java version was found. Using the default one."), MessageLevel::Warning); + emitSucceeded(); + }); + return; + } + auto wantedJavaName = packProfile->getProfile()->getCompatibleJavaName(); + QDir javaDir(APPLICATION->javaPath()); + auto wantedJavaPath = javaDir.absoluteFilePath(wantedJavaName); + if (QFileInfo::exists(wantedJavaPath)) { + setJavaPathFromPartial(); + return; + } + auto versionList = APPLICATION->metadataIndex()->get("net.minecraft.java"); + m_current_task = versionList->getLoadTask(); + connect(m_current_task.get(), &Task::succeeded, this, &AutoInstallJava::tryNextMajorJava); + connect(m_current_task.get(), &Task::failed, this, &AutoInstallJava::emitFailed); +} + +void AutoInstallJava::setJavaPath(QString path) +{ + auto settings = m_instance->settings(); + settings->set("OverrideJava", true); + settings->set("OverrideJavaLocation", true); + settings->set("JavaPath", path); + emit logLine(tr("Compatible java found at: %1.").arg(path), MessageLevel::Info); + emitSucceeded(); +} + +void AutoInstallJava::setJavaPathFromPartial() +{ + QString executable = "java"; +#if defined(Q_OS_WIN32) + executable += "w.exe"; +#endif + auto packProfile = m_instance->getPackProfile(); + auto javaName = packProfile->getProfile()->getCompatibleJavaName(); + QDir javaDir(APPLICATION->javaPath()); + // just checking if the executable is there should suffice + // but if needed this can be achieved through refreshing the javalist + // and retrieving the path that contains the java name + auto relativeBinary = FS::PathCombine(javaName, "bin", executable); + auto finalPath = javaDir.absoluteFilePath(relativeBinary); + if (QFileInfo::exists(finalPath)) { + setJavaPath(finalPath); + } else { + emit logLine(tr("No comptatible java version was found. Using the default one."), MessageLevel::Warning); + emitSucceeded(); + } + return; +} + +void AutoInstallJava::downloadJava(Meta::Version::Ptr version, QString javaName) +{ + auto runtimes = version->data()->runtimes; + if (runtimes.contains(m_supported_arch)) { + for (auto java : runtimes.value(m_supported_arch)) { + if (java->name() == javaName) { + Task::Ptr task; + QDir javaDir(APPLICATION->javaPath()); + auto final_path = javaDir.absoluteFilePath(java->m_name); + switch (java->downloadType) { + case Java::DownloadType::Manifest: + task = makeShared(java->url, final_path, java->checksumType, java->checksumHash); + break; + case Java::DownloadType::Archive: + task = makeShared(java->url, final_path, java->checksumType, java->checksumHash); + break; + } + QEventLoop loop; + auto deletePath = [final_path] { FS::deletePath(final_path); }; + connect(task.get(), &Task::failed, this, [this, deletePath](QString reason) { + deletePath(); + emitFailed(reason); + }); + connect(this, &Task::aborted, this, [task, deletePath] { + task->abort(); + deletePath(); + }); + connect(task.get(), &Task::succeeded, this, &AutoInstallJava::setJavaPathFromPartial); + task->start(); + return; + } + } + } + tryNextMajorJava(); +} + +void AutoInstallJava::tryNextMajorJava() +{ + if (!isRunning()) + return; + auto versionList = APPLICATION->metadataIndex()->get("net.minecraft.java"); + auto packProfile = m_instance->getPackProfile(); + auto wantedJavaName = packProfile->getProfile()->getCompatibleJavaName(); + auto majorJavaVersions = packProfile->getProfile()->getCompatibleJavaMajors(); + if (m_majorJavaVersionIndex >= majorJavaVersions.length()) { + emit logLine(tr("No comptatible java version was found. Using the default one."), MessageLevel::Warning); + emitSucceeded(); + return; + } + auto majorJavaVersion = majorJavaVersions[m_majorJavaVersionIndex]; + m_majorJavaVersionIndex++; + + auto javaMajor = versionList->getVersion(QString("java%1").arg(majorJavaVersion)); + javaMajor->load(Net::Mode::Online); + auto task = javaMajor->getCurrentTask(); + if (javaMajor->isLoaded() || !task) { + downloadJava(javaMajor, wantedJavaName); + } else { + connect(task.get(), &Task::succeeded, this, [this, javaMajor, wantedJavaName] { downloadJava(javaMajor, wantedJavaName); }); + connect(task.get(), &Task::failed, this, &AutoInstallJava::tryNextMajorJava); + } +} diff --git a/launcher/minecraft/launch/AutoInstallJava.h b/launcher/minecraft/launch/AutoInstallJava.h new file mode 100644 index 0000000000..4dcd3796af --- /dev/null +++ b/launcher/minecraft/launch/AutoInstallJava.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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 +#include +#include "java/JavaMetadata.h" +#include "meta/Version.h" +#include "minecraft/MinecraftInstance.h" +#include "tasks/Task.h" + +class AutoInstallJava : public LaunchStep { + Q_OBJECT + + public: + explicit AutoInstallJava(LaunchTask* parent); + ~AutoInstallJava() override = default; + + void executeTask() override; + bool canAbort() const override { return m_current_task ? m_current_task->canAbort() : false; } + + protected: + void setJavaPath(QString path); + void setJavaPathFromPartial(); + void downloadJava(Meta::Version::Ptr version, QString javaName); + void tryNextMajorJava(); + + private: + MinecraftInstancePtr m_instance; + Task::Ptr m_current_task; + + qsizetype m_majorJavaVersionIndex = 0; + const QString m_supported_arch; +}; diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp index 2188112902..279545fed5 100644 --- a/launcher/minecraft/launch/VerifyJavaInstall.cpp +++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp @@ -50,7 +50,6 @@ void VerifyJavaInstall::executeTask() auto settings = instance->settings(); auto storedVersion = settings->get("JavaVersion").toString(); auto ignoreCompatibility = settings->get("IgnoreJavaCompatibility").toBool(); - auto automaticJavaSwitch = settings->get("AutomaticJavaSwitch").toBool(); auto compatibleMajors = packProfile->getProfile()->getCompatibleJavaMajors(); @@ -67,38 +66,16 @@ void VerifyJavaInstall::executeTask() return; } - auto logFail = [this, &javaVersion, compatibleMajors] { - emit logLine(tr("This instance is not compatible with Java version %1.\n" - "Please switch to one of the following Java versions for this instance:") - .arg(javaVersion.major()), - MessageLevel::Error); - for (auto major : compatibleMajors) { - emit logLine(tr("Java version %1").arg(major), MessageLevel::Error); - } - emit logLine(tr("Go to instance Java settings to change your Java version or disable the Java compatibility check if you know what " - "you're doing."), - MessageLevel::Error); - - emitFailed(QString("Incompatible Java major version")); - }; - - if (automaticJavaSwitch || true) { - settings->set("OverrideJava", true); - auto javas = APPLICATION->javalist().get(); - auto task = javas->getLoadTask(); - connect(task.get(), &Task::finished, this, [this, javas, compatibleMajors, settings, &logFail] { - for (auto i = 0; i < javas->count(); i++) { - auto java = std::dynamic_pointer_cast(javas->at(i)); - if (java && compatibleMajors.contains(java->id.major())) { - settings->set("OverrideJavaLocation", true); - settings->set("JavaPath", java->path); - emitSucceeded(); - return; - } - } - logFail(); - }); - } else { - logFail(); + emit logLine(tr("This instance is not compatible with Java version %1.\n" + "Please switch to one of the following Java versions for this instance:") + .arg(javaVersion.major()), + MessageLevel::Error); + for (auto major : compatibleMajors) { + emit logLine(tr("Java version %1").arg(major), MessageLevel::Error); } + emit logLine(tr("Go to instance Java settings to change your Java version or disable the Java compatibility check if you know what " + "you're doing."), + MessageLevel::Error); + + emitFailed(QString("Incompatible Java major version")); } diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 9b2aa7637a..496cb69a70 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -102,6 +102,8 @@ void JavaPage::applySettings() s->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " ")); s->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked()); s->set("IgnoreJavaWizard", ui->skipJavaWizardCheckbox->isChecked()); + s->set("AutomaticJavaSwitch", ui->autodetectJavaCheckBox->isChecked()); + s->set("AutomaticJavaDownload", ui->autodownloadCheckBox->isChecked()); s->set("JavaExtraSearchPaths", m_extra_paths->stringList()); JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget()); } @@ -125,6 +127,8 @@ void JavaPage::loadSettings() ui->jvmArgsTextBox->setPlainText(s->get("JvmArgs").toString()); ui->skipCompatibilityCheckbox->setChecked(s->get("IgnoreJavaCompatibility").toBool()); ui->skipJavaWizardCheckbox->setChecked(s->get("IgnoreJavaWizard").toBool()); + ui->autodetectJavaCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool()); + ui->autodownloadCheckBox->setChecked(s->get("AutomaticJavaDownload").toBool()); m_extra_paths = new QStringListModel(s->get("JavaExtraSearchPaths").toStringList()); ui->extraJavaPathsList->setModel(m_extra_paths); } diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index a518b0b149..d135adc9a1 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -160,25 +160,6 @@ Java Runtime - - - - true - - - - 0 - 0 - - - - - 16777215 - 100 - - - - @@ -232,7 +213,7 @@
    - + @@ -248,6 +229,42 @@ + + + + If enabled, the launcher will not prompt you to choose a Java version if one isn't found. + + + Skip Java &Wizard + + + + + + + true + + + + 0 + 0 + + + + + 16777215 + 100 + + + + + + + + Autodetect Java version + + + @@ -284,13 +301,10 @@ - - - - If enabled, the launcher will not prompt you to choose a Java version if one isn't found. - + + - Skip Java &Wizard + Autodownload Mojang Java From ea2adf909dfe10c0adaf53ef2fabbd94ee7ae8cd Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 17 Feb 2024 11:14:32 +0200 Subject: [PATCH 0140/2054] Added progeess dialog to autodownload java Signed-off-by: Trial97 --- .../java/download/ArchiveDownloadTask.cpp | 5 +- .../java/download/ManifestDownloadTask.cpp | 10 +++- launcher/minecraft/launch/AutoInstallJava.cpp | 59 ++++++++++++++----- launcher/minecraft/launch/AutoInstallJava.h | 1 + 4 files changed, 56 insertions(+), 19 deletions(-) diff --git a/launcher/java/download/ArchiveDownloadTask.cpp b/launcher/java/download/ArchiveDownloadTask.cpp index 847f3f3649..9f8dc05438 100644 --- a/launcher/java/download/ArchiveDownloadTask.cpp +++ b/launcher/java/download/ArchiveDownloadTask.cpp @@ -50,9 +50,12 @@ void ArchiveDownloadTask::executeTask() auto fullPath = entry->getFullPath(); connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); - connect(download.get(), &NetJob::progress, this, &ArchiveDownloadTask::progress); connect(download.get(), &NetJob::failed, this, &ArchiveDownloadTask::emitFailed); connect(this, &Task::aborted, download.get(), &NetJob::abort); + connect(download.get(), &Task::progress, this, &ArchiveDownloadTask::setProgress); + connect(download.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress); + connect(download.get(), &Task::status, this, &ArchiveDownloadTask::setStatus); + connect(download.get(), &Task::details, this, &ArchiveDownloadTask::setDetails); connect(download.get(), &NetJob::succeeded, [this, fullPath] { // This should do all of the extracting and creating folders extractJava(fullPath); diff --git a/launcher/java/download/ManifestDownloadTask.cpp b/launcher/java/download/ManifestDownloadTask.cpp index 1a30715301..0c2eee0c85 100644 --- a/launcher/java/download/ManifestDownloadTask.cpp +++ b/launcher/java/download/ManifestDownloadTask.cpp @@ -52,9 +52,12 @@ void ManifestDownloadTask::executeTask() download->addNetAction(action); connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); - connect(download.get(), &NetJob::progress, this, &ManifestDownloadTask::progress); connect(download.get(), &NetJob::failed, this, &ManifestDownloadTask::emitFailed); connect(this, &Task::aborted, download.get(), &NetJob::abort); + connect(download.get(), &Task::progress, this, &ManifestDownloadTask::setProgress); + connect(download.get(), &Task::stepProgress, this, &ManifestDownloadTask::propagateStepProgress); + connect(download.get(), &Task::status, this, &ManifestDownloadTask::setStatus); + connect(download.get(), &Task::details, this, &ManifestDownloadTask::setDetails); connect(download.get(), &NetJob::succeeded, [files, this] { QJsonParseError parse_error{}; @@ -117,8 +120,11 @@ void ManifestDownloadTask::downloadJava(const QJsonDocument& doc) disconnect(this, &Task::aborted, elementDownload, &NetJob::abort); elementDownload->deleteLater(); }); - connect(elementDownload, &NetJob::progress, this, &ManifestDownloadTask::progress); connect(elementDownload, &NetJob::failed, this, &ManifestDownloadTask::emitFailed); + connect(elementDownload, &Task::progress, this, &ManifestDownloadTask::setProgress); + connect(elementDownload, &Task::stepProgress, this, &ManifestDownloadTask::propagateStepProgress); + connect(elementDownload, &Task::status, this, &ManifestDownloadTask::setStatus); + connect(elementDownload, &Task::details, this, &ManifestDownloadTask::setDetails); connect(this, &Task::aborted, elementDownload, &NetJob::abort); connect(elementDownload, &NetJob::succeeded, [this] { emitSucceeded(); }); diff --git a/launcher/minecraft/launch/AutoInstallJava.cpp b/launcher/minecraft/launch/AutoInstallJava.cpp index 0d3740c932..d93276432e 100644 --- a/launcher/minecraft/launch/AutoInstallJava.cpp +++ b/launcher/minecraft/launch/AutoInstallJava.cpp @@ -34,8 +34,8 @@ */ #include "AutoInstallJava.h" -#include -#include +#include +#include #include #include "Application.h" @@ -61,7 +61,8 @@ void AutoInstallJava::executeTask() { auto settings = m_instance->settings(); if (!APPLICATION->settings()->get("AutomaticJavaSwitch").toBool() || - (settings->get("OverrideJava").toBool() && settings->get("OverrideJavaLocation").toBool())) { + (settings->get("OverrideJava").toBool() && settings->get("OverrideJavaLocation").toBool() && + QFileInfo::exists(settings->get("JavaPath").toString()))) { emitSucceeded(); return; } @@ -80,6 +81,11 @@ void AutoInstallJava::executeTask() emit logLine(tr("No comptatible java version was found. Using the default one."), MessageLevel::Warning); emitSucceeded(); }); + connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress); + connect(m_current_task.get(), &Task::stepProgress, this, &AutoInstallJava::propagateStepProgress); + connect(m_current_task.get(), &Task::status, this, &AutoInstallJava::setStatus); + connect(m_current_task.get(), &Task::details, this, &AutoInstallJava::setDetails); + emit progressReportingRequest(); return; } auto wantedJavaName = packProfile->getProfile()->getCompatibleJavaName(); @@ -93,6 +99,11 @@ void AutoInstallJava::executeTask() m_current_task = versionList->getLoadTask(); connect(m_current_task.get(), &Task::succeeded, this, &AutoInstallJava::tryNextMajorJava); connect(m_current_task.get(), &Task::failed, this, &AutoInstallJava::emitFailed); + connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress); + connect(m_current_task.get(), &Task::stepProgress, this, &AutoInstallJava::propagateStepProgress); + connect(m_current_task.get(), &Task::status, this, &AutoInstallJava::setStatus); + connect(m_current_task.get(), &Task::details, this, &AutoInstallJava::setDetails); + emit progressReportingRequest(); } void AutoInstallJava::setJavaPath(QString path) @@ -134,29 +145,34 @@ void AutoInstallJava::downloadJava(Meta::Version::Ptr version, QString javaName) if (runtimes.contains(m_supported_arch)) { for (auto java : runtimes.value(m_supported_arch)) { if (java->name() == javaName) { - Task::Ptr task; QDir javaDir(APPLICATION->javaPath()); auto final_path = javaDir.absoluteFilePath(java->m_name); switch (java->downloadType) { case Java::DownloadType::Manifest: - task = makeShared(java->url, final_path, java->checksumType, java->checksumHash); + m_current_task = + makeShared(java->url, final_path, java->checksumType, java->checksumHash); break; case Java::DownloadType::Archive: - task = makeShared(java->url, final_path, java->checksumType, java->checksumHash); + m_current_task = + makeShared(java->url, final_path, java->checksumType, java->checksumHash); break; } - QEventLoop loop; auto deletePath = [final_path] { FS::deletePath(final_path); }; - connect(task.get(), &Task::failed, this, [this, deletePath](QString reason) { + connect(m_current_task.get(), &Task::failed, this, [this, deletePath](QString reason) { deletePath(); emitFailed(reason); }); - connect(this, &Task::aborted, this, [task, deletePath] { - task->abort(); + connect(this, &Task::aborted, this, [this, deletePath] { + m_current_task->abort(); deletePath(); }); - connect(task.get(), &Task::succeeded, this, &AutoInstallJava::setJavaPathFromPartial); - task->start(); + connect(m_current_task.get(), &Task::succeeded, this, &AutoInstallJava::setJavaPathFromPartial); + connect(m_current_task.get(), &Task::failed, this, &AutoInstallJava::tryNextMajorJava); + connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress); + connect(m_current_task.get(), &Task::stepProgress, this, &AutoInstallJava::propagateStepProgress); + connect(m_current_task.get(), &Task::status, this, &AutoInstallJava::setStatus); + connect(m_current_task.get(), &Task::details, this, &AutoInstallJava::setDetails); + m_current_task->start(); return; } } @@ -182,11 +198,22 @@ void AutoInstallJava::tryNextMajorJava() auto javaMajor = versionList->getVersion(QString("java%1").arg(majorJavaVersion)); javaMajor->load(Net::Mode::Online); - auto task = javaMajor->getCurrentTask(); - if (javaMajor->isLoaded() || !task) { + m_current_task = javaMajor->getCurrentTask(); + if (javaMajor->isLoaded() || !m_current_task) { downloadJava(javaMajor, wantedJavaName); } else { - connect(task.get(), &Task::succeeded, this, [this, javaMajor, wantedJavaName] { downloadJava(javaMajor, wantedJavaName); }); - connect(task.get(), &Task::failed, this, &AutoInstallJava::tryNextMajorJava); + connect(m_current_task.get(), &Task::succeeded, this, + [this, javaMajor, wantedJavaName] { downloadJava(javaMajor, wantedJavaName); }); + connect(m_current_task.get(), &Task::failed, this, &AutoInstallJava::tryNextMajorJava); + connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress); + connect(m_current_task.get(), &Task::stepProgress, this, &AutoInstallJava::propagateStepProgress); + connect(m_current_task.get(), &Task::status, this, &AutoInstallJava::setStatus); + connect(m_current_task.get(), &Task::details, this, &AutoInstallJava::setDetails); } } +bool AutoInstallJava::abort() +{ + if (m_current_task && m_current_task->canAbort()) + return m_current_task->abort(); + return true; +} diff --git a/launcher/minecraft/launch/AutoInstallJava.h b/launcher/minecraft/launch/AutoInstallJava.h index 4dcd3796af..45d0f870e1 100644 --- a/launcher/minecraft/launch/AutoInstallJava.h +++ b/launcher/minecraft/launch/AutoInstallJava.h @@ -51,6 +51,7 @@ class AutoInstallJava : public LaunchStep { void executeTask() override; bool canAbort() const override { return m_current_task ? m_current_task->canAbort() : false; } + bool abort() override; protected: void setJavaPath(QString path); From 5232b3cd89ce70722865bc1ad6d96d8f52db1789 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 17 Feb 2024 12:17:00 +0200 Subject: [PATCH 0141/2054] Added some logs and fixed natives extraction Signed-off-by: Trial97 --- launcher/minecraft/MinecraftInstance.cpp | 11 ++++++----- launcher/minecraft/launch/AutoInstallJava.cpp | 5 ++++- launcher/minecraft/launch/VerifyJavaInstall.cpp | 11 +++++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 00ce405aa8..cfc65428cb 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1078,6 +1078,12 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt process->appendStep(makeShared(pptr, Net::Mode::Offline)); } + // check java + { + process->appendStep(makeShared(pptr)); + process->appendStep(makeShared(pptr)); + } + // if there are any jar mods { process->appendStep(makeShared(pptr)); @@ -1102,11 +1108,6 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt { process->appendStep(makeShared(pptr)); } - // check java - { - process->appendStep(makeShared(pptr)); - process->appendStep(makeShared(pptr)); - } // verify that minimum Java requirements are met { diff --git a/launcher/minecraft/launch/AutoInstallJava.cpp b/launcher/minecraft/launch/AutoInstallJava.cpp index d93276432e..7905689da8 100644 --- a/launcher/minecraft/launch/AutoInstallJava.cpp +++ b/launcher/minecraft/launch/AutoInstallJava.cpp @@ -68,12 +68,15 @@ void AutoInstallJava::executeTask() } auto packProfile = m_instance->getPackProfile(); if (!APPLICATION->settings()->get("AutomaticJavaDownload").toBool()) { - auto javas = APPLICATION->javalist().get(); + auto javas = APPLICATION->javalist(); m_current_task = javas->getLoadTask(); connect(m_current_task.get(), &Task::finished, this, [this, javas, packProfile] { for (auto i = 0; i < javas->count(); i++) { auto java = std::dynamic_pointer_cast(javas->at(i)); if (java && packProfile->getProfile()->getCompatibleJavaMajors().contains(java->id.major())) { + if (!java->is_64bit) { + emit logLine(tr("The automatic Java mechanism detected a x32 java."), MessageLevel::Info); + } setJavaPath(java->path); return; } diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp index 279545fed5..536384f126 100644 --- a/launcher/minecraft/launch/VerifyJavaInstall.cpp +++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp @@ -37,6 +37,7 @@ #include #include "Application.h" +#include "MessageLevel.h" #include "java/JavaInstall.h" #include "java/JavaInstallList.h" #include "java/JavaVersion.h" @@ -50,6 +51,16 @@ void VerifyJavaInstall::executeTask() auto settings = instance->settings(); auto storedVersion = settings->get("JavaVersion").toString(); auto ignoreCompatibility = settings->get("IgnoreJavaCompatibility").toBool(); + auto javaArchitecture = settings->get("JavaArchitecture").toString(); + auto maxMemAlloc = settings->get("MaxMemAlloc").toInt(); + + emit logLine(tr("Java architecture is x%1.").arg(javaArchitecture), MessageLevel::Info); + if (javaArchitecture == "32" && maxMemAlloc > 2048) { + emit logLine(tr("Max memory allocation exceeds the supported value.\n" + "The selected java is 32 bit and doesn't support more than 2GB of ram.\n" + "The instance may not start due to this."), + MessageLevel::Error); + } auto compatibleMajors = packProfile->getProfile()->getCompatibleJavaMajors(); From a7dad96a701100ab583b4c9ca00a7c9b979fcf84 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 18 Feb 2024 09:32:31 +0200 Subject: [PATCH 0142/2054] Added refresh button on managed java list Signed-off-by: Trial97 --- launcher/ui/pages/global/JavaPage.cpp | 4 ++++ launcher/ui/pages/global/JavaPage.h | 1 + launcher/ui/pages/global/JavaPage.ui | 7 +++++++ 3 files changed, 12 insertions(+) diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 496cb69a70..e967204c8d 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -296,3 +296,7 @@ void JavaPage::on_removeJavaButton_clicked() } } } +void JavaPage::on_refreshJavaButton_clicked() +{ + ui->managedJavaList->loadList(); +} diff --git a/launcher/ui/pages/global/JavaPage.h b/launcher/ui/pages/global/JavaPage.h index 03d14b8246..1a521e2db9 100644 --- a/launcher/ui/pages/global/JavaPage.h +++ b/launcher/ui/pages/global/JavaPage.h @@ -77,6 +77,7 @@ class JavaPage : public QWidget, public BasePage { void on_removeExtraPathButton_clicked(); void on_downloadJavaButton_clicked(); void on_removeJavaButton_clicked(); + void on_refreshJavaButton_clicked(); void on_maxMemSpinBox_valueChanged(int i); void checkerFinished(); diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index d135adc9a1..80be81fb4d 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -376,6 +376,13 @@ + + + + Refresh + + + From 4c76f7afe0a1ea015d7c0eac7a1a6b1c8a1a760a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 18 Feb 2024 09:46:44 +0200 Subject: [PATCH 0143/2054] Made auto java checkbox dependent Signed-off-by: Trial97 --- launcher/ui/pages/global/JavaPage.cpp | 8 +++++++- launcher/ui/pages/global/JavaPage.ui | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index e967204c8d..0e97211d96 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -41,6 +41,7 @@ #include "ui/java/JavaDownloader.h" #include "ui_JavaPage.h" +#include #include #include #include @@ -65,6 +66,11 @@ JavaPage::JavaPage(QWidget* parent) : QWidget(parent), ui(new Ui::JavaPage) ui->managedJavaList->selectCurrent(); ui->managedJavaList->setEmptyString(tr("No java versions are currently available in the meta")); ui->managedJavaList->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); + connect(ui->autodetectJavaCheckBox, &QCheckBox::stateChanged, this, [this] { + ui->autodownloadCheckBox->setEnabled(ui->autodetectJavaCheckBox->isChecked()); + if (!ui->autodetectJavaCheckBox->isChecked()) + ui->autodownloadCheckBox->setChecked(false); + }); loadSettings(); updateThresholds(); @@ -128,7 +134,7 @@ void JavaPage::loadSettings() ui->skipCompatibilityCheckbox->setChecked(s->get("IgnoreJavaCompatibility").toBool()); ui->skipJavaWizardCheckbox->setChecked(s->get("IgnoreJavaWizard").toBool()); ui->autodetectJavaCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool()); - ui->autodownloadCheckBox->setChecked(s->get("AutomaticJavaDownload").toBool()); + ui->autodownloadCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool() && s->get("AutomaticJavaDownload").toBool()); m_extra_paths = new QStringListModel(s->get("JavaExtraSearchPaths").toStringList()); ui->extraJavaPathsList->setModel(m_extra_paths); } diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index 80be81fb4d..43843017c4 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -303,6 +303,9 @@ + + false + Autodownload Mojang Java From 2f489d1aec120274b3e98187bf8326c8990bca12 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 18 Feb 2024 11:34:54 +0200 Subject: [PATCH 0144/2054] Added auto java options to the java wizzard page Signed-off-by: Trial97 --- launcher/ui/setupwizard/JavaWizardPage.cpp | 2 ++ launcher/ui/widgets/JavaSettingsWidget.cpp | 38 ++++++++++++++++++++++ launcher/ui/widgets/JavaSettingsWidget.h | 8 +++++ 3 files changed, 48 insertions(+) diff --git a/launcher/ui/setupwizard/JavaWizardPage.cpp b/launcher/ui/setupwizard/JavaWizardPage.cpp index abe4860da4..d537580fcd 100644 --- a/launcher/ui/setupwizard/JavaWizardPage.cpp +++ b/launcher/ui/setupwizard/JavaWizardPage.cpp @@ -57,6 +57,8 @@ bool JavaWizardPage::validatePage() { auto settings = APPLICATION->settings(); auto result = m_java_widget->validate(); + settings->set("AutomaticJavaSwitch", m_java_widget->autodetectJava()); + settings->set("AutomaticJavaDownload", m_java_widget->autodownloadJava()); switch (result) { default: case JavaSettingsWidget::ValidationStatus::Bad: { diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 507c632d53..c15f7bf429 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -136,6 +136,26 @@ void JavaSettingsWidget::setupUi() m_verticalLayout->addLayout(m_horizontalBtnLayout); + m_autoJavaGroupBox = new QGroupBox(this); + m_autoJavaGroupBox->setObjectName(QStringLiteral("autoJavaGroupBox")); + m_veriticalJavaLayout = new QVBoxLayout(m_autoJavaGroupBox); + m_veriticalJavaLayout->setObjectName(QStringLiteral("veriticalJavaLayout")); + + m_autodetectJavaCheckBox = new QCheckBox(m_autoJavaGroupBox); + m_autodetectJavaCheckBox->setObjectName("autodetectJavaCheckBox"); + m_veriticalJavaLayout->addWidget(m_autodetectJavaCheckBox); + + m_autodownloadCheckBox = new QCheckBox(m_autoJavaGroupBox); + m_autodownloadCheckBox->setObjectName("autodownloadCheckBox"); + m_autodownloadCheckBox->setEnabled(false); + m_veriticalJavaLayout->addWidget(m_autodownloadCheckBox); + connect(m_autodetectJavaCheckBox, &QCheckBox::stateChanged, this, [this] { + m_autodownloadCheckBox->setEnabled(m_autodetectJavaCheckBox->isChecked()); + if (!m_autodetectJavaCheckBox->isChecked()) + m_autodownloadCheckBox->setChecked(false); + }); + m_verticalLayout->addWidget(m_autoJavaGroupBox); + retranslate(); } @@ -153,6 +173,9 @@ void JavaSettingsWidget::initialize() m_maxMemSpinBox->setValue(observedMaxMemory); m_permGenSpinBox->setValue(observedPermGenMemory); updateThresholds(); + + m_autodetectJavaCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool()); + m_autodownloadCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool() && s->get("AutomaticJavaDownload").toBool()); } void JavaSettingsWidget::refresh() @@ -280,11 +303,13 @@ void JavaSettingsWidget::on_javaBrowseBtn_clicked() m_javaPathTextBox->setText(cooked_path); checkJavaPath(cooked_path); } + void JavaSettingsWidget::on_javaDownloadBtn_clicked() { auto jdialog = new Java::Downloader(this); jdialog->exec(); } + void JavaSettingsWidget::on_javaStatusBtn_clicked() { QString text; @@ -418,6 +443,9 @@ void JavaSettingsWidget::retranslate() m_minMemSpinBox->setToolTip(tr("The amount of memory Minecraft is started with.")); m_permGenSpinBox->setToolTip(tr("The amount of memory available to store loaded Java classes.")); m_javaBrowseBtn->setText(tr("Browse")); + m_autodownloadCheckBox->setText(tr("Autodownload Mojang Java")); + m_autodetectJavaCheckBox->setText(tr("Autodetect Java version")); + m_autoJavaGroupBox->setTitle(tr("Autodetect Java")); } void JavaSettingsWidget::updateThresholds() @@ -464,3 +492,13 @@ void JavaSettingsWidget::on_addJavaPathBtn_clicked() APPLICATION->settings()->set("JavaExtraSearchPaths", currentList); refresh(); } + +bool JavaSettingsWidget::autodownloadJava() const +{ + return m_autodetectJavaCheckBox->isChecked(); +} + +bool JavaSettingsWidget::autodetectJava() const +{ + return m_autodownloadCheckBox->isChecked(); +} diff --git a/launcher/ui/widgets/JavaSettingsWidget.h b/launcher/ui/widgets/JavaSettingsWidget.h index 385a9256a3..205b83d8c7 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.h +++ b/launcher/ui/widgets/JavaSettingsWidget.h @@ -4,6 +4,7 @@ #include #include #include +#include #include class QLineEdit; @@ -41,6 +42,8 @@ class JavaSettingsWidget : public QWidget { int minHeapSize() const; int maxHeapSize() const; QString javaPath() const; + bool autodetectJava() const; + bool autodownloadJava() const; void updateThresholds(); @@ -86,6 +89,11 @@ class JavaSettingsWidget : public QWidget { QIcon yellowIcon; QIcon badIcon; + QGroupBox* m_autoJavaGroupBox = nullptr; + QVBoxLayout* m_veriticalJavaLayout = nullptr; + QCheckBox* m_autodetectJavaCheckBox = nullptr; + QCheckBox* m_autodownloadCheckBox = nullptr; + unsigned int observedMinMemory = 0; unsigned int observedMaxMemory = 0; unsigned int observedPermGenMemory = 0; From 4aafa98852ec1f89cc765def26ab9e4174b8af3b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 18 Feb 2024 12:58:34 +0200 Subject: [PATCH 0145/2054] Improved the message boxes for java wizzard Signed-off-by: Trial97 --- launcher/ui/widgets/JavaSettingsWidget.cpp | 71 ++++++++++++++++++---- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index c15f7bf429..66278abb0f 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -11,6 +12,7 @@ #include +#include "DesktopServices.h" #include "FileSystem.h" #include "JavaCommon.h" #include "java/JavaChecker.h" @@ -174,8 +176,19 @@ void JavaSettingsWidget::initialize() m_permGenSpinBox->setValue(observedPermGenMemory); updateThresholds(); - m_autodetectJavaCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool()); - m_autodownloadCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool() && s->get("AutomaticJavaDownload").toBool()); + auto button = CustomMessageBox::selectable(this, tr("Auto Java Download"), + tr("%1 has now the ability to auto downloand the correct java for each minecraft version.\n" + "Do you want to enable java auto-download?\n") + .arg(BuildConfig.LAUNCHER_DISPLAYNAME), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) + ->exec(); + if (button == QMessageBox::Yes) { + m_autodetectJavaCheckBox->setChecked(true); + m_autodownloadCheckBox->setChecked(true); + } else { + m_autodetectJavaCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool()); + m_autodownloadCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool() && s->get("AutomaticJavaDownload").toBool()); + } } void JavaSettingsWidget::refresh() @@ -192,20 +205,52 @@ JavaSettingsWidget::ValidationStatus JavaSettingsWidget::validate() switch (javaStatus) { default: case JavaStatus::NotSet: + /* fallthrough */ case JavaStatus::DoesNotExist: + /* fallthrough */ case JavaStatus::DoesNotStart: + /* fallthrough */ case JavaStatus::ReturnedInvalidData: { - int button = CustomMessageBox::selectable(this, tr("No Java version selected"), - tr("You didn't select a Java version or selected something that doesn't work.\n" - "%1 will not be able to start Minecraft.\n" - "Do you wish to proceed without any Java?" - "\n\n" - "You can change the Java version in the settings later.\n") - .arg(BuildConfig.LAUNCHER_DISPLAYNAME), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::NoButton) - ->exec(); - if (button == QMessageBox::No) { - return ValidationStatus::Bad; + if (!m_autodownloadCheckBox->isChecked()) { // the java will not be autodownloaded + int button = QMessageBox::No; + if (m_result.mojangPlatform == "32" && maxHeapSize() > 2048) { + button = CustomMessageBox::selectable( + this, tr("Java x32 detected"), + tr("You selected a 32 bit java, but allocated more than 2048MiB as maximum memory.\n" + "%1 will not be able to start Minecraft.\n" + "Do you wish to proceed?" + "\n\n" + "You can change the Java version in the settings later.\n") + .arg(BuildConfig.LAUNCHER_DISPLAYNAME), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No | QMessageBox::Help, QMessageBox::NoButton) + ->exec(); + + } else { + button = CustomMessageBox::selectable(this, tr("No Java version selected"), + tr("You didn't select a Java version or selected something that doesn't work.\n" + "%1 will not be able to start Minecraft.\n" + "Do you wish to proceed without any Java?" + "\n\n" + "You can change the Java version in the settings later.\n") + .arg(BuildConfig.LAUNCHER_DISPLAYNAME), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No | QMessageBox::Help, + QMessageBox::NoButton) + ->exec(); + } + switch (button) { + case QMessageBox::Yes: + return ValidationStatus::JavaBad; + case QMessageBox::Help: + DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("java-wizzard"))); + /* fallthrough */ + case QMessageBox::No: + /* fallthrough */ + default: + return ValidationStatus::Bad; + } + if (button == QMessageBox::No) { + return ValidationStatus::Bad; + } } return ValidationStatus::JavaBad; } break; From b4f3a969b3fd2f96675d25e3c8726c76395b3d7e Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 18 Feb 2024 14:30:38 +0200 Subject: [PATCH 0146/2054] Updated strings that are displayed to user Signed-off-by: Trial97 --- launcher/MMCZip.cpp | 2 +- launcher/MMCZip.h | 2 +- launcher/java/JavaInstall.cpp | 2 +- launcher/java/JavaInstall.h | 2 +- launcher/java/JavaMetadata.cpp | 2 +- launcher/java/JavaMetadata.h | 2 +- launcher/java/download/ArchiveDownloadTask.cpp | 2 +- launcher/java/download/ArchiveDownloadTask.h | 2 +- launcher/java/download/ManifestDownloadTask.cpp | 2 +- launcher/java/download/ManifestDownloadTask.h | 2 +- launcher/minecraft/launch/AutoInstallJava.cpp | 10 +++++----- launcher/minecraft/launch/AutoInstallJava.h | 2 +- launcher/minecraft/launch/VerifyJavaInstall.cpp | 2 +- launcher/ui/java/JavaDownloader.cpp | 2 +- launcher/ui/pages/global/JavaPage.cpp | 2 +- launcher/ui/pages/global/JavaPage.ui | 2 +- launcher/ui/pages/instance/InstanceSettingsPage.cpp | 2 +- launcher/ui/pages/instance/InstanceSettingsPage.ui | 3 --- launcher/ui/widgets/JavaSettingsWidget.cpp | 6 +++--- 19 files changed, 24 insertions(+), 27 deletions(-) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 9e26c5eda5..8273acbb68 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -2,7 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (c) 2023 Trial97 + * Copyright (c) 2023-2024 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index db6a7421a8..e2987badd5 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -2,7 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (c) 2023 Trial97 + * Copyright (c) 2023-2024 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/java/JavaInstall.cpp b/launcher/java/JavaInstall.cpp index cfa471402f..8e97e0e144 100644 --- a/launcher/java/JavaInstall.cpp +++ b/launcher/java/JavaInstall.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023 Trial97 + * Copyright (c) 2023-2024 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/java/JavaInstall.h b/launcher/java/JavaInstall.h index 6890264f33..7d8d392fab 100644 --- a/launcher/java/JavaInstall.h +++ b/launcher/java/JavaInstall.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023 Trial97 + * Copyright (c) 2023-2024 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/java/JavaMetadata.cpp b/launcher/java/JavaMetadata.cpp index b261122dc0..b73c54fe6c 100644 --- a/launcher/java/JavaMetadata.cpp +++ b/launcher/java/JavaMetadata.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023 Trial97 + * Copyright (c) 2023-2024 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/java/JavaMetadata.h b/launcher/java/JavaMetadata.h index dd3ae865c4..640a412f11 100644 --- a/launcher/java/JavaMetadata.h +++ b/launcher/java/JavaMetadata.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023 Trial97 + * Copyright (c) 2023-2024 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/java/download/ArchiveDownloadTask.cpp b/launcher/java/download/ArchiveDownloadTask.cpp index 9f8dc05438..e3db77d2cc 100644 --- a/launcher/java/download/ArchiveDownloadTask.cpp +++ b/launcher/java/download/ArchiveDownloadTask.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023 Trial97 + * Copyright (c) 2023-2024 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/java/download/ArchiveDownloadTask.h b/launcher/java/download/ArchiveDownloadTask.h index 88c0223d5a..c656567fe1 100644 --- a/launcher/java/download/ArchiveDownloadTask.h +++ b/launcher/java/download/ArchiveDownloadTask.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023 Trial97 + * Copyright (c) 2023-2024 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/java/download/ManifestDownloadTask.cpp b/launcher/java/download/ManifestDownloadTask.cpp index 0c2eee0c85..ba5ff2cc05 100644 --- a/launcher/java/download/ManifestDownloadTask.cpp +++ b/launcher/java/download/ManifestDownloadTask.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023 Trial97 + * Copyright (c) 2023-2024 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/java/download/ManifestDownloadTask.h b/launcher/java/download/ManifestDownloadTask.h index dcfee4d4da..f0eaf95a61 100644 --- a/launcher/java/download/ManifestDownloadTask.h +++ b/launcher/java/download/ManifestDownloadTask.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023 Trial97 + * Copyright (c) 2023-2024 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/minecraft/launch/AutoInstallJava.cpp b/launcher/minecraft/launch/AutoInstallJava.cpp index 7905689da8..ab68910a77 100644 --- a/launcher/minecraft/launch/AutoInstallJava.cpp +++ b/launcher/minecraft/launch/AutoInstallJava.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2023-2024 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -75,13 +75,13 @@ void AutoInstallJava::executeTask() auto java = std::dynamic_pointer_cast(javas->at(i)); if (java && packProfile->getProfile()->getCompatibleJavaMajors().contains(java->id.major())) { if (!java->is_64bit) { - emit logLine(tr("The automatic Java mechanism detected a x32 java."), MessageLevel::Info); + emit logLine(tr("The automatic Java mechanism detected an x32 java."), MessageLevel::Info); } setJavaPath(java->path); return; } } - emit logLine(tr("No comptatible java version was found. Using the default one."), MessageLevel::Warning); + emit logLine(tr("No compatible java version was found. Using the default one."), MessageLevel::Warning); emitSucceeded(); }); connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress); @@ -136,7 +136,7 @@ void AutoInstallJava::setJavaPathFromPartial() if (QFileInfo::exists(finalPath)) { setJavaPath(finalPath); } else { - emit logLine(tr("No comptatible java version was found. Using the default one."), MessageLevel::Warning); + emit logLine(tr("No compatible java version was found. Using the default one."), MessageLevel::Warning); emitSucceeded(); } return; @@ -192,7 +192,7 @@ void AutoInstallJava::tryNextMajorJava() auto wantedJavaName = packProfile->getProfile()->getCompatibleJavaName(); auto majorJavaVersions = packProfile->getProfile()->getCompatibleJavaMajors(); if (m_majorJavaVersionIndex >= majorJavaVersions.length()) { - emit logLine(tr("No comptatible java version was found. Using the default one."), MessageLevel::Warning); + emit logLine(tr("No compatible java version was found. Using the default one."), MessageLevel::Warning); emitSucceeded(); return; } diff --git a/launcher/minecraft/launch/AutoInstallJava.h b/launcher/minecraft/launch/AutoInstallJava.h index 45d0f870e1..7e4efc50cf 100644 --- a/launcher/minecraft/launch/AutoInstallJava.h +++ b/launcher/minecraft/launch/AutoInstallJava.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2023-2024 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp index 536384f126..f26b850ebd 100644 --- a/launcher/minecraft/launch/VerifyJavaInstall.cpp +++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp @@ -57,7 +57,7 @@ void VerifyJavaInstall::executeTask() emit logLine(tr("Java architecture is x%1.").arg(javaArchitecture), MessageLevel::Info); if (javaArchitecture == "32" && maxMemAlloc > 2048) { emit logLine(tr("Max memory allocation exceeds the supported value.\n" - "The selected java is 32 bit and doesn't support more than 2GB of ram.\n" + "The selected java is 32-bit and doesn't support more than 2048MiB of RAM.\n" "The instance may not start due to this."), MessageLevel::Error); } diff --git a/launcher/ui/java/JavaDownloader.cpp b/launcher/ui/java/JavaDownloader.cpp index 4495210c37..7417a4ea8c 100644 --- a/launcher/ui/java/JavaDownloader.cpp +++ b/launcher/ui/java/JavaDownloader.cpp @@ -49,7 +49,7 @@ Downloader::Downloader(QWidget* parent) : QDialog(parent), ui(new Ui::JavaDownlo versionList->setProvidedRoles({ BaseVersionList::VersionRole, BaseVersionList::RecommendedRole, BaseVersionList::VersionPointerRole }); ui->majorVersionSelect->initialize(versionList.get()); ui->majorVersionSelect->selectCurrent(); - ui->majorVersionSelect->setEmptyString(tr("No java versions are currently available in the meta")); + ui->majorVersionSelect->setEmptyString(tr("No java versions are currently available in the meta.")); ui->majorVersionSelect->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); ui->javaVersionSelect->setEmptyString(tr("No java versions are currently available for your OS.")); diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 0e97211d96..524165e62c 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -158,7 +158,7 @@ void JavaPage::on_javaDetectBtn_clicked() if (!java->is_64bit && APPLICATION->settings()->get("MaxMemAlloc").toInt() > 2048) { CustomMessageBox::selectable(this, tr("Confirm Selection"), tr("You selected an x86 java version.\n" - "This means that will not support more than 2Gb(2048Mb) of ram.\n" + "This means that will not support more than 2048MiB of RAM.\n" "Please make sure that the maximum memory value is lower."), QMessageBox::Warning, QMessageBox::Ok, QMessageBox::Ok) ->exec(); diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index 43843017c4..3f763f8d29 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -307,7 +307,7 @@ false - Autodownload Mojang Java + Auto-download Mojang Java diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 926e48952d..8a63aab22e 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -417,7 +417,7 @@ void InstanceSettingsPage::on_javaDetectBtn_clicked() if (!java->is_64bit && m_settings->get("MaxMemAlloc").toInt() > 2048) { CustomMessageBox::selectable(this, tr("Confirm Selection"), tr("You selected an x86 java version.\n" - "This means that will not support more than 2Gb(2048Mb) of ram.\n" + "This means that will not support more than 2048MiB of RAM.\n" "Please make sure that the maximum memory value is lower."), QMessageBox::Warning, QMessageBox::Ok, QMessageBox::Ok) ->exec(); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index 7762ca8a7e..94464064ee 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -617,9 +617,6 @@ Enable online fixes (experimental) - - Disable Quilt Loader Beacon - diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 66278abb0f..91fd6ff03e 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -216,7 +216,7 @@ JavaSettingsWidget::ValidationStatus JavaSettingsWidget::validate() if (m_result.mojangPlatform == "32" && maxHeapSize() > 2048) { button = CustomMessageBox::selectable( this, tr("Java x32 detected"), - tr("You selected a 32 bit java, but allocated more than 2048MiB as maximum memory.\n" + tr("You selected an 32-bit java, but allocated more than 2048MiB as maximum memory.\n" "%1 will not be able to start Minecraft.\n" "Do you wish to proceed?" "\n\n" @@ -488,7 +488,7 @@ void JavaSettingsWidget::retranslate() m_minMemSpinBox->setToolTip(tr("The amount of memory Minecraft is started with.")); m_permGenSpinBox->setToolTip(tr("The amount of memory available to store loaded Java classes.")); m_javaBrowseBtn->setText(tr("Browse")); - m_autodownloadCheckBox->setText(tr("Autodownload Mojang Java")); + m_autodownloadCheckBox->setText(tr("Auto-download Mojang Java")); m_autodetectJavaCheckBox->setText(tr("Autodetect Java version")); m_autoJavaGroupBox->setTitle(tr("Autodetect Java")); } @@ -509,7 +509,7 @@ void JavaSettingsWidget::updateThresholds() } else if (observedMaxMemory > 2048 && m_result.is_64bit) { iconName = "status-bad"; m_labelMaxMemIcon->setToolTip( - tr("Your maximum memory allocation exceeds selected java posible memory(due to x86 applicatiion limitations).")); + tr("Your maximum memory allocation exceeds selected java possible memory(due to x86 application limitations).")); } else { iconName = "status-good"; m_labelMaxMemIcon->setToolTip(""); From 038d3e4596586718ca6982c68a17c36723561a42 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 18 Feb 2024 16:49:10 +0200 Subject: [PATCH 0147/2054] Fixed asan complains Signed-off-by: Trial97 --- launcher/java/download/ArchiveDownloadTask.cpp | 5 ++++- launcher/java/download/ManifestDownloadTask.cpp | 5 ++++- launcher/ui/pages/global/JavaPage.cpp | 3 ++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/launcher/java/download/ArchiveDownloadTask.cpp b/launcher/java/download/ArchiveDownloadTask.cpp index e3db77d2cc..34f60260ed 100644 --- a/launcher/java/download/ArchiveDownloadTask.cpp +++ b/launcher/java/download/ArchiveDownloadTask.cpp @@ -49,7 +49,10 @@ void ArchiveDownloadTask::executeTask() download->addNetAction(action); auto fullPath = entry->getFullPath(); - connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); + connect(download.get(), &NetJob::finished, [download, this] { + disconnect(this, &Task::aborted, download.get(), &NetJob::abort); + download->deleteLater(); + }); connect(download.get(), &NetJob::failed, this, &ArchiveDownloadTask::emitFailed); connect(this, &Task::aborted, download.get(), &NetJob::abort); connect(download.get(), &Task::progress, this, &ArchiveDownloadTask::setProgress); diff --git a/launcher/java/download/ManifestDownloadTask.cpp b/launcher/java/download/ManifestDownloadTask.cpp index ba5ff2cc05..5551c75d56 100644 --- a/launcher/java/download/ManifestDownloadTask.cpp +++ b/launcher/java/download/ManifestDownloadTask.cpp @@ -51,7 +51,10 @@ void ManifestDownloadTask::executeTask() } download->addNetAction(action); - connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); + connect(download.get(), &NetJob::finished, [download, this] { + disconnect(this, &Task::aborted, download.get(), &NetJob::abort); + download->deleteLater(); + }); connect(download.get(), &NetJob::failed, this, &ManifestDownloadTask::emitFailed); connect(this, &Task::aborted, download.get(), &NetJob::abort); connect(download.get(), &Task::progress, this, &ManifestDownloadTask::setProgress); diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 524165e62c..deb085f93f 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -135,7 +135,7 @@ void JavaPage::loadSettings() ui->skipJavaWizardCheckbox->setChecked(s->get("IgnoreJavaWizard").toBool()); ui->autodetectJavaCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool()); ui->autodownloadCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool() && s->get("AutomaticJavaDownload").toBool()); - m_extra_paths = new QStringListModel(s->get("JavaExtraSearchPaths").toStringList()); + m_extra_paths = new QStringListModel(s->get("JavaExtraSearchPaths").toStringList(), this); ui->extraJavaPathsList->setModel(m_extra_paths); } @@ -199,6 +199,7 @@ void JavaPage::on_javaDownloadBtn_clicked() { auto jdialog = new Java::Downloader(this); jdialog->exec(); + ui->managedJavaList->loadList(); } void JavaPage::on_maxMemSpinBox_valueChanged([[maybe_unused]] int i) From 889f604a415fd01150868090a1d47b7c408104d5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 18 Feb 2024 21:38:31 +0200 Subject: [PATCH 0148/2054] Removed JavaExtraSearchPaths Signed-off-by: Trial97 --- launcher/Application.cpp | 1 - launcher/java/JavaUtils.cpp | 37 ------------------- launcher/java/JavaUtils.h | 1 - launcher/ui/pages/global/JavaPage.cpp | 27 -------------- launcher/ui/pages/global/JavaPage.h | 3 -- launcher/ui/pages/global/JavaPage.ui | 43 ---------------------- launcher/ui/widgets/JavaSettingsWidget.cpp | 19 ---------- launcher/ui/widgets/JavaSettingsWidget.h | 2 - 8 files changed, 133 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 6b7333972f..31950a8135 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -632,7 +632,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("JvmArgs", ""); m_settings->registerSetting("IgnoreJavaCompatibility", false); m_settings->registerSetting("IgnoreJavaWizard", false); - m_settings->registerSetting("JavaExtraSearchPaths", QStringList()); m_settings->registerSetting("AutomaticJavaSwitch", false); m_settings->registerSetting("AutomaticJavaDownload", false); diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 1d254e4057..57e381e1dd 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -338,7 +338,6 @@ QList JavaUtils::FindJavaPaths() candidates.append(getMinecraftJavaBundle()); candidates.append(getPrismJavaBundle()); - candidates.append(getPrismExtraJavaPaths()); candidates = addJavasFromEnv(candidates); candidates.removeDuplicates(); return candidates; @@ -366,7 +365,6 @@ QList JavaUtils::FindJavaPaths() } javas.append(getMinecraftJavaBundle()); javas.append(getPrismJavaBundle()); - javas.append(getPrismExtraJavaPaths()); javas = addJavasFromEnv(javas); javas.removeDuplicates(); return javas; @@ -420,7 +418,6 @@ QList JavaUtils::FindJavaPaths() javas.append(getMinecraftJavaBundle()); javas.append(getPrismJavaBundle()); - javas.append(getPrismExtraJavaPaths()); javas = addJavasFromEnv(javas); javas.removeDuplicates(); return javas; @@ -435,7 +432,6 @@ QList JavaUtils::FindJavaPaths() javas.append(getMinecraftJavaBundle()); javas.append(getPrismJavaBundle()); - javas.append(getPrismExtraJavaPaths()); javas.removeDuplicates(); return addJavasFromEnv(javas); } @@ -521,36 +517,3 @@ QStringList getPrismJavaBundle() return javas; } - -QStringList getPrismExtraJavaPaths() -{ - QList javas; - - QString executable = "java"; -#if defined(Q_OS_WIN32) - executable += "w.exe"; -#endif - - auto scanDir = [&](QString prefix) { - javas.append(FS::PathCombine(prefix, "jre", "bin", executable)); - javas.append(FS::PathCombine(prefix, "bin", executable)); - javas.append(FS::PathCombine(prefix, executable)); - }; - auto scanJavaDir = [&](const QString& dirPath) { - QDir dir(dirPath); - if (!dir.exists()) - return; - auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); - for (auto& entry : entries) { - scanDir(entry.canonicalFilePath()); - } - }; - - auto extra_paths = APPLICATION->settings()->get("JavaExtraSearchPaths").toStringList(); - for (auto& entry : extra_paths) { - scanDir(entry); - scanJavaDir(entry); - } - - return javas; -} diff --git a/launcher/java/JavaUtils.h b/launcher/java/JavaUtils.h index 66e4398340..aa5315a194 100644 --- a/launcher/java/JavaUtils.h +++ b/launcher/java/JavaUtils.h @@ -27,7 +27,6 @@ QString stripVariableEntries(QString name, QString target, QString remove); QProcessEnvironment CleanEnviroment(); QStringList getMinecraftJavaBundle(); QStringList getPrismJavaBundle(); -QStringList getPrismExtraJavaPaths(); class JavaUtils : public QObject { Q_OBJECT diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index deb085f93f..cb2a8b90b5 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -110,7 +110,6 @@ void JavaPage::applySettings() s->set("IgnoreJavaWizard", ui->skipJavaWizardCheckbox->isChecked()); s->set("AutomaticJavaSwitch", ui->autodetectJavaCheckBox->isChecked()); s->set("AutomaticJavaDownload", ui->autodownloadCheckBox->isChecked()); - s->set("JavaExtraSearchPaths", m_extra_paths->stringList()); JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget()); } void JavaPage::loadSettings() @@ -135,8 +134,6 @@ void JavaPage::loadSettings() ui->skipJavaWizardCheckbox->setChecked(s->get("IgnoreJavaWizard").toBool()); ui->autodetectJavaCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool()); ui->autodownloadCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool() && s->get("AutomaticJavaDownload").toBool()); - m_extra_paths = new QStringListModel(s->get("JavaExtraSearchPaths").toStringList(), this); - ui->extraJavaPathsList->setModel(m_extra_paths); } void JavaPage::on_javaDetectBtn_clicked() @@ -247,30 +244,6 @@ void JavaPage::updateThresholds() } } -void JavaPage::on_addExtraPathButton_clicked() -{ - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Add Extra Java Folder")); - - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { - QString cooked_dir = FS::NormalizePath(raw_dir); - auto currentList = m_extra_paths->stringList(); - if (!currentList.contains(cooked_dir)) { - currentList << cooked_dir; - m_extra_paths->setStringList(currentList); - } - } - APPLICATION->settings()->set("JavaExtraSearchPaths", m_extra_paths->stringList()); -} - -void JavaPage::on_removeExtraPathButton_clicked() -{ - auto indexes = ui->extraJavaPathsList->selectionModel()->selectedIndexes(); - if (indexes.size()) { - m_extra_paths->removeRow(indexes.first().row()); - } - APPLICATION->settings()->set("JavaExtraSearchPaths", m_extra_paths->stringList()); -} - void JavaPage::on_downloadJavaButton_clicked() { on_javaDownloadBtn_clicked(); diff --git a/launcher/ui/pages/global/JavaPage.h b/launcher/ui/pages/global/JavaPage.h index 1a521e2db9..48e5577408 100644 --- a/launcher/ui/pages/global/JavaPage.h +++ b/launcher/ui/pages/global/JavaPage.h @@ -73,8 +73,6 @@ class JavaPage : public QWidget, public BasePage { void on_javaTestBtn_clicked(); void on_javaBrowseBtn_clicked(); void on_javaDownloadBtn_clicked(); - void on_addExtraPathButton_clicked(); - void on_removeExtraPathButton_clicked(); void on_downloadJavaButton_clicked(); void on_removeJavaButton_clicked(); void on_refreshJavaButton_clicked(); @@ -84,5 +82,4 @@ class JavaPage : public QWidget, public BasePage { private: Ui::JavaPage* ui; unique_qobject_ptr checker; - QStringListModel* m_extra_paths; }; diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index 3f763f8d29..dc1668ee3d 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -391,49 +391,6 @@ - - - - Java extra paths - - - - - - - - - - - Add - - - - - - - Remove - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 91fd6ff03e..4b58192881 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -44,7 +44,6 @@ JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent) connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaSettingsWidget::javaPathEdited); connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaSettingsWidget::on_javaStatusBtn_clicked); connect(m_javaDownloadBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaDownloadBtn_clicked); - connect(m_addJavaPathBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_addJavaPathBtn_clicked); } void JavaSettingsWidget::setupUi() @@ -133,9 +132,6 @@ void JavaSettingsWidget::setupUi() m_javaDownloadBtn = new QPushButton(tr("Download Java"), this); m_horizontalBtnLayout->addWidget(m_javaDownloadBtn); - m_addJavaPathBtn = new QPushButton(tr("Add extra Java path"), this); - m_horizontalBtnLayout->addWidget(m_addJavaPathBtn); - m_verticalLayout->addLayout(m_horizontalBtnLayout); m_autoJavaGroupBox = new QGroupBox(this); @@ -523,21 +519,6 @@ void JavaSettingsWidget::updateThresholds() } } -void JavaSettingsWidget::on_addJavaPathBtn_clicked() -{ - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Add Extra Java Folder")); - - auto currentList = APPLICATION->settings()->get("JavaExtraSearchPaths").toStringList(); - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { - QString cooked_dir = FS::NormalizePath(raw_dir); - if (!currentList.contains(cooked_dir)) { - currentList << cooked_dir; - } - } - APPLICATION->settings()->set("JavaExtraSearchPaths", currentList); - refresh(); -} - bool JavaSettingsWidget::autodownloadJava() const { return m_autodetectJavaCheckBox->isChecked(); diff --git a/launcher/ui/widgets/JavaSettingsWidget.h b/launcher/ui/widgets/JavaSettingsWidget.h index 205b83d8c7..c8b6953df6 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.h +++ b/launcher/ui/widgets/JavaSettingsWidget.h @@ -54,7 +54,6 @@ class JavaSettingsWidget : public QWidget { void on_javaBrowseBtn_clicked(); void on_javaStatusBtn_clicked(); void on_javaDownloadBtn_clicked(); - void on_addJavaPathBtn_clicked(); void checkFinished(const JavaChecker::Result& result); protected: /* methods */ @@ -84,7 +83,6 @@ class JavaSettingsWidget : public QWidget { QHBoxLayout* m_horizontalBtnLayout = nullptr; QPushButton* m_javaDownloadBtn = nullptr; - QPushButton* m_addJavaPathBtn = nullptr; QIcon goodIcon; QIcon yellowIcon; QIcon badIcon; From 1c96ae58077cae7a8d5b8f851d5ede385811f6ea Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 18 Feb 2024 22:32:54 +0200 Subject: [PATCH 0149/2054] Fixed java refresh button Signed-off-by: Trial97 --- launcher/ui/java/JavaDownloader.cpp | 3 ++- launcher/ui/java/JavaDownloader.ui | 10 +++++----- launcher/ui/java/VersionList.cpp | 4 +++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/launcher/ui/java/JavaDownloader.cpp b/launcher/ui/java/JavaDownloader.cpp index 7417a4ea8c..24918dd763 100644 --- a/launcher/ui/java/JavaDownloader.cpp +++ b/launcher/ui/java/JavaDownloader.cpp @@ -55,7 +55,7 @@ Downloader::Downloader(QWidget* parent) : QDialog(parent), ui(new Ui::JavaDownlo ui->javaVersionSelect->setEmptyString(tr("No java versions are currently available for your OS.")); ui->javaVersionSelect->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); - ui->buttonBox->button(QDialogButtonBox::Retry)->setText(tr("Refresh")); + ui->buttonBox->button(QDialogButtonBox::Reset)->setText(tr("Refresh")); ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Download")); connect(ui->majorVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &Downloader::setSelectedVersion); @@ -98,6 +98,7 @@ void Downloader::accept() connect(task.get(), &Task::failed, this, deletePath); connect(task.get(), &Task::aborted, this, deletePath); ProgressDialog pg(this); + pg.setSkipButton(true, tr("Abort")); pg.execWithTask(task.get()); QDialog::accept(); } diff --git a/launcher/ui/java/JavaDownloader.ui b/launcher/ui/java/JavaDownloader.ui index 0eeabb26db..3f6a8adc6d 100644 --- a/launcher/ui/java/JavaDownloader.ui +++ b/launcher/ui/java/JavaDownloader.ui @@ -48,7 +48,7 @@ Qt::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Retry + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset @@ -71,8 +71,8 @@ accept() - 248 - 254 + 257 + 583 157 @@ -87,8 +87,8 @@ reject() - 316 - 260 + 325 + 583 286 diff --git a/launcher/ui/java/VersionList.cpp b/launcher/ui/java/VersionList.cpp index 7c5668d206..4bf04224a2 100644 --- a/launcher/ui/java/VersionList.cpp +++ b/launcher/ui/java/VersionList.cpp @@ -104,10 +104,12 @@ bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right) void VersionList::sortVersions() { + if (!m_version || !m_version->data()) + return; QString versionStr = SysInfo::getSupportedJavaArchitecture(); beginResetModel(); auto runtimes = m_version->data()->runtimes; - if (!versionStr.isEmpty() && runtimes.contains(versionStr)) { + if (!versionStr.isEmpty() && !runtimes.isEmpty() && runtimes.contains(versionStr)) { m_vlist = runtimes.value(versionStr); std::sort(m_vlist.begin(), m_vlist.end(), sortJavas); } else { From 344398f2382b879689aef7d4d02785faf0d9bff8 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 26 Feb 2024 21:20:14 +0200 Subject: [PATCH 0150/2054] Updated ftb app import instance detection Signed-off-by: Trial97 --- .../modplatform/import_ftb/ImportFTBPage.cpp | 10 +- .../modplatform/import_ftb/ListModel.cpp | 92 ++++++++++++------- .../pages/modplatform/import_ftb/ListModel.h | 16 ++-- 3 files changed, 77 insertions(+), 41 deletions(-) diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp index ac06f4cdda..c72a5a9da6 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp @@ -21,6 +21,7 @@ #include "ui_ImportFTBPage.h" #include +#include #include #include "FileSystem.h" #include "ListModel.h" @@ -58,7 +59,14 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch); connect(ui->browseButton, &QPushButton::clicked, this, [this] { - auto path = listModel->getPath(); + auto paths = listModel->getPosiblePaths(); + QString path; + for (auto p : paths) { + if (p != "" && QFileInfo::exists(p)) { + path = p; + break; + } + } QString dir = QFileDialog::getExistingDirectory(this, tr("Select FTBApp instances directory"), path, QFileDialog::ShowDirsOnly); if (!dir.isEmpty()) listModel->setPath(dir); diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index e058937a61..ad03d571b2 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -24,7 +24,9 @@ #include #include #include "Application.h" +#include "Exception.h" #include "FileSystem.h" +#include "Json.h" #include "StringUtils.h" #include "modplatform/import_ftb/PackHelpers.h" #include "ui/widgets/ProjectItem.h" @@ -41,27 +43,54 @@ QString getStaticPath() #else partialPath = QDir::homePath(); #endif - return FS::PathCombine(partialPath, ".ftba"); + return FS::PathCombine(partialPath, ".ftba", "instances"); } -static const QString FTB_APP_PATH = FS::PathCombine(getStaticPath(), "instances"); +QString getDynamicPath() +{ + auto settingsPath = FS::PathCombine(QDir::homePath(), ".ftba", "bin", "settings.json"); + if (!QFileInfo::exists(settingsPath)) { + qWarning() << "The ftb app setings doesn't exist."; + return {}; + } + try { + auto doc = Json::requireDocument(FS::read(settingsPath)); + return Json::requireString(Json::requireObject(doc), "instanceLocation"); + } catch (const Exception& e) { + qCritical() << "Could not read ftb settings file: " << e.cause(); + } + return {}; +} + +ListModel::ListModel(QObject* parent) : QAbstractListModel(parent), m_static_path(getStaticPath()), m_dynamic_path(getDynamicPath()) {} void ListModel::update() { beginResetModel(); - modpacks.clear(); - - QString instancesPath = getPath(); - if (auto instancesInfo = QFileInfo(instancesPath); instancesInfo.exists() && instancesInfo.isDir()) { - QDirIterator directoryIterator(instancesPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden, - QDirIterator::FollowSymlinks); - while (directoryIterator.hasNext()) { - auto modpack = parseDirectory(directoryIterator.next()); - if (!modpack.path.isEmpty()) - modpacks.append(modpack); + m_modpacks.clear(); + + auto paths = getPosiblePaths(); + paths.removeDuplicates(); + for (auto instancesPath : paths) { + if (auto instancesInfo = QFileInfo(instancesPath); instancesInfo.exists() && instancesInfo.isDir()) { + QDirIterator directoryIterator(instancesPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden, + QDirIterator::FollowSymlinks); + while (directoryIterator.hasNext()) { + auto currentPath = directoryIterator.next(); + bool wasAdded = false; + for (auto pack : m_modpacks) { + if (pack.path == currentPath) { + wasAdded = true; + break; + } + } + if (!wasAdded) { + auto modpack = parseDirectory(currentPath); + if (!modpack.path.isEmpty()) + m_modpacks.append(modpack); + } + } } - } else { - qDebug() << "Couldn't find ftb instances folder: " << instancesPath; } endResetModel(); @@ -70,11 +99,11 @@ void ListModel::update() QVariant ListModel::data(const QModelIndex& index, int role) const { int pos = index.row(); - if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { + if (pos >= m_modpacks.size() || pos < 0 || !index.isValid()) { return QVariant(); } - auto pack = modpacks.at(pos); + auto pack = m_modpacks.at(pos); if (role == Qt::ToolTipRole) { } @@ -110,9 +139,9 @@ QVariant ListModel::data(const QModelIndex& index, int role) const FilterModel::FilterModel(QObject* parent) : QSortFilterProxyModel(parent) { - currentSorting = Sorting::ByGameVersion; - sortings.insert(tr("Sort by Name"), Sorting::ByName); - sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion); + m_currentSorting = Sorting::ByGameVersion; + m_sortings.insert(tr("Sort by Name"), Sorting::ByName); + m_sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion); } bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const @@ -120,12 +149,12 @@ bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) co Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value(); Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value(); - if (currentSorting == Sorting::ByGameVersion) { + if (m_currentSorting == Sorting::ByGameVersion) { Version lv(leftPack.mcVersion); Version rv(rightPack.mcVersion); return lv < rv; - } else if (currentSorting == Sorting::ByName) { + } else if (m_currentSorting == Sorting::ByName) { return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; } @@ -136,39 +165,39 @@ bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) co bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const { - if (searchTerm.isEmpty()) { + if (m_searchTerm.isEmpty()) { return true; } QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); Modpack pack = sourceModel()->data(index, Qt::UserRole).value(); - return pack.name.contains(searchTerm, Qt::CaseInsensitive); + return pack.name.contains(m_searchTerm, Qt::CaseInsensitive); } void FilterModel::setSearchTerm(const QString term) { - searchTerm = term.trimmed(); + m_searchTerm = term.trimmed(); invalidate(); } const QMap FilterModel::getAvailableSortings() { - return sortings; + return m_sortings; } QString FilterModel::translateCurrentSorting() { - return sortings.key(currentSorting); + return m_sortings.key(m_currentSorting); } void FilterModel::setSorting(Sorting s) { - currentSorting = s; + m_currentSorting = s; invalidate(); } FilterModel::Sorting FilterModel::getCurrentSorting() { - return currentSorting; + return m_currentSorting; } void ListModel::setPath(QString path) { @@ -176,11 +205,8 @@ void ListModel::setPath(QString path) update(); } -QString ListModel::getPath() +QStringList ListModel::getPosiblePaths() { - auto path = APPLICATION->settings()->get("FTBAppInstancesPath").toString(); - if (path.isEmpty() || !QFileInfo(path).exists()) - path = FTB_APP_PATH; - return path; + return { APPLICATION->settings()->get("FTBAppInstancesPath").toString(), m_dynamic_path, m_static_path }; } } // namespace FTBImportAPP \ No newline at end of file diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.h b/launcher/ui/pages/modplatform/import_ftb/ListModel.h index ed33a88f39..393836b268 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.h +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.h @@ -42,28 +42,30 @@ class FilterModel : public QSortFilterProxyModel { bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; private: - QMap sortings; - Sorting currentSorting; - QString searchTerm; + QMap m_sortings; + Sorting m_currentSorting; + QString m_searchTerm; }; class ListModel : public QAbstractListModel { Q_OBJECT public: - ListModel(QObject* parent) : QAbstractListModel(parent) {} + ListModel(QObject* parent); virtual ~ListModel() = default; - int rowCount(const QModelIndex& parent) const { return modpacks.size(); } + int rowCount(const QModelIndex& parent) const { return m_modpacks.size(); } int columnCount(const QModelIndex& parent) const { return 1; } QVariant data(const QModelIndex& index, int role) const; void update(); - QString getPath(); + QStringList getPosiblePaths(); void setPath(QString path); private: - ModpackList modpacks; + ModpackList m_modpacks; + const QString m_static_path; + const QString m_dynamic_path; }; } // namespace FTBImportAPP \ No newline at end of file From da96172b0b8835df8591368e96d20464a3a4eb41 Mon Sep 17 00:00:00 2001 From: Alexandru Ionut Tripon Date: Mon, 26 Feb 2024 22:00:40 +0200 Subject: [PATCH 0151/2054] Apply suggestions from code review Co-authored-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Signed-off-by: Alexandru Ionut Tripon --- launcher/ui/java/VersionList.cpp | 2 +- launcher/ui/pages/global/JavaPage.cpp | 2 +- launcher/ui/pages/instance/InstanceSettingsPage.cpp | 2 +- launcher/ui/widgets/JavaSettingsWidget.cpp | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/launcher/ui/java/VersionList.cpp b/launcher/ui/java/VersionList.cpp index 4bf04224a2..dc454f2645 100644 --- a/launcher/ui/java/VersionList.cpp +++ b/launcher/ui/java/VersionList.cpp @@ -114,7 +114,7 @@ void VersionList::sortVersions() std::sort(m_vlist.begin(), m_vlist.end(), sortJavas); } else { m_vlist = {}; - qWarning() << "Your operating system is not yet supported: " << SysInfo::currentSystem() << " " << SysInfo::useQTForArch(); + qWarning() << "No Java versions found for your operating system." << SysInfo::currentSystem() << " " << SysInfo::useQTForArch(); } endResetModel(); } diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index cb2a8b90b5..95d5f581e1 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -154,7 +154,7 @@ void JavaPage::on_javaDetectBtn_clicked() ui->javaPathTextBox->setText(java->path); if (!java->is_64bit && APPLICATION->settings()->get("MaxMemAlloc").toInt() > 2048) { CustomMessageBox::selectable(this, tr("Confirm Selection"), - tr("You selected an x86 java version.\n" + tr("You selected a 32 bit java version.\n" "This means that will not support more than 2048MiB of RAM.\n" "Please make sure that the maximum memory value is lower."), QMessageBox::Warning, QMessageBox::Ok, QMessageBox::Ok) diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 8a63aab22e..50cffc9a7f 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -416,7 +416,7 @@ void InstanceSettingsPage::on_javaDetectBtn_clicked() if (!java->is_64bit && m_settings->get("MaxMemAlloc").toInt() > 2048) { CustomMessageBox::selectable(this, tr("Confirm Selection"), - tr("You selected an x86 java version.\n" + tr("You selected a 32 bit java version.\n" "This means that will not support more than 2048MiB of RAM.\n" "Please make sure that the maximum memory value is lower."), QMessageBox::Warning, QMessageBox::Ok, QMessageBox::Ok) diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 4b58192881..f8031687b2 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -173,8 +173,8 @@ void JavaSettingsWidget::initialize() updateThresholds(); auto button = CustomMessageBox::selectable(this, tr("Auto Java Download"), - tr("%1 has now the ability to auto downloand the correct java for each minecraft version.\n" - "Do you want to enable java auto-download?\n") + tr("%1 can automatically download the correct Java version for each version of Minecraft..\n" + "Do you want to enable Java auto-download?\n") .arg(BuildConfig.LAUNCHER_DISPLAYNAME), QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) ->exec(); @@ -505,7 +505,7 @@ void JavaSettingsWidget::updateThresholds() } else if (observedMaxMemory > 2048 && m_result.is_64bit) { iconName = "status-bad"; m_labelMaxMemIcon->setToolTip( - tr("Your maximum memory allocation exceeds selected java possible memory(due to x86 application limitations).")); + tr("Because you're using 32 bit Java, you're exceeding the maximum possible allocation.")); } else { iconName = "status-good"; m_labelMaxMemIcon->setToolTip(""); From 24fd07861b797fb845217c04b9ad5267edcc4470 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 26 Feb 2024 22:25:14 +0200 Subject: [PATCH 0152/2054] format code Signed-off-by: Trial97 --- launcher/ui/widgets/JavaSettingsWidget.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index f8031687b2..29357bdab2 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -504,8 +504,7 @@ void JavaSettingsWidget::updateThresholds() m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value")); } else if (observedMaxMemory > 2048 && m_result.is_64bit) { iconName = "status-bad"; - m_labelMaxMemIcon->setToolTip( - tr("Because you're using 32 bit Java, you're exceeding the maximum possible allocation.")); + m_labelMaxMemIcon->setToolTip(tr("Because you're using 32 bit Java, you're exceeding the maximum possible allocation.")); } else { iconName = "status-good"; m_labelMaxMemIcon->setToolTip(""); From 27e76d0dcb69d0f34fa64fca89964b231c194387 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 27 Feb 2024 17:11:19 +0200 Subject: [PATCH 0153/2054] Removed static path Signed-off-by: Trial97 --- .../modplatform/import_ftb/ImportFTBPage.cpp | 11 +-- .../modplatform/import_ftb/ListModel.cpp | 69 ++++++++++--------- .../pages/modplatform/import_ftb/ListModel.h | 5 +- 3 files changed, 40 insertions(+), 45 deletions(-) diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp index c72a5a9da6..db59fe10a3 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp @@ -59,15 +59,8 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch); connect(ui->browseButton, &QPushButton::clicked, this, [this] { - auto paths = listModel->getPosiblePaths(); - QString path; - for (auto p : paths) { - if (p != "" && QFileInfo::exists(p)) { - path = p; - break; - } - } - QString dir = QFileDialog::getExistingDirectory(this, tr("Select FTBApp instances directory"), path, QFileDialog::ShowDirsOnly); + QString dir = QFileDialog::getExistingDirectory(this, tr("Select FTBApp instances directory"), listModel->getUserPath(), + QFileDialog::ShowDirsOnly); if (!dir.isEmpty()) listModel->setPath(dir); }); diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index ad03d571b2..52671ea8eb 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -33,22 +33,18 @@ namespace FTBImportAPP { -QString getStaticPath() +QString getFTBRoot() { - QString partialPath; + QString partialPath = QDir::homePath(); #if defined(Q_OS_OSX) - partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support"); -#elif defined(Q_OS_WIN32) - partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", ""); -#else - partialPath = QDir::homePath(); + partialPath = FS::PathCombine(partialPath, "Library/Application Support"); #endif - return FS::PathCombine(partialPath, ".ftba", "instances"); + return FS::PathCombine(partialPath, ".ftba"); } QString getDynamicPath() { - auto settingsPath = FS::PathCombine(QDir::homePath(), ".ftba", "bin", "settings.json"); + auto settingsPath = FS::PathCombine(getFTBRoot(), "bin", "settings.json"); if (!QFileInfo::exists(settingsPath)) { qWarning() << "The ftb app setings doesn't exist."; return {}; @@ -62,36 +58,40 @@ QString getDynamicPath() return {}; } -ListModel::ListModel(QObject* parent) : QAbstractListModel(parent), m_static_path(getStaticPath()), m_dynamic_path(getDynamicPath()) {} +ListModel::ListModel(QObject* parent) : QAbstractListModel(parent), m_instances_path(getDynamicPath()) {} void ListModel::update() { beginResetModel(); m_modpacks.clear(); - auto paths = getPosiblePaths(); - paths.removeDuplicates(); - for (auto instancesPath : paths) { - if (auto instancesInfo = QFileInfo(instancesPath); instancesInfo.exists() && instancesInfo.isDir()) { - QDirIterator directoryIterator(instancesPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden, - QDirIterator::FollowSymlinks); - while (directoryIterator.hasNext()) { - auto currentPath = directoryIterator.next(); - bool wasAdded = false; - for (auto pack : m_modpacks) { - if (pack.path == currentPath) { - wasAdded = true; - break; - } - } - if (!wasAdded) { - auto modpack = parseDirectory(currentPath); - if (!modpack.path.isEmpty()) - m_modpacks.append(modpack); - } + auto wasPathAdded = [this](QString path) { + for (auto pack : m_modpacks) { + if (pack.path == path) + return true; + } + return false; + }; + + auto scanPath = [this, wasPathAdded](QString path) { + if (path.isEmpty()) + return; + if (auto instancesInfo = QFileInfo(path); !instancesInfo.exists() || !instancesInfo.isDir()) + return; + QDirIterator directoryIterator(path, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden, + QDirIterator::FollowSymlinks); + while (directoryIterator.hasNext()) { + auto currentPath = directoryIterator.next(); + if (!wasPathAdded(currentPath)) { + auto modpack = parseDirectory(currentPath); + if (!modpack.path.isEmpty()) + m_modpacks.append(modpack); } } - } + }; + + scanPath(APPLICATION->settings()->get("FTBAppInstancesPath").toString()); + scanPath(m_instances_path); endResetModel(); } @@ -205,8 +205,11 @@ void ListModel::setPath(QString path) update(); } -QStringList ListModel::getPosiblePaths() +QString ListModel::getUserPath() { - return { APPLICATION->settings()->get("FTBAppInstancesPath").toString(), m_dynamic_path, m_static_path }; + auto path = APPLICATION->settings()->get("FTBAppInstancesPath").toString(); + if (path.isEmpty()) + path = m_instances_path; + return path; } } // namespace FTBImportAPP \ No newline at end of file diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.h b/launcher/ui/pages/modplatform/import_ftb/ListModel.h index 393836b268..a842ac8ffa 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.h +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.h @@ -60,12 +60,11 @@ class ListModel : public QAbstractListModel { void update(); - QStringList getPosiblePaths(); + QString getUserPath(); void setPath(QString path); private: ModpackList m_modpacks; - const QString m_static_path; - const QString m_dynamic_path; + const QString m_instances_path; }; } // namespace FTBImportAPP \ No newline at end of file From 41d52dc34717aa2cad0537abb255a5a475bd4bca Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 29 Feb 2024 00:06:28 +0200 Subject: [PATCH 0154/2054] Remove dependencies if review mods is rejected Signed-off-by: Trial97 --- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 1431ea92ce..11833c4381 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -133,6 +133,7 @@ void ResourceDownloadDialog::confirm() confirm_dialog->retranslateUi(resourcesString()); QHash getRequiredBy; + QStringList depNames; if (auto task = getModDependenciesTask(); task) { connect(task.get(), &Task::failed, this, [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); @@ -155,8 +156,10 @@ void ResourceDownloadDialog::confirm() QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; } else { - for (auto dep : task->getDependecies()) + for (auto dep : task->getDependecies()) { addResource(dep->pack, dep->version); + depNames << dep->pack->name; + } getRequiredBy = task->getRequiredBy(); } } @@ -180,6 +183,9 @@ void ResourceDownloadDialog::confirm() } this->accept(); + } else { + for (auto name : depNames) + removeResource(name); } } From 27780cc7aeee5fd1a5bd535d721c642491cca192 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 6 Mar 2024 14:56:04 +0000 Subject: [PATCH 0155/2054] Add update UI for all resource types Signed-off-by: TheKodeToad --- launcher/LaunchController.cpp | 2 +- launcher/minecraft/mod/ResourceFolderModel.h | 2 +- .../minecraft/mod/ResourcePackFolderModel.cpp | 5 +- .../minecraft/mod/ShaderPackFolderModel.h | 2 + launcher/modplatform/ResourceAPI.h | 2 +- launcher/modplatform/packwiz/Packwiz.cpp | 28 ++-- launcher/modplatform/packwiz/Packwiz.h | 2 +- launcher/ui/dialogs/ResourceUpdateDialog.cpp | 16 +- launcher/ui/dialogs/ResourceUpdateDialog.h | 4 +- .../pages/instance/ExternalResourcesPage.ui | 46 +++--- launcher/ui/pages/instance/ModFolderPage.cpp | 153 ++++++------------ launcher/ui/pages/instance/ModFolderPage.h | 7 +- .../ui/pages/instance/ResourcePackPage.cpp | 128 ++++++++++++++- launcher/ui/pages/instance/ResourcePackPage.h | 9 +- launcher/ui/pages/instance/ShaderPackPage.cpp | 130 ++++++++++++++- launcher/ui/pages/instance/ShaderPackPage.h | 7 +- .../ui/pages/instance/TexturePackPage.cpp | 128 ++++++++++++++- launcher/ui/pages/instance/TexturePackPage.h | 7 +- 18 files changed, 496 insertions(+), 182 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index a30f99439b..ff8558ce72 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -316,7 +316,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/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 3de3c54763..e65c69b257 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -255,7 +255,7 @@ class ResourceFolderModel : public QAbstractListModel { protected: // Represents the relationship between a column's index (represented by the list index), and it's sorting key. // As such, the order in with they appear is very important! - QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE }; + QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::PROVIDER }; QStringList m_column_names = { "Enable", "Name", "Last Modified", "Provider" }; QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider") }; QList m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive }; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index b777dbafd9..ccbf80367c 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -51,8 +51,9 @@ ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* : ResourceFolderModel(dir, instance, is_indexed, create_dir, parent) { m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; + m_column_names_translated = + QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE, SortType::PROVIDER }; m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, QHeaderView::Interactive }; m_columnsHideable = { false, true, false, true, true, true }; diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h index c63def238d..cd01f6226c 100644 --- a/launcher/minecraft/mod/ShaderPackFolderModel.h +++ b/launcher/minecraft/mod/ShaderPackFolderModel.h @@ -20,4 +20,6 @@ class ShaderPackFolderModel : public ResourceFolderModel { { return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast(resource)); } + + RESOURCE_HELPERS(ShaderPack); }; diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index 2c7bec5d4d..7b787c6a0c 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -83,7 +83,7 @@ class ResourceAPI { struct VersionSearchArgs { ModPlatform::IndexedPack pack; - std::optional > mcVersions; + std::optional> mcVersions; std::optional loaders; VersionSearchArgs(VersionSearchArgs const&) = default; diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index c609e78fc5..6e1f507fdc 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -110,11 +110,14 @@ auto V1::createModFormat([[maybe_unused]] const QDir& index_dir, mod.hash = mod_version.hash; mod.provider = mod_pack.provider; - mod.version_number = mod_version.version_number; mod.file_id = mod_version.fileId; mod.project_id = mod_pack.addonId; mod.side = stringToSide(mod_pack.side); + mod.version_number = mod_version.version_number; + if (mod.version_number.isNull()) // on CurseForge, there is only a version name - not a versio + mod.version_number = mod_version.version; + return mod; } @@ -164,10 +167,9 @@ void V1::updateModIndex(const QDir& index_dir, Mod& mod) qCritical() << QString("Did not write file %1 because missing information!").arg(normalized_fname); return; } - update = toml::table{ - { "file-id", mod.file_id.toInt() }, - { "project-id", mod.project_id.toInt() }, - }; + update = toml::table{ { "file-id", mod.file_id.toInt() }, + { "project-id", mod.project_id.toInt() }, + { "x-prismlauncher-version-number", mod.version_number.toStdString() } }; break; case (ModPlatform::ResourceProvider::MODRINTH): if (mod.mod_id().toString().isEmpty() || mod.version().toString().isEmpty()) { @@ -177,6 +179,7 @@ void V1::updateModIndex(const QDir& index_dir, Mod& mod) update = toml::table{ { "mod-id", mod.mod_id().toString().toStdString() }, { "version", mod.version().toString().toStdString() }, + { "x-prismlauncher-version-number", mod.version_number.toStdString() }, }; break; } @@ -199,8 +202,7 @@ void V1::updateModIndex(const QDir& index_dir, Mod& mod) { "hash-format", mod.hash_format.toStdString() }, { "hash", mod.hash.toStdString() }, } }, - { "update", toml::table{ { ModPlatform::ProviderCapabilities::name(mod.provider), update }, - { "x-prismlauncher-version-number", mod.version_number.toStdString() } } } }; + { "update", toml::table{ { ModPlatform::ProviderCapabilities::name(mod.provider), update } } } }; std::stringstream ss; ss << tbl; in_stream << QString::fromStdString(ss.str()); @@ -295,23 +297,23 @@ auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod { // [update] info using Provider = ModPlatform::ResourceProvider; - auto update_table = table["update"].as_table(); - if (!update_table) { + auto update_table = table["update"]; + if (!update_table || !update_table.is_table()) { qCritical() << QString("No [update] section found on mod metadata!"); return {}; } - mod.version_number = stringEntry(*update_table, "x-prismlauncher-version-number"); - toml::table* mod_provider_table = nullptr; - if ((mod_provider_table = (*update_table)[ModPlatform::ProviderCapabilities::name(Provider::FLAME)].as_table())) { + if ((mod_provider_table = update_table[ModPlatform::ProviderCapabilities::name(Provider::FLAME)].as_table())) { mod.provider = Provider::FLAME; mod.file_id = intEntry(*mod_provider_table, "file-id"); mod.project_id = intEntry(*mod_provider_table, "project-id"); - } else if ((mod_provider_table = (*update_table)[ModPlatform::ProviderCapabilities::name(Provider::MODRINTH)].as_table())) { + mod.version_number = stringEntry(*mod_provider_table, "x-prismlauncher-version-number"); + } else if ((mod_provider_table = update_table[ModPlatform::ProviderCapabilities::name(Provider::MODRINTH)].as_table())) { mod.provider = Provider::MODRINTH; mod.mod_id() = stringEntry(*mod_provider_table, "mod-id"); mod.version() = stringEntry(*mod_provider_table, "version"); + mod.version_number = stringEntry(*mod_provider_table, "x-prismlauncher-version-number"); } else { qCritical() << QString("No mod provider on mod metadata!"); return {}; diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 07bb4248d1..19ef22dd82 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -52,9 +52,9 @@ class V1 { // [update] ModPlatform::ResourceProvider provider{}; - QString version_number{}; QVariant file_id{}; QVariant project_id{}; + QString version_number{}; public: // This is a totally heuristic, but should work for now. diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index 459ba7f232..6426c2bfd5 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -38,7 +38,8 @@ ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, BaseInstance* instance, const std::shared_ptr resource_model, QList& search_for, - bool includeDeps) + bool include_deps, + bool filter_loaders) : ReviewMessageBox(parent, tr("Confirm mods to update"), "") , m_parent(parent) , m_resource_model(resource_model) @@ -46,7 +47,8 @@ ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, , m_second_try_metadata( new ConcurrentTask(nullptr, "Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())) , m_instance(instance) - , m_include_deps(includeDeps) + , m_include_deps(include_deps) + , m_filter_loaders(filter_loaders) { ReviewMessageBox::setGeometry(0, 0, 800, 600); @@ -85,7 +87,7 @@ void ResourceUpdateDialog::checkCandidates() } auto versions = mcVersions(m_instance); - auto loaders = mcLoaders(m_instance); + auto loaders = m_filter_loaders ? mcLoaders(m_instance) : std::optional(); SequentialTask check_task(m_parent, tr("Checking for updates")); @@ -224,10 +226,10 @@ void ResourceUpdateDialog::checkCandidates() if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); auto download_task = makeShared(dep->pack, dep->version, m_resource_model); - CheckUpdateTask::Update updatable = { - dep->pack->name, dep->version.hash, tr("Not installed"), dep->version.version, dep->version.version_type, - changelog, dep->pack->provider, download_task - }; + CheckUpdateTask::Update updatable = { dep->pack->name, dep->version.hash, + tr("Not installed"), dep->version.version, + dep->version.version_type, changelog, + dep->pack->provider, download_task }; appendResource(updatable, getRequiredBy.value(dep->version.addonId.toString())); m_tasks.insert(updatable.name, updatable.download); diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.h b/launcher/ui/dialogs/ResourceUpdateDialog.h index 31aab8f5db..de1d845d20 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.h +++ b/launcher/ui/dialogs/ResourceUpdateDialog.h @@ -20,7 +20,8 @@ class ResourceUpdateDialog final : public ReviewMessageBox { BaseInstance* instance, std::shared_ptr resource_model, QList& search_for, - bool includeDeps); + bool include_deps, + bool filter_loaders); void checkCandidates(); @@ -63,4 +64,5 @@ class ResourceUpdateDialog final : public ReviewMessageBox { bool m_no_updates = false; bool m_aborted = false; bool m_include_deps = false; + bool m_filter_loaders = false; }; diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui index ff08e12d25..2b4a47b9d1 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.ui +++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui @@ -70,15 +70,15 @@ - - true - Actions Qt::ToolButtonTextOnly + + true + RightToolBarArea @@ -95,10 +95,10 @@ - &Add + &Add File - Add + Add a locally downloaded file. @@ -106,7 +106,7 @@ &Remove - Remove selected item + Remove all selected items. @@ -114,7 +114,7 @@ &Enable - Enable selected item + Enable all selected items. @@ -122,7 +122,7 @@ &Disable - Disable selected item + Disable all selected items. @@ -137,6 +137,9 @@ View &Folder + + Open the folder in the system file manager. + @@ -146,29 +149,34 @@ &Download - Download a new resource + Download resources from online mod platforms. - + - false + true - Visit mod's page + Check for &Updates - Go to mods home page + Try to check or update all selected resources (all resources if none are selected). - - - true + + + Reset Update Metadata + + + QAction::NoRole + + - Check for &Updates + Verify Dependencies - - Try to check or update all selected resources (all resources if none are selected) + + QAction::NoRole diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index e513cb1fad..5b10a38dfb 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -68,88 +68,37 @@ #include "tasks/ConcurrentTask.h" #include "ui/dialogs/ProgressDialog.h" -ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent) - : ExternalResourcesPage(inst, mods, parent), m_model(mods) +ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr model, QWidget* parent) + : ExternalResourcesPage(inst, model, parent), m_model(model) { - // This is structured like that so that these changes - // do not affect the Resource pack and Shader pack tabs - { - ui->actionDownloadItem->setText(tr("Download mods")); - ui->actionDownloadItem->setToolTip(tr("Download mods from online mod platforms")); - ui->actionDownloadItem->setEnabled(true); - ui->actionAddItem->setText(tr("Add file")); - ui->actionAddItem->setToolTip(tr("Add a locally downloaded file")); - - ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); - - connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::installMods); - - // update menu - auto updateMenu = ui->actionUpdateItem->menu(); - if (updateMenu) { - updateMenu->clear(); - } else { - updateMenu = new QMenu(this); - } - - auto update = updateMenu->addAction(tr("Check for Updates")); - update->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)")); - connect(update, &QAction::triggered, this, &ModFolderPage::updateMods); - - auto updateWithDeps = updateMenu->addAction(tr("Verify Dependencies")); - updateWithDeps->setToolTip( - tr("Try to update and check for missing dependencies all selected mods (all mods if none are selected)")); - connect(updateWithDeps, &QAction::triggered, this, [this] { updateMods(true); }); - - auto depsDisabled = APPLICATION->settings()->getSetting("ModDependenciesDisabled"); - updateWithDeps->setVisible(!depsDisabled->get().toBool()); - connect(depsDisabled.get(), &Setting::SettingChanged, this, - [updateWithDeps](const Setting& setting, QVariant value) { updateWithDeps->setVisible(!value.toBool()); }); - - auto actionRemoveItemMetadata = updateMenu->addAction(tr("Reset update metadata")); - actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata")); - connect(actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata); - actionRemoveItemMetadata->setEnabled(false); - - ui->actionUpdateItem->setMenu(updateMenu); - - ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)")); - connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); - ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); + ui->actionDownloadItem->setText(tr("Download Mods")); + ui->actionDownloadItem->setToolTip(tr("Download mods from online mod platforms")); + ui->actionDownloadItem->setEnabled(true); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); - ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page")); - ui->actionsToolbar->addAction(ui->actionVisitItemPage); - connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages); + connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::downloadMods); - auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); }; + ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)")); + connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, - [this, check_allow_update, actionRemoveItemMetadata] { - ui->actionUpdateItem->setEnabled(check_allow_update()); + auto updateMenu = new QMenu(this); - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedMods(selection); - auto selected = std::count_if(mods_list.cbegin(), mods_list.cend(), - [](Mod* v) { return v->metadata() != nullptr || v->homeurl().size() != 0; }); - if (selected <= 1) { - ui->actionVisitItemPage->setText(tr("Visit mod's page")); - ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page")); + auto update = updateMenu->addAction(tr("Check for Updates")); + connect(update, &QAction::triggered, this, &ModFolderPage::updateMods); - } else { - ui->actionVisitItemPage->setText(tr("Visit mods' pages")); - ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods")); - } - ui->actionVisitItemPage->setEnabled(selected != 0); - actionRemoveItemMetadata->setEnabled(selected != 0); - }); + updateMenu->addAction(ui->actionVerifyItemDependencies); + connect(ui->actionVerifyItemDependencies, &QAction::triggered, this, [this] { updateMods(true); }); - auto updateButtons = [this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); }; - connect(mods.get(), &ModFolderModel::rowsInserted, this, updateButtons); + auto depsDisabled = APPLICATION->settings()->getSetting("ModDependenciesDisabled"); + ui->actionVerifyItemDependencies->setVisible(!depsDisabled->get().toBool()); + connect(depsDisabled.get(), &Setting::SettingChanged, this, + [this](const Setting& setting, const QVariant& value) { ui->actionVerifyItemDependencies->setVisible(!value.toBool()); }); - connect(mods.get(), &ModFolderModel::rowsRemoved, this, updateButtons); + updateMenu->addAction(ui->actionResetItemMetadata); + connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata); - connect(mods.get(), &ModFolderModel::updateFinished, this, updateButtons); - } + ui->actionUpdateItem->setMenu(updateMenu); } bool ModFolderPage::shouldDisplay() const @@ -182,7 +131,7 @@ void ModFolderPage::removeItems(const QItemSelection& selection) m_model->deleteResources(selection.indexes()); } -void ModFolderPage::installMods() +void ModFolderPage::downloadMods() { if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance @@ -195,7 +144,7 @@ void ModFolderPage::installMods() ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance); if (mdownload.exec()) { - auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + auto tasks = new ConcurrentTask(this, tr("Download Mods"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); @@ -257,7 +206,7 @@ void ModFolderPage::updateMods(bool includeDeps) if (use_all) mods_list = m_model->allResources(); - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps); + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps, true); update_dialog.checkCandidates(); if (update_dialog.aborted()) { @@ -307,6 +256,27 @@ void ModFolderPage::updateMods(bool includeDeps) } } +void ModFolderPage::deleteModMetadata() +{ + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto selectionCount = m_model->selectedMods(selection).length(); + if (selectionCount == 0) + return; + if (selectionCount > 1) { + auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), + tr("You are about to remove the metadata for %1 mods.\n" + "Are you sure?") + .arg(selectionCount), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + + m_model->deleteMetadata(selection); +} + CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent) : ModFolderPage(inst, mods, parent) {} @@ -340,34 +310,3 @@ bool NilModFolderPage::shouldDisplay() const { return m_model->dir().exists(); } - -void ModFolderPage::visitModPages() -{ - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - for (auto mod : m_model->selectedMods(selection)) { - auto url = mod->metaurl(); - if (!url.isEmpty()) - DesktopServices::openUrl(url); - } -} - -void ModFolderPage::deleteModMetadata() -{ - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto selectionCount = m_model->selectedMods(selection).length(); - if (selectionCount == 0) - return; - if (selectionCount > 1) { - auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), - tr("You are about to remove the metadata for %1 mods.\n" - "Are you sure?") - .arg(selectionCount), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) - ->exec(); - - if (response != QMessageBox::Yes) - return; - } - - m_model->deleteMetadata(selection); -} diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 4672350c66..455db33cb4 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -44,7 +44,7 @@ class ModFolderPage : public ExternalResourcesPage { Q_OBJECT public: - explicit ModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent = nullptr); + explicit ModFolderPage(BaseInstance* inst, std::shared_ptr model, QWidget* parent = nullptr); virtual ~ModFolderPage() = default; void setFilter(const QString& filter) { m_fileSelectionFilter = filter; } @@ -61,11 +61,10 @@ class ModFolderPage : public ExternalResourcesPage { private slots: void removeItems(const QItemSelection& selection) override; - void deleteModMetadata(); - void installMods(); + void downloadMods(); void updateMods(bool includeDeps = false); - void visitModPages(); + void deleteModMetadata(); protected: std::shared_ptr m_model; diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index 85be642563..e51ebafac7 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -42,17 +42,31 @@ #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h" +#include "ui/dialogs/ResourceUpdateDialog.h" ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) - : ExternalResourcesPage(instance, model, parent) + : ExternalResourcesPage(instance, model, parent), m_model(model) { - ui->actionDownloadItem->setText(tr("Download packs")); - ui->actionDownloadItem->setToolTip(tr("Download resource packs from online platforms")); + ui->actionDownloadItem->setText(tr("Download Packs")); + ui->actionDownloadItem->setToolTip(tr("Download resource packs from online mod platforms")); ui->actionDownloadItem->setEnabled(true); - connect(ui->actionDownloadItem, &QAction::triggered, this, &ResourcePackPage::downloadRPs); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); - ui->actionViewConfigs->setVisible(false); + connect(ui->actionDownloadItem, &QAction::triggered, this, &ResourcePackPage::downloadResourcePacks); + + ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected resource packs (all resource packs if none are selected)")); + connect(ui->actionUpdateItem, &QAction::triggered, this, &ResourcePackPage::updateResourcePacks); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); + + auto updateMenu = new QMenu(this); + + auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + connect(update, &QAction::triggered, this, &ResourcePackPage::updateResourcePacks); + + updateMenu->addAction(ui->actionResetItemMetadata); + connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ResourcePackPage::deleteResourcePackMetadata); + + ui->actionUpdateItem->setMenu(updateMenu); } bool ResourcePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) @@ -65,12 +79,12 @@ bool ResourcePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_un return true; } -void ResourcePackPage::downloadRPs() +void ResourcePackPage::downloadResourcePacks() { if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - ResourceDownload::ResourcePackDownloadDialog mdownload(this, std::static_pointer_cast(m_model), m_instance); + ResourceDownload::ResourcePackDownloadDialog mdownload(this, m_model, m_instance); if (mdownload.exec()) { auto tasks = new ConcurrentTask(this, "Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); @@ -101,3 +115,103 @@ void ResourcePackPage::downloadRPs() m_model->update(); } } + +void ResourcePackPage::updateResourcePacks() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + auto profile = static_cast(m_instance)->getPackProfile(); + if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + QMessageBox::critical(this, tr("Error"), tr("Resource pack updates are unavailable when metadata is disabled!")); + return; + } + if (m_instance != nullptr && m_instance->isRunning()) { + auto response = + CustomMessageBox::selectable(this, tr("Confirm Update"), + tr("Updating resource packs while the game is running may cause pack duplication and game crashes.\n" + "The old files may not be deleted as they are in use.\n" + "Are you sure you want to do this?"), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + + auto mods_list = m_model->selectedResources(selection); + bool use_all = mods_list.empty(); + if (use_all) + mods_list = m_model->allResources(); + + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false); + update_dialog.checkCandidates(); + + if (update_dialog.aborted()) { + CustomMessageBox::selectable(this, tr("Aborted"), tr("The resource pack updater was aborted!"), QMessageBox::Warning)->show(); + return; + } + if (update_dialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; + if (mods_list.size() > 1) { + if (use_all) { + message = tr("All resource packs are up-to-date! :)"); + } else { + message = tr("All selected resource packs are up-to-date! :)"); + } + } + CustomMessageBox::selectable(this, tr("Update checker"), message)->exec(); + return; + } + + if (update_dialog.exec()) { + auto tasks = new ConcurrentTask(this, "Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) { + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } + tasks->deleteLater(); + }); + + for (auto task : update_dialog.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} + +void ResourcePackPage::deleteResourcePackMetadata() +{ + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto selectionCount = m_model->selectedResourcePacks(selection).length(); + if (selectionCount == 0) + return; + if (selectionCount > 1) { + auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), + tr("You are about to remove the metadata for %1 resource packs.\n" + "Are you sure?") + .arg(selectionCount), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + + m_model->deleteMetadata(selection); +} diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index cb84ca96d8..c2d36cea1b 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -59,5 +59,12 @@ class ResourcePackPage : public ExternalResourcesPage { public slots: bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; - void downloadRPs(); + + private slots: + void downloadResourcePacks(); + void updateResourcePacks(); + void deleteResourcePackMetadata(); + + protected: + std::shared_ptr m_model; }; diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index 40366a1bec..768d7c710b 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -45,27 +45,41 @@ #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h" +#include "ui/dialogs/ResourceUpdateDialog.h" ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) - : ExternalResourcesPage(instance, model, parent) + : ExternalResourcesPage(instance, model, parent), m_model(model) { - ui->actionDownloadItem->setText(tr("Download shaders")); - ui->actionDownloadItem->setToolTip(tr("Download shaders from online platforms")); + ui->actionDownloadItem->setText(tr("Download Packs")); + ui->actionDownloadItem->setToolTip(tr("Download shader packs from online mod platforms")); ui->actionDownloadItem->setEnabled(true); - connect(ui->actionDownloadItem, &QAction::triggered, this, &ShaderPackPage::downloadShaders); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); - ui->actionViewConfigs->setVisible(false); + connect(ui->actionDownloadItem, &QAction::triggered, this, &ShaderPackPage::downloadShaderPack); + + ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected shader packs (all shader packs if none are selected)")); + connect(ui->actionUpdateItem, &QAction::triggered, this, &ShaderPackPage::updateShaderPacks); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); + + auto updateMenu = new QMenu(this); + + auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + connect(update, &QAction::triggered, this, &ShaderPackPage::updateShaderPacks); + + updateMenu->addAction(ui->actionResetItemMetadata); + connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ShaderPackPage::deleteShaderPackMetadata); + + ui->actionUpdateItem->setMenu(updateMenu); } -void ShaderPackPage::downloadShaders() +void ShaderPackPage::downloadShaderPack() { if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - ResourceDownload::ShaderPackDownloadDialog mdownload(this, std::static_pointer_cast(m_model), m_instance); + ResourceDownload::ShaderPackDownloadDialog mdownload(this, m_model, m_instance); if (mdownload.exec()) { - auto tasks = new ConcurrentTask(this, "Download Shaders", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + auto tasks = new ConcurrentTask(this, "Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); @@ -93,3 +107,103 @@ void ShaderPackPage::downloadShaders() m_model->update(); } } + +void ShaderPackPage::updateShaderPacks() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + auto profile = static_cast(m_instance)->getPackProfile(); + if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + QMessageBox::critical(this, tr("Error"), tr("Shader pack updates are unavailable when metadata is disabled!")); + return; + } + if (m_instance != nullptr && m_instance->isRunning()) { + auto response = + CustomMessageBox::selectable(this, tr("Confirm Update"), + tr("Updating shader packs while the game is running may pack duplication and game crashes.\n" + "The old files may not be deleted as they are in use.\n" + "Are you sure you want to do this?"), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + + auto mods_list = m_model->selectedResources(selection); + bool use_all = mods_list.empty(); + if (use_all) + mods_list = m_model->allResources(); + + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false); + update_dialog.checkCandidates(); + + if (update_dialog.aborted()) { + CustomMessageBox::selectable(this, tr("Aborted"), tr("The shader pack updater was aborted!"), QMessageBox::Warning)->show(); + return; + } + if (update_dialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; + if (mods_list.size() > 1) { + if (use_all) { + message = tr("All shader packs are up-to-date! :)"); + } else { + message = tr("All selected shader packs are up-to-date! :)"); + } + } + CustomMessageBox::selectable(this, tr("Update checker"), message)->exec(); + return; + } + + if (update_dialog.exec()) { + auto tasks = new ConcurrentTask(this, "Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) { + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } + tasks->deleteLater(); + }); + + for (auto task : update_dialog.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} + +void ShaderPackPage::deleteShaderPackMetadata() +{ + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto selectionCount = m_model->selectedShaderPacks(selection).length(); + if (selectionCount == 0) + return; + if (selectionCount > 1) { + auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), + tr("You are about to remove the metadata for %1 shader packs.\n" + "Are you sure?") + .arg(selectionCount), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + + m_model->deleteMetadata(selection); +} diff --git a/launcher/ui/pages/instance/ShaderPackPage.h b/launcher/ui/pages/instance/ShaderPackPage.h index 7c43a37564..fadffe82d9 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.h +++ b/launcher/ui/pages/instance/ShaderPackPage.h @@ -53,5 +53,10 @@ class ShaderPackPage : public ExternalResourcesPage { bool shouldDisplay() const override { return true; } public slots: - void downloadShaders(); + void downloadShaderPack(); + void updateShaderPacks(); + void deleteShaderPackMetadata(); + + private: + std::shared_ptr m_model; }; diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index 7c8d7e0617..75f8410bcb 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -44,17 +44,31 @@ #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h" +#include "ui/dialogs/ResourceUpdateDialog.h" TexturePackPage::TexturePackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) - : ExternalResourcesPage(instance, model, parent) + : ExternalResourcesPage(instance, model, parent), m_model(model) { - ui->actionDownloadItem->setText(tr("Download packs")); - ui->actionDownloadItem->setToolTip(tr("Download texture packs from online platforms")); + ui->actionDownloadItem->setText(tr("Download Packs")); + ui->actionDownloadItem->setToolTip(tr("Download texture packs from online mod platforms")); ui->actionDownloadItem->setEnabled(true); - connect(ui->actionDownloadItem, &QAction::triggered, this, &TexturePackPage::downloadTPs); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); - ui->actionViewConfigs->setVisible(false); + connect(ui->actionDownloadItem, &QAction::triggered, this, &TexturePackPage::downloadTexturePacks); + + ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected texture packs (all texture packs if none are selected)")); + connect(ui->actionUpdateItem, &QAction::triggered, this, &TexturePackPage::updateTexturePacks); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); + + auto updateMenu = new QMenu(this); + + auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + connect(update, &QAction::triggered, this, &TexturePackPage::updateTexturePacks); + + updateMenu->addAction(ui->actionResetItemMetadata); + connect(ui->actionResetItemMetadata, &QAction::triggered, this, &TexturePackPage::deleteTexturePackMetadata); + + ui->actionUpdateItem->setMenu(updateMenu); } bool TexturePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) @@ -67,12 +81,12 @@ bool TexturePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unu return true; } -void TexturePackPage::downloadTPs() +void TexturePackPage::downloadTexturePacks() { if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - ResourceDownload::TexturePackDownloadDialog mdownload(this, std::static_pointer_cast(m_model), m_instance); + ResourceDownload::TexturePackDownloadDialog mdownload(this, m_model, m_instance); if (mdownload.exec()) { auto tasks = new ConcurrentTask(this, "Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); @@ -103,3 +117,103 @@ void TexturePackPage::downloadTPs() m_model->update(); } } + +void TexturePackPage::updateTexturePacks() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + auto profile = static_cast(m_instance)->getPackProfile(); + if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + QMessageBox::critical(this, tr("Error"), tr("Texture pack updates are unavailable when metadata is disabled!")); + return; + } + if (m_instance != nullptr && m_instance->isRunning()) { + auto response = + CustomMessageBox::selectable(this, tr("Confirm Update"), + tr("Updating texture packs while the game is running may cause pack duplication and game crashes.\n" + "The old files may not be deleted as they are in use.\n" + "Are you sure you want to do this?"), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + + auto mods_list = m_model->selectedResources(selection); + bool use_all = mods_list.empty(); + if (use_all) + mods_list = m_model->allResources(); + + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false); + update_dialog.checkCandidates(); + + if (update_dialog.aborted()) { + CustomMessageBox::selectable(this, tr("Aborted"), tr("The texture pack updater was aborted!"), QMessageBox::Warning)->show(); + return; + } + if (update_dialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; + if (mods_list.size() > 1) { + if (use_all) { + message = tr("All texture packs are up-to-date! :)"); + } else { + message = tr("All selected texture packs are up-to-date! :)"); + } + } + CustomMessageBox::selectable(this, tr("Update checker"), message)->exec(); + return; + } + + if (update_dialog.exec()) { + auto tasks = new ConcurrentTask(this, "Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) { + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } + tasks->deleteLater(); + }); + + for (auto task : update_dialog.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} + +void TexturePackPage::deleteTexturePackMetadata() +{ + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto selectionCount = m_model->selectedTexturePacks(selection).length(); + if (selectionCount == 0) + return; + if (selectionCount > 1) { + auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), + tr("You are about to remove the metadata for %1 texture packs.\n" + "Are you sure?") + .arg(selectionCount), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + + m_model->deleteMetadata(selection); +} diff --git a/launcher/ui/pages/instance/TexturePackPage.h b/launcher/ui/pages/instance/TexturePackPage.h index 9c4f24b704..e42613568f 100644 --- a/launcher/ui/pages/instance/TexturePackPage.h +++ b/launcher/ui/pages/instance/TexturePackPage.h @@ -56,5 +56,10 @@ class TexturePackPage : public ExternalResourcesPage { public slots: bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; - void downloadTPs(); + void downloadTexturePacks(); + void updateTexturePacks(); + void deleteTexturePackMetadata(); + + private: + std::shared_ptr m_model; }; From 9120848278f8709525b574e92a4e602c593e1f4c Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 6 Mar 2024 19:33:14 +0200 Subject: [PATCH 0156/2054] Made java downloader as a build option Signed-off-by: Trial97 --- CMakeLists.txt | 3 +++ buildconfig/BuildConfig.cpp.in | 4 +++ buildconfig/BuildConfig.h | 1 + launcher/CMakeLists.txt | 4 +++ launcher/ui/pages/global/JavaPage.cpp | 25 +++++++++++------- .../pages/instance/InstanceSettingsPage.cpp | 2 ++ launcher/ui/widgets/JavaSettingsWidget.cpp | 26 +++++++++++-------- 7 files changed, 45 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cceb2599c..9b71044787 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -219,6 +219,9 @@ set(Launcher_SUBREDDIT_URL "https://prismlauncher.org/reddit" CACHE STRING "URL set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules") set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against") +# Java downloader +option(ENABLE_JAVA_DOWNLOADER "Build the java downloader feature" ON) + # Native libraries if(UNIX AND APPLE) set(Launcher_GLFW_LIBRARY_NAME "libglfw.dylib" CACHE STRING "Name of native glfw library") diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index b40cacb0f3..cdf3cbffc2 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -81,6 +81,10 @@ Config::Config() UPDATER_ENABLED = true; } +#if ENABLE_JAVA_DOWNLOADER + JAVA_DOWNLOADER_ENABLED = true; +#endif + GIT_COMMIT = "@Launcher_GIT_COMMIT@"; GIT_TAG = "@Launcher_GIT_TAG@"; GIT_REFSPEC = "@Launcher_GIT_REFSPEC@"; diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 77b6eef549..e3beb8dbe6 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -67,6 +67,7 @@ class Config { QString VERSION_CHANNEL; bool UPDATER_ENABLED = false; + bool JAVA_DOWNLOADER_ENABLED = false; /// A short string identifying this build's platform or distribution. QString BUILD_PLATFORM; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index fcc3c2f3a5..f620388dca 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1320,6 +1320,10 @@ if(DEFINED Launcher_APP_BINARY_DEFS) target_compile_definitions(Launcher_logic PRIVATE ${Launcher_APP_BINARY_DEFS}) endif() +if(ENABLE_JAVA_DOWNLOADER) + target_compile_definitions(Launcher_logic PUBLIC ENABLE_JAVA_DOWNLOADER) +endif() + install(TARGETS ${Launcher_Name} BUNDLE DESTINATION "." COMPONENT Runtime LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 95d5f581e1..0276daed59 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -35,6 +35,7 @@ */ #include "JavaPage.h" +#include "BuildConfig.h" #include "JavaCommon.h" #include "java/JavaInstall.h" #include "ui/dialogs/CustomMessageBox.h" @@ -62,15 +63,21 @@ JavaPage::JavaPage(QWidget* parent) : QWidget(parent), ui(new Ui::JavaPage) { ui->setupUi(this); - ui->managedJavaList->initialize(new JavaInstallList(this, true)); - ui->managedJavaList->selectCurrent(); - ui->managedJavaList->setEmptyString(tr("No java versions are currently available in the meta")); - ui->managedJavaList->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); - connect(ui->autodetectJavaCheckBox, &QCheckBox::stateChanged, this, [this] { - ui->autodownloadCheckBox->setEnabled(ui->autodetectJavaCheckBox->isChecked()); - if (!ui->autodetectJavaCheckBox->isChecked()) - ui->autodownloadCheckBox->setChecked(false); - }); + if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { + ui->managedJavaList->initialize(new JavaInstallList(this, true)); + ui->managedJavaList->selectCurrent(); + ui->managedJavaList->setEmptyString(tr("No java versions are currently available in the meta")); + ui->managedJavaList->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); + connect(ui->autodetectJavaCheckBox, &QCheckBox::stateChanged, this, [this] { + ui->autodownloadCheckBox->setEnabled(ui->autodetectJavaCheckBox->isChecked()); + if (!ui->autodetectJavaCheckBox->isChecked()) + ui->autodownloadCheckBox->setChecked(false); + }); + } else { + ui->autodownloadCheckBox->setHidden(true); + ui->javaDownloadBtn->setHidden(true); + ui->tabWidget->tabBar()->hide(); + } loadSettings(); updateThresholds(); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 50cffc9a7f..f2a723b81d 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -64,6 +64,8 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance* inst, QWidget* parent) m_settings = inst->settings(); ui->setupUi(this); + ui->javaDownloadBtn->setHidden(!BuildConfig.JAVA_DOWNLOADER_ENABLED); + connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked); connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings); connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings); diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 29357bdab2..866d4c1824 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -129,8 +129,10 @@ void JavaSettingsWidget::setupUi() m_horizontalBtnLayout = new QHBoxLayout(); m_horizontalBtnLayout->setObjectName(QStringLiteral("horizontalBtnLayout")); - m_javaDownloadBtn = new QPushButton(tr("Download Java"), this); - m_horizontalBtnLayout->addWidget(m_javaDownloadBtn); + if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { + m_javaDownloadBtn = new QPushButton(tr("Download Java"), this); + m_horizontalBtnLayout->addWidget(m_javaDownloadBtn); + } m_verticalLayout->addLayout(m_horizontalBtnLayout); @@ -143,15 +145,17 @@ void JavaSettingsWidget::setupUi() m_autodetectJavaCheckBox->setObjectName("autodetectJavaCheckBox"); m_veriticalJavaLayout->addWidget(m_autodetectJavaCheckBox); - m_autodownloadCheckBox = new QCheckBox(m_autoJavaGroupBox); - m_autodownloadCheckBox->setObjectName("autodownloadCheckBox"); - m_autodownloadCheckBox->setEnabled(false); - m_veriticalJavaLayout->addWidget(m_autodownloadCheckBox); - connect(m_autodetectJavaCheckBox, &QCheckBox::stateChanged, this, [this] { - m_autodownloadCheckBox->setEnabled(m_autodetectJavaCheckBox->isChecked()); - if (!m_autodetectJavaCheckBox->isChecked()) - m_autodownloadCheckBox->setChecked(false); - }); + if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { + m_autodownloadCheckBox = new QCheckBox(m_autoJavaGroupBox); + m_autodownloadCheckBox->setObjectName("autodownloadCheckBox"); + m_autodownloadCheckBox->setEnabled(false); + m_veriticalJavaLayout->addWidget(m_autodownloadCheckBox); + connect(m_autodetectJavaCheckBox, &QCheckBox::stateChanged, this, [this] { + m_autodownloadCheckBox->setEnabled(m_autodetectJavaCheckBox->isChecked()); + if (!m_autodetectJavaCheckBox->isChecked()) + m_autodownloadCheckBox->setChecked(false); + }); + } m_verticalLayout->addWidget(m_autoJavaGroupBox); retranslate(); From ef4e5eb3cf726e3b59f202d6eceee6f53450743e Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 18 Mar 2024 19:15:36 +0200 Subject: [PATCH 0157/2054] fixed java build option Signed-off-by: Trial97 --- buildconfig/BuildConfig.cpp.in | 5 ++--- launcher/CMakeLists.txt | 4 ---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index cdf3cbffc2..a2b5c21871 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -81,9 +81,8 @@ Config::Config() UPDATER_ENABLED = true; } -#if ENABLE_JAVA_DOWNLOADER - JAVA_DOWNLOADER_ENABLED = true; -#endif + #cmakedefine01 ENABLE_JAVA_DOWNLOADER + JAVA_DOWNLOADER_ENABLED = ENABLE_JAVA_DOWNLOADER; GIT_COMMIT = "@Launcher_GIT_COMMIT@"; GIT_TAG = "@Launcher_GIT_TAG@"; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index f620388dca..fcc3c2f3a5 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1320,10 +1320,6 @@ if(DEFINED Launcher_APP_BINARY_DEFS) target_compile_definitions(Launcher_logic PRIVATE ${Launcher_APP_BINARY_DEFS}) endif() -if(ENABLE_JAVA_DOWNLOADER) - target_compile_definitions(Launcher_logic PUBLIC ENABLE_JAVA_DOWNLOADER) -endif() - install(TARGETS ${Launcher_Name} BUNDLE DESTINATION "." COMPONENT Runtime LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime From 09c2c6793b52639f86f2edead268b51ad4b5fffc Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 20 Mar 2024 00:15:02 +0200 Subject: [PATCH 0158/2054] Improve UI Signed-off-by: Trial97 --- launcher/BaseVersionList.cpp | 1 - launcher/BaseVersionList.h | 1 - launcher/CMakeLists.txt | 5 +- launcher/VersionProxyModel.cpp | 9 - launcher/VersionProxyModel.h | 2 +- launcher/java/JavaMetadata.cpp | 2 +- launcher/java/JavaMetadata.h | 2 +- launcher/minecraft/OneSixVersionFormat.cpp | 9 +- launcher/minecraft/VersionFile.h | 2 +- launcher/minecraft/launch/AutoInstallJava.cpp | 60 +++-- launcher/ui/java/InstallJavaDialog.cpp | 234 ++++++++++++++++++ .../{JavaDownloader.h => InstallJavaDialog.h} | 28 +-- launcher/ui/java/JavaDownloader.cpp | 110 -------- launcher/ui/java/JavaDownloader.ui | 100 -------- launcher/ui/java/VersionList.cpp | 14 +- launcher/ui/pages/global/JavaPage.cpp | 4 +- .../pages/instance/InstanceSettingsPage.cpp | 4 +- launcher/ui/widgets/JavaSettingsWidget.cpp | 4 +- 18 files changed, 295 insertions(+), 296 deletions(-) create mode 100644 launcher/ui/java/InstallJavaDialog.cpp rename launcher/ui/java/{JavaDownloader.h => InstallJavaDialog.h} (63%) delete mode 100644 launcher/ui/java/JavaDownloader.cpp delete mode 100644 launcher/ui/java/JavaDownloader.ui diff --git a/launcher/BaseVersionList.cpp b/launcher/BaseVersionList.cpp index 1456a17bd9..afee8388a1 100644 --- a/launcher/BaseVersionList.cpp +++ b/launcher/BaseVersionList.cpp @@ -112,6 +112,5 @@ QHash BaseVersionList::roleNames() const roles.insert(PathRole, "path"); roles.insert(JavaNameRole, "javaName"); roles.insert(CPUArchitectureRole, "architecture"); - roles.insert(JavaVendorRole, "javaVendor"); return roles; } diff --git a/launcher/BaseVersionList.h b/launcher/BaseVersionList.h index 7517c71b47..bc37e9e53e 100644 --- a/launcher/BaseVersionList.h +++ b/launcher/BaseVersionList.h @@ -50,7 +50,6 @@ class BaseVersionList : public QAbstractListModel { PathRole, JavaNameRole, CPUArchitectureRole, - JavaVendorRole, SortRole }; using RoleList = QList; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index fcc3c2f3a5..dd493682eb 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -440,8 +440,8 @@ set(JAVA_SOURCES java/download/ManifestDownloadTask.cpp java/download/ManifestDownloadTask.h - ui/java/JavaDownloader.h - ui/java/JavaDownloader.cpp + ui/java/InstallJavaDialog.h + ui/java/InstallJavaDialog.cpp ui/java/VersionList.h ui/java/VersionList.cpp ) @@ -1194,7 +1194,6 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/ScrollMessageBox.ui ui/dialogs/BlockedModsDialog.ui ui/dialogs/ChooseProviderDialog.ui - ui/java/JavaDownloader.ui ) qt_wrap_ui(PRISM_UPDATE_UI diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index ed4c0767d6..070e952a4a 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -116,8 +116,6 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation, return tr("Type"); case CPUArchitecture: return tr("Architecture"); - case JavaVendor: - return tr("Vendor"); case Path: return tr("Path"); case JavaName: @@ -137,8 +135,6 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation, return tr("The version's type"); case CPUArchitecture: return tr("CPU Architecture"); - case JavaVendor: - return tr("Java vendor"); case Path: return tr("Filesystem path to this version"); case JavaName: @@ -175,8 +171,6 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const return sourceModel()->data(parentIndex, BaseVersionList::TypeRole); case CPUArchitecture: return sourceModel()->data(parentIndex, BaseVersionList::CPUArchitectureRole); - case JavaVendor: - return sourceModel()->data(parentIndex, BaseVersionList::JavaVendorRole); case Path: return sourceModel()->data(parentIndex, BaseVersionList::PathRole); case JavaName: @@ -323,9 +317,6 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw) if (roles.contains(BaseVersionList::CPUArchitectureRole)) { m_columns.push_back(CPUArchitecture); } - if (roles.contains(BaseVersionList::JavaVendorRole)) { - m_columns.push_back(JavaVendor); - } if (roles.contains(BaseVersionList::PathRole)) { m_columns.push_back(Path); } diff --git a/launcher/VersionProxyModel.h b/launcher/VersionProxyModel.h index 5a1017bee3..cb55b7f14a 100644 --- a/launcher/VersionProxyModel.h +++ b/launcher/VersionProxyModel.h @@ -9,7 +9,7 @@ class VersionFilterModel; class VersionProxyModel : public QAbstractProxyModel { Q_OBJECT public: - enum Column { Name, ParentVersion, Branch, Type, CPUArchitecture, Path, Time, JavaName, JavaVendor }; + enum Column { Name, ParentVersion, Branch, Type, CPUArchitecture, Path, Time, JavaName }; using FilterMap = QHash>; public: diff --git a/launcher/java/JavaMetadata.cpp b/launcher/java/JavaMetadata.cpp index b73c54fe6c..e721947a75 100644 --- a/launcher/java/JavaMetadata.cpp +++ b/launcher/java/JavaMetadata.cpp @@ -52,9 +52,9 @@ MetadataPtr parseJavaMeta(const QJsonObject& in) meta->vendor = Json::ensureString(in, "vendor", ""); meta->url = Json::ensureString(in, "url", ""); meta->releaseTime = timeFromS3Time(Json::ensureString(in, "releaseTime", "")); - meta->recommended = Json::ensureBoolean(in, "recommended", false); meta->downloadType = parseDownloadType(Json::ensureString(in, "downloadType", "")); meta->packageType = Json::ensureString(in, "packageType", ""); + meta->runtimeOS = Json::ensureString(in, "runtimeOS", "unknown"); if (in.contains("checksum")) { auto obj = Json::requireObject(in, "checksum"); diff --git a/launcher/java/JavaMetadata.h b/launcher/java/JavaMetadata.h index 640a412f11..5281f2e27c 100644 --- a/launcher/java/JavaMetadata.h +++ b/launcher/java/JavaMetadata.h @@ -50,10 +50,10 @@ class Metadata : public BaseVersion { QDateTime releaseTime; QString checksumType; QString checksumHash; - bool recommended; DownloadType downloadType; QString packageType; JavaVersion version; + QString runtimeOS; }; using MetadataPtr = std::shared_ptr; diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp index 56e9c8ca25..bd587beb27 100644 --- a/launcher/minecraft/OneSixVersionFormat.cpp +++ b/launcher/minecraft/OneSixVersionFormat.cpp @@ -258,14 +258,9 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc } if (root.contains("runtimes")) { - auto runtimes = requireObject(root, "runtimes"); out->runtimes = {}; - for (auto key : runtimes.keys()) { - QList list; - for (auto runtime : ensureArray(runtimes, key)) { - list.append(Java::parseJavaMeta(ensureObject(runtime))); - } - out->runtimes[key] = list; + for (auto runtime : ensureArray(root, "runtimes")) { + out->runtimes.append(Java::parseJavaMeta(ensureObject(runtime))); } } diff --git a/launcher/minecraft/VersionFile.h b/launcher/minecraft/VersionFile.h index a9c1473d20..85ac554267 100644 --- a/launcher/minecraft/VersionFile.h +++ b/launcher/minecraft/VersionFile.h @@ -155,7 +155,7 @@ class VersionFile : public ProblemContainer { /// is volatile -- may be removed as soon as it is no longer needed by something else bool m_volatile = false; - QHash> runtimes; + QList runtimes; public: // Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more. diff --git a/launcher/minecraft/launch/AutoInstallJava.cpp b/launcher/minecraft/launch/AutoInstallJava.cpp index ab68910a77..3ae6ffa88a 100644 --- a/launcher/minecraft/launch/AutoInstallJava.cpp +++ b/launcher/minecraft/launch/AutoInstallJava.cpp @@ -145,39 +145,35 @@ void AutoInstallJava::setJavaPathFromPartial() void AutoInstallJava::downloadJava(Meta::Version::Ptr version, QString javaName) { auto runtimes = version->data()->runtimes; - if (runtimes.contains(m_supported_arch)) { - for (auto java : runtimes.value(m_supported_arch)) { - if (java->name() == javaName) { - QDir javaDir(APPLICATION->javaPath()); - auto final_path = javaDir.absoluteFilePath(java->m_name); - switch (java->downloadType) { - case Java::DownloadType::Manifest: - m_current_task = - makeShared(java->url, final_path, java->checksumType, java->checksumHash); - break; - case Java::DownloadType::Archive: - m_current_task = - makeShared(java->url, final_path, java->checksumType, java->checksumHash); - break; - } - auto deletePath = [final_path] { FS::deletePath(final_path); }; - connect(m_current_task.get(), &Task::failed, this, [this, deletePath](QString reason) { - deletePath(); - emitFailed(reason); - }); - connect(this, &Task::aborted, this, [this, deletePath] { - m_current_task->abort(); - deletePath(); - }); - connect(m_current_task.get(), &Task::succeeded, this, &AutoInstallJava::setJavaPathFromPartial); - connect(m_current_task.get(), &Task::failed, this, &AutoInstallJava::tryNextMajorJava); - connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress); - connect(m_current_task.get(), &Task::stepProgress, this, &AutoInstallJava::propagateStepProgress); - connect(m_current_task.get(), &Task::status, this, &AutoInstallJava::setStatus); - connect(m_current_task.get(), &Task::details, this, &AutoInstallJava::setDetails); - m_current_task->start(); - return; + for (auto java : runtimes) { + if (java->runtimeOS == m_supported_arch && java->name() == javaName) { + QDir javaDir(APPLICATION->javaPath()); + auto final_path = javaDir.absoluteFilePath(java->m_name); + switch (java->downloadType) { + case Java::DownloadType::Manifest: + m_current_task = makeShared(java->url, final_path, java->checksumType, java->checksumHash); + break; + case Java::DownloadType::Archive: + m_current_task = makeShared(java->url, final_path, java->checksumType, java->checksumHash); + break; } + auto deletePath = [final_path] { FS::deletePath(final_path); }; + connect(m_current_task.get(), &Task::failed, this, [this, deletePath](QString reason) { + deletePath(); + emitFailed(reason); + }); + connect(this, &Task::aborted, this, [this, deletePath] { + m_current_task->abort(); + deletePath(); + }); + connect(m_current_task.get(), &Task::succeeded, this, &AutoInstallJava::setJavaPathFromPartial); + connect(m_current_task.get(), &Task::failed, this, &AutoInstallJava::tryNextMajorJava); + connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress); + connect(m_current_task.get(), &Task::stepProgress, this, &AutoInstallJava::propagateStepProgress); + connect(m_current_task.get(), &Task::status, this, &AutoInstallJava::setStatus); + connect(m_current_task.get(), &Task::details, this, &AutoInstallJava::setDetails); + m_current_task->start(); + return; } } tryNextMajorJava(); diff --git a/launcher/ui/java/InstallJavaDialog.cpp b/launcher/ui/java/InstallJavaDialog.cpp new file mode 100644 index 0000000000..054e5cece0 --- /dev/null +++ b/launcher/ui/java/InstallJavaDialog.cpp @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2024 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "InstallJavaDialog.h" + +#include +#include +#include +#include + +#include "Application.h" +#include "FileSystem.h" +#include "java/download/ArchiveDownloadTask.h" +#include "java/download/ManifestDownloadTask.h" +#include "meta/Index.h" +#include "meta/VersionList.h" +#include "ui/dialogs/ProgressDialog.h" +#include "ui/java/VersionList.h" +#include "ui/widgets/PageContainer.h" +#include "ui/widgets/VersionSelectWidget.h" + +class InstallLoaderPage : public QWidget, public BasePage { + public: + Q_OBJECT + public: + explicit InstallLoaderPage(const QString& id, const QString& iconName, const QString& name, QWidget* parent = nullptr) + : QWidget(parent), uid(id), iconName(iconName), name(name) + { + setObjectName(QStringLiteral("VersionSelectWidget")); + horizontalLayout = new QHBoxLayout(this); + horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); + horizontalLayout->setContentsMargins(0, 0, 0, 0); + + majorVersionSelect = new VersionSelectWidget(this); + majorVersionSelect->selectCurrent(); + majorVersionSelect->setEmptyString(tr("No java versions are currently available in the meta.")); + majorVersionSelect->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); + horizontalLayout->addWidget(majorVersionSelect, 1); + + javaVersionSelect = new VersionSelectWidget(this); + javaVersionSelect->setEmptyString(tr("No java versions are currently available for your OS.")); + javaVersionSelect->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); + horizontalLayout->addWidget(javaVersionSelect, 4); + connect(majorVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &InstallLoaderPage::setSelectedVersion); + connect(javaVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &InstallLoaderPage::selectedVersionChanged); + + QMetaObject::connectSlotsByName(this); + } + ~InstallLoaderPage() + { + delete horizontalLayout; + delete majorVersionSelect; + delete javaVersionSelect; + } + + //! loads the list if needed. + void initialize(Meta::VersionList::Ptr vlist) + { + vlist->setProvidedRoles({ BaseVersionList::VersionRole, BaseVersionList::RecommendedRole, BaseVersionList::VersionPointerRole }); + majorVersionSelect->initialize(vlist.get()); + } + + void setSelectedVersion(BaseVersion::Ptr version) + { + auto dcast = std::dynamic_pointer_cast(version); + if (!dcast) { + return; + } + javaVersionSelect->initialize(new Java::VersionList(dcast, this)); + javaVersionSelect->selectCurrent(); + } + + QString id() const override { return uid; } + QString displayName() const override { return name; } + QIcon icon() const override { return APPLICATION->getThemedIcon(iconName); } + + void openedImpl() override + { + if (loaded) + return; + + const auto versions = APPLICATION->metadataIndex()->get(uid); + if (!versions) + return; + + initialize(versions); + loaded = true; + } + + void setParentContainer(BasePageContainer* container) override + { + auto dialog = dynamic_cast(dynamic_cast(container)->parent()); + connect(javaVersionSelect->view(), &QAbstractItemView::doubleClicked, dialog, &QDialog::accept); + } + + BaseVersion::Ptr selectedVersion() const { return javaVersionSelect->selectedVersion(); } + void selectSearch() { javaVersionSelect->selectSearch(); } + void loadList() + { + majorVersionSelect->loadList(); + javaVersionSelect->loadList(); + } + signals: + void selectedVersionChanged(BaseVersion::Ptr version); + + private: + const QString uid; + const QString iconName; + const QString name; + bool loaded = false; + + QHBoxLayout* horizontalLayout = nullptr; + VersionSelectWidget* majorVersionSelect = nullptr; + VersionSelectWidget* javaVersionSelect = nullptr; +}; + +static InstallLoaderPage* pageCast(BasePage* page) +{ + auto result = dynamic_cast(page); + Q_ASSERT(result != nullptr); + return result; +} +namespace Java { + +InstallDialog::InstallDialog(const QString& uid, QWidget* parent) + : QDialog(parent), container(new PageContainer(this, QString(), this)), buttons(new QDialogButtonBox(this)) +{ + auto layout = new QVBoxLayout(this); + + container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + layout->addWidget(container); + + auto buttonLayout = new QHBoxLayout(this); + + auto refreshButton = new QPushButton(tr("&Refresh"), this); + connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(); }); + buttonLayout->addWidget(refreshButton); + + buttons->setOrientation(Qt::Horizontal); + buttons->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + buttons->button(QDialogButtonBox::Ok)->setText(tr("Download")); + connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + buttonLayout->addWidget(buttons); + + layout->addLayout(buttonLayout); + + setWindowTitle(dialogTitle()); + setWindowModality(Qt::WindowModal); + resize(840, 480); + + for (BasePage* page : container->getPages()) { + if (page->id() == uid) + container->selectPage(page->id()); + + connect(pageCast(page), &InstallLoaderPage::selectedVersionChanged, this, [this, page] { + if (page->id() == container->selectedPage()->id()) + validate(container->selectedPage()); + }); + } + connect(container, &PageContainer::selectedPageChanged, this, [this](BasePage* previous, BasePage* current) { validate(current); }); + pageCast(container->selectedPage())->selectSearch(); + validate(container->selectedPage()); +} + +QList InstallDialog::getPages() +{ + return { + // NeoForge + new InstallLoaderPage("net.minecraft.java", "", tr("Mojang")), + // Forge + new InstallLoaderPage("net.adoptium.java", "", tr("Adoptium")), + // Fabric + new InstallLoaderPage("com.azul.java", "", tr("Azul")), + }; +} + +QString InstallDialog::dialogTitle() +{ + return tr("Install Loader"); +} + +void InstallDialog::validate(BasePage* page) +{ + buttons->button(QDialogButtonBox::Ok)->setEnabled(pageCast(page)->selectedVersion() != nullptr); +} + +void InstallDialog::done(int result) +{ + if (result == Accepted) { + auto* page = pageCast(container->selectedPage()); + if (page->selectedVersion()) { + auto meta = std::dynamic_pointer_cast(page->selectedVersion()); + if (meta) { + Task::Ptr task; + auto final_path = FS::PathCombine(APPLICATION->javaPath(), meta->m_name); + switch (meta->downloadType) { + case Java::DownloadType::Manifest: + task = makeShared(meta->url, final_path, meta->checksumType, meta->checksumHash); + break; + case Java::DownloadType::Archive: + task = makeShared(meta->url, final_path, meta->checksumType, meta->checksumHash); + break; + } + auto deletePath = [final_path] { FS::deletePath(final_path); }; + connect(task.get(), &Task::failed, this, deletePath); + connect(task.get(), &Task::aborted, this, deletePath); + ProgressDialog pg(this); + pg.setSkipButton(true, tr("Abort")); + pg.execWithTask(task.get()); + } + } + } + + QDialog::done(result); +} +} // namespace Java + +#include "InstallJavaDialog.moc" \ No newline at end of file diff --git a/launcher/ui/java/JavaDownloader.h b/launcher/ui/java/InstallJavaDialog.h similarity index 63% rename from launcher/ui/java/JavaDownloader.h rename to launcher/ui/java/InstallJavaDialog.h index b8bdde41a4..e228adf21e 100644 --- a/launcher/ui/java/JavaDownloader.h +++ b/launcher/ui/java/InstallJavaDialog.h @@ -19,30 +19,28 @@ #pragma once #include -#include "BaseVersion.h" +#include "ui/pages/BasePageProvider.h" -namespace Ui { -class JavaDownloader; -} +class MinecraftInstance; +class PageContainer; +class PackProfile; +class QDialogButtonBox; namespace Java { - -class Downloader : public QDialog { +class InstallDialog final : public QDialog, protected BasePageProvider { Q_OBJECT public: - explicit Downloader(QWidget* parent = 0); - ~Downloader(); - - void accept(); + explicit InstallDialog(const QString& uid = QString(), QWidget* parent = nullptr); - public slots: - void refresh(); + QList getPages() override; + QString dialogTitle() override; - protected slots: - void setSelectedVersion(BaseVersion::Ptr version); + void validate(BasePage* page); + void done(int result) override; private: - Ui::JavaDownloader* ui; + PageContainer* container; + QDialogButtonBox* buttons; }; } // namespace Java diff --git a/launcher/ui/java/JavaDownloader.cpp b/launcher/ui/java/JavaDownloader.cpp deleted file mode 100644 index 24918dd763..0000000000 --- a/launcher/ui/java/JavaDownloader.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2024 Trial97 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "JavaDownloader.h" - -#include - -#include - -#include "Application.h" -#include "BaseVersionList.h" -#include "FileSystem.h" -#include "QObjectPtr.h" -#include "SysInfo.h" - -#include "java/JavaMetadata.h" -#include "java/download/ArchiveDownloadTask.h" -#include "java/download/ManifestDownloadTask.h" - -#include "meta/Index.h" -#include "meta/Version.h" - -#include "meta/VersionList.h" -#include "ui/dialogs/ProgressDialog.h" -#include "ui/java/VersionList.h" -#include "ui_JavaDownloader.h" - -namespace Java { - -Downloader::Downloader(QWidget* parent) : QDialog(parent), ui(new Ui::JavaDownloader) -{ - ui->setupUi(this); - auto versionList = APPLICATION->metadataIndex()->get("net.minecraft.java"); - versionList->setProvidedRoles({ BaseVersionList::VersionRole, BaseVersionList::RecommendedRole, BaseVersionList::VersionPointerRole }); - ui->majorVersionSelect->initialize(versionList.get()); - ui->majorVersionSelect->selectCurrent(); - ui->majorVersionSelect->setEmptyString(tr("No java versions are currently available in the meta.")); - ui->majorVersionSelect->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); - - ui->javaVersionSelect->setEmptyString(tr("No java versions are currently available for your OS.")); - ui->javaVersionSelect->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); - - ui->buttonBox->button(QDialogButtonBox::Reset)->setText(tr("Refresh")); - ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Download")); - - connect(ui->majorVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &Downloader::setSelectedVersion); - auto reset = ui->buttonBox->button(QDialogButtonBox::Reset); - connect(reset, &QPushButton::clicked, this, &Downloader::refresh); -} - -Downloader::~Downloader() -{ - delete ui; -} - -void Downloader::setSelectedVersion(BaseVersion::Ptr version) -{ - auto dcast = std::dynamic_pointer_cast(version); - if (!dcast) { - return; - } - ui->javaVersionSelect->initialize(new Java::VersionList(dcast, this)); - ui->javaVersionSelect->selectCurrent(); -} - -void Downloader::accept() -{ - auto meta = std::dynamic_pointer_cast(ui->javaVersionSelect->selectedVersion()); - if (!meta) { - return; - } - Task::Ptr task; - auto final_path = FS::PathCombine(APPLICATION->javaPath(), meta->m_name); - switch (meta->downloadType) { - case Java::DownloadType::Manifest: - task = makeShared(meta->url, final_path, meta->checksumType, meta->checksumHash); - break; - case Java::DownloadType::Archive: - task = makeShared(meta->url, final_path, meta->checksumType, meta->checksumHash); - break; - } - auto deletePath = [final_path] { FS::deletePath(final_path); }; - connect(task.get(), &Task::failed, this, deletePath); - connect(task.get(), &Task::aborted, this, deletePath); - ProgressDialog pg(this); - pg.setSkipButton(true, tr("Abort")); - pg.execWithTask(task.get()); - QDialog::accept(); -} - -void Downloader::refresh() -{ - ui->majorVersionSelect->loadList(); -} -} // namespace Java diff --git a/launcher/ui/java/JavaDownloader.ui b/launcher/ui/java/JavaDownloader.ui deleted file mode 100644 index 3f6a8adc6d..0000000000 --- a/launcher/ui/java/JavaDownloader.ui +++ /dev/null @@ -1,100 +0,0 @@ - - - JavaDownloader - - - - 0 - 0 - 821 - 593 - - - - Dialog - - - - - - - - Major - - - - - - - - - - - - Runtime - - - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset - - - - - - - - VersionSelectWidget - QWidget -
    ui/widgets/VersionSelectWidget.h
    - 1 -
    -
    - - - - buttonBox - accepted() - JavaDownloader - accept() - - - 257 - 583 - - - 157 - 274 - - - - - buttonBox - rejected() - JavaDownloader - reject() - - - 325 - 583 - - - 286 - 274 - - - - -
    diff --git a/launcher/ui/java/VersionList.cpp b/launcher/ui/java/VersionList.cpp index dc454f2645..78448ddafe 100644 --- a/launcher/ui/java/VersionList.cpp +++ b/launcher/ui/java/VersionList.cpp @@ -75,11 +75,9 @@ QVariant VersionList::data(const QModelIndex& index, int role) const case VersionRole: return version->version.toString(); case RecommendedRole: - return version->recommended; + return false; // do not recommend any version case JavaNameRole: return version->name(); - case JavaVendorRole: - return version->vendor; case TypeRole: return version->packageType; case Meta::VersionList::TimeRole: @@ -91,8 +89,7 @@ QVariant VersionList::data(const QModelIndex& index, int role) const BaseVersionList::RoleList VersionList::providesRoles() const { - return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, - JavaNameRole, JavaVendorRole, TypeRole, Meta::VersionList::TimeRole }; + return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, JavaNameRole, TypeRole, Meta::VersionList::TimeRole }; } bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right) @@ -109,11 +106,12 @@ void VersionList::sortVersions() QString versionStr = SysInfo::getSupportedJavaArchitecture(); beginResetModel(); auto runtimes = m_version->data()->runtimes; - if (!versionStr.isEmpty() && !runtimes.isEmpty() && runtimes.contains(versionStr)) { - m_vlist = runtimes.value(versionStr); + m_vlist = {}; + if (!versionStr.isEmpty() && !runtimes.isEmpty()) { + std::copy_if(runtimes.begin(), runtimes.end(), std::back_inserter(m_vlist), + [versionStr](Java::MetadataPtr val) { return val->runtimeOS == versionStr; }); std::sort(m_vlist.begin(), m_vlist.end(), sortJavas); } else { - m_vlist = {}; qWarning() << "No Java versions found for your operating system." << SysInfo::currentSystem() << " " << SysInfo::useQTForArch(); } endResetModel(); diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 0276daed59..41a666cf26 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -39,7 +39,7 @@ #include "JavaCommon.h" #include "java/JavaInstall.h" #include "ui/dialogs/CustomMessageBox.h" -#include "ui/java/JavaDownloader.h" +#include "ui/java/InstallJavaDialog.h" #include "ui_JavaPage.h" #include @@ -201,7 +201,7 @@ void JavaPage::on_javaTestBtn_clicked() void JavaPage::on_javaDownloadBtn_clicked() { - auto jdialog = new Java::Downloader(this); + auto jdialog = new Java::InstallDialog({}, this); jdialog->exec(); ui->managedJavaList->loadList(); } diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 0b731e64ff..c248a47f72 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -37,7 +37,7 @@ #include "InstanceSettingsPage.h" #include "ui/dialogs/CustomMessageBox.h" -#include "ui/java/JavaDownloader.h" +#include "ui/java/InstallJavaDialog.h" #include "ui_InstanceSettingsPage.h" #include @@ -394,7 +394,7 @@ void InstanceSettingsPage::loadSettings() void InstanceSettingsPage::on_javaDownloadBtn_clicked() { - auto jdialog = new Java::Downloader(this); + auto jdialog = new Java::InstallDialog({}, this); jdialog->exec(); } diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 866d4c1824..e2e4980678 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -21,7 +21,7 @@ #include "java/JavaUtils.h" #include "ui/dialogs/CustomMessageBox.h" -#include "ui/java/JavaDownloader.h" +#include "ui/java/InstallJavaDialog.h" #include "ui/widgets/VersionSelectWidget.h" #include "Application.h" @@ -351,7 +351,7 @@ void JavaSettingsWidget::on_javaBrowseBtn_clicked() void JavaSettingsWidget::on_javaDownloadBtn_clicked() { - auto jdialog = new Java::Downloader(this); + auto jdialog = new Java::InstallDialog({}, this); jdialog->exec(); } From 5f8269f5e1ac41b29ed386e1a675e972c8d5624e Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 20 Mar 2024 00:29:46 +0200 Subject: [PATCH 0159/2054] Small class rename Signed-off-by: Trial97 --- launcher/ui/java/InstallJavaDialog.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/launcher/ui/java/InstallJavaDialog.cpp b/launcher/ui/java/InstallJavaDialog.cpp index 054e5cece0..40616aafee 100644 --- a/launcher/ui/java/InstallJavaDialog.cpp +++ b/launcher/ui/java/InstallJavaDialog.cpp @@ -34,11 +34,11 @@ #include "ui/widgets/PageContainer.h" #include "ui/widgets/VersionSelectWidget.h" -class InstallLoaderPage : public QWidget, public BasePage { +class InstallJavaPage : public QWidget, public BasePage { public: Q_OBJECT public: - explicit InstallLoaderPage(const QString& id, const QString& iconName, const QString& name, QWidget* parent = nullptr) + explicit InstallJavaPage(const QString& id, const QString& iconName, const QString& name, QWidget* parent = nullptr) : QWidget(parent), uid(id), iconName(iconName), name(name) { setObjectName(QStringLiteral("VersionSelectWidget")); @@ -56,12 +56,12 @@ class InstallLoaderPage : public QWidget, public BasePage { javaVersionSelect->setEmptyString(tr("No java versions are currently available for your OS.")); javaVersionSelect->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); horizontalLayout->addWidget(javaVersionSelect, 4); - connect(majorVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &InstallLoaderPage::setSelectedVersion); - connect(javaVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &InstallLoaderPage::selectedVersionChanged); + connect(majorVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &InstallJavaPage::setSelectedVersion); + connect(javaVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &InstallJavaPage::selectedVersionChanged); QMetaObject::connectSlotsByName(this); } - ~InstallLoaderPage() + ~InstallJavaPage() { delete horizontalLayout; delete majorVersionSelect; @@ -129,9 +129,9 @@ class InstallLoaderPage : public QWidget, public BasePage { VersionSelectWidget* javaVersionSelect = nullptr; }; -static InstallLoaderPage* pageCast(BasePage* page) +static InstallJavaPage* pageCast(BasePage* page) { - auto result = dynamic_cast(page); + auto result = dynamic_cast(page); Q_ASSERT(result != nullptr); return result; } @@ -168,7 +168,7 @@ InstallDialog::InstallDialog(const QString& uid, QWidget* parent) if (page->id() == uid) container->selectPage(page->id()); - connect(pageCast(page), &InstallLoaderPage::selectedVersionChanged, this, [this, page] { + connect(pageCast(page), &InstallJavaPage::selectedVersionChanged, this, [this, page] { if (page->id() == container->selectedPage()->id()) validate(container->selectedPage()); }); @@ -182,11 +182,11 @@ QList InstallDialog::getPages() { return { // NeoForge - new InstallLoaderPage("net.minecraft.java", "", tr("Mojang")), + new InstallJavaPage("net.minecraft.java", "", tr("Mojang")), // Forge - new InstallLoaderPage("net.adoptium.java", "", tr("Adoptium")), + new InstallJavaPage("net.adoptium.java", "", tr("Adoptium")), // Fabric - new InstallLoaderPage("com.azul.java", "", tr("Azul")), + new InstallJavaPage("com.azul.java", "", tr("Azul")), }; } From 703470e57d615b1bb1998a253865c8fe7fadcccd Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 20 Mar 2024 08:24:40 +0200 Subject: [PATCH 0160/2054] Hopefully fix codeql Signed-off-by: Trial97 --- launcher/ui/java/InstallJavaDialog.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/java/InstallJavaDialog.h b/launcher/ui/java/InstallJavaDialog.h index e228adf21e..c98c1deae3 100644 --- a/launcher/ui/java/InstallJavaDialog.h +++ b/launcher/ui/java/InstallJavaDialog.h @@ -27,7 +27,7 @@ class PackProfile; class QDialogButtonBox; namespace Java { -class InstallDialog final : public QDialog, protected BasePageProvider { +class InstallDialog final : public QDialog, public BasePageProvider { Q_OBJECT public: From 1c809f0fb1ef477d0315a7d1af174852b3b831e8 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 20 Mar 2024 17:39:10 +0200 Subject: [PATCH 0161/2054] Added more logs for auto java install Signed-off-by: Trial97 --- launcher/minecraft/launch/AutoInstallJava.cpp | 19 ++++++++++++++++++- launcher/ui/java/InstallJavaDialog.cpp | 1 + launcher/ui/java/InstallJavaDialog.h | 2 +- launcher/ui/pages/global/JavaPage.cpp | 5 ++--- launcher/ui/pages/global/JavaPage.ui | 8 -------- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/launcher/minecraft/launch/AutoInstallJava.cpp b/launcher/minecraft/launch/AutoInstallJava.cpp index 3ae6ffa88a..1dc91003f8 100644 --- a/launcher/minecraft/launch/AutoInstallJava.cpp +++ b/launcher/minecraft/launch/AutoInstallJava.cpp @@ -91,7 +91,21 @@ void AutoInstallJava::executeTask() emit progressReportingRequest(); return; } + if (m_supported_arch.isEmpty()) { + emit logLine(tr("Your system(%1 %2) is not compatible with auto java download. Using the default java path.") + .arg(SysInfo::currentSystem(), SysInfo::useQTForArch()), + MessageLevel::Warning); + emitSucceeded(); + return; + } auto wantedJavaName = packProfile->getProfile()->getCompatibleJavaName(); + if (wantedJavaName.isEmpty()) { + emit logLine(tr("Your meta informtation is old or doesn't have the information necesary to determine what java should be used. " + "Using the default java path."), + MessageLevel::Warning); + emitSucceeded(); + return; + } QDir javaDir(APPLICATION->javaPath()); auto wantedJavaPath = javaDir.absoluteFilePath(wantedJavaName); if (QFileInfo::exists(wantedJavaPath)) { @@ -136,7 +150,8 @@ void AutoInstallJava::setJavaPathFromPartial() if (QFileInfo::exists(finalPath)) { setJavaPath(finalPath); } else { - emit logLine(tr("No compatible java version was found. Using the default one."), MessageLevel::Warning); + emit logLine(tr("No compatible java version was found(the binary file doesn't exists). Using the default one."), + MessageLevel::Warning); emitSucceeded(); } return; @@ -188,6 +203,8 @@ void AutoInstallJava::tryNextMajorJava() auto wantedJavaName = packProfile->getProfile()->getCompatibleJavaName(); auto majorJavaVersions = packProfile->getProfile()->getCompatibleJavaMajors(); if (m_majorJavaVersionIndex >= majorJavaVersions.length()) { + emit logLine(tr("No Java versions found for your operating system: %1 %2").arg(SysInfo::currentSystem(), SysInfo::useQTForArch()), + MessageLevel::Warning); emit logLine(tr("No compatible java version was found. Using the default one."), MessageLevel::Warning); emitSucceeded(); return; diff --git a/launcher/ui/java/InstallJavaDialog.cpp b/launcher/ui/java/InstallJavaDialog.cpp index 40616aafee..874fc9133d 100644 --- a/launcher/ui/java/InstallJavaDialog.cpp +++ b/launcher/ui/java/InstallJavaDialog.cpp @@ -72,6 +72,7 @@ class InstallJavaPage : public QWidget, public BasePage { void initialize(Meta::VersionList::Ptr vlist) { vlist->setProvidedRoles({ BaseVersionList::VersionRole, BaseVersionList::RecommendedRole, BaseVersionList::VersionPointerRole }); + vlist->sort(1); majorVersionSelect->initialize(vlist.get()); } diff --git a/launcher/ui/java/InstallJavaDialog.h b/launcher/ui/java/InstallJavaDialog.h index c98c1deae3..525e750aa7 100644 --- a/launcher/ui/java/InstallJavaDialog.h +++ b/launcher/ui/java/InstallJavaDialog.h @@ -27,7 +27,7 @@ class PackProfile; class QDialogButtonBox; namespace Java { -class InstallDialog final : public QDialog, public BasePageProvider { +class InstallDialog final : public QDialog, private BasePageProvider { Q_OBJECT public: diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 41a666cf26..6d8b95c24b 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -66,8 +66,8 @@ JavaPage::JavaPage(QWidget* parent) : QWidget(parent), ui(new Ui::JavaPage) if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { ui->managedJavaList->initialize(new JavaInstallList(this, true)); ui->managedJavaList->selectCurrent(); - ui->managedJavaList->setEmptyString(tr("No java versions are currently available in the meta")); - ui->managedJavaList->setEmptyErrorString(tr("Couldn't load or download the java version lists!")); + ui->managedJavaList->setEmptyString(tr("No managed java versions are installed")); + ui->managedJavaList->setEmptyErrorString(tr("Couldn't load the managed java list!")); connect(ui->autodetectJavaCheckBox, &QCheckBox::stateChanged, this, [this] { ui->autodownloadCheckBox->setEnabled(ui->autodetectJavaCheckBox->isChecked()); if (!ui->autodetectJavaCheckBox->isChecked()) @@ -75,7 +75,6 @@ JavaPage::JavaPage(QWidget* parent) : QWidget(parent), ui(new Ui::JavaPage) }); } else { ui->autodownloadCheckBox->setHidden(true); - ui->javaDownloadBtn->setHidden(true); ui->tabWidget->tabBar()->hide(); } diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index dc1668ee3d..c8f62e0757 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -178,13 +178,6 @@ - - - - Download Java - - - @@ -424,7 +417,6 @@ permGenSpinBox javaPathTextBox javaBrowseBtn - javaDownloadBtn javaDetectBtn javaTestBtn skipCompatibilityCheckbox From ab7fc2e46cd1ebe3d29436c8939de57d681a24a2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 21 Mar 2024 00:42:04 +0200 Subject: [PATCH 0162/2054] Fixed archive java download Signed-off-by: Trial97 --- launcher/MMCZip.cpp | 4 ++++ launcher/java/download/ArchiveDownloadTask.cpp | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 265eb8b7a5..33f5f88fcb 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -510,6 +510,10 @@ bool ExportToZipTask::abort() void ExtractZipTask::executeTask() { + if (!m_input->isOpen() && !m_input->open(QuaZip::mdUnzip)) { + emitFailed(tr("Unable to open supplied zip file.")); + return; + } m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); }); connect(&m_zip_watcher, &QFutureWatcher::finished, this, &ExtractZipTask::finish); m_zip_watcher.setFuture(m_zip_future); diff --git a/launcher/java/download/ArchiveDownloadTask.cpp b/launcher/java/download/ArchiveDownloadTask.cpp index 34f60260ed..d62d4d8e2f 100644 --- a/launcher/java/download/ArchiveDownloadTask.cpp +++ b/launcher/java/download/ArchiveDownloadTask.cpp @@ -35,7 +35,7 @@ void ArchiveDownloadTask::executeTask() // JRE found ! download the zip setStatus(tr("Downloading Java")); - MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("java", m_url.toLocalFile()); + MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("java", m_url.fileName()); auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); auto action = Net::Download::makeCached(m_url, entry); @@ -70,12 +70,16 @@ void ArchiveDownloadTask::extractJava(QString input) { setStatus(tr("Extracting java")); auto zip = std::make_shared(input); + if (!zip->open(QuaZip::mdUnzip)) { + emitFailed(tr("Unable to open supplied zip file.")); + return; + } auto files = zip->getFileNameList(); if (files.isEmpty()) { emitFailed("Empty archive"); return; } - auto zipTask = makeShared(input, m_final_path, files[0]); + auto zipTask = makeShared(zip, m_final_path, files[0]); auto progressStep = std::make_shared(); connect(zipTask.get(), &Task::finished, this, [this, progressStep] { From 1a6dfd04d66edb667f2ceb6646620b219dc17daf Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 21 Mar 2024 19:46:57 +0200 Subject: [PATCH 0163/2054] Made tar.gz parser Signed-off-by: Trial97 --- launcher/Application.cpp | 1 + launcher/BaseVersionList.cpp | 9 + launcher/BaseVersionList.h | 1 + launcher/CMakeLists.txt | 2 + launcher/FileSystem.cpp | 3 + launcher/Untar.cpp | 262 ++++++++++++++++++ launcher/Untar.h | 46 +++ launcher/VersionProxyModel.cpp | 9 + launcher/VersionProxyModel.h | 2 +- .../java/download/ArchiveDownloadTask.cpp | 23 ++ launcher/meta/VersionList.cpp | 7 + .../minecraft/launch/VerifyJavaInstall.cpp | 1 - launcher/ui/java/InstallJavaDialog.cpp | 12 +- launcher/ui/java/VersionList.cpp | 7 + launcher/ui/widgets/JavaSettingsWidget.cpp | 2 +- 15 files changed, 381 insertions(+), 6 deletions(-) create mode 100644 launcher/Untar.cpp create mode 100644 launcher/Untar.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index b0929a0ae4..f7fa3aedd6 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -869,6 +869,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); m_metacache->addBase("meta", QDir("meta").absolutePath()); + m_metacache->addBase("java", QDir("cache/java").absolutePath()); m_metacache->Load(); qDebug() << "<> Cache initialized."; } diff --git a/launcher/BaseVersionList.cpp b/launcher/BaseVersionList.cpp index afee8388a1..22077c9623 100644 --- a/launcher/BaseVersionList.cpp +++ b/launcher/BaseVersionList.cpp @@ -78,6 +78,14 @@ QVariant BaseVersionList::data(const QModelIndex& index, int role) const case TypeRole: return version->typeString(); + case JavaMajorRole: { + auto major = version->name(); + if (major.startsWith("java")) { + major = "Java " + major.mid(4); + } + return major; + } + default: return QVariant(); } @@ -112,5 +120,6 @@ QHash BaseVersionList::roleNames() const roles.insert(PathRole, "path"); roles.insert(JavaNameRole, "javaName"); roles.insert(CPUArchitectureRole, "architecture"); + roles.insert(JavaMajorRole, "javaMajor"); return roles; } diff --git a/launcher/BaseVersionList.h b/launcher/BaseVersionList.h index bc37e9e53e..673d135628 100644 --- a/launcher/BaseVersionList.h +++ b/launcher/BaseVersionList.h @@ -49,6 +49,7 @@ class BaseVersionList : public QAbstractListModel { BranchRole, PathRole, JavaNameRole, + JavaMajorRole, CPUArchitectureRole, SortRole }; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index dd493682eb..4f8ab598ef 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -24,6 +24,8 @@ set(CORE_SOURCES NullInstance.h MMCZip.h MMCZip.cpp + Untar.h + Untar.cpp StringUtils.h StringUtils.cpp QVariantUtils.h diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index f9be91a2ae..8320dfe408 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -276,6 +276,9 @@ bool ensureFolderPathExists(const QFileInfo folderPath) { QDir dir; QString ensuredPath = folderPath.filePath(); + if (folderPath.exists()) + return true; + bool success = dir.mkpath(ensuredPath); return success; } diff --git a/launcher/Untar.cpp b/launcher/Untar.cpp new file mode 100644 index 0000000000..521c504a6e --- /dev/null +++ b/launcher/Untar.cpp @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023-2024 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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 "Untar.h" +#include +#include +#include +#include +#include +#include +#include +#include "FileSystem.h" + +// adaptation of the: +// - https://github.com/madler/zlib/blob/develop/contrib/untgz/untgz.c +// - https://en.wikipedia.org/wiki/Tar_(computing) +// - https://github.com/euroelessar/cutereader/blob/master/karchive/src/ktar.cpp + +#define BLOCKSIZE 512 +#define SHORTNAMESIZE 100 + +enum class TypeFlag : char { + Regular = '0', // regular file + ARegular = 0, // regular file + Link = '1', // link + Symlink = '2', // reserved + Character = '3', // character special + Block = '4', // block special + Directory = '5', // directory + FIFO = '6', // FIFO special + Contiguous = '7', // reserved + // Posix stuff + GlobalPosixHeader = 'g', + ExtendedPosixHeader = 'x', + // 'A'– 'Z' Vendor specific extensions(POSIX .1 - 1988) + // GNU + GNULongLink = 'K', /* long link name */ + GNULongName = 'L', /* long file name */ +}; + +struct Header { /* byte offset */ + char name[100]; /* 0 */ + char mode[8]; /* 100 */ + char uid[8]; /* 108 */ + char gid[8]; /* 116 */ + char size[12]; /* 124 */ + char mtime[12]; /* 136 */ + char chksum[8]; /* 148 */ + TypeFlag typeflag; /* 156 */ + char linkname[100]; /* 157 */ + char magic[6]; /* 257 */ + char version[2]; /* 263 */ + char uname[32]; /* 265 */ + char gname[32]; /* 297 */ + char devmajor[8]; /* 329 */ + char devminor[8]; /* 337 */ + char prefix[155]; /* 345 */ + /* 500 */ +}; + +union Buffer { + char buffer[BLOCKSIZE]; + struct Header header; +}; + +bool readLonglink(QIODevice* in, Buffer buffer, QByteArray& longlink) +{ + qint64 n = 0; + qint64 size = strtoll(buffer.header.size, NULL, 8); + size--; // ignore trailing null + if (errno == ERANGE) { + qCritical() << "The filename size can't be read"; + return false; + } + if (size < 0) { + qCritical() << "The filename size is negative"; + return false; + } + longlink.resize(size + (BLOCKSIZE - size % BLOCKSIZE)); // make the size divisible by BLOCKSIZE + for (qint64 offset = 0; offset < longlink.size(); offset += BLOCKSIZE) { + n = in->read(longlink.data() + offset, BLOCKSIZE); + if (n != BLOCKSIZE) { + qCritical() << "The expected blocksize was not respected for the name"; + return false; + } + } + longlink.truncate(qstrlen(longlink.constData())); + return true; +} + +bool Tar::extract(QIODevice* in, QString dst) +{ + Buffer buffer; + QString name, symlink, firstFolderName; + bool doNotReset = false; + while (true) { + auto n = in->read(buffer.buffer, BLOCKSIZE); + if (n != BLOCKSIZE) { // allways expect complete blocks + qCritical() << "The expected blocksize was not respected"; + return false; + } + if (buffer.header.name[0] == 0) { // end of archive + return true; + } + int mode = strtol(buffer.header.mode, NULL, 8) | QFile::ReadUser | QFile::WriteUser; // hack to ensure write and read permisions + if (errno == ERANGE) { + qCritical() << "The file mode can't be read"; + return false; + } + // there are names that are exactly 100 bytes long + // and neither longlink nor \0 terminated (bug:101472) + if (name.isEmpty()) { + name = QFile::decodeName(QByteArray(buffer.header.name, qstrnlen(buffer.header.name, 100))); + if (!firstFolderName.isEmpty() && name.startsWith(firstFolderName)) { + name = name.mid(firstFolderName.size()); + } + } + if (symlink.isEmpty()) + symlink = QFile::decodeName(QByteArray(buffer.header.linkname, qstrnlen(buffer.header.linkname, 100))); + switch (buffer.header.typeflag) { + case TypeFlag::Regular: + /* fallthrough */ + case TypeFlag::ARegular: { + auto fileName = FS::PathCombine(dst, name); + if (!FS::ensureFilePathExists(fileName)) { + qCritical() << "Can't ensure the file path to exist: " << fileName; + return false; + } + QFile out(fileName); + if (!out.open(QFile::WriteOnly, QFile::Permission(mode))) { + qCritical() << "Can't open file:" << fileName; + return false; + } + qint64 size = strtoll(buffer.header.size, NULL, 8); + if (errno == ERANGE) { + qCritical() << "The file size can't be read"; + return false; + } + while (size > 0) { + QByteArray tmp(BLOCKSIZE, 0); + n = in->read(tmp.data(), BLOCKSIZE); + if (n != BLOCKSIZE) { + qCritical() << "The expected blocksize was not respected when reading file"; + return false; + } + tmp.truncate(qMin(BLOCKSIZE, size)); + out.write(tmp); + size -= BLOCKSIZE; + } + QFile::setPermissions(fileName, QFile::Permissions(mode)); + break; + } + case TypeFlag::Directory: { + if (firstFolderName.isEmpty()) { + firstFolderName = name; + break; + } + auto folderPath = FS::PathCombine(dst, name); + if (!FS::ensureFolderPathExists(folderPath)) { + qCritical() << "Can't ensure that folder exists: " << folderPath; + return false; + } + break; + } + case TypeFlag::GNULongLink: { + doNotReset = true; + QByteArray longlink; + if (readLonglink(in, buffer, longlink)) { + symlink = QFile::decodeName(longlink.constData()); + } else { + qCritical() << "Failed to read long link"; + return false; + } + break; + } + case TypeFlag::GNULongName: { + doNotReset = true; + QByteArray longlink; + if (readLonglink(in, buffer, longlink)) { + name = QFile::decodeName(longlink.constData()); + } else { + qCritical() << "Failed to read long name"; + return false; + } + break; + } + case TypeFlag::Link: + /* fallthrough */ + case TypeFlag::Symlink: { + auto fileName = FS::PathCombine(dst, name); + if (!FS::create_link(FS::PathCombine(QFileInfo(fileName).path(), symlink), fileName)()) { // do not use symlinks + qCritical() << "Can't create link for:" << fileName << " to:" << FS::PathCombine(QFileInfo(fileName).path(), symlink); + return false; + } + FS::ensureFilePathExists(fileName); + QFile::setPermissions(fileName, QFile::Permissions(mode)); + break; + } + case TypeFlag::Character: + /* fallthrough */ + case TypeFlag::Block: + /* fallthrough */ + case TypeFlag::FIFO: + /* fallthrough */ + case TypeFlag::Contiguous: + /* fallthrough */ + case TypeFlag::GlobalPosixHeader: + /* fallthrough */ + case TypeFlag::ExtendedPosixHeader: + /* fallthrough */ + default: + break; + } + if (!doNotReset) { + name.truncate(0); + symlink.truncate(0); + } + doNotReset = false; + } + return true; +} + +bool GZTar::extract(QString src, QString dst) +{ + QuaGzipFile a(src); + if (!a.open(QIODevice::ReadOnly)) { + qCritical() << "Can't open tar file:" << src; + return false; + } + return Tar::extract(&a, dst); +} \ No newline at end of file diff --git a/launcher/Untar.h b/launcher/Untar.h new file mode 100644 index 0000000000..54010e085f --- /dev/null +++ b/launcher/Untar.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023-2024 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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 + +// this is a hack used for the java downloader (feel free to remove it in favor of a library) +// both extract functions will extract the first folder inside dest(disregarding the prefix) +namespace Tar { +bool extract(QIODevice* in, QString dst); +}; + +namespace GZTar { +bool extract(QString src, QString dst); +}; \ No newline at end of file diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index 070e952a4a..62cf4c2215 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -120,6 +120,8 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation, return tr("Path"); case JavaName: return tr("Java Name"); + case JavaMajor: + return tr("Major"); case Time: return tr("Released"); } @@ -139,6 +141,8 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation, return tr("Filesystem path to this version"); case JavaName: return tr("The alternative name of the java version"); + case JavaMajor: + return tr("The java major version"); case Time: return tr("Release date of this version"); } @@ -175,6 +179,8 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const return sourceModel()->data(parentIndex, BaseVersionList::PathRole); case JavaName: return sourceModel()->data(parentIndex, BaseVersionList::JavaNameRole); + case JavaMajor: + return sourceModel()->data(parentIndex, BaseVersionList::JavaMajorRole); case Time: return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate(); default: @@ -323,6 +329,9 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw) if (roles.contains(BaseVersionList::JavaNameRole)) { m_columns.push_back(JavaName); } + if (roles.contains(BaseVersionList::JavaMajorRole)) { + m_columns.push_back(JavaMajor); + } if (roles.contains(Meta::VersionList::TimeRole)) { m_columns.push_back(Time); } diff --git a/launcher/VersionProxyModel.h b/launcher/VersionProxyModel.h index cb55b7f14a..8aea257954 100644 --- a/launcher/VersionProxyModel.h +++ b/launcher/VersionProxyModel.h @@ -9,7 +9,7 @@ class VersionFilterModel; class VersionProxyModel : public QAbstractProxyModel { Q_OBJECT public: - enum Column { Name, ParentVersion, Branch, Type, CPUArchitecture, Path, Time, JavaName }; + enum Column { Name, ParentVersion, Branch, Type, CPUArchitecture, Path, Time, JavaName, JavaMajor }; using FilterMap = QHash>; public: diff --git a/launcher/java/download/ArchiveDownloadTask.cpp b/launcher/java/download/ArchiveDownloadTask.cpp index d62d4d8e2f..3588606104 100644 --- a/launcher/java/download/ArchiveDownloadTask.cpp +++ b/launcher/java/download/ArchiveDownloadTask.cpp @@ -21,6 +21,7 @@ #include "MMCZip.h" #include "Application.h" +#include "Untar.h" #include "net/ChecksumValidator.h" #include "net/NetJob.h" #include "tasks/Task.h" @@ -69,6 +70,28 @@ void ArchiveDownloadTask::executeTask() void ArchiveDownloadTask::extractJava(QString input) { setStatus(tr("Extracting java")); + if (input.endsWith("tar")) { + setStatus(tr("Extracting java(the progress will not be reported for tar)")); + QFile in(input); + if (!in.open(QFile::ReadOnly)) { + emitFailed(tr("Unable to open supplied tar file.")); + return; + } + if (!Tar::extract(&in, QDir(m_final_path).absolutePath())) { + emitFailed(tr("Unable to extract supplied tar file.")); + return; + } + emitSucceeded(); + return; + } else if (input.endsWith("tar.gz") || input.endsWith("taz") || input.endsWith("tgz")) { + setStatus(tr("Extracting java(the progress will not be reported for tar)")); + if (!GZTar::extract(input, QDir(m_final_path).absolutePath())) { + emitFailed(tr("Unable to extract supplied tar file.")); + return; + } + emitSucceeded(); + return; + } auto zip = std::make_shared(input); if (!zip->open(QuaZip::mdUnzip)) { emitFailed(tr("Unable to open supplied zip file.")); diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp index 76b914b6a3..66412d6a40 100644 --- a/launcher/meta/VersionList.cpp +++ b/launcher/meta/VersionList.cpp @@ -92,6 +92,13 @@ QVariant VersionList::data(const QModelIndex& index, int role) const return QVariant::fromValue(version); case RecommendedRole: return version->isRecommended(); + case JavaMajorRole: { + auto major = version->version(); + if (major.startsWith("java")) { + major = "Java " + major.mid(4); + } + return major; + } // FIXME: this should be determined in whatever view/proxy is used... // case LatestRole: return version == getLatestStable(); default: diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp index f26b850ebd..5f8b35b02c 100644 --- a/launcher/minecraft/launch/VerifyJavaInstall.cpp +++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp @@ -54,7 +54,6 @@ void VerifyJavaInstall::executeTask() auto javaArchitecture = settings->get("JavaArchitecture").toString(); auto maxMemAlloc = settings->get("MaxMemAlloc").toInt(); - emit logLine(tr("Java architecture is x%1.").arg(javaArchitecture), MessageLevel::Info); if (javaArchitecture == "32" && maxMemAlloc > 2048) { emit logLine(tr("Max memory allocation exceeds the supported value.\n" "The selected java is 32-bit and doesn't support more than 2048MiB of RAM.\n" diff --git a/launcher/ui/java/InstallJavaDialog.cpp b/launcher/ui/java/InstallJavaDialog.cpp index 874fc9133d..78de3e20a0 100644 --- a/launcher/ui/java/InstallJavaDialog.cpp +++ b/launcher/ui/java/InstallJavaDialog.cpp @@ -19,16 +19,19 @@ #include "InstallJavaDialog.h" #include +#include #include #include #include #include "Application.h" +#include "BaseVersionList.h" #include "FileSystem.h" #include "java/download/ArchiveDownloadTask.h" #include "java/download/ManifestDownloadTask.h" #include "meta/Index.h" #include "meta/VersionList.h" +#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui/java/VersionList.h" #include "ui/widgets/PageContainer.h" @@ -71,8 +74,7 @@ class InstallJavaPage : public QWidget, public BasePage { //! loads the list if needed. void initialize(Meta::VersionList::Ptr vlist) { - vlist->setProvidedRoles({ BaseVersionList::VersionRole, BaseVersionList::RecommendedRole, BaseVersionList::VersionPointerRole }); - vlist->sort(1); + vlist->setProvidedRoles({ BaseVersionList::JavaMajorRole, BaseVersionList::RecommendedRole, BaseVersionList::VersionPointerRole }); majorVersionSelect->initialize(vlist.get()); } @@ -219,7 +221,11 @@ void InstallDialog::done(int result) break; } auto deletePath = [final_path] { FS::deletePath(final_path); }; - connect(task.get(), &Task::failed, this, deletePath); + connect(task.get(), &Task::failed, this, [this, &deletePath](QString reason) { + QString error = QString("Java download failed: %1").arg(reason); + CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show(); + deletePath(); + }); connect(task.get(), &Task::aborted, this, deletePath); ProgressDialog pg(this); pg.setSkipButton(true, tr("Abort")); diff --git a/launcher/ui/java/VersionList.cpp b/launcher/ui/java/VersionList.cpp index 78448ddafe..5e385604d6 100644 --- a/launcher/ui/java/VersionList.cpp +++ b/launcher/ui/java/VersionList.cpp @@ -78,6 +78,13 @@ QVariant VersionList::data(const QModelIndex& index, int role) const return false; // do not recommend any version case JavaNameRole: return version->name(); + case JavaMajorRole: { + auto major = version->version.toString(); + if (major.startsWith("java")) { + major = "Java " + major.mid(4); + } + return major; + } case TypeRole: return version->packageType; case Meta::VersionList::TimeRole: diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index e2e4980678..1645f2b5f6 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -180,7 +180,7 @@ void JavaSettingsWidget::initialize() tr("%1 can automatically download the correct Java version for each version of Minecraft..\n" "Do you want to enable Java auto-download?\n") .arg(BuildConfig.LAUNCHER_DISPLAYNAME), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) + QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) ->exec(); if (button == QMessageBox::Yes) { m_autodetectJavaCheckBox->setChecked(true); From 3046822272ee97a831667db1c8a222eb304c259e Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 21 Mar 2024 19:53:52 +0200 Subject: [PATCH 0164/2054] Fixed buid Signed-off-by: Trial97 --- launcher/Untar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/Untar.cpp b/launcher/Untar.cpp index 521c504a6e..b2f05038c3 100644 --- a/launcher/Untar.cpp +++ b/launcher/Untar.cpp @@ -158,10 +158,11 @@ bool Tar::extract(QIODevice* in, QString dst) return false; } QFile out(fileName); - if (!out.open(QFile::WriteOnly, QFile::Permission(mode))) { + if (!out.open(QFile::WriteOnly)) { qCritical() << "Can't open file:" << fileName; return false; } + out.setPermissions(QFile::Permissions(mode)); qint64 size = strtoll(buffer.header.size, NULL, 8); if (errno == ERANGE) { qCritical() << "The file size can't be read"; @@ -178,7 +179,6 @@ bool Tar::extract(QIODevice* in, QString dst) out.write(tmp); size -= BLOCKSIZE; } - QFile::setPermissions(fileName, QFile::Permissions(mode)); break; } case TypeFlag::Directory: { From a7029a9e53cd3d9a2371cd26c257beb898251667 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 21 Mar 2024 20:01:40 +0200 Subject: [PATCH 0165/2054] Fixed qMin Signed-off-by: Trial97 --- launcher/Untar.cpp | 2 +- launcher/Untar.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/Untar.cpp b/launcher/Untar.cpp index b2f05038c3..bae6c5d17f 100644 --- a/launcher/Untar.cpp +++ b/launcher/Untar.cpp @@ -175,7 +175,7 @@ bool Tar::extract(QIODevice* in, QString dst) qCritical() << "The expected blocksize was not respected when reading file"; return false; } - tmp.truncate(qMin(BLOCKSIZE, size)); + tmp.truncate(qMin(qint64(BLOCKSIZE), size)); out.write(tmp); size -= BLOCKSIZE; } diff --git a/launcher/Untar.h b/launcher/Untar.h index 54010e085f..50e3a16e32 100644 --- a/launcher/Untar.h +++ b/launcher/Untar.h @@ -39,8 +39,8 @@ // both extract functions will extract the first folder inside dest(disregarding the prefix) namespace Tar { bool extract(QIODevice* in, QString dst); -}; +} namespace GZTar { bool extract(QString src, QString dst); -}; \ No newline at end of file +} \ No newline at end of file From 0b87e4c03b55c96966255eb8440b0c4e7b182c63 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 22 Mar 2024 12:29:15 +0200 Subject: [PATCH 0166/2054] Fiexed codeql warning Signed-off-by: Trial97 --- launcher/Untar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/Untar.cpp b/launcher/Untar.cpp index bae6c5d17f..1d11dfd60b 100644 --- a/launcher/Untar.cpp +++ b/launcher/Untar.cpp @@ -94,7 +94,7 @@ union Buffer { struct Header header; }; -bool readLonglink(QIODevice* in, Buffer buffer, QByteArray& longlink) +bool readLonglink(QIODevice* in, Buffer& buffer, QByteArray& longlink) { qint64 n = 0; qint64 size = strtoll(buffer.header.size, NULL, 8); From c0754b80a2b8612ce2874888c0f40b41b2eac765 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 22 Mar 2024 22:17:58 +0200 Subject: [PATCH 0167/2054] Add resize to last column Signed-off-by: Trial97 --- launcher/ui/pages/global/JavaPage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 6d8b95c24b..fda4f87052 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -65,6 +65,7 @@ JavaPage::JavaPage(QWidget* parent) : QWidget(parent), ui(new Ui::JavaPage) if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { ui->managedJavaList->initialize(new JavaInstallList(this, true)); + ui->managedJavaList->setResizeOn(2); ui->managedJavaList->selectCurrent(); ui->managedJavaList->setEmptyString(tr("No managed java versions are installed")); ui->managedJavaList->setEmptyErrorString(tr("Couldn't load the managed java list!")); From 93f6315b16de3b2170d0263d7befcac5dcfaa01d Mon Sep 17 00:00:00 2001 From: Alexandru Ionut Tripon Date: Wed, 27 Mar 2024 19:34:04 +0200 Subject: [PATCH 0168/2054] Update CMakeLists.txt Co-authored-by: seth Signed-off-by: Alexandru Ionut Tripon --- CMakeLists.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 40ccff5957..f4298aedba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -220,7 +220,17 @@ set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against") # Java downloader -option(ENABLE_JAVA_DOWNLOADER "Build the java downloader feature" ON) +set(ENABLE_JAVA_DOWNLOADER_DEFAULT ON) + +# Although we recommend enabling this, we cannot guarantee binary compatibility on +# differing Linux/BSD/etc distributions. Downstream packagers should be explicitly opt-ing into this +# feature if they know it will work with their distribution. +if(UNIX AND NOT APPLE) + set(ENABLE_JAVA_DOWNLOADER_DEFAULT OFF) +endif() + +# Java downloader +option(ENABLE_JAVA_DOWNLOADER "Build the java downloader feature" ${ENABLE_JAVA_DOWNLOADER_DEFAULT}) # Native libraries if(UNIX AND APPLE) From 45028ddc6142b019d28dadd31b8c87433adea312 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 28 Mar 2024 14:58:05 +0200 Subject: [PATCH 0169/2054] Added java downloader to CI build Signed-off-by: Trial97 --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9cb737df6e..500aca5cb1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -274,23 +274,23 @@ jobs: - name: Configure CMake (macOS) if: runner.os == 'macOS' && matrix.qt_ver == 6 run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja - name: Configure CMake (macOS-Legacy) if: runner.os == 'macOS' && matrix.qt_ver == 5 run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja - name: Configure CMake (Windows MinGW-w64) if: runner.os == 'Windows' && matrix.msystem != '' shell: msys2 {0} run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja - name: Configure CMake (Windows MSVC) if: runner.os == 'Windows' && matrix.msystem == '' run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) if ("${{ env.CCACHE_VAR }}") { @@ -305,7 +305,7 @@ jobs: - name: Configure CMake (Linux) if: runner.os == 'Linux' run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -G Ninja ## # BUILD From 81874f22f74e3d254e16c3054abb28881b50d01a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 29 Mar 2024 00:10:59 +0200 Subject: [PATCH 0170/2054] Fixed tar extract Signed-off-by: Trial97 --- launcher/Untar.cpp | 96 +++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 49 deletions(-) diff --git a/launcher/Untar.cpp b/launcher/Untar.cpp index 1d11dfd60b..f1963e7aaf 100644 --- a/launcher/Untar.cpp +++ b/launcher/Untar.cpp @@ -33,13 +33,11 @@ * limitations under the License. */ #include "Untar.h" -#include -#include #include #include +#include #include #include -#include #include "FileSystem.h" // adaptation of the: @@ -69,40 +67,30 @@ enum class TypeFlag : char { GNULongName = 'L', /* long file name */ }; -struct Header { /* byte offset */ - char name[100]; /* 0 */ - char mode[8]; /* 100 */ - char uid[8]; /* 108 */ - char gid[8]; /* 116 */ - char size[12]; /* 124 */ - char mtime[12]; /* 136 */ - char chksum[8]; /* 148 */ - TypeFlag typeflag; /* 156 */ - char linkname[100]; /* 157 */ - char magic[6]; /* 257 */ - char version[2]; /* 263 */ - char uname[32]; /* 265 */ - char gname[32]; /* 297 */ - char devmajor[8]; /* 329 */ - char devminor[8]; /* 337 */ - char prefix[155]; /* 345 */ - /* 500 */ -}; +// struct Header { /* byte offset */ +// char name[100]; /* 0 */ +// char mode[8]; /* 100 */ +// char uid[8]; /* 108 */ +// char gid[8]; /* 116 */ +// char size[12]; /* 124 */ +// char mtime[12]; /* 136 */ +// char chksum[8]; /* 148 */ +// TypeFlag typeflag; /* 156 */ +// char linkname[100]; /* 157 */ +// char magic[6]; /* 257 */ +// char version[2]; /* 263 */ +// char uname[32]; /* 265 */ +// char gname[32]; /* 297 */ +// char devmajor[8]; /* 329 */ +// char devminor[8]; /* 337 */ +// char prefix[155]; /* 345 */ +// /* 500 */ +// }; -union Buffer { - char buffer[BLOCKSIZE]; - struct Header header; -}; - -bool readLonglink(QIODevice* in, Buffer& buffer, QByteArray& longlink) +bool readLonglink(QIODevice* in, qint64 size, QByteArray& longlink) { qint64 n = 0; - qint64 size = strtoll(buffer.header.size, NULL, 8); size--; // ignore trailing null - if (errno == ERANGE) { - qCritical() << "The filename size can't be read"; - return false; - } if (size < 0) { qCritical() << "The filename size is negative"; return false; @@ -119,36 +107,51 @@ bool readLonglink(QIODevice* in, Buffer& buffer, QByteArray& longlink) return true; } +int getOctal(char* buffer, int maxlenght, bool* ok) +{ + return QByteArray(buffer, qstrnlen(buffer, maxlenght)).toInt(ok, 8); +} + +QString decodeName(char* name) +{ + return QFile::decodeName(QByteArray(name, qstrnlen(name, 100))); +} bool Tar::extract(QIODevice* in, QString dst) { - Buffer buffer; + char buffer[BLOCKSIZE]; QString name, symlink, firstFolderName; - bool doNotReset = false; + bool doNotReset = false, ok; while (true) { - auto n = in->read(buffer.buffer, BLOCKSIZE); + auto n = in->read(buffer, BLOCKSIZE); if (n != BLOCKSIZE) { // allways expect complete blocks qCritical() << "The expected blocksize was not respected"; return false; } - if (buffer.header.name[0] == 0) { // end of archive + if (buffer[0] == 0) { // end of archive return true; } - int mode = strtol(buffer.header.mode, NULL, 8) | QFile::ReadUser | QFile::WriteUser; // hack to ensure write and read permisions - if (errno == ERANGE) { + int mode = getOctal(buffer + 100, 8, &ok) | QFile::ReadUser | QFile::WriteUser; // hack to ensure write and read permisions + if (!ok) { qCritical() << "The file mode can't be read"; return false; } // there are names that are exactly 100 bytes long // and neither longlink nor \0 terminated (bug:101472) + if (name.isEmpty()) { - name = QFile::decodeName(QByteArray(buffer.header.name, qstrnlen(buffer.header.name, 100))); + name = decodeName(buffer); if (!firstFolderName.isEmpty() && name.startsWith(firstFolderName)) { name = name.mid(firstFolderName.size()); } } if (symlink.isEmpty()) - symlink = QFile::decodeName(QByteArray(buffer.header.linkname, qstrnlen(buffer.header.linkname, 100))); - switch (buffer.header.typeflag) { + symlink = decodeName(buffer); + qint64 size = getOctal(buffer + 124, 12, &ok); + if (!ok) { + qCritical() << "The file size can't be read"; + return false; + } + switch (TypeFlag(buffer[156])) { case TypeFlag::Regular: /* fallthrough */ case TypeFlag::ARegular: { @@ -163,11 +166,6 @@ bool Tar::extract(QIODevice* in, QString dst) return false; } out.setPermissions(QFile::Permissions(mode)); - qint64 size = strtoll(buffer.header.size, NULL, 8); - if (errno == ERANGE) { - qCritical() << "The file size can't be read"; - return false; - } while (size > 0) { QByteArray tmp(BLOCKSIZE, 0); n = in->read(tmp.data(), BLOCKSIZE); @@ -196,7 +194,7 @@ bool Tar::extract(QIODevice* in, QString dst) case TypeFlag::GNULongLink: { doNotReset = true; QByteArray longlink; - if (readLonglink(in, buffer, longlink)) { + if (readLonglink(in, size, longlink)) { symlink = QFile::decodeName(longlink.constData()); } else { qCritical() << "Failed to read long link"; @@ -207,7 +205,7 @@ bool Tar::extract(QIODevice* in, QString dst) case TypeFlag::GNULongName: { doNotReset = true; QByteArray longlink; - if (readLonglink(in, buffer, longlink)) { + if (readLonglink(in, size, longlink)) { name = QFile::decodeName(longlink.constData()); } else { qCritical() << "Failed to read long name"; From 71998cb7d99504d5a6dc16f0c2af606f10c33a94 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 1 Apr 2024 15:22:09 +0100 Subject: [PATCH 0171/2054] Rework filter widget Signed-off-by: TheKodeToad --- launcher/ui/widgets/ModFilterWidget.cpp | 204 ++++++------- launcher/ui/widgets/ModFilterWidget.h | 10 +- launcher/ui/widgets/ModFilterWidget.ui | 368 ++++++++++-------------- 3 files changed, 250 insertions(+), 332 deletions(-) diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index f419537c7e..b44a573d85 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -46,9 +46,9 @@ #include "Application.h" #include "minecraft/PackProfile.h" -unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, bool extendedSupport, QWidget* parent) +unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, bool extended, QWidget* parent) { - return unique_qobject_ptr(new ModFilterWidget(instance, extendedSupport, parent)); + return unique_qobject_ptr(new ModFilterWidget(instance, extended, parent)); } class VersionBasicModel : public QIdentityProxyModel { @@ -65,55 +65,50 @@ class VersionBasicModel : public QIdentityProxyModel { } }; -ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent) +ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWidget* parent) : QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter()) { ui->setupUi(this); m_versions_proxy = new VersionProxyModel(this); - m_versions_proxy->setFilter(BaseVersionList::TypeRole, new RegexpFilter("(release)", false)); + m_versions_proxy->setFilter(BaseVersionList::TypeRole, new ExactFilter("release")); auto proxy = new VersionBasicModel(this); proxy->setSourceModel(m_versions_proxy); - if (!extendedSupport) { - ui->versionsSimpleCb->setModel(proxy); - ui->versionsCb->hide(); - ui->snapshotsCb->hide(); - ui->envBox->hide(); + if (extended) { + ui->versions->setSourceModel(proxy); + ui->versions->setSeparator(", "); + ui->version->hide(); } else { - ui->versionsCb->setSourceModel(proxy); - ui->versionsCb->setSeparator("| "); - ui->versionsSimpleCb->hide(); + ui->version->setModel(proxy); + ui->versions->hide(); + ui->showAllVersions->hide(); + ui->environmentGroup->hide(); } - ui->versionsCb->setStyleSheet("combobox-popup: 0;"); - ui->versionsSimpleCb->setStyleSheet("combobox-popup: 0;"); - connect(ui->snapshotsCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onIncludeSnapshotsChanged); - connect(ui->versionsCb, QOverload::of(&QComboBox::currentIndexChanged), this, &ModFilterWidget::onVersionFilterChanged); - connect(ui->versionsSimpleCb, &QComboBox::currentTextChanged, this, &ModFilterWidget::onVersionFilterTextChanged); - - connect(ui->neoForgeCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - connect(ui->forgeCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - connect(ui->fabricCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - connect(ui->quiltCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - connect(ui->liteLoaderCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - connect(ui->cauldronCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - - ui->liteLoaderCb->hide(); - ui->cauldronCb->hide(); - - connect(ui->serverEnv, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged); - connect(ui->clientEnv, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged); - - connect(ui->hide_installed, &QCheckBox::stateChanged, this, &ModFilterWidget::onHideInstalledFilterChanged); - - connect(ui->releaseCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onReleaseFilterChanged); - connect(ui->betaCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onReleaseFilterChanged); - connect(ui->alphaCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onReleaseFilterChanged); - connect(ui->unknownCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onReleaseFilterChanged); + ui->versions->setStyleSheet("combobox-popup: 0;"); + ui->version->setStyleSheet("combobox-popup: 0;"); + connect(ui->showAllVersions, &QCheckBox::stateChanged, this, &ModFilterWidget::onShowAllVersionsChanged); + connect(ui->versions, QOverload::of(&QComboBox::currentIndexChanged), this, &ModFilterWidget::onVersionFilterChanged); + connect(ui->version, &QComboBox::currentTextChanged, this, &ModFilterWidget::onVersionFilterTextChanged); + + connect(ui->neoForge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + + connect(ui->neoForge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + + if (extended) { + connect(ui->clientSide, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged); + connect(ui->serverSide, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged); + } - connect(ui->categoriesList, &QListWidget::itemClicked, this, &ModFilterWidget::onCategoryClicked); + connect(ui->hideInstalled, &QCheckBox::stateChanged, this, &ModFilterWidget::onHideInstalledFilterChanged); setHidden(true); loadVersionList(); @@ -147,8 +142,8 @@ void ModFilterWidget::loadVersionList() auto task = m_version_list->getLoadTask(); connect(task.get(), &Task::failed, [this] { - ui->versionsCb->setEnabled(false); - ui->snapshotsCb->setEnabled(false); + ui->versions->setEnabled(false); + ui->showAllVersions->setEnabled(false); }); connect(task.get(), &Task::finished, &load_version_list_loop, &QEventLoop::quit); @@ -165,35 +160,35 @@ void ModFilterWidget::loadVersionList() void ModFilterWidget::prepareBasicFilter() { m_filter->hideInstalled = false; - m_filter->side = ""; // or "both"t + m_filter->side = ""; // or "both" auto loaders = m_instance->getPackProfile()->getSupportedModLoaders().value(); - ui->neoForgeCb->setChecked(loaders & ModPlatform::NeoForge); - ui->forgeCb->setChecked(loaders & ModPlatform::Forge); - ui->fabricCb->setChecked(loaders & ModPlatform::Fabric); - ui->quiltCb->setChecked(loaders & ModPlatform::Quilt); - ui->liteLoaderCb->setChecked(loaders & ModPlatform::LiteLoader); - ui->cauldronCb->setChecked(loaders & ModPlatform::Cauldron); + ui->neoForge->setChecked(loaders & ModPlatform::NeoForge); + ui->forge->setChecked(loaders & ModPlatform::Forge); + ui->fabric->setChecked(loaders & ModPlatform::Fabric); + ui->quilt->setChecked(loaders & ModPlatform::Quilt); m_filter->loaders = loaders; auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft"); - m_filter->versions.push_front(Version{ def }); - ui->versionsCb->setCheckedItems({ def }); - ui->versionsSimpleCb->setCurrentIndex(ui->versionsSimpleCb->findText(def)); + m_filter->versions.emplace_front(def); + ui->versions->setCheckedItems({ def }); + ui->version->setCurrentIndex(ui->version->findText(def)); } -void ModFilterWidget::onIncludeSnapshotsChanged() +void ModFilterWidget::onShowAllVersionsChanged() { - QString filter = "(release)"; - if (ui->snapshotsCb->isChecked()) - filter += "|(snapshot)"; - m_versions_proxy->setFilter(BaseVersionList::TypeRole, new RegexpFilter(filter, false)); + if (ui->showAllVersions->isChecked()) + m_versions_proxy->clearFilters(); + else + m_versions_proxy->setFilter(BaseVersionList::TypeRole, new ExactFilter("release")); } void ModFilterWidget::onVersionFilterChanged(int) { - auto versions = ui->versionsCb->checkedItems(); + auto versions = ui->versions->checkedItems(); m_filter->versions.clear(); - for (auto version : versions) - m_filter->versions.push_back(version); + + for (const QString& version : versions) + m_filter->versions.emplace_back(version); + m_filter_changed = true; emit filterChanged(); } @@ -201,18 +196,14 @@ void ModFilterWidget::onVersionFilterChanged(int) void ModFilterWidget::onLoadersFilterChanged() { ModPlatform::ModLoaderTypes loaders; - if (ui->neoForgeCb->isChecked()) + if (ui->neoForge->isChecked()) loaders |= ModPlatform::NeoForge; - if (ui->forgeCb->isChecked()) + if (ui->forge->isChecked()) loaders |= ModPlatform::Forge; - if (ui->fabricCb->isChecked()) + if (ui->fabric->isChecked()) loaders |= ModPlatform::Fabric; - if (ui->quiltCb->isChecked()) + if (ui->quilt->isChecked()) loaders |= ModPlatform::Quilt; - if (ui->cauldronCb->isChecked()) - loaders |= ModPlatform::Cauldron; - if (ui->liteLoaderCb->isChecked()) - loaders |= ModPlatform::LiteLoader; m_filter_changed = loaders != m_filter->loaders; m_filter->loaders = loaders; if (m_filter_changed) @@ -224,14 +215,17 @@ void ModFilterWidget::onLoadersFilterChanged() void ModFilterWidget::onSideFilterChanged() { QString side; - if (ui->serverEnv->isChecked()) - side = "server"; - if (ui->clientEnv->isChecked()) { - if (side.isEmpty()) + + if (ui->clientSide->isChecked() != ui->serverSide->isChecked()) { + if (ui->clientSide->isChecked()) side = "client"; else - side = ""; // or both + side = "server"; + } else { + // both are checked or none are checked; in either case no filtering will happen + side = ""; } + m_filter_changed = side != m_filter->side; m_filter->side = side; if (m_filter_changed) @@ -242,7 +236,7 @@ void ModFilterWidget::onSideFilterChanged() void ModFilterWidget::onHideInstalledFilterChanged() { - auto hide = ui->hide_installed->isChecked(); + auto hide = ui->hideInstalled->isChecked(); m_filter_changed = hide != m_filter->hideInstalled; m_filter->hideInstalled = hide; if (m_filter_changed) @@ -251,60 +245,36 @@ void ModFilterWidget::onHideInstalledFilterChanged() emit filterUnchanged(); } -void ModFilterWidget::onReleaseFilterChanged() -{ - std::list releases; - if (ui->releaseCb->isChecked()) - releases.push_back(ModPlatform::IndexedVersionType(ModPlatform::IndexedVersionType::VersionType::Release)); - if (ui->betaCb->isChecked()) - releases.push_back(ModPlatform::IndexedVersionType(ModPlatform::IndexedVersionType::VersionType::Beta)); - if (ui->alphaCb->isChecked()) - releases.push_back(ModPlatform::IndexedVersionType(ModPlatform::IndexedVersionType::VersionType::Alpha)); - if (ui->unknownCb->isChecked()) - releases.push_back(ModPlatform::IndexedVersionType(ModPlatform::IndexedVersionType::VersionType::Unknown)); - m_filter_changed = releases != m_filter->releases; - m_filter->releases = releases; - if (m_filter_changed) - emit filterChanged(); - else - emit filterUnchanged(); -} - -void ModFilterWidget::onVersionFilterTextChanged(QString version) +void ModFilterWidget::onVersionFilterTextChanged(const QString& version) { m_filter->versions.clear(); - m_filter->versions.push_front(version); + m_filter->versions.emplace_back(version); m_filter_changed = true; emit filterChanged(); } -void ModFilterWidget::setCategories(QList categories) +void ModFilterWidget::setCategories(const QList& categories) { - ui->categoriesList->clear(); m_categories = categories; - for (auto cat : categories) { - auto item = new QListWidgetItem(cat.name, ui->categoriesList); - item->setFlags(item->flags() & (~Qt::ItemIsUserCheckable)); - item->setCheckState(Qt::Unchecked); - ui->categoriesList->addItem(item); - } -} -void ModFilterWidget::onCategoryClicked(QListWidgetItem* itm) -{ - if (itm) - itm->setCheckState(itm->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked); - m_filter->categoryIds.clear(); - for (auto i = 0; i < ui->categoriesList->count(); i++) { - auto item = ui->categoriesList->item(i); - if (item->checkState() == Qt::Checked) { - auto c = std::find_if(m_categories.cbegin(), m_categories.cend(), [item](auto v) { return v.name == item->text(); }); - if (c != m_categories.cend()) - m_filter->categoryIds << c->id; - } + delete ui->categoryGroup->layout(); + auto layout = new QVBoxLayout(ui->categoryGroup); + + for (const auto& category : categories) { + auto checkbox = new QCheckBox(category.name); + layout->addWidget(checkbox); + + const QString id = category.id; + connect(checkbox, &QCheckBox::toggled, this, [this, id](bool checked) { + if (checked) + m_filter->categoryIds.append(id); + else + m_filter->categoryIds.removeOne(id); + + m_filter_changed = true; + emit filterChanged(); + }); } - m_filter_changed = true; - emit filterChanged(); -}; +} #include "ModFilterWidget.moc" \ No newline at end of file diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index dd98d9afaf..4358dd91da 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -73,7 +73,7 @@ class ModFilterWidget : public QTabWidget { bool operator!=(const Filter& other) const { return !(*this == other); } }; - static unique_qobject_ptr create(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); + static unique_qobject_ptr create(MinecraftInstance* instance, bool extended, QWidget* parent = nullptr); virtual ~ModFilterWidget(); auto getFilter() -> std::shared_ptr; @@ -84,7 +84,7 @@ class ModFilterWidget : public QTabWidget { void filterUnchanged(); public slots: - void setCategories(QList); + void setCategories(const QList&); private: ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); @@ -94,13 +94,11 @@ class ModFilterWidget : public QTabWidget { private slots: void onVersionFilterChanged(int); - void onVersionFilterTextChanged(QString version); - void onReleaseFilterChanged(); + void onVersionFilterTextChanged(const QString& version); void onLoadersFilterChanged(); void onSideFilterChanged(); void onHideInstalledFilterChanged(); - void onIncludeSnapshotsChanged(); - void onCategoryClicked(QListWidgetItem* item); + void onShowAllVersionsChanged(); private: Ui::ModFilterWidget* ui; diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui index 56cd33fa6f..27f3b86101 100644 --- a/launcher/ui/widgets/ModFilterWidget.ui +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -1,237 +1,187 @@ ModFilterWidget - + 0 0 - 460 - 132 + 553 + 604 - - - 0 - 0 - + + Form - - 0 - - - - Minecraft versions - - - - - - Include Snapshots - - - - - - - - 0 - 0 - - - - 5 - - - QComboBox::AdjustToContentsOnFirstShow - - - - - - - - - - - Loaders - - - - QLayout::SetMinimumSize - - - - - Fabric - - - - - - - NeoForge - - - - - - - Forge - - - - - - - false - - - Cauldron - - - - - - - Quilt - - - - - - - false - - - LiteLoader - - - - - - - - Release type - - - - - - Release - - - - - - - Beta - - - - - - - Alpha - - - - - - - Unknown - - - - - - - - Categories - - - - - - QAbstractScrollArea::AdjustToContents - - - QAbstractItemView::NoEditTriggers - - - false - - - QAbstractItemView::NoSelection - - - QAbstractItemView::ScrollPerItem - - - QListView::LeftToRight - - - true - - - QListView::ListMode - - - 0 - - - - - - - - Others - - - - - - Environments - - - - QLayout::SetDefaultConstraint - + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 553 + 604 + + + - - - Client + + + Category + + + false + + + false - - - Server + + + Loader + + + false + + false + + + + + + NeoForge + + + + + + + Forge + + + + + + + Fabric + + + + + + + Quilt + + + + - - - - - - - Instaled status - - - + + + Version + + + false + + + false + + + + + + + + + + + + Show all versions + + + + + + + + + + Environment + + + false + + + false + + + + + + Client + + + + + + + Server + + + + + + + + - Hide already installed + Hide installed items + + + + Qt::Vertical + + + + 20 + 40 + + + + - - - + + + From 71904b52c1cd6addd467a44b9dffd9a2f96f30e4 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 1 Apr 2024 15:31:10 +0100 Subject: [PATCH 0172/2054] Make option groups plural Signed-off-by: TheKodeToad --- launcher/ui/widgets/ModFilterWidget.ui | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui index 27f3b86101..5f1e661e44 100644 --- a/launcher/ui/widgets/ModFilterWidget.ui +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -47,7 +47,7 @@ - Category + Categories false @@ -60,7 +60,7 @@ - Loader + Loaders false @@ -103,7 +103,7 @@ - Version + Versions false @@ -112,12 +112,6 @@ false - - - - - - @@ -125,13 +119,19 @@ + + + + + + - Environment + Environments false From 186a6d0c523cf63461fb4d883f0e1ead026bcb6d Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 1 Apr 2024 17:00:10 +0100 Subject: [PATCH 0173/2054] Make filters vertical Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ModPage.cpp | 3 +- .../ui/pages/modplatform/ResourcePage.cpp | 6 +- launcher/ui/pages/modplatform/ResourcePage.ui | 72 +++++++++++-------- launcher/ui/widgets/ModFilterWidget.ui | 17 +++-- 4 files changed, 57 insertions(+), 41 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 8fd42b427d..3561afd38e 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -68,10 +68,9 @@ void ModPage::setFilterWidget(unique_qobject_ptr& widget) if (m_filter_widget) disconnect(m_filter_widget.get(), nullptr, nullptr, nullptr); + m_ui->horizontalLayout->replaceWidget(m_filter_widget == nullptr ? m_ui->filterWidget : m_filter_widget.get(), widget.get()); m_filter_widget.swap(widget); - m_ui->gridLayout_3->addWidget(m_filter_widget.get(), 0, 0, 1, m_ui->gridLayout_3->columnCount()); - m_filter = m_filter_widget->getFilter(); connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, &ResourcePage::updateVersionList); diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 35c4afc0d2..112b01b201 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -71,7 +71,7 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in m_fetch_progress.setFixedHeight(24); m_fetch_progress.progressFormat(""); - m_ui->gridLayout_3->addWidget(&m_fetch_progress, 0, 0, 1, m_ui->gridLayout_3->columnCount()); + layout()->replaceWidget(m_ui->fetchProgress, &m_fetch_progress); m_ui->packView->setItemDelegate(new ProjectItemDelegate(this)); m_ui->packView->installEventFilter(this); @@ -93,8 +93,10 @@ void ResourcePage::retranslate() void ResourcePage::openedImpl() { - if (!supportsFiltering()) + if (!supportsFiltering()) { m_ui->resourceFilterButton->setVisible(false); + m_ui->filterWidget->hide(); + } //: String in the search bar of the mod downloading dialog m_ui->searchEdit->setPlaceholderText(tr("Search for %1...").arg(resourcesString())); diff --git a/launcher/ui/pages/modplatform/ResourcePage.ui b/launcher/ui/pages/modplatform/ResourcePage.ui index 73a9d3b1af..b4f48f0742 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.ui +++ b/launcher/ui/pages/modplatform/ResourcePage.ui @@ -11,19 +11,12 @@ - - - - - - false - - - false - - + + + + - + Qt::ScrollBarAlwaysOff @@ -39,19 +32,19 @@ + + + + false + + + false + + + - - - - Search - - - - - - - + @@ -74,13 +67,6 @@ - - - - Filter options - - - @@ -88,6 +74,26 @@ + + + + + + + Search + + + + + + + + + + Filter options + + + @@ -96,6 +102,12 @@ QTextBrowser
    ui/widgets/ProjectDescriptionPage.h
    + + ProgressWidget + QWidget +
    ui/widgets/ProgressWidget.h
    + 1 +
    searchEdit diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui index 5f1e661e44..13727167fe 100644 --- a/launcher/ui/widgets/ModFilterWidget.ui +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -6,10 +6,16 @@ 0 0 - 553 - 604 + 310 + 600
    + + + 310 + 16777215 + + Form @@ -28,9 +34,6 @@ - - QFrame::NoFrame - true @@ -39,8 +42,8 @@ 0 0 - 553 - 604 + 308 + 598 From 1c3c9d24573d4f3cf25580129d3611f5082dcc29 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 7 Apr 2024 13:48:34 +0300 Subject: [PATCH 0174/2054] Added the new ftb settings path Signed-off-by: Trial97 --- launcher/ui/pages/modplatform/import_ftb/ListModel.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index 52671ea8eb..f3c7379773 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -44,7 +44,9 @@ QString getFTBRoot() QString getDynamicPath() { - auto settingsPath = FS::PathCombine(getFTBRoot(), "bin", "settings.json"); + auto settingsPath = FS::PathCombine(getFTBRoot(), "storage", "settings.json"); + if (!QFileInfo::exists(settingsPath)) + settingsPath = FS::PathCombine(getFTBRoot(), "bin", "settings.json"); if (!QFileInfo::exists(settingsPath)) { qWarning() << "The ftb app setings doesn't exist."; return {}; From 89230c4f8a69a66b46fdd00544ccfaf444a13652 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 20 Apr 2024 18:01:25 +0300 Subject: [PATCH 0175/2054] Moved the export modlist out of the sub-menu Signed-off-by: Trial97 --- .../ui/pages/instance/ExternalResourcesPage.ui | 17 ++++++++++++++--- launcher/ui/pages/instance/ModFolderPage.cpp | 7 +++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui index ff08e12d25..9d6f61db03 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.ui +++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui @@ -70,15 +70,15 @@ - - true - Actions Qt::ToolButtonTextOnly + + true + RightToolBarArea @@ -171,6 +171,17 @@ Try to check or update all selected resources (all resources if none are selected)
    + + + true + + + Export modlist + + + Export mod's metadata to text + + diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 8b9924470d..f045ab6c00 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -112,10 +112,6 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr connect(actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata); actionRemoveItemMetadata->setEnabled(false); - auto actionExportMetadata = updateMenu->addAction(tr("Export metadata")); - actionExportMetadata->setToolTip(tr("Export mod's metadata to text")); - connect(actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata); - ui->actionUpdateItem->setMenu(updateMenu); ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)")); @@ -126,6 +122,9 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr ui->actionsToolbar->addAction(ui->actionVisitItemPage); connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages); + ui->actionsToolbar->insertActionAfter(ui->actionVisitItemPage, ui->actionExportMetadata); + connect(ui->actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata); + auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); }; connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, From 1961e2081fc8f0787622820a33543751b8aef0a7 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 25 Apr 2024 00:48:40 +0300 Subject: [PATCH 0176/2054] Removed scroll from version checkbox Signed-off-by: Trial97 --- launcher/ui/widgets/CheckComboBox.cpp | 4 ++-- launcher/ui/widgets/ModFilterWidget.cpp | 9 +++++++++ launcher/ui/widgets/ModFilterWidget.h | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/launcher/ui/widgets/CheckComboBox.cpp b/launcher/ui/widgets/CheckComboBox.cpp index 8117baa584..189f7ebb40 100644 --- a/launcher/ui/widgets/CheckComboBox.cpp +++ b/launcher/ui/widgets/CheckComboBox.cpp @@ -68,10 +68,10 @@ class CheckComboModel : public QIdentityProxyModel { QStringList checked; }; -CheckComboBox::CheckComboBox(QWidget* parent) : QComboBox(parent), m_separator(",") +CheckComboBox::CheckComboBox(QWidget* parent) : QComboBox(parent), m_separator(", ") { QLineEdit* lineEdit = new QLineEdit(this); - lineEdit->setReadOnly(false); + lineEdit->setReadOnly(true); setLineEdit(lineEdit); lineEdit->disconnect(this); setInsertPolicy(QComboBox::NoInsert); diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index b44a573d85..ead573aeec 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -89,6 +89,8 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWi ui->versions->setStyleSheet("combobox-popup: 0;"); ui->version->setStyleSheet("combobox-popup: 0;"); + ui->versions->installEventFilter(this); + ui->version->installEventFilter(this); connect(ui->showAllVersions, &QCheckBox::stateChanged, this, &ModFilterWidget::onShowAllVersionsChanged); connect(ui->versions, QOverload::of(&QComboBox::currentIndexChanged), this, &ModFilterWidget::onVersionFilterChanged); connect(ui->version, &QComboBox::currentTextChanged, this, &ModFilterWidget::onVersionFilterTextChanged); @@ -115,6 +117,13 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWi prepareBasicFilter(); } +bool ModFilterWidget::eventFilter(QObject* obj, QEvent* evt) +{ + if ((obj != ui->versions && obj != ui->version) || evt->type() != QEvent::Wheel) + return QTabWidget::eventFilter(obj, evt); + return true; +} + auto ModFilterWidget::getFilter() -> std::shared_ptr { m_filter_changed = false; diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index 4358dd91da..35125c9106 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -85,6 +85,7 @@ class ModFilterWidget : public QTabWidget { public slots: void setCategories(const QList&); + bool eventFilter(QObject* obj, QEvent* evt); private: ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); From dedea2c05d8503fea0d6b7c0adea25e64c8d511c Mon Sep 17 00:00:00 2001 From: SabrePenguin Date: Thu, 25 Apr 2024 14:56:22 -0400 Subject: [PATCH 0177/2054] Added a naive implementation of a
    inserter Signed-off-by: SabrePenguin --- launcher/Markdown.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp index 426067bf6d..8b1f11f33c 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -24,7 +24,15 @@ QString markdownToHTML(const QString& markdown) char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE); QString htmlStr(buffer); - + int first_pos = htmlStr.indexOf(""); + int img_pos = 0; + while( first_pos != -1 ) + { + img_pos = htmlStr.indexOf(""); + first_pos = htmlStr.indexOf("", first_pos+5); + } free(buffer); return htmlStr; From 2cfe482298826bf0bdb948cca65f72930e142859 Mon Sep 17 00:00:00 2001 From: SabrePenguin Date: Thu, 25 Apr 2024 15:40:24 -0400 Subject: [PATCH 0178/2054] Formatting and fixed img_pos allowed as negative Signed-off-by: SabrePenguin --- launcher/Markdown.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp index 8b1f11f33c..31e026dc76 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -24,16 +24,20 @@ QString markdownToHTML(const QString& markdown) char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE); QString htmlStr(buffer); + + free(buffer); + + // Insert a breakpoint between a and tag as this can cause visual bugs int first_pos = htmlStr.indexOf(""); - int img_pos = 0; - while( first_pos != -1 ) - { + int img_pos; + while (first_pos != -1) { img_pos = htmlStr.indexOf(" -1) // 5 is the size of the tag htmlStr.insert(img_pos, "
    "); - first_pos = htmlStr.indexOf("", first_pos+5); + + first_pos = htmlStr.indexOf("", first_pos + 5); } - free(buffer); return htmlStr; } \ No newline at end of file From e1ed317c137c2ea3f4f54cb5a1c65e9f309d3ad1 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 26 Apr 2024 00:16:48 +0300 Subject: [PATCH 0179/2054] Updated CheckComboBox to look more like QComboBox Signed-off-by: Trial97 --- launcher/ui/widgets/CheckComboBox.cpp | 56 +++++++++++++------------ launcher/ui/widgets/CheckComboBox.h | 5 ++- launcher/ui/widgets/ModFilterWidget.cpp | 9 ---- launcher/ui/widgets/ModFilterWidget.h | 1 - 4 files changed, 34 insertions(+), 37 deletions(-) diff --git a/launcher/ui/widgets/CheckComboBox.cpp b/launcher/ui/widgets/CheckComboBox.cpp index 189f7ebb40..f8411dd797 100644 --- a/launcher/ui/widgets/CheckComboBox.cpp +++ b/launcher/ui/widgets/CheckComboBox.cpp @@ -26,6 +26,7 @@ #include #include #include +#include class CheckComboModel : public QIdentityProxyModel { Q_OBJECT @@ -70,12 +71,6 @@ class CheckComboModel : public QIdentityProxyModel { CheckComboBox::CheckComboBox(QWidget* parent) : QComboBox(parent), m_separator(", ") { - QLineEdit* lineEdit = new QLineEdit(this); - lineEdit->setReadOnly(true); - setLineEdit(lineEdit); - lineEdit->disconnect(this); - setInsertPolicy(QComboBox::NoInsert); - view()->installEventFilter(this); view()->window()->installEventFilter(this); view()->viewport()->installEventFilter(this); @@ -89,9 +84,9 @@ void CheckComboBox::setSourceModel(QAbstractItemModel* new_model) model()->disconnect(this); QComboBox::setModel(proxy); connect(this, QOverload::of(&QComboBox::activated), this, &CheckComboBox::toggleCheckState); - connect(proxy, &CheckComboModel::checkStateChanged, this, &CheckComboBox::updateCheckedItems); - connect(model(), &CheckComboModel::rowsInserted, this, &CheckComboBox::updateCheckedItems); - connect(model(), &CheckComboModel::rowsRemoved, this, &CheckComboBox::updateCheckedItems); + connect(proxy, &CheckComboModel::checkStateChanged, this, &CheckComboBox::emitCheckedItemsChanged); + connect(model(), &CheckComboModel::rowsInserted, this, &CheckComboBox::emitCheckedItemsChanged); + connect(model(), &CheckComboModel::rowsRemoved, this, &CheckComboBox::emitCheckedItemsChanged); } void CheckComboBox::hidePopup() @@ -100,15 +95,9 @@ void CheckComboBox::hidePopup() QComboBox::hidePopup(); } -void CheckComboBox::updateCheckedItems() +void CheckComboBox::emitCheckedItemsChanged() { - QStringList items = checkedItems(); - if (items.isEmpty()) - setEditText(defaultText()); - else - setEditText(items.join(separator())); - - emit checkedItemsChanged(items); + emit checkedItemsChanged(checkedItems()); } QString CheckComboBox::defaultText() const @@ -118,10 +107,7 @@ QString CheckComboBox::defaultText() const void CheckComboBox::setDefaultText(const QString& text) { - if (m_default_text != text) { - m_default_text = text; - updateCheckedItems(); - } + m_default_text = text; } QString CheckComboBox::separator() const @@ -131,10 +117,7 @@ QString CheckComboBox::separator() const void CheckComboBox::setSeparator(const QString& separator) { - if (m_separator != separator) { - m_separator = separator; - updateCheckedItems(); - } + m_separator = separator; } bool CheckComboBox::eventFilter(QObject* receiver, QEvent* event) @@ -157,6 +140,8 @@ bool CheckComboBox::eventFilter(QObject* receiver, QEvent* event) case QEvent::MouseButtonRelease: containerMousePress = false; break; + case QEvent::Wheel: + return receiver == this; default: break; } @@ -170,7 +155,7 @@ void CheckComboBox::toggleCheckState(int index) Qt::CheckState state = static_cast(value.toInt()); setItemData(index, (state == Qt::Unchecked ? Qt::Checked : Qt::Unchecked), Qt::CheckStateRole); } - updateCheckedItems(); + emitCheckedItemsChanged(); } Qt::CheckState CheckComboBox::itemCheckState(int index) const @@ -198,4 +183,23 @@ void CheckComboBox::setCheckedItems(const QStringList& items) } } +void CheckComboBox::paintEvent(QPaintEvent*) +{ + QStylePainter painter(this); + painter.setPen(palette().color(QPalette::Text)); + + // draw the combobox frame, focusrect and selected etc. + QStyleOptionComboBox opt; + initStyleOption(&opt); + QStringList items = checkedItems(); + if (items.isEmpty()) + opt.currentText = defaultText(); + else + opt.currentText = items.join(separator()); + painter.drawComplexControl(QStyle::CC_ComboBox, opt); + + // draw the icon and text + painter.drawControl(QStyle::CE_ComboBoxLabel, opt); +} + #include "CheckComboBox.moc" \ No newline at end of file diff --git a/launcher/ui/widgets/CheckComboBox.h b/launcher/ui/widgets/CheckComboBox.h index 4d6daf5a48..876c6e3e10 100644 --- a/launcher/ui/widgets/CheckComboBox.h +++ b/launcher/ui/widgets/CheckComboBox.h @@ -49,8 +49,11 @@ class CheckComboBox : public QComboBox { signals: void checkedItemsChanged(const QStringList& items); + protected: + void paintEvent(QPaintEvent*) override; + private: - void updateCheckedItems(); + void emitCheckedItemsChanged(); bool eventFilter(QObject* receiver, QEvent* event) override; void toggleCheckState(int index); diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index ead573aeec..b44a573d85 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -89,8 +89,6 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWi ui->versions->setStyleSheet("combobox-popup: 0;"); ui->version->setStyleSheet("combobox-popup: 0;"); - ui->versions->installEventFilter(this); - ui->version->installEventFilter(this); connect(ui->showAllVersions, &QCheckBox::stateChanged, this, &ModFilterWidget::onShowAllVersionsChanged); connect(ui->versions, QOverload::of(&QComboBox::currentIndexChanged), this, &ModFilterWidget::onVersionFilterChanged); connect(ui->version, &QComboBox::currentTextChanged, this, &ModFilterWidget::onVersionFilterTextChanged); @@ -117,13 +115,6 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWi prepareBasicFilter(); } -bool ModFilterWidget::eventFilter(QObject* obj, QEvent* evt) -{ - if ((obj != ui->versions && obj != ui->version) || evt->type() != QEvent::Wheel) - return QTabWidget::eventFilter(obj, evt); - return true; -} - auto ModFilterWidget::getFilter() -> std::shared_ptr { m_filter_changed = false; diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index 35125c9106..4358dd91da 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -85,7 +85,6 @@ class ModFilterWidget : public QTabWidget { public slots: void setCategories(const QList&); - bool eventFilter(QObject* obj, QEvent* evt); private: ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); From 803e26a4018d788178032b653fd06d1b8970b3a4 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 26 Apr 2024 01:40:18 +0100 Subject: [PATCH 0180/2054] Make actions more consistent Signed-off-by: TheKodeToad --- .../pages/instance/ExternalResourcesPage.cpp | 34 +++++++++++++------ .../ui/pages/instance/ExternalResourcesPage.h | 5 ++- .../pages/instance/ExternalResourcesPage.ui | 11 +++++- launcher/ui/pages/instance/ModFolderPage.cpp | 4 +-- launcher/ui/pages/instance/ModFolderPage.h | 2 +- .../ui/pages/instance/ResourcePackPage.cpp | 21 ++++++------ launcher/ui/pages/instance/ResourcePackPage.h | 2 +- .../ui/pages/instance/TexturePackPage.cpp | 21 ++++++------ launcher/ui/pages/instance/TexturePackPage.h | 2 +- 9 files changed, 59 insertions(+), 43 deletions(-) diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index 2068fa6b1a..a3b58f4df6 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -81,15 +81,28 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared connect(ui->treeView, &ModListView::activated, this, &ExternalResourcesPage::itemActivated); auto selection_model = ui->treeView->selectionModel(); - connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current); + + connect(selection_model, &QItemSelectionModel::currentChanged, this, [this](const QModelIndex& current, const QModelIndex& previous) { + if (!current.isValid()) { + ui->frame->clear(); + return; + } + + updateFrame(current, previous); + }); + auto updateExtra = [this]() { if (updateExtraInfo) updateExtraInfo(id(), extraHeaderInfoString()); }; + connect(selection_model, &QItemSelectionModel::selectionChanged, this, updateExtra); connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra); - connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged); + connect(selection_model, &QItemSelectionModel::selectionChanged, this, [this] { updateActions(); }); + connect(m_model.get(), &ResourceFolderModel::rowsInserted, this, [this] { updateActions(); }); + connect(m_model.get(), &ResourceFolderModel::rowsRemoved, this, [this] { updateActions(); }); + connect(m_model.get(), &ResourceFolderModel::updateFinished, this, [this] { updateActions(); }); auto viewHeader = ui->treeView->header(); viewHeader->setContextMenuPolicy(Qt::CustomContextMenu); @@ -298,23 +311,22 @@ void ExternalResourcesPage::viewFolder() DesktopServices::openPath(m_model->dir().absolutePath(), true); } -bool ExternalResourcesPage::current(const QModelIndex& current, const QModelIndex& previous) +void ExternalResourcesPage::updateActions() { - if (!current.isValid()) { - ui->frame->clear(); - return false; - } - - return onSelectionChanged(current, previous); + const bool hasSelection = ui->treeView->selectionModel()->hasSelection(); + ui->actionUpdateItem->setEnabled(!m_model->empty()); + ui->actionResetItemMetadata->setEnabled(hasSelection); + ui->actionRemoveItem->setEnabled(hasSelection); + ui->actionEnableItem->setEnabled(hasSelection); + ui->actionDisableItem->setEnabled(hasSelection); } -bool ExternalResourcesPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) +void ExternalResourcesPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); Resource const& resource = m_model->at(row); ui->frame->updateWithResource(resource); - return true; } QString ExternalResourcesPage::extraHeaderInfoString() diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h index d29be0fc3a..9bbd139845 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.h +++ b/launcher/ui/pages/instance/ExternalResourcesPage.h @@ -42,9 +42,8 @@ class ExternalResourcesPage : public QMainWindow, public BasePage { QMenu* createPopupMenu() override; public slots: - bool current(const QModelIndex& current, const QModelIndex& previous); - - virtual bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous); + virtual void updateActions(); + virtual void updateFrame(const QModelIndex& current, const QModelIndex& previous); protected slots: void itemActivated(const QModelIndex& index); diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui index 2b4a47b9d1..c671efaf8e 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.ui +++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui @@ -102,6 +102,9 @@
    + + false + &Remove @@ -110,6 +113,9 @@ + + false + &Enable @@ -118,6 +124,9 @@ + + false + &Disable @@ -154,7 +163,7 @@ - true + false Check for &Updates diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 5b10a38dfb..e647120c22 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -106,14 +106,12 @@ bool ModFolderPage::shouldDisplay() const return true; } -bool ModFolderPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) +void ModFolderPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); const Mod& mod = m_model->at(row); ui->frame->updateWithMod(mod); - - return true; } void ModFolderPage::removeItems(const QItemSelection& selection) diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 455db33cb4..4fac801415 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -57,7 +57,7 @@ class ModFolderPage : public ExternalResourcesPage { virtual bool shouldDisplay() const override; public slots: - bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; + void updateFrame(const QModelIndex& current, const QModelIndex& previous) override; private slots: void removeItems(const QItemSelection& selection) override; diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index e51ebafac7..d03511ca47 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -69,14 +69,12 @@ ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, std::shared_ptr< ui->actionUpdateItem->setMenu(updateMenu); } -bool ResourcePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) +void ResourcePackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); auto& rp = static_cast(m_model->at(row)); ui->frame->updateWithResourcePack(rp); - - return true; } void ResourcePackPage::downloadResourcePacks() @@ -127,13 +125,13 @@ void ResourcePackPage::updateResourcePacks() return; } if (m_instance != nullptr && m_instance->isRunning()) { - auto response = - CustomMessageBox::selectable(this, tr("Confirm Update"), - tr("Updating resource packs while the game is running may cause pack duplication and game crashes.\n" - "The old files may not be deleted as they are in use.\n" - "Are you sure you want to do this?"), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) - ->exec(); + auto response = CustomMessageBox::selectable( + this, tr("Confirm Update"), + tr("Updating resource packs while the game is running may cause pack duplication and game crashes.\n" + "The old files may not be deleted as they are in use.\n" + "Are you sure you want to do this?"), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); if (response != QMessageBox::Yes) return; @@ -166,7 +164,8 @@ void ResourcePackPage::updateResourcePacks() } if (update_dialog.exec()) { - auto tasks = new ConcurrentTask(this, "Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + auto tasks = + new ConcurrentTask(this, "Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index c2d36cea1b..e95809cf80 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -58,7 +58,7 @@ class ResourcePackPage : public ExternalResourcesPage { } public slots: - bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; + void updateFrame(const QModelIndex& current, const QModelIndex& previous) override; private slots: void downloadResourcePacks(); diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index 75f8410bcb..cdd2b09642 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -71,14 +71,12 @@ TexturePackPage::TexturePackPage(MinecraftInstance* instance, std::shared_ptractionUpdateItem->setMenu(updateMenu); } -bool TexturePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) +void TexturePackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); auto& rp = static_cast(m_model->at(row)); ui->frame->updateWithTexturePack(rp); - - return true; } void TexturePackPage::downloadTexturePacks() @@ -129,13 +127,13 @@ void TexturePackPage::updateTexturePacks() return; } if (m_instance != nullptr && m_instance->isRunning()) { - auto response = - CustomMessageBox::selectable(this, tr("Confirm Update"), - tr("Updating texture packs while the game is running may cause pack duplication and game crashes.\n" - "The old files may not be deleted as they are in use.\n" - "Are you sure you want to do this?"), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) - ->exec(); + auto response = CustomMessageBox::selectable( + this, tr("Confirm Update"), + tr("Updating texture packs while the game is running may cause pack duplication and game crashes.\n" + "The old files may not be deleted as they are in use.\n" + "Are you sure you want to do this?"), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); if (response != QMessageBox::Yes) return; @@ -168,7 +166,8 @@ void TexturePackPage::updateTexturePacks() } if (update_dialog.exec()) { - auto tasks = new ConcurrentTask(this, "Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + auto tasks = + new ConcurrentTask(this, "Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); diff --git a/launcher/ui/pages/instance/TexturePackPage.h b/launcher/ui/pages/instance/TexturePackPage.h index e42613568f..4dc2c16c22 100644 --- a/launcher/ui/pages/instance/TexturePackPage.h +++ b/launcher/ui/pages/instance/TexturePackPage.h @@ -55,7 +55,7 @@ class TexturePackPage : public ExternalResourcesPage { virtual bool shouldDisplay() const override { return m_instance->traits().contains("texturepacks"); } public slots: - bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; + void updateFrame(const QModelIndex& current, const QModelIndex& previous) override; void downloadTexturePacks(); void updateTexturePacks(); void deleteTexturePackMetadata(); From 9ce10231edcf256eb0bcb2a3e78c3c06b156c800 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 26 Apr 2024 01:44:43 +0100 Subject: [PATCH 0181/2054] Remove redundant event handler Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/ExternalResourcesPage.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index a3b58f4df6..1ee473541d 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -102,7 +102,6 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared connect(selection_model, &QItemSelectionModel::selectionChanged, this, [this] { updateActions(); }); connect(m_model.get(), &ResourceFolderModel::rowsInserted, this, [this] { updateActions(); }); connect(m_model.get(), &ResourceFolderModel::rowsRemoved, this, [this] { updateActions(); }); - connect(m_model.get(), &ResourceFolderModel::updateFinished, this, [this] { updateActions(); }); auto viewHeader = ui->treeView->header(); viewHeader->setContextMenuPolicy(Qt::CustomContextMenu); From 6ecd2c7f314d0e5fe7c8ce18ab520e3a38e8d717 Mon Sep 17 00:00:00 2001 From: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> Date: Fri, 26 Apr 2024 16:51:23 -0400 Subject: [PATCH 0182/2054] Update launcher/Markdown.cpp Co-authored-by: Alexandru Ionut Tripon Signed-off-by: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> --- launcher/Markdown.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp index 31e026dc76..f9081057c6 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -26,18 +26,23 @@ QString markdownToHTML(const QString& markdown) QString htmlStr(buffer); free(buffer); - - // Insert a breakpoint between a and tag as this can cause visual bugs - int first_pos = htmlStr.indexOf(""); - int img_pos; - while (first_pos != -1) { - img_pos = htmlStr.indexOf(" -1) // 5 is the size of the tag - htmlStr.insert(img_pos, "
    "); + int pos = htmlStr.indexOf(""); + int imgPos; + while (pos != -1) { + pos = pos + 5; // 5 is the size of the tag + imgPos = htmlStr.indexOf("", first_pos + 5); + auto textBetween = htmlStr.mid(pos, imgPos - pos).trimmed(); // trim all white spaces + + if (textBetween.isEmpty()) + htmlStr.insert(pos, "
    "); + + pos = htmlStr.indexOf("", pos); } + return htmlStr; } \ No newline at end of file From a988415028fafb1414420573b13d4e1113efe03e Mon Sep 17 00:00:00 2001 From: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> Date: Fri, 26 Apr 2024 16:59:51 -0400 Subject: [PATCH 0183/2054] Pleasing the format check Co-authored-by: Alexandru Ionut Tripon Signed-off-by: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> --- launcher/Markdown.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp index f9081057c6..1e7fb9f35b 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -43,6 +43,5 @@ QString markdownToHTML(const QString& markdown) pos = htmlStr.indexOf("", pos); } - return htmlStr; } \ No newline at end of file From 5348a90a15676ed178f790648d60f4d3c3c23cf1 Mon Sep 17 00:00:00 2001 From: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:21:17 -0400 Subject: [PATCH 0184/2054] Fix img tag allowing img.... Co-authored-by: TheKodeToad Signed-off-by: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> --- launcher/Markdown.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp index 1e7fb9f35b..4ee1c0d28a 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -31,7 +31,7 @@ QString markdownToHTML(const QString& markdown) int imgPos; while (pos != -1) { pos = pos + 5; // 5 is the size of the tag - imgPos = htmlStr.indexOf(" Date: Tue, 30 Apr 2024 22:40:04 -0400 Subject: [PATCH 0185/2054] Moved html patch to StringUtils Signed-off-by: SabrePenguin --- launcher/Markdown.cpp | 18 +----------------- launcher/StringUtils.cpp | 20 ++++++++++++++++++++ launcher/StringUtils.h | 2 ++ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp index 4ee1c0d28a..a624791dfd 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -26,22 +26,6 @@ QString markdownToHTML(const QString& markdown) QString htmlStr(buffer); free(buffer); - - int pos = htmlStr.indexOf(""); - int imgPos; - while (pos != -1) { - pos = pos + 5; // 5 is the size of the tag - imgPos = htmlStr.indexOf(""); - - pos = htmlStr.indexOf("", pos); - } - + return htmlStr; } \ No newline at end of file diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index 72ccdfbff7..db53f353e7 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -212,3 +212,23 @@ QPair StringUtils::splitFirst(const QString& s, const QRegular right = s.mid(end); return qMakePair(left, right); } + +QString StringUtils::htmlListPatch(QString htmlStr) +{ + int pos = htmlStr.indexOf(""); + int imgPos; + while (pos != -1) { + pos = pos + 5; // 5 is the size of the tag + imgPos = htmlStr.indexOf(""); + + pos = htmlStr.indexOf("", pos); + } + return htmlStr; +} \ No newline at end of file diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h index 9d2bdd85e4..624ee41a38 100644 --- a/launcher/StringUtils.h +++ b/launcher/StringUtils.h @@ -85,4 +85,6 @@ QPair splitFirst(const QString& s, const QString& sep, Qt::Cas QPair splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs = Qt::CaseSensitive); QPair splitFirst(const QString& s, const QRegularExpression& re); +QString htmlListPatch(QString htmlStr); + } // namespace StringUtils From e3d64608b947eeb774a4ff317c3984bcac8d83be Mon Sep 17 00:00:00 2001 From: SabrePenguin Date: Tue, 30 Apr 2024 23:28:50 -0400 Subject: [PATCH 0186/2054] Switched to a Regex expression Signed-off-by: SabrePenguin --- launcher/StringUtils.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index db53f353e7..125b8ca6f7 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -215,11 +215,13 @@ QPair StringUtils::splitFirst(const QString& s, const QRegular QString StringUtils::htmlListPatch(QString htmlStr) { - int pos = htmlStr.indexOf(""); - int imgPos; + QRegularExpression match("|"); + int pos = htmlStr.indexOf(match); + int imgPos, dist; while (pos != -1) { - pos = pos + 5; // 5 is the size of the tag - imgPos = htmlStr.indexOf("", pos) - pos + 1; // Get the size of the tag. Add one for zeroeth index + pos = pos + dist; + imgPos = htmlStr.indexOf(""); - pos = htmlStr.indexOf("", pos); + pos = htmlStr.indexOf(match, pos); } return htmlStr; } \ No newline at end of file From b9c010ffb2bcf8c08a38014079064eeba0af2fd9 Mon Sep 17 00:00:00 2001 From: SabrePenguin Date: Tue, 30 Apr 2024 23:43:36 -0400 Subject: [PATCH 0187/2054] Added patch to all setHtml calls Signed-off-by: SabrePenguin --- launcher/ui/dialogs/AboutDialog.cpp | 5 +++-- launcher/ui/dialogs/ExportToModListDialog.cpp | 5 +++-- launcher/ui/dialogs/ModUpdateDialog.cpp | 3 ++- launcher/ui/dialogs/UpdateAvailableDialog.cpp | 3 ++- launcher/ui/pages/instance/ManagedPackPage.cpp | 8 +++++--- launcher/ui/pages/modplatform/ResourcePage.cpp | 5 +++-- launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp | 3 ++- launcher/ui/pages/modplatform/flame/FlamePage.cpp | 3 ++- launcher/ui/pages/modplatform/legacy_ftb/Page.cpp | 7 +++++-- launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp | 3 ++- launcher/ui/pages/modplatform/technic/TechnicPage.cpp | 3 ++- launcher/updater/prismupdater/UpdaterDialogs.cpp | 3 ++- 12 files changed, 33 insertions(+), 18 deletions(-) diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index 17b79ecaaa..40c852c378 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -39,6 +39,7 @@ #include "BuildConfig.h" #include "Markdown.h" #include "ui_AboutDialog.h" +#include "StringUtils.h" #include #include @@ -139,10 +140,10 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia setWindowTitle(tr("About %1").arg(launcherName)); QString chtml = getCreditsHtml(); - ui->creditsText->setHtml(chtml); + ui->creditsText->setHtml(StringUtils::htmlListPatch(chtml)); QString lhtml = getLicenseHtml(); - ui->licenseText->setHtml(lhtml); + ui->licenseText->setHtml(StringUtils::htmlListPatch(lhtml)); ui->urlLabel->setOpenExternalLinks(true); diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp index a343f555ac..ab85cf584e 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.cpp +++ b/launcher/ui/dialogs/ExportToModListDialog.cpp @@ -26,6 +26,7 @@ #include "minecraft/mod/ModFolderModel.h" #include "modplatform/helpers/ExportToModList.h" #include "ui_ExportToModListDialog.h" +#include "StringUtils.h" #include #include @@ -143,10 +144,10 @@ void ExportToModListDialog::triggerImp() case ExportToModList::CUSTOM: return; case ExportToModList::HTML: - ui->resultText->setHtml(txt); + ui->resultText->setHtml(StringUtils::htmlListPatch(txt)); break; case ExportToModList::MARKDOWN: - ui->resultText->setHtml(markdownToHTML(txt)); + ui->resultText->setHtml(StringUtils::htmlListPatch(markdownToHTML(txt))); break; case ExportToModList::PLAINTXT: break; diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index 54893d7756..a08b36ec1f 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -7,6 +7,7 @@ #include "modplatform/ModIndex.h" #include "modplatform/flame/FlameAPI.h" #include "ui_ReviewMessageBox.h" +#include "StringUtils.h" #include "Markdown.h" @@ -473,7 +474,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri break; } - changelog_area->setHtml(text); + changelog_area->setHtml(StringUtils::htmlListPatch(text)); changelog_area->setOpenExternalLinks(true); changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth); changelog_area->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); diff --git a/launcher/ui/dialogs/UpdateAvailableDialog.cpp b/launcher/ui/dialogs/UpdateAvailableDialog.cpp index 5eebe87a31..797b763c27 100644 --- a/launcher/ui/dialogs/UpdateAvailableDialog.cpp +++ b/launcher/ui/dialogs/UpdateAvailableDialog.cpp @@ -26,6 +26,7 @@ #include "BuildConfig.h" #include "Markdown.h" #include "ui_UpdateAvailableDialog.h" +#include "StringUtils.h" UpdateAvailableDialog::UpdateAvailableDialog(const QString& currentVersion, const QString& availableVersion, @@ -43,7 +44,7 @@ UpdateAvailableDialog::UpdateAvailableDialog(const QString& currentVersion, ui->icon->setPixmap(APPLICATION->getThemedIcon("checkupdate").pixmap(64)); auto releaseNotesHtml = markdownToHTML(releaseNotes); - ui->releaseNotes->setHtml(releaseNotesHtml); + ui->releaseNotes->setHtml(StringUtils::htmlListPatch(releaseNotesHtml)); ui->releaseNotes->setOpenExternalLinks(true); connect(ui->skipButton, &QPushButton::clicked, this, [this]() { diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index 2210d02638..9c3fcc3544 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -20,6 +20,7 @@ #include "InstanceTask.h" #include "Json.h" #include "Markdown.h" +#include "StringUtils.h" #include "modplatform/modrinth/ModrinthPackManifest.h" @@ -332,7 +333,7 @@ void ModrinthManagedPackPage::suggestVersion() } auto version = m_pack.versions.at(index); - ui->changelogTextBrowser->setHtml(markdownToHTML(version.changelog.toUtf8())); + ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch(markdownToHTML(version.changelog.toUtf8()))); ManagedPackPage::suggestVersion(); } @@ -420,7 +421,7 @@ void FlameManagedPackPage::parseManagedPack() "Don't worry though, it will ask you to update this instance instead, so you'll not lose this instance!" ""); - ui->changelogTextBrowser->setHtml(message); + ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch(message)); return; } @@ -502,7 +503,8 @@ void FlameManagedPackPage::suggestVersion() } auto version = m_pack.versions.at(index); - ui->changelogTextBrowser->setHtml(m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId)); + ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch( + m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId))); ManagedPackPage::suggestVersion(); } diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index ae48e5523d..b9c706c6ce 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -45,6 +45,7 @@ #include "Markdown.h" +#include "StringUtils.h" #include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/pages/modplatform/ResourceModel.h" #include "ui/widgets/ProjectItem.h" @@ -234,8 +235,8 @@ void ResourcePage::updateUi() text += "
    "; - m_ui->packDescription->setHtml( - text + (current_pack->extraData.body.isEmpty() ? current_pack->description : markdownToHTML(current_pack->extraData.body))); + m_ui->packDescription->setHtml(StringUtils::htmlListPatch( + text + (current_pack->extraData.body.isEmpty() ? current_pack->description : markdownToHTML(current_pack->extraData.body)))); m_ui->packDescription->flush(); } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index e492830c6c..d79b7621a1 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -39,6 +39,7 @@ #include "ui_AtlPage.h" #include "BuildConfig.h" +#include "StringUtils.h" #include "AtlUserInteractionSupportImpl.h" #include "modplatform/atlauncher/ATLPackInstallTask.h" @@ -144,7 +145,7 @@ void AtlPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex selected = filterModel->data(first, Qt::UserRole).value(); - ui->packDescription->setHtml(selected.description.replace("\n", "
    ")); + ui->packDescription->setHtml(StringUtils::htmlListPatch(selected.description.replace("\n", "
    "))); for (const auto& version : selected.versions) { ui->versionSelectionBox->addItem(version.version); diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index f1fd9b5d89..e851e47be8 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -46,6 +46,7 @@ #include "modplatform/flame/FlameAPI.h" #include "ui/dialogs/NewInstanceDialog.h" #include "ui/widgets/ProjectItem.h" +#include "StringUtils.h" #include "net/ApiDownload.h" @@ -292,6 +293,6 @@ void FlamePage::updateUi() text += "
    "; text += api.getModDescription(current.addonId).toUtf8(); - ui->packDescription->setHtml(text + current.description); + ui->packDescription->setHtml(StringUtils::htmlListPatch(text + current.description)); ui->packDescription->flush(); } diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 0ecaf46250..683d2e81da 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -37,6 +37,7 @@ #include "Page.h" #include "ui/widgets/ProjectItem.h" #include "ui_Page.h" +#include "StringUtils.h" #include @@ -260,8 +261,10 @@ void Page::onPackSelectionChanged(Modpack* pack) { ui->versionSelectionBox->clear(); if (pack) { - currentModpackInfo->setHtml("Pack by " + pack->author + "" + "
    Minecraft " + pack->mcVersion + "
    " + "
    " + - pack->description + "
    • " + pack->mods.replace(";", "
    • ") + "
    "); + currentModpackInfo->setHtml(StringUtils::htmlListPatch( + "Pack by " + pack->author + "" + "
    Minecraft " + pack->mcVersion + + "
    " + "
    " + + pack->description + "
    • " + pack->mods.replace(";", "
    • ") + "
    ")); bool currentAdded = false; for (int i = 0; i < pack->oldVersions.size(); i++) { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index da5fe1e7be..b44afda7ed 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -44,6 +44,7 @@ #include "InstanceImportTask.h" #include "Json.h" #include "Markdown.h" +#include "StringUtils.h" #include "ui/widgets/ProjectItem.h" @@ -304,7 +305,7 @@ void ModrinthPage::updateUI() text += markdownToHTML(current.extra.body.toUtf8()); - ui->packDescription->setHtml(text + current.description); + ui->packDescription->setHtml(StringUtils::htmlListPatch(text + current.description)); ui->packDescription->flush(); } diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index 6b1ec8cb51..72b7814c92 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -47,6 +47,7 @@ #include "TechnicModel.h" #include "modplatform/technic/SingleZipPackInstallTask.h" #include "modplatform/technic/SolderPackInstallTask.h" +#include "StringUtils.h" #include "Application.h" #include "modplatform/technic/SolderPackManifest.h" @@ -233,7 +234,7 @@ void TechnicPage::metadataLoaded() text += "

    "; - ui->packDescription->setHtml(text + current.description); + ui->packDescription->setHtml(StringUtils::htmlListPatch(text + current.description)); // Strip trailing forward-slashes from Solder URL's if (current.isSolder) { diff --git a/launcher/updater/prismupdater/UpdaterDialogs.cpp b/launcher/updater/prismupdater/UpdaterDialogs.cpp index 395b658db5..06dc161b1c 100644 --- a/launcher/updater/prismupdater/UpdaterDialogs.cpp +++ b/launcher/updater/prismupdater/UpdaterDialogs.cpp @@ -26,6 +26,7 @@ #include #include "Markdown.h" +#include "StringUtils.h" SelectReleaseDialog::SelectReleaseDialog(const Version& current_version, const QList& releases, QWidget* parent) : QDialog(parent), m_releases(releases), m_currentVersion(current_version), ui(new Ui::SelectReleaseDialog) @@ -96,7 +97,7 @@ void SelectReleaseDialog::selectionChanged(QTreeWidgetItem* current, QTreeWidget QString body = markdownToHTML(release.body.toUtf8()); m_selectedRelease = release; - ui->changelogTextBrowser->setHtml(body); + ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch(body)); } SelectReleaseAssetDialog::SelectReleaseAssetDialog(const QList& assets, QWidget* parent) From 51f4ede7977c3bb6d0d2f3866d3faf57f3b43dab Mon Sep 17 00:00:00 2001 From: SabrePenguin Date: Wed, 1 May 2024 00:11:53 -0400 Subject: [PATCH 0188/2054] Fixing CI format issues Signed-off-by: SabrePenguin --- launcher/ui/dialogs/AboutDialog.cpp | 2 +- launcher/ui/dialogs/ExportToModListDialog.cpp | 2 +- launcher/ui/dialogs/ModUpdateDialog.cpp | 2 +- launcher/ui/dialogs/UpdateAvailableDialog.cpp | 2 +- launcher/ui/pages/instance/ManagedPackPage.cpp | 4 ++-- launcher/ui/pages/modplatform/flame/FlamePage.cpp | 2 +- launcher/ui/pages/modplatform/legacy_ftb/Page.cpp | 9 ++++----- launcher/ui/pages/modplatform/technic/TechnicPage.cpp | 2 +- 8 files changed, 12 insertions(+), 13 deletions(-) diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index 40c852c378..b652ba9913 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -38,8 +38,8 @@ #include "Application.h" #include "BuildConfig.h" #include "Markdown.h" -#include "ui_AboutDialog.h" #include "StringUtils.h" +#include "ui_AboutDialog.h" #include #include diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp index ab85cf584e..7debf0cd8b 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.cpp +++ b/launcher/ui/dialogs/ExportToModListDialog.cpp @@ -22,11 +22,11 @@ #include #include "FileSystem.h" #include "Markdown.h" +#include "StringUtils.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/mod/ModFolderModel.h" #include "modplatform/helpers/ExportToModList.h" #include "ui_ExportToModListDialog.h" -#include "StringUtils.h" #include #include diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index a08b36ec1f..6649bee4e0 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -3,11 +3,11 @@ #include "CustomMessageBox.h" #include "ProgressDialog.h" #include "ScrollMessageBox.h" +#include "StringUtils.h" #include "minecraft/mod/tasks/GetModDependenciesTask.h" #include "modplatform/ModIndex.h" #include "modplatform/flame/FlameAPI.h" #include "ui_ReviewMessageBox.h" -#include "StringUtils.h" #include "Markdown.h" diff --git a/launcher/ui/dialogs/UpdateAvailableDialog.cpp b/launcher/ui/dialogs/UpdateAvailableDialog.cpp index 797b763c27..810a1f089b 100644 --- a/launcher/ui/dialogs/UpdateAvailableDialog.cpp +++ b/launcher/ui/dialogs/UpdateAvailableDialog.cpp @@ -25,8 +25,8 @@ #include "Application.h" #include "BuildConfig.h" #include "Markdown.h" -#include "ui_UpdateAvailableDialog.h" #include "StringUtils.h" +#include "ui_UpdateAvailableDialog.h" UpdateAvailableDialog::UpdateAvailableDialog(const QString& currentVersion, const QString& availableVersion, diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index 9c3fcc3544..a474039262 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -503,8 +503,8 @@ void FlameManagedPackPage::suggestVersion() } auto version = m_pack.versions.at(index); - ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch( - m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId))); + ui->changelogTextBrowser->setHtml( + StringUtils::htmlListPatch(m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId))); ManagedPackPage::suggestVersion(); } diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index e851e47be8..d3473412a6 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -43,10 +43,10 @@ #include "FlameModel.h" #include "InstanceImportTask.h" #include "Json.h" +#include "StringUtils.h" #include "modplatform/flame/FlameAPI.h" #include "ui/dialogs/NewInstanceDialog.h" #include "ui/widgets/ProjectItem.h" -#include "StringUtils.h" #include "net/ApiDownload.h" diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 683d2e81da..80345883c7 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -35,9 +35,9 @@ */ #include "Page.h" +#include "StringUtils.h" #include "ui/widgets/ProjectItem.h" #include "ui_Page.h" -#include "StringUtils.h" #include @@ -261,10 +261,9 @@ void Page::onPackSelectionChanged(Modpack* pack) { ui->versionSelectionBox->clear(); if (pack) { - currentModpackInfo->setHtml(StringUtils::htmlListPatch( - "Pack by " + pack->author + "" + "
    Minecraft " + pack->mcVersion + - "
    " + "
    " + - pack->description + "
    • " + pack->mods.replace(";", "
    • ") + "
    ")); + currentModpackInfo->setHtml(StringUtils::htmlListPatch("Pack by " + pack->author + "" + "
    Minecraft " + pack->mcVersion + + "
    " + "
    " + + pack->description + "
    • " + pack->mods.replace(";", "
    • ") + "
    ")); bool currentAdded = false; for (int i = 0; i < pack->oldVersions.size(); i++) { diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index 72b7814c92..391c101225 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -44,10 +44,10 @@ #include "BuildConfig.h" #include "Json.h" +#include "StringUtils.h" #include "TechnicModel.h" #include "modplatform/technic/SingleZipPackInstallTask.h" #include "modplatform/technic/SolderPackInstallTask.h" -#include "StringUtils.h" #include "Application.h" #include "modplatform/technic/SolderPackManifest.h" From 814f84efab9af2fa59156d3e6101ce7fd8cffcb9 Mon Sep 17 00:00:00 2001 From: SabrePenguin Date: Wed, 1 May 2024 00:21:32 -0400 Subject: [PATCH 0189/2054] Fixed more clang formatting Signed-off-by: SabrePenguin --- launcher/Markdown.cpp | 2 +- launcher/StringUtils.cpp | 2 +- launcher/ui/pages/modplatform/legacy_ftb/Page.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp index a624791dfd..426067bf6d 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -26,6 +26,6 @@ QString markdownToHTML(const QString& markdown) QString htmlStr(buffer); free(buffer); - + return htmlStr; } \ No newline at end of file diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index 125b8ca6f7..e05affde10 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -219,7 +219,7 @@ QString StringUtils::htmlListPatch(QString htmlStr) int pos = htmlStr.indexOf(match); int imgPos, dist; while (pos != -1) { - dist = htmlStr.indexOf(">", pos) - pos + 1; // Get the size of the tag. Add one for zeroeth index + dist = htmlStr.indexOf(">", pos) - pos + 1; // Get the size of the tag. Add one for zeroeth index pos = pos + dist; imgPos = htmlStr.indexOf("versionSelectionBox->clear(); if (pack) { currentModpackInfo->setHtml(StringUtils::htmlListPatch("Pack by " + pack->author + "" + "
    Minecraft " + pack->mcVersion + - "
    " + "
    " + - pack->description + "
    • " + pack->mods.replace(";", "
    • ") + "
    ")); + "
    " + "
    " + pack->description + "
    • " + + pack->mods.replace(";", "
    • ") + "
    ")); bool currentAdded = false; for (int i = 0; i < pack->oldVersions.size(); i++) { From ce873e4a0dd517fd68cb49bab037362c9616672b Mon Sep 17 00:00:00 2001 From: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> Date: Wed, 1 May 2024 12:49:34 -0400 Subject: [PATCH 0190/2054] Regex correction Co-authored-by: TheKodeToad Signed-off-by: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> --- launcher/StringUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index e05affde10..82147c16a1 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -215,7 +215,7 @@ QPair StringUtils::splitFirst(const QString& s, const QRegular QString StringUtils::htmlListPatch(QString htmlStr) { - QRegularExpression match("|"); + QRegularExpression match("<\\s/\\s*ul\\s*>"); int pos = htmlStr.indexOf(match); int imgPos, dist; while (pos != -1) { From 0c76e7ab20f24ecc0a804732aca6ccf3ebd1e0c3 Mon Sep 17 00:00:00 2001 From: SabrePenguin Date: Wed, 1 May 2024 13:00:55 -0400 Subject: [PATCH 0191/2054] Made Regex static and const, removed dist Signed-off-by: SabrePenguin --- launcher/StringUtils.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index 82147c16a1..edda9f2476 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -213,14 +213,14 @@ QPair StringUtils::splitFirst(const QString& s, const QRegular return qMakePair(left, right); } +static const QRegularExpression ulMatcher("<\\s*/\\s*ul\\s*>"); + QString StringUtils::htmlListPatch(QString htmlStr) { - QRegularExpression match("<\\s/\\s*ul\\s*>"); - int pos = htmlStr.indexOf(match); - int imgPos, dist; + int pos = htmlStr.indexOf(ulMatcher); + int imgPos; while (pos != -1) { - dist = htmlStr.indexOf(">", pos) - pos + 1; // Get the size of the tag. Add one for zeroeth index - pos = pos + dist; + pos = htmlStr.indexOf(">", pos) + 1; // Get the size of the tag. Add one for zeroeth index imgPos = htmlStr.indexOf(""); - pos = htmlStr.indexOf(match, pos); + pos = htmlStr.indexOf(ulMatcher, pos); } return htmlStr; } \ No newline at end of file From 8e0af16de979ba92e9983b47c44ee0f321fcb116 Mon Sep 17 00:00:00 2001 From: Samuel Stidham Date: Sun, 5 May 2024 19:53:18 -0400 Subject: [PATCH 0192/2054] Add extra java locations for MacOs. Signed-off-by: Samuel Stidham --- launcher/java/JavaUtils.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 3627cec395..b996bf0464 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -362,6 +362,12 @@ QList JavaUtils::FindJavaPaths() javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java"); } + + auto home = qEnvironmentVariable("HOME"); + + // javas downloaded by sdkman + scanJavaDirs(FS::PathCombine(home, ".sdkman/candidates/java")); + javas.append(getMinecraftJavaBundle()); javas = addJavasFromEnv(javas); javas.removeDuplicates(); From adf0cfdebf2d27148139da2965900a9b3c1ec389 Mon Sep 17 00:00:00 2001 From: Samuel Stidham Date: Sun, 5 May 2024 20:01:29 -0400 Subject: [PATCH 0193/2054] Fixed the code. Signed-off-by: Samuel Stidham --- launcher/java/JavaUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index b996bf0464..e767eff89d 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -366,7 +366,7 @@ QList JavaUtils::FindJavaPaths() auto home = qEnvironmentVariable("HOME"); // javas downloaded by sdkman - scanJavaDirs(FS::PathCombine(home, ".sdkman/candidates/java")); + javas.append(FS::PathCombine(home, ".sdkman/candidates/java")); javas.append(getMinecraftJavaBundle()); javas = addJavasFromEnv(javas); From a95c1768a778f3ade8d61fef4c636a21a55bb4fe Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 8 May 2024 14:16:12 +0300 Subject: [PATCH 0194/2054] Add neoforge support for technic packs and atlauncher packs Signed-off-by: Trial97 --- .../atlauncher/ATLPackInstallTask.cpp | 6 +++++ .../technic/TechnicPackProcessor.cpp | 23 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 8ae8145de7..01de88b041 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -1031,6 +1031,12 @@ void PackInstallTask::install() return; components->setComponentVersion("net.minecraftforge", version); + } else if (m_version.loader.type == QString("neoforge")) { + auto version = getVersionForLoader("net.neoforged"); + if (version == Q_NULLPTR) + return; + + components->setComponentVersion("net.neoforged", version); } else if (m_version.loader.type == QString("fabric")) { auto version = getVersionForLoader("net.fabricmc.fabric-loader"); if (version == Q_NULLPTR) diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp index 90f59ce548..a47a4811f2 100644 --- a/launcher/modplatform/technic/TechnicPackProcessor.cpp +++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp @@ -155,8 +155,26 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, auto libraryObject = Json::ensureObject(library, {}, ""); auto libraryName = Json::ensureString(libraryObject, "name", "", ""); - if ((libraryName.startsWith("net.minecraftforge:forge:") || libraryName.startsWith("net.minecraftforge:fmlloader:")) && - libraryName.contains('-')) { + if (libraryName.startsWith("net.neoforged.fancymodloader:")) { // it is neoforge + // no easy way to get the version from the libs so use the arguments + auto arguments = Json::ensureObject(root, "arguments", {}); + bool isVersionArg = false; + QString neoforgeVersion; + for (auto arg : Json::ensureArray(arguments, "game", {})) { + auto argument = Json::ensureString(arg, ""); + if (isVersionArg) { + neoforgeVersion = argument; + break; + } else { + isVersionArg = "--fml.neoForgeVersion" == argument || "--fml.forgeVersion" == argument; + } + } + if (!neoforgeVersion.isEmpty()) { + components->setComponentVersion("net.neoforged", neoforgeVersion); + } + break; + } else if ((libraryName.startsWith("net.minecraftforge:forge:") || libraryName.startsWith("net.minecraftforge:fmlloader:")) && + libraryName.contains('-')) { QString libraryVersion = libraryName.section(':', 2); if (!libraryVersion.startsWith("1.7.10-")) { components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1)); @@ -164,6 +182,7 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, // 1.7.10 versions sometimes look like 1.7.10-10.13.4.1614-1.7.10, this filters out the 10.13.4.1614 part components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1)); } + break; } else { // -> static QMap loaderMap{ { "net.minecraftforge:minecraftforge:", "net.minecraftforge" }, From d795ba25e745912c9772bd084eb98f5f039d041d Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 10 May 2024 20:55:26 +0300 Subject: [PATCH 0195/2054] Delete instaces tmp diectory on startup Signed-off-by: Trial97 --- launcher/Application.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index bb8751cccc..f696d9022c 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1209,6 +1209,12 @@ void Application::performMainStartupAction() qDebug() << "<> Updater started."; } + { // delete instances tmp dirctory + auto instDir = m_settings->get("InstanceDir").toString(); + const QString tempRoot = FS::PathCombine(instDir, ".tmp"); + FS::deletePath(tempRoot); + } + if (!m_urlsToImport.isEmpty()) { qDebug() << "<> Importing from url:" << m_urlsToImport; m_mainWindow->processURLs(m_urlsToImport); From 3336f8107c48e8d0c85e45fcc2e0ca46db25fc10 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 13 May 2024 23:25:08 +0300 Subject: [PATCH 0196/2054] Removed AuthRequest and NetAction Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 4 - launcher/InstanceCreationTask.cpp | 2 - launcher/InstanceCreationTask.h | 2 +- launcher/minecraft/AssetsUtils.cpp | 3 +- launcher/minecraft/AssetsUtils.h | 4 +- launcher/minecraft/Library.cpp | 11 +- launcher/minecraft/Library.h | 10 +- launcher/minecraft/auth/AccountList.cpp | 5 +- launcher/minecraft/auth/AuthRequest.cpp | 175 ------------------ launcher/minecraft/auth/AuthRequest.h | 67 ------- launcher/minecraft/auth/AuthStep.cpp | 2 - launcher/minecraft/auth/AuthStep.h | 3 +- launcher/minecraft/auth/MinecraftAccount.cpp | 2 +- launcher/minecraft/auth/flows/Offline.cpp | 5 - launcher/minecraft/auth/flows/Offline.h | 6 - .../minecraft/auth/steps/EntitlementsStep.cpp | 50 ++--- .../minecraft/auth/steps/EntitlementsStep.h | 12 +- launcher/minecraft/auth/steps/GetSkinStep.cpp | 32 ++-- launcher/minecraft/auth/steps/GetSkinStep.h | 12 +- .../auth/steps/LauncherLoginStep.cpp | 54 +++--- .../minecraft/auth/steps/LauncherLoginStep.h | 12 +- launcher/minecraft/auth/steps/MSAStep.cpp | 20 -- launcher/minecraft/auth/steps/MSAStep.h | 3 +- .../auth/steps/MinecraftProfileStep.cpp | 55 +++--- .../auth/steps/MinecraftProfileStep.h | 12 +- launcher/minecraft/auth/steps/OfflineStep.cpp | 8 - launcher/minecraft/auth/steps/OfflineStep.h | 6 +- .../auth/steps/XboxAuthorizationStep.cpp | 59 +++--- .../auth/steps/XboxAuthorizationStep.h | 13 +- .../minecraft/auth/steps/XboxProfileStep.cpp | 55 +++--- .../minecraft/auth/steps/XboxProfileStep.h | 12 +- .../minecraft/auth/steps/XboxUserStep.cpp | 51 +++-- launcher/minecraft/auth/steps/XboxUserStep.h | 12 +- .../helpers/NetworkResourceAPI.cpp | 12 +- .../modrinth/ModrinthInstanceCreationTask.cpp | 2 +- launcher/net/ApiDownload.cpp | 1 - launcher/net/ApiUpload.cpp | 3 - launcher/net/Download.cpp | 2 - launcher/net/NetAction.h | 100 ---------- launcher/net/NetJob.cpp | 11 +- launcher/net/NetJob.h | 6 +- launcher/net/NetRequest.cpp | 36 +++- launcher/net/NetRequest.h | 44 +++-- launcher/net/Sink.h | 3 +- launcher/net/Validator.h | 2 +- launcher/ui/dialogs/ProfileSetupDialog.cpp | 71 ++++--- launcher/ui/dialogs/ProfileSetupDialog.h | 12 +- .../ui/pages/modplatform/ResourceModel.cpp | 4 +- .../modplatform/modrinth/ModrinthModel.cpp | 4 +- tests/Library_test.cpp | 26 +-- 50 files changed, 378 insertions(+), 740 deletions(-) delete mode 100644 launcher/minecraft/auth/AuthRequest.cpp delete mode 100644 launcher/minecraft/auth/AuthRequest.h delete mode 100644 launcher/net/NetAction.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e93219015e..cf1ab87981 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -126,7 +126,6 @@ set(NET_SOURCES net/MetaCacheSink.h net/Logging.h net/Logging.cpp - net/NetAction.h net/NetJob.cpp net/NetJob.h net/NetUtils.h @@ -212,8 +211,6 @@ set(MINECRAFT_SOURCES minecraft/auth/AccountList.h minecraft/auth/AccountTask.cpp minecraft/auth/AccountTask.h - minecraft/auth/AuthRequest.cpp - minecraft/auth/AuthRequest.h minecraft/auth/AuthSession.cpp minecraft/auth/AuthSession.h minecraft/auth/AuthStep.cpp @@ -624,7 +621,6 @@ set(PRISMUPDATER_SOURCES net/HttpMetaCache.h net/Logging.h net/Logging.cpp - net/NetAction.h net/NetRequest.cpp net/NetRequest.h net/NetJob.cpp diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index 73dc17891d..1499199ae9 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -3,8 +3,6 @@ #include #include -InstanceCreationTask::InstanceCreationTask() = default; - void InstanceCreationTask::executeTask() { setAbortable(true); diff --git a/launcher/InstanceCreationTask.h b/launcher/InstanceCreationTask.h index 380fdf8a40..84fb2a1455 100644 --- a/launcher/InstanceCreationTask.h +++ b/launcher/InstanceCreationTask.h @@ -6,7 +6,7 @@ class InstanceCreationTask : public InstanceTask { Q_OBJECT public: - InstanceCreationTask(); + InstanceCreationTask() = default; virtual ~InstanceCreationTask() = default; protected: diff --git a/launcher/minecraft/AssetsUtils.cpp b/launcher/minecraft/AssetsUtils.cpp index 48e150d160..6bbe0bb2c3 100644 --- a/launcher/minecraft/AssetsUtils.cpp +++ b/launcher/minecraft/AssetsUtils.cpp @@ -51,6 +51,7 @@ #include "net/Download.h" #include "Application.h" +#include "net/NetRequest.h" namespace { QSet collectPathsFromDir(QString dirPath) @@ -276,7 +277,7 @@ bool reconstructAssets(QString assetsId, QString resourcesFolder) } // namespace AssetsUtils -NetAction::Ptr AssetObject::getDownloadAction() +Net::NetRequest::Ptr AssetObject::getDownloadAction() { QFileInfo objectFile(getLocalPath()); if ((!objectFile.isFile()) || (objectFile.size() != size)) { diff --git a/launcher/minecraft/AssetsUtils.h b/launcher/minecraft/AssetsUtils.h index 87956e57aa..ea3613bd07 100644 --- a/launcher/minecraft/AssetsUtils.h +++ b/launcher/minecraft/AssetsUtils.h @@ -17,14 +17,14 @@ #include #include -#include "net/NetAction.h" #include "net/NetJob.h" +#include "net/NetRequest.h" struct AssetObject { QString getRelPath(); QUrl getUrl(); QString getLocalPath(); - NetAction::Ptr getDownloadAction(); + Net::NetRequest::Ptr getDownloadAction(); QString hash; qint64 size; diff --git a/launcher/minecraft/Library.cpp b/launcher/minecraft/Library.cpp index 0e8ddf03d8..2c3f2035ff 100644 --- a/launcher/minecraft/Library.cpp +++ b/launcher/minecraft/Library.cpp @@ -35,6 +35,7 @@ #include "Library.h" #include "MinecraftInstance.h" +#include "net/NetRequest.h" #include #include @@ -74,12 +75,12 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext, } } -QList Library::getDownloads(const RuntimeContext& runtimeContext, - class HttpMetaCache* cache, - QStringList& failedLocalFiles, - const QString& overridePath) const +QList Library::getDownloads(const RuntimeContext& runtimeContext, + class HttpMetaCache* cache, + QStringList& failedLocalFiles, + const QString& overridePath) const { - QList out; + QList out; bool stale = isAlwaysStale(); bool local = isLocal(); diff --git a/launcher/minecraft/Library.h b/launcher/minecraft/Library.h index adb89c4c61..d3019e814a 100644 --- a/launcher/minecraft/Library.h +++ b/launcher/minecraft/Library.h @@ -34,7 +34,6 @@ */ #pragma once -#include #include #include #include @@ -48,6 +47,7 @@ #include "MojangDownloadInfo.h" #include "Rule.h" #include "RuntimeContext.h" +#include "net/NetRequest.h" class Library; class MinecraftInstance; @@ -144,10 +144,10 @@ class Library { bool isForge() const; // Get a list of downloads for this library - QList getDownloads(const RuntimeContext& runtimeContext, - class HttpMetaCache* cache, - QStringList& failedLocalFiles, - const QString& overridePath) const; + QList getDownloads(const RuntimeContext& runtimeContext, + class HttpMetaCache* cache, + QStringList& failedLocalFiles, + const QString& overridePath) const; QString getCompatibleNative(const RuntimeContext& runtimeContext) const; diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index 68ebe36264..af83502bcb 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -36,6 +36,7 @@ #include "AccountList.h" #include "AccountData.h" #include "AccountTask.h" +#include "tasks/Task.h" #include #include @@ -639,8 +640,8 @@ void AccountList::tryNext() if (account->internalId() == accountId) { m_currentTask = account->refresh(); if (m_currentTask) { - connect(m_currentTask.get(), &AccountTask::succeeded, this, &AccountList::authSucceeded); - connect(m_currentTask.get(), &AccountTask::failed, this, &AccountList::authFailed); + connect(m_currentTask.get(), &Task::succeeded, this, &AccountList::authSucceeded); + connect(m_currentTask.get(), &Task::failed, this, &AccountList::authFailed); m_currentTask->start(); qDebug() << "RefreshSchedule: Processing account " << account->accountDisplayString() << " with internal ID " << accountId; diff --git a/launcher/minecraft/auth/AuthRequest.cpp b/launcher/minecraft/auth/AuthRequest.cpp deleted file mode 100644 index 189978cc05..0000000000 --- a/launcher/minecraft/auth/AuthRequest.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * 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 - -#include -#include -#include -#include - -#include "Application.h" -#include "AuthRequest.h" -#include "katabasis/Globals.h" - -AuthRequest::AuthRequest(QObject* parent) : QObject(parent) {} - -AuthRequest::~AuthRequest() {} - -void AuthRequest::get(const QNetworkRequest& req, int timeout /* = 60*1000*/) -{ - setup(req, QNetworkAccessManager::GetOperation); - reply_ = APPLICATION->network()->get(request_); - status_ = Requesting; - timedReplies_.add(new Katabasis::Reply(reply_, timeout)); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 - connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError); -#else // &QNetworkReply::error SIGNAL depricated - connect(reply_, QOverload::of(&QNetworkReply::error), this, &AuthRequest::onRequestError); -#endif - connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished); - connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors); -} - -void AuthRequest::post(const QNetworkRequest& req, const QByteArray& data, int timeout /* = 60*1000*/) -{ - setup(req, QNetworkAccessManager::PostOperation); - data_ = data; - status_ = Requesting; - reply_ = APPLICATION->network()->post(request_, data_); - timedReplies_.add(new Katabasis::Reply(reply_, timeout)); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 - connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError); -#else // &QNetworkReply::error SIGNAL depricated - connect(reply_, QOverload::of(&QNetworkReply::error), this, &AuthRequest::onRequestError); -#endif - connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished); - connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors); - connect(reply_, &QNetworkReply::uploadProgress, this, &AuthRequest::onUploadProgress); -} - -void AuthRequest::onRequestFinished() -{ - if (status_ == Idle) { - return; - } - if (reply_ != qobject_cast(sender())) { - return; - } - httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - finish(); -} - -void AuthRequest::onRequestError(QNetworkReply::NetworkError error) -{ - qWarning() << "AuthRequest::onRequestError: Error" << (int)error; - if (status_ == Idle) { - return; - } - if (reply_ != qobject_cast(sender())) { - return; - } - errorString_ = reply_->errorString(); - httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - error_ = error; - qWarning() << "AuthRequest::onRequestError: Error string: " << errorString_; - qWarning() << "AuthRequest::onRequestError: HTTP status" << httpStatus_ - << reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); - - // QTimer::singleShot(10, this, SLOT(finish())); -} - -void AuthRequest::onSslErrors(QList 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 AuthRequest::onUploadProgress(qint64 uploaded, qint64 total) -{ - if (status_ == Idle) { - qWarning() << "AuthRequest::onUploadProgress: No pending request"; - return; - } - if (reply_ != qobject_cast(sender())) { - return; - } - // Restart timeout because request in progress - Katabasis::Reply* o2Reply = timedReplies_.find(reply_); - if (o2Reply) { - o2Reply->start(); - } - emit uploadProgress(uploaded, total); -} - -void AuthRequest::setup(const QNetworkRequest& req, QNetworkAccessManager::Operation operation, const QByteArray& verb) -{ - request_ = req; - operation_ = operation; - url_ = req.url(); - - QUrl url = url_; - request_.setUrl(url); - - if (!verb.isEmpty()) { - request_.setRawHeader(Katabasis::HTTP_HTTP_HEADER, verb); - } - - status_ = Requesting; - error_ = QNetworkReply::NoError; - errorString_.clear(); - httpStatus_ = 0; -} - -void AuthRequest::finish() -{ - QByteArray data; - if (status_ == Idle) { - qWarning() << "AuthRequest::finish: No pending request"; - return; - } - data = reply_->readAll(); - status_ = Idle; - timedReplies_.remove(reply_); - reply_->disconnect(this); - reply_->deleteLater(); - QList headers = reply_->rawHeaderPairs(); - emit finished(error_, data, headers); -} diff --git a/launcher/minecraft/auth/AuthRequest.h b/launcher/minecraft/auth/AuthRequest.h deleted file mode 100644 index 84d2a7d686..0000000000 --- a/launcher/minecraft/auth/AuthRequest.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include - -#include "katabasis/Reply.h" - -/// Makes authentication requests. -class AuthRequest : public QObject { - Q_OBJECT - - public: - explicit AuthRequest(QObject* parent = 0); - ~AuthRequest(); - - public slots: - void get(const QNetworkRequest& req, int timeout = 60 * 1000); - void post(const QNetworkRequest& req, const QByteArray& data, int timeout = 60 * 1000); - - signals: - - /// Emitted when a request has been completed or failed. - void finished(QNetworkReply::NetworkError error, QByteArray data, QList headers); - - /// Emitted when an upload has progressed. - void uploadProgress(qint64 bytesSent, qint64 bytesTotal); - - protected slots: - - /// Handle request finished. - void onRequestFinished(); - - /// Handle request error. - void onRequestError(QNetworkReply::NetworkError error); - - /// Handle ssl errors. - void onSslErrors(QList errors); - - /// Finish the request, emit finished() signal. - void finish(); - - /// Handle upload progress. - void onUploadProgress(qint64 uploaded, qint64 total); - - public: - QNetworkReply::NetworkError error_; - int httpStatus_ = 0; - QString errorString_; - - protected: - void setup(const QNetworkRequest& request, QNetworkAccessManager::Operation operation, const QByteArray& verb = QByteArray()); - - enum Status { Idle, Requesting, ReRequesting }; - - QNetworkRequest request_; - QByteArray data_; - QNetworkReply* reply_; - Status status_; - QNetworkAccessManager::Operation operation_; - QUrl url_; - Katabasis::ReplyList timedReplies_; - - QTimer* timer_; -}; diff --git a/launcher/minecraft/auth/AuthStep.cpp b/launcher/minecraft/auth/AuthStep.cpp index 6240cc549f..6b78c415a2 100644 --- a/launcher/minecraft/auth/AuthStep.cpp +++ b/launcher/minecraft/auth/AuthStep.cpp @@ -1,5 +1,3 @@ #include "AuthStep.h" AuthStep::AuthStep(AccountData* data) : QObject(nullptr), m_data(data) {} - -AuthStep::~AuthStep() noexcept = default; diff --git a/launcher/minecraft/auth/AuthStep.h b/launcher/minecraft/auth/AuthStep.h index becd9b0c52..b837b5703b 100644 --- a/launcher/minecraft/auth/AuthStep.h +++ b/launcher/minecraft/auth/AuthStep.h @@ -15,13 +15,12 @@ class AuthStep : public QObject { public: explicit AuthStep(AccountData* data); - virtual ~AuthStep() noexcept; + virtual ~AuthStep() noexcept = default; virtual QString describe() = 0; public slots: virtual void perform() = 0; - virtual void rehydrate() = 0; signals: void finished(AccountTaskState resultingState, QString message); diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index ecee93d985..db4d1c07bb 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -153,7 +153,7 @@ shared_qobject_ptr MinecraftAccount::refresh() if (data.type == AccountType::MSA) { m_currentTask.reset(new MSASilent(&data)); } else { - m_currentTask.reset(new OfflineRefresh(&data)); + m_currentTask.reset(new OfflineLogin(&data)); } connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); diff --git a/launcher/minecraft/auth/flows/Offline.cpp b/launcher/minecraft/auth/flows/Offline.cpp index 3770b869a1..1836533bdc 100644 --- a/launcher/minecraft/auth/flows/Offline.cpp +++ b/launcher/minecraft/auth/flows/Offline.cpp @@ -2,11 +2,6 @@ #include "minecraft/auth/steps/OfflineStep.h" -OfflineRefresh::OfflineRefresh(AccountData* data, QObject* parent) : AuthFlow(data, parent) -{ - m_steps.append(makeShared(m_data)); -} - OfflineLogin::OfflineLogin(AccountData* data, QObject* parent) : AuthFlow(data, parent) { m_steps.append(makeShared(m_data)); diff --git a/launcher/minecraft/auth/flows/Offline.h b/launcher/minecraft/auth/flows/Offline.h index 2bc9c76127..a8d378e16f 100644 --- a/launcher/minecraft/auth/flows/Offline.h +++ b/launcher/minecraft/auth/flows/Offline.h @@ -1,12 +1,6 @@ #pragma once #include "AuthFlow.h" -class OfflineRefresh : public AuthFlow { - Q_OBJECT - public: - explicit OfflineRefresh(AccountData* data, QObject* parent = 0); -}; - class OfflineLogin : public AuthFlow { Q_OBJECT public: diff --git a/launcher/minecraft/auth/steps/EntitlementsStep.cpp b/launcher/minecraft/auth/steps/EntitlementsStep.cpp index 0573dcb6e0..19cbe6898c 100644 --- a/launcher/minecraft/auth/steps/EntitlementsStep.cpp +++ b/launcher/minecraft/auth/steps/EntitlementsStep.cpp @@ -1,16 +1,20 @@ #include "EntitlementsStep.h" +#include #include +#include #include +#include +#include "Application.h" #include "Logging.h" -#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" +#include "net/Download.h" +#include "net/StaticHeaderProxy.h" +#include "tasks/Task.h" EntitlementsStep::EntitlementsStep(AccountData* data) : AuthStep(data) {} -EntitlementsStep::~EntitlementsStep() noexcept = default; - QString EntitlementsStep::describe() { return tr("Determining game ownership."); @@ -19,35 +23,31 @@ QString EntitlementsStep::describe() void EntitlementsStep::perform() { auto uuid = QUuid::createUuid(); - m_entitlementsRequestId = uuid.toString().remove('{').remove('}'); - auto url = "https://api.minecraftservices.com/entitlements/license?requestId=" + m_entitlementsRequestId; - QNetworkRequest request = QNetworkRequest(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8()); - AuthRequest* requestor = new AuthRequest(this); - connect(requestor, &AuthRequest::finished, this, &EntitlementsStep::onRequestDone); - requestor->get(request); - qDebug() << "Getting entitlements..."; -} + m_entitlements_request_id = uuid.toString().remove('{').remove('}'); -void EntitlementsStep::rehydrate() -{ - // NOOP, for now. We only save bools and there's nothing to check. + QUrl url("https://api.minecraftservices.com/entitlements/license?requestId=" + m_entitlements_request_id); + auto headers = QList{ { "Content-Type", "application/json" }, + { "Accept", "application/json" }, + { "Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8() } }; + + m_response.reset(new QByteArray()); + m_task = Net::Download::makeByteArray(url, m_response); + m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers)); + + connect(m_task.get(), &Task::finished, this, &EntitlementsStep::onRequestDone); + + m_task->setNetwork(APPLICATION->network()); + m_task->start(); + qDebug() << "Getting entitlements..."; } -void EntitlementsStep::onRequestDone([[maybe_unused]] QNetworkReply::NetworkError error, - QByteArray data, - [[maybe_unused]] QList headers) +void EntitlementsStep::onRequestDone() { - auto requestor = qobject_cast(QObject::sender()); - requestor->deleteLater(); - - qCDebug(authCredentials()) << data; + qCDebug(authCredentials()) << *m_response; // TODO: check presence of same entitlementsRequestId? // TODO: validate JWTs? - Parsers::parseMinecraftEntitlements(data, m_data->minecraftEntitlement); + Parsers::parseMinecraftEntitlements(*m_response, m_data->minecraftEntitlement); emit finished(AccountTaskState::STATE_WORKING, tr("Got entitlements")); } diff --git a/launcher/minecraft/auth/steps/EntitlementsStep.h b/launcher/minecraft/auth/steps/EntitlementsStep.h index be16bda132..dd8ec7aaa4 100644 --- a/launcher/minecraft/auth/steps/EntitlementsStep.h +++ b/launcher/minecraft/auth/steps/EntitlementsStep.h @@ -1,24 +1,26 @@ #pragma once #include +#include -#include "QObjectPtr.h" #include "minecraft/auth/AuthStep.h" +#include "net/Download.h" class EntitlementsStep : public AuthStep { Q_OBJECT public: explicit EntitlementsStep(AccountData* data); - virtual ~EntitlementsStep() noexcept; + virtual ~EntitlementsStep() noexcept = default; void perform() override; - void rehydrate() override; QString describe() override; private slots: - void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList); + void onRequestDone(); private: - QString m_entitlementsRequestId; + QString m_entitlements_request_id; + std::shared_ptr m_response; + Net::Download::Ptr m_task; }; diff --git a/launcher/minecraft/auth/steps/GetSkinStep.cpp b/launcher/minecraft/auth/steps/GetSkinStep.cpp index 520877020e..d9785b16a1 100644 --- a/launcher/minecraft/auth/steps/GetSkinStep.cpp +++ b/launcher/minecraft/auth/steps/GetSkinStep.cpp @@ -3,13 +3,10 @@ #include -#include "minecraft/auth/AuthRequest.h" -#include "minecraft/auth/Parsers.h" +#include "Application.h" GetSkinStep::GetSkinStep(AccountData* data) : AuthStep(data) {} -GetSkinStep::~GetSkinStep() noexcept = default; - QString GetSkinStep::describe() { return tr("Getting skin."); @@ -17,25 +14,20 @@ QString GetSkinStep::describe() void GetSkinStep::perform() { - auto url = QUrl(m_data->minecraftProfile.skin.url); - QNetworkRequest request = QNetworkRequest(url); - AuthRequest* requestor = new AuthRequest(this); - connect(requestor, &AuthRequest::finished, this, &GetSkinStep::onRequestDone); - requestor->get(request); -} + QUrl url(m_data->minecraftProfile.skin.url); -void GetSkinStep::rehydrate() -{ - // NOOP, for now. + m_response.reset(new QByteArray()); + m_task = Net::Download::makeByteArray(url, m_response); + + connect(m_task.get(), &Task::finished, this, &GetSkinStep::onRequestDone); + + m_task->setNetwork(APPLICATION->network()); + m_task->start(); } -void GetSkinStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList headers) +void GetSkinStep::onRequestDone() { - auto requestor = qobject_cast(QObject::sender()); - requestor->deleteLater(); - - if (error == QNetworkReply::NoError) { - m_data->minecraftProfile.skin.data = data; - } + if (m_task->error() == QNetworkReply::NoError) + m_data->minecraftProfile.skin.data = *m_response; emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Got skin")); } diff --git a/launcher/minecraft/auth/steps/GetSkinStep.h b/launcher/minecraft/auth/steps/GetSkinStep.h index 105e497d1a..fffd8be039 100644 --- a/launcher/minecraft/auth/steps/GetSkinStep.h +++ b/launcher/minecraft/auth/steps/GetSkinStep.h @@ -1,21 +1,25 @@ #pragma once #include +#include -#include "QObjectPtr.h" #include "minecraft/auth/AuthStep.h" +#include "net/Download.h" class GetSkinStep : public AuthStep { Q_OBJECT public: explicit GetSkinStep(AccountData* data); - virtual ~GetSkinStep() noexcept; + virtual ~GetSkinStep() noexcept = default; void perform() override; - void rehydrate() override; QString describe() override; private slots: - void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList); + void onRequestDone(); + + private: + std::shared_ptr m_response; + Net::Download::Ptr m_task; }; diff --git a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp index c57f511137..8981c57528 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp @@ -1,17 +1,18 @@ #include "LauncherLoginStep.h" #include +#include +#include "Application.h" #include "Logging.h" #include "minecraft/auth/AccountTask.h" -#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" +#include "net/StaticHeaderProxy.h" +#include "net/Upload.h" LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {} -LauncherLoginStep::~LauncherLoginStep() noexcept = default; - QString LauncherLoginStep::describe() { return tr("Accessing Mojang services."); @@ -19,7 +20,7 @@ QString LauncherLoginStep::describe() void LauncherLoginStep::perform() { - auto requestURL = "https://api.minecraftservices.com/launcher/login"; + QUrl url("https://api.minecraftservices.com/launcher/login"); auto uhs = m_data->mojangservicesToken.extra["uhs"].toString(); auto xToken = m_data->mojangservicesToken.token; @@ -31,40 +32,37 @@ void LauncherLoginStep::perform() )XXX"; auto requestBody = mc_auth_template.arg(uhs, xToken); - QNetworkRequest request = QNetworkRequest(QUrl(requestURL)); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - AuthRequest* requestor = new AuthRequest(this); - connect(requestor, &AuthRequest::finished, this, &LauncherLoginStep::onRequestDone); - requestor->post(request, requestBody.toUtf8()); - qDebug() << "Getting Minecraft access token..."; -} + auto headers = QList{ + { "Content-Type", "application/json" }, + { "Accept", "application/json" }, + }; -void LauncherLoginStep::rehydrate() -{ - // TODO: check the token validity + m_response.reset(new QByteArray()); + m_task = Net::Upload::makeByteArray(url, m_response, requestBody.toUtf8()); + m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers)); + + connect(m_task.get(), &Task::finished, this, &LauncherLoginStep::onRequestDone); + + m_task->setNetwork(APPLICATION->network()); + m_task->start(); + qDebug() << "Getting Minecraft access token..."; } -void LauncherLoginStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList headers) +void LauncherLoginStep::onRequestDone() { - auto requestor = qobject_cast(QObject::sender()); - requestor->deleteLater(); - - qCDebug(authCredentials()) << data; - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - qCDebug(authCredentials()) << data; - if (Net::isApplicationError(error)) { - emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_)); + qCDebug(authCredentials()) << *m_response; + if (m_task->error() != QNetworkReply::NoError) { + qWarning() << "Reply error:" << m_task->error(); + if (Net::isApplicationError(m_task->error())) { + emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(m_task->errorString())); } else { - emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_)); + emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(m_task->errorString())); } return; } - if (!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) { + if (!Parsers::parseMojangResponse(*m_response, m_data->yggdrasilToken)) { qWarning() << "Could not parse login_with_xbox response..."; - qCDebug(authCredentials()) << data; emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to parse the Minecraft access token response.")); return; } diff --git a/launcher/minecraft/auth/steps/LauncherLoginStep.h b/launcher/minecraft/auth/steps/LauncherLoginStep.h index 30c18e6759..21a2a49202 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.h +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.h @@ -1,21 +1,25 @@ #pragma once #include +#include -#include "QObjectPtr.h" #include "minecraft/auth/AuthStep.h" +#include "net/Upload.h" class LauncherLoginStep : public AuthStep { Q_OBJECT public: explicit LauncherLoginStep(AccountData* data); - virtual ~LauncherLoginStep() noexcept; + virtual ~LauncherLoginStep() noexcept = default; void perform() override; - void rehydrate() override; QString describe() override; private slots: - void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList); + void onRequestDone(); + + private: + std::shared_ptr m_response; + Net::Upload::Ptr m_task; }; diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp index 1aa22765d4..0a4b7c8144 100644 --- a/launcher/minecraft/auth/steps/MSAStep.cpp +++ b/launcher/minecraft/auth/steps/MSAStep.cpp @@ -37,10 +37,6 @@ #include -#include "BuildConfig.h" -#include "minecraft/auth/AuthRequest.h" -#include "minecraft/auth/Parsers.h" - #include "Application.h" #include "Logging.h" @@ -63,27 +59,11 @@ MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(ac connect(m_oauth2, &OAuth2::showVerificationUriAndCode, this, &MSAStep::showVerificationUriAndCode); } -MSAStep::~MSAStep() noexcept = default; - QString MSAStep::describe() { return tr("Logging in with Microsoft account."); } -void MSAStep::rehydrate() -{ - switch (m_action) { - case Refresh: { - // TODO: check the tokens and see if they are old (older than a day) - return; - } - case Login: { - // NOOP - return; - } - } -} - void MSAStep::perform() { switch (m_action) { diff --git a/launcher/minecraft/auth/steps/MSAStep.h b/launcher/minecraft/auth/steps/MSAStep.h index b6635d4a52..ee441308fa 100644 --- a/launcher/minecraft/auth/steps/MSAStep.h +++ b/launcher/minecraft/auth/steps/MSAStep.h @@ -48,10 +48,9 @@ class MSAStep : public AuthStep { public: explicit MSAStep(AccountData* data, Action action); - virtual ~MSAStep() noexcept; + virtual ~MSAStep() noexcept = default; void perform() override; - void rehydrate() override; QString describe() override; diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index a854342bc0..305f44320a 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -2,15 +2,13 @@ #include -#include "Logging.h" -#include "minecraft/auth/AuthRequest.h" +#include "Application.h" #include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" +#include "net/StaticHeaderProxy.h" MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {} -MinecraftProfileStep::~MinecraftProfileStep() noexcept = default; - QString MinecraftProfileStep::describe() { return tr("Fetching the Minecraft profile."); @@ -18,52 +16,47 @@ QString MinecraftProfileStep::describe() void MinecraftProfileStep::perform() { - auto url = QUrl("https://api.minecraftservices.com/minecraft/profile"); - QNetworkRequest request = QNetworkRequest(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8()); + QUrl url("https://api.minecraftservices.com/minecraft/profile"); + auto headers = QList{ { "Content-Type", "application/json" }, + { "Accept", "application/json" }, + { "Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8() } }; - AuthRequest* requestor = new AuthRequest(this); - connect(requestor, &AuthRequest::finished, this, &MinecraftProfileStep::onRequestDone); - requestor->get(request); -} + m_response.reset(new QByteArray()); + m_task = Net::Download::makeByteArray(url, m_response); + m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers)); -void MinecraftProfileStep::rehydrate() -{ - // NOOP, for now. We only save bools and there's nothing to check. + connect(m_task.get(), &Task::finished, this, &MinecraftProfileStep::onRequestDone); + + m_task->setNetwork(APPLICATION->network()); + m_task->start(); } -void MinecraftProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList headers) +void MinecraftProfileStep::onRequestDone() { - auto requestor = qobject_cast(QObject::sender()); - requestor->deleteLater(); - - qCDebug(authCredentials()) << data; - if (error == QNetworkReply::ContentNotFoundError) { + if (m_task->error() == QNetworkReply::ContentNotFoundError) { // NOTE: Succeed even if we do not have a profile. This is a valid account state. m_data->minecraftProfile = MinecraftProfile(); emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile.")); return; } - if (error != QNetworkReply::NoError) { + if (m_task->error() != QNetworkReply::NoError) { qWarning() << "Error getting profile:"; - qWarning() << " HTTP Status: " << requestor->httpStatus_; - qWarning() << " Internal error no.: " << error; - qWarning() << " Error string: " << requestor->errorString_; + qWarning() << " HTTP Status: " << m_task->replyStatusCode(); + qWarning() << " Internal error no.: " << m_task->error(); + qWarning() << " Error string: " << m_task->errorString(); qWarning() << " Response:"; - qWarning() << QString::fromUtf8(data); + qWarning() << QString::fromUtf8(*m_response); - if (Net::isApplicationError(error)) { + if (Net::isApplicationError(m_task->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, - tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)); + tr("Minecraft Java profile acquisition failed: %1").arg(m_task->errorString())); } else { - emit finished(AccountTaskState::STATE_OFFLINE, - tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)); + emit finished(AccountTaskState::STATE_OFFLINE, tr("Minecraft Java profile acquisition failed: %1").arg(m_task->errorString())); } return; } - if (!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) { + if (!Parsers::parseMinecraftProfile(*m_response, m_data->minecraftProfile)) { m_data->minecraftProfile = MinecraftProfile(); emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed")); return; diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.h b/launcher/minecraft/auth/steps/MinecraftProfileStep.h index cb30dab215..831cd52f7b 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.h +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.h @@ -1,21 +1,25 @@ #pragma once #include +#include -#include "QObjectPtr.h" #include "minecraft/auth/AuthStep.h" +#include "net/Download.h" class MinecraftProfileStep : public AuthStep { Q_OBJECT public: explicit MinecraftProfileStep(AccountData* data); - virtual ~MinecraftProfileStep() noexcept; + virtual ~MinecraftProfileStep() noexcept = default; void perform() override; - void rehydrate() override; QString describe() override; private slots: - void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList); + void onRequestDone(); + + private: + std::shared_ptr m_response; + Net::Download::Ptr m_task; }; diff --git a/launcher/minecraft/auth/steps/OfflineStep.cpp b/launcher/minecraft/auth/steps/OfflineStep.cpp index bf111abe81..a96b08377d 100644 --- a/launcher/minecraft/auth/steps/OfflineStep.cpp +++ b/launcher/minecraft/auth/steps/OfflineStep.cpp @@ -1,20 +1,12 @@ #include "OfflineStep.h" -#include "Application.h" - OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {} -OfflineStep::~OfflineStep() noexcept = default; QString OfflineStep::describe() { return tr("Creating offline account."); } -void OfflineStep::rehydrate() -{ - // NOOP -} - void OfflineStep::perform() { emit finished(AccountTaskState::STATE_WORKING, tr("Created offline account.")); diff --git a/launcher/minecraft/auth/steps/OfflineStep.h b/launcher/minecraft/auth/steps/OfflineStep.h index 3bf123d6a3..411879b105 100644 --- a/launcher/minecraft/auth/steps/OfflineStep.h +++ b/launcher/minecraft/auth/steps/OfflineStep.h @@ -1,19 +1,15 @@ #pragma once #include -#include "QObjectPtr.h" #include "minecraft/auth/AuthStep.h" -#include - class OfflineStep : public AuthStep { Q_OBJECT public: explicit OfflineStep(AccountData* data); - virtual ~OfflineStep() noexcept; + virtual ~OfflineStep() noexcept = default; void perform() override; - void rehydrate() override; QString describe() override; }; diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp index 84c52c3866..2ae3af0dd5 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -4,27 +4,22 @@ #include #include +#include "Application.h" #include "Logging.h" -#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" +#include "net/StaticHeaderProxy.h" +#include "net/Upload.h" XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind) : AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind) {} -XboxAuthorizationStep::~XboxAuthorizationStep() noexcept = default; - QString XboxAuthorizationStep::describe() { return tr("Getting authorization to access %1 services.").arg(m_authorizationKind); } -void XboxAuthorizationStep::rehydrate() -{ - // FIXME: check if the tokens are good? -} - void XboxAuthorizationStep::perform() { QString xbox_auth_template = R"XXX( @@ -41,40 +36,44 @@ void XboxAuthorizationStep::perform() )XXX"; auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty); // http://xboxlive.com - QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - AuthRequest* requestor = new AuthRequest(this); - connect(requestor, &AuthRequest::finished, this, &XboxAuthorizationStep::onRequestDone); - requestor->post(request, xbox_auth_data.toUtf8()); + QUrl url("https://xsts.auth.xboxlive.com/xsts/authorize"); + auto headers = QList{ + { "Content-Type", "application/json" }, + { "Accept", "application/json" }, + }; + m_response.reset(new QByteArray()); + m_task = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8()); + m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers)); + + connect(m_task.get(), &Task::finished, this, &XboxAuthorizationStep::onRequestDone); + + m_task->setNetwork(APPLICATION->network()); + m_task->start(); qDebug() << "Getting authorization token for " << m_relyingParty; } -void XboxAuthorizationStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList headers) +void XboxAuthorizationStep::onRequestDone() { - auto requestor = qobject_cast(QObject::sender()); - requestor->deleteLater(); - - qCDebug(authCredentials()) << data; - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - if (Net::isApplicationError(error)) { - if (!processSTSError(error, data, headers)) { + qCDebug(authCredentials()) << *m_response; + if (m_task->error() != QNetworkReply::NoError) { + qWarning() << "Reply error:" << m_task->error(); + if (Net::isApplicationError(m_task->error())) { + if (!processSTSError()) { emit finished(AccountTaskState::STATE_FAILED_SOFT, - tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, error)); + tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, m_task->error())); } else { emit finished(AccountTaskState::STATE_FAILED_SOFT, - tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, requestor->errorString_)); + tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_task->errorString())); } } else { emit finished(AccountTaskState::STATE_OFFLINE, - tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, requestor->errorString_)); + tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_task->errorString())); } return; } Katabasis::Token temp; - if (!Parsers::parseXTokenResponse(data, temp, m_authorizationKind)) { + if (!Parsers::parseXTokenResponse(*m_response, temp, m_authorizationKind)) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind)); return; @@ -91,11 +90,11 @@ void XboxAuthorizationStep::onRequestDone(QNetworkReply::NetworkError error, QBy emit finished(AccountTaskState::STATE_WORKING, tr("Got authorization to access %1").arg(m_relyingParty)); } -bool XboxAuthorizationStep::processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList headers) +bool XboxAuthorizationStep::processSTSError() { - if (error == QNetworkReply::AuthenticationRequiredError) { + if (m_task->error() == QNetworkReply::AuthenticationRequiredError) { QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); + QJsonDocument doc = QJsonDocument::fromJson(*m_response, &jsonError); if (jsonError.error) { qWarning() << "Cannot parse error XSTS response as JSON: " << jsonError.errorString(); emit finished(AccountTaskState::STATE_FAILED_SOFT, diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.h b/launcher/minecraft/auth/steps/XboxAuthorizationStep.h index dee24c9544..eb7097f6f4 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.h +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.h @@ -1,29 +1,32 @@ #pragma once #include +#include -#include "QObjectPtr.h" #include "minecraft/auth/AuthStep.h" +#include "net/Upload.h" class XboxAuthorizationStep : public AuthStep { Q_OBJECT public: explicit XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind); - virtual ~XboxAuthorizationStep() noexcept; + virtual ~XboxAuthorizationStep() noexcept = default; void perform() override; - void rehydrate() override; QString describe() override; private: - bool processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList headers); + bool processSTSError(); private slots: - void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList); + void onRequestDone(); private: Katabasis::Token* m_token; QString m_relyingParty; QString m_authorizationKind; + + std::shared_ptr m_response; + Net::Upload::Ptr m_task; }; diff --git a/launcher/minecraft/auth/steps/XboxProfileStep.cpp b/launcher/minecraft/auth/steps/XboxProfileStep.cpp index fd2b32cce9..440a4657c8 100644 --- a/launcher/minecraft/auth/steps/XboxProfileStep.cpp +++ b/launcher/minecraft/auth/steps/XboxProfileStep.cpp @@ -3,28 +3,21 @@ #include #include +#include "Application.h" #include "Logging.h" -#include "minecraft/auth/AuthRequest.h" -#include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" +#include "net/StaticHeaderProxy.h" XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {} -XboxProfileStep::~XboxProfileStep() noexcept = default; - QString XboxProfileStep::describe() { return tr("Fetching Xbox profile."); } -void XboxProfileStep::rehydrate() -{ - // NOOP, for now. We only save bools and there's nothing to check. -} - void XboxProfileStep::perform() { - auto url = QUrl("https://profile.xboxlive.com/users/me/profile/settings"); + QUrl url("https://profile.xboxlive.com/users/me/profile/settings"); QUrlQuery q; q.addQueryItem("settings", "GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw," @@ -33,36 +26,38 @@ void XboxProfileStep::perform() "PreferredColor,Location,Bio,Watermarks," "RealName,RealNameOverride,IsQuarantined"); url.setQuery(q); + auto headers = QList{ + { "Content-Type", "application/json" }, + { "Accept", "application/json" }, + { "x-xbl-contract-version", "3" }, + { "Authorization", QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8() } + }; - QNetworkRequest request = QNetworkRequest(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - request.setRawHeader("x-xbl-contract-version", "3"); - request.setRawHeader("Authorization", - QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8()); - AuthRequest* requestor = new AuthRequest(this); - connect(requestor, &AuthRequest::finished, this, &XboxProfileStep::onRequestDone); - requestor->get(request); + m_response.reset(new QByteArray()); + m_task = Net::Download::makeByteArray(url, m_response); + m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers)); + + connect(m_task.get(), &Task::finished, this, &XboxProfileStep::onRequestDone); + + m_task->setNetwork(APPLICATION->network()); + m_task->start(); qDebug() << "Getting Xbox profile..."; } -void XboxProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList headers) +void XboxProfileStep::onRequestDone() { - auto requestor = qobject_cast(QObject::sender()); - requestor->deleteLater(); - - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - qCDebug(authCredentials()) << data; - if (Net::isApplicationError(error)) { - emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_)); + if (m_task->error() != QNetworkReply::NoError) { + qWarning() << "Reply error:" << m_task->error(); + qCDebug(authCredentials()) << *m_response; + if (Net::isApplicationError(m_task->error())) { + emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to retrieve the Xbox profile: %1").arg(m_task->errorString())); } else { - emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_)); + emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to retrieve the Xbox profile: %1").arg(m_task->errorString())); } return; } - qCDebug(authCredentials()) << "XBox profile: " << data; + qCDebug(authCredentials()) << "XBox profile: " << *m_response; emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile")); } diff --git a/launcher/minecraft/auth/steps/XboxProfileStep.h b/launcher/minecraft/auth/steps/XboxProfileStep.h index b8494b6e5d..dfa273d9c3 100644 --- a/launcher/minecraft/auth/steps/XboxProfileStep.h +++ b/launcher/minecraft/auth/steps/XboxProfileStep.h @@ -1,21 +1,25 @@ #pragma once #include +#include -#include "QObjectPtr.h" #include "minecraft/auth/AuthStep.h" +#include "net/Download.h" class XboxProfileStep : public AuthStep { Q_OBJECT public: explicit XboxProfileStep(AccountData* data); - virtual ~XboxProfileStep() noexcept; + virtual ~XboxProfileStep() noexcept = default; void perform() override; - void rehydrate() override; QString describe() override; private slots: - void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList); + void onRequestDone(); + + private: + std::shared_ptr m_response; + Net::Download::Ptr m_task; }; diff --git a/launcher/minecraft/auth/steps/XboxUserStep.cpp b/launcher/minecraft/auth/steps/XboxUserStep.cpp index 856036d23a..46c3f0365b 100644 --- a/launcher/minecraft/auth/steps/XboxUserStep.cpp +++ b/launcher/minecraft/auth/steps/XboxUserStep.cpp @@ -2,24 +2,18 @@ #include -#include "minecraft/auth/AuthRequest.h" +#include "Application.h" #include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" +#include "net/StaticHeaderProxy.h" XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {} -XboxUserStep::~XboxUserStep() noexcept = default; - QString XboxUserStep::describe() { return tr("Logging in as an Xbox user."); } -void XboxUserStep::rehydrate() -{ - // NOOP, for now. We only save bools and there's nothing to check. -} - void XboxUserStep::perform() { QString xbox_auth_template = R"XXX( @@ -35,36 +29,39 @@ void XboxUserStep::perform() )XXX"; auto xbox_auth_data = xbox_auth_template.arg(m_data->msaToken.token); - QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - // set contract-version header (prevent err 400 bad-request?) - // https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders - request.setRawHeader("x-xbl-contract-version", "1"); + QUrl url("https://user.auth.xboxlive.com/user/authenticate"); + auto headers = QList{ + { "Content-Type", "application/json" }, + { "Accept", "application/json" }, + // set contract-version header (prevent err 400 bad-request?) + // https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders + { "x-xbl-contract-version", "1" } + }; + m_response.reset(new QByteArray()); + m_task = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8()); + m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers)); + + connect(m_task.get(), &Task::finished, this, &XboxUserStep::onRequestDone); - auto* requestor = new AuthRequest(this); - connect(requestor, &AuthRequest::finished, this, &XboxUserStep::onRequestDone); - requestor->post(request, xbox_auth_data.toUtf8()); + m_task->setNetwork(APPLICATION->network()); + m_task->start(); qDebug() << "First layer of XBox auth ... commencing."; } -void XboxUserStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList headers) +void XboxUserStep::onRequestDone() { - auto requestor = qobject_cast(QObject::sender()); - requestor->deleteLater(); - - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - if (Net::isApplicationError(error)) { - emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed: %1").arg(requestor->errorString_)); + if (m_task->error() != QNetworkReply::NoError) { + qWarning() << "Reply error:" << m_task->error(); + if (Net::isApplicationError(m_task->error())) { + emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed: %1").arg(m_task->errorString())); } else { - emit finished(AccountTaskState::STATE_OFFLINE, tr("XBox user authentication failed: %1").arg(requestor->errorString_)); + emit finished(AccountTaskState::STATE_OFFLINE, tr("XBox user authentication failed: %1").arg(m_task->errorString())); } return; } Katabasis::Token temp; - if (!Parsers::parseXTokenResponse(data, temp, "UToken")) { + if (!Parsers::parseXTokenResponse(*m_response, temp, "UToken")) { qWarning() << "Could not parse user authentication response..."; emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication response could not be understood.")); return; diff --git a/launcher/minecraft/auth/steps/XboxUserStep.h b/launcher/minecraft/auth/steps/XboxUserStep.h index e92727a4d6..934a00c526 100644 --- a/launcher/minecraft/auth/steps/XboxUserStep.h +++ b/launcher/minecraft/auth/steps/XboxUserStep.h @@ -1,21 +1,25 @@ #pragma once #include +#include -#include "QObjectPtr.h" #include "minecraft/auth/AuthStep.h" +#include "net/Upload.h" class XboxUserStep : public AuthStep { Q_OBJECT public: explicit XboxUserStep(AccountData* data); - virtual ~XboxUserStep() noexcept; + virtual ~XboxUserStep() noexcept = default; void perform() override; - void rehydrate() override; QString describe() override; private slots: - void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList); + void onRequestDone(); + + private: + std::shared_ptr m_response; + Net::Upload::Ptr m_task; }; diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp index 225583764a..974e732a76 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -45,8 +45,8 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks& QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) { int network_error_code = -1; - if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply) - network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (auto* failed_action = netJob->getFailedActions().at(0); failed_action) + network_error_code = failed_action->replyStatusCode(); callbacks.on_fail(reason, network_error_code); }); @@ -104,8 +104,8 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi }); QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) { int network_error_code = -1; - if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply) - network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (auto* failed_action = netJob->getFailedActions().at(0); failed_action) + network_error_code = failed_action->replyStatusCode(); callbacks.on_fail(reason, network_error_code); }); @@ -155,8 +155,8 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args, }); QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) { int network_error_code = -1; - if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply) - network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (auto* failed_action = netJob->getFailedActions().at(0); failed_action) + network_error_code = failed_action->replyStatusCode(); callbacks.on_fail(reason, network_error_code); }); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 3b875103bc..e924899998 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -261,7 +261,7 @@ bool ModrinthCreationTask::createInstance() // FIXME: This really needs to be put into a ConcurrentTask of // MultipleOptionsTask's , once those exist :) auto param = dl.toWeakRef(); - connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] { + connect(dl.get(), &Task::failed, [this, &file, file_path, param] { auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path); ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); m_files_job->addNetAction(ndl); diff --git a/launcher/net/ApiDownload.cpp b/launcher/net/ApiDownload.cpp index aaa8ff650f..8768b63f86 100644 --- a/launcher/net/ApiDownload.cpp +++ b/launcher/net/ApiDownload.cpp @@ -21,7 +21,6 @@ #include "ByteArraySink.h" #include "ChecksumValidator.h" #include "MetaCacheSink.h" -#include "net/NetAction.h" namespace Net { diff --git a/launcher/net/ApiUpload.cpp b/launcher/net/ApiUpload.cpp index c1221b7649..505cbd9f99 100644 --- a/launcher/net/ApiUpload.cpp +++ b/launcher/net/ApiUpload.cpp @@ -19,9 +19,6 @@ #include "net/ApiUpload.h" #include "ByteArraySink.h" -#include "ChecksumValidator.h" -#include "MetaCacheSink.h" -#include "net/NetAction.h" namespace Net { diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index bae364f127..49686db989 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -47,8 +47,6 @@ #include "ChecksumValidator.h" #include "MetaCacheSink.h" -#include "net/NetAction.h" - namespace Net { #if defined(LAUNCHER_APPLICATION) diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h deleted file mode 100644 index b66b91941f..0000000000 --- a/launcher/net/NetAction.h +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2022 flowln - * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * 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 -#include - -#include "QObjectPtr.h" -#include "tasks/Task.h" - -#include "HeaderProxy.h" - -class NetAction : public Task { - Q_OBJECT - protected: - explicit NetAction() : Task() {} - - public: - using Ptr = shared_qobject_ptr; - - virtual ~NetAction() = default; - - QUrl url() { return m_url; } - - void setNetwork(shared_qobject_ptr network) { m_network = network; } - - void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr(proxy)); } - virtual void init() = 0; - - protected slots: - virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; - virtual void downloadError(QNetworkReply::NetworkError error) = 0; - virtual void downloadFinished() = 0; - virtual void downloadReadyRead() = 0; - - virtual void sslErrors(const QList& errors) - { - int i = 1; - for (auto error : errors) { - qCritical() << "Network SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } - } - - public slots: - void startAction(shared_qobject_ptr network) - { - m_network = network; - executeTask(); - } - - protected: - void executeTask() override {} - - public: - shared_qobject_ptr m_network; - - /// the network reply - unique_qobject_ptr m_reply; - - /// source URL - QUrl m_url; - std::vector> m_headerProxies; -}; diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index d027e31c9c..1ceb0a8600 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -36,6 +36,7 @@ */ #include "NetJob.h" +#include "net/NetRequest.h" #include "tasks/ConcurrentTask.h" #if defined(LAUNCHER_APPLICATION) #include "Application.h" @@ -48,7 +49,7 @@ NetJob::NetJob(QString job_name, shared_qobject_ptr netwo #endif } -auto NetJob::addNetAction(NetAction::Ptr action) -> bool +auto NetJob::addNetAction(Net::NetRequest::Ptr action) -> bool { action->setNetwork(m_network); @@ -111,11 +112,11 @@ auto NetJob::abort() -> bool return fullyAborted; } -auto NetJob::getFailedActions() -> QList +auto NetJob::getFailedActions() -> QList { - QList failed; + QList failed; for (auto index : m_failed) { - failed.push_back(dynamic_cast(index.get())); + failed.push_back(dynamic_cast(index.get())); } return failed; } @@ -124,7 +125,7 @@ auto NetJob::getFailedFiles() -> QList { QList failed; for (auto index : m_failed) { - failed.append(static_cast(index.get())->url().toString()); + failed.append(static_cast(index.get())->url().toString()); } return failed; } diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h index f6c0058096..1661842f04 100644 --- a/launcher/net/NetJob.h +++ b/launcher/net/NetJob.h @@ -39,7 +39,7 @@ #include #include -#include "NetAction.h" +#include "net/NetRequest.h" #include "tasks/ConcurrentTask.h" // Those are included so that they are also included by anyone using NetJob @@ -58,9 +58,9 @@ class NetJob : public ConcurrentTask { auto size() const -> int; auto canAbort() const -> bool override; - auto addNetAction(NetAction::Ptr action) -> bool; + auto addNetAction(Net::NetRequest::Ptr action) -> bool; - auto getFailedActions() -> QList; + auto getFailedActions() -> QList; auto getFailedFiles() -> QList; public slots: diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index 526fe77a53..009213332d 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -37,6 +37,7 @@ */ #include "NetRequest.h" +#include #include #include @@ -48,8 +49,6 @@ #endif #include "BuildConfig.h" -#include "net/NetAction.h" - #include "MMCTime.h" #include "StringUtils.h" @@ -105,20 +104,18 @@ void NetRequest::executeTask() for (auto& header_proxy : m_headerProxies) { header_proxy->writeHeaders(request); } - // TODO remove duplication -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - request.setTransferTimeout(); -#endif + request.setTransferTimeout(QNetworkRequest::DefaultTransferTimeoutConstant); m_last_progress_time = m_clock.now(); m_last_progress_bytes = 0; - QNetworkReply* rep = getReply(request); + auto rep = getReply(request); if (rep == nullptr) // it failed return; m_reply.reset(rep); - connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::downloadProgress); + connect(rep, &QNetworkReply::uploadProgress, this, &NetRequest::onProgress); + connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::onProgress); connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 connect(rep, &QNetworkReply::errorOccurred, this, &NetRequest::downloadError); @@ -129,7 +126,7 @@ void NetRequest::executeTask() connect(rep, &QNetworkReply::readyRead, this, &NetRequest::downloadReadyRead); } -void NetRequest::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +void NetRequest::onProgress(qint64 bytesReceived, qint64 bytesTotal) { auto now = m_clock.now(); auto elapsed = now - m_last_progress_time; @@ -237,7 +234,7 @@ auto NetRequest::handleRedirect() -> bool m_url = QUrl(redirect.toString()); qCDebug(logCat) << getUid().toString() << "Following redirect to " << m_url.toString(); - startAction(m_network); + executeTask(); return true; } @@ -334,4 +331,23 @@ auto NetRequest::abort() -> bool return true; } +int NetRequest::replyStatusCode() const +{ + return m_reply ? m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() : -1; +} + +QNetworkReply::NetworkError NetRequest::error() const +{ + return m_reply ? m_reply->error() : QNetworkReply::NoError; +} + +QUrl NetRequest::url() const +{ + return m_url; +} + +QString NetRequest::errorString() const +{ + return m_reply ? m_reply->errorString() : ""; +} } // namespace Net diff --git a/launcher/net/NetRequest.h b/launcher/net/NetRequest.h index 0b307b4f65..6c32215b03 100644 --- a/launcher/net/NetRequest.h +++ b/launcher/net/NetRequest.h @@ -39,20 +39,23 @@ #pragma once #include +#include +#include #include -#include "NetAction.h" +#include "HeaderProxy.h" #include "Sink.h" #include "Validator.h" #include "QObjectPtr.h" #include "net/Logging.h" +#include "tasks/Task.h" namespace Net { -class NetRequest : public NetAction { +class NetRequest : public Task { Q_OBJECT protected: - explicit NetRequest() : NetAction() {} + explicit NetRequest() : Task() {} public: using Ptr = shared_qobject_ptr; @@ -61,26 +64,30 @@ class NetRequest : public NetAction { public: ~NetRequest() override = default; - - void init() override {} - - public: void addValidator(Validator* v); auto abort() -> bool override; auto canAbort() const -> bool override { return true; } + void setNetwork(shared_qobject_ptr network) { m_network = network; } + void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr(proxy)); } + + virtual void init() {} + + QUrl url() const; + int replyStatusCode() const; + QNetworkReply::NetworkError error() const; + QString errorString() const; + private: auto handleRedirect() -> bool; virtual QNetworkReply* getReply(QNetworkRequest&) = 0; protected slots: - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; - void downloadError(QNetworkReply::NetworkError error) override; - void sslErrors(const QList& errors) override; - void downloadFinished() override; - void downloadReadyRead() override; - - public slots: + void onProgress(qint64 bytesReceived, qint64 bytesTotal); + void downloadError(QNetworkReply::NetworkError error); + void sslErrors(const QList& errors); + void downloadFinished(); + void downloadReadyRead(); void executeTask() override; protected: @@ -93,6 +100,15 @@ class NetRequest : public NetAction { std::chrono::steady_clock m_clock; std::chrono::time_point m_last_progress_time; qint64 m_last_progress_bytes; + + shared_qobject_ptr m_network; + + /// the network reply + unique_qobject_ptr m_reply; + + /// source URL + QUrl m_url; + std::vector> m_headerProxies; }; } // namespace Net diff --git a/launcher/net/Sink.h b/launcher/net/Sink.h index fcdabf372d..d1fd9de101 100644 --- a/launcher/net/Sink.h +++ b/launcher/net/Sink.h @@ -35,9 +35,8 @@ #pragma once -#include "net/NetAction.h" - #include "Validator.h" +#include "tasks/Task.h" namespace Net { class Sink { diff --git a/launcher/net/Validator.h b/launcher/net/Validator.h index 92ac6ea15a..6d1945ee62 100644 --- a/launcher/net/Validator.h +++ b/launcher/net/Validator.h @@ -34,7 +34,7 @@ #pragma once -#include "net/NetAction.h" +#include namespace Net { class Validator { diff --git a/launcher/ui/dialogs/ProfileSetupDialog.cpp b/launcher/ui/dialogs/ProfileSetupDialog.cpp index 4b0c5b7688..c5d4c26219 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.cpp +++ b/launcher/ui/dialogs/ProfileSetupDialog.cpp @@ -45,8 +45,9 @@ #include "ui/dialogs/ProgressDialog.h" #include -#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" +#include "net/StaticHeaderProxy.h" +#include "net/Upload.h" ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget* parent) : QDialog(parent), m_accountToSetup(accountToSetup), ui(new Ui::ProfileSetupDialog) @@ -150,28 +151,27 @@ void ProfileSetupDialog::checkName(const QString& name) currentCheck = name; isChecking = true; - auto token = m_accountToSetup->accessToken(); + QUrl url(QString("https://api.minecraftservices.com/minecraft/profile/name/%1/available").arg(name)); + auto headers = QList{ { "Content-Type", "application/json" }, + { "Accept", "application/json" }, + { "Authorization", QString("Bearer %1").arg(m_accountToSetup->accessToken()).toUtf8() } }; - auto url = QString("https://api.minecraftservices.com/minecraft/profile/name/%1/available").arg(name); - QNetworkRequest request = QNetworkRequest(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8()); + m_check_response.reset(new QByteArray()); + if (m_check_task) + disconnect(m_check_task.get(), nullptr, this, nullptr); + m_check_task = Net::Download::makeByteArray(url, m_check_response); + m_check_task->addHeaderProxy(new Net::StaticHeaderProxy(headers)); - AuthRequest* requestor = new AuthRequest(this); - connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::checkFinished); - requestor->get(request); + connect(m_check_task.get(), &Task::finished, this, &ProfileSetupDialog::checkFinished); + + m_check_task->setNetwork(APPLICATION->network()); + m_check_task->start(); } -void ProfileSetupDialog::checkFinished(QNetworkReply::NetworkError error, - QByteArray profileData, - [[maybe_unused]] QList headers) +void ProfileSetupDialog::checkFinished() { - auto requestor = qobject_cast(QObject::sender()); - requestor->deleteLater(); - - if (error == QNetworkReply::NoError) { - auto doc = QJsonDocument::fromJson(profileData); + if (m_check_task->error() == QNetworkReply::NoError) { + auto doc = QJsonDocument::fromJson(*m_check_response); auto root = doc.object(); auto statusValue = root.value("status").toString("INVALID"); if (statusValue == "AVAILABLE") { @@ -195,20 +195,22 @@ void ProfileSetupDialog::setupProfile(const QString& profileName) return; } - auto token = m_accountToSetup->accessToken(); + QString payloadTemplate("{\"profileName\":\"%1\"}"); - auto url = QString("https://api.minecraftservices.com/minecraft/profile"); - QNetworkRequest request = QNetworkRequest(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8()); + QUrl url("https://api.minecraftservices.com/minecraft/profile"); + auto headers = QList{ { "Content-Type", "application/json" }, + { "Accept", "application/json" }, + { "Authorization", QString("Bearer %1").arg(m_accountToSetup->accessToken()).toUtf8() } }; - QString payloadTemplate("{\"profileName\":\"%1\"}"); - auto profileData = payloadTemplate.arg(profileName).toUtf8(); + m_profile_response.reset(new QByteArray()); + m_profile_task = Net::Upload::makeByteArray(url, m_profile_response, payloadTemplate.arg(profileName).toUtf8()); + m_profile_task->addHeaderProxy(new Net::StaticHeaderProxy(headers)); + + connect(m_profile_task.get(), &Task::finished, this, &ProfileSetupDialog::setupProfileFinished); + + m_profile_task->setNetwork(APPLICATION->network()); + m_profile_task->start(); - AuthRequest* requestor = new AuthRequest(this); - connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::setupProfileFinished); - requestor->post(request, profileData); isWorking = true; auto button = ui->buttonBox->button(QDialogButtonBox::Cancel); @@ -244,22 +246,17 @@ struct MojangError { } // namespace -void ProfileSetupDialog::setupProfileFinished(QNetworkReply::NetworkError error, - QByteArray errorData, - [[maybe_unused]] QList headers) +void ProfileSetupDialog::setupProfileFinished() { - auto requestor = qobject_cast(QObject::sender()); - requestor->deleteLater(); - isWorking = false; - if (error == QNetworkReply::NoError) { + if (m_profile_task->error() == QNetworkReply::NoError) { /* * data contains the profile in the response * ... we could parse it and update the account, but let's just return back to the normal login flow instead... */ accept(); } else { - auto parsedError = MojangError::fromJSON(errorData); + auto parsedError = MojangError::fromJSON(*m_profile_response); ui->errorLabel->setVisible(true); ui->errorLabel->setText(tr("The server returned the following error:") + "\n\n" + parsedError.errorMessage); qDebug() << parsedError.rawError; diff --git a/launcher/ui/dialogs/ProfileSetupDialog.h b/launcher/ui/dialogs/ProfileSetupDialog.h index 09f8124e24..c005a41380 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.h +++ b/launcher/ui/dialogs/ProfileSetupDialog.h @@ -22,6 +22,8 @@ #include #include +#include "net/Download.h" +#include "net/Upload.h" namespace Ui { class ProfileSetupDialog; @@ -40,10 +42,10 @@ class ProfileSetupDialog : public QDialog { void on_buttonBox_rejected(); void nameEdited(const QString& name); - void checkFinished(QNetworkReply::NetworkError error, QByteArray data, QList headers); void startCheck(); - void setupProfileFinished(QNetworkReply::NetworkError error, QByteArray data, QList headers); + void checkFinished(); + void setupProfileFinished(); protected: void scheduleCheck(const QString& name); @@ -67,4 +69,10 @@ class ProfileSetupDialog : public QDialog { QString currentCheck; QTimer checkStartTimer; + + std::shared_ptr m_check_response; + Net::Download::Ptr m_check_task; + + std::shared_ptr m_profile_response; + Net::Upload::Ptr m_profile_task; }; diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index f3c7ff60b9..8a69e910d0 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -331,7 +331,7 @@ std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry); auto full_file_path = cache_entry->getFullPath(); - connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] { + connect(icon_fetch_action.get(), &Task::succeeded, this, [=] { auto icon = QIcon(full_file_path); QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 }))); @@ -339,7 +339,7 @@ std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) emit dataChanged(index, index, { Qt::DecorationRole }); }); - connect(icon_fetch_action.get(), &NetAction::failed, this, [=] { + connect(icon_fetch_action.get(), &Task::failed, this, [=] { m_currently_running_icon_actions.remove(url); m_failed_icon_actions.insert(url); }); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index bac294b606..ccfe7eccb3 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -352,10 +352,10 @@ void ModpackListModel::searchRequestForOneSucceeded(QJsonDocument& doc) void ModpackListModel::searchRequestFailed(QString reason) { auto failed_action = dynamic_cast(jobPtr.get())->getFailedActions().at(0); - if (!failed_action->m_reply) { + if (failed_action->replyStatusCode() == -1) { // Network error QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks.")); - } else if (failed_action->m_reply && failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) { + } else if (failed_action->replyStatusCode() == 409) { // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), //: %1 refers to the launcher itself diff --git a/tests/Library_test.cpp b/tests/Library_test.cpp index 8b8d4c55c3..9826abbdf6 100644 --- a/tests/Library_test.cpp +++ b/tests/Library_test.cpp @@ -95,8 +95,8 @@ class LibraryTest : public QObject { auto downloads = test.getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(downloads.size(), 1); QCOMPARE(failedFiles, {}); - NetAction::Ptr dl = downloads[0]; - QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar")); + Net::NetRequest::Ptr dl = downloads[0]; + QCOMPARE(dl->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar")); } void test_legacy_url_local_broken() { @@ -147,7 +147,7 @@ class LibraryTest : public QObject { QCOMPARE(dls.size(), 1); QCOMPARE(failedFiles, {}); auto dl = dls[0]; - QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar")); + QCOMPARE(dl->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar")); } } void test_legacy_native_arch() @@ -170,8 +170,8 @@ class LibraryTest : public QObject { auto dls = test.getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(dls.size(), 2); QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar")); + QCOMPARE(dls[0]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar")); + QCOMPARE(dls[1]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar")); } r.system = "windows"; { @@ -185,8 +185,8 @@ class LibraryTest : public QObject { auto dls = test.getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(dls.size(), 2); QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar")); + QCOMPARE(dls[0]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar")); + QCOMPARE(dls[1]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar")); } r.system = "osx"; { @@ -200,8 +200,8 @@ class LibraryTest : public QObject { auto dls = test.getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(dls.size(), 2); QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar")); + QCOMPARE(dls[0]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar")); + QCOMPARE(dls[1]->url(), QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar")); } } void test_legacy_native_arch_local_override() @@ -244,7 +244,7 @@ class LibraryTest : public QObject { auto dls = test->getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(dls.size(), 1); QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar")); + QCOMPARE(dls[0]->url(), QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar")); } r.system = "osx"; test->setHint("local"); @@ -300,7 +300,7 @@ class LibraryTest : public QObject { auto dls = test->getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(dls.size(), 1); QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/" + QCOMPARE(dls[0]->url(), QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/" "lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); } void test_onenine_native_arch() @@ -317,9 +317,9 @@ class LibraryTest : public QObject { auto dls = test->getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(dls.size(), 2); QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, + QCOMPARE(dls[0]->url(), QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); - QCOMPARE(dls[1]->m_url, + QCOMPARE(dls[1]->url(), QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); } From e285a85fe88e78ddced5c0d839056f2132e4c3b6 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 14 May 2024 00:20:32 +0300 Subject: [PATCH 0197/2054] Removed AccountTask Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 3 - launcher/LaunchController.cpp | 1 - launcher/minecraft/auth/AccountList.cpp | 1 - launcher/minecraft/auth/AccountList.h | 3 +- launcher/minecraft/auth/AccountTask.cpp | 134 ------------------ launcher/minecraft/auth/AccountTask.h | 92 ------------ launcher/minecraft/auth/AuthStep.cpp | 3 - launcher/minecraft/auth/AuthStep.h | 19 ++- launcher/minecraft/auth/MinecraftAccount.cpp | 10 +- launcher/minecraft/auth/MinecraftAccount.h | 14 +- launcher/minecraft/auth/flows/AuthFlow.cpp | 93 ++++++++++-- launcher/minecraft/auth/flows/AuthFlow.h | 30 +++- .../auth/steps/LauncherLoginStep.cpp | 1 - launcher/net/NetRequest.cpp | 2 + launcher/ui/dialogs/MSALoginDialog.cpp | 6 +- launcher/ui/dialogs/MSALoginDialog.h | 3 +- launcher/ui/dialogs/OfflineLoginDialog.cpp | 2 - 17 files changed, 142 insertions(+), 275 deletions(-) delete mode 100644 launcher/minecraft/auth/AccountTask.cpp delete mode 100644 launcher/minecraft/auth/AccountTask.h delete mode 100644 launcher/minecraft/auth/AuthStep.cpp diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index cf1ab87981..e4143ee288 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -209,11 +209,8 @@ set(MINECRAFT_SOURCES minecraft/auth/AccountData.h minecraft/auth/AccountList.cpp minecraft/auth/AccountList.h - minecraft/auth/AccountTask.cpp - minecraft/auth/AccountTask.h minecraft/auth/AuthSession.cpp minecraft/auth/AuthSession.h - minecraft/auth/AuthStep.cpp minecraft/auth/AuthStep.h minecraft/auth/MinecraftAccount.cpp minecraft/auth/MinecraftAccount.h diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index a30f99439b..cf8d0a7945 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -57,7 +57,6 @@ #include "BuildConfig.h" #include "JavaCommon.h" #include "launch/steps/TextPrint.h" -#include "minecraft/auth/AccountTask.h" #include "tasks/Task.h" LaunchController::LaunchController(QObject* parent) : Task(parent) {} diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index af83502bcb..d276d4c41f 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -35,7 +35,6 @@ #include "AccountList.h" #include "AccountData.h" -#include "AccountTask.h" #include "tasks/Task.h" #include diff --git a/launcher/minecraft/auth/AccountList.h b/launcher/minecraft/auth/AccountList.h index 0397307390..b6038edb7d 100644 --- a/launcher/minecraft/auth/AccountList.h +++ b/launcher/minecraft/auth/AccountList.h @@ -36,6 +36,7 @@ #pragma once #include "MinecraftAccount.h" +#include "minecraft/auth/flows/AuthFlow.h" #include #include @@ -144,7 +145,7 @@ class AccountList : public QAbstractListModel { QList m_refreshQueue; QTimer* m_refreshTimer; QTimer* m_nextTimer; - shared_qobject_ptr m_currentTask; + shared_qobject_ptr m_currentTask; /*! * Called whenever the list changes. diff --git a/launcher/minecraft/auth/AccountTask.cpp b/launcher/minecraft/auth/AccountTask.cpp deleted file mode 100644 index 4c3d6ee194..0000000000 --- a/launcher/minecraft/auth/AccountTask.cpp +++ /dev/null @@ -1,134 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * 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 "AccountTask.h" -#include "MinecraftAccount.h" - -#include -#include -#include -#include -#include -#include - -#include - -AccountTask::AccountTask(AccountData* data, QObject* parent) : Task(parent), m_data(data) -{ - changeState(AccountTaskState::STATE_CREATED); -} - -QString AccountTask::getStateMessage() const -{ - switch (m_taskState) { - case AccountTaskState::STATE_CREATED: - return "Waiting..."; - case AccountTaskState::STATE_WORKING: - return tr("Sending request to auth servers..."); - case AccountTaskState::STATE_SUCCEEDED: - return tr("Authentication task succeeded."); - case AccountTaskState::STATE_OFFLINE: - return tr("Failed to contact the authentication server."); - case AccountTaskState::STATE_DISABLED: - return tr("Client ID has changed. New session needs to be created."); - case AccountTaskState::STATE_FAILED_SOFT: - return tr("Encountered an error during authentication."); - case AccountTaskState::STATE_FAILED_HARD: - return tr("Failed to authenticate. The session has expired."); - case AccountTaskState::STATE_FAILED_GONE: - return tr("Failed to authenticate. The account no longer exists."); - default: - return tr("..."); - } -} - -bool AccountTask::changeState(AccountTaskState newState, QString reason) -{ - m_taskState = newState; - // FIXME: virtual method invoked in constructor. - // We want that behavior, but maybe make it less weird? - setStatus(getStateMessage()); - switch (newState) { - case AccountTaskState::STATE_CREATED: { - m_data->errorString.clear(); - return true; - } - case AccountTaskState::STATE_WORKING: { - m_data->accountState = AccountState::Working; - return true; - } - case AccountTaskState::STATE_SUCCEEDED: { - m_data->accountState = AccountState::Online; - emitSucceeded(); - return false; - } - case AccountTaskState::STATE_OFFLINE: { - m_data->errorString = reason; - m_data->accountState = AccountState::Offline; - emitFailed(reason); - return false; - } - case AccountTaskState::STATE_DISABLED: { - m_data->errorString = reason; - m_data->accountState = AccountState::Disabled; - emitFailed(reason); - return false; - } - case AccountTaskState::STATE_FAILED_SOFT: { - m_data->errorString = reason; - m_data->accountState = AccountState::Errored; - emitFailed(reason); - return false; - } - case AccountTaskState::STATE_FAILED_HARD: { - m_data->errorString = reason; - m_data->accountState = AccountState::Expired; - emitFailed(reason); - return false; - } - case AccountTaskState::STATE_FAILED_GONE: { - m_data->errorString = reason; - m_data->accountState = AccountState::Gone; - emitFailed(reason); - return false; - } - default: { - QString error = tr("Unknown account task state: %1").arg(int(newState)); - m_data->accountState = AccountState::Errored; - emitFailed(error); - return false; - } - } -} diff --git a/launcher/minecraft/auth/AccountTask.h b/launcher/minecraft/auth/AccountTask.h deleted file mode 100644 index 82332c0b90..0000000000 --- a/launcher/minecraft/auth/AccountTask.h +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * 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 - -#include -#include -#include -#include - -#include "MinecraftAccount.h" - -class QNetworkReply; - -/** - * Enum for describing the state of the current task. - * Used by the getStateMessage function to determine what the status message should be. - */ -enum class AccountTaskState { - STATE_CREATED, - STATE_WORKING, - STATE_SUCCEEDED, - STATE_DISABLED, //!< MSA Client ID has changed. Tell user to reloginn - STATE_FAILED_SOFT, //!< soft failure. authentication went through partially - STATE_FAILED_HARD, //!< hard failure. main tokens are invalid - STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists - STATE_OFFLINE //!< soft failure. authentication failed in the first step in a 'soft' way -}; - -class AccountTask : public Task { - Q_OBJECT - public: - explicit AccountTask(AccountData* data, QObject* parent = 0); - virtual ~AccountTask(){}; - - AccountTaskState m_taskState = AccountTaskState::STATE_CREATED; - - AccountTaskState taskState() { return m_taskState; } - - signals: - void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn); - void hideVerificationUriAndCode(); - - protected: - /** - * Returns the state message for the given state. - * Used to set the status message for the task. - * Should be overridden by subclasses that want to change messages for a given state. - */ - virtual QString getStateMessage() const; - - protected slots: - // NOTE: true -> non-terminal state, false -> terminal state - bool changeState(AccountTaskState newState, QString reason = QString()); - - protected: - AccountData* m_data = nullptr; -}; diff --git a/launcher/minecraft/auth/AuthStep.cpp b/launcher/minecraft/auth/AuthStep.cpp deleted file mode 100644 index 6b78c415a2..0000000000 --- a/launcher/minecraft/auth/AuthStep.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "AuthStep.h" - -AuthStep::AuthStep(AccountData* data) : QObject(nullptr), m_data(data) {} diff --git a/launcher/minecraft/auth/AuthStep.h b/launcher/minecraft/auth/AuthStep.h index b837b5703b..dccbec11ed 100644 --- a/launcher/minecraft/auth/AuthStep.h +++ b/launcher/minecraft/auth/AuthStep.h @@ -3,18 +3,31 @@ #include #include -#include "AccountTask.h" #include "QObjectPtr.h" #include "minecraft/auth/AccountData.h" +/** + * Enum for describing the state of the current task. + * Used by the getStateMessage function to determine what the status message should be. + */ +enum class AccountTaskState { + STATE_CREATED, + STATE_WORKING, + STATE_SUCCEEDED, + STATE_DISABLED, //!< MSA Client ID has changed. Tell user to reloginn + STATE_FAILED_SOFT, //!< soft failure. authentication went through partially + STATE_FAILED_HARD, //!< hard failure. main tokens are invalid + STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists + STATE_OFFLINE //!< soft failure. authentication failed in the first step in a 'soft' way +}; + class AuthStep : public QObject { Q_OBJECT public: using Ptr = shared_qobject_ptr; - public: - explicit AuthStep(AccountData* data); + explicit AuthStep(AccountData* data) : QObject(nullptr), m_data(data){}; virtual ~AuthStep() noexcept = default; virtual QString describe() = 0; diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index db4d1c07bb..d927cd6b4f 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -53,6 +53,8 @@ #include "flows/MSA.h" #include "flows/Offline.h" #include "minecraft/auth/AccountData.h" +#include "minecraft/auth/flows/AuthFlow.h" +#include "tasks/Task.h" MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) { @@ -120,7 +122,7 @@ QPixmap MinecraftAccount::getFace() const return skin.scaled(64, 64, Qt::KeepAspectRatio); } -shared_qobject_ptr MinecraftAccount::loginMSA() +shared_qobject_ptr MinecraftAccount::loginMSA() { Q_ASSERT(m_currentTask.get() == nullptr); @@ -132,7 +134,7 @@ shared_qobject_ptr MinecraftAccount::loginMSA() return m_currentTask; } -shared_qobject_ptr MinecraftAccount::loginOffline() +shared_qobject_ptr MinecraftAccount::loginOffline() { Q_ASSERT(m_currentTask.get() == nullptr); @@ -144,7 +146,7 @@ shared_qobject_ptr MinecraftAccount::loginOffline() return m_currentTask; } -shared_qobject_ptr MinecraftAccount::refresh() +shared_qobject_ptr MinecraftAccount::refresh() { if (m_currentTask) { return m_currentTask; @@ -163,7 +165,7 @@ shared_qobject_ptr MinecraftAccount::refresh() return m_currentTask; } -shared_qobject_ptr MinecraftAccount::currentTask() +shared_qobject_ptr MinecraftAccount::currentTask() { return m_currentTask; } diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index f773b3bc97..24600cb73c 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -43,15 +43,13 @@ #include #include -#include - #include "AccountData.h" #include "AuthSession.h" #include "QObjectPtr.h" #include "Usable.h" +#include "minecraft/auth/flows/AuthFlow.h" class Task; -class AccountTask; class MinecraftAccount; using MinecraftAccountPtr = shared_qobject_ptr; @@ -97,13 +95,13 @@ class MinecraftAccount : public QObject, public Usable { QJsonObject saveToJson() const; public: /* manipulation */ - shared_qobject_ptr loginMSA(); + shared_qobject_ptr loginMSA(); - shared_qobject_ptr loginOffline(); + shared_qobject_ptr loginOffline(); - shared_qobject_ptr refresh(); + shared_qobject_ptr refresh(); - shared_qobject_ptr currentTask(); + shared_qobject_ptr currentTask(); public: /* queries */ QString internalId() const { return data.internalId; } @@ -166,7 +164,7 @@ class MinecraftAccount : public QObject, public Usable { AccountData data; // current task we are executing here - shared_qobject_ptr m_currentTask; + shared_qobject_ptr m_currentTask; protected: /* methods */ void incrementUses() override; diff --git a/launcher/minecraft/auth/flows/AuthFlow.cpp b/launcher/minecraft/auth/flows/AuthFlow.cpp index c51839a8cc..20d90f5047 100644 --- a/launcher/minecraft/auth/flows/AuthFlow.cpp +++ b/launcher/minecraft/auth/flows/AuthFlow.cpp @@ -4,11 +4,13 @@ #include #include "AuthFlow.h" -#include "katabasis/Globals.h" #include -AuthFlow::AuthFlow(AccountData* data, QObject* parent) : AccountTask(data, parent) {} +AuthFlow::AuthFlow(AccountData* data, QObject* parent) : Task(parent), m_data(data) +{ + changeState(AccountTaskState::STATE_CREATED); +} void AuthFlow::succeed() { @@ -46,16 +48,26 @@ void AuthFlow::nextStep() QString AuthFlow::getStateMessage() const { switch (m_taskState) { - case AccountTaskState::STATE_WORKING: { - if (m_currentStep) { + case AccountTaskState::STATE_CREATED: + return "Waiting..."; + case AccountTaskState::STATE_WORKING: + if (m_currentStep) return m_currentStep->describe(); - } else { - return tr("Working..."); - } - } - default: { - return AccountTask::getStateMessage(); - } + return tr("Working..."); + case AccountTaskState::STATE_SUCCEEDED: + return tr("Authentication task succeeded."); + case AccountTaskState::STATE_OFFLINE: + return tr("Failed to contact the authentication server."); + case AccountTaskState::STATE_DISABLED: + return tr("Client ID has changed. New session needs to be created."); + case AccountTaskState::STATE_FAILED_SOFT: + return tr("Encountered an error during authentication."); + case AccountTaskState::STATE_FAILED_HARD: + return tr("Failed to authenticate. The session has expired."); + case AccountTaskState::STATE_FAILED_GONE: + return tr("Failed to authenticate. The account no longer exists."); + default: + return tr("..."); } } @@ -65,3 +77,62 @@ void AuthFlow::stepFinished(AccountTaskState resultingState, QString message) nextStep(); } } + +bool AuthFlow::changeState(AccountTaskState newState, QString reason) +{ + m_taskState = newState; + // FIXME: virtual method invoked in constructor. + // We want that behavior, but maybe make it less weird? + setStatus(getStateMessage()); + switch (newState) { + case AccountTaskState::STATE_CREATED: { + m_data->errorString.clear(); + return true; + } + case AccountTaskState::STATE_WORKING: { + m_data->accountState = AccountState::Working; + return true; + } + case AccountTaskState::STATE_SUCCEEDED: { + m_data->accountState = AccountState::Online; + emitSucceeded(); + return false; + } + case AccountTaskState::STATE_OFFLINE: { + m_data->errorString = reason; + m_data->accountState = AccountState::Offline; + emitFailed(reason); + return false; + } + case AccountTaskState::STATE_DISABLED: { + m_data->errorString = reason; + m_data->accountState = AccountState::Disabled; + emitFailed(reason); + return false; + } + case AccountTaskState::STATE_FAILED_SOFT: { + m_data->errorString = reason; + m_data->accountState = AccountState::Errored; + emitFailed(reason); + return false; + } + case AccountTaskState::STATE_FAILED_HARD: { + m_data->errorString = reason; + m_data->accountState = AccountState::Expired; + emitFailed(reason); + return false; + } + case AccountTaskState::STATE_FAILED_GONE: { + m_data->errorString = reason; + m_data->accountState = AccountState::Gone; + emitFailed(reason); + return false; + } + default: { + QString error = tr("Unknown account task state: %1").arg(int(newState)); + m_data->accountState = AccountState::Errored; + emitFailed(error); + return false; + } + } +} \ No newline at end of file diff --git a/launcher/minecraft/auth/flows/AuthFlow.h b/launcher/minecraft/auth/flows/AuthFlow.h index e39e926ddf..10fdd22fac 100644 --- a/launcher/minecraft/auth/flows/AuthFlow.h +++ b/launcher/minecraft/auth/flows/AuthFlow.h @@ -10,32 +10,48 @@ #include #include "minecraft/auth/AccountData.h" -#include "minecraft/auth/AccountTask.h" #include "minecraft/auth/AuthStep.h" +#include "tasks/Task.h" -class AuthFlow : public AccountTask { +class AuthFlow : public Task { Q_OBJECT public: explicit AuthFlow(AccountData* data, QObject* parent = 0); + virtual ~AuthFlow() = default; Katabasis::Validity validity() { return m_data->validity_; }; - QString getStateMessage() const override; - void executeTask() override; + AccountTaskState taskState() { return m_taskState; } + signals: - void activityChanged(Katabasis::Activity activity); + void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn); + void hideVerificationUriAndCode(); - private slots: - void stepFinished(AccountTaskState resultingState, QString message); + void activityChanged(Katabasis::Activity activity); protected: + /** + * Returns the state message for the given state. + * Used to set the status message for the task. + * Should be overridden by subclasses that want to change messages for a given state. + */ + virtual QString getStateMessage() const; void succeed(); void nextStep(); + protected slots: + // NOTE: true -> non-terminal state, false -> terminal state + bool changeState(AccountTaskState newState, QString reason = QString()); + + private slots: + void stepFinished(AccountTaskState resultingState, QString message); + protected: + AccountTaskState m_taskState = AccountTaskState::STATE_CREATED; QList m_steps; AuthStep::Ptr m_currentStep; + AccountData* m_data = nullptr; }; diff --git a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp index 8981c57528..d72346c74a 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp @@ -5,7 +5,6 @@ #include "Application.h" #include "Logging.h" -#include "minecraft/auth/AccountTask.h" #include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" #include "net/StaticHeaderProxy.h" diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index 009213332d..29de2febfe 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -170,6 +170,8 @@ void NetRequest::downloadError(QNetworkReply::NetworkError error) } // error happened during download. qCCritical(logCat) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; + if (m_reply) + qCCritical(logCat) << getUid().toString() << "HTTP Status " << replyStatusCode() << ";error " << errorString(); m_state = State::Failed; } } diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index 7df423412a..e396c01f6f 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -37,7 +37,7 @@ #include "ui_MSALoginDialog.h" #include "DesktopServices.h" -#include "minecraft/auth/AccountTask.h" +#include "minecraft/auth/flows/AuthFlow.h" #include #include @@ -67,8 +67,8 @@ 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_loginTask.get(), &AuthFlow::showVerificationUriAndCode, this, &MSALoginDialog::showVerificationUriAndCode); + connect(m_loginTask.get(), &AuthFlow::hideVerificationUriAndCode, this, &MSALoginDialog::hideVerificationUriAndCode); connect(&m_externalLoginTimer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick); m_loginTask->start(); diff --git a/launcher/ui/dialogs/MSALoginDialog.h b/launcher/ui/dialogs/MSALoginDialog.h index 03e276bc06..531ae3cc16 100644 --- a/launcher/ui/dialogs/MSALoginDialog.h +++ b/launcher/ui/dialogs/MSALoginDialog.h @@ -20,6 +20,7 @@ #include #include "minecraft/auth/MinecraftAccount.h" +#include "minecraft/auth/flows/AuthFlow.h" namespace Ui { class MSALoginDialog; @@ -52,7 +53,7 @@ class MSALoginDialog : public QDialog { private: Ui::MSALoginDialog* ui; MinecraftAccountPtr m_account; - shared_qobject_ptr m_loginTask; + shared_qobject_ptr m_loginTask; QTimer m_externalLoginTimer; int m_externalLoginElapsed = 0; int m_externalLoginTimeout = 0; diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp index 137620be47..cd31021356 100644 --- a/launcher/ui/dialogs/OfflineLoginDialog.cpp +++ b/launcher/ui/dialogs/OfflineLoginDialog.cpp @@ -1,8 +1,6 @@ #include "OfflineLoginDialog.h" #include "ui_OfflineLoginDialog.h" -#include "minecraft/auth/AccountTask.h" - #include OfflineLoginDialog::OfflineLoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::OfflineLoginDialog) From c2ed50627dfb09d3ef99f38a70955d8ef2803de3 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 14 May 2024 00:36:35 +0300 Subject: [PATCH 0198/2054] Added back qt version check Signed-off-by: Trial97 --- launcher/net/NetRequest.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index 29de2febfe..90ce154516 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -105,7 +105,9 @@ void NetRequest::executeTask() header_proxy->writeHeaders(request); } - request.setTransferTimeout(QNetworkRequest::DefaultTransferTimeoutConstant); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + request.setTransferTimeout(); +#endif m_last_progress_time = m_clock.now(); m_last_progress_bytes = 0; From 80d8e3ee06e8f71503523d4f388b1f729ae7a75e Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 15 May 2024 12:53:06 +0300 Subject: [PATCH 0199/2054] Remove katabasis Signed-off-by: Trial97 --- .github/workflows/build.yml | 25 +- CMakeLists.txt | 5 +- COPYING.md | 26 - launcher/CMakeLists.txt | 3 +- launcher/minecraft/auth/AccountData.h | 33 +- launcher/minecraft/auth/AuthStep.h | 2 - launcher/minecraft/auth/flows/AuthFlow.cpp | 42 +- launcher/minecraft/auth/flows/AuthFlow.h | 11 - launcher/minecraft/auth/steps/MSAStep.cpp | 102 ++-- launcher/minecraft/auth/steps/MSAStep.h | 9 +- launcher/net/NetRequest.cpp | 4 +- launcher/qtlogging.ini | 1 - launcher/ui/dialogs/MSALoginDialog.cpp | 52 -- launcher/ui/dialogs/MSALoginDialog.h | 8 - libraries/README.md | 8 - libraries/katabasis/.gitignore | 2 - libraries/katabasis/CMakeLists.txt | 58 --- libraries/katabasis/LICENSE | 23 - libraries/katabasis/README.md | 36 -- libraries/katabasis/acknowledgements.md | 108 ---- libraries/katabasis/include/katabasis/Bits.h | 33 -- .../katabasis/include/katabasis/DeviceFlow.h | 149 ------ .../katabasis/include/katabasis/Globals.h | 59 --- .../katabasis/include/katabasis/PollServer.h | 51 -- libraries/katabasis/include/katabasis/Reply.h | 63 --- .../include/katabasis/RequestParameter.h | 13 - libraries/katabasis/src/DeviceFlow.cpp | 467 ------------------ libraries/katabasis/src/JsonResponse.cpp | 27 - libraries/katabasis/src/JsonResponse.h | 12 - libraries/katabasis/src/PollServer.cpp | 118 ----- libraries/katabasis/src/Reply.cpp | 74 --- 31 files changed, 99 insertions(+), 1525 deletions(-) delete mode 100644 libraries/katabasis/.gitignore delete mode 100644 libraries/katabasis/CMakeLists.txt delete mode 100644 libraries/katabasis/LICENSE delete mode 100644 libraries/katabasis/README.md delete mode 100644 libraries/katabasis/acknowledgements.md delete mode 100644 libraries/katabasis/include/katabasis/Bits.h delete mode 100644 libraries/katabasis/include/katabasis/DeviceFlow.h delete mode 100644 libraries/katabasis/include/katabasis/Globals.h delete mode 100644 libraries/katabasis/include/katabasis/PollServer.h delete mode 100644 libraries/katabasis/include/katabasis/Reply.h delete mode 100644 libraries/katabasis/include/katabasis/RequestParameter.h delete mode 100644 libraries/katabasis/src/DeviceFlow.cpp delete mode 100644 libraries/katabasis/src/JsonResponse.cpp delete mode 100644 libraries/katabasis/src/JsonResponse.h delete mode 100644 libraries/katabasis/src/PollServer.cpp delete mode 100644 libraries/katabasis/src/Reply.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e502318a3b..999029bd2a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,14 +57,14 @@ jobs: qt_host: linux qt_arch: "" qt_version: "5.12.8" - qt_modules: "" + qt_modules: "qtnetworkauth" - os: ubuntu-20.04 qt_ver: 6 qt_host: linux qt_arch: "" qt_version: "6.2.4" - qt_modules: "qt5compat qtimageformats" + qt_modules: "qt5compat qtimageformats qtnetworkauth" - os: windows-2022 name: "Windows-MinGW-w64" @@ -78,9 +78,9 @@ jobs: vcvars_arch: "amd64" qt_ver: 6 qt_host: windows - qt_arch: '' - qt_version: '6.7.0' - qt_modules: 'qt5compat qtimageformats' + qt_arch: "" + qt_version: "6.7.0" + qt_modules: "qt5compat qtimageformats qtnetworkauth" - os: windows-2022 name: "Windows-MSVC-arm64" @@ -89,18 +89,18 @@ jobs: vcvars_arch: "amd64_arm64" qt_ver: 6 qt_host: windows - qt_arch: 'win64_msvc2019_arm64' - qt_version: '6.7.0' - qt_modules: 'qt5compat qtimageformats' + qt_arch: "win64_msvc2019_arm64" + qt_version: "6.7.0" + qt_modules: "qt5compat qtimageformats qtnetworkauth" - os: macos-12 name: macOS macosx_deployment_target: 11.0 qt_ver: 6 qt_host: mac - qt_arch: '' - qt_version: '6.7.0' - qt_modules: 'qt5compat qtimageformats' + qt_arch: "" + qt_version: "6.7.0" + qt_modules: "qt5compat qtimageformats qtnetworkauth" - os: macos-12 name: macOS-Legacy @@ -108,7 +108,7 @@ jobs: qt_ver: 5 qt_host: mac qt_version: "5.15.2" - qt_modules: "" + qt_modules: "qtnetworkauth" runs-on: ${{ matrix.os }} @@ -150,6 +150,7 @@ jobs: quazip-qt6:p ccache:p qt6-5compat:p + qt6-networkauth:p cmark:p - name: Force newer ccache diff --git a/CMakeLists.txt b/CMakeLists.txt index 63408ec210..5c39874068 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -282,7 +282,7 @@ endif() include(QtVersionlessBackport) if(Launcher_QT_VERSION_MAJOR EQUAL 5) set(QT_VERSION_MAJOR 5) - find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml) + find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth) if(NOT Launcher_FORCE_BUNDLED_LIBS) find_package(QuaZip-Qt5 1.3 QUIET) @@ -296,7 +296,7 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE") elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_VERSION_MAJOR 6) - find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat) + find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth) list(APPEND Launcher_QT_LIBS Qt6::Core5Compat) if(NOT Launcher_FORCE_BUNDLED_LIBS) @@ -523,7 +523,6 @@ if(NOT cmark_FOUND) else() message(STATUS "Using system cmark") endif() -add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much add_subdirectory(libraries/gamemode) add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API if (NOT ghc_filesystem_FOUND) diff --git a/COPYING.md b/COPYING.md index f14e2958e3..1115870605 100644 --- a/COPYING.md +++ b/COPYING.md @@ -333,32 +333,6 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -## O2 (Katabasis fork) - - Copyright (c) 2012, Akos Polster - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ## Gamemode Copyright (c) 2017-2022, Feral Interactive diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e4143ee288..e5eb4b7338 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1234,7 +1234,6 @@ target_link_libraries(Launcher_logic tomlplusplus::tomlplusplus qdcss BuildConfig - Katabasis Qt${QT_VERSION_MAJOR}::Widgets ghcFilesystem::ghc_filesystem ) @@ -1252,6 +1251,7 @@ target_link_libraries(Launcher_logic Qt${QT_VERSION_MAJOR}::Concurrent Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::NetworkAuth ${Launcher_QT_LIBS} ) target_link_libraries(Launcher_logic @@ -1322,7 +1322,6 @@ if(Launcher_BUILD_UPDATER) Qt${QT_VERSION_MAJOR}::Network ${Launcher_QT_LIBS} cmark::cmark - Katabasis ) add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp) diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index bac77e17f7..71f1d00b2e 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -34,12 +34,43 @@ */ #pragma once -#include #include #include #include #include +#include +#include +#include +#include + +namespace Katabasis { +enum class Activity { + Idle, + LoggingIn, + LoggingOut, + Refreshing, + FailedSoft, //!< soft failure. this generally means the user auth details haven't been invalidated + FailedHard, //!< hard failure. auth is invalid + FailedGone, //!< hard failure. auth is invalid, and the account no longer exists + Succeeded +}; + +enum class Validity { None, Assumed, Certain }; + +struct Token { + QDateTime issueInstant; + QDateTime notAfter; + QString token; + QString refresh_token; + QVariantMap extra; + + Validity validity = Validity::None; + bool persistent = true; +}; + +} // namespace Katabasis + struct Skin { QString id; QString url; diff --git a/launcher/minecraft/auth/AuthStep.h b/launcher/minecraft/auth/AuthStep.h index dccbec11ed..4d2cf69c15 100644 --- a/launcher/minecraft/auth/AuthStep.h +++ b/launcher/minecraft/auth/AuthStep.h @@ -37,8 +37,6 @@ class AuthStep : public QObject { signals: void finished(AccountTaskState resultingState, QString message); - void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn); - void hideVerificationUriAndCode(); protected: AccountData* m_data; diff --git a/launcher/minecraft/auth/flows/AuthFlow.cpp b/launcher/minecraft/auth/flows/AuthFlow.cpp index 20d90f5047..1b5e015695 100644 --- a/launcher/minecraft/auth/flows/AuthFlow.cpp +++ b/launcher/minecraft/auth/flows/AuthFlow.cpp @@ -21,6 +21,7 @@ void AuthFlow::succeed() void AuthFlow::executeTask() { if (m_currentStep) { + emitFailed("No task"); return; } changeState(AccountTaskState::STATE_WORKING, tr("Initializing")); @@ -39,38 +40,10 @@ 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); m_currentStep->perform(); } -QString AuthFlow::getStateMessage() const -{ - switch (m_taskState) { - case AccountTaskState::STATE_CREATED: - return "Waiting..."; - case AccountTaskState::STATE_WORKING: - if (m_currentStep) - return m_currentStep->describe(); - return tr("Working..."); - case AccountTaskState::STATE_SUCCEEDED: - return tr("Authentication task succeeded."); - case AccountTaskState::STATE_OFFLINE: - return tr("Failed to contact the authentication server."); - case AccountTaskState::STATE_DISABLED: - return tr("Client ID has changed. New session needs to be created."); - case AccountTaskState::STATE_FAILED_SOFT: - return tr("Encountered an error during authentication."); - case AccountTaskState::STATE_FAILED_HARD: - return tr("Failed to authenticate. The session has expired."); - case AccountTaskState::STATE_FAILED_GONE: - return tr("Failed to authenticate. The account no longer exists."); - default: - return tr("..."); - } -} - void AuthFlow::stepFinished(AccountTaskState resultingState, QString message) { if (changeState(resultingState, message)) { @@ -81,54 +54,61 @@ void AuthFlow::stepFinished(AccountTaskState resultingState, QString message) bool AuthFlow::changeState(AccountTaskState newState, QString reason) { m_taskState = newState; - // FIXME: virtual method invoked in constructor. - // We want that behavior, but maybe make it less weird? - setStatus(getStateMessage()); + setDetails(reason); switch (newState) { case AccountTaskState::STATE_CREATED: { + setStatus(tr("Waiting...")); m_data->errorString.clear(); return true; } case AccountTaskState::STATE_WORKING: { + setStatus(m_currentStep ? m_currentStep->describe() : tr("Working...")); m_data->accountState = AccountState::Working; return true; } case AccountTaskState::STATE_SUCCEEDED: { + setStatus(tr("Authentication task succeeded.")); m_data->accountState = AccountState::Online; emitSucceeded(); return false; } case AccountTaskState::STATE_OFFLINE: { + setStatus(tr("Failed to contact the authentication server.")); m_data->errorString = reason; m_data->accountState = AccountState::Offline; emitFailed(reason); return false; } case AccountTaskState::STATE_DISABLED: { + setStatus(tr("Client ID has changed. New session needs to be created.")); m_data->errorString = reason; m_data->accountState = AccountState::Disabled; emitFailed(reason); return false; } case AccountTaskState::STATE_FAILED_SOFT: { + setStatus(tr("Encountered an error during authentication.")); m_data->errorString = reason; m_data->accountState = AccountState::Errored; emitFailed(reason); return false; } case AccountTaskState::STATE_FAILED_HARD: { + setStatus(tr("Failed to authenticate. The session has expired.")); m_data->errorString = reason; m_data->accountState = AccountState::Expired; emitFailed(reason); return false; } case AccountTaskState::STATE_FAILED_GONE: { + setStatus(tr("Failed to authenticate. The account no longer exists.")); m_data->errorString = reason; m_data->accountState = AccountState::Gone; emitFailed(reason); return false; } default: { + setStatus(tr("...")); QString error = tr("Unknown account task state: %1").arg(int(newState)); m_data->accountState = AccountState::Errored; emitFailed(error); diff --git a/launcher/minecraft/auth/flows/AuthFlow.h b/launcher/minecraft/auth/flows/AuthFlow.h index 10fdd22fac..de563c3c55 100644 --- a/launcher/minecraft/auth/flows/AuthFlow.h +++ b/launcher/minecraft/auth/flows/AuthFlow.h @@ -7,8 +7,6 @@ #include #include -#include - #include "minecraft/auth/AccountData.h" #include "minecraft/auth/AuthStep.h" #include "tasks/Task.h" @@ -27,18 +25,9 @@ class AuthFlow : public Task { AccountTaskState taskState() { return m_taskState; } signals: - void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn); - void hideVerificationUriAndCode(); - void activityChanged(Katabasis::Activity activity); protected: - /** - * Returns the state message for the given state. - * Used to set the status message for the task. - * Should be overridden by subclasses that want to change messages for a given state. - */ - virtual QString getStateMessage() const; void succeed(); void nextStep(); diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp index 0a4b7c8144..71bd250960 100644 --- a/launcher/minecraft/auth/steps/MSAStep.cpp +++ b/launcher/minecraft/auth/steps/MSAStep.cpp @@ -35,28 +35,44 @@ #include "MSAStep.h" +#include +#include +#include #include #include "Application.h" -#include "Logging.h" - -using OAuth2 = Katabasis::DeviceFlow; -using Activity = Katabasis::Activity; MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action) { m_clientId = APPLICATION->getMSAClientID(); - OAuth2::Options opts; - opts.scope = "XboxLive.signin offline_access"; - opts.clientIdentifier = m_clientId; - opts.authorizationUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode"; - opts.accessTokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; - // FIXME: OAuth2 is not aware of our fancy shared pointers - m_oauth2 = new OAuth2(opts, m_data->msaToken, this, APPLICATION->network().get()); + auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this); + oauth2.setReplyHandler(replyHandler); + oauth2.setAuthorizationUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize")); + oauth2.setAccessTokenUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/token")); + oauth2.setScope("XboxLive.SignIn XboxLive.offline_access"); + oauth2.setClientIdentifier(m_clientId); + oauth2.setNetworkAccessManager(APPLICATION->network().get()); + + connect(&oauth2, &QOAuth2AuthorizationCodeFlow::granted, this, [this] { + m_data->msaClientID = oauth2.clientIdentifier(); + m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc(); + m_data->msaToken.notAfter = oauth2.expirationAt(); + m_data->msaToken.extra = oauth2.extraTokens(); + m_data->msaToken.refresh_token = oauth2.refreshToken(); + m_data->msaToken.token = oauth2.token(); + emit finished(AccountTaskState::STATE_WORKING, tr("Got ")); + }); + connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &QDesktopServices::openUrl); + connect(&oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this](const QAbstractOAuth2::Error err) { + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication failed.")); + }); + + connect(&oauth2, &QOAuth2AuthorizationCodeFlow::extraTokensChanged, this, + [this](const QVariantMap& tokens) { m_data->msaToken.extra = tokens; }); - connect(m_oauth2, &OAuth2::activityChanged, this, &MSAStep::onOAuthActivityChanged); - connect(m_oauth2, &OAuth2::showVerificationUriAndCode, this, &MSAStep::showVerificationUriAndCode); + connect(&oauth2, &QOAuth2AuthorizationCodeFlow::clientIdentifierChanged, this, + [this](const QString& clientIdentifier) { m_data->msaClientID = clientIdentifier; }); } QString MSAStep::describe() @@ -69,68 +85,22 @@ void MSAStep::perform() switch (m_action) { case Refresh: { if (m_data->msaClientID != m_clientId) { - emit hideVerificationUriAndCode(); emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - client identification has changed.")); } - m_oauth2->refresh(); + oauth2.setRefreshToken(m_data->msaToken.refresh_token); + oauth2.refreshAccessToken(); return; } case Login: { - QVariantMap extraOpts; - extraOpts["prompt"] = "select_account"; - m_oauth2->setExtraRequestParams(extraOpts); + oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMultiMap* map) { + map->insert("prompt", "select_account"); + map->insert("cobrandid", "8058f65d-ce06-4c30-9559-473c9275a65d"); + }); *m_data = AccountData(); m_data->msaClientID = m_clientId; - m_oauth2->login(); - 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(); - if (!extraTokens.isEmpty()) { - qCDebug(authCredentials()) << "Extra tokens in response:"; - foreach (QString key, extraTokens.keys()) { - qCDebug(authCredentials()) << "\t" << key << ":" << extraTokens.value(key); - } - } - 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.")); - return; - } - case Katabasis::Activity::FailedGone: { - emit hideVerificationUriAndCode(); - emit finished(AccountTaskState::STATE_FAILED_GONE, tr("Microsoft user authentication failed - user no longer exists.")); - return; - } - case Katabasis::Activity::FailedHard: { - emit hideVerificationUriAndCode(); - emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication failed.")); - return; - } - default: { - emit hideVerificationUriAndCode(); - emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication completed with an unrecognized result.")); + oauth2.grant(); return; } } diff --git a/launcher/minecraft/auth/steps/MSAStep.h b/launcher/minecraft/auth/steps/MSAStep.h index ee441308fa..f38f5c70eb 100644 --- a/launcher/minecraft/auth/steps/MSAStep.h +++ b/launcher/minecraft/auth/steps/MSAStep.h @@ -36,11 +36,9 @@ #pragma once #include -#include "QObjectPtr.h" #include "minecraft/auth/AuthStep.h" -#include - +#include class MSAStep : public AuthStep { Q_OBJECT public: @@ -54,11 +52,8 @@ class MSAStep : public AuthStep { QString describe() override; - private slots: - void onOAuthActivityChanged(Katabasis::Activity activity); - private: - Katabasis::DeviceFlow* m_oauth2 = nullptr; Action m_action; QString m_clientId; + QOAuth2AuthorizationCodeFlow oauth2; }; diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index 90ce154516..55a4f185c3 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -171,9 +171,9 @@ void NetRequest::downloadError(QNetworkReply::NetworkError error) } } // error happened during download. - qCCritical(logCat) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; + qCCritical(logCat) << getUid().toString() << "Failed" << m_url.toString() << "with reason" << error; if (m_reply) - qCCritical(logCat) << getUid().toString() << "HTTP Status " << replyStatusCode() << ";error " << errorString(); + qCCritical(logCat) << getUid().toString() << "HTTP Status" << replyStatusCode() << ";error" << errorString(); m_state = State::Failed; } } diff --git a/launcher/qtlogging.ini b/launcher/qtlogging.ini index 5266de59b8..c12d1e1098 100644 --- a/launcher/qtlogging.ini +++ b/launcher/qtlogging.ini @@ -5,7 +5,6 @@ qt.*.debug=false # don't log credentials by default launcher.auth.credentials.debug=false -katabasis.*.debug=false # remove the debug lines, other log levels still get through launcher.task.net.download.debug=false # enable or disable whole catageries diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index e396c01f6f..b249346a41 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -67,9 +67,6 @@ 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(), &AuthFlow::showVerificationUriAndCode, this, &MSALoginDialog::showVerificationUriAndCode); - connect(m_loginTask.get(), &AuthFlow::hideVerificationUriAndCode, this, &MSALoginDialog::hideVerificationUriAndCode); - connect(&m_externalLoginTimer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick); m_loginTask->start(); return QDialog::exec(); @@ -80,55 +77,6 @@ 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("%2").arg(urlString, urlString); - if (urlString == "https://www.microsoft.com/link" && !code.isEmpty()) { - urlString += QString("?otc=%1").arg(code); - DesktopServices::openUrl(urlString); - ui->label->setText(tr("

    Please login in the opened browser. If no browser was opened, please open up %1 in " - "a browser and put in the code %2 to proceed with login.

    ") - .arg(linkString, code)); - } else { - ui->label->setText( - tr("

    Please open up %1 in a browser and put in the code %2 to proceed with login.

    ").arg(linkString, code)); - } - ui->actionButton->setVisible(true); - connect(ui->actionButton, &QPushButton::clicked, [=]() { - DesktopServices::openUrl(uri); - QClipboard* cb = QApplication::clipboard(); - cb->setText(code); - }); -} - -void MSALoginDialog::hideVerificationUriAndCode() -{ - m_externalLoginTimer.stop(); - ui->actionButton->setVisible(false); -} - void MSALoginDialog::setUserInputsEnabled(bool enable) { ui->buttonBox->setEnabled(enable); diff --git a/launcher/ui/dialogs/MSALoginDialog.h b/launcher/ui/dialogs/MSALoginDialog.h index 531ae3cc16..f14e04776d 100644 --- a/launcher/ui/dialogs/MSALoginDialog.h +++ b/launcher/ui/dialogs/MSALoginDialog.h @@ -15,7 +15,6 @@ #pragma once -#include #include #include @@ -45,16 +44,9 @@ class MSALoginDialog : public QDialog { 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(); private: Ui::MSALoginDialog* ui; MinecraftAccountPtr m_account; shared_qobject_ptr m_loginTask; - QTimer m_externalLoginTimer; - int m_externalLoginElapsed = 0; - int m_externalLoginTimeout = 0; }; diff --git a/libraries/README.md b/libraries/README.md index e75a381ee4..67d78daded 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -32,14 +32,6 @@ Simple Java tool that prints the JVM details - version and platform bitness. Do what you want with it. It is so trivial that noone cares. -## Katabasis - -Oauth2 library customized for Microsoft authentication. - -This is a fork of the [O2 library](https://github.com/pipacs/o2). - -MIT licensed. - ## launcher Java launcher part for Minecraft. diff --git a/libraries/katabasis/.gitignore b/libraries/katabasis/.gitignore deleted file mode 100644 index 35e189c5ef..0000000000 --- a/libraries/katabasis/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build/ -*.kdev4 diff --git a/libraries/katabasis/CMakeLists.txt b/libraries/katabasis/CMakeLists.txt deleted file mode 100644 index 643244ede0..0000000000 --- a/libraries/katabasis/CMakeLists.txt +++ /dev/null @@ -1,58 +0,0 @@ -cmake_minimum_required(VERSION 3.9.4) - -string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD) -if(IS_IN_SOURCE_BUILD) - message(FATAL_ERROR "You are building Katabasis in-source. Please separate the build tree from the source tree.") -endif() - -project(Katabasis) -enable_testing() - -set(CMAKE_AUTOMOC ON) -set(CMAKE_INCLUDE_CURRENT_DIR ON) - -set(CMAKE_CXX_STANDARD_REQUIRED true) -set(CMAKE_C_STANDARD_REQUIRED true) -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_C_STANDARD 11) - -if(QT_VERSION_MAJOR EQUAL 5) - find_package(Qt5 COMPONENTS Core Network REQUIRED) -elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) - find_package(Qt6 COMPONENTS Core Network REQUIRED) -endif() - -set( katabasis_PRIVATE - src/DeviceFlow.cpp - src/JsonResponse.cpp - src/JsonResponse.h - src/PollServer.cpp - src/Reply.cpp -) - -set( katabasis_PUBLIC - include/katabasis/DeviceFlow.h - include/katabasis/Globals.h - include/katabasis/PollServer.h - include/katabasis/Reply.h - include/katabasis/RequestParameter.h -) - -ecm_qt_declare_logging_category(katabasis_PRIVATE - HEADER KatabasisLogging.h # NOTE: this won't be in src/, but CMAKE_BINARY_DIR/src isn't included by default so this should be fine - IDENTIFIER katabasisCredentials - CATEGORY_NAME "katabasis.credentials" - DEFAULT_SEVERITY Warning - DESCRIPTION "Secrets and credentials from Katabasis" - EXPORT "Katabasis" -) - -add_library( Katabasis STATIC ${katabasis_PRIVATE} ${katabasis_PUBLIC} ) -target_link_libraries(Katabasis Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network) - -# needed for statically linked Katabasis in shared libs on x86_64 -set_target_properties(Katabasis - PROPERTIES POSITION_INDEPENDENT_CODE TRUE -) - -target_include_directories(Katabasis PUBLIC include PRIVATE src include/katabasis) diff --git a/libraries/katabasis/LICENSE b/libraries/katabasis/LICENSE deleted file mode 100644 index 9ac8d42fb0..0000000000 --- a/libraries/katabasis/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) 2012, Akos Polster -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/libraries/katabasis/README.md b/libraries/katabasis/README.md deleted file mode 100644 index fe6dd4acaa..0000000000 --- a/libraries/katabasis/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Katabasis - MS-flavored OAuth for Qt, derived from the O2 library - -This library's sole purpose is to make interacting with MSA and various MSA and XBox authenticated services less painful. - -It may be possible to backport some of the changes to O2 in the future, but for the sake of going fast, all compatibility concerns have been ignored. - -[You can find the original library's git repository here.](https://github.com/pipacs/o2) - -Notes to contributors: - -* Please follow the coding style of the existing source, where reasonable -* Code contributions are released under Simplified BSD License, as specified in LICENSE. Do not contribute if this license does not suit your code -* If you are interested in working on this, come to the Prism Launcher Discord server and talk first - -## Installation - -Clone the Github repository, integrate the it into your CMake build system. - -The library is static only, dynamic linking and system-wide installation are out of scope and undesirable. - -## Usage - -At this stage, don't, unless you want to help with the library itself. - -This is an experimental fork of the O2 library and is undergoing a big design/architecture shift in order to support different features: - -* Multiple accounts -* Multi-stage authentication/authorization schemes -* Tighter control over token chains and their storage -* Talking to complex APIs and individually authorized microservices -* Token lifetime management, 'offline mode' and resilience in face of network failures -* Token and claims/entitlements validation -* Caching of some API results -* XBox magic -* Mojang magic -* Generally, magic that you would spend weeks on researching while getting confused by contradictory/incomplete documentation (if any is available) diff --git a/libraries/katabasis/acknowledgements.md b/libraries/katabasis/acknowledgements.md deleted file mode 100644 index a6989d15aa..0000000000 --- a/libraries/katabasis/acknowledgements.md +++ /dev/null @@ -1,108 +0,0 @@ -## O2 library by Akos Polster and contributors - -[The origin of this fork.](https://github.com/pipacs/o2) - -> Copyright (c) 2012, Akos Polster -> All rights reserved. -> -> Redistribution and use in source and binary forms, with or without -> modification, are permitted provided that the following conditions are met: -> -> * Redistributions of source code must retain the above copyright notice, this -> list of conditions and the following disclaimer. -> -> * Redistributions in binary form must reproduce the above copyright notice, -> this list of conditions and the following disclaimer in the documentation -> and/or other materials provided with the distribution. -> -> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -> DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -> FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -> SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -> OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -## SimpleCrypt by Andre Somers - -Cryptographic methods for Qt. - -> Copyright (c) 2011, Andre Somers -> All rights reserved. -> -> Redistribution and use in source and binary forms, with or without -> modification, are permitted provided that the following conditions are met: -> -> * Redistributions of source code must retain the above copyright -> notice, this list of conditions and the following disclaimer. -> * Redistributions in binary form must reproduce the above copyright -> notice, this list of conditions and the following disclaimer in the -> documentation and/or other materials provided with the distribution. -> * Neither the name of the Rathenau Instituut, Andre Somers nor the -> names of its contributors may be used to endorse or promote products -> derived from this software without specific prior written permission. -> -> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -> ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -> WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -> DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY -> DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -> (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -> ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -> (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -> SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -## Mandeep Sandhu - -Configurable settings storage, Twitter XAuth specialization, new demos, cleanups. - -> "Hi Akos, -> -> I'm writing this mail to confirm that my contributions to the O2 library, available here , can be freely distributed according to the project's license (as shown in the LICENSE file). -> -> Regards, -> -mandeep" - -## Sergey Gavrushkin - -FreshBooks specialization - -## Theofilos Intzoglou - -Hubic specialization - -## Dimitar - -SurveyMonkey specialization - -## David Brooks - -CMake related fixes and improvements. - -## Lukas Vogel - -Spotify support - -## Alan Garny - -Windows DLL build support - -## MartinMikita - -Bug fixes - -## Larry Shaffer - -Versioning, shared lib, install target and header support - -## Gilmanov Ildar - -Bug fixes, support for ```qml``` module - -## Fabian Vogt - -Bug fixes, support for building without Qt keywords enabled diff --git a/libraries/katabasis/include/katabasis/Bits.h b/libraries/katabasis/include/katabasis/Bits.h deleted file mode 100644 index 15da2a5a8a..0000000000 --- a/libraries/katabasis/include/katabasis/Bits.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace Katabasis { -enum class Activity { - Idle, - LoggingIn, - LoggingOut, - Refreshing, - FailedSoft, //!< soft failure. this generally means the user auth details haven't been invalidated - FailedHard, //!< hard failure. auth is invalid - FailedGone, //!< hard failure. auth is invalid, and the account no longer exists - Succeeded -}; - -enum class Validity { None, Assumed, Certain }; - -struct Token { - QDateTime issueInstant; - QDateTime notAfter; - QString token; - QString refresh_token; - QVariantMap extra; - - Validity validity = Validity::None; - bool persistent = true; -}; - -} // namespace Katabasis diff --git a/libraries/katabasis/include/katabasis/DeviceFlow.h b/libraries/katabasis/include/katabasis/DeviceFlow.h deleted file mode 100644 index 98724d81b9..0000000000 --- a/libraries/katabasis/include/katabasis/DeviceFlow.h +++ /dev/null @@ -1,149 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "Bits.h" -#include "Reply.h" -#include "RequestParameter.h" - -namespace Katabasis { - -class ReplyServer; -class PollServer; - -/// Simple OAuth2 Device Flow authenticator. -class DeviceFlow : public QObject { - Q_OBJECT - public: - Q_ENUMS(GrantFlow) - - public: - struct Options { - QString userAgent = QStringLiteral("Katabasis/1.0"); - QString responseType = QStringLiteral("code"); - QString scope; - QString clientIdentifier; - QString clientSecret; - QUrl authorizationUrl; - QUrl accessTokenUrl; - }; - - public: - /// Are we authenticated? - bool linked(); - - /// Authentication token. - QString token(); - - /// Provider-specific extra tokens, available after a successful authentication - QVariantMap extraTokens(); - - public: - // TODO: put in `Options` - /// User-defined extra parameters to append to request URL - QVariantMap extraRequestParams(); - void setExtraRequestParams(const QVariantMap& value); - - // TODO: split up the class into multiple, each implementing one OAuth2 flow - /// Grant type (if non-standard) - QString grantType(); - void setGrantType(const QString& value); - - public: - /// Constructor. - /// @param parent Parent object. - explicit DeviceFlow(Options& opts, Token& token, QObject* parent = 0, QNetworkAccessManager* manager = 0); - - /// Get refresh token. - QString refreshToken(); - - /// Get token expiration time - QDateTime expires(); - - public slots: - /// Authenticate. - void login(); - - /// De-authenticate. - void logout(); - - /// Refresh token. - bool refresh(); - - /// Handle situation where reply server has opted to close its connection - void serverHasClosed(bool paramsfound = false); - - signals: - /// Emitted when client needs to open a web browser window, with the given URL. - void openBrowser(const QUrl& url); - - /// Emitted when client can close the browser window. - void closeBrowser(); - - /// Emitted when client needs to show a verification uri and user code - void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn); - - /// Emitted when the internal state changes - void activityChanged(Activity activity); - - public slots: - /// Handle verification response. - void onVerificationReceived(QMap); - - protected slots: - /// Handle completion of a Device Authorization Request - void onDeviceAuthReplyFinished(); - - /// Handle completion of a refresh request. - void onRefreshFinished(); - - /// Handle failure of a refresh request. - void onRefreshError(QNetworkReply::NetworkError error, QNetworkReply* reply); - - protected: - /// Set refresh token. - void setRefreshToken(const QString& v); - - /// Set token expiration time. - void setExpires(QDateTime v); - - /// Start polling authorization server - void startPollServer(const QVariantMap& params, int expiresIn); - - /// Set authentication token. - void setToken(const QString& v); - - /// Set the linked state - void setLinked(bool v); - - /// Set extra tokens found in OAuth response - void setExtraTokens(QVariantMap extraTokens); - - /// Set local poll server - void setPollServer(PollServer* server); - - PollServer* pollServer() const; - - void updateActivity(Activity activity); - - protected: - Options options_; - - QVariantMap extraReqParams_; - QNetworkAccessManager* manager_ = nullptr; - ReplyList timedReplies_; - QString grantType_; - - protected: - Token& token_; - - private: - PollServer* pollServer_ = nullptr; - Activity activity_ = Activity::Idle; -}; - -} // namespace Katabasis diff --git a/libraries/katabasis/include/katabasis/Globals.h b/libraries/katabasis/include/katabasis/Globals.h deleted file mode 100644 index 02fe1cf45c..0000000000 --- a/libraries/katabasis/include/katabasis/Globals.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -namespace Katabasis { - -// Common constants -const char ENCRYPTION_KEY[] = "12345678"; -const char MIME_TYPE_XFORM[] = "application/x-www-form-urlencoded"; -const char MIME_TYPE_JSON[] = "application/json"; - -// OAuth 1/1.1 Request Parameters -const char OAUTH_CALLBACK[] = "oauth_callback"; -const char OAUTH_CONSUMER_KEY[] = "oauth_consumer_key"; -const char OAUTH_NONCE[] = "oauth_nonce"; -const char OAUTH_SIGNATURE[] = "oauth_signature"; -const char OAUTH_SIGNATURE_METHOD[] = "oauth_signature_method"; -const char OAUTH_TIMESTAMP[] = "oauth_timestamp"; -const char OAUTH_VERSION[] = "oauth_version"; -// OAuth 1/1.1 Response Parameters -const char OAUTH_TOKEN[] = "oauth_token"; -const char OAUTH_TOKEN_SECRET[] = "oauth_token_secret"; -const char OAUTH_CALLBACK_CONFIRMED[] = "oauth_callback_confirmed"; -const char OAUTH_VERFIER[] = "oauth_verifier"; - -// OAuth 2 Request Parameters -const char OAUTH2_RESPONSE_TYPE[] = "response_type"; -const char OAUTH2_CLIENT_ID[] = "client_id"; -const char OAUTH2_CLIENT_SECRET[] = "client_secret"; -const char OAUTH2_USERNAME[] = "username"; -const char OAUTH2_PASSWORD[] = "password"; -const char OAUTH2_REDIRECT_URI[] = "redirect_uri"; -const char OAUTH2_SCOPE[] = "scope"; -const char OAUTH2_GRANT_TYPE_CODE[] = "code"; -const char OAUTH2_GRANT_TYPE_TOKEN[] = "token"; -const char OAUTH2_GRANT_TYPE_PASSWORD[] = "password"; -const char OAUTH2_GRANT_TYPE_DEVICE[] = "urn:ietf:params:oauth:grant-type:device_code"; -const char OAUTH2_GRANT_TYPE[] = "grant_type"; -const char OAUTH2_API_KEY[] = "api_key"; -const char OAUTH2_STATE[] = "state"; -const char OAUTH2_CODE[] = "code"; - -// OAuth 2 Response Parameters -const char OAUTH2_ACCESS_TOKEN[] = "access_token"; -const char OAUTH2_REFRESH_TOKEN[] = "refresh_token"; -const char OAUTH2_EXPIRES_IN[] = "expires_in"; -const char OAUTH2_DEVICE_CODE[] = "device_code"; -const char OAUTH2_USER_CODE[] = "user_code"; -const char OAUTH2_VERIFICATION_URI[] = "verification_uri"; -const char OAUTH2_VERIFICATION_URL[] = "verification_url"; // Google sign-in -const char OAUTH2_VERIFICATION_URI_COMPLETE[] = "verification_uri_complete"; -const char OAUTH2_INTERVAL[] = "interval"; - -// Parameter values -const char AUTHORIZATION_CODE[] = "authorization_code"; - -// Standard HTTP headers -const char HTTP_HTTP_HEADER[] = "HTTP"; -const char HTTP_AUTHORIZATION_HEADER[] = "Authorization"; - -} // namespace Katabasis diff --git a/libraries/katabasis/include/katabasis/PollServer.h b/libraries/katabasis/include/katabasis/PollServer.h deleted file mode 100644 index fd6a5351c1..0000000000 --- a/libraries/katabasis/include/katabasis/PollServer.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -class QNetworkAccessManager; - -namespace Katabasis { - -/// Poll an authorization server for token -class PollServer : public QObject { - Q_OBJECT - - public: - explicit PollServer(QNetworkAccessManager* manager, - const QNetworkRequest& request, - const QByteArray& payload, - int expiresIn, - QObject* parent = 0); - - /// Seconds to wait between polling requests - Q_PROPERTY(int interval READ interval WRITE setInterval) - int interval() const; - void setInterval(int interval); - - signals: - void verificationReceived(QMap); - void serverClosed(bool); // whether it has found parameters - - public slots: - void startPolling(); - - protected slots: - void onPollTimeout(); - void onExpiration(); - void onReplyFinished(); - - protected: - QNetworkAccessManager* manager_; - const QNetworkRequest request_; - const QByteArray payload_; - const int expiresIn_; - QTimer expirationTimer; - QTimer pollTimer; -}; - -} // namespace Katabasis diff --git a/libraries/katabasis/include/katabasis/Reply.h b/libraries/katabasis/include/katabasis/Reply.h deleted file mode 100644 index 89ee90e984..0000000000 --- a/libraries/katabasis/include/katabasis/Reply.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace Katabasis { - -constexpr int defaultTimeout = 30 * 1000; - -/// A network request/reply pair that can time out. -class Reply : public QTimer { - Q_OBJECT - - public: - Reply(QNetworkReply* reply, int timeOut = defaultTimeout, QObject* parent = 0); - - signals: - void error(QNetworkReply::NetworkError); - - public slots: - /// When time out occurs, the QNetworkReply's error() signal is triggered. - void onTimeOut(); - - public: - QNetworkReply* reply; - bool timedOut = false; -}; - -/// List of O2Replies. -class ReplyList { - public: - ReplyList() { ignoreSslErrors_ = false; } - - /// Destructor. - /// Deletes all O2Reply instances in the list. - virtual ~ReplyList(); - - /// Create a new O2Reply from a QNetworkReply, and add it to this list. - void add(QNetworkReply* reply, int timeOut = defaultTimeout); - - /// Add an O2Reply to the list, while taking ownership of it. - void add(Reply* reply); - - /// Remove item from the list that corresponds to a QNetworkReply. - void remove(QNetworkReply* reply); - - /// Find an O2Reply in the list, corresponding to a QNetworkReply. - /// @return Matching O2Reply or NULL. - Reply* find(QNetworkReply* reply); - - bool ignoreSslErrors(); - void setIgnoreSslErrors(bool ignoreSslErrors); - - protected: - QList replies_; - bool ignoreSslErrors_; -}; - -} // namespace Katabasis diff --git a/libraries/katabasis/include/katabasis/RequestParameter.h b/libraries/katabasis/include/katabasis/RequestParameter.h deleted file mode 100644 index 1d23cf0e14..0000000000 --- a/libraries/katabasis/include/katabasis/RequestParameter.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -namespace Katabasis { - -/// Request parameter (name-value pair) participating in authentication. -struct RequestParameter { - RequestParameter(const QByteArray& n, const QByteArray& v) : name(n), value(v) {} - bool operator<(const RequestParameter& other) const { return (name == other.name) ? (value < other.value) : (name < other.name); } - QByteArray name; - QByteArray value; -}; - -} // namespace Katabasis diff --git a/libraries/katabasis/src/DeviceFlow.cpp b/libraries/katabasis/src/DeviceFlow.cpp deleted file mode 100644 index 3b9d9c53f5..0000000000 --- a/libraries/katabasis/src/DeviceFlow.cpp +++ /dev/null @@ -1,467 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "katabasis/DeviceFlow.h" -#include "katabasis/Globals.h" -#include "katabasis/PollServer.h" - -#include "JsonResponse.h" -#include "KatabasisLogging.h" - -namespace { - -// ref: https://tools.ietf.org/html/rfc8628#section-3.2 -// Exception: Google sign-in uses "verification_url" instead of "*_uri" - we'll accept both. -bool hasMandatoryDeviceAuthParams(const QVariantMap& params) -{ - if (!params.contains(Katabasis::OAUTH2_DEVICE_CODE)) - return false; - - if (!params.contains(Katabasis::OAUTH2_USER_CODE)) - return false; - - if (!(params.contains(Katabasis::OAUTH2_VERIFICATION_URI) || params.contains(Katabasis::OAUTH2_VERIFICATION_URL))) - return false; - - if (!params.contains(Katabasis::OAUTH2_EXPIRES_IN)) - return false; - - return true; -} - -QByteArray createQueryParameters(const QList& parameters) -{ - QByteArray ret; - bool first = true; - for (auto& h : parameters) { - if (first) { - first = false; - } else { - ret.append("&"); - } - ret.append(QUrl::toPercentEncoding(h.name) + "=" + QUrl::toPercentEncoding(h.value)); - } - return ret; -} -} // namespace - -namespace Katabasis { - -DeviceFlow::DeviceFlow(Options& opts, Token& token, QObject* parent, QNetworkAccessManager* manager) : QObject(parent), token_(token) -{ - manager_ = manager ? manager : new QNetworkAccessManager(this); - qRegisterMetaType("QNetworkReply::NetworkError"); - options_ = opts; -} - -bool DeviceFlow::linked() -{ - return token_.validity != Validity::None; -} -void DeviceFlow::setLinked(bool v) -{ - qDebug() << "DeviceFlow::setLinked:" << (v ? "true" : "false"); - token_.validity = v ? Validity::Certain : Validity::None; -} - -void DeviceFlow::updateActivity(Activity activity) -{ - if (activity_ == activity) { - return; - } - - activity_ = activity; - switch (activity) { - case Katabasis::Activity::Idle: - case Katabasis::Activity::LoggingIn: - case Katabasis::Activity::LoggingOut: - case Katabasis::Activity::Refreshing: - // non-terminal states... - break; - case Katabasis::Activity::FailedSoft: - // terminal state, tokens did not change - break; - case Katabasis::Activity::FailedHard: - case Katabasis::Activity::FailedGone: - // terminal state, tokens are invalid - token_ = Token(); - break; - case Katabasis::Activity::Succeeded: - setLinked(true); - break; - } - emit activityChanged(activity_); -} - -QString DeviceFlow::token() -{ - return token_.token; -} -void DeviceFlow::setToken(const QString& v) -{ - token_.token = v; -} - -QVariantMap DeviceFlow::extraTokens() -{ - return token_.extra; -} - -void DeviceFlow::setExtraTokens(QVariantMap extraTokens) -{ - token_.extra = extraTokens; -} - -void DeviceFlow::setPollServer(PollServer* server) -{ - if (pollServer_) - pollServer_->deleteLater(); - - pollServer_ = server; -} - -PollServer* DeviceFlow::pollServer() const -{ - return pollServer_; -} - -QVariantMap DeviceFlow::extraRequestParams() -{ - return extraReqParams_; -} - -void DeviceFlow::setExtraRequestParams(const QVariantMap& value) -{ - extraReqParams_ = value; -} - -QString DeviceFlow::grantType() -{ - if (!grantType_.isEmpty()) - return grantType_; - - return OAUTH2_GRANT_TYPE_DEVICE; -} - -void DeviceFlow::setGrantType(const QString& value) -{ - grantType_ = value; -} - -// First get the URL and token to display to the user -void DeviceFlow::login() -{ - qDebug() << "DeviceFlow::link"; - - updateActivity(Activity::LoggingIn); - setLinked(false); - setToken(""); - setExtraTokens(QVariantMap()); - setRefreshToken(QString()); - setExpires(QDateTime()); - - QList parameters; - parameters.append(RequestParameter(OAUTH2_CLIENT_ID, options_.clientIdentifier.toUtf8())); - parameters.append(RequestParameter(OAUTH2_SCOPE, options_.scope.toUtf8())); - QByteArray payload = createQueryParameters(parameters); - - QUrl url(options_.authorizationUrl); - QNetworkRequest deviceRequest(url); - deviceRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - QNetworkReply* tokenReply = manager_->post(deviceRequest, payload); - - connect(tokenReply, &QNetworkReply::finished, this, &DeviceFlow::onDeviceAuthReplyFinished, Qt::QueuedConnection); -} - -// Then, once we get them, present them to the user -void DeviceFlow::onDeviceAuthReplyFinished() -{ - qDebug() << "DeviceFlow::onDeviceAuthReplyFinished"; - QNetworkReply* tokenReply = qobject_cast(sender()); - if (!tokenReply) { - qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: reply is null"; - return; - } - if (tokenReply->error() == QNetworkReply::NoError) { - QByteArray replyData = tokenReply->readAll(); - - // Dump replyData - // SENSITIVE DATA in RelWithDebInfo or Debug builds - // qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: replyData\n"; - // qDebug() << QString( replyData ); - - QVariantMap params = parseJsonResponse(replyData); - - // Dump tokens - qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: Tokens returned:\n"; - foreach (QString key, params.keys()) { - // SENSITIVE DATA in RelWithDebInfo or Debug builds, so it is truncated first - qDebug() << key << ": " << params.value(key).toString(); - } - - // Check for mandatory parameters - if (hasMandatoryDeviceAuthParams(params)) { - qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: Device auth request response"; - - const QString userCode = params.take(OAUTH2_USER_CODE).toString(); - QUrl uri = params.take(OAUTH2_VERIFICATION_URI).toUrl(); - if (uri.isEmpty()) - uri = params.take(OAUTH2_VERIFICATION_URL).toUrl(); - - if (params.contains(OAUTH2_VERIFICATION_URI_COMPLETE)) - emit openBrowser(params.take(OAUTH2_VERIFICATION_URI_COMPLETE).toUrl()); - - bool ok = false; - int expiresIn = params[OAUTH2_EXPIRES_IN].toInt(&ok); - if (!ok) { - qWarning() << "DeviceFlow::startPollServer: No expired_in parameter"; - updateActivity(Activity::FailedHard); - return; - } - - emit showVerificationUriAndCode(uri, userCode, expiresIn); - - startPollServer(params, expiresIn); - } else { - qWarning() << "DeviceFlow::onDeviceAuthReplyFinished: Mandatory parameters missing from response"; - updateActivity(Activity::FailedHard); - } - } - tokenReply->deleteLater(); -} - -// Spin up polling for the user completing the login flow out of band -void DeviceFlow::startPollServer(const QVariantMap& params, int expiresIn) -{ - qDebug() << "DeviceFlow::startPollServer: device_ and user_code expires in" << expiresIn << "seconds"; - - QUrl url(options_.accessTokenUrl); - QNetworkRequest authRequest(url); - authRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - - const QString deviceCode = params[OAUTH2_DEVICE_CODE].toString(); - const QString grantType = grantType_.isEmpty() ? OAUTH2_GRANT_TYPE_DEVICE : grantType_; - - QList parameters; - parameters.append(RequestParameter(OAUTH2_CLIENT_ID, options_.clientIdentifier.toUtf8())); - if (!options_.clientSecret.isEmpty()) { - parameters.append(RequestParameter(OAUTH2_CLIENT_SECRET, options_.clientSecret.toUtf8())); - } - parameters.append(RequestParameter(OAUTH2_CODE, deviceCode.toUtf8())); - parameters.append(RequestParameter(OAUTH2_GRANT_TYPE, grantType.toUtf8())); - QByteArray payload = createQueryParameters(parameters); - - PollServer* pollServer = new PollServer(manager_, authRequest, payload, expiresIn, this); - if (params.contains(OAUTH2_INTERVAL)) { - bool ok = false; - int interval = params[OAUTH2_INTERVAL].toInt(&ok); - if (ok) { - pollServer->setInterval(interval); - } - } - connect(pollServer, &PollServer::verificationReceived, this, &DeviceFlow::onVerificationReceived); - connect(pollServer, &PollServer::serverClosed, this, &DeviceFlow::serverHasClosed); - setPollServer(pollServer); - pollServer->startPolling(); -} - -// Once the user completes the flow, update the internal state and report it to observers -void DeviceFlow::onVerificationReceived(const QMap response) -{ - qDebug() << "DeviceFlow::onVerificationReceived: Emitting closeBrowser()"; - emit closeBrowser(); - - if (response.contains("error")) { - qWarning() << "DeviceFlow::onVerificationReceived: Verification failed:" << response; - updateActivity(Activity::FailedHard); - return; - } - - // Check for mandatory tokens - if (response.contains(OAUTH2_ACCESS_TOKEN)) { - qDebug() << "DeviceFlow::onVerificationReceived: Access token returned for implicit or device flow"; - setToken(response.value(OAUTH2_ACCESS_TOKEN)); - if (response.contains(OAUTH2_EXPIRES_IN)) { - bool ok = false; - int expiresIn = response.value(OAUTH2_EXPIRES_IN).toInt(&ok); - if (ok) { - qDebug() << "DeviceFlow::onVerificationReceived: Token expires in" << expiresIn << "seconds"; - setExpires(QDateTime::currentDateTimeUtc().addSecs(expiresIn)); - } - } - if (response.contains(OAUTH2_REFRESH_TOKEN)) { - setRefreshToken(response.value(OAUTH2_REFRESH_TOKEN)); - } - updateActivity(Activity::Succeeded); - } else { - qWarning() << "DeviceFlow::onVerificationReceived: Access token missing from response for implicit or device flow"; - updateActivity(Activity::FailedHard); - } -} - -// Or if the flow fails or the polling times out, update the internal state with error and report it to observers -void DeviceFlow::serverHasClosed(bool paramsfound) -{ - if (!paramsfound) { - // server has probably timed out after receiving first response - updateActivity(Activity::FailedHard); - } - // poll server is not re-used for later auth requests - setPollServer(NULL); -} - -void DeviceFlow::logout() -{ - qDebug() << "DeviceFlow::unlink"; - updateActivity(Activity::LoggingOut); - // FIXME: implement logout flows... if they exist - token_ = Token(); - updateActivity(Activity::FailedHard); -} - -QDateTime DeviceFlow::expires() -{ - return token_.notAfter; -} -void DeviceFlow::setExpires(QDateTime v) -{ - token_.notAfter = v; -} - -QString DeviceFlow::refreshToken() -{ - return token_.refresh_token; -} - -void DeviceFlow::setRefreshToken(const QString& v) -{ - qCDebug(katabasisCredentials) << "new refresh token:" << v; - token_.refresh_token = v; -} - -namespace { -QByteArray buildRequestBody(const QMap& parameters) -{ - QByteArray body; - bool first = true; - foreach (QString key, parameters.keys()) { - if (first) { - first = false; - } else { - body.append("&"); - } - QString value = parameters.value(key); - body.append(QUrl::toPercentEncoding(key) + QString("=").toUtf8() + QUrl::toPercentEncoding(value)); - } - return body; -} -} // namespace - -bool DeviceFlow::refresh() -{ - qDebug() << "DeviceFlow::refresh: Token: ..." << refreshToken().right(7); - - updateActivity(Activity::Refreshing); - - if (refreshToken().isEmpty()) { - qWarning() << "DeviceFlow::refresh: No refresh token"; - onRefreshError(QNetworkReply::AuthenticationRequiredError, nullptr); - return false; - } - if (options_.accessTokenUrl.isEmpty()) { - qWarning() << "DeviceFlow::refresh: Refresh token URL not set"; - onRefreshError(QNetworkReply::AuthenticationRequiredError, nullptr); - return false; - } - - QNetworkRequest refreshRequest(options_.accessTokenUrl); - refreshRequest.setHeader(QNetworkRequest::ContentTypeHeader, MIME_TYPE_XFORM); - QMap parameters; - parameters.insert(OAUTH2_CLIENT_ID, options_.clientIdentifier); - if (!options_.clientSecret.isEmpty()) { - parameters.insert(OAUTH2_CLIENT_SECRET, options_.clientSecret); - } - parameters.insert(OAUTH2_REFRESH_TOKEN, refreshToken()); - parameters.insert(OAUTH2_GRANT_TYPE, OAUTH2_REFRESH_TOKEN); - - QByteArray data = buildRequestBody(parameters); - QNetworkReply* refreshReply = manager_->post(refreshRequest, data); - timedReplies_.add(refreshReply); - connect(refreshReply, &QNetworkReply::finished, this, &DeviceFlow::onRefreshFinished, Qt::QueuedConnection); - return true; -} - -void DeviceFlow::onRefreshFinished() -{ - QNetworkReply* refreshReply = qobject_cast(sender()); - - auto networkError = refreshReply->error(); - if (networkError == QNetworkReply::NoError) { - QByteArray reply = refreshReply->readAll(); - QVariantMap tokens = parseJsonResponse(reply); - setToken(tokens.value(OAUTH2_ACCESS_TOKEN).toString()); - setExpires(QDateTime::currentDateTimeUtc().addSecs(tokens.value(OAUTH2_EXPIRES_IN).toInt())); - QString refreshToken = tokens.value(OAUTH2_REFRESH_TOKEN).toString(); - if (!refreshToken.isEmpty()) { - setRefreshToken(refreshToken); - } else { - qDebug() << "No new refresh token. Keep the old one."; - } - timedReplies_.remove(refreshReply); - refreshReply->deleteLater(); - updateActivity(Activity::Succeeded); - qDebug() << "New token expires in" << expires() << "seconds"; - } else { - // FIXME: differentiate the error more here - onRefreshError(networkError, refreshReply); - } -} - -void DeviceFlow::onRefreshError(QNetworkReply::NetworkError error, QNetworkReply* refreshReply) -{ - QString errorString = "No Reply"; - if (refreshReply) { - timedReplies_.remove(refreshReply); - errorString = refreshReply->errorString(); - } - - switch (error) { - // used for invalid credentials and similar errors. Fall through. - case QNetworkReply::AuthenticationRequiredError: - case QNetworkReply::ContentAccessDenied: - case QNetworkReply::ContentOperationNotPermittedError: - case QNetworkReply::ProtocolInvalidOperationError: - updateActivity(Activity::FailedHard); - break; - case QNetworkReply::ContentGoneError: { - updateActivity(Activity::FailedGone); - break; - } - case QNetworkReply::TimeoutError: - case QNetworkReply::OperationCanceledError: - case QNetworkReply::SslHandshakeFailedError: - default: - updateActivity(Activity::FailedSoft); - return; - } - if (refreshReply) { - refreshReply->deleteLater(); - } - qDebug() << "DeviceFlow::onRefreshFinished: Error" << static_cast(error) << " - " << errorString; -} - -} // namespace Katabasis diff --git a/libraries/katabasis/src/JsonResponse.cpp b/libraries/katabasis/src/JsonResponse.cpp deleted file mode 100644 index 6840627ac9..0000000000 --- a/libraries/katabasis/src/JsonResponse.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "JsonResponse.h" - -#include -#include -#include -#include - -namespace Katabasis { - -QVariantMap parseJsonResponse(const QByteArray& data) -{ - QJsonParseError err; - QJsonDocument doc = QJsonDocument::fromJson(data, &err); - if (err.error != QJsonParseError::NoError) { - qWarning() << "parseTokenResponse: Failed to parse token response due to err:" << err.errorString(); - return QVariantMap(); - } - - if (!doc.isObject()) { - qWarning() << "parseTokenResponse: Token response is not an object"; - return QVariantMap(); - } - - return doc.object().toVariantMap(); -} - -} // namespace Katabasis diff --git a/libraries/katabasis/src/JsonResponse.h b/libraries/katabasis/src/JsonResponse.h deleted file mode 100644 index ff34717525..0000000000 --- a/libraries/katabasis/src/JsonResponse.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -class QByteArray; - -namespace Katabasis { - -/// Parse JSON data into a QVariantMap -QVariantMap parseJsonResponse(const QByteArray& data); - -} // namespace Katabasis diff --git a/libraries/katabasis/src/PollServer.cpp b/libraries/katabasis/src/PollServer.cpp deleted file mode 100644 index c1c316df9b..0000000000 --- a/libraries/katabasis/src/PollServer.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include -#include - -#include "JsonResponse.h" -#include "katabasis/PollServer.h" - -namespace { -QMap toVerificationParams(const QVariantMap& map) -{ - QMap params; - for (QVariantMap::const_iterator i = map.constBegin(); i != map.constEnd(); ++i) { - params[i.key()] = i.value().toString(); - } - return params; -} -} // namespace - -namespace Katabasis { - -PollServer::PollServer(QNetworkAccessManager* manager, - const QNetworkRequest& request, - const QByteArray& payload, - int expiresIn, - QObject* parent) - : QObject(parent), manager_(manager), request_(request), payload_(payload), expiresIn_(expiresIn) -{ - expirationTimer.setTimerType(Qt::VeryCoarseTimer); - expirationTimer.setInterval(expiresIn * 1000); - expirationTimer.setSingleShot(true); - connect(&expirationTimer, SIGNAL(timeout()), this, SLOT(onExpiration())); - expirationTimer.start(); - - pollTimer.setTimerType(Qt::VeryCoarseTimer); - pollTimer.setInterval(5 * 1000); - pollTimer.setSingleShot(true); - connect(&pollTimer, SIGNAL(timeout()), this, SLOT(onPollTimeout())); -} - -int PollServer::interval() const -{ - return pollTimer.interval() / 1000; -} - -void PollServer::setInterval(int interval) -{ - pollTimer.setInterval(interval * 1000); -} - -void PollServer::startPolling() -{ - if (expirationTimer.isActive()) { - pollTimer.start(); - } -} - -void PollServer::onPollTimeout() -{ - qDebug() << "PollServer::onPollTimeout: retrying"; - QNetworkReply* reply = manager_->post(request_, payload_); - connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished())); -} - -void PollServer::onExpiration() -{ - pollTimer.stop(); - emit serverClosed(false); -} - -void PollServer::onReplyFinished() -{ - QNetworkReply* reply = qobject_cast(sender()); - - if (!reply) { - qDebug() << "PollServer::onReplyFinished: reply is null"; - return; - } - - QByteArray replyData = reply->readAll(); - QMap params = toVerificationParams(parseJsonResponse(replyData)); - - // Dump replyData - // SENSITIVE DATA in RelWithDebInfo or Debug builds - // qDebug() << "PollServer::onReplyFinished: replyData\n"; - // qDebug() << QString( replyData ); - - if (reply->error() == QNetworkReply::TimeoutError) { - // rfc8628#section-3.2 - // "On encountering a connection timeout, clients MUST unilaterally - // reduce their polling frequency before retrying. The use of an - // exponential backoff algorithm to achieve this, such as doubling the - // polling interval on each such connection timeout, is RECOMMENDED." - setInterval(interval() * 2); - pollTimer.start(); - } else { - QString error = params.value("error"); - if (error == "slow_down") { - // rfc8628#section-3.2 - // "A variant of 'authorization_pending', the authorization request is - // still pending and polling should continue, but the interval MUST - // be increased by 5 seconds for this and all subsequent requests." - setInterval(interval() + 5); - pollTimer.start(); - } else if (error == "authorization_pending") { - // keep trying - rfc8628#section-3.2 - // "The authorization request is still pending as the end user hasn't - // yet completed the user-interaction steps (Section 3.3)." - pollTimer.start(); - } else { - expirationTimer.stop(); - emit serverClosed(true); - // let O2 handle the other cases - emit verificationReceived(params); - } - } - reply->deleteLater(); -} - -} // namespace Katabasis diff --git a/libraries/katabasis/src/Reply.cpp b/libraries/katabasis/src/Reply.cpp deleted file mode 100644 index 4a5017e22e..0000000000 --- a/libraries/katabasis/src/Reply.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include - -#include "katabasis/Reply.h" - -namespace Katabasis { - -Reply::Reply(QNetworkReply* r, int timeOut, QObject* parent) : QTimer(parent), reply(r) -{ - setSingleShot(true); - connect(this, &Reply::timeout, this, &Reply::onTimeOut, Qt::QueuedConnection); - start(timeOut); -} - -void Reply::onTimeOut() -{ - timedOut = true; - reply->abort(); -} - -// ---------------------------- - -ReplyList::~ReplyList() -{ - foreach (Reply* timedReply, replies_) { - delete timedReply; - } -} - -void ReplyList::add(QNetworkReply* reply, int timeOut) -{ - if (reply && ignoreSslErrors()) { - reply->ignoreSslErrors(); - } - add(new Reply(reply, timeOut)); -} - -void ReplyList::add(Reply* reply) -{ - replies_.append(reply); -} - -void ReplyList::remove(QNetworkReply* reply) -{ - Reply* o2Reply = find(reply); - if (o2Reply) { - o2Reply->stop(); - (void)replies_.removeOne(o2Reply); - // we took ownership, we must free - delete o2Reply; - } -} - -Reply* ReplyList::find(QNetworkReply* reply) -{ - foreach (Reply* timedReply, replies_) { - if (timedReply->reply == reply) { - return timedReply; - } - } - return 0; -} - -bool ReplyList::ignoreSslErrors() -{ - return ignoreSslErrors_; -} - -void ReplyList::setIgnoreSslErrors(bool ignoreSslErrors) -{ - ignoreSslErrors_ = ignoreSslErrors; -} - -} // namespace Katabasis From 849c3faeb4dfeb9761fba4bc9b1d35e0b8129c41 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 15 May 2024 13:06:55 +0300 Subject: [PATCH 0200/2054] Fix CI Signed-off-by: Trial97 --- .github/workflows/codeql.yml | 2 +- launcher/minecraft/auth/steps/MSAStep.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d40d7eb686..261f67819d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -23,7 +23,7 @@ jobs: run: sudo apt-get -y update - sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 + sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 libqt5networkauth5 - name: Configure and Build run: | diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp index 71bd250960..79cb062b68 100644 --- a/launcher/minecraft/auth/steps/MSAStep.cpp +++ b/launcher/minecraft/auth/steps/MSAStep.cpp @@ -93,7 +93,11 @@ void MSAStep::perform() return; } case Login: { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0 oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMultiMap* map) { +#else + oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMap* map) { +#endif map->insert("prompt", "select_account"); map->insert("cobrandid", "8058f65d-ce06-4c30-9559-473c9275a65d"); }); From abedc6a23ce85c27f2816f9510d1acdcd59480f4 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 15 May 2024 17:25:15 +0300 Subject: [PATCH 0201/2054] Improve login UI Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 12 +--- launcher/minecraft/auth/AccountData.cpp | 18 +++--- launcher/minecraft/auth/AccountData.h | 30 +++------ launcher/minecraft/auth/AccountList.h | 2 +- .../minecraft/auth/{flows => }/AuthFlow.cpp | 35 +++++++--- .../minecraft/auth/{flows => }/AuthFlow.h | 12 ++-- launcher/minecraft/auth/MinecraftAccount.cpp | 47 ++++---------- launcher/minecraft/auth/MinecraftAccount.h | 6 +- launcher/minecraft/auth/Parsers.cpp | 14 ++-- launcher/minecraft/auth/Parsers.h | 4 +- launcher/minecraft/auth/flows/MSA.cpp | 36 ----------- launcher/minecraft/auth/flows/MSA.h | 14 ---- launcher/minecraft/auth/flows/Offline.cpp | 8 --- launcher/minecraft/auth/flows/Offline.h | 8 --- launcher/minecraft/auth/steps/MSAStep.cpp | 40 +++++------- launcher/minecraft/auth/steps/MSAStep.h | 10 +-- launcher/minecraft/auth/steps/OfflineStep.cpp | 13 ---- launcher/minecraft/auth/steps/OfflineStep.h | 15 ----- .../auth/steps/XboxAuthorizationStep.cpp | 4 +- .../auth/steps/XboxAuthorizationStep.h | 4 +- .../minecraft/auth/steps/XboxUserStep.cpp | 2 +- launcher/ui/dialogs/MSALoginDialog.cpp | 64 ++++++++++--------- launcher/ui/dialogs/MSALoginDialog.h | 7 +- launcher/ui/dialogs/MSALoginDialog.ui | 41 ++++++------ launcher/ui/dialogs/OfflineLoginDialog.cpp | 2 +- 25 files changed, 160 insertions(+), 288 deletions(-) rename launcher/minecraft/auth/{flows => }/AuthFlow.cpp (70%) rename launcher/minecraft/auth/{flows => }/AuthFlow.h (80%) delete mode 100644 launcher/minecraft/auth/flows/MSA.cpp delete mode 100644 launcher/minecraft/auth/flows/MSA.h delete mode 100644 launcher/minecraft/auth/flows/Offline.cpp delete mode 100644 launcher/minecraft/auth/flows/Offline.h delete mode 100644 launcher/minecraft/auth/steps/OfflineStep.cpp delete mode 100644 launcher/minecraft/auth/steps/OfflineStep.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e5eb4b7338..e63328e6e1 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -217,15 +217,9 @@ set(MINECRAFT_SOURCES minecraft/auth/Parsers.cpp minecraft/auth/Parsers.h - minecraft/auth/flows/AuthFlow.cpp - minecraft/auth/flows/AuthFlow.h - minecraft/auth/flows/MSA.cpp - minecraft/auth/flows/MSA.h - minecraft/auth/flows/Offline.cpp - minecraft/auth/flows/Offline.h - - minecraft/auth/steps/OfflineStep.cpp - minecraft/auth/steps/OfflineStep.h + minecraft/auth/AuthFlow.cpp + minecraft/auth/AuthFlow.h + minecraft/auth/steps/EntitlementsStep.cpp minecraft/auth/steps/EntitlementsStep.h minecraft/auth/steps/GetSkinStep.cpp diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index e1f1e9b1ec..fd20820357 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -42,7 +42,7 @@ #include namespace { -void tokenToJSONV3(QJsonObject& parent, Katabasis::Token t, const char* tokenName) +void tokenToJSONV3(QJsonObject& parent, Token t, const char* tokenName) { if (!t.persistent) { return; @@ -74,9 +74,9 @@ void tokenToJSONV3(QJsonObject& parent, Katabasis::Token t, const char* tokenNam } } -Katabasis::Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenName) +Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenName) { - Katabasis::Token out; + Token out; auto tokenObject = parent.value(tokenName).toObject(); if (tokenObject.isEmpty()) { return out; @@ -94,7 +94,7 @@ Katabasis::Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenNam auto token = tokenObject.value("token"); if (token.isString()) { out.token = token.toString(); - out.validity = Katabasis::Validity::Assumed; + out.validity = Validity::Assumed; } auto refresh_token = tokenObject.value("refresh_token"); @@ -241,13 +241,13 @@ MinecraftProfile profileFromJSONV3(const QJsonObject& parent, const char* tokenN } } } - out.validity = Katabasis::Validity::Assumed; + out.validity = Validity::Assumed; return out; } void entitlementToJSONV3(QJsonObject& parent, MinecraftEntitlement p) { - if (p.validity == Katabasis::Validity::None) { + if (p.validity == Validity::None) { return; } QJsonObject out; @@ -271,7 +271,7 @@ bool entitlementFromJSONV3(const QJsonObject& parent, MinecraftEntitlement& out) } out.canPlayMinecraft = canPlayMinecraftV.toBool(false); out.ownsMinecraft = ownsMinecraftV.toBool(false); - out.validity = Katabasis::Validity::Assumed; + out.validity = Validity::Assumed; } return true; } @@ -313,10 +313,10 @@ bool AccountData::resumeStateFromV3(QJsonObject data) minecraftProfile = profileFromJSONV3(data, "profile"); if (!entitlementFromJSONV3(data, minecraftEntitlement)) { - if (minecraftProfile.validity != Katabasis::Validity::None) { + if (minecraftProfile.validity != Validity::None) { minecraftEntitlement.canPlayMinecraft = true; minecraftEntitlement.ownsMinecraft = true; - minecraftEntitlement.validity = Katabasis::Validity::Assumed; + minecraftEntitlement.validity = Validity::Assumed; } } diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index 71f1d00b2e..1ada4e38a6 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -44,18 +44,6 @@ #include #include -namespace Katabasis { -enum class Activity { - Idle, - LoggingIn, - LoggingOut, - Refreshing, - FailedSoft, //!< soft failure. this generally means the user auth details haven't been invalidated - FailedHard, //!< hard failure. auth is invalid - FailedGone, //!< hard failure. auth is invalid, and the account no longer exists - Succeeded -}; - enum class Validity { None, Assumed, Certain }; struct Token { @@ -69,8 +57,6 @@ struct Token { bool persistent = true; }; -} // namespace Katabasis - struct Skin { QString id; QString url; @@ -90,7 +76,7 @@ struct Cape { struct MinecraftEntitlement { bool ownsMinecraft = false; bool canPlayMinecraft = false; - Katabasis::Validity validity = Katabasis::Validity::None; + Validity validity = Validity::None; }; struct MinecraftProfile { @@ -99,7 +85,7 @@ struct MinecraftProfile { Skin skin; QString currentCape; QMap capes; - Katabasis::Validity validity = Katabasis::Validity::None; + Validity validity = Validity::None; }; enum class AccountType { MSA, Offline }; @@ -124,15 +110,15 @@ struct AccountData { AccountType type = AccountType::MSA; QString msaClientID; - Katabasis::Token msaToken; - Katabasis::Token userToken; - Katabasis::Token xboxApiToken; - Katabasis::Token mojangservicesToken; + Token msaToken; + Token userToken; + Token xboxApiToken; + Token mojangservicesToken; - Katabasis::Token yggdrasilToken; + Token yggdrasilToken; MinecraftProfile minecraftProfile; MinecraftEntitlement minecraftEntitlement; - Katabasis::Validity validity_ = Katabasis::Validity::None; + Validity validity_ = Validity::None; // runtime only information (not saved with the account) QString internalId; diff --git a/launcher/minecraft/auth/AccountList.h b/launcher/minecraft/auth/AccountList.h index b6038edb7d..d3be6740e5 100644 --- a/launcher/minecraft/auth/AccountList.h +++ b/launcher/minecraft/auth/AccountList.h @@ -36,7 +36,7 @@ #pragma once #include "MinecraftAccount.h" -#include "minecraft/auth/flows/AuthFlow.h" +#include "minecraft/auth/AuthFlow.h" #include #include diff --git a/launcher/minecraft/auth/flows/AuthFlow.cpp b/launcher/minecraft/auth/AuthFlow.cpp similarity index 70% rename from launcher/minecraft/auth/flows/AuthFlow.cpp rename to launcher/minecraft/auth/AuthFlow.cpp index 1b5e015695..c98d0b2c92 100644 --- a/launcher/minecraft/auth/flows/AuthFlow.cpp +++ b/launcher/minecraft/auth/AuthFlow.cpp @@ -3,27 +3,47 @@ #include #include +#include "minecraft/auth/AccountData.h" +#include "minecraft/auth/steps/EntitlementsStep.h" +#include "minecraft/auth/steps/GetSkinStep.h" +#include "minecraft/auth/steps/LauncherLoginStep.h" +#include "minecraft/auth/steps/MSAStep.h" +#include "minecraft/auth/steps/MinecraftProfileStep.h" +#include "minecraft/auth/steps/XboxAuthorizationStep.h" +#include "minecraft/auth/steps/XboxProfileStep.h" +#include "minecraft/auth/steps/XboxUserStep.h" + #include "AuthFlow.h" #include -AuthFlow::AuthFlow(AccountData* data, QObject* parent) : Task(parent), m_data(data) +AuthFlow::AuthFlow(AccountData* data, bool silent, QObject* parent) : Task(parent), m_data(data) { + if (data->type == AccountType::MSA) { + auto oauthStep = makeShared(m_data, silent); + connect(oauthStep.get(), &MSAStep::authorizeWithBrowser, this, &AuthFlow::authorizeWithBrowser); + m_steps.append(oauthStep); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox")); + m_steps.append( + makeShared(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang")); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); + } changeState(AccountTaskState::STATE_CREATED); } void AuthFlow::succeed() { - m_data->validity_ = Katabasis::Validity::Certain; + m_data->validity_ = Validity::Certain; changeState(AccountTaskState::STATE_SUCCEEDED, tr("Finished all authentication steps")); } void AuthFlow::executeTask() { - if (m_currentStep) { - emitFailed("No task"); - return; - } changeState(AccountTaskState::STATE_WORKING, tr("Initializing")); nextStep(); } @@ -46,9 +66,8 @@ void AuthFlow::nextStep() void AuthFlow::stepFinished(AccountTaskState resultingState, QString message) { - if (changeState(resultingState, message)) { + if (changeState(resultingState, message)) nextStep(); - } } bool AuthFlow::changeState(AccountTaskState newState, QString reason) diff --git a/launcher/minecraft/auth/flows/AuthFlow.h b/launcher/minecraft/auth/AuthFlow.h similarity index 80% rename from launcher/minecraft/auth/flows/AuthFlow.h rename to launcher/minecraft/auth/AuthFlow.h index de563c3c55..611c250581 100644 --- a/launcher/minecraft/auth/flows/AuthFlow.h +++ b/launcher/minecraft/auth/AuthFlow.h @@ -15,30 +15,26 @@ class AuthFlow : public Task { Q_OBJECT public: - explicit AuthFlow(AccountData* data, QObject* parent = 0); + explicit AuthFlow(AccountData* data, bool silent = false, QObject* parent = 0); virtual ~AuthFlow() = default; - Katabasis::Validity validity() { return m_data->validity_; }; - void executeTask() override; AccountTaskState taskState() { return m_taskState; } signals: - void activityChanged(Katabasis::Activity activity); + void authorizeWithBrowser(const QUrl& url); protected: void succeed(); void nextStep(); - protected slots: + private slots: // NOTE: true -> non-terminal state, false -> terminal state bool changeState(AccountTaskState newState, QString reason = QString()); - - private slots: void stepFinished(AccountTaskState resultingState, QString message); - protected: + private: AccountTaskState m_taskState = AccountTaskState::STATE_CREATED; QList m_steps; AuthStep::Ptr m_currentStep; diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index d927cd6b4f..f8a43900b2 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -50,11 +50,8 @@ #include -#include "flows/MSA.h" -#include "flows/Offline.h" #include "minecraft/auth/AccountData.h" -#include "minecraft/auth/flows/AuthFlow.h" -#include "tasks/Task.h" +#include "minecraft/auth/AuthFlow.h" MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) { @@ -82,7 +79,7 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString& username) auto account = makeShared(); account->data.type = AccountType::Offline; account->data.yggdrasilToken.token = "0"; - account->data.yggdrasilToken.validity = Katabasis::Validity::Certain; + account->data.yggdrasilToken.validity = Validity::Certain; account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); account->data.yggdrasilToken.extra["userName"] = username; account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); @@ -90,7 +87,7 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString& username) account->data.minecraftEntitlement.canPlayMinecraft = true; account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(QRegularExpression("[{}-]")); account->data.minecraftProfile.name = username; - account->data.minecraftProfile.validity = Katabasis::Validity::Certain; + account->data.minecraftProfile.validity = Validity::Certain; return account; } @@ -122,23 +119,11 @@ QPixmap MinecraftAccount::getFace() const return skin.scaled(64, 64, Qt::KeepAspectRatio); } -shared_qobject_ptr MinecraftAccount::loginMSA() +shared_qobject_ptr MinecraftAccount::login() { Q_ASSERT(m_currentTask.get() == nullptr); - m_currentTask.reset(new MSAInteractive(&data)); - connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); - connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); - connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); }); - emit activityChanged(true); - return m_currentTask; -} - -shared_qobject_ptr MinecraftAccount::loginOffline() -{ - Q_ASSERT(m_currentTask.get() == nullptr); - - m_currentTask.reset(new OfflineLogin(&data)); + m_currentTask.reset(new AuthFlow(&data, false, this)); connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); }); @@ -152,11 +137,7 @@ shared_qobject_ptr MinecraftAccount::refresh() return m_currentTask; } - if (data.type == AccountType::MSA) { - m_currentTask.reset(new MSASilent(&data)); - } else { - m_currentTask.reset(new OfflineLogin(&data)); - } + m_currentTask.reset(new AuthFlow(&data, true, this)); connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); @@ -191,17 +172,17 @@ void MinecraftAccount::authFailed(QString reason) if (accountType() == AccountType::MSA) { data.msaToken.token = QString(); data.msaToken.refresh_token = QString(); - data.msaToken.validity = Katabasis::Validity::None; - data.validity_ = Katabasis::Validity::None; + data.msaToken.validity = Validity::None; + data.validity_ = Validity::None; } else { data.yggdrasilToken.token = QString(); - data.yggdrasilToken.validity = Katabasis::Validity::None; - data.validity_ = Katabasis::Validity::None; + data.yggdrasilToken.validity = Validity::None; + data.validity_ = Validity::None; } emit changed(); } break; case AccountTaskState::STATE_FAILED_GONE: { - data.validity_ = Katabasis::Validity::None; + data.validity_ = Validity::None; emit changed(); } break; case AccountTaskState::STATE_CREATED: @@ -231,13 +212,13 @@ bool MinecraftAccount::shouldRefresh() const return false; } switch (data.validity_) { - case Katabasis::Validity::Certain: { + case Validity::Certain: { break; } - case Katabasis::Validity::None: { + case Validity::None: { return false; } - case Katabasis::Validity::Assumed: { + case Validity::Assumed: { return true; } } diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index 24600cb73c..6f540c572d 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -47,7 +47,7 @@ #include "AuthSession.h" #include "QObjectPtr.h" #include "Usable.h" -#include "minecraft/auth/flows/AuthFlow.h" +#include "minecraft/auth/AuthFlow.h" class Task; class MinecraftAccount; @@ -95,9 +95,7 @@ class MinecraftAccount : public QObject, public Usable { QJsonObject saveToJson() const; public: /* manipulation */ - shared_qobject_ptr loginMSA(); - - shared_qobject_ptr loginOffline(); + shared_qobject_ptr login(); shared_qobject_ptr refresh(); diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp index f6179a93eb..a2b97e2786 100644 --- a/launcher/minecraft/auth/Parsers.cpp +++ b/launcher/minecraft/auth/Parsers.cpp @@ -79,7 +79,7 @@ bool getBool(QJsonValue value, bool& out) // 2148916238 = child account not linked to a family */ -bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString name) +bool parseXTokenResponse(QByteArray& data, Token& output, QString name) { qDebug() << "Parsing" << name << ":"; qCDebug(authCredentials()) << data; @@ -135,7 +135,7 @@ bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString nam qWarning() << "Missing uhs"; return false; } - output.validity = Katabasis::Validity::Certain; + output.validity = Validity::Certain; qDebug() << name << "is valid."; return true; } @@ -213,7 +213,7 @@ bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output) output.capes[capeOut.id] = capeOut; } output.currentCape = currentCape; - output.validity = Katabasis::Validity::Certain; + output.validity = Validity::Certain; return true; } @@ -388,7 +388,7 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output) output.currentCape = capeOut.alias; } - output.validity = Katabasis::Validity::Certain; + output.validity = Validity::Certain; return true; } @@ -422,7 +422,7 @@ bool parseMinecraftEntitlements(QByteArray& data, MinecraftEntitlement& output) output.ownsMinecraft = true; } } - output.validity = Katabasis::Validity::Certain; + output.validity = Validity::Certain; return true; } @@ -456,7 +456,7 @@ bool parseRolloutResponse(QByteArray& data, bool& result) return true; } -bool parseMojangResponse(QByteArray& data, Katabasis::Token& output) +bool parseMojangResponse(QByteArray& data, Token& output) { QJsonParseError jsonError; qDebug() << "Parsing Mojang response..."; @@ -488,7 +488,7 @@ bool parseMojangResponse(QByteArray& data, Katabasis::Token& output) qWarning() << "access_token is not valid"; return false; } - output.validity = Katabasis::Validity::Certain; + output.validity = Validity::Certain; qDebug() << "Mojang response is valid."; return true; } diff --git a/launcher/minecraft/auth/Parsers.h b/launcher/minecraft/auth/Parsers.h index d073f9994d..4a235e4c2f 100644 --- a/launcher/minecraft/auth/Parsers.h +++ b/launcher/minecraft/auth/Parsers.h @@ -9,8 +9,8 @@ bool getNumber(QJsonValue value, double& out); bool getNumber(QJsonValue value, int64_t& out); bool getBool(QJsonValue value, bool& out); -bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString name); -bool parseMojangResponse(QByteArray& data, Katabasis::Token& output); +bool parseXTokenResponse(QByteArray& data, Token& output, QString name); +bool parseMojangResponse(QByteArray& data, Token& output); bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output); bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output); diff --git a/launcher/minecraft/auth/flows/MSA.cpp b/launcher/minecraft/auth/flows/MSA.cpp deleted file mode 100644 index f0399342ec..0000000000 --- a/launcher/minecraft/auth/flows/MSA.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "MSA.h" - -#include "minecraft/auth/steps/EntitlementsStep.h" -#include "minecraft/auth/steps/GetSkinStep.h" -#include "minecraft/auth/steps/LauncherLoginStep.h" -#include "minecraft/auth/steps/MSAStep.h" -#include "minecraft/auth/steps/MinecraftProfileStep.h" -#include "minecraft/auth/steps/XboxAuthorizationStep.h" -#include "minecraft/auth/steps/XboxProfileStep.h" -#include "minecraft/auth/steps/XboxUserStep.h" - -MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent) -{ - m_steps.append(makeShared(m_data, MSAStep::Action::Refresh)); - m_steps.append(makeShared(m_data)); - m_steps.append(makeShared(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox")); - m_steps.append(makeShared(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang")); - m_steps.append(makeShared(m_data)); - m_steps.append(makeShared(m_data)); - m_steps.append(makeShared(m_data)); - m_steps.append(makeShared(m_data)); - m_steps.append(makeShared(m_data)); -} - -MSAInteractive::MSAInteractive(AccountData* data, QObject* parent) : AuthFlow(data, parent) -{ - m_steps.append(makeShared(m_data, MSAStep::Action::Login)); - m_steps.append(makeShared(m_data)); - m_steps.append(makeShared(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox")); - m_steps.append(makeShared(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang")); - m_steps.append(makeShared(m_data)); - m_steps.append(makeShared(m_data)); - m_steps.append(makeShared(m_data)); - m_steps.append(makeShared(m_data)); - m_steps.append(makeShared(m_data)); -} diff --git a/launcher/minecraft/auth/flows/MSA.h b/launcher/minecraft/auth/flows/MSA.h deleted file mode 100644 index e403d530f0..0000000000 --- a/launcher/minecraft/auth/flows/MSA.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include "AuthFlow.h" - -class MSAInteractive : public AuthFlow { - Q_OBJECT - public: - explicit MSAInteractive(AccountData* data, QObject* parent = 0); -}; - -class MSASilent : public AuthFlow { - Q_OBJECT - public: - explicit MSASilent(AccountData* data, QObject* parent = 0); -}; diff --git a/launcher/minecraft/auth/flows/Offline.cpp b/launcher/minecraft/auth/flows/Offline.cpp deleted file mode 100644 index 1836533bdc..0000000000 --- a/launcher/minecraft/auth/flows/Offline.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "Offline.h" - -#include "minecraft/auth/steps/OfflineStep.h" - -OfflineLogin::OfflineLogin(AccountData* data, QObject* parent) : AuthFlow(data, parent) -{ - m_steps.append(makeShared(m_data)); -} diff --git a/launcher/minecraft/auth/flows/Offline.h b/launcher/minecraft/auth/flows/Offline.h deleted file mode 100644 index a8d378e16f..0000000000 --- a/launcher/minecraft/auth/flows/Offline.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include "AuthFlow.h" - -class OfflineLogin : public AuthFlow { - Q_OBJECT - public: - explicit OfflineLogin(AccountData* data, QObject* parent = 0); -}; diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp index 79cb062b68..4d3c2d2025 100644 --- a/launcher/minecraft/auth/steps/MSAStep.cpp +++ b/launcher/minecraft/auth/steps/MSAStep.cpp @@ -37,12 +37,11 @@ #include #include -#include #include #include "Application.h" -MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action) +MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent) { m_clientId = APPLICATION->getMSAClientID(); @@ -63,7 +62,7 @@ MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(ac m_data->msaToken.token = oauth2.token(); emit finished(AccountTaskState::STATE_WORKING, tr("Got ")); }); - connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &QDesktopServices::openUrl); + connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &MSAStep::authorizeWithBrowser); connect(&oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this](const QAbstractOAuth2::Error err) { emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication failed.")); }); @@ -82,30 +81,25 @@ QString MSAStep::describe() void MSAStep::perform() { - switch (m_action) { - case Refresh: { - if (m_data->msaClientID != m_clientId) { - emit finished(AccountTaskState::STATE_DISABLED, - tr("Microsoft user authentication failed - client identification has changed.")); - } - oauth2.setRefreshToken(m_data->msaToken.refresh_token); - oauth2.refreshAccessToken(); - return; + if (m_silent) { + if (m_data->msaClientID != m_clientId) { + emit finished(AccountTaskState::STATE_DISABLED, + tr("Microsoft user authentication failed - client identification has changed.")); } - case Login: { + oauth2.setRefreshToken(m_data->msaToken.refresh_token); + oauth2.refreshAccessToken(); + } else { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0 - oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMultiMap* map) { + oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMultiMap* map) { #else - oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMap* map) { + oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMap* map) { #endif - map->insert("prompt", "select_account"); - map->insert("cobrandid", "8058f65d-ce06-4c30-9559-473c9275a65d"); - }); + map->insert("prompt", "select_account"); + map->insert("cobrandid", "8058f65d-ce06-4c30-9559-473c9275a65d"); + }); - *m_data = AccountData(); - m_data->msaClientID = m_clientId; - oauth2.grant(); - return; - } + *m_data = AccountData(); + m_data->msaClientID = m_clientId; + oauth2.grant(); } } diff --git a/launcher/minecraft/auth/steps/MSAStep.h b/launcher/minecraft/auth/steps/MSAStep.h index f38f5c70eb..675cfb2ca5 100644 --- a/launcher/minecraft/auth/steps/MSAStep.h +++ b/launcher/minecraft/auth/steps/MSAStep.h @@ -42,18 +42,18 @@ class MSAStep : public AuthStep { Q_OBJECT public: - enum Action { Refresh, Login }; - - public: - explicit MSAStep(AccountData* data, Action action); + explicit MSAStep(AccountData* data, bool silent = false); virtual ~MSAStep() noexcept = default; void perform() override; QString describe() override; + signals: + void authorizeWithBrowser(const QUrl& url); + private: - Action m_action; + bool m_silent; QString m_clientId; QOAuth2AuthorizationCodeFlow oauth2; }; diff --git a/launcher/minecraft/auth/steps/OfflineStep.cpp b/launcher/minecraft/auth/steps/OfflineStep.cpp deleted file mode 100644 index a96b08377d..0000000000 --- a/launcher/minecraft/auth/steps/OfflineStep.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "OfflineStep.h" - -OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {} - -QString OfflineStep::describe() -{ - return tr("Creating offline account."); -} - -void OfflineStep::perform() -{ - emit finished(AccountTaskState::STATE_WORKING, tr("Created offline account.")); -} diff --git a/launcher/minecraft/auth/steps/OfflineStep.h b/launcher/minecraft/auth/steps/OfflineStep.h deleted file mode 100644 index 411879b105..0000000000 --- a/launcher/minecraft/auth/steps/OfflineStep.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once -#include - -#include "minecraft/auth/AuthStep.h" - -class OfflineStep : public AuthStep { - Q_OBJECT - public: - explicit OfflineStep(AccountData* data); - virtual ~OfflineStep() noexcept = default; - - void perform() override; - - QString describe() override; -}; diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp index 2ae3af0dd5..f072209865 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -11,7 +11,7 @@ #include "net/StaticHeaderProxy.h" #include "net/Upload.h" -XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind) +XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind) : AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind) {} @@ -72,7 +72,7 @@ void XboxAuthorizationStep::onRequestDone() return; } - Katabasis::Token temp; + Token temp; if (!Parsers::parseXTokenResponse(*m_response, temp, m_authorizationKind)) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind)); diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.h b/launcher/minecraft/auth/steps/XboxAuthorizationStep.h index eb7097f6f4..f6329b7f07 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.h +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.h @@ -9,7 +9,7 @@ class XboxAuthorizationStep : public AuthStep { Q_OBJECT public: - explicit XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind); + explicit XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind); virtual ~XboxAuthorizationStep() noexcept = default; void perform() override; @@ -23,7 +23,7 @@ class XboxAuthorizationStep : public AuthStep { void onRequestDone(); private: - Katabasis::Token* m_token; + Token* m_token; QString m_relyingParty; QString m_authorizationKind; diff --git a/launcher/minecraft/auth/steps/XboxUserStep.cpp b/launcher/minecraft/auth/steps/XboxUserStep.cpp index 46c3f0365b..c9453dba14 100644 --- a/launcher/minecraft/auth/steps/XboxUserStep.cpp +++ b/launcher/minecraft/auth/steps/XboxUserStep.cpp @@ -60,7 +60,7 @@ void XboxUserStep::onRequestDone() return; } - Katabasis::Token temp; + Token temp; if (!Parsers::parseXTokenResponse(*m_response, temp, "UToken")) { qWarning() << "Could not parse user authentication response..."; emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication response could not be understood.")); diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index b249346a41..33df1876ea 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -37,7 +37,7 @@ #include "ui_MSALoginDialog.h" #include "DesktopServices.h" -#include "minecraft/auth/flows/AuthFlow.h" +#include "minecraft/auth/AuthFlow.h" #include #include @@ -47,26 +47,24 @@ MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MSALoginDialog) { ui->setupUi(this); - ui->progressBar->setVisible(false); - ui->actionButton->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); + ui->cancel->setEnabled(false); + ui->link->setVisible(false); + ui->copy->setVisible(false); + + connect(ui->cancel, &QPushButton::pressed, this, &QDialog::reject); + connect(ui->copy, &QPushButton::pressed, this, &MSALoginDialog::copyUrl); } int MSALoginDialog::exec() { - setUserInputsEnabled(false); - ui->progressBar->setVisible(true); - // Setup the login task and start it m_account = MinecraftAccount::createBlankMSA(); - m_loginTask = m_account->loginMSA(); + m_loginTask = m_account->login(); connect(m_loginTask.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed); 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(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser); m_loginTask->start(); return QDialog::exec(); @@ -77,11 +75,6 @@ MSALoginDialog::~MSALoginDialog() delete ui; } -void MSALoginDialog::setUserInputsEnabled(bool enable) -{ - ui->buttonBox->setEnabled(enable); -} - void MSALoginDialog::onTaskFailed(const QString& reason) { // Set message @@ -94,12 +87,7 @@ void MSALoginDialog::onTaskFailed(const QString& reason) processed += "
    "; } } - ui->label->setText(processed); - - // Re-enable user-interaction - setUserInputsEnabled(true); - ui->progressBar->setVisible(false); - ui->actionButton->setVisible(false); + ui->message->setText(processed); } void MSALoginDialog::onTaskSucceeded() @@ -109,22 +97,38 @@ void MSALoginDialog::onTaskSucceeded() void MSALoginDialog::onTaskStatus(const QString& status) { - ui->label->setText(status); -} - -void MSALoginDialog::onTaskProgress(qint64 current, qint64 total) -{ - ui->progressBar->setMaximum(total); - ui->progressBar->setValue(current); + ui->message->setText(status); + ui->cancel->setEnabled(false); + ui->link->setVisible(false); + ui->copy->setVisible(false); } // Public interface MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent, QString msg) { MSALoginDialog dlg(parent); - dlg.ui->label->setText(msg); + dlg.ui->message->setText(msg); if (dlg.exec() == QDialog::Accepted) { return dlg.m_account; } return nullptr; } + +void MSALoginDialog::authorizeWithBrowser(const QUrl& url) +{ + ui->cancel->setEnabled(true); + ui->link->setVisible(true); + ui->copy->setVisible(true); + DesktopServices::openUrl(url); + ui->link->setText(url.toDisplayString()); + ui->message->setText( + tr("Browser opened to complete the login process." + "

    " + "If your browser hasn't opened, please manually open the bellow link in your browser:")); +} + +void MSALoginDialog::copyUrl() +{ + QClipboard* cb = QApplication::clipboard(); + cb->setText(ui->link->text()); +} diff --git a/launcher/ui/dialogs/MSALoginDialog.h b/launcher/ui/dialogs/MSALoginDialog.h index f14e04776d..b57b83ec2d 100644 --- a/launcher/ui/dialogs/MSALoginDialog.h +++ b/launcher/ui/dialogs/MSALoginDialog.h @@ -18,8 +18,8 @@ #include #include +#include "minecraft/auth/AuthFlow.h" #include "minecraft/auth/MinecraftAccount.h" -#include "minecraft/auth/flows/AuthFlow.h" namespace Ui { class MSALoginDialog; @@ -37,13 +37,12 @@ class MSALoginDialog : public QDialog { private: explicit MSALoginDialog(QWidget* parent = 0); - void setUserInputsEnabled(bool enable); - protected slots: void onTaskFailed(const QString& reason); void onTaskSucceeded(); void onTaskStatus(const QString& status); - void onTaskProgress(qint64 current, qint64 total); + void authorizeWithBrowser(const QUrl& url); + void copyUrl(); private: Ui::MSALoginDialog* ui; diff --git a/launcher/ui/dialogs/MSALoginDialog.ui b/launcher/ui/dialogs/MSALoginDialog.ui index c18d01a166..e6c9f1aad8 100644 --- a/launcher/ui/dialogs/MSALoginDialog.ui +++ b/launcher/ui/dialogs/MSALoginDialog.ui @@ -21,11 +21,9 @@
    - + - Message label placeholder. - -aaaaa + Qt::RichText @@ -39,36 +37,33 @@ aaaaa - - - 24 - - - false - - - - - + - - - Open page and copy code + + + false - - - Qt::Horizontal + + + - - QDialogButtonBox::Cancel + + + + + + Cancel + + + diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp index cd31021356..b9d1c29153 100644 --- a/launcher/ui/dialogs/OfflineLoginDialog.cpp +++ b/launcher/ui/dialogs/OfflineLoginDialog.cpp @@ -26,7 +26,7 @@ void OfflineLoginDialog::accept() // Setup the login task and start it m_account = MinecraftAccount::createOffline(ui->userTextBox->text()); - m_loginTask = m_account->loginOffline(); + m_loginTask = m_account->login(); connect(m_loginTask.get(), &Task::failed, this, &OfflineLoginDialog::onTaskFailed); connect(m_loginTask.get(), &Task::succeeded, this, &OfflineLoginDialog::onTaskSucceeded); connect(m_loginTask.get(), &Task::status, this, &OfflineLoginDialog::onTaskStatus); From 8b051b97fd671d7a04ece2e5df580b3099bb90a3 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 15 May 2024 17:54:35 +0300 Subject: [PATCH 0202/2054] Fix CI2 Signed-off-by: Trial97 --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 261f67819d..5255f865b7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -23,7 +23,7 @@ jobs: run: sudo apt-get -y update - sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 libqt5networkauth5 + sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 libqt5networkauth5 libqt5networkauth5-dev - name: Configure and Build run: | From 1d384544d36fdc31b137827b1fc6e0ca62a350cb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 May 2024 19:40:15 +0000 Subject: [PATCH 0203/2054] chore(deps): update cachix/install-nix-action action to v27 --- .github/workflows/update-flake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 855b105eab..a923edd1d1 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26 + - uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b # v27 - uses: DeterminateSystems/update-flake-lock@v21 with: From 898ee67a07aa62ff01c0224d7d7dc1c7658eee22 Mon Sep 17 00:00:00 2001 From: Alexandru Ionut Tripon Date: Thu, 16 May 2024 00:41:07 +0300 Subject: [PATCH 0204/2054] Update launcher/ui/dialogs/MSALoginDialog.cpp Co-authored-by: Tayou Signed-off-by: Alexandru Ionut Tripon --- launcher/ui/dialogs/MSALoginDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index 33df1876ea..b36e8f9b3e 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -124,7 +124,7 @@ void MSALoginDialog::authorizeWithBrowser(const QUrl& url) ui->message->setText( tr("Browser opened to complete the login process." "

    " - "If your browser hasn't opened, please manually open the bellow link in your browser:")); + "If your browser hasn't opened, please manually open the below link in your browser:")); } void MSALoginDialog::copyUrl() From c15f6fcd2a8a380b59f7f49cae5010cc93efd386 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 16 May 2024 17:21:30 +0300 Subject: [PATCH 0205/2054] Fix portable if manifest is missing Signed-off-by: Trial97 --- launcher/updater/prismupdater/PrismUpdater.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 5fe22bdd0d..e9c75e0806 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -474,8 +474,7 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar target_dir = QDir(m_rootPath).absoluteFilePath(".."); } - QMetaObject::invokeMethod( - this, [this, target_dir]() { moveAndFinishUpdate(target_dir); }, Qt::QueuedConnection); + QMetaObject::invokeMethod(this, [this, target_dir]() { moveAndFinishUpdate(target_dir); }, Qt::QueuedConnection); } else { QMetaObject::invokeMethod(this, &PrismUpdaterApp::loadReleaseList, Qt::QueuedConnection); @@ -1118,7 +1117,6 @@ void PrismUpdaterApp::backupAppDir() "Qt*.dll", }); } - file_list.append("portable.txt"); logUpdate("manifest.txt empty or missing. making best guess at files to back up."); } logUpdate(tr("Backing up:\n %1").arg(file_list.join(",\n "))); From 2a58fb0ac51ebe1a1aa1b81658b4f0334f81cfa8 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 16 May 2024 17:31:25 +0300 Subject: [PATCH 0206/2054] Remove cobrandid Signed-off-by: Trial97 --- launcher/minecraft/auth/steps/MSAStep.cpp | 1 - launcher/net/NetRequest.cpp | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp index 4d3c2d2025..3c55540dce 100644 --- a/launcher/minecraft/auth/steps/MSAStep.cpp +++ b/launcher/minecraft/auth/steps/MSAStep.cpp @@ -95,7 +95,6 @@ void MSAStep::perform() oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMap* map) { #endif map->insert("prompt", "select_account"); - map->insert("cobrandid", "8058f65d-ce06-4c30-9559-473c9275a65d"); }); *m_data = AccountData(); diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index 55a4f185c3..cfd93b61f8 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -37,11 +37,11 @@ */ #include "NetRequest.h" -#include -#include #include #include +#include +#include #include #if defined(LAUNCHER_APPLICATION) From 051e7886837090ba62ad81b6f835372649a93b69 Mon Sep 17 00:00:00 2001 From: Alexandru Ionut Tripon Date: Thu, 16 May 2024 19:45:00 +0300 Subject: [PATCH 0207/2054] Update launcher/updater/prismupdater/PrismUpdater.cpp Signed-off-by: Alexandru Ionut Tripon --- launcher/updater/prismupdater/PrismUpdater.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index e9c75e0806..054206c67d 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -474,7 +474,8 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar target_dir = QDir(m_rootPath).absoluteFilePath(".."); } - QMetaObject::invokeMethod(this, [this, target_dir]() { moveAndFinishUpdate(target_dir); }, Qt::QueuedConnection); + QMetaObject::invokeMethod( + this, [this, target_dir]() { moveAndFinishUpdate(target_dir); }, Qt::QueuedConnection);Qt::QueuedConnection); } else { QMetaObject::invokeMethod(this, &PrismUpdaterApp::loadReleaseList, Qt::QueuedConnection); From ab5f628453de6fc9301f73413027846451a78bd0 Mon Sep 17 00:00:00 2001 From: Alexandru Ionut Tripon Date: Thu, 16 May 2024 19:45:43 +0300 Subject: [PATCH 0208/2054] Update launcher/updater/prismupdater/PrismUpdater.cpp Signed-off-by: Alexandru Ionut Tripon --- launcher/updater/prismupdater/PrismUpdater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 054206c67d..f3cc2ad56b 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -475,7 +475,7 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar } QMetaObject::invokeMethod( - this, [this, target_dir]() { moveAndFinishUpdate(target_dir); }, Qt::QueuedConnection);Qt::QueuedConnection); + this, [this, target_dir]() { moveAndFinishUpdate(target_dir); }, Qt::QueuedConnection); } else { QMetaObject::invokeMethod(this, &PrismUpdaterApp::loadReleaseList, Qt::QueuedConnection); From 09c0c11033756e7fd18b5bb51d4427f6cf793b42 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 16 May 2024 21:58:25 +0300 Subject: [PATCH 0209/2054] Add back device code flow Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 2 + launcher/minecraft/auth/AuthFlow.cpp | 17 +- launcher/minecraft/auth/AuthFlow.h | 5 +- launcher/minecraft/auth/MinecraftAccount.cpp | 6 +- launcher/minecraft/auth/MinecraftAccount.h | 2 +- .../auth/steps/MSADeviceCodeStep.cpp | 272 ++++++++++++++++++ .../minecraft/auth/steps/MSADeviceCodeStep.h | 76 +++++ launcher/net/ByteArraySink.h | 4 - launcher/net/NetRequest.cpp | 11 +- launcher/net/Upload.cpp | 3 +- launcher/ui/dialogs/MSALoginDialog.cpp | 61 +++- launcher/ui/dialogs/MSALoginDialog.h | 13 +- launcher/ui/dialogs/MSALoginDialog.ui | 32 ++- launcher/ui/pages/global/AccountListPage.cpp | 13 +- 14 files changed, 481 insertions(+), 36 deletions(-) create mode 100644 launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp create mode 100644 launcher/minecraft/auth/steps/MSADeviceCodeStep.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e63328e6e1..833f994ff4 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -228,6 +228,8 @@ set(MINECRAFT_SOURCES minecraft/auth/steps/LauncherLoginStep.h minecraft/auth/steps/MinecraftProfileStep.cpp minecraft/auth/steps/MinecraftProfileStep.h + minecraft/auth/steps/MSADeviceCodeStep.cpp + minecraft/auth/steps/MSADeviceCodeStep.h minecraft/auth/steps/MSAStep.cpp minecraft/auth/steps/MSAStep.h minecraft/auth/steps/XboxAuthorizationStep.cpp diff --git a/launcher/minecraft/auth/AuthFlow.cpp b/launcher/minecraft/auth/AuthFlow.cpp index c98d0b2c92..5648fe9f63 100644 --- a/launcher/minecraft/auth/AuthFlow.cpp +++ b/launcher/minecraft/auth/AuthFlow.cpp @@ -7,22 +7,31 @@ #include "minecraft/auth/steps/EntitlementsStep.h" #include "minecraft/auth/steps/GetSkinStep.h" #include "minecraft/auth/steps/LauncherLoginStep.h" +#include "minecraft/auth/steps/MSADeviceCodeStep.h" #include "minecraft/auth/steps/MSAStep.h" #include "minecraft/auth/steps/MinecraftProfileStep.h" #include "minecraft/auth/steps/XboxAuthorizationStep.h" #include "minecraft/auth/steps/XboxProfileStep.h" #include "minecraft/auth/steps/XboxUserStep.h" +#include "tasks/Task.h" #include "AuthFlow.h" #include -AuthFlow::AuthFlow(AccountData* data, bool silent, QObject* parent) : Task(parent), m_data(data) +AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent) : Task(parent), m_data(data) { if (data->type == AccountType::MSA) { - auto oauthStep = makeShared(m_data, silent); - connect(oauthStep.get(), &MSAStep::authorizeWithBrowser, this, &AuthFlow::authorizeWithBrowser); - m_steps.append(oauthStep); + if (action == Action::DeviceCode) { + auto oauthStep = makeShared(m_data); + connect(oauthStep.get(), &MSADeviceCodeStep::authorizeWithBrowser, this, &AuthFlow::authorizeWithBrowserWithExtra); + connect(this, &Task::aborted, oauthStep.get(), &MSADeviceCodeStep::abort); + m_steps.append(oauthStep); + } else { + auto oauthStep = makeShared(m_data, action == Action::Refresh); + connect(oauthStep.get(), &MSAStep::authorizeWithBrowser, this, &AuthFlow::authorizeWithBrowser); + m_steps.append(oauthStep); + } m_steps.append(makeShared(m_data)); m_steps.append(makeShared(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox")); m_steps.append( diff --git a/launcher/minecraft/auth/AuthFlow.h b/launcher/minecraft/auth/AuthFlow.h index 611c250581..d99deec3c9 100644 --- a/launcher/minecraft/auth/AuthFlow.h +++ b/launcher/minecraft/auth/AuthFlow.h @@ -15,7 +15,9 @@ class AuthFlow : public Task { Q_OBJECT public: - explicit AuthFlow(AccountData* data, bool silent = false, QObject* parent = 0); + enum class Action { Refresh, Login, DeviceCode }; + + explicit AuthFlow(AccountData* data, Action action = Action::Refresh, QObject* parent = 0); virtual ~AuthFlow() = default; void executeTask() override; @@ -24,6 +26,7 @@ class AuthFlow : public Task { signals: void authorizeWithBrowser(const QUrl& url); + void authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn); protected: void succeed(); diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index f8a43900b2..3c7129d5fc 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -119,11 +119,11 @@ QPixmap MinecraftAccount::getFace() const return skin.scaled(64, 64, Qt::KeepAspectRatio); } -shared_qobject_ptr MinecraftAccount::login() +shared_qobject_ptr MinecraftAccount::login(bool useDeviceCode) { Q_ASSERT(m_currentTask.get() == nullptr); - m_currentTask.reset(new AuthFlow(&data, false, this)); + m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login, this)); connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); }); @@ -137,7 +137,7 @@ shared_qobject_ptr MinecraftAccount::refresh() return m_currentTask; } - m_currentTask.reset(new AuthFlow(&data, true, this)); + m_currentTask.reset(new AuthFlow(&data, AuthFlow::Action::Refresh, this)); connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index 6f540c572d..b5c623a26d 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -95,7 +95,7 @@ class MinecraftAccount : public QObject, public Usable { QJsonObject saveToJson() const; public: /* manipulation */ - shared_qobject_ptr login(); + shared_qobject_ptr login(bool useDeviceCode = false); shared_qobject_ptr refresh(); diff --git a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp new file mode 100644 index 0000000000..436ed58bf8 --- /dev/null +++ b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2024 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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 "MSADeviceCodeStep.h" +#include +#include +#include + +#include + +#include "Application.h" +#include "Json.h" +#include "net/StaticHeaderProxy.h" + +// https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code +MSADeviceCodeStep::MSADeviceCodeStep(AccountData* data) : AuthStep(data) +{ + m_clientId = APPLICATION->getMSAClientID(); +} + +QString MSADeviceCodeStep::describe() +{ + return tr("Logging in with Microsoft account(device code)."); +} + +void MSADeviceCodeStep::perform() +{ + QUrlQuery data; + data.addQueryItem("client_id", m_clientId); + data.addQueryItem("scope", "XboxLive.SignIn XboxLive.offline_access"); + auto payload = data.query(QUrl::FullyEncoded).toUtf8(); + QUrl url("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode"); + auto headers = QList{ + { "Content-Type", "application/x-www-form-urlencoded" }, + { "Accept", "application/json" }, + }; + m_response.reset(new QByteArray()); + m_task = Net::Upload::makeByteArray(url, m_response, payload); + m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers)); + + connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAutorizationFinished); + + m_task->setNetwork(APPLICATION->network()); + m_task->start(); +} + +struct DeviceAutorizationResponse { + QString device_code; + QString user_code; + QString verification_uri; + int expires_in; + int interval; + + QString error; + QString error_description; +}; + +DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& data) +{ + QJsonParseError err; + QJsonDocument doc = QJsonDocument::fromJson(data, &err); + if (err.error != QJsonParseError::NoError) { + qWarning() << "Failed to parse device autorization response due to err:" << err.errorString(); + return {}; + } + + if (!doc.isObject()) { + qWarning() << "Device autorization response is not an object"; + return {}; + } + auto obj = doc.object(); + return { + Json::ensureString(obj, "device_code"), Json::ensureString(obj, "user_code"), Json::ensureString(obj, "verification_uri"), + Json::ensureInteger(obj, "expires_in"), Json::ensureInteger(obj, "interval"), Json::ensureString(obj, "error"), + Json::ensureString(obj, "error_description"), + }; +} + +void MSADeviceCodeStep::deviceAutorizationFinished() +{ + auto rsp = parseDeviceAutorizationResponse(*m_response); + if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) { + qWarning() << "Device authorization failed:" << rsp.error; + emit finished(AccountTaskState::STATE_FAILED_HARD, + tr("Device authorization failed: %1").arg(rsp.error_description.isEmpty() ? rsp.error : rsp.error_description)); + return; + } + if (!m_task->wasSuccessful() || m_task->error() != QNetworkReply::NoError) { + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Failed to retrieve device authorization")); + qDebug() << *m_response; + return; + } + + if (rsp.device_code.isEmpty() || rsp.user_code.isEmpty() || rsp.verification_uri.isEmpty() || rsp.expires_in == 0) { + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Device authorization failed: required fields missing")); + return; + } + if (rsp.interval != 0) { + interval = rsp.interval; + } + m_device_code = rsp.device_code; + emit authorizeWithBrowser(rsp.verification_uri, rsp.user_code, rsp.expires_in); + m_expiration_timer.setTimerType(Qt::VeryCoarseTimer); + m_expiration_timer.setInterval(rsp.expires_in * 1000); + m_expiration_timer.setSingleShot(true); + connect(&m_expiration_timer, &QTimer::timeout, this, &MSADeviceCodeStep::abort); + m_expiration_timer.start(); + + m_pool_timer.setTimerType(Qt::VeryCoarseTimer); + m_pool_timer.setSingleShot(true); + startPoolTimer(); +} + +void MSADeviceCodeStep::abort() +{ + m_expiration_timer.stop(); + m_pool_timer.stop(); + if (m_task) { + m_task->abort(); + } + m_is_aborted = true; + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Task aborted")); +} + +void MSADeviceCodeStep::startPoolTimer() +{ + if (m_is_aborted) { + return; + } + m_pool_timer.setInterval(interval * 1000); + connect(&m_pool_timer, &QTimer::timeout, this, &MSADeviceCodeStep::authenticateUser); + m_pool_timer.start(); +} + +void MSADeviceCodeStep::authenticateUser() +{ + QUrlQuery data; + data.addQueryItem("client_id", m_clientId); + data.addQueryItem("grant_type", "urn:ietf:params:oauth:grant-type:device_code"); + data.addQueryItem("device_code", m_device_code); + auto payload = data.query(QUrl::FullyEncoded).toUtf8(); + QUrl url("https://login.microsoftonline.com/consumers/oauth2/v2.0/token"); + auto headers = QList{ + { "Content-Type", "application/x-www-form-urlencoded" }, + { "Accept", "application/json" }, + }; + m_response.reset(new QByteArray()); + m_task = Net::Upload::makeByteArray(url, m_response, payload); + m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers)); + + connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::authenticationFinished); + + m_task->setNetwork(APPLICATION->network()); + m_task->start(); +} + +struct AuthenticationResponse { + QString access_token; + QString token_type; + QString refresh_token; + int expires_in; + + QString error; + QString error_description; + + QVariantMap extra; +}; + +AuthenticationResponse parseAuthenticationResponse(const QByteArray& data) +{ + QJsonParseError err; + QJsonDocument doc = QJsonDocument::fromJson(data, &err); + if (err.error != QJsonParseError::NoError) { + qWarning() << "Failed to parse device autorization response due to err:" << err.errorString(); + return {}; + } + + if (!doc.isObject()) { + qWarning() << "Device autorization response is not an object"; + return {}; + } + auto obj = doc.object(); + return { Json::ensureString(obj, "access_token"), + Json::ensureString(obj, "token_type"), + Json::ensureString(obj, "refresh_token"), + Json::ensureInteger(obj, "expires_in"), + Json::ensureString(obj, "error"), + Json::ensureString(obj, "error_description"), + obj.toVariantMap() }; +} + +void MSADeviceCodeStep::authenticationFinished() +{ + if (m_task->error() == QNetworkReply::TimeoutError) { + // rfc8628#section-3.5 + // "On encountering a connection timeout, clients MUST unilaterally + // reduce their polling frequency before retrying. The use of an + // exponential backoff algorithm to achieve this, such as doubling the + // polling interval on each such connection timeout, is RECOMMENDED." + interval *= 2; + startPoolTimer(); + return; + } + auto rsp = parseAuthenticationResponse(*m_response); + if (rsp.error == "slow_down") { + // rfc8628#section-3.5 + // "A variant of 'authorization_pending', the authorization request is + // still pending and polling should continue, but the interval MUST + // be increased by 5 seconds for this and all subsequent requests." + interval += 5; + startPoolTimer(); + return; + } + if (rsp.error == "authorization_pending") { + // keep trying - rfc8628#section-3.5 + // "The authorization request is still pending as the end user hasn't + // yet completed the user-interaction steps (Section 3.3)." + startPoolTimer(); + return; + } + if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) { + qWarning() << "Device Access failed:" << rsp.error; + emit finished(AccountTaskState::STATE_FAILED_HARD, + tr("Device Access failed: %1").arg(rsp.error_description.isEmpty() ? rsp.error : rsp.error_description)); + return; + } + if (!m_task->wasSuccessful() || m_task->error() != QNetworkReply::NoError) { + startPoolTimer(); // it failed so just try again without increasing the interval + return; + } + + m_expiration_timer.stop(); + m_data->msaClientID = m_clientId; + m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc(); + m_data->msaToken.notAfter = QDateTime::currentDateTime().addSecs(rsp.expires_in); + m_data->msaToken.extra = rsp.extra; + m_data->msaToken.refresh_token = rsp.refresh_token; + m_data->msaToken.token = rsp.access_token; + emit finished(AccountTaskState::STATE_WORKING, tr("Got")); +} \ No newline at end of file diff --git a/launcher/minecraft/auth/steps/MSADeviceCodeStep.h b/launcher/minecraft/auth/steps/MSADeviceCodeStep.h new file mode 100644 index 0000000000..e53eebc628 --- /dev/null +++ b/launcher/minecraft/auth/steps/MSADeviceCodeStep.h @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2024 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * 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 +#include + +#include "minecraft/auth/AuthStep.h" +#include "net/Upload.h" + +class MSADeviceCodeStep : public AuthStep { + Q_OBJECT + public: + explicit MSADeviceCodeStep(AccountData* data); + virtual ~MSADeviceCodeStep() noexcept = default; + + void perform() override; + + QString describe() override; + + public slots: + void abort(); + + signals: + void authorizeWithBrowser(QString url, QString code, int expiresIn); + + private slots: + void deviceAutorizationFinished(); + void startPoolTimer(); + void authenticateUser(); + void authenticationFinished(); + + private: + QString m_clientId; + QString m_device_code; + bool m_is_aborted = false; + int interval = 5; + + QTimer m_pool_timer; + QTimer m_expiration_timer; + + std::shared_ptr m_response; + Net::Upload::Ptr m_task; +}; diff --git a/launcher/net/ByteArraySink.h b/launcher/net/ByteArraySink.h index 7b8f0f8aa4..d636f6634f 100644 --- a/launcher/net/ByteArraySink.h +++ b/launcher/net/ByteArraySink.h @@ -74,10 +74,6 @@ class ByteArraySink : public Sink { auto abort() -> Task::State override { - if (m_output) - m_output->clear(); - else - qWarning() << "ByteArraySink did not clear the buffer because it's not addressable"; failAllValidators(); return Task::State::Failed; } diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index cfd93b61f8..abecc0cf3f 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -256,21 +256,18 @@ void NetRequest::downloadFinished() { qCDebug(logCat) << getUid().toString() << "Request failed but we are allowed to proceed:" << m_url.toString(); m_sink->abort(); - m_reply.reset(); emit succeeded(); emit finished(); return; } else if (m_state == State::Failed) { qCDebug(logCat) << getUid().toString() << "Request failed in previous step:" << m_url.toString(); m_sink->abort(); - m_reply.reset(); - emit failed(""); + emit failed(m_reply->errorString()); emit finished(); return; } else if (m_state == State::AbortedByUser) { qCDebug(logCat) << getUid().toString() << "Request aborted in previous step:" << m_url.toString(); m_sink->abort(); - m_reply.reset(); emit aborted(); emit finished(); return; @@ -284,7 +281,7 @@ void NetRequest::downloadFinished() if (m_state != State::Succeeded) { qCDebug(logCat) << getUid().toString() << "Request failed to write:" << m_url.toString(); m_sink->abort(); - emit failed(""); + emit failed("failed to write in sink"); emit finished(); return; } @@ -295,13 +292,11 @@ void NetRequest::downloadFinished() if (m_state != State::Succeeded) { qCDebug(logCat) << getUid().toString() << "Request failed to finalize:" << m_url.toString(); m_sink->abort(); - m_reply.reset(); - emit failed(""); + emit failed("failed to finalize the request"); emit finished(); return; } - m_reply.reset(); qCDebug(logCat) << getUid().toString() << "Request succeeded:" << m_url.toString(); emit succeeded(); emit finished(); diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index 726572e52d..623ec80f4c 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -46,7 +46,8 @@ namespace Net { QNetworkReply* Upload::getReply(QNetworkRequest& request) { - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + if (!request.hasRawHeader("Content-Type")) + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); return m_network->post(request, m_post_data); } diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index b36e8f9b3e..a654c1d299 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -51,6 +51,7 @@ MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MS ui->cancel->setEnabled(false); ui->link->setVisible(false); ui->copy->setVisible(false); + ui->progressBar->setVisible(false); connect(ui->cancel, &QPushButton::pressed, this, &QDialog::reject); connect(ui->copy, &QPushButton::pressed, this, &MSALoginDialog::copyUrl); @@ -60,12 +61,15 @@ int MSALoginDialog::exec() { // Setup the login task and start it m_account = MinecraftAccount::createBlankMSA(); - m_loginTask = m_account->login(); - connect(m_loginTask.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed); - connect(m_loginTask.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded); - connect(m_loginTask.get(), &Task::status, this, &MSALoginDialog::onTaskStatus); - connect(m_loginTask.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser); - m_loginTask->start(); + m_task = m_account->login(m_using_device_code); + connect(m_task.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed); + connect(m_task.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded); + connect(m_task.get(), &Task::status, this, &MSALoginDialog::onTaskStatus); + connect(m_task.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser); + connect(m_task.get(), &AuthFlow::authorizeWithBrowserWithExtra, this, &MSALoginDialog::authorizeWithBrowserWithExtra); + connect(ui->cancel, &QPushButton::pressed, m_task.get(), &Task::abort); + connect(&m_external_timer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick); + m_task->start(); return QDialog::exec(); } @@ -101,12 +105,14 @@ void MSALoginDialog::onTaskStatus(const QString& status) ui->cancel->setEnabled(false); ui->link->setVisible(false); ui->copy->setVisible(false); + ui->progressBar->setVisible(false); } // Public interface -MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent, QString msg) +MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent, QString msg, bool usingDeviceCode) { MSALoginDialog dlg(parent); + dlg.m_using_device_code = usingDeviceCode; dlg.ui->message->setText(msg); if (dlg.exec() == QDialog::Accepted) { return dlg.m_account; @@ -132,3 +138,44 @@ void MSALoginDialog::copyUrl() QClipboard* cb = QApplication::clipboard(); cb->setText(ui->link->text()); } + +void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn) +{ + m_external_elapsed = 0; + m_external_timeout = expiresIn; + + m_external_timer.setInterval(1000); + m_external_timer.setSingleShot(false); + m_external_timer.start(); + + ui->progressBar->setMaximum(expiresIn); + ui->progressBar->setValue(m_external_elapsed); + + QString linkString = QString("%2").arg(url, url); + if (url == "https://www.microsoft.com/link" && !code.isEmpty()) { + url += QString("?otc=%1").arg(code); + ui->message->setText(tr("

    Please login in the opened browser. If no browser was opened, please open up %1 in " + "a browser and put in the code %2 to proceed with login.

    ") + .arg(linkString, code)); + } else { + ui->message->setText( + tr("

    Please open up %1 in a browser and put in the code %2 to proceed with login.

    ").arg(linkString, code)); + } + ui->cancel->setEnabled(true); + ui->link->setVisible(true); + ui->copy->setVisible(true); + ui->progressBar->setVisible(true); + DesktopServices::openUrl(url); + ui->link->setText(code); +} + +void MSALoginDialog::externalLoginTick() +{ + m_external_elapsed++; + ui->progressBar->setValue(m_external_elapsed); + ui->progressBar->repaint(); + + if (m_external_elapsed >= m_external_timeout) { + m_external_timer.stop(); + } +} \ No newline at end of file diff --git a/launcher/ui/dialogs/MSALoginDialog.h b/launcher/ui/dialogs/MSALoginDialog.h index b57b83ec2d..cef647ee44 100644 --- a/launcher/ui/dialogs/MSALoginDialog.h +++ b/launcher/ui/dialogs/MSALoginDialog.h @@ -15,6 +15,7 @@ #pragma once +#include #include #include @@ -31,7 +32,7 @@ class MSALoginDialog : public QDialog { public: ~MSALoginDialog(); - static MinecraftAccountPtr newAccount(QWidget* parent, QString message); + static MinecraftAccountPtr newAccount(QWidget* parent, QString message, bool usingDeviceCode = false); int exec() override; private: @@ -42,10 +43,18 @@ class MSALoginDialog : public QDialog { void onTaskSucceeded(); void onTaskStatus(const QString& status); void authorizeWithBrowser(const QUrl& url); + void authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn); void copyUrl(); + void externalLoginTick(); private: Ui::MSALoginDialog* ui; MinecraftAccountPtr m_account; - shared_qobject_ptr m_loginTask; + shared_qobject_ptr m_task; + + int m_external_elapsed; + int m_external_timeout; + QTimer m_external_timer; + + bool m_using_device_code = false; }; diff --git a/launcher/ui/dialogs/MSALoginDialog.ui b/launcher/ui/dialogs/MSALoginDialog.ui index e6c9f1aad8..df1b320449 100644 --- a/launcher/ui/dialogs/MSALoginDialog.ui +++ b/launcher/ui/dialogs/MSALoginDialog.ui @@ -7,11 +7,11 @@ 0 0 491 - 143 + 208 - + 0 0 @@ -22,12 +22,27 @@ + + + 0 + 0 + + + + + 500 + 500 + + Qt::RichText + + true + true @@ -51,12 +66,23 @@ - + + .. + + + + 24 + + + false + + + diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index abd8fa2288..9aec32cef3 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -40,6 +40,7 @@ #include #include +#include #include @@ -134,8 +135,16 @@ void AccountListPage::listChanged() void AccountListPage::on_actionAddMicrosoft_triggered() { - MinecraftAccountPtr account = - MSALoginDialog::newAccount(this, tr("Please enter your Mojang account email and password to add your account.")); + QMessageBox box(this); + box.setWindowTitle(tr("Add account")); + box.setText(tr("How do you want to login?")); + box.setIcon(QMessageBox::Question); + auto deviceCode = box.addButton(tr("Using device code"), QMessageBox::ButtonRole::YesRole); + auto authCode = box.addButton(tr("Using auth code"), QMessageBox::ButtonRole::NoRole); + box.setDefaultButton(authCode); + box.exec(); + MinecraftAccountPtr account = MSALoginDialog::newAccount( + this, tr("Please enter your Mojang account email and password to add your account."), box.clickedButton() == deviceCode); if (account) { m_accounts->addAccount(account); From ce09f06bda74552fddd8e6fbf6588057cd2fb98b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 16 May 2024 22:16:42 +0300 Subject: [PATCH 0210/2054] Fix CI3 Signed-off-by: Trial97 --- launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp index 436ed58bf8..22f5d40693 100644 --- a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp +++ b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp @@ -34,10 +34,8 @@ */ #include "MSADeviceCodeStep.h" -#include -#include -#include +#include #include #include "Application.h" From 75a457ebfaf9009d87f9baa772df993e1ca32644 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 17 May 2024 00:28:37 +0300 Subject: [PATCH 0211/2054] Add custom page to land after microsoft login Signed-off-by: Trial97 --- launcher/minecraft/auth/steps/MSAStep.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp index 3c55540dce..3f31cdc161 100644 --- a/launcher/minecraft/auth/steps/MSAStep.cpp +++ b/launcher/minecraft/auth/steps/MSAStep.cpp @@ -46,6 +46,10 @@ MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(sile m_clientId = APPLICATION->getMSAClientID(); auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this); + replyHandler->setCallbackText( + "