aboutsummaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
authorSefa Eyeoglu <contact@scrumplex.net>2022-07-10 19:38:30 +0200
committerSefa Eyeoglu <contact@scrumplex.net>2022-07-10 19:38:30 +0200
commitb3b76d5d56f9b6849464a6df2031058c98359fbc (patch)
tree211464fb189ef1202093f206d93f3b9f139fd284 /launcher
parent3bc02b9662b84c2ab86b5de1b08b4537177fde90 (diff)
parentcd948dceaed4625e7a876f680d3dc028e6cfe6de (diff)
downloadPrismLauncher-b3b76d5d56f9b6849464a6df2031058c98359fbc.tar.gz
PrismLauncher-b3b76d5d56f9b6849464a6df2031058c98359fbc.tar.bz2
PrismLauncher-b3b76d5d56f9b6849464a6df2031058c98359fbc.zip
Merge branch 'develop' into feature/sparkle-mac
# Conflicts: # .github/workflows/build.yml
Diffstat (limited to 'launcher')
-rw-r--r--launcher/Application.cpp132
-rw-r--r--launcher/Application.h20
-rw-r--r--launcher/ApplicationMessage.cpp44
-rw-r--r--launcher/BaseInstance.cpp58
-rw-r--r--launcher/BaseInstance.h10
-rw-r--r--launcher/BaseVersionList.cpp42
-rw-r--r--launcher/CMakeLists.txt173
-rw-r--r--launcher/Commandline.cpp46
-rw-r--r--launcher/FileSystem.cpp82
-rw-r--r--launcher/FileSystem.h46
-rw-r--r--launcher/FileSystem_test.cpp44
-rw-r--r--launcher/GZip.cpp39
-rw-r--r--launcher/GZip_test.cpp1
-rw-r--r--launcher/InstanceImportTask.cpp336
-rw-r--r--launcher/InstanceImportTask.h6
-rw-r--r--launcher/InstanceList.cpp64
-rw-r--r--launcher/InstancePageProvider.h4
-rw-r--r--launcher/JavaCommon.cpp53
-rw-r--r--launcher/JavaCommon.h10
-rw-r--r--launcher/Json.cpp51
-rw-r--r--launcher/Json.h37
-rw-r--r--launcher/LaunchController.cpp5
-rw-r--r--launcher/LoggedProcess.cpp52
-rw-r--r--launcher/LoggedProcess.h41
-rw-r--r--launcher/MMCZip.cpp18
-rw-r--r--launcher/ModDownloadTask.cpp47
-rw-r--r--launcher/ModDownloadTask.h49
-rw-r--r--launcher/NullInstance.h4
-rw-r--r--launcher/Version.h44
-rw-r--r--launcher/VersionProxyModel.cpp40
-rw-r--r--launcher/icons/IconList.cpp63
-rw-r--r--launcher/icons/IconList.h1
-rw-r--r--launcher/icons/MMCIcon.cpp44
-rw-r--r--launcher/java/JavaChecker.cpp56
-rw-r--r--launcher/java/JavaInstallList.cpp43
-rw-r--r--launcher/java/JavaUtils.cpp75
-rw-r--r--launcher/java/JavaUtils.h2
-rw-r--r--launcher/java/JavaVersion_test.cpp1
-rw-r--r--launcher/launch/LaunchTask.cpp17
-rw-r--r--launcher/launch/LaunchTask.h43
-rw-r--r--launcher/launch/steps/CheckJava.cpp25
-rw-r--r--launcher/launch/steps/CheckJava.h2
-rw-r--r--launcher/launch/steps/PostLaunchCommand.cpp50
-rw-r--r--launcher/launch/steps/PreLaunchCommand.cpp49
-rw-r--r--launcher/main.cpp39
-rw-r--r--launcher/meta/BaseEntity.cpp11
-rw-r--r--launcher/meta/Index_test.cpp1
-rw-r--r--launcher/minecraft/AssetsUtils.cpp42
-rw-r--r--launcher/minecraft/ComponentUpdateTask.cpp8
-rw-r--r--launcher/minecraft/GradleSpecifier.h55
-rw-r--r--launcher/minecraft/GradleSpecifier_test.cpp1
-rw-r--r--launcher/minecraft/Library_test.cpp44
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp98
-rw-r--r--launcher/minecraft/MinecraftInstance.h1
-rw-r--r--launcher/minecraft/MinecraftLoadAndCheck.cpp1
-rw-r--r--launcher/minecraft/MinecraftUpdate.cpp2
-rw-r--r--launcher/minecraft/MinecraftUpdate.h2
-rw-r--r--launcher/minecraft/MojangDownloadInfo.h2
-rw-r--r--launcher/minecraft/MojangVersionFormat_test.cpp8
-rw-r--r--launcher/minecraft/OneSixVersionFormat.cpp37
-rw-r--r--launcher/minecraft/PackProfile.cpp76
-rw-r--r--launcher/minecraft/PackProfile.h2
-rw-r--r--launcher/minecraft/ParseUtils_test.cpp2
-rw-r--r--launcher/minecraft/ProfileUtils.cpp53
-rw-r--r--launcher/minecraft/ProfileUtils.h38
-rw-r--r--launcher/minecraft/VersionFile.cpp14
-rw-r--r--launcher/minecraft/World.cpp43
-rw-r--r--launcher/minecraft/WorldList.cpp50
-rw-r--r--launcher/minecraft/auth/AccountData.cpp5
-rw-r--r--launcher/minecraft/auth/AccountList.cpp22
-rw-r--r--launcher/minecraft/auth/AccountList.h4
-rw-r--r--launcher/minecraft/auth/AccountTask.cpp2
-rw-r--r--launcher/minecraft/auth/AuthRequest.cpp43
-rw-r--r--launcher/minecraft/auth/MinecraftAccount.cpp14
-rw-r--r--launcher/minecraft/auth/steps/YggdrasilStep.cpp1
-rw-r--r--launcher/minecraft/launch/DirectJavaLaunch.cpp22
-rw-r--r--launcher/minecraft/launch/LauncherPartLaunch.cpp70
-rw-r--r--launcher/minecraft/mod/MetadataHandler.h59
-rw-r--r--launcher/minecraft/mod/Mod.cpp226
-rw-r--r--launcher/minecraft/mod/Mod.h145
-rw-r--r--launcher/minecraft/mod/ModDetails.h66
-rw-r--r--launcher/minecraft/mod/ModFolderLoadTask.cpp18
-rw-r--r--launcher/minecraft/mod/ModFolderLoadTask.h29
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp117
-rw-r--r--launcher/minecraft/mod/ModFolderModel.h63
-rw-r--r--launcher/minecraft/mod/ModFolderModel_test.cpp47
-rw-r--r--launcher/minecraft/mod/ResourcePackFolderModel.cpp35
-rw-r--r--launcher/minecraft/mod/TexturePackFolderModel.cpp35
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.cpp (renamed from launcher/minecraft/mod/LocalModParseTask.cpp)22
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.h (renamed from launcher/minecraft/mod/LocalModParseTask.h)8
-rw-r--r--launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp57
-rw-r--r--launcher/minecraft/mod/tasks/LocalModUpdateTask.h44
-rw-r--r--launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp104
-rw-r--r--launcher/minecraft/mod/tasks/ModFolderLoadTask.h71
-rw-r--r--launcher/minecraft/mod/testdata/test_folder/assets/minecraft/textures/blah.txt1
-rw-r--r--launcher/minecraft/mod/testdata/test_folder/pack.mcmeta6
-rw-r--r--launcher/minecraft/mod/testdata/test_folder/pack.nfo1
-rw-r--r--launcher/minecraft/services/CapeChange.cpp39
-rw-r--r--launcher/minecraft/services/SkinDelete.cpp39
-rw-r--r--launcher/minecraft/services/SkinUpload.cpp39
-rw-r--r--launcher/minecraft/update/AssetUpdateTask.cpp2
-rw-r--r--launcher/minecraft/update/FMLLibrariesTask.cpp1
-rw-r--r--launcher/minecraft/update/LibrariesTask.cpp1
-rw-r--r--launcher/modplatform/ModAPI.h55
-rw-r--r--launcher/modplatform/ModIndex.cpp86
-rw-r--r--launcher/modplatform/ModIndex.h54
-rw-r--r--launcher/modplatform/atlauncher/ATLPackInstallTask.cpp218
-rw-r--r--launcher/modplatform/atlauncher/ATLPackInstallTask.h52
-rw-r--r--launcher/modplatform/atlauncher/ATLPackManifest.cpp78
-rw-r--r--launcher/modplatform/atlauncher/ATLPackManifest.h70
-rw-r--r--launcher/modplatform/flame/FileResolvingTask.cpp128
-rw-r--r--launcher/modplatform/flame/FileResolvingTask.h8
-rw-r--r--launcher/modplatform/flame/FlameAPI.h23
-rw-r--r--launcher/modplatform/flame/FlameModIndex.cpp98
-rw-r--r--launcher/modplatform/flame/FlameModIndex.h2
-rw-r--r--launcher/modplatform/flame/FlamePackIndex.cpp35
-rw-r--r--launcher/modplatform/flame/FlamePackIndex.h12
-rw-r--r--launcher/modplatform/flame/PackManifest.cpp35
-rw-r--r--launcher/modplatform/flame/PackManifest.h47
-rw-r--r--launcher/modplatform/helpers/NetworkModAPI.cpp25
-rw-r--r--launcher/modplatform/helpers/NetworkModAPI.h2
-rw-r--r--launcher/modplatform/legacy_ftb/PackFetchTask.cpp37
-rw-r--r--launcher/modplatform/legacy_ftb/PackInstallTask.cpp39
-rw-r--r--launcher/modplatform/legacy_ftb/PrivatePackManager.cpp43
-rw-r--r--launcher/modplatform/modpacksch/FTBPackInstallTask.cpp48
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.h47
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.cpp166
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.h2
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackManifest.cpp49
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackManifest.h19
-rw-r--r--launcher/modplatform/packwiz/Packwiz.cpp289
-rw-r--r--launcher/modplatform/packwiz/Packwiz.h93
-rw-r--r--launcher/modplatform/packwiz/Packwiz_test.cpp86
-rw-r--r--launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml13
-rw-r--r--launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml13
-rw-r--r--launcher/modplatform/technic/SolderPackManifest.cpp2
-rw-r--r--launcher/modplatform/technic/TechnicPackProcessor.cpp21
-rw-r--r--launcher/mojang/PackageManifest_test.cpp1
-rw-r--r--launcher/net/ByteArraySink.h95
-rw-r--r--launcher/net/ChecksumValidator.h83
-rw-r--r--launcher/net/Download.cpp159
-rw-r--r--launcher/net/Download.h101
-rw-r--r--launcher/net/FileSink.cpp122
-rw-r--r--launcher/net/FileSink.h77
-rw-r--r--launcher/net/HttpMetaCache.cpp224
-rw-r--r--launcher/net/HttpMetaCache.h148
-rw-r--r--launcher/net/MetaCacheSink.cpp57
-rw-r--r--launcher/net/MetaCacheSink.h62
-rw-r--r--launcher/net/Mode.h9
-rw-r--r--launcher/net/NetAction.h134
-rw-r--r--launcher/net/NetJob.cpp280
-rw-r--r--launcher/net/NetJob.h110
-rw-r--r--launcher/net/PasteUpload.cpp207
-rw-r--r--launcher/net/PasteUpload.h64
-rw-r--r--launcher/net/Sink.h102
-rw-r--r--launcher/net/Upload.cpp199
-rw-r--r--launcher/net/Upload.h31
-rw-r--r--launcher/net/Validator.h34
-rw-r--r--launcher/news/NewsChecker.cpp42
-rw-r--r--launcher/news/NewsEntry.cpp2
-rw-r--r--launcher/resources/multimc/128x128/instances/flame.pngbin3375 -> 6226 bytes
-rw-r--r--launcher/resources/multimc/32x32/instances/flame.pngbin849 -> 0 bytes
-rw-r--r--launcher/resources/multimc/multimc.qrc4
-rw-r--r--launcher/screenshots/ImgurAlbumCreation.cpp67
-rw-r--r--launcher/screenshots/ImgurAlbumCreation.h49
-rw-r--r--launcher/screenshots/ImgurUpload.cpp71
-rw-r--r--launcher/screenshots/ImgurUpload.h39
-rw-r--r--launcher/settings/INIFile.cpp48
-rw-r--r--launcher/settings/INIFile_test.cpp1
-rw-r--r--launcher/tasks/ConcurrentTask.cpp144
-rw-r--r--launcher/tasks/ConcurrentTask.h58
-rw-r--r--launcher/tasks/SequentialTask.cpp29
-rw-r--r--launcher/tasks/SequentialTask.h9
-rw-r--r--launcher/tasks/Task.cpp42
-rw-r--r--launcher/tasks/Task.h56
-rw-r--r--launcher/tasks/Task_test.cpp1
-rw-r--r--launcher/translations/POTranslator.cpp5
-rw-r--r--launcher/translations/POTranslator.h1
-rw-r--r--launcher/translations/TranslationsModel.cpp44
-rw-r--r--launcher/ui/GuiUtil.cpp41
-rw-r--r--launcher/ui/InstanceWindow.cpp50
-rw-r--r--launcher/ui/InstanceWindow.h44
-rw-r--r--launcher/ui/MainWindow.cpp260
-rw-r--r--launcher/ui/MainWindow.h52
-rw-r--r--launcher/ui/dialogs/AboutDialog.cpp2
-rw-r--r--launcher/ui/dialogs/AboutDialog.ui27
-rw-r--r--launcher/ui/dialogs/CopyInstanceDialog.cpp46
-rw-r--r--launcher/ui/dialogs/ExportInstanceDialog.cpp4
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.cpp24
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.h3
-rw-r--r--launcher/ui/dialogs/NewComponentDialog.cpp41
-rw-r--r--launcher/ui/dialogs/NewInstanceDialog.cpp46
-rw-r--r--launcher/ui/dialogs/NewsDialog.cpp49
-rw-r--r--launcher/ui/dialogs/NewsDialog.h30
-rw-r--r--launcher/ui/dialogs/NewsDialog.ui113
-rw-r--r--launcher/ui/dialogs/ProfileSetupDialog.cpp46
-rw-r--r--launcher/ui/dialogs/ProgressDialog.cpp91
-rw-r--r--launcher/ui/dialogs/ProgressDialog.ui6
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.cpp27
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.h12
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.ui81
-rw-r--r--launcher/ui/dialogs/ScrollMessageBox.cpp15
-rw-r--r--launcher/ui/dialogs/ScrollMessageBox.h20
-rw-r--r--launcher/ui/dialogs/ScrollMessageBox.ui84
-rw-r--r--launcher/ui/dialogs/SkinUploadDialog.cpp39
-rw-r--r--launcher/ui/dialogs/UpdateDialog.cpp37
-rw-r--r--launcher/ui/instanceview/InstanceDelegate.cpp46
-rw-r--r--launcher/ui/instanceview/InstanceView.cpp90
-rw-r--r--launcher/ui/instanceview/InstanceView.h44
-rw-r--r--launcher/ui/instanceview/VisualGroup.cpp49
-rw-r--r--launcher/ui/pages/global/APIPage.cpp103
-rw-r--r--launcher/ui/pages/global/APIPage.h5
-rw-r--r--launcher/ui/pages/global/APIPage.ui207
-rw-r--r--launcher/ui/pages/global/AccountListPage.cpp18
-rw-r--r--launcher/ui/pages/global/CustomCommandsPage.cpp2
-rw-r--r--launcher/ui/pages/global/JavaPage.cpp11
-rw-r--r--launcher/ui/pages/global/JavaPage.ui69
-rw-r--r--launcher/ui/pages/global/LanguagePage.cpp1
-rw-r--r--launcher/ui/pages/global/LanguagePage.h1
-rw-r--r--launcher/ui/pages/global/LauncherPage.cpp12
-rw-r--r--launcher/ui/pages/global/LauncherPage.h1
-rw-r--r--launcher/ui/pages/global/LauncherPage.ui75
-rw-r--r--launcher/ui/pages/global/MinecraftPage.cpp13
-rw-r--r--launcher/ui/pages/global/MinecraftPage.ui60
-rw-r--r--launcher/ui/pages/global/ProxyPage.cpp16
-rw-r--r--launcher/ui/pages/global/ProxyPage.h9
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.cpp297
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.h73
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.ui (renamed from launcher/ui/pages/instance/ModFolderPage.ui)49
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.cpp34
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.ui68
-rw-r--r--launcher/ui/pages/instance/LogPage.cpp3
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp387
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.h112
-rw-r--r--launcher/ui/pages/instance/ResourcePackPage.h20
-rw-r--r--launcher/ui/pages/instance/ScreenshotsPage.cpp6
-rw-r--r--launcher/ui/pages/instance/ServersPage.cpp13
-rw-r--r--launcher/ui/pages/instance/ServersPage.h3
-rw-r--r--launcher/ui/pages/instance/ShaderPackPage.h16
-rw-r--r--launcher/ui/pages/instance/TexturePackPage.h18
-rw-r--r--launcher/ui/pages/instance/VersionPage.cpp2
-rw-r--r--launcher/ui/pages/instance/WorldListPage.cpp1
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.cpp17
-rw-r--r--launcher/ui/pages/modplatform/ModModel.cpp87
-rw-r--r--launcher/ui/pages/modplatform/ModModel.h4
-rw-r--r--launcher/ui/pages/modplatform/ModPage.cpp128
-rw-r--r--launcher/ui/pages/modplatform/ModPage.h7
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp37
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h6
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp20
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.h3
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModPage.cpp6
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModPage.h4
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.cpp16
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.h1
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.cpp95
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.h2
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.ui192
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbListModel.cpp12
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp37
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.cpp5
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.ui6
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp5
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h1
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp6
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h4
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp27
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.h1
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp65
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui10
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.cpp5
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.ui6
-rw-r--r--launcher/ui/setupwizard/PasteWizardPage.cpp42
-rw-r--r--launcher/ui/setupwizard/PasteWizardPage.h27
-rw-r--r--launcher/ui/setupwizard/PasteWizardPage.ui80
-rw-r--r--launcher/ui/widgets/CustomCommands.cpp2
-rw-r--r--launcher/ui/widgets/CustomCommands.h2
-rw-r--r--launcher/ui/widgets/JavaSettingsWidget.cpp5
-rw-r--r--launcher/ui/widgets/LabeledToolButton.cpp44
-rw-r--r--launcher/ui/widgets/LogView.cpp37
-rw-r--r--launcher/ui/widgets/MCModInfoFrame.cpp2
-rw-r--r--launcher/ui/widgets/PageContainer.cpp4
-rw-r--r--launcher/ui/widgets/VersionListView.cpp42
-rw-r--r--launcher/updater/DownloadTask_test.cpp196
-rw-r--r--launcher/updater/UpdateChecker_test.cpp149
-rw-r--r--launcher/updater/testdata/1.json43
-rw-r--r--launcher/updater/testdata/2.json31
-rw-r--r--launcher/updater/testdata/channels.json23
-rw-r--r--launcher/updater/testdata/errorChannels.json23
-rw-r--r--launcher/updater/testdata/fileOneA1
-rw-r--r--launcher/updater/testdata/fileOneB3
-rw-r--r--launcher/updater/testdata/fileThree1
-rw-r--r--launcher/updater/testdata/fileTwo1
-rw-r--r--launcher/updater/testdata/garbageChannels.json22
-rw-r--r--launcher/updater/testdata/index.json9
-rw-r--r--launcher/updater/testdata/noChannels.json5
-rw-r--r--launcher/updater/testdata/oneChannel.json11
-rw-r--r--launcher/updater/testdata/tst_DownloadTask-test_writeInstallScript.xml17
298 files changed, 10094 insertions, 3930 deletions
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 456ea02c..f8c93259 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
*
* 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
@@ -36,6 +37,7 @@
#include "Application.h"
#include "BuildConfig.h"
+#include "net/PasteUpload.h"
#include "ui/MainWindow.h"
#include "ui/InstanceWindow.h"
@@ -61,6 +63,7 @@
#include "ui/setupwizard/SetupWizard.h"
#include "ui/setupwizard/LanguageWizardPage.h"
#include "ui/setupwizard/JavaWizardPage.h"
+#include "ui/setupwizard/PasteWizardPage.h"
#include "ui/dialogs/CustomMessageBox.h"
@@ -81,6 +84,7 @@
#include <QDebug>
#include <QStyleFactory>
#include <QWindow>
+#include <QIcon>
#include "InstanceList.h"
@@ -96,7 +100,6 @@
#include "tools/JVisualVM.h"
#include "tools/MCEditTool.h"
-#include <xdgicon.h>
#include "settings/INISettingsObject.h"
#include "settings/Setting.h"
@@ -221,7 +224,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
setOrganizationName(BuildConfig.LAUNCHER_NAME);
setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN);
setApplicationName(BuildConfig.LAUNCHER_NAME);
- setApplicationDisplayName(BuildConfig.LAUNCHER_DISPLAYNAME);
+ setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()));
setApplicationVersion(BuildConfig.printableVersionString());
setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
startTime = QDateTime::currentDateTime();
@@ -329,10 +332,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues)
FS::updateTimestamp(m_rootPath);
#endif
-
-#ifdef LAUNCHER_JARS_LOCATION
- m_jarsPath = TOSTRING(LAUNCHER_JARS_LOCATION);
-#endif
}
QString adjustedBy;
@@ -622,6 +621,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("JavaPath", "");
m_settings->registerSetting("JavaTimestamp", 0);
m_settings->registerSetting("JavaArchitecture", "");
+ m_settings->registerSetting("JavaRealArchitecture", "");
m_settings->registerSetting("JavaVersion", "");
m_settings->registerSetting("JavaVendor", "");
m_settings->registerSetting("LastHostname", "");
@@ -633,6 +633,11 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("UseNativeOpenAL", false);
m_settings->registerSetting("UseNativeGLFW", false);
+ // Peformance related options
+ m_settings->registerSetting("EnableFeralGamemode", false);
+ m_settings->registerSetting("EnableMangoHud", false);
+ m_settings->registerSetting("UseDiscreteGpu", false);
+
// Game time
m_settings->registerSetting("ShowGameTime", true);
m_settings->registerSetting("ShowGlobalGameTime", true);
@@ -641,6 +646,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Minecraft launch method
m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
+ // Minecraft mods
+ m_settings->registerSetting("ModMetadataDisabled", false);
+
// Minecraft offline player name
m_settings->registerSetting("LastOfflinePlayerName", "");
@@ -672,14 +680,41 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("UpdateDialogGeometry", "");
- // pastebin URL
- m_settings->registerSetting("PastebinURL", "https://0x0.st");
+ // HACK: This code feels so stupid is there a less stupid way of doing this?
+ {
+ m_settings->registerSetting("PastebinURL", "");
+ m_settings->registerSetting("PastebinType", PasteUpload::PasteType::Mclogs);
+ m_settings->registerSetting("PastebinCustomAPIBase", "");
+
+ QString pastebinURL = m_settings->get("PastebinURL").toString();
+
+ bool userHadDefaultPastebin = pastebinURL == "https://0x0.st";
+ if (!pastebinURL.isEmpty() && !userHadDefaultPastebin)
+ {
+ m_settings->set("PastebinType", PasteUpload::PasteType::NullPointer);
+ m_settings->set("PastebinCustomAPIBase", pastebinURL);
+ m_settings->reset("PastebinURL");
+ }
+
+ bool ok;
+ int pasteType = m_settings->get("PastebinType").toInt(&ok);
+ // If PastebinType is invalid then reset the related settings.
+ if (!ok || !(PasteUpload::PasteType::First <= pasteType && pasteType <= PasteUpload::PasteType::Last))
+ {
+ m_settings->reset("PastebinType");
+ m_settings->reset("PastebinCustomAPIBase");
+ }
+ }
+ // meta URL
+ m_settings->registerSetting("MetaURLOverride", "");
m_settings->registerSetting("CloseAfterLaunch", false);
m_settings->registerSetting("QuitAfterGameStop", false);
// Custom MSA credentials
m_settings->registerSetting("MSAClientIDOverride", "");
+ m_settings->registerSetting("CFKeyOverride", "");
+ m_settings->registerSetting("UserAgentOverride", "");
// Init page provider
{
@@ -843,6 +878,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_mcedit.reset(new MCEditTool(m_settings));
}
+#ifdef Q_OS_MACOS
+ connect(this, &Application::clickedOnDock, [this]() {
+ this->showMainWindow();
+ });
+#endif
+
connect(this, &Application::aboutToQuit, [this](){
if(m_instances)
{
@@ -899,7 +940,8 @@ bool Application::createSetupWizard()
return true;
return false;
}();
- bool wizardRequired = javaRequired || languageRequired;
+ bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
+ bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired;
if(wizardRequired)
{
@@ -913,6 +955,11 @@ bool Application::createSetupWizard()
{
m_setupWizard->addPage(new JavaWizardPage(m_setupWizard));
}
+
+ if (pasteInterventionRequired)
+ {
+ m_setupWizard->addPage(new PasteWizardPage(m_setupWizard));
+ }
connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished);
m_setupWizard->show();
return true;
@@ -920,6 +967,21 @@ bool Application::createSetupWizard()
return false;
}
+bool Application::event(QEvent* event) {
+#ifdef Q_OS_MACOS
+ if (event->type() == QEvent::ApplicationStateChange) {
+ auto ev = static_cast<QApplicationStateChangeEvent*>(event);
+
+ if (m_prevAppState == Qt::ApplicationActive
+ && ev->applicationState() == Qt::ApplicationActive) {
+ emit clickedOnDock();
+ }
+ m_prevAppState = ev->applicationState();
+ }
+#endif
+ return QApplication::event(event);
+}
+
void Application::setupWizardFinished(int status)
{
qDebug() << "Wizard result =" << status;
@@ -1121,7 +1183,7 @@ void Application::setApplicationTheme(const QString& name, bool initial)
void Application::setIconTheme(const QString& name)
{
- XdgIcon::setThemeName(name);
+ QIcon::setThemeName(name);
}
QIcon Application::getThemedIcon(const QString& name)
@@ -1129,7 +1191,7 @@ QIcon Application::getThemedIcon(const QString& name)
if(name == "logo") {
return QIcon(":/org.polymc.PolyMC.svg");
}
- return XdgIcon::fromTheme(name);
+ return QIcon::fromTheme(name);
}
bool Application::openJsonEditor(const QString &filename)
@@ -1491,13 +1553,22 @@ shared_qobject_ptr<Meta::Index> Application::metadataIndex()
return m_metadataIndex;
}
-QString Application::getJarsPath()
+QString Application::getJarPath(QString jarFile)
{
- if(m_jarsPath.isEmpty())
+ QStringList potentialPaths = {
+#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
+ FS::PathCombine(m_rootPath, "share/jars"),
+#endif
+ FS::PathCombine(m_rootPath, "jars"),
+ FS::PathCombine(applicationDirPath(), "jars")
+ };
+ for(QString p : potentialPaths)
{
- return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars");
+ QString jarPath = FS::PathCombine(p, jarFile);
+ if (QFileInfo(jarPath).isFile())
+ return jarPath;
}
- return FS::PathCombine(m_rootPath, m_jarsPath);
+ return {};
}
QString Application::getMSAClientID()
@@ -1509,3 +1580,34 @@ QString Application::getMSAClientID()
return BuildConfig.MSA_CLIENT_ID;
}
+
+QString Application::getCurseKey()
+{
+ QString keyOverride = m_settings->get("CFKeyOverride").toString();
+ if (!keyOverride.isEmpty()) {
+ return keyOverride;
+ }
+
+ return BuildConfig.CURSEFORGE_API_KEY;
+}
+
+QString Application::getUserAgent()
+{
+ QString uaOverride = m_settings->get("UserAgentOverride").toString();
+ if (!uaOverride.isEmpty()) {
+ return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString());
+ }
+
+ return BuildConfig.USER_AGENT;
+}
+
+QString Application::getUserAgentUncached()
+{
+ QString uaOverride = m_settings->get("UserAgentOverride").toString();
+ if (!uaOverride.isEmpty()) {
+ uaOverride += " (Uncached)";
+ return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString());
+ }
+
+ return BuildConfig.USER_AGENT_UNCACHED;
+}
diff --git a/launcher/Application.h b/launcher/Application.h
index 172321c0..e3b4fced 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -94,6 +94,8 @@ public:
Application(int &argc, char **argv);
virtual ~Application();
+ bool event(QEvent* event) override;
+
std::shared_ptr<SettingsObject> settings() const {
return m_settings;
}
@@ -152,9 +154,16 @@ public:
shared_qobject_ptr<Meta::Index> metadataIndex();
- QString getJarsPath();
+ /*!
+ * Finds and returns the full path to a jar file.
+ * Returns a null-string if it could not be found.
+ */
+ QString getJarPath(QString jarFile);
QString getMSAClientID();
+ QString getCurseKey();
+ QString getUserAgent();
+ QString getUserAgentUncached();
/// this is the root of the 'installation'. Used for automatic updates
const QString &root() {
@@ -180,6 +189,10 @@ signals:
void globalSettingsAboutToOpen();
void globalSettingsClosed();
+#ifdef Q_OS_MACOS
+ void clickedOnDock();
+#endif
+
public slots:
bool launch(
InstancePtr instance,
@@ -229,7 +242,6 @@ private:
std::shared_ptr<GenericPageProvider> m_globalSettingsProvider;
std::map<QString, std::unique_ptr<ITheme>> m_themes;
std::unique_ptr<MCEditTool> m_mcedit;
- QString m_jarsPath;
QSet<QString> m_features;
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;
@@ -237,6 +249,10 @@ private:
QString m_rootPath;
Status m_status = Application::StartingUp;
+#ifdef Q_OS_MACOS
+ Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;
+#endif
+
#if defined Q_OS_WIN32
// used on Windows to attach the standard IO streams
bool consoleAttached = false;
diff --git a/launcher/ApplicationMessage.cpp b/launcher/ApplicationMessage.cpp
index e22bf13c..ca276b89 100644
--- a/launcher/ApplicationMessage.cpp
+++ b/launcher/ApplicationMessage.cpp
@@ -1,11 +1,47 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "ApplicationMessage.h"
#include <QJsonDocument>
#include <QJsonObject>
+#include "Json.h"
void ApplicationMessage::parse(const QByteArray & input) {
- auto doc = QJsonDocument::fromBinaryData(input);
- auto root = doc.object();
+ auto doc = Json::requireDocument(input, "ApplicationMessage");
+ auto root = Json::requireObject(doc, "ApplicationMessage");
command = root.value("command").toString();
args.clear();
@@ -25,7 +61,5 @@ QByteArray ApplicationMessage::serialize() {
}
root.insert("args", outArgs);
- QJsonDocument out;
- out.setObject(root);
- return out.toBinaryData();
+ return Json::toText(root);
}
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index 2fb31d94..5a84a931 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* 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
@@ -38,6 +39,7 @@
#include <QFileInfo>
#include <QDir>
#include <QDebug>
+#include <QRegularExpression>
#include "settings/INISettingsObject.h"
#include "settings/Setting.h"
@@ -59,7 +61,11 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0);
m_settings->registerSetting("lastTimePlayed", 0);
- m_settings->registerSetting("InstanceType", "OneSix");
+
+ // NOTE: Sometimees InstanceType is already registered, as it was used to identify the type of
+ // a locally stored instance
+ if (!m_settings->getSetting("InstanceType"))
+ m_settings->registerSetting("InstanceType", "");
// Custom Commands
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
@@ -76,6 +82,14 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr);
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr);
+
+ // Managed Packs
+ m_settings->registerSetting("ManagedPack", false);
+ m_settings->registerSetting("ManagedPackType", "");
+ m_settings->registerSetting("ManagedPackID", "");
+ m_settings->registerSetting("ManagedPackName", "");
+ m_settings->registerSetting("ManagedPackVersionID", "");
+ m_settings->registerSetting("ManagedPackVersionName", "");
}
QString BaseInstance::getPreLaunchCommand()
@@ -93,6 +107,46 @@ QString BaseInstance::getPostExitCommand()
return settings()->get("PostExitCommand").toString();
}
+bool BaseInstance::isManagedPack()
+{
+ return settings()->get("ManagedPack").toBool();
+}
+
+QString BaseInstance::getManagedPackType()
+{
+ return settings()->get("ManagedPackType").toString();
+}
+
+QString BaseInstance::getManagedPackID()
+{
+ return settings()->get("ManagedPackID").toString();
+}
+
+QString BaseInstance::getManagedPackName()
+{
+ return settings()->get("ManagedPackName").toString();
+}
+
+QString BaseInstance::getManagedPackVersionID()
+{
+ return settings()->get("ManagedPackVersionID").toString();
+}
+
+QString BaseInstance::getManagedPackVersionName()
+{
+ return settings()->get("ManagedPackVersionName").toString();
+}
+
+void BaseInstance::setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version)
+{
+ settings()->set("ManagedPack", true);
+ settings()->set("ManagedPackType", type);
+ settings()->set("ManagedPackID", id);
+ settings()->set("ManagedPackName", name);
+ settings()->set("ManagedPackVersionID", versionId);
+ settings()->set("ManagedPackVersionName", version);
+}
+
int BaseInstance::getConsoleMaxLines() const
{
auto lineSetting = settings()->getSetting("ConsoleMaxLines");
@@ -282,7 +336,7 @@ QString BaseInstance::name() const
QString BaseInstance::windowTitle() const
{
- return BuildConfig.LAUNCHER_NAME + ": " + name().replace(QRegExp("[ \n\r\t]+"), " ");
+ return BuildConfig.LAUNCHER_NAME + ": " + name().replace(QRegularExpression("\\s+"), " ");
}
// FIXME: why is this here? move it to MinecraftInstance!!!
diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h
index c973fcd4..2a94dcc6 100644
--- a/launcher/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* 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
@@ -139,6 +140,14 @@ public:
QString getPostExitCommand();
QString getWrapperCommand();
+ bool isManagedPack();
+ QString getManagedPackType();
+ QString getManagedPackID();
+ QString getManagedPackName();
+ QString getManagedPackVersionID();
+ QString getManagedPackVersionName();
+ void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version);
+
/// guess log level from a line of game log
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
{
@@ -179,6 +188,7 @@ public:
* Create envrironment variables for running the instance
*/
virtual QProcessEnvironment createEnvironment() = 0;
+ virtual QProcessEnvironment createLaunchEnvironment() = 0;
/*!
* Returns a matcher that can maps relative paths within the instance to whether they are 'log files'
diff --git a/launcher/BaseVersionList.cpp b/launcher/BaseVersionList.cpp
index aa9cb6cf..b4a7d6dd 100644
--- a/launcher/BaseVersionList.cpp
+++ b/launcher/BaseVersionList.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "BaseVersionList.h"
@@ -51,7 +71,7 @@ QVariant BaseVersionList::data(const QModelIndex &index, int role) const
switch (role)
{
case VersionPointerRole:
- return qVariantFromValue(version);
+ return QVariant::fromValue(version);
case VersionRole:
return version->name();
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 0be10682..ecdeaac0 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -4,8 +4,6 @@ project(application)
######## Sources and headers ########
-include (UnitTest)
-
set(CORE_SOURCES
# LOGIC - Base classes and infrastructure
BaseInstaller.h
@@ -90,16 +88,11 @@ set(CORE_SOURCES
MMCTime.cpp
)
-add_unit_test(FileSystem
- SOURCES FileSystem_test.cpp
- LIBS Launcher_logic
- DATA testdata
- )
+ecm_add_test(FileSystem_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
+ TEST_NAME FileSystem) # TODO: needs testdata
-add_unit_test(GZip
- SOURCES GZip_test.cpp
- LIBS Launcher_logic
- )
+ecm_add_test(GZip_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
+ TEST_NAME GZip)
set(PATHMATCHER_SOURCES
# Path matchers
@@ -128,6 +121,8 @@ set(NET_SOURCES
net/PasteUpload.h
net/Sink.h
net/Validator.h
+ net/Upload.cpp
+ net/Upload.h
)
# Game launch logic
@@ -170,18 +165,6 @@ set(MAC_UPDATE_SOURCES
updater/MacSparkleUpdater.mm
)
-add_unit_test(UpdateChecker
- SOURCES updater/UpdateChecker_test.cpp
- LIBS Launcher_logic
- DATA updater/testdata
- )
-
-add_unit_test(DownloadTask
- SOURCES updater/DownloadTask_test.cpp
- LIBS Launcher_logic
- DATA updater/testdata
- )
-
# Backend for the news bar... there's usually no news.
set(NEWS_SOURCES
# News System
@@ -328,19 +311,22 @@ set(MINECRAFT_SOURCES
minecraft/WorldList.h
minecraft/WorldList.cpp
+ minecraft/mod/MetadataHandler.h
minecraft/mod/Mod.h
minecraft/mod/Mod.cpp
minecraft/mod/ModDetails.h
minecraft/mod/ModFolderModel.h
minecraft/mod/ModFolderModel.cpp
- minecraft/mod/ModFolderLoadTask.h
- minecraft/mod/ModFolderLoadTask.cpp
- minecraft/mod/LocalModParseTask.h
- minecraft/mod/LocalModParseTask.cpp
minecraft/mod/ResourcePackFolderModel.h
minecraft/mod/ResourcePackFolderModel.cpp
minecraft/mod/TexturePackFolderModel.h
minecraft/mod/TexturePackFolderModel.cpp
+ minecraft/mod/tasks/ModFolderLoadTask.h
+ minecraft/mod/tasks/ModFolderLoadTask.cpp
+ minecraft/mod/tasks/LocalModParseTask.h
+ minecraft/mod/tasks/LocalModParseTask.cpp
+ minecraft/mod/tasks/LocalModUpdateTask.h
+ minecraft/mod/tasks/LocalModUpdateTask.cpp
# Assets
minecraft/AssetsUtils.h
@@ -358,10 +344,8 @@ set(MINECRAFT_SOURCES
mojang/PackageManifest.cpp
minecraft/Agent.h)
-add_unit_test(GradleSpecifier
- SOURCES minecraft/GradleSpecifier_test.cpp
- LIBS Launcher_logic
- )
+ecm_add_test(minecraft/GradleSpecifier_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
+ TEST_NAME GradleSpecifier)
if(BUILD_TESTING)
add_executable(PackageManifest
@@ -369,7 +353,7 @@ if(BUILD_TESTING)
)
target_link_libraries(PackageManifest
Launcher_logic
- Qt5::Test
+ Qt${QT_VERSION_MAJOR}::Test
)
target_include_directories(PackageManifest
PRIVATE ../cmake/UnitTest/
@@ -381,28 +365,20 @@ if(BUILD_TESTING)
)
endif()
-add_unit_test(MojangVersionFormat
- SOURCES minecraft/MojangVersionFormat_test.cpp
- LIBS Launcher_logic
- DATA minecraft/testdata
- )
+# TODO: needs minecraft/testdata
+ecm_add_test(minecraft/MojangVersionFormat_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
+ TEST_NAME MojangVersionFormat)
-add_unit_test(Library
- SOURCES minecraft/Library_test.cpp
- LIBS Launcher_logic
- )
+ecm_add_test(minecraft/Library_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
+ TEST_NAME Library)
# FIXME: shares data with FileSystem test
-add_unit_test(ModFolderModel
- SOURCES minecraft/mod/ModFolderModel_test.cpp
- DATA testdata
- LIBS Launcher_logic
- )
+# TODO: needs testdata
+ecm_add_test(minecraft/mod/ModFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
+ TEST_NAME ModFolderModel)
-add_unit_test(ParseUtils
- SOURCES minecraft/ParseUtils_test.cpp
- LIBS Launcher_logic
- )
+ecm_add_test(minecraft/ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
+ TEST_NAME ParseUtils)
# the screenshots feature
set(SCREENSHOTS_SOURCES
@@ -417,14 +393,14 @@ set(TASKS_SOURCES
# Tasks
tasks/Task.h
tasks/Task.cpp
+ tasks/ConcurrentTask.h
+ tasks/ConcurrentTask.cpp
tasks/SequentialTask.h
tasks/SequentialTask.cpp
)
-add_unit_test(Task
- SOURCES tasks/Task_test.cpp
- LIBS Launcher_logic
- )
+ecm_add_test(tasks/Task_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
+ TEST_NAME Task)
set(SETTINGS_SOURCES
# Settings
@@ -442,10 +418,8 @@ set(SETTINGS_SOURCES
settings/SettingsObject.h
)
-add_unit_test(INIFile
- SOURCES settings/INIFile_test.cpp
- LIBS Launcher_logic
- )
+ecm_add_test(settings/INIFile_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
+ TEST_NAME INIFile)
set(JAVA_SOURCES
java/JavaChecker.h
@@ -462,10 +436,8 @@ set(JAVA_SOURCES
java/JavaVersion.cpp
)
-add_unit_test(JavaVersion
- SOURCES java/JavaVersion_test.cpp
- LIBS Launcher_logic
- )
+ecm_add_test(java/JavaVersion_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
+ TEST_NAME JavaVersion)
set(TRANSLATIONS_SOURCES
translations/TranslationsModel.h
@@ -503,6 +475,9 @@ set(META_SOURCES
)
set(API_SOURCES
+ modplatform/ModIndex.h
+ modplatform/ModIndex.cpp
+
modplatform/ModAPI.h
modplatform/flame/FlameAPI.h
@@ -549,6 +524,15 @@ set(MODPACKSCH_SOURCES
modplatform/modpacksch/FTBPackManifest.cpp
)
+set(PACKWIZ_SOURCES
+ modplatform/packwiz/Packwiz.h
+ modplatform/packwiz/Packwiz.cpp
+)
+
+# TODO: needs modplatform/packwiz/testdata
+ecm_add_test(modplatform/packwiz/Packwiz_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
+ TEST_NAME Packwiz)
+
set(TECHNIC_SOURCES
modplatform/technic/SingleZipPackInstallTask.h
modplatform/technic/SingleZipPackInstallTask.cpp
@@ -571,10 +555,8 @@ set(ATLAUNCHER_SOURCES
modplatform/atlauncher/ATLShareCode.h
)
-add_unit_test(Index
- SOURCES meta/Index_test.cpp
- LIBS Launcher_logic
- )
+ecm_add_test(meta/Index_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
+ TEST_NAME Index)
################################ COMPILE ################################
@@ -602,6 +584,7 @@ set(LOGIC_SOURCES
${FLAME_SOURCES}
${MODRINTH_SOURCES}
${MODPACKSCH_SOURCES}
+ ${PACKWIZ_SOURCES}
${TECHNIC_SOURCES}
${ATLAUNCHER_SOURCES}
)
@@ -671,6 +654,8 @@ SET(LAUNCHER_SOURCES
ui/setupwizard/JavaWizardPage.h
ui/setupwizard/LanguageWizardPage.cpp
ui/setupwizard/LanguageWizardPage.h
+ ui/setupwizard/PasteWizardPage.cpp
+ ui/setupwizard/PasteWizardPage.h
# GUI - themes
ui/themes/FusionTheme.cpp
@@ -703,6 +688,8 @@ SET(LAUNCHER_SOURCES
ui/pages/BasePageProvider.h
# GUI - instance pages
+ ui/pages/instance/ExternalResourcesPage.cpp
+ ui/pages/instance/ExternalResourcesPage.h
ui/pages/instance/GameOptionsPage.cpp
ui/pages/instance/GameOptionsPage.h
ui/pages/instance/VersionPage.cpp
@@ -831,6 +818,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/NewComponentDialog.h
ui/dialogs/NewInstanceDialog.cpp
ui/dialogs/NewInstanceDialog.h
+ ui/dialogs/NewsDialog.cpp
+ ui/dialogs/NewsDialog.h
ui/pagedialog/PageDialog.cpp
ui/pagedialog/PageDialog.h
ui/dialogs/ProgressDialog.cpp
@@ -845,6 +834,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/SkinUploadDialog.h
ui/dialogs/ModDownloadDialog.cpp
ui/dialogs/ModDownloadDialog.h
+ ui/dialogs/ScrollMessageBox.cpp
+ ui/dialogs/ScrollMessageBox.h
# GUI - widgets
ui/widgets/Common.cpp
@@ -899,7 +890,8 @@ SET(LAUNCHER_SOURCES
ui/instanceview/VisualGroup.h
)
-qt5_wrap_ui(LAUNCHER_UI
+qt_wrap_ui(LAUNCHER_UI
+ ui/setupwizard/PasteWizardPage.ui
ui/pages/global/AccountListPage.ui
ui/pages/global/JavaPage.ui
ui/pages/global/LauncherPage.ui
@@ -907,7 +899,7 @@ qt5_wrap_ui(LAUNCHER_UI
ui/pages/global/ProxyPage.ui
ui/pages/global/MinecraftPage.ui
ui/pages/global/ExternalToolsPage.ui
- ui/pages/instance/ModFolderPage.ui
+ ui/pages/instance/ExternalResourcesPage.ui
ui/pages/instance/NotesPage.ui
ui/pages/instance/LogPage.ui
ui/pages/instance/ServersPage.ui
@@ -937,6 +929,7 @@ qt5_wrap_ui(LAUNCHER_UI
ui/dialogs/NewInstanceDialog.ui
ui/dialogs/UpdateDialog.ui
ui/dialogs/NewComponentDialog.ui
+ ui/dialogs/NewsDialog.ui
ui/dialogs/ProfileSelectDialog.ui
ui/dialogs/SkinUploadDialog.ui
ui/dialogs/ExportInstanceDialog.ui
@@ -947,9 +940,10 @@ qt5_wrap_ui(LAUNCHER_UI
ui/dialogs/LoginDialog.ui
ui/dialogs/EditAccountDialog.ui
ui/dialogs/ReviewMessageBox.ui
+ ui/dialogs/ScrollMessageBox.ui
)
-qt5_add_resources(LAUNCHER_RESOURCES
+qt_add_resources(LAUNCHER_RESOURCES
resources/backgrounds/backgrounds.qrc
resources/multimc/multimc.qrc
resources/pe_dark/pe_dark.qrc
@@ -965,7 +959,7 @@ qt5_add_resources(LAUNCHER_RESOURCES
######## Windows resource files ########
if(WIN32)
- set(LAUNCHER_RCS ../${Launcher_Branding_WindowsRC})
+ set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
endif()
# Add executable
@@ -979,16 +973,25 @@ target_link_libraries(Launcher_logic
tomlc99
BuildConfig
Katabasis
+ Qt${QT_VERSION_MAJOR}::Widgets
)
+
+if (UNIX AND NOT CYGWIN AND NOT APPLE)
+ target_link_libraries(Launcher_logic
+ gamemode
+ )
+endif()
+
target_link_libraries(Launcher_logic
- Qt5::Core
- Qt5::Xml
- Qt5::Network
- Qt5::Concurrent
- Qt5::Gui
+ Qt${QT_VERSION_MAJOR}::Core
+ Qt${QT_VERSION_MAJOR}::Xml
+ Qt${QT_VERSION_MAJOR}::Network
+ Qt${QT_VERSION_MAJOR}::Concurrent
+ Qt${QT_VERSION_MAJOR}::Gui
+ Qt${QT_VERSION_MAJOR}::Widgets
+ ${Launcher_QT_LIBS}
)
target_link_libraries(Launcher_logic
- Launcher_iconfix
QuaZip::QuaZip
hoedown
LocalPeer
@@ -1082,6 +1085,14 @@ if(INSTALL_BUNDLE STREQUAL "full")
COMPONENT Runtime
)
endif()
+ # TLS plugins (Qt 6 only)
+ if(EXISTS "${QT_PLUGINS_DIR}/tls")
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/tls"
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ )
+ endif()
else()
# Image formats
install(
@@ -1124,6 +1135,16 @@ if(INSTALL_BUNDLE STREQUAL "full")
REGEX "\\.dSYM" EXCLUDE
)
endif()
+ # TLS plugins (Qt 6 only)
+ if(EXISTS "${QT_PLUGINS_DIR}/tls")
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/tls"
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ REGEX "_debug\\." EXCLUDE
+ REGEX "\\.dSYM" EXCLUDE
+ )
+ endif()
endif()
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"
diff --git a/launcher/Commandline.cpp b/launcher/Commandline.cpp
index 2c0fde64..8e7356bb 100644
--- a/launcher/Commandline.cpp
+++ b/launcher/Commandline.cpp
@@ -1,18 +1,38 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
*
- * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
*
- * 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
+ * Copyright 2013-2021 MultiMC Contributors
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
- * 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.
+ * 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 "Commandline.h"
@@ -47,7 +67,7 @@ QStringList splitArgs(QString args)
if (cchar == '\\')
escape = true;
else if (cchar == inquotes)
- inquotes = 0;
+ inquotes = QChar::Null;
else
current += cchar;
// otherwise
@@ -480,4 +500,4 @@ void Parser::getPrefix(QString &opt, QString &flag)
ParsingError::ParsingError(const QString &what) : std::runtime_error(what.toStdString())
{
}
-} \ No newline at end of file
+}
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 6de20de6..ebb4460d 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -1,4 +1,37 @@
-// Licensed under the Apache-2.0 license. See README.md for details.
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "FileSystem.h"
@@ -346,7 +379,7 @@ bool checkProblemticPathJava(QDir folder)
}
// Win32 crap
-#if defined Q_OS_WIN
+#ifdef Q_OS_WIN
bool called_coinit = false;
@@ -366,7 +399,7 @@ HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args)
}
}
- IShellLink *link;
+ IShellLinkA *link;
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
(LPVOID *)&link);
@@ -454,4 +487,47 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
return false;
#endif
}
+
+QStringList listFolderPaths(QDir root)
+{
+ auto createAbsPath = [](QFileInfo const& entry) { return FS::PathCombine(entry.path(), entry.fileName()); };
+
+ QStringList entries;
+
+ root.refresh();
+ for (auto entry : root.entryInfoList(QDir::Filter::Files)) {
+ entries.append(createAbsPath(entry));
+ }
+
+ for (auto entry : root.entryInfoList(QDir::Filter::AllDirs | QDir::Filter::NoDotAndDotDot)) {
+ entries.append(listFolderPaths(createAbsPath(entry)));
+ }
+
+ return entries;
+}
+
+bool overrideFolder(QString overwritten_path, QString override_path)
+{
+ if (!FS::ensureFolderPathExists(overwritten_path))
+ return false;
+
+ QStringList paths_to_override;
+ QDir root_override (override_path);
+ for (auto file : listFolderPaths(root_override)) {
+ QString destination = file;
+ destination.replace(override_path, overwritten_path);
+
+ qDebug() << QString("Applying override %1 in %2").arg(file, destination);
+
+ if (QFile::exists(destination))
+ QFile::remove(destination);
+ if (!QFile::rename(file, destination)) {
+ qCritical() << QString("Failed to apply override from %1 to %2").arg(file, destination);
+ return false;
+ }
+ }
+
+ return true;
+}
+
}
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 8f6e8b48..fd305b01 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -1,4 +1,37 @@
-// Licensed under the Apache-2.0 license. See README.md for details.
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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
@@ -49,8 +82,8 @@ class copy
public:
copy(const QString & src, const QString & dst)
{
- m_src = src;
- m_dst = dst;
+ m_src.setPath(src);
+ m_dst.setPath(dst);
}
copy & followSymlinks(const bool follow)
{
@@ -120,8 +153,7 @@ bool checkProblemticPathJava(QDir folder);
// Get the Directory representing the User's Desktop
QString getDesktopDir();
-// Create a shortcut at *location*, pointing to *dest* called with the arguments *args*
-// call it *name* and assign it the icon *icon*
-// return true if operation succeeded
-bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation);
+// Overrides one folder with the contents of another, preserving items exclusive to the first folder
+// Equivalent to doing QDir::rename, but allowing for overrides
+bool overrideFolder(QString overwritten_path, QString override_path);
}
diff --git a/launcher/FileSystem_test.cpp b/launcher/FileSystem_test.cpp
index 90ddc499..99ae9269 100644
--- a/launcher/FileSystem_test.cpp
+++ b/launcher/FileSystem_test.cpp
@@ -1,7 +1,6 @@
#include <QTest>
#include <QTemporaryDir>
#include <QStandardPaths>
-#include "TestUtil.h"
#include "FileSystem.h"
@@ -81,7 +80,7 @@ slots:
void test_copy()
{
- QString folder = QFINDTESTDATA("data/test_folder");
+ QString folder = QFINDTESTDATA("testdata/test_folder");
auto f = [&folder]()
{
QTemporaryDir tempDir;
@@ -116,47 +115,6 @@ slots:
{
QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
}
-
-// this is only valid on linux
-// FIXME: implement on windows, OSX, then test.
-#if defined(Q_OS_LINUX)
- void test_createShortcut_data()
- {
- QTest::addColumn<QString>("location");
- QTest::addColumn<QString>("dest");
- QTest::addColumn<QStringList>("args");
- QTest::addColumn<QString>("name");
- QTest::addColumn<QString>("iconLocation");
- QTest::addColumn<QByteArray>("result");
-
- QTest::newRow("unix") << QDir::currentPath()
- << "asdfDest"
- << (QStringList() << "arg1" << "arg2")
- << "asdf"
- << QString()
- #if defined(Q_OS_LINUX)
- << GET_TEST_FILE("data/FileSystem-test_createShortcut-unix")
- #elif defined(Q_OS_WIN)
- << QByteArray()
- #endif
- ;
- }
-
- void test_createShortcut()
- {
- QFETCH(QString, location);
- QFETCH(QString, dest);
- QFETCH(QStringList, args);
- QFETCH(QString, name);
- QFETCH(QString, iconLocation);
- QFETCH(QByteArray, result);
-
- QVERIFY(FS::createShortCut(location, dest, args, name, iconLocation));
- QCOMPARE(QString::fromLocal8Bit(TestsInternal::readFile(location + QDir::separator() + name + ".desktop")), QString::fromLocal8Bit(result));
-
- //QDir().remove(location);
- }
-#endif
};
QTEST_GUILESS_MAIN(FileSystemTest)
diff --git a/launcher/GZip.cpp b/launcher/GZip.cpp
index 0368c32d..067104cf 100644
--- a/launcher/GZip.cpp
+++ b/launcher/GZip.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "GZip.h"
#include <zlib.h>
#include <QByteArray>
@@ -67,7 +102,7 @@ bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes)
return true;
}
- unsigned compLength = std::min(uncompressedBytes.size(), 16);
+ unsigned compLength = qMin(uncompressedBytes.size(), 16);
compressedBytes.clear();
compressedBytes.resize(compLength);
@@ -112,4 +147,4 @@ bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes)
return false;
}
return true;
-} \ No newline at end of file
+}
diff --git a/launcher/GZip_test.cpp b/launcher/GZip_test.cpp
index 3f4d181c..73859fbc 100644
--- a/launcher/GZip_test.cpp
+++ b/launcher/GZip_test.cpp
@@ -1,5 +1,4 @@
#include <QTest>
-#include "TestUtil.h"
#include "GZip.h"
#include <random>
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index 8f68b95f..14e1cd47 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -41,6 +41,7 @@
#include "FileSystem.h"
#include "MMCZip.h"
#include "NullInstance.h"
+#include "icons/IconList.h"
#include "icons/IconUtils.h"
#include "settings/INISettingsObject.h"
@@ -59,9 +60,9 @@
#include "net/ChecksumValidator.h"
#include "ui/dialogs/CustomMessageBox.h"
+#include "ui/dialogs/ScrollMessageBox.h"
#include <algorithm>
-#include <iterator>
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
{
@@ -71,7 +72,8 @@ InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
bool InstanceImportTask::abort()
{
- m_filesNetJob->abort();
+ if (m_filesNetJob)
+ m_filesNetJob->abort();
m_extractFuture.cancel();
return false;
@@ -134,18 +136,20 @@ void InstanceImportTask::processZipPack()
return;
}
- QStringList blacklist = {"instance.cfg", "manifest.json"};
- QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
- bool technicFound = QuaZipDir(m_packZip.get()).exists("/bin/modpack.jar") || QuaZipDir(m_packZip.get()).exists("/bin/version.json");
- QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
- QString modrinthFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "modrinth.index.json");
+ QuaZipDir packZipDir(m_packZip.get());
+
+ // 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;
- if(!mmcFound.isNull())
+
+ // 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)
{
- // process as MultiMC instance/pack
- qDebug() << "MultiMC:" << mmcFound;
- root = mmcFound;
- m_modpackType = ModpackType::MultiMC;
+ // process as Modrinth pack
+ qDebug() << "Modrinth:" << modrinthFound;
+ m_modpackType = ModpackType::Modrinth;
}
else if (technicFound)
{
@@ -155,19 +159,25 @@ void InstanceImportTask::processZipPack()
extractDir.cd(".minecraft");
m_modpackType = ModpackType::Technic;
}
- else if(!flameFound.isNull())
- {
- // process as Flame pack
- qDebug() << "Flame:" << flameFound;
- root = flameFound;
- m_modpackType = ModpackType::Flame;
- }
- else if(!modrinthFound.isNull())
+ else
{
- // process as Modrinth pack
- qDebug() << "Modrinth:" << modrinthFound;
- root = modrinthFound;
- m_modpackType = ModpackType::Modrinth;
+ QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
+ QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
+
+ if (!mmcRoot.isNull())
+ {
+ // process as MultiMC instance/pack
+ qDebug() << "MultiMC:" << mmcRoot;
+ root = mmcRoot;
+ m_modpackType = ModpackType::MultiMC;
+ }
+ else if(!flameRoot.isNull())
+ {
+ // process as Flame pack
+ qDebug() << "Flame:" << flameRoot;
+ root = flameRoot;
+ m_modpackType = ModpackType::Flame;
+ }
}
if(m_modpackType == ModpackType::Unknown)
{
@@ -315,7 +325,7 @@ void InstanceImportTask::processFlame()
// Hack to correct some 'special sauce'...
if(mcVersion.endsWith('.'))
{
- mcVersion.remove(QRegExp("[.]+$"));
+ mcVersion.remove(QRegularExpression("[.]+$"));
logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack."));
}
auto components = instance.getPackProfile();
@@ -384,61 +394,135 @@ void InstanceImportTask::processFlame()
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]()
{
auto results = m_modIdResolver->getResults();
- m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
- for(auto result: results.files)
- {
- QString filename = result.fileName;
- if(!result.required)
- {
- filename += ".disabled";
+ //first check for blocked mods
+ QString text;
+ auto anyBlocked = false;
+ for(const auto& result: results.files.values()) {
+ if (!result.resolved || result.url.isEmpty()) {
+ text += QString("%1: <a href='%2'>%2</a><br/>").arg(result.fileName, result.websiteUrl);
+ anyBlocked = true;
}
+ }
+ if(anyBlocked) {
+ qWarning() << "Blocked mods found, displaying mod list";
+
+ auto message_dialog = new ScrollMessageBox(m_parent,
+ tr("Blocked mods found"),
+ tr("The following mods were blocked on third party launchers.<br/>"
+ "You will need to manually download them and add them to the modpack"),
+ text);
+ message_dialog->setModal(true);
+
+ if (message_dialog->exec()) {
+ m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
+ for (const auto &result: m_modIdResolver->getResults().files) {
+ QString filename = result.fileName;
+ if (!result.required) {
+ filename += ".disabled";
+ }
- auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
- auto path = FS::PathCombine(m_stagingPath , relpath);
+ auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
+ auto path = FS::PathCombine(m_stagingPath, relpath);
- switch(result.type)
- {
- case Flame::File::Type::Folder:
- {
- logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
- // fall-through intentional, we treat these as plain old mods and dump them wherever.
+ switch (result.type) {
+ case Flame::File::Type::Folder: {
+ logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
+ // fall-through intentional, we treat these as plain old mods and dump them wherever.
+ }
+ case Flame::File::Type::SingleFile:
+ case Flame::File::Type::Mod: {
+ if (!result.url.isEmpty()) {
+ qDebug() << "Will download" << result.url << "to" << path;
+ auto dl = Net::Download::makeFile(result.url, path);
+ m_filesNetJob->addNetAction(dl);
+ }
+ break;
+ }
+ case Flame::File::Type::Modpack:
+ logWarning(
+ tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(
+ relpath));
+ break;
+ case Flame::File::Type::Cmod2:
+ case Flame::File::Type::Ctoc:
+ case Flame::File::Type::Unknown:
+ logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
+ break;
+ }
+ }
+ m_modIdResolver.reset();
+ connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() {
+ m_filesNetJob.reset();
+ emitSucceeded();
+ }
+ );
+ connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) {
+ m_filesNetJob.reset();
+ emitFailed(reason);
+ });
+ connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
+ setProgress(current, total);
+ });
+ setStatus(tr("Downloading mods..."));
+ m_filesNetJob->start();
+ } else {
+ m_modIdResolver.reset();
+ emitFailed("Canceled");
+ }
+ } else {
+ //TODO extract to function ?
+ m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
+ for (const auto &result: m_modIdResolver->getResults().files) {
+ QString filename = result.fileName;
+ if (!result.required) {
+ filename += ".disabled";
}
- case Flame::File::Type::SingleFile:
- case Flame::File::Type::Mod:
- {
- qDebug() << "Will download" << result.url << "to" << path;
- auto dl = Net::Download::makeFile(result.url, path);
- m_filesNetJob->addNetAction(dl);
- break;
+
+ auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
+ auto path = FS::PathCombine(m_stagingPath, relpath);
+
+ switch (result.type) {
+ case Flame::File::Type::Folder: {
+ logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
+ // fall-through intentional, we treat these as plain old mods and dump them wherever.
+ }
+ case Flame::File::Type::SingleFile:
+ case Flame::File::Type::Mod: {
+ if (!result.url.isEmpty()) {
+ qDebug() << "Will download" << result.url << "to" << path;
+ auto dl = Net::Download::makeFile(result.url, path);
+ m_filesNetJob->addNetAction(dl);
+ }
+ break;
+ }
+ case Flame::File::Type::Modpack:
+ logWarning(
+ tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(
+ relpath));
+ break;
+ case Flame::File::Type::Cmod2:
+ case Flame::File::Type::Ctoc:
+ case Flame::File::Type::Unknown:
+ logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
+ break;
}
- case Flame::File::Type::Modpack:
- logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath));
- break;
- case Flame::File::Type::Cmod2:
- case Flame::File::Type::Ctoc:
- case Flame::File::Type::Unknown:
- logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
- break;
}
+ m_modIdResolver.reset();
+ connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() {
+ m_filesNetJob.reset();
+ emitSucceeded();
+ }
+ );
+ connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) {
+ m_filesNetJob.reset();
+ emitFailed(reason);
+ });
+ connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
+ setProgress(current, total);
+ });
+ setStatus(tr("Downloading mods..."));
+ m_filesNetJob->start();
}
- m_modIdResolver.reset();
- connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
- {
- m_filesNetJob.reset();
- emitSucceeded();
- }
- );
- connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason)
- {
- m_filesNetJob.reset();
- emitFailed(reason);
- });
- connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total)
- {
- setProgress(current, total);
- });
- setStatus(tr("Downloading mods..."));
- m_filesNetJob->start();
}
);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason)
@@ -497,6 +581,7 @@ void InstanceImportTask::processMultiMC()
emitSucceeded();
}
+// https://docs.modrinth.com/docs/modpacks/format_definition/
void InstanceImportTask::processModrinth()
{
std::vector<Modrinth::File> files;
@@ -514,29 +599,33 @@ void InstanceImportTask::processModrinth()
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
bool had_optional = false;
- for (auto& obj : jsonFiles) {
+ for (auto modInfo : jsonFiles) {
Modrinth::File file;
- file.path = Json::requireString(obj, "path");
-
- auto env = Json::ensureObject(obj, "env");
- QString support = Json::ensureString(env, "client", "unsupported");
- if (support == "unsupported") {
- continue;
- } else if (support == "optional") {
- // TODO: Make a review dialog for choosing which ones the user wants!
- if (!had_optional) {
- had_optional = true;
- auto info = CustomMessageBox::selectable(
- m_parent, tr("Optional mod detected!"),
- tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), QMessageBox::Information);
- info->exec();
- }
+ file.path = Json::requireString(modInfo, "path");
+
+ auto env = Json::ensureObject(modInfo, "env");
+ // 'env' field is optional
+ if (!env.isEmpty()) {
+ QString support = Json::ensureString(env, "client", "unsupported");
+ if (support == "unsupported") {
+ continue;
+ } else if (support == "optional") {
+ // TODO: Make a review dialog for choosing which ones the user wants!
+ if (!had_optional) {
+ had_optional = true;
+ auto info = CustomMessageBox::selectable(
+ m_parent, tr("Optional mod detected!"),
+ tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"),
+ QMessageBox::Information);
+ info->exec();
+ }
- if (file.path.endsWith(".jar"))
- file.path += ".disabled";
+ if (file.path.endsWith(".jar"))
+ file.path += ".disabled";
+ }
}
- QJsonObject hashes = Json::requireObject(obj, "hashes");
+ QJsonObject hashes = Json::requireObject(modInfo, "hashes");
QString hash;
QCryptographicHash::Algorithm hashAlgorithm;
hash = Json::ensureString(hashes, "sha1");
@@ -554,12 +643,28 @@ void InstanceImportTask::processModrinth()
}
file.hash = QByteArray::fromHex(hash.toLatin1());
file.hashAlgorithm = hashAlgorithm;
+
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
// (as Modrinth seems to incorrectly handle spaces)
- file.download = Json::requireString(Json::ensureArray(obj, "downloads").first(), "Download URL for " + file.path);
- if (!file.download.isValid() || !Modrinth::validateDownloadUrl(file.download)) {
- throw JSONValidationError("Download URL for " + file.path + " is not a correctly formatted URL");
+
+ auto download_arr = Json::ensureArray(modInfo, "downloads");
+ for(auto download : download_arr) {
+ qWarning() << download.toString();
+ bool is_last = download.toString() == download_arr.last().toString();
+
+ auto download_url = QUrl(download.toString());
+
+ if (!download_url.isValid()) {
+ qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL")
+ .arg(download_url.toString(), file.path);
+ if(is_last && file.downloads.isEmpty())
+ throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
+ }
+ else {
+ file.downloads.push_back(download_url);
+ }
}
+
files.push_back(file);
}
@@ -590,16 +695,26 @@ void InstanceImportTask::processModrinth()
emitFailed(tr("Could not understand pack index:\n") + e.cause());
return;
}
+
+ auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
- QString overridePath = FS::PathCombine(m_stagingPath, "overrides");
- if (QFile::exists(overridePath)) {
- QString mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
- if (!QFile::rename(overridePath, mcPath)) {
+ auto override_path = FS::PathCombine(m_stagingPath, "overrides");
+ if (QFile::exists(override_path)) {
+ if (!QFile::rename(override_path, mcPath)) {
emitFailed(tr("Could not rename the overrides folder:\n") + "overrides");
return;
}
}
+ // Do client overrides
+ auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides");
+ if (QFile::exists(client_override_path)) {
+ if (!FS::overrideFolder(mcPath, client_override_path)) {
+ emitFailed(tr("Could not rename the client overrides folder:\n") + "client overrides");
+ return;
+ }
+ }
+
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
@@ -607,11 +722,11 @@ void InstanceImportTask::processModrinth()
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", minecraftVersion, true);
if (!fabricVersion.isEmpty())
- components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion, true);
+ components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);
if (!quiltVersion.isEmpty())
- components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion, true);
+ components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion);
if (!forgeVersion.isEmpty())
- components->setComponentVersion("net.minecraftforge", forgeVersion, true);
+ components->setComponentVersion("net.minecraftforge", forgeVersion);
if (m_instIcon != "default")
{
instance.setIconKey(m_instIcon);
@@ -624,13 +739,24 @@ void InstanceImportTask::processModrinth()
instance.saveNow();
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
- for (auto &file : files)
+ for (auto file : files)
{
auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path);
- qDebug() << "Will download" << file.download << "to" << path;
- auto dl = Net::Download::makeFile(file.download, path);
+ qDebug() << "Will try to download" << file.downloads.front() << "to" << path;
+ auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_filesNetJob->addNetAction(dl);
+
+ if (file.downloads.size() > 0) {
+ // FIXME: This really needs to be put into a ConcurrentTask of
+ // MultipleOptionsTask's , once those exist :)
+ connect(dl.get(), &NetAction::failed, [this, &file, path, dl]{
+ auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
+ dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
+ m_filesNetJob->addNetAction(dl);
+ dl->succeeded();
+ });
+ }
}
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
{
diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h
index 5e4d3235..b67d48f3 100644
--- a/launcher/InstanceImportTask.h
+++ b/launcher/InstanceImportTask.h
@@ -42,6 +42,7 @@
#include <QFutureWatcher>
#include "settings/SettingsObject.h"
#include "QObjectPtr.h"
+#include "modplatform/flame/PackManifest.h"
#include <nonstd/optional>
@@ -59,6 +60,10 @@ public:
bool canAbort() const override { return true; }
bool abort() override;
+ const QVector<Flame::File> &getBlockedFiles() const
+ {
+ return m_blockedMods;
+ }
protected:
//! Entry point for tasks.
@@ -87,6 +92,7 @@ private: /* data */
std::unique_ptr<QuaZip> m_packZip;
QFuture<nonstd::optional<QStringList>> m_extractFuture;
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
+ QVector<Flame::File> m_blockedMods;
enum class ModpackType{
Unknown,
MultiMC,
diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp
index 847d897e..fb7103dd 100644
--- a/launcher/InstanceList.cpp
+++ b/launcher/InstanceList.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QDir>
@@ -136,7 +156,7 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
{
case InstancePointerRole:
{
- QVariant v = qVariantFromValue((void *)pdata);
+ QVariant v = QVariant::fromValue((void *)pdata);
return v;
}
case InstanceIDRole:
@@ -252,7 +272,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
QStringList InstanceList::getGroups()
{
- return m_groupNameCache.toList();
+ return m_groupNameCache.values();
}
void InstanceList::deleteGroup(const QString& name)
@@ -353,7 +373,11 @@ QList< InstanceId > InstanceList::discoverInstances()
out.append(id);
qDebug() << "Found instance ID" << id;
}
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ instanceSet = QSet<QString>(out.begin(), out.end());
+#else
instanceSet = out.toSet();
+#endif
m_instancesProbed = true;
return out;
}
@@ -547,8 +571,20 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
auto instanceRoot = FS::PathCombine(m_instDir, id);
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg"));
InstancePtr inst;
- // TODO: Handle incompatible instances
- inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
+
+ instanceSettings->registerSetting("InstanceType", "");
+
+ 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
+ if (inst_type == "OneSix" || inst_type.isEmpty())
+ {
+ inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
+ }
+ else
+ {
+ inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
+ }
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
return inst;
}
diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h
index 357157d0..78fb7016 100644
--- a/launcher/InstancePageProvider.h
+++ b/launcher/InstancePageProvider.h
@@ -33,10 +33,10 @@ public:
values.append(new LogPage(inst));
std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst);
values.append(new VersionPage(onesix.get()));
- auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Mods"), "Loader-mods");
+ auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList());
modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
values.append(modsPage);
- values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList(), "coremods", "coremods", tr("Core mods"), "Core-mods"));
+ values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList()));
values.append(new ResourcePackPage(onesix.get()));
values.append(new TexturePackPage(onesix.get()));
values.append(new ShaderPackPage(onesix.get()));
diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp
index 17278d86..4ba319b8 100644
--- a/launcher/JavaCommon.cpp
+++ b/launcher/JavaCommon.cpp
@@ -1,10 +1,47 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "JavaCommon.h"
+#include "java/JavaUtils.h"
#include "ui/dialogs/CustomMessageBox.h"
#include <MMCStrings.h>
+#include <QRegularExpression>
bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent)
{
- if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegExp("-Xm[sx]"))
+ if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegularExpression("-Xm[sx]"))
|| jvmargs.contains("-XX-MaxHeapSize") || jvmargs.contains("-XX:InitialHeapSize"))
{
auto warnStr = QObject::tr(
@@ -18,7 +55,7 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent)
return false;
}
// block lunacy with passing required version to the JVM
- if (jvmargs.contains(QRegExp("-version:.*"))) {
+ if (jvmargs.contains(QRegularExpression("-version:.*"))) {
auto warnStr = QObject::tr(
"You tried to pass required Java version argument to the JVM (using \"-version:xxx\"). This is not safe and will not be allowed.\n"
"This message will be displayed until you remove this from the JVM arguments.");
@@ -65,6 +102,13 @@ void JavaCommon::javaBinaryWasBad(QWidget *parent, JavaCheckResult result)
CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show();
}
+void JavaCommon::javaCheckNotFound(QWidget *parent)
+{
+ QString text;
+ text += QObject::tr("Java checker library could not be found. Please check your installation");
+ CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show();
+}
+
void JavaCommon::TestCheck::run()
{
if (!JavaCommon::checkJVMArgs(m_args, m_parent))
@@ -72,6 +116,11 @@ void JavaCommon::TestCheck::run()
emit finished();
return;
}
+ if (JavaUtils::getJavaCheckPath().isEmpty()) {
+ javaCheckNotFound(m_parent);
+ emit finished();
+ return;
+ }
checker.reset(new JavaChecker());
connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
SLOT(checkFinished(JavaCheckResult)));
diff --git a/launcher/JavaCommon.h b/launcher/JavaCommon.h
index ca98145c..59cb7a67 100644
--- a/launcher/JavaCommon.h
+++ b/launcher/JavaCommon.h
@@ -10,12 +10,14 @@ namespace JavaCommon
{
bool checkJVMArgs(QString args, QWidget *parent);
- // Show a dialog saying that the Java binary was not usable
- void javaBinaryWasBad(QWidget *parent, JavaCheckResult result);
- // Show a dialog saying that the Java binary was not usable because of bad options
- void javaArgsWereBad(QWidget *parent, JavaCheckResult result);
// Show a dialog saying that the Java binary was usable
void javaWasOk(QWidget *parent, JavaCheckResult result);
+ // Show a dialog saying that the Java binary was not usable because of bad options
+ void javaArgsWereBad(QWidget *parent, JavaCheckResult result);
+ // Show a dialog saying that the Java binary was not usable
+ void javaBinaryWasBad(QWidget *parent, JavaCheckResult result);
+ // Show a dialog if we couldn't find Java Checker
+ void javaCheckNotFound(QWidget *parent);
class TestCheck : public QObject
{
diff --git a/launcher/Json.cpp b/launcher/Json.cpp
index 37ada1aa..06b3d3bd 100644
--- a/launcher/Json.cpp
+++ b/launcher/Json.cpp
@@ -1,4 +1,37 @@
-// Licensed under the Apache-2.0 license. See README.md for details.
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "Json.h"
@@ -22,14 +55,6 @@ void write(const QJsonArray &array, const QString &filename)
write(QJsonDocument(array), filename);
}
-QByteArray toBinary(const QJsonObject &obj)
-{
- return QJsonDocument(obj).toBinaryData();
-}
-QByteArray toBinary(const QJsonArray &array)
-{
- return QJsonDocument(array).toBinaryData();
-}
QByteArray toText(const QJsonObject &obj)
{
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
@@ -48,12 +73,8 @@ QJsonDocument requireDocument(const QByteArray &data, const QString &what)
{
if (isBinaryJson(data))
{
- QJsonDocument doc = QJsonDocument::fromBinaryData(data);
- if (doc.isNull())
- {
- throw JsonException(what + ": Invalid JSON (binary JSON detected)");
- }
- return doc;
+ // FIXME: Is this needed?
+ throw JsonException(what + ": Invalid JSON. Binary JSON unsupported");
}
else
{
diff --git a/launcher/Json.h b/launcher/Json.h
index f2e68f0c..b11a356c 100644
--- a/launcher/Json.h
+++ b/launcher/Json.h
@@ -1,4 +1,37 @@
-// Licensed under the Apache-2.0 license. See README.md for details.
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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
@@ -29,8 +62,6 @@ void write(const QJsonObject &object, const QString &filename);
/// @throw FileSystemException
void write(const QJsonArray &array, const QString &filename);
-QByteArray toBinary(const QJsonObject &obj);
-QByteArray toBinary(const QJsonArray &array);
QByteArray toText(const QJsonObject &obj);
QByteArray toText(const QJsonArray &array);
diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp
index 002c08b9..d36ee3fe 100644
--- a/launcher/LaunchController.cpp
+++ b/launcher/LaunchController.cpp
@@ -105,6 +105,11 @@ void LaunchController::decideAccount()
// Open the account manager.
APPLICATION->ShowGlobalSettings(m_parentWidget, "accounts");
}
+ else if (reply == QMessageBox::No)
+ {
+ // Do not open "profile select" dialog.
+ return;
+ }
}
m_accountToUse = accounts->defaultAccount();
diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp
index 2479f4ff..fbdeed8f 100644
--- a/launcher/LoggedProcess.cpp
+++ b/launcher/LoggedProcess.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "LoggedProcess.h"
#include "MessageLevel.h"
#include <QDebug>
@@ -8,7 +43,11 @@ LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr);
connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus)));
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ connect(this, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError)));
+#else
connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError)));
+#endif
connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange);
}
@@ -157,19 +196,6 @@ void LoggedProcess::on_stateChange(QProcess::ProcessState state)
}
}
-#if defined Q_OS_WIN32
-#include <windows.h>
-#endif
-
-qint64 LoggedProcess::processId() const
-{
-#ifdef Q_OS_WIN
- return pid() ? pid()->dwProcessId : 0;
-#else
- return pid();
-#endif
-}
-
void LoggedProcess::setDetachable(bool detachable)
{
m_is_detachable = detachable;
diff --git a/launcher/LoggedProcess.h b/launcher/LoggedProcess.h
index e52b8a7b..61e74bd9 100644
--- a/launcher/LoggedProcess.h
+++ b/launcher/LoggedProcess.h
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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
+ * 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
+ * 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.
+ * 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
@@ -43,7 +63,6 @@ public:
State state() const;
int exitCode() const;
- qint64 processId() const;
void setDetachable(bool detachable);
diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp
index 8591fcc0..f20d6dff 100644
--- a/launcher/MMCZip.cpp
+++ b/launcher/MMCZip.cpp
@@ -151,23 +151,23 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
continue;
if (mod.type() == Mod::MOD_ZIPFILE)
{
- if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles))
+ if (!mergeZipFiles(&zipOut, mod.fileinfo(), addedFiles))
{
zipOut.close();
QFile::remove(targetJarPath);
- qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
+ qCritical() << "Failed to add" << mod.fileinfo().fileName() << "to the jar.";
return false;
}
}
else if (mod.type() == Mod::MOD_SINGLEFILE)
{
// FIXME: buggy - does not work with addedFiles
- auto filename = mod.filename();
+ auto filename = mod.fileinfo();
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName()))
{
zipOut.close();
QFile::remove(targetJarPath);
- qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
+ qCritical() << "Failed to add" << mod.fileinfo().fileName() << "to the jar.";
return false;
}
addedFiles.insert(filename.fileName());
@@ -176,7 +176,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
{
// untested, but seems to be unused / not possible to reach
// FIXME: buggy - does not work with addedFiles
- auto filename = mod.filename();
+ auto filename = mod.fileinfo();
QString what_to_zip = filename.absoluteFilePath();
QDir dir(what_to_zip);
dir.cdUp();
@@ -193,7 +193,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
{
zipOut.close();
QFile::remove(targetJarPath);
- qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
+ qCritical() << "Failed to add" << mod.fileinfo().fileName() << "to the jar.";
return false;
}
qDebug() << "Adding folder " << filename.fileName() << " from "
@@ -204,7 +204,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
// Make sure we do not continue launching when something is missing or undefined...
zipOut.close();
QFile::remove(targetJarPath);
- qCritical() << "Failed to add unknown mod type" << mod.filename().fileName() << "to the jar.";
+ qCritical() << "Failed to add unknown mod type" << mod.fileinfo().fileName() << "to the jar.";
return false;
}
}
@@ -305,7 +305,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
QString path;
if(name.contains('/') && !name.endsWith('/')){
path = name.section('/', 0, -2) + "/";
- FS::ensureFolderPathExists(path);
+ FS::ensureFolderPathExists(FS::PathCombine(target, path));
name = name.split('/').last();
}
@@ -421,7 +421,7 @@ bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& s
continue;
}
- files->append(e.filePath()); // we want the original paths for MMCZip::compressDirFiles
+ files->append(e); // we want the original paths for MMCZip::compressDirFiles
}
return true;
}
diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp
index 08a02d29..41856fb5 100644
--- a/launcher/ModDownloadTask.cpp
+++ b/launcher/ModDownloadTask.cpp
@@ -1,24 +1,50 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+*
+* 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 <https://www.gnu.org/licenses/>.
+*/
+
#include "ModDownloadTask.h"
+
#include "Application.h"
+#include "minecraft/mod/ModFolderModel.h"
-ModDownloadTask::ModDownloadTask(const QUrl sourceUrl,const QString filename, const std::shared_ptr<ModFolderModel> mods)
-: m_sourceUrl(sourceUrl), mods(mods), filename(filename) {
-}
+ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods, bool is_indexed)
+ : m_mod(mod), m_mod_version(version), mods(mods)
+{
+ if (is_indexed) {
+ m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version));
-void ModDownloadTask::executeTask() {
- setStatus(tr("Downloading mod:\n%1").arg(m_sourceUrl.toString()));
+ addTask(m_update_task);
+ }
m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
- m_filesNetJob->addNetAction(Net::Download::makeFile(m_sourceUrl, mods->dir().absoluteFilePath(filename)));
+ m_filesNetJob->setStatus(tr("Downloading mod:\n%1").arg(m_mod_version.downloadUrl));
+
+ m_filesNetJob->addNetAction(Net::Download::makeFile(m_mod_version.downloadUrl, mods->dir().absoluteFilePath(getFilename())));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ModDownloadTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &ModDownloadTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::failed, this, &ModDownloadTask::downloadFailed);
- m_filesNetJob->start();
+
+ addTask(m_filesNetJob);
+
}
void ModDownloadTask::downloadSucceeded()
{
- emitSucceeded();
m_filesNetJob.reset();
}
@@ -32,8 +58,3 @@ void ModDownloadTask::downloadProgressChanged(qint64 current, qint64 total)
{
emit progress(current, total);
}
-
-bool ModDownloadTask::abort() {
- return m_filesNetJob->abort();
-}
-
diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h
index ddada5a2..b3c25909 100644
--- a/launcher/ModDownloadTask.h
+++ b/launcher/ModDownloadTask.h
@@ -1,28 +1,45 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+*
+* 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 <https://www.gnu.org/licenses/>.
+*/
+
#pragma once
-#include "QObjectPtr.h"
-#include "tasks/Task.h"
-#include "minecraft/mod/ModFolderModel.h"
+
#include "net/NetJob.h"
-#include <QUrl>
+#include "tasks/SequentialTask.h"
+#include "modplatform/ModIndex.h"
+#include "minecraft/mod/tasks/LocalModUpdateTask.h"
-class ModDownloadTask : public Task {
+class ModFolderModel;
+
+class ModDownloadTask : public SequentialTask {
Q_OBJECT
public:
- explicit ModDownloadTask(const QUrl sourceUrl, const QString filename, const std::shared_ptr<ModFolderModel> mods);
- const QString& getFilename() const { return filename; }
-
-public slots:
- bool abort() override;
-protected:
- //! Entry point for tasks.
- void executeTask() override;
+ explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods, bool is_indexed);
+ const QString& getFilename() const { return m_mod_version.fileName; }
private:
- QUrl m_sourceUrl;
- NetJob::Ptr m_filesNetJob;
+ ModPlatform::IndexedPack m_mod;
+ ModPlatform::IndexedVersion m_mod_version;
const std::shared_ptr<ModFolderModel> mods;
- const QString filename;
+
+ NetJob::Ptr m_filesNetJob;
+ LocalModUpdateTask::Ptr m_update_task;
void downloadProgressChanged(qint64 current, qint64 total);
diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h
index ed421433..9b0a9331 100644
--- a/launcher/NullInstance.h
+++ b/launcher/NullInstance.h
@@ -39,6 +39,10 @@ public:
{
return QProcessEnvironment();
}
+ QProcessEnvironment createLaunchEnvironment() override
+ {
+ return QProcessEnvironment();
+ }
QMap<QString, QString> getVariables() const override
{
return QMap<QString, QString>();
diff --git a/launcher/Version.h b/launcher/Version.h
index 9fe12d6d..aceb7a07 100644
--- a/launcher/Version.h
+++ b/launcher/Version.h
@@ -1,6 +1,42 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QString>
+#include <QStringView>
#include <QList>
class QUrl;
@@ -39,13 +75,21 @@ private:
break;
}
}
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ auto numPart = QStringView{m_fullString}.left(cutoff);
+#else
auto numPart = m_fullString.leftRef(cutoff);
+#endif
if(numPart.size())
{
numValid = true;
m_numPart = numPart.toInt();
}
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ auto stringPart = QStringView{m_fullString}.mid(cutoff);
+#else
auto stringPart = m_fullString.midRef(cutoff);
+#endif
if(stringPart.size())
{
m_stringPart = stringPart.toString();
diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp
index b9a87c9c..032f21f9 100644
--- a/launcher/VersionProxyModel.cpp
+++ b/launcher/VersionProxyModel.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "VersionProxyModel.h"
#include "Application.h"
#include <QSortFilterProxyModel>
@@ -208,7 +243,8 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const
{
return APPLICATION->getThemedIcon("bug");
}
- auto pixmap = QPixmapCache::find("placeholder");
+ QPixmap pixmap;
+ QPixmapCache::find("placeholder", &pixmap);
if(!pixmap)
{
QPixmap px(16,16);
@@ -216,7 +252,7 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const
QPixmapCache::insert("placeholder", px);
return px;
}
- return *pixmap;
+ return pixmap;
}
}
default:
diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp
index c269d10a..3a223d1b 100644
--- a/launcher/icons/IconList.cpp
+++ b/launcher/icons/IconList.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "IconList.h"
@@ -56,6 +76,15 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
emit iconUpdated({});
}
+void IconList::sortIconList()
+{
+ qDebug() << "Sorting icon list...";
+ std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) {
+ return a.m_key.localeAwareCompare(b.m_key) < 0;
+ });
+ reindex();
+}
+
void IconList::directoryChanged(const QString &path)
{
QDir new_dir (path);
@@ -77,7 +106,11 @@ void IconList::directoryChanged(const QString &path)
QString &foo = (*it);
foo = m_dir.filePath(foo);
}
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ QSet<QString> new_set(new_list.begin(), new_list.end());
+#else
auto new_set = new_list.toSet();
+#endif
QList<QString> current_list;
for (auto &it : icons)
{
@@ -85,7 +118,11 @@ void IconList::directoryChanged(const QString &path)
continue;
current_list.push_back(it.m_images[IconType::FileBased].filename);
}
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ QSet<QString> current_set(current_list.begin(), current_list.end());
+#else
QSet<QString> current_set = current_list.toSet();
+#endif
QSet<QString> to_remove = current_set;
to_remove -= new_set;
@@ -141,6 +178,8 @@ void IconList::directoryChanged(const QString &path)
emit iconUpdated(key);
}
}
+
+ sortIconList();
}
void IconList::fileChanged(const QString &path)
@@ -273,7 +312,7 @@ void IconList::installIcons(const QStringList &iconFiles)
QFileInfo fileinfo(file);
if (!fileinfo.isReadable() || !fileinfo.isFile())
continue;
- QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
+ QString target = FS::PathCombine(getDirectory(), fileinfo.fileName());
QString suffix = fileinfo.suffix();
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
@@ -290,7 +329,7 @@ void IconList::installIcon(const QString &file, const QString &name)
if(!fileinfo.isReadable() || !fileinfo.isFile())
return;
- QString target = FS::PathCombine(m_dir.dirName(), name);
+ QString target = FS::PathCombine(getDirectory(), name);
QFile::copy(file, target);
}
diff --git a/launcher/icons/IconList.h b/launcher/icons/IconList.h
index ebbb52e2..f9f49e7f 100644
--- a/launcher/icons/IconList.h
+++ b/launcher/icons/IconList.h
@@ -71,6 +71,7 @@ private:
// hide assign op
IconList &operator=(const IconList &) = delete;
void reindex();
+ void sortIconList();
public slots:
void directoryChanged(const QString &path);
diff --git a/launcher/icons/MMCIcon.cpp b/launcher/icons/MMCIcon.cpp
index f0b82ec9..436ef75f 100644
--- a/launcher/icons/MMCIcon.cpp
+++ b/launcher/icons/MMCIcon.cpp
@@ -1,21 +1,41 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "MMCIcon.h"
#include <QFileInfo>
-#include <xdgicon.h>
+#include <QIcon>
IconType operator--(IconType &t, int)
{
@@ -63,7 +83,7 @@ QIcon MMCIcon::icon() const
if(!icon.isNull())
return icon;
// FIXME: inject this.
- return XdgIcon::fromTheme(m_images[m_current_type].key);
+ return QIcon::fromTheme(m_images[m_current_type].key);
}
void MMCIcon::remove(IconType rm_type)
diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp
index 946599c5..041583d1 100644
--- a/launcher/java/JavaChecker.cpp
+++ b/launcher/java/JavaChecker.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "JavaChecker.h"
#include <QFile>
@@ -16,7 +51,13 @@ JavaChecker::JavaChecker(QObject *parent) : QObject(parent)
void JavaChecker::performCheck()
{
- QString checkerJar = FS::PathCombine(APPLICATION->getJarsPath(), "JavaCheck.jar");
+ QString checkerJar = JavaUtils::getJavaCheckPath();
+
+ if (checkerJar.isEmpty())
+ {
+ qDebug() << "Java checker library could not be found. Please check your installation.";
+ return;
+ }
QStringList args;
@@ -47,7 +88,11 @@ void JavaChecker::performCheck()
qDebug() << "Running java checker: " + m_path + args.join(" ");;
connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus)));
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ connect(process.get(), SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
+#else
connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
+#endif
connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady()));
connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady()));
connect(&killTimer, SIGNAL(timeout()), SLOT(timeout()));
@@ -99,7 +144,12 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
bool success = true;
QMap<QString, QString> results;
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ QStringList lines = m_stdout.split("\n", Qt::SkipEmptyParts);
+#else
QStringList lines = m_stdout.split("\n", QString::SkipEmptyParts);
+#endif
for(QString line : lines)
{
line = line.trimmed();
@@ -108,7 +158,11 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
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;
diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp
index 9b745095..0249bd22 100644
--- a/launcher/java/JavaInstallList.cpp
+++ b/launcher/java/JavaInstallList.cpp
@@ -1,21 +1,40 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QtNetwork>
#include <QtXml>
-#include <QRegExp>
#include <QDebug>
@@ -81,7 +100,7 @@ QVariant JavaInstallList::data(const QModelIndex &index, int role) const
switch (role)
{
case VersionPointerRole:
- return qVariantFromValue(m_vlist[index.row()]);
+ return QVariant::fromValue(m_vlist[index.row()]);
case VersionIdRole:
return version->descriptor();
case VersionRole:
diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp
index 65a8b1db..c2b776ae 100644
--- a/launcher/java/JavaUtils.cpp
+++ b/launcher/java/JavaUtils.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QStringList>
@@ -24,6 +44,7 @@
#include "java/JavaUtils.h"
#include "java/JavaInstallList.h"
#include "FileSystem.h"
+#include "Application.h"
#define IBUS "@im=ibus"
@@ -176,25 +197,25 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
archType = "32";
HKEY jreKey;
- if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0,
+ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyName.toStdWString().c_str(), 0,
KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS)
{
// Read the current type version from the registry.
// This will be used to find any key that contains the JavaHome value.
char *value = new char[0];
DWORD valueSz = 0;
- if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) ==
+ if (RegQueryValueExW(jreKey, L"CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) ==
ERROR_MORE_DATA)
{
value = new char[valueSz];
- RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz);
+ RegQueryValueExW(jreKey, L"CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz);
}
TCHAR subKeyName[255];
DWORD subKeyNameSize, numSubKeys, retCode;
// Get the number of subkeys
- RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL,
+ RegQueryInfoKeyW(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL,
NULL, NULL);
// Iterate until RegEnumKeyEx fails
@@ -203,31 +224,32 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
for (DWORD i = 0; i < numSubKeys; i++)
{
subKeyNameSize = 255;
- retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL,
- NULL);
+ retCode = RegEnumKeyExW(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL,
+ NULL);
+ QString newSubkeyName = QString::fromWCharArray(subKeyName);
if (retCode == ERROR_SUCCESS)
{
// Now open the registry key for the version that we just got.
- QString newKeyName = keyName + "\\" + subKeyName + subkeySuffix;
+ QString newKeyName = keyName + "\\" + newSubkeyName + subkeySuffix;
HKEY newKey;
- if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0,
- KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS)
+ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, newKeyName.toStdWString().c_str(), 0,
+ KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS)
{
// Read the JavaHome value to find where Java is installed.
value = new char[0];
valueSz = 0;
- if (RegQueryValueEx(newKey, keyJavaDir.toStdString().c_str(), NULL, NULL, (BYTE *)value,
- &valueSz) == ERROR_MORE_DATA)
+ if (RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE *)value,
+ &valueSz) == ERROR_MORE_DATA)
{
value = new char[valueSz];
- RegQueryValueEx(newKey, keyJavaDir.toStdString().c_str(), NULL, NULL, (BYTE *)value,
- &valueSz);
+ RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE *)value,
+ &valueSz);
// Now, we construct the version object and add it to the list.
JavaInstallPtr javaVersion(new JavaInstall());
- javaVersion->id = subKeyName;
+ javaVersion->id = newSubkeyName;
javaVersion->arch = archType;
javaVersion->path =
QDir(FS::PathCombine(value, "bin")).absoluteFilePath("javaw.exe");
@@ -437,3 +459,8 @@ QList<QString> JavaUtils::FindJavaPaths()
return addJavasFromEnv(javas);
}
#endif
+
+QString JavaUtils::getJavaCheckPath()
+{
+ return APPLICATION->getJarPath("JavaCheck.jar");
+}
diff --git a/launcher/java/JavaUtils.h b/launcher/java/JavaUtils.h
index 3152d143..26d8003b 100644
--- a/launcher/java/JavaUtils.h
+++ b/launcher/java/JavaUtils.h
@@ -39,4 +39,6 @@ public:
#ifdef Q_OS_WIN
QList<JavaInstallPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName, QString keyJavaDir, QString subkeySuffix = "");
#endif
+
+ static QString getJavaCheckPath();
};
diff --git a/launcher/java/JavaVersion_test.cpp b/launcher/java/JavaVersion_test.cpp
index 10ae13a7..545947ef 100644
--- a/launcher/java/JavaVersion_test.cpp
+++ b/launcher/java/JavaVersion_test.cpp
@@ -1,5 +1,4 @@
#include <QTest>
-#include "TestUtil.h"
#include "java/JavaVersion.h"
diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp
index d5442a2b..3aa95052 100644
--- a/launcher/launch/LaunchTask.cpp
+++ b/launcher/launch/LaunchTask.cpp
@@ -282,6 +282,23 @@ void LaunchTask::emitFailed(QString reason)
Task::emitFailed(reason);
}
+void LaunchTask::substituteVariables(const QStringList &args) const
+{
+ auto variables = m_instance->getVariables();
+ auto envVariables = QProcessEnvironment::systemEnvironment();
+
+ for (auto arg : args) {
+ for (auto key : variables)
+ {
+ arg.replace("$" + key, variables.value(key));
+ }
+ for (auto env : envVariables.keys())
+ {
+ arg.replace("$" + env, envVariables.value(env));
+ }
+ }
+}
+
QString LaunchTask::substituteVariables(const QString &cmd) const
{
QString out = cmd;
diff --git a/launcher/launch/LaunchTask.h b/launcher/launch/LaunchTask.h
index a1e891ac..2efe1fa2 100644
--- a/launcher/launch/LaunchTask.h
+++ b/launcher/launch/LaunchTask.h
@@ -1,18 +1,38 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * Authors: Orochimarufan <orochimarufan.x3@gmail.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.
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
*
- * 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.
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
+ *
+ * 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
@@ -85,6 +105,7 @@ public: /* methods */
shared_qobject_ptr<LogModel> getLogModel();
public:
+ void substituteVariables(const QStringList &args) const;
QString substituteVariables(const QString &cmd) const;
QString censorPrivateInfo(QString in);
diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp
index 3226fae7..db56b652 100644
--- a/launcher/launch/steps/CheckJava.cpp
+++ b/launcher/launch/steps/CheckJava.cpp
@@ -34,6 +34,7 @@
*/
#include "CheckJava.h"
+#include "java/JavaUtils.h"
#include <launch/LaunchTask.h>
#include <FileSystem.h>
#include <QStandardPaths>
@@ -71,15 +72,26 @@ void CheckJava::executeTask()
emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::Launcher);
}
+ if (JavaUtils::getJavaCheckPath().isEmpty())
+ {
+ const char *reason = QT_TR_NOOP("Java checker library could not be found. Please check your installation.");
+ emit logLine(tr(reason), MessageLevel::Fatal);
+ emitFailed(tr(reason));
+ return;
+ }
+
QFileInfo javaInfo(realJavaPath);
qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
auto storedArchitecture = settings->get("JavaArchitecture").toString();
+ auto storedRealArchitecture = settings->get("JavaRealArchitecture").toString();
auto storedVersion = settings->get("JavaVersion").toString();
auto storedVendor = settings->get("JavaVendor").toString();
m_javaUnixTime = javaUnixTime;
// if timestamps are not the same, or something is missing, check!
- if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0 || storedVendor.size() == 0)
+ if (javaUnixTime != storedUnixTime || storedVersion.size() == 0
+ || storedArchitecture.size() == 0 || storedRealArchitecture.size() == 0
+ || storedVendor.size() == 0)
{
m_JavaChecker = new JavaChecker();
emit logLine(QString("Checking Java version..."), MessageLevel::Launcher);
@@ -92,8 +104,9 @@ void CheckJava::executeTask()
{
auto verString = instance->settings()->get("JavaVersion").toString();
auto archString = instance->settings()->get("JavaArchitecture").toString();
+ auto realArchString = settings->get("JavaRealArchitecture").toString();
auto vendorString = instance->settings()->get("JavaVendor").toString();
- printJavaInfo(verString, archString, vendorString);
+ printJavaInfo(verString, archString, realArchString, vendorString);
}
emitSucceeded();
}
@@ -124,10 +137,11 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
case JavaCheckResult::Validity::Valid:
{
auto instance = m_parent->instance();
- printJavaInfo(result.javaVersion.toString(), result.realPlatform, result.javaVendor);
+ printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor);
printSystemInfo(true, result.is_64bit);
instance->settings()->set("JavaVersion", result.javaVersion.toString());
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
+ instance->settings()->set("JavaRealArchitecture", result.realPlatform);
instance->settings()->set("JavaVendor", result.javaVendor);
instance->settings()->set("JavaTimestamp", m_javaUnixTime);
emitSucceeded();
@@ -136,9 +150,10 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
}
}
-void CheckJava::printJavaInfo(const QString& version, const QString& architecture, const QString & vendor)
+void CheckJava::printJavaInfo(const QString& version, const QString& architecture, const QString& realArchitecture, const QString & vendor)
{
- emit logLine(QString("Java is version %1, using %2 architecture, from %3.\n\n").arg(version, architecture, vendor), MessageLevel::Launcher);
+ emit logLine(QString("Java is version %1, using %2 (%3) architecture, from %4.\n\n")
+ .arg(version, architecture, realArchitecture, vendor), MessageLevel::Launcher);
}
void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit)
diff --git a/launcher/launch/steps/CheckJava.h b/launcher/launch/steps/CheckJava.h
index 68cd618b..d084b132 100644
--- a/launcher/launch/steps/CheckJava.h
+++ b/launcher/launch/steps/CheckJava.h
@@ -35,7 +35,7 @@ private slots:
void checkJavaFinished(JavaCheckResult result);
private:
- void printJavaInfo(const QString & version, const QString & architecture, const QString & vendor);
+ void printJavaInfo(const QString & version, const QString & architecture, const QString & realArchitecture, const QString & vendor);
void printSystemInfo(bool javaIsKnown, bool javaIs64bit);
private:
diff --git a/launcher/launch/steps/PostLaunchCommand.cpp b/launcher/launch/steps/PostLaunchCommand.cpp
index 143eb441..cf765bc0 100644
--- a/launcher/launch/steps/PostLaunchCommand.cpp
+++ b/launcher/launch/steps/PostLaunchCommand.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "PostLaunchCommand.h"
@@ -27,9 +47,19 @@ PostLaunchCommand::PostLaunchCommand(LaunchTask *parent) : LaunchStep(parent)
void PostLaunchCommand::executeTask()
{
+ //FIXME: where to put this?
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
+ auto args = QProcess::splitCommand(m_command);
+ m_parent->substituteVariables(args);
+
+ emit logLine(tr("Running Post-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher);
+ const QString program = args.takeFirst();
+ m_process.start(program, args);
+#else
QString postlaunch_cmd = m_parent->substituteVariables(m_command);
emit logLine(tr("Running Post-Launch command: %1").arg(postlaunch_cmd), MessageLevel::Launcher);
m_process.start(postlaunch_cmd);
+#endif
}
void PostLaunchCommand::on_state(LoggedProcess::State state)
diff --git a/launcher/launch/steps/PreLaunchCommand.cpp b/launcher/launch/steps/PreLaunchCommand.cpp
index 1a0889c8..bf7d27eb 100644
--- a/launcher/launch/steps/PreLaunchCommand.cpp
+++ b/launcher/launch/steps/PreLaunchCommand.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "PreLaunchCommand.h"
@@ -28,9 +48,18 @@ PreLaunchCommand::PreLaunchCommand(LaunchTask *parent) : LaunchStep(parent)
void PreLaunchCommand::executeTask()
{
//FIXME: where to put this?
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
+ auto args = QProcess::splitCommand(m_command);
+ m_parent->substituteVariables(args);
+
+ emit logLine(tr("Running Pre-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher);
+ const QString program = args.takeFirst();
+ m_process.start(program, args);
+#else
QString prelaunch_cmd = m_parent->substituteVariables(m_command);
emit logLine(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd), MessageLevel::Launcher);
m_process.start(prelaunch_cmd);
+#endif
}
void PreLaunchCommand::on_state(LoggedProcess::State state)
diff --git a/launcher/main.cpp b/launcher/main.cpp
index 85c5fdee..85fe1260 100644
--- a/launcher/main.cpp
+++ b/launcher/main.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "Application.h"
// #define BREAK_INFINITE_LOOP
@@ -24,11 +59,9 @@ int main(int argc, char *argv[])
return 42;
#endif
+#if QT_VERSION <= QT_VERSION_CHECK(6, 0, 0)
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
-
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
- QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
// initialize Qt
diff --git a/launcher/meta/BaseEntity.cpp b/launcher/meta/BaseEntity.cpp
index 84155922..de4e1012 100644
--- a/launcher/meta/BaseEntity.cpp
+++ b/launcher/meta/BaseEntity.cpp
@@ -75,7 +75,16 @@ Meta::BaseEntity::~BaseEntity()
QUrl Meta::BaseEntity::url() const
{
- return QUrl(BuildConfig.META_URL).resolved(localFilename());
+ auto s = APPLICATION->settings();
+ QString metaOverride = s->get("MetaURLOverride").toString();
+ if(metaOverride.isEmpty())
+ {
+ return QUrl(BuildConfig.META_URL).resolved(localFilename());
+ }
+ else
+ {
+ return QUrl(metaOverride).resolved(localFilename());
+ }
}
bool Meta::BaseEntity::loadLocalFile()
diff --git a/launcher/meta/Index_test.cpp b/launcher/meta/Index_test.cpp
index 5d3ddc50..261858c4 100644
--- a/launcher/meta/Index_test.cpp
+++ b/launcher/meta/Index_test.cpp
@@ -1,5 +1,4 @@
#include <QTest>
-#include "TestUtil.h"
#include "meta/Index.h"
#include "meta/VersionList.h"
diff --git a/launcher/minecraft/AssetsUtils.cpp b/launcher/minecraft/AssetsUtils.cpp
index 7290aeb4..15062c2b 100644
--- a/launcher/minecraft/AssetsUtils.cpp
+++ b/launcher/minecraft/AssetsUtils.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+ *
+ * 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
+ * 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
+ * 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.
+ * 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 <QFileInfo>
@@ -297,7 +317,7 @@ NetAction::Ptr AssetObject::getDownloadAction()
auto rawHash = QByteArray::fromHex(hash.toLatin1());
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
}
- objectDL->m_total_progress = size;
+ objectDL->setProgress(objectDL->getProgress(), size);
return objectDL;
}
return nullptr;
diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp
index ff7ed0af..6db21622 100644
--- a/launcher/minecraft/ComponentUpdateTask.cpp
+++ b/launcher/minecraft/ComponentUpdateTask.cpp
@@ -197,6 +197,10 @@ void ComponentUpdateTask::loadComponents()
{
remoteLoadFailed(taskIndex, error);
});
+ connect(indexLoadTask.get(), &Task::aborted, [=]()
+ {
+ remoteLoadFailed(taskIndex, tr("Aborted"));
+ });
taskIndex++;
}
}
@@ -243,6 +247,10 @@ void ComponentUpdateTask::loadComponents()
{
remoteLoadFailed(taskIndex, error);
});
+ connect(loadTask.get(), &Task::aborted, [=]()
+ {
+ remoteLoadFailed(taskIndex, tr("Aborted"));
+ });
RemoteLoadStatus status;
status.type = loadType;
status.PackProfileIndex = componentIndex;
diff --git a/launcher/minecraft/GradleSpecifier.h b/launcher/minecraft/GradleSpecifier.h
index d9bb0207..27514ab9 100644
--- a/launcher/minecraft/GradleSpecifier.h
+++ b/launcher/minecraft/GradleSpecifier.h
@@ -1,7 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QString>
#include <QStringList>
+#include <QRegularExpression>
#include "DefaultVariable.h"
struct GradleSpecifier
@@ -25,20 +61,21 @@ struct GradleSpecifier
4 "jdk15"
5 "jar"
*/
- QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(?::([^:@]+))?" "(?:@([^:@]+))?");
- m_valid = matcher.exactMatch(value);
+ QRegularExpression matcher(QRegularExpression::anchoredPattern("([^:@]+):([^:@]+):([^:@]+)" "(?::([^:@]+))?" "(?:@([^:@]+))?"));
+ QRegularExpressionMatch match = matcher.match(value);
+ m_valid = match.hasMatch();
if(!m_valid) {
m_invalidValue = value;
return *this;
}
- auto elements = matcher.capturedTexts();
- m_groupId = elements[1];
- m_artifactId = elements[2];
- m_version = elements[3];
- m_classifier = elements[4];
- if(!elements[5].isEmpty())
+ auto elements = match.captured();
+ m_groupId = match.captured(1);
+ m_artifactId = match.captured(2);
+ m_version = match.captured(3);
+ m_classifier = match.captured(4);
+ if(match.lastCapturedIndex() >= 5)
{
- m_extension = elements[5];
+ m_extension = match.captured(5);
}
return *this;
}
diff --git a/launcher/minecraft/GradleSpecifier_test.cpp b/launcher/minecraft/GradleSpecifier_test.cpp
index 0900c9d8..a062dfac 100644
--- a/launcher/minecraft/GradleSpecifier_test.cpp
+++ b/launcher/minecraft/GradleSpecifier_test.cpp
@@ -1,5 +1,4 @@
#include <QTest>
-#include "TestUtil.h"
#include "minecraft/GradleSpecifier.h"
diff --git a/launcher/minecraft/Library_test.cpp b/launcher/minecraft/Library_test.cpp
index 47531ad6..834dd558 100644
--- a/launcher/minecraft/Library_test.cpp
+++ b/launcher/minecraft/Library_test.cpp
@@ -1,5 +1,4 @@
#include <QTest>
-#include "TestUtil.h"
#include "minecraft/MojangVersionFormat.h"
#include "minecraft/OneSixVersionFormat.h"
@@ -11,15 +10,14 @@ class LibraryTest : public QObject
{
Q_OBJECT
private:
- LibraryPtr readMojangJson(const char *file)
+ LibraryPtr readMojangJson(const QString path)
{
- auto path = QFINDTESTDATA(file);
QFile jsonFile(path);
jsonFile.open(QIODevice::ReadOnly);
auto data = jsonFile.readAll();
jsonFile.close();
ProblemContainer problems;
- return MojangVersionFormat::libraryFromJson(problems, QJsonDocument::fromJson(data).object(), file);
+ return MojangVersionFormat::libraryFromJson(problems, QJsonDocument::fromJson(data).object(), path);
}
// get absolute path to expected storage, assuming default cache prefix
QStringList getStorage(QString relative)
@@ -32,7 +30,7 @@ slots:
{
cache.reset(new HttpMetaCache());
cache->addBase("libraries", QDir("libraries").absolutePath());
- dataDir = QDir("data").absolutePath();
+ dataDir = QDir(QFINDTESTDATA("testdata")).absolutePath();
}
void test_legacy()
{
@@ -74,14 +72,14 @@ slots:
QCOMPARE(test.isNative(), false);
QStringList failedFiles;
test.setHint("local");
- auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString("data"));
+ auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QFINDTESTDATA("testdata"));
QCOMPARE(downloads.size(), 0);
qDebug() << failedFiles;
QCOMPARE(failedFiles.size(), 0);
QStringList jar, native, native32, native64;
- test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString("data"));
- QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()});
+ test.getApplicableFiles(currentSystem, jar, native, native32, native64, QFINDTESTDATA("testdata"));
+ QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
@@ -167,20 +165,20 @@ slots:
test.setRepositoryURL("file://foo/bar");
{
QStringList jar, native, native32, native64;
- test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString("data"));
+ test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QFINDTESTDATA("testdata"));
QCOMPARE(jar, {});
QCOMPARE(native, {});
- QCOMPARE(native32, {QFileInfo("data/testname-testversion-linux-32.jar").absoluteFilePath()});
- QCOMPARE(native64, {QFileInfo("data/testname-testversion-linux-64.jar").absoluteFilePath()});
+ QCOMPARE(native32, {QFileInfo(QFINDTESTDATA("testdata/testname-testversion-linux-32.jar")).absoluteFilePath()});
+ QCOMPARE(native64, {QFileInfo(QFINDTESTDATA("testdata") + "/testname-testversion-linux-64.jar").absoluteFilePath()});
QStringList failedFiles;
- auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString("data"));
+ auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata"));
QCOMPARE(dls.size(), 0);
- QCOMPARE(failedFiles, {"data/testname-testversion-linux-64.jar"});
+ QCOMPARE(failedFiles, {QFileInfo(QFINDTESTDATA("testdata") + "/testname-testversion-linux-64.jar").absoluteFilePath()});
}
}
void test_onenine()
{
- auto test = readMojangJson("data/lib-simple.json");
+ auto test = readMojangJson(QFINDTESTDATA("testdata/lib-simple.json"));
{
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
@@ -199,41 +197,41 @@ slots:
test->setHint("local");
{
QStringList jar, native, native32, native64;
- test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data"));
- QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()});
+ test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QFINDTESTDATA("testdata"));
+ QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
{
QStringList failedFiles;
- auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data"));
+ auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata"));
QCOMPARE(dls.size(), 0);
QCOMPARE(failedFiles, {});
}
}
void test_onenine_local_override()
{
- auto test = readMojangJson("data/lib-simple.json");
+ auto test = readMojangJson(QFINDTESTDATA("testdata/lib-simple.json"));
test->setHint("local");
{
QStringList jar, native, native32, native64;
- test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data"));
- QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()});
+ test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QFINDTESTDATA("testdata"));
+ QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
{
QStringList failedFiles;
- auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data"));
+ auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata"));
QCOMPARE(dls.size(), 0);
QCOMPARE(failedFiles, {});
}
}
void test_onenine_native()
{
- auto test = readMojangJson("data/lib-native.json");
+ auto test = readMojangJson(QFINDTESTDATA("testdata/lib-native.json"));
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
QCOMPARE(jar, QStringList());
@@ -248,7 +246,7 @@ slots:
}
void test_onenine_native_arch()
{
- auto test = readMojangJson("data/lib-native-arch.json");
+ auto test = readMojangJson(QFINDTESTDATA("testdata/lib-native-arch.json"));
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index e20dc24c..abc022b6 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* 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
@@ -153,6 +154,12 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
+ // Peformance related options
+ auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
+ m_settings->registerOverride(globalSettings->getSetting("EnableFeralGamemode"), performanceOverride);
+ m_settings->registerOverride(globalSettings->getSetting("EnableMangoHud"), performanceOverride);
+ m_settings->registerOverride(globalSettings->getSetting("UseDiscreteGpu"), performanceOverride);
+
// Game time
auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false);
m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride);
@@ -167,6 +174,8 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
+ m_settings->set("InstanceType", "OneSix");
+
m_components.reset(new PackProfile(this));
}
@@ -432,27 +441,57 @@ QProcessEnvironment MinecraftInstance::createEnvironment()
return env;
}
+QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
+{
+ // prepare the process environment
+ QProcessEnvironment env = createEnvironment();
+
+#ifdef Q_OS_LINUX
+ if (settings()->get("EnableMangoHud").toBool())
+ {
+ auto preload = env.value("LD_PRELOAD", "") + ":libMangoHud_dlsym.so:libMangoHud.so";
+ auto lib_path = env.value("LD_LIBRARY_PATH", "") + ":/usr/local/$LIB/mangohud/:/usr/$LIB/mangohud/";
+
+ env.insert("LD_PRELOAD", preload);
+ env.insert("LD_LIBRARY_PATH", lib_path);
+ env.insert("MANGOHUD", "1");
+ }
+
+ if (settings()->get("UseDiscreteGpu").toBool())
+ {
+ // Open Source Drivers
+ env.insert("DRI_PRIME", "1");
+ // Proprietary Nvidia Drivers
+ env.insert("__NV_PRIME_RENDER_OFFLOAD", "1");
+ env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only");
+ env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
+ }
+#endif
+
+ return env;
+}
+
static QString replaceTokensIn(QString text, QMap<QString, QString> with)
{
+ // TODO: does this still work??
QString result;
- QRegExp token_regexp("\\$\\{(.+)\\}");
- token_regexp.setMinimal(true);
+ QRegularExpression token_regexp("\\$\\{(.+)\\}", QRegularExpression::InvertedGreedinessOption);
QStringList list;
- int tail = 0;
- int head = 0;
- while ((head = token_regexp.indexIn(text, head)) != -1)
+ QRegularExpressionMatchIterator i = token_regexp.globalMatch(text);
+ int lastCapturedEnd = 0;
+ while (i.hasNext())
{
- result.append(text.mid(tail, head - tail));
- QString key = token_regexp.cap(1);
+ QRegularExpressionMatch match = i.next();
+ result.append(text.mid(lastCapturedEnd, match.capturedStart()));
+ QString key = match.captured(1);
auto iter = with.find(key);
if (iter != with.end())
{
result.append(*iter);
}
- head += token_regexp.matchedLength();
- tail = head;
+ lastCapturedEnd = match.capturedEnd();
}
- result.append(text.mid(tail));
+ result.append(text.mid(lastCapturedEnd));
return result;
}
@@ -487,9 +526,8 @@ QStringList MinecraftInstance::processMinecraftArgs(
}
}
- // blatant self-promotion.
- token_mapping["profile_name"] = token_mapping["version_name"] = BuildConfig.LAUNCHER_NAME;
-
+ token_mapping["profile_name"] = name();
+ token_mapping["version_name"] = profile->getMinecraftVersion();
token_mapping["version_type"] = profile->getMinecraftVersionType();
QString absRootDir = QDir(gameRoot()).absolutePath();
@@ -502,7 +540,11 @@ QStringList MinecraftInstance::processMinecraftArgs(
token_mapping["assets_root"] = absAssetsDir;
token_mapping["assets_index_name"] = assets->id;
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ QStringList parts = args_pattern.split(' ', Qt::SkipEmptyParts);
+#else
QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts);
+#endif
for (int i = 0; i < parts.length(); i++)
{
parts[i] = replaceTokensIn(parts[i], token_mapping);
@@ -659,23 +701,23 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << QString("%1:").arg(label);
auto modList = model.allMods();
std::sort(modList.begin(), modList.end(), [](Mod &a, Mod &b) {
- auto aName = a.filename().completeBaseName();
- auto bName = b.filename().completeBaseName();
+ auto aName = a.fileinfo().completeBaseName();
+ auto bName = b.fileinfo().completeBaseName();
return aName.localeAwareCompare(bName) < 0;
});
for(auto & mod: modList)
{
if(mod.type() == Mod::MOD_FOLDER)
{
- out << u8" [📁] " + mod.filename().completeBaseName() + " (folder)";
+ out << u8" [📁] " + mod.fileinfo().completeBaseName() + " (folder)";
continue;
}
if(mod.enabled()) {
- out << u8" [✔️] " + mod.filename().completeBaseName();
+ out << u8" [✔️] " + mod.fileinfo().completeBaseName();
}
else {
- out << u8" [❌] " + mod.filename().completeBaseName() + " (disabled)";
+ out << u8" [❌] " + mod.fileinfo().completeBaseName() + " (disabled)";
}
}
@@ -745,7 +787,9 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
{
addToFilter(sessionRef.session, tr("<SESSION ID>"));
}
- addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
+ if (sessionRef.access_token != "offline") {
+ addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
+ }
if(sessionRef.client_token.size()) {
addToFilter(sessionRef.client_token, tr("<CLIENT TOKEN>"));
}
@@ -824,8 +868,16 @@ QString MinecraftInstance::getStatusbarDescription()
traits.append(tr("broken"));
}
+ QString mcVersion = m_components->getComponentVersion("net.minecraft");
+ if (mcVersion.isEmpty())
+ {
+ // Load component info if needed
+ m_components->reload(Net::Mode::Offline);
+ mcVersion = m_components->getComponentVersion("net.minecraft");
+ }
+
QString description;
- description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName()));
+ description.append(tr("Minecraft %1").arg(mcVersion));
if(m_settings->get("ShowGameTime").toBool())
{
if (lastTimePlayed() > 0) {
@@ -1013,7 +1065,8 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList() const
{
if (!m_loader_mod_list)
{
- m_loader_mod_list.reset(new ModFolderModel(modsRoot()));
+ bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
+ m_loader_mod_list.reset(new ModFolderModel(modsRoot(), is_indexed));
m_loader_mod_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction);
}
@@ -1024,7 +1077,8 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const
{
if (!m_core_mod_list)
{
- m_core_mod_list.reset(new ModFolderModel(coreModsDir()));
+ bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
+ m_core_mod_list.reset(new ModFolderModel(coreModsDir(), is_indexed));
m_core_mod_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction);
}
diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h
index fda58aa7..05450d41 100644
--- a/launcher/minecraft/MinecraftInstance.h
+++ b/launcher/minecraft/MinecraftInstance.h
@@ -91,6 +91,7 @@ public:
/// create an environment for launching processes
QProcessEnvironment createEnvironment() override;
+ QProcessEnvironment createLaunchEnvironment() override;
/// guess log level from a line of minecraft log
MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override;
diff --git a/launcher/minecraft/MinecraftLoadAndCheck.cpp b/launcher/minecraft/MinecraftLoadAndCheck.cpp
index 79b0c484..d72bc7be 100644
--- a/launcher/minecraft/MinecraftLoadAndCheck.cpp
+++ b/launcher/minecraft/MinecraftLoadAndCheck.cpp
@@ -20,6 +20,7 @@ void MinecraftLoadAndCheck::executeTask()
}
connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded);
connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed);
+ connect(m_task.get(), &Task::aborted, this, [this]{ subtaskFailed(tr("Aborted")); });
connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress);
connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus);
}
diff --git a/launcher/minecraft/MinecraftUpdate.cpp b/launcher/minecraft/MinecraftUpdate.cpp
index 32e9cbb6..0ce0c347 100644
--- a/launcher/minecraft/MinecraftUpdate.cpp
+++ b/launcher/minecraft/MinecraftUpdate.cpp
@@ -98,6 +98,7 @@ void MinecraftUpdate::next()
auto task = m_tasks[m_currentTask - 1];
disconnect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded);
disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
+ disconnect(task.get(), &Task::aborted, this, &Task::abort);
disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
}
@@ -115,6 +116,7 @@ void MinecraftUpdate::next()
}
connect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded);
connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
+ connect(task.get(), &Task::aborted, this, &Task::abort);
connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
// if the task is already running, do not start it again
diff --git a/launcher/minecraft/MinecraftUpdate.h b/launcher/minecraft/MinecraftUpdate.h
index 9ebef656..acf2eb86 100644
--- a/launcher/minecraft/MinecraftUpdate.h
+++ b/launcher/minecraft/MinecraftUpdate.h
@@ -27,6 +27,8 @@
class MinecraftVersion;
class MinecraftInstance;
+// FIXME: This looks very similar to a SequentialTask. Maybe we can reduce code duplications? :^)
+
class MinecraftUpdate : public Task
{
Q_OBJECT
diff --git a/launcher/minecraft/MojangDownloadInfo.h b/launcher/minecraft/MojangDownloadInfo.h
index 88f87287..13e27e15 100644
--- a/launcher/minecraft/MojangDownloadInfo.h
+++ b/launcher/minecraft/MojangDownloadInfo.h
@@ -65,7 +65,7 @@ struct MojangAssetIndexInfo : public MojangDownloadInfo
// https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/
if(id == "legacy")
{
- url = "https://launchermeta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json";
+ url = "https://piston-meta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json";
}
// HACK
else
diff --git a/launcher/minecraft/MojangVersionFormat_test.cpp b/launcher/minecraft/MojangVersionFormat_test.cpp
index 9d095340..71df784b 100644
--- a/launcher/minecraft/MojangVersionFormat_test.cpp
+++ b/launcher/minecraft/MojangVersionFormat_test.cpp
@@ -1,6 +1,5 @@
#include <QTest>
#include <QDebug>
-#include "TestUtil.h"
#include "minecraft/MojangVersionFormat.h"
@@ -8,9 +7,8 @@ class MojangVersionFormatTest : public QObject
{
Q_OBJECT
- static QJsonDocument readJson(const char *file)
+ static QJsonDocument readJson(const QString path)
{
- auto path = QFINDTESTDATA(file);
QFile jsonFile(path);
jsonFile.open(QIODevice::ReadOnly);
auto data = jsonFile.readAll();
@@ -31,7 +29,7 @@ private
slots:
void test_Through_Simple()
{
- QJsonDocument doc = readJson("data/1.9-simple.json");
+ QJsonDocument doc = readJson(QFINDTESTDATA("testdata/1.9-simple.json"));
auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9-simple.json");
auto doc2 = MojangVersionFormat::versionFileToJson(vfile);
writeJson("1.9-simple-passthorugh.json", doc2);
@@ -41,7 +39,7 @@ slots:
void test_Through()
{
- QJsonDocument doc = readJson("data/1.9.json");
+ QJsonDocument doc = readJson(QFINDTESTDATA("testdata/1.9.json"));
auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9.json");
auto doc2 = MojangVersionFormat::versionFileToJson(vfile);
writeJson("1.9-passthorugh.json", doc2);
diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp
index 879f18c1..cec4a55b 100644
--- a/launcher/minecraft/OneSixVersionFormat.cpp
+++ b/launcher/minecraft/OneSixVersionFormat.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "OneSixVersionFormat.h"
#include <Json.h>
#include "minecraft/Agent.h"
@@ -296,7 +331,7 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
}
writeString(root, "appletClass", patch->appletClass);
writeStringList(root, "+tweakers", patch->addTweakers);
- writeStringList(root, "+traits", patch->traits.toList());
+ writeStringList(root, "+traits", patch->traits.values());
if (!patch->libraries.isEmpty())
{
QJsonArray array;
diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp
index d53f41e1..5e76b892 100644
--- a/launcher/minecraft/PackProfile.cpp
+++ b/launcher/minecraft/PackProfile.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QFile>
@@ -36,6 +56,13 @@
#include "ComponentUpdateTask.h"
#include "Application.h"
+#include "modplatform/ModAPI.h"
+
+static const QMap<QString, ModAPI::ModLoaderType> modloaderMapping{
+ {"net.minecraftforge", ModAPI::Forge},
+ {"net.fabricmc.fabric-loader", ModAPI::Fabric},
+ {"org.quiltmc.quilt-loader", ModAPI::Quilt}
+};
PackProfile::PackProfile(MinecraftInstance * instance)
: QAbstractListModel()
@@ -339,6 +366,7 @@ void PackProfile::resolve(Net::Mode netmode)
d->m_updateTask.reset(updateTask);
connect(updateTask, &ComponentUpdateTask::succeeded, this, &PackProfile::updateSucceeded);
connect(updateTask, &ComponentUpdateTask::failed, this, &PackProfile::updateFailed);
+ connect(updateTask, &ComponentUpdateTask::aborted, this, [this]{ updateFailed(tr("Aborted")); });
d->m_updateTask->start();
}
@@ -680,7 +708,11 @@ void PackProfile::move(const int index, const MoveDirection direction)
return;
}
beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
+ d->components.swapItemsAt(index, theirIndex);
+#else
d->components.swap(index, theirIndex);
+#endif
endMoveRows();
invalidateLaunchProfile();
scheduleSave();
@@ -971,19 +1003,19 @@ void PackProfile::disableInteraction(bool disable)
}
}
-ModAPI::ModLoaderType PackProfile::getModLoader()
+ModAPI::ModLoaderTypes PackProfile::getModLoaders()
{
- if (!getComponentVersion("net.minecraftforge").isEmpty())
- {
- return ModAPI::Forge;
- }
- else if (!getComponentVersion("net.fabricmc.fabric-loader").isEmpty())
- {
- return ModAPI::Fabric;
- }
- else if (!getComponentVersion("org.quiltmc.quilt-loader").isEmpty())
+ ModAPI::ModLoaderTypes result = ModAPI::Unspecified;
+
+ QMapIterator<QString, ModAPI::ModLoaderType> i(modloaderMapping);
+
+ while (i.hasNext())
{
- return ModAPI::Quilt;
+ i.next();
+ Component* c = getComponent(i.key());
+ if (c != nullptr && c->isEnabled()) {
+ result |= i.value();
+ }
}
- return ModAPI::Unspecified;
+ return result;
}
diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h
index ab4cd5c8..918e7f7a 100644
--- a/launcher/minecraft/PackProfile.h
+++ b/launcher/minecraft/PackProfile.h
@@ -118,7 +118,7 @@ public:
// todo(merged): is this the best approach
void appendComponent(ComponentPtr component);
- ModAPI::ModLoaderType getModLoader();
+ ModAPI::ModLoaderTypes getModLoaders();
private:
void scheduleSave();
diff --git a/launcher/minecraft/ParseUtils_test.cpp b/launcher/minecraft/ParseUtils_test.cpp
index fcc137e5..7721a46d 100644
--- a/launcher/minecraft/ParseUtils_test.cpp
+++ b/launcher/minecraft/ParseUtils_test.cpp
@@ -1,5 +1,4 @@
#include <QTest>
-#include "TestUtil.h"
#include "minecraft/ParseUtils.h"
@@ -42,4 +41,3 @@ slots:
QTEST_GUILESS_MAIN(ParseUtilsTest)
#include "ParseUtils_test.moc"
-
diff --git a/launcher/minecraft/ProfileUtils.cpp b/launcher/minecraft/ProfileUtils.cpp
index 8ca24cc8..03f8c198 100644
--- a/launcher/minecraft/ProfileUtils.cpp
+++ b/launcher/minecraft/ProfileUtils.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "ProfileUtils.h"
#include "minecraft/VersionFilterData.h"
#include "minecraft/OneSixVersionFormat.h"
@@ -141,24 +176,6 @@ bool saveJsonFile(const QJsonDocument doc, const QString & filename)
return true;
}
-VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo)
-{
- QFile file(fileInfo.absoluteFilePath());
- if (!file.open(QFile::ReadOnly))
- {
- auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString());
- return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr);
- }
- QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll());
- file.close();
- if (doc.isNull())
- {
- file.remove();
- throw JSONValidationError(QObject::tr("Unable to process the version file %1.").arg(fileInfo.fileName()));
- }
- return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), false);
-}
-
void removeLwjglFromPatch(VersionFilePtr patch)
{
auto filter = [](QList<LibraryPtr>& libs)
diff --git a/launcher/minecraft/ProfileUtils.h b/launcher/minecraft/ProfileUtils.h
index 351c36cb..5b938784 100644
--- a/launcher/minecraft/ProfileUtils.h
+++ b/launcher/minecraft/ProfileUtils.h
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "Library.h"
#include "VersionFile.h"
@@ -19,9 +54,6 @@ VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder)
/// Save a JSON file (in any format)
bool saveJsonFile(const QJsonDocument doc, const QString & filename);
-/// Parse a version file in binary JSON format
-VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo);
-
/// Remove LWJGL from a patch file. This is applied to all Mojang-like profile files.
void removeLwjglFromPatch(VersionFilePtr patch);
diff --git a/launcher/minecraft/VersionFile.cpp b/launcher/minecraft/VersionFile.cpp
index 9db30ba2..a9a0f7f4 100644
--- a/launcher/minecraft/VersionFile.cpp
+++ b/launcher/minecraft/VersionFile.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* 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
@@ -55,7 +56,7 @@ void VersionFile::applyTo(LaunchProfile *profile)
// Only real Minecraft can set those. Don't let anything override them.
if (isMinecraftVersion(uid))
{
- profile->applyMinecraftVersion(minecraftVersion);
+ profile->applyMinecraftVersion(version);
profile->applyMinecraftVersionType(type);
// HACK: ignore assets from other version files than Minecraft
// workaround for stupid assets issue caused by amazon:
@@ -88,14 +89,3 @@ void VersionFile::applyTo(LaunchProfile *profile)
}
profile->applyProblemSeverity(getProblemSeverity());
}
-
-/*
- auto theirVersion = profile->getMinecraftVersion();
- if (!theirVersion.isNull() && !dependsOnMinecraftVersion.isNull())
- {
- if (QRegExp(dependsOnMinecraftVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(theirVersion) == -1)
- {
- throw MinecraftVersionMismatch(uid, dependsOnMinecraftVersion, theirVersion);
- }
- }
-*/
diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp
index dc756e06..dfcb43d8 100644
--- a/launcher/minecraft/World.cpp
+++ b/launcher/minecraft/World.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2015-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QDir>
@@ -321,7 +341,8 @@ bool World::install(const QString &to, const QString &name)
if(ok && !name.isEmpty() && m_actualName != name)
{
- World newWorld(finalPath);
+ QFileInfo finalPathInfo(finalPath);
+ World newWorld(finalPathInfo);
if(newWorld.isValid())
{
newWorld.rename(name);
diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp
index 955609bf..aee7be35 100644
--- a/launcher/minecraft/WorldList.cpp
+++ b/launcher/minecraft/WorldList.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2015-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "WorldList.h"
@@ -195,7 +215,7 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
switch (column)
{
case SizeColumn:
- return qVariantFromValue<qlonglong>(world.bytes());
+ return QVariant::fromValue<qlonglong>(world.bytes());
default:
return data(index, Qt::DisplayRole);
@@ -215,7 +235,7 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
}
case SeedRole:
{
- return qVariantFromValue<qlonglong>(world.seed());
+ return QVariant::fromValue<qlonglong>(world.seed());
}
case NameRole:
{
@@ -227,7 +247,7 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
}
case SizeRole:
{
- return qVariantFromValue<qlonglong>(world.bytes());
+ return QVariant::fromValue<qlonglong>(world.bytes());
}
case IconFileRole:
{
@@ -301,7 +321,11 @@ public:
}
protected:
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ QVariant retrieveData(const QString &mimetype, QMetaType type) const
+#else
QVariant retrieveData(const QString &mimetype, QVariant::Type type) const
+#endif
{
QList<QUrl> urls;
for(auto &world: m_worlds)
diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp
index dd9c3f8f..44f7e256 100644
--- a/launcher/minecraft/auth/AccountData.cpp
+++ b/launcher/minecraft/auth/AccountData.cpp
@@ -39,6 +39,7 @@
#include <QJsonArray>
#include <QDebug>
#include <QUuid>
+#include <QRegularExpression>
namespace {
void tokenToJSONV3(QJsonObject &parent, Katabasis::Token t, const char * tokenName) {
@@ -451,7 +452,7 @@ void AccountData::invalidateClientToken() {
if(type != AccountType::Mojang) {
return;
}
- yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegExp("[{-}]"));
+ yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]"));
}
QString AccountData::profileId() const {
@@ -473,7 +474,7 @@ QString AccountData::accountDisplayString() const {
return userName();
}
case AccountType::Offline: {
- return userName();
+ return QObject::tr("<Offline>");
}
case AccountType::MSA: {
if(xboxApiToken.extra.contains("gtg")) {
diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp
index 3422df7c..2b851e18 100644
--- a/launcher/minecraft/auth/AccountList.cpp
+++ b/launcher/minecraft/auth/AccountList.cpp
@@ -282,6 +282,10 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
case Qt::DisplayRole:
switch (index.column())
{
+ case ProfileNameColumn: {
+ return account->profileName();
+ }
+
case NameColumn:
return account->accountDisplayString();
@@ -300,7 +304,7 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
return tr("Offline", "Account status");
}
case AccountState::Online: {
- return tr("Online", "Account status");
+ return tr("Ready", "Account status");
}
case AccountState::Working: {
return tr("Working", "Account status");
@@ -320,10 +324,6 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
}
}
- case ProfileNameColumn: {
- return account->profileName();
- }
-
case MigrationColumn: {
if(account->isMSA() || account->isOffline()) {
return tr("N/A", "Can Migrate?");
@@ -349,7 +349,7 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
case Qt::CheckStateRole:
switch (index.column())
{
- case NameColumn:
+ case ProfileNameColumn:
return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked;
}
@@ -365,6 +365,8 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r
case Qt::DisplayRole:
switch (section)
{
+ case ProfileNameColumn:
+ return tr("Username");
case NameColumn:
return tr("Account");
case TypeColumn:
@@ -373,8 +375,6 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r
return tr("Status");
case MigrationColumn:
return tr("Can Migrate?");
- case ProfileNameColumn:
- return tr("Profile");
default:
return QVariant();
}
@@ -382,6 +382,8 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r
case Qt::ToolTipRole:
switch (section)
{
+ case ProfileNameColumn:
+ return tr("Minecraft username associated with the account.");
case NameColumn:
return tr("User name of the account.");
case TypeColumn:
@@ -389,9 +391,7 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r
case StatusColumn:
return tr("Current status of the account.");
case MigrationColumn:
- return tr("Can this account migrate to Microsoft account?");
- case ProfileNameColumn:
- return tr("Name of the Minecraft profile associated with the account.");
+ return tr("Can this account migrate to a Microsoft account?");
default:
return QVariant();
}
diff --git a/launcher/minecraft/auth/AccountList.h b/launcher/minecraft/auth/AccountList.h
index baaf7414..8136a92e 100644
--- a/launcher/minecraft/auth/AccountList.h
+++ b/launcher/minecraft/auth/AccountList.h
@@ -58,8 +58,8 @@ public:
enum VListColumns
{
// TODO: Add icon column.
- NameColumn = 0,
- ProfileNameColumn,
+ ProfileNameColumn = 0,
+ NameColumn,
MigrationColumn,
TypeColumn,
StatusColumn,
diff --git a/launcher/minecraft/auth/AccountTask.cpp b/launcher/minecraft/auth/AccountTask.cpp
index 49b6e46f..4118c3c5 100644
--- a/launcher/minecraft/auth/AccountTask.cpp
+++ b/launcher/minecraft/auth/AccountTask.cpp
@@ -79,6 +79,8 @@ QString AccountTask::getStateMessage() const
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: {
diff --git a/launcher/minecraft/auth/AuthRequest.cpp b/launcher/minecraft/auth/AuthRequest.cpp
index feface80..bb82e1e2 100644
--- a/launcher/minecraft/auth/AuthRequest.cpp
+++ b/launcher/minecraft/auth/AuthRequest.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <cassert>
#include <QDebug>
@@ -20,7 +55,11 @@ void AuthRequest::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) {
reply_ = APPLICATION->network()->get(request_);
status_ = Requesting;
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
+#else
connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
+#endif
connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()));
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
}
@@ -31,7 +70,11 @@ void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int t
status_ = Requesting;
reply_ = APPLICATION->network()->post(request_, data_);
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
+#else
connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
+#endif
connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()));
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64)));
diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp
index ec86fa5c..a5c6f542 100644
--- a/launcher/minecraft/auth/MinecraftAccount.cpp
+++ b/launcher/minecraft/auth/MinecraftAccount.cpp
@@ -40,7 +40,7 @@
#include <QUuid>
#include <QJsonObject>
#include <QJsonArray>
-#include <QRegExp>
+#include <QRegularExpression>
#include <QStringList>
#include <QJsonDocument>
@@ -53,7 +53,7 @@
#include "flows/Offline.h"
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) {
- data.internalId = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
+ data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
}
@@ -78,7 +78,7 @@ MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString &username
MinecraftAccountPtr account = new MinecraftAccount();
account->data.type = AccountType::Mojang;
account->data.yggdrasilToken.extra["userName"] = username;
- account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
+ account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
return account;
}
@@ -97,10 +97,10 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username)
account->data.yggdrasilToken.validity = Katabasis::Validity::Certain;
account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
account->data.yggdrasilToken.extra["userName"] = username;
- account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
+ account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
account->data.minecraftEntitlement.ownsMinecraft = true;
account->data.minecraftEntitlement.canPlayMinecraft = true;
- account->data.minecraftProfile.id = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
+ account->data.minecraftProfile.id = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
account->data.minecraftProfile.name = username;
account->data.minecraftProfile.validity = Katabasis::Validity::Certain;
return account;
@@ -135,6 +135,7 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password) {
m_currentTask.reset(new MojangLogin(&data, password));
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
+ connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
emit activityChanged(true);
return m_currentTask;
}
@@ -145,6 +146,7 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA() {
m_currentTask.reset(new MSAInteractive(&data));
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
+ connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
emit activityChanged(true);
return m_currentTask;
}
@@ -155,6 +157,7 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::loginOffline() {
m_currentTask.reset(new OfflineLogin(&data));
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
+ connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
emit activityChanged(true);
return m_currentTask;
}
@@ -176,6 +179,7 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::refresh() {
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
+ connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
emit activityChanged(true);
return m_currentTask;
}
diff --git a/launcher/minecraft/auth/steps/YggdrasilStep.cpp b/launcher/minecraft/auth/steps/YggdrasilStep.cpp
index 4c6b1624..e1d33172 100644
--- a/launcher/minecraft/auth/steps/YggdrasilStep.cpp
+++ b/launcher/minecraft/auth/steps/YggdrasilStep.cpp
@@ -9,6 +9,7 @@ YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(dat
connect(m_yggdrasil, &Task::failed, this, &YggdrasilStep::onAuthFailed);
connect(m_yggdrasil, &Task::succeeded, this, &YggdrasilStep::onAuthSucceeded);
+ connect(m_yggdrasil, &Task::aborted, this, &YggdrasilStep::onAuthFailed);
}
YggdrasilStep::~YggdrasilStep() noexcept = default;
diff --git a/launcher/minecraft/launch/DirectJavaLaunch.cpp b/launcher/minecraft/launch/DirectJavaLaunch.cpp
index 742170fa..152485b3 100644
--- a/launcher/minecraft/launch/DirectJavaLaunch.cpp
+++ b/launcher/minecraft/launch/DirectJavaLaunch.cpp
@@ -12,13 +12,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
#include "DirectJavaLaunch.h"
+
+#include <QStandardPaths>
+
#include <launch/LaunchTask.h>
#include <minecraft/MinecraftInstance.h>
#include <FileSystem.h>
#include <Commandline.h>
-#include <QStandardPaths>
+
+#ifdef Q_OS_LINUX
+#include "gamemode_client.h"
+#endif
DirectJavaLaunch::DirectJavaLaunch(LaunchTask *parent) : LaunchStep(parent)
{
@@ -50,7 +55,7 @@ void DirectJavaLaunch::executeTask()
auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString());
- m_process.setProcessEnvironment(instance->createEnvironment());
+ m_process.setProcessEnvironment(instance->createLaunchEnvironment());
// make detachable - this will keep the process running even if the object is destroyed
m_process.setDetachable(true);
@@ -79,6 +84,17 @@ void DirectJavaLaunch::executeTask()
{
m_process.start(javaPath, args);
}
+
+#ifdef Q_OS_LINUX
+ if (instance->settings()->get("EnableFeralGamemode").toBool())
+ {
+ auto pid = m_process.processId();
+ if (pid)
+ {
+ gamemode_request_start_for(pid);
+ }
+ }
+#endif
}
void DirectJavaLaunch::on_state(LoggedProcess::State state)
diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp
index d7010355..63e4d90f 100644
--- a/launcher/minecraft/launch/LauncherPartLaunch.cpp
+++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "LauncherPartLaunch.h"
@@ -23,6 +43,10 @@
#include "Commandline.h"
#include "Application.h"
+#ifdef Q_OS_LINUX
+#include "gamemode_client.h"
+#endif
+
LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent)
{
auto instance = parent->instance();
@@ -71,6 +95,15 @@ bool fitsInLocal8bit(const QString & string)
void LauncherPartLaunch::executeTask()
{
+ QString jarPath = APPLICATION->getJarPath("NewLaunch.jar");
+ if (jarPath.isEmpty())
+ {
+ const char *reason = QT_TR_NOOP("Launcher library could not be found. Please check your installation.");
+ emit logLine(tr(reason), MessageLevel::Fatal);
+ emitFailed(tr(reason));
+ return;
+ }
+
auto instance = m_parent->instance();
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
@@ -81,13 +114,13 @@ void LauncherPartLaunch::executeTask()
auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString());
- m_process.setProcessEnvironment(instance->createEnvironment());
+ m_process.setProcessEnvironment(instance->createLaunchEnvironment());
// make detachable - this will keep the process running even if the object is destroyed
m_process.setDetachable(true);
auto classPath = minecraftInstance->getClassPath();
- classPath.prepend(FS::PathCombine(APPLICATION->getJarsPath(), "NewLaunch.jar"));
+ classPath.prepend(jarPath);
auto natPath = minecraftInstance->getNativePath();
#ifdef Q_OS_WIN
@@ -121,7 +154,7 @@ void LauncherPartLaunch::executeTask()
#else
args << classPath.join(':');
#endif
- args << "org.multimc.EntryPoint";
+ args << "org.polymc.EntryPoint";
qDebug() << args.join(' ');
@@ -146,6 +179,17 @@ void LauncherPartLaunch::executeTask()
{
m_process.start(javaPath, args);
}
+
+#ifdef Q_OS_LINUX
+ if (instance->settings()->get("EnableFeralGamemode").toBool())
+ {
+ auto pid = m_process.processId();
+ if (pid)
+ {
+ gamemode_request_start_for(pid);
+ }
+ }
+#endif
}
void LauncherPartLaunch::on_state(LoggedProcess::State state)
diff --git a/launcher/minecraft/mod/MetadataHandler.h b/launcher/minecraft/mod/MetadataHandler.h
new file mode 100644
index 00000000..56962818
--- /dev/null
+++ b/launcher/minecraft/mod/MetadataHandler.h
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <memory>
+
+#include "modplatform/packwiz/Packwiz.h"
+
+// 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;
+
+ 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);
+ }
+
+ static auto create(QDir& index_dir, Mod& internal_mod) -> ModStruct
+ {
+ return Packwiz::V1::createModFormat(index_dir, internal_mod);
+ }
+
+ static void update(QDir& index_dir, ModStruct& mod)
+ {
+ Packwiz::V1::updateModIndex(index_dir, mod);
+ }
+
+ static void remove(QDir& index_dir, QString& mod_name)
+ {
+ Packwiz::V1::deleteModIndex(index_dir, mod_name);
+ }
+
+ static auto get(QDir& index_dir, QString& mod_name) -> ModStruct
+ {
+ return Packwiz::V1::getIndexForMod(index_dir, mod_name);
+ }
+};
diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp
index b6bff29b..742709e3 100644
--- a/launcher/minecraft/mod/Mod.cpp
+++ b/launcher/minecraft/mod/Mod.cpp
@@ -1,24 +1,49 @@
-/* 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.
- */
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+*
+* 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 <https://www.gnu.org/licenses/>.
+*
+* 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 "Mod.h"
#include <QDir>
#include <QString>
-#include "Mod.h"
-#include <QDebug>
#include <FileSystem.h>
+#include <QDebug>
+
+#include "Application.h"
+#include "MetadataHandler.h"
namespace {
@@ -26,57 +51,67 @@ ModDetails invalidDetails;
}
-
-Mod::Mod(const QFileInfo &file)
+Mod::Mod(const QFileInfo& file)
{
repath(file);
m_changedDateTime = file.lastModified();
}
-void Mod::repath(const QFileInfo &file)
+Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata)
+ : m_file(mods_dir.absoluteFilePath(metadata.filename))
+ , m_internal_id(metadata.filename)
+ , m_name(metadata.name)
+{
+ if (m_file.isDir()) {
+ m_type = MOD_FOLDER;
+ } else {
+ if (metadata.filename.endsWith(".zip") || metadata.filename.endsWith(".jar"))
+ m_type = MOD_ZIPFILE;
+ else if (metadata.filename.endsWith(".litemod"))
+ m_type = MOD_LITEMOD;
+ else
+ m_type = MOD_SINGLEFILE;
+ }
+
+ m_enabled = true;
+ m_changedDateTime = m_file.lastModified();
+
+ m_temp_metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
+}
+
+void Mod::repath(const QFileInfo& file)
{
m_file = file;
QString name_base = file.fileName();
m_type = Mod::MOD_UNKNOWN;
- m_mmc_id = name_base;
+ m_internal_id = name_base;
- if (m_file.isDir())
- {
+ if (m_file.isDir()) {
m_type = MOD_FOLDER;
m_name = name_base;
- }
- else if (m_file.isFile())
- {
- if (name_base.endsWith(".disabled"))
- {
+ } else if (m_file.isFile()) {
+ if (name_base.endsWith(".disabled")) {
m_enabled = false;
name_base.chop(9);
- }
- else
- {
+ } else {
m_enabled = true;
}
- if (name_base.endsWith(".zip") || name_base.endsWith(".jar"))
- {
+ if (name_base.endsWith(".zip") || name_base.endsWith(".jar")) {
m_type = MOD_ZIPFILE;
name_base.chop(4);
- }
- else if (name_base.endsWith(".litemod"))
- {
+ } else if (name_base.endsWith(".litemod")) {
m_type = MOD_LITEMOD;
name_base.chop(8);
- }
- else
- {
+ } else {
m_type = MOD_SINGLEFILE;
}
m_name = name_base;
}
}
-bool Mod::enable(bool value)
+auto Mod::enable(bool value) -> bool
{
if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
return false;
@@ -85,67 +120,126 @@ bool Mod::enable(bool value)
return false;
QString path = m_file.absoluteFilePath();
- if (value)
- {
- QFile foo(path);
+ QFile file(path);
+ if (value) {
if (!path.endsWith(".disabled"))
return false;
path.chop(9);
- if (!foo.rename(path))
+
+ if (!file.rename(path))
return false;
- }
- else
- {
- QFile foo(path);
+ } else {
path += ".disabled";
- if (!foo.rename(path))
+
+ if (!file.rename(path))
return false;
}
- repath(QFileInfo(path));
+
+ if (status() == ModStatus::NoMetadata)
+ repath(QFileInfo(path));
+
m_enabled = value;
return true;
}
-bool Mod::destroy()
+void Mod::setStatus(ModStatus status)
{
- m_type = MOD_UNKNOWN;
- return FS::deletePath(m_file.filePath());
+ if (m_localDetails) {
+ m_localDetails->status = status;
+ } else {
+ m_temp_status = status;
+ }
}
+void Mod::setMetadata(Metadata::ModStruct* metadata)
+{
+ if (status() == ModStatus::NoMetadata)
+ setStatus(ModStatus::Installed);
+ if (m_localDetails) {
+ m_localDetails->metadata.reset(metadata);
+ } else {
+ m_temp_metadata.reset(metadata);
+ }
+}
-const ModDetails & Mod::details() const
+auto Mod::destroy(QDir& index_dir) -> bool
{
- if(!m_localDetails)
- return invalidDetails;
- return *m_localDetails;
-}
+ auto n = name();
+ // FIXME: This can fail to remove the metadata if the
+ // "ModMetadataDisabled" setting is on, since there could
+ // be a name mismatch!
+ Metadata::remove(index_dir, n);
+ m_type = MOD_UNKNOWN;
+ return FS::deletePath(m_file.filePath());
+}
-QString Mod::version() const
+auto Mod::details() const -> const ModDetails&
{
- return details().version;
+ return m_localDetails ? *m_localDetails : invalidDetails;
}
-QString Mod::name() const
+auto Mod::name() const -> QString
{
- auto & d = details();
- if(!d.name.isEmpty()) {
- return d.name;
+ auto d_name = details().name;
+ if (!d_name.isEmpty()) {
+ return d_name;
}
return m_name;
}
-QString Mod::homeurl() const
+auto Mod::version() const -> QString
+{
+ return details().version;
+}
+
+auto Mod::homeurl() const -> QString
{
return details().homeurl;
}
-QString Mod::description() const
+auto Mod::description() const -> QString
{
return details().description;
}
-QStringList Mod::authors() const
+auto Mod::authors() const -> QStringList
{
return details().authors;
}
+
+auto Mod::status() const -> ModStatus
+{
+ if (!m_localDetails)
+ return m_temp_status;
+ return details().status;
+}
+
+auto Mod::metadata() -> std::shared_ptr<Metadata::ModStruct>
+{
+ if (m_localDetails)
+ return m_localDetails->metadata;
+ return m_temp_metadata;
+}
+
+auto Mod::metadata() const -> const std::shared_ptr<Metadata::ModStruct>
+{
+ if (m_localDetails)
+ return m_localDetails->metadata;
+ return m_temp_metadata;
+}
+
+void Mod::finishResolvingWithDetails(std::shared_ptr<ModDetails> details)
+{
+ m_resolving = false;
+ m_resolved = true;
+ m_localDetails = details;
+
+ if (m_localDetails && m_temp_metadata && m_temp_metadata->isValid()) {
+ m_localDetails->metadata = m_temp_metadata;
+ if (status() == ModStatus::NoMetadata)
+ setStatus(ModStatus::Installed);
+ }
+
+ setStatus(m_temp_status);
+}
diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h
index 921faeb1..5f9c4684 100644
--- a/launcher/minecraft/mod/Mod.h
+++ b/launcher/minecraft/mod/Mod.h
@@ -1,28 +1,46 @@
-/* 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.
- */
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+*
+* 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 <QFileInfo>
+
#include <QDateTime>
+#include <QFileInfo>
#include <QList>
-#include <memory>
#include "ModDetails.h"
-
-
class Mod
{
public:
@@ -32,84 +50,73 @@ public:
MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files.
MOD_SINGLEFILE, //!< The mod is a single file (not a zip file).
MOD_FOLDER, //!< The mod is in a folder on the filesystem.
- MOD_LITEMOD, //!< The mod is a litemod
+ MOD_LITEMOD, //!< The mod is a litemod
};
Mod() = default;
Mod(const QFileInfo &file);
+ explicit Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata);
- QFileInfo filename() const
- {
- return m_file;
- }
- QString mmc_id() const
- {
- return m_mmc_id;
- }
- ModType type() const
- {
- return m_type;
- }
- bool valid()
- {
- return m_type != MOD_UNKNOWN;
- }
+ auto fileinfo() const -> QFileInfo { return m_file; }
+ auto dateTimeChanged() const -> QDateTime { return m_changedDateTime; }
+ auto internal_id() const -> QString { return m_internal_id; }
+ auto type() const -> ModType { return m_type; }
+ auto enabled() const -> bool { return m_enabled; }
- QDateTime dateTimeChanged() const
- {
- return m_changedDateTime;
- }
+ auto valid() const -> bool { return m_type != MOD_UNKNOWN; }
- bool enabled() const
- {
- return m_enabled;
- }
+ auto details() const -> const ModDetails&;
+ auto name() const -> QString;
+ auto version() const -> QString;
+ auto homeurl() const -> QString;
+ auto description() const -> QString;
+ auto authors() const -> QStringList;
+ auto status() const -> ModStatus;
- const ModDetails &details() const;
+ auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
+ auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
- QString name() const;
- QString version() const;
- QString homeurl() const;
- QString description() const;
- QStringList authors() const;
+ void setStatus(ModStatus status);
+ void setMetadata(Metadata::ModStruct* metadata);
- bool enable(bool value);
+ auto enable(bool value) -> bool;
// delete all the files of this mod
- bool destroy();
+ auto destroy(QDir& index_dir) -> bool;
// change the mod's filesystem path (used by mod lists for *MAGIC* purposes)
void repath(const QFileInfo &file);
- bool shouldResolve() {
- return !m_resolving && !m_resolved;
- }
- bool isResolving() {
- return m_resolving;
- }
- int resolutionTicket()
- {
- return m_resolutionTicket;
- }
+ auto shouldResolve() const -> bool { return !m_resolving && !m_resolved; }
+ auto isResolving() const -> bool { return m_resolving; }
+ auto resolutionTicket() const -> int { return m_resolutionTicket; }
+
void setResolving(bool resolving, int resolutionTicket) {
m_resolving = resolving;
m_resolutionTicket = resolutionTicket;
}
- void finishResolvingWithDetails(std::shared_ptr<ModDetails> details){
- m_resolving = false;
- m_resolved = true;
- m_localDetails = details;
- }
+ void finishResolvingWithDetails(std::shared_ptr<ModDetails> details);
protected:
QFileInfo m_file;
QDateTime m_changedDateTime;
- QString m_mmc_id;
+
+ QString m_internal_id;
+ /* Name as reported via the file name */
QString m_name;
+ ModType m_type = MOD_UNKNOWN;
+
+ /* If the mod has metadata, this will be filled in the constructor, and passed to
+ * the ModDetails when calling finishResolvingWithDetails */
+ std::shared_ptr<Metadata::ModStruct> m_temp_metadata;
+
+ /* Set the mod status while it doesn't have local details just yet */
+ ModStatus m_temp_status = ModStatus::NotInstalled;
+
+ std::shared_ptr<ModDetails> m_localDetails;
+
bool m_enabled = true;
bool m_resolving = false;
bool m_resolved = false;
int m_resolutionTicket = 0;
- ModType m_type = MOD_UNKNOWN;
- std::shared_ptr<ModDetails> m_localDetails;
};
diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h
index 6ab4aee7..3e0a7ab0 100644
--- a/launcher/minecraft/mod/ModDetails.h
+++ b/launcher/minecraft/mod/ModDetails.h
@@ -1,17 +1,79 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+*
+* 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 <memory>
+
#include <QString>
#include <QStringList>
+#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
+};
+
struct ModDetails
{
+ /* Mod ID as defined in the ModLoader-specific metadata */
QString mod_id;
+
+ /* Human-readable name */
QString name;
+
+ /* Human-readable mod version */
QString version;
+
+ /* Human-readable minecraft version */
QString mcversion;
+
+ /* URL for mod's home page */
QString homeurl;
- QString updateurl;
+
+ /* Human-readable description */
QString description;
+
+ /* List of the author's names */
QStringList authors;
- QString credits;
+
+ /* Installation status of the mod */
+ ModStatus status;
+
+ /* Metadata information, if any */
+ std::shared_ptr<Metadata::ModStruct> metadata;
};
diff --git a/launcher/minecraft/mod/ModFolderLoadTask.cpp b/launcher/minecraft/mod/ModFolderLoadTask.cpp
deleted file mode 100644
index 88349877..00000000
--- a/launcher/minecraft/mod/ModFolderLoadTask.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#include "ModFolderLoadTask.h"
-#include <QDebug>
-
-ModFolderLoadTask::ModFolderLoadTask(QDir dir) :
- m_dir(dir), m_result(new Result())
-{
-}
-
-void ModFolderLoadTask::run()
-{
- m_dir.refresh();
- for (auto entry : m_dir.entryInfoList())
- {
- Mod m(entry);
- m_result->mods[m.mmc_id()] = m;
- }
- emit succeeded();
-}
diff --git a/launcher/minecraft/mod/ModFolderLoadTask.h b/launcher/minecraft/mod/ModFolderLoadTask.h
deleted file mode 100644
index 8d720e65..00000000
--- a/launcher/minecraft/mod/ModFolderLoadTask.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#pragma once
-#include <QRunnable>
-#include <QObject>
-#include <QDir>
-#include <QMap>
-#include "Mod.h"
-#include <memory>
-
-class ModFolderLoadTask : public QObject, public QRunnable
-{
- Q_OBJECT
-public:
- struct Result {
- QMap<QString, Mod> mods;
- };
- using ResultPtr = std::shared_ptr<Result>;
- ResultPtr result() const {
- return m_result;
- }
-
-public:
- ModFolderLoadTask(QDir dir);
- void run();
-signals:
- void succeeded();
-private:
- QDir m_dir;
- ResultPtr m_result;
-};
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index f0c53c39..5ee08cbf 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -1,32 +1,55 @@
-/* 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.
- */
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+*
+* 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 <https://www.gnu.org/licenses/>.
+*
+* 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 "ModFolderModel.h"
+
#include <FileSystem.h>
+#include <QDebug>
+#include <QFileSystemWatcher>
#include <QMimeData>
-#include <QUrl>
-#include <QUuid>
#include <QString>
-#include <QFileSystemWatcher>
-#include <QDebug>
-#include "ModFolderLoadTask.h"
#include <QThreadPool>
+#include <QUrl>
+#include <QUuid>
#include <algorithm>
-#include "LocalModParseTask.h"
-ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir)
+#include "minecraft/mod/tasks/LocalModParseTask.h"
+#include "minecraft/mod/tasks/ModFolderLoadTask.h"
+
+ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : QAbstractListModel(), m_dir(dir), m_is_indexed(is_indexed)
{
FS::ensureFolderPathExists(m_dir.absolutePath());
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
@@ -79,19 +102,31 @@ bool ModFolderModel::update()
return true;
}
- auto task = new ModFolderLoadTask(m_dir);
+ auto index_dir = indexDir();
+ auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed);
+
m_update = task->result();
+
QThreadPool *threadPool = QThreadPool::globalInstance();
connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate);
+
threadPool->start(task);
return true;
}
void ModFolderModel::finishUpdate()
{
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ auto currentList = modsIndex.keys();
+ QSet<QString> currentSet(currentList.begin(), currentList.end());
+ auto & newMods = m_update->mods;
+ auto newList = newMods.keys();
+ QSet<QString> newSet(newList.begin(), newList.end());
+#else
QSet<QString> currentSet = modsIndex.keys().toSet();
auto & newMods = m_update->mods;
QSet<QString> newSet = newMods.keys().toSet();
+#endif
// see if the kept mods changed in some way
{
@@ -140,12 +175,16 @@ void ModFolderModel::finishUpdate()
{
QSet<QString> added = newSet;
added.subtract(currentSet);
- beginInsertRows(QModelIndex(), mods.size(), mods.size() + added.size() - 1);
- for(auto & addedMod: added) {
- mods.append(newMods[addedMod]);
- resolveMod(mods.last());
+
+ // When you have a Qt build with assertions turned on, proceeding here will abort the application
+ if (added.size() > 0) {
+ beginInsertRows(QModelIndex(), mods.size(), mods.size() + added.size() - 1);
+ for (auto& addedMod : added) {
+ mods.append(newMods[addedMod]);
+ resolveMod(mods.last());
+ }
+ endInsertRows();
}
- endInsertRows();
}
// update index
@@ -153,7 +192,7 @@ void ModFolderModel::finishUpdate()
modsIndex.clear();
int idx = 0;
for(auto & mod: mods) {
- modsIndex[mod.mmc_id()] = idx;
+ modsIndex[mod.internal_id()] = idx;
idx++;
}
}
@@ -174,9 +213,9 @@ void ModFolderModel::resolveMod(Mod& m)
return;
}
- auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.filename());
+ auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.fileinfo());
auto result = task->result();
- result->id = m.mmc_id();
+ result->id = m.internal_id();
activeTickets.insert(nextResolutionTicket, result);
m.setResolving(true, nextResolutionTicket);
nextResolutionTicket++;
@@ -278,7 +317,8 @@ bool ModFolderModel::installMod(const QString &filename)
return false;
}
FS::updateTimestamp(newpath);
- installedMod.repath(newpath);
+ QFileInfo newpathInfo(newpath);
+ installedMod.repath(newpathInfo);
update();
return true;
}
@@ -296,7 +336,8 @@ bool ModFolderModel::installMod(const QString &filename)
qWarning() << "Copy of folder from" << originalPath << "to" << newpath << "has (potentially partially) failed.";
return false;
}
- installedMod.repath(newpath);
+ QFileInfo newpathInfo(newpath);
+ installedMod.repath(newpathInfo);
update();
return true;
}
@@ -333,8 +374,12 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
for (auto i: indexes)
{
+ if(i.column() != 0) {
+ continue;
+ }
Mod &m = mods[i.row()];
- m.destroy();
+ auto index_dir = indexDir();
+ m.destroy(index_dir);
}
return true;
}
@@ -381,7 +426,7 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
}
case Qt::ToolTipRole:
- return mods[row].mmc_id();
+ return mods[row].internal_id();
case Qt::CheckStateRole:
switch (column)
@@ -436,11 +481,11 @@ bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction actio
}
// preserve the row, but change its ID
- auto oldId = mod.mmc_id();
+ auto oldId = mod.internal_id();
if(!mod.enable(!mod.enabled())) {
return false;
}
- auto newId = mod.mmc_id();
+ auto newId = mod.internal_id();
if(modsIndex.contains(newId)) {
// NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled
// But is it necessary?
diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h
index 62c504df..24b4d358 100644
--- a/launcher/minecraft/mod/ModFolderModel.h
+++ b/launcher/minecraft/mod/ModFolderModel.h
@@ -1,17 +1,38 @@
-/* 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.
- */
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+*
+* 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 <https://www.gnu.org/licenses/>.
+*
+* 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
@@ -24,8 +45,8 @@
#include "Mod.h"
-#include "ModFolderLoadTask.h"
-#include "LocalModParseTask.h"
+#include "minecraft/mod/tasks/ModFolderLoadTask.h"
+#include "minecraft/mod/tasks/LocalModParseTask.h"
class LegacyInstance;
class BaseInstance;
@@ -52,7 +73,7 @@ public:
Enable,
Toggle
};
- ModFolderModel(const QString &dir);
+ ModFolderModel(const QString &dir, bool is_indexed = false);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
@@ -108,11 +129,16 @@ public:
bool isValid();
- QDir dir()
+ QDir& dir()
{
return m_dir;
}
+ QDir indexDir()
+ {
+ return { QString("%1/.index").arg(dir().absolutePath()) };
+ }
+
const QList<Mod> & allMods()
{
return mods;
@@ -141,6 +167,7 @@ protected:
bool scheduled_update = false;
bool interaction_disabled = false;
QDir m_dir;
+ bool m_is_indexed;
QMap<QString, int> modsIndex;
QMap<int, LocalModParseTask::ResultPtr> activeTickets;
int nextResolutionTicket = 0;
diff --git a/launcher/minecraft/mod/ModFolderModel_test.cpp b/launcher/minecraft/mod/ModFolderModel_test.cpp
index 76f16ed5..b4d37ce5 100644
--- a/launcher/minecraft/mod/ModFolderModel_test.cpp
+++ b/launcher/minecraft/mod/ModFolderModel_test.cpp
@@ -1,7 +1,40 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+*
+* 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 <https://www.gnu.org/licenses/>.
+*
+* 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 <QTest>
#include <QTemporaryDir>
-#include "TestUtil.h"
#include "FileSystem.h"
#include "minecraft/mod/ModFolderModel.h"
@@ -16,7 +49,7 @@ slots:
void test_1178()
{
// source
- QString source = QFINDTESTDATA("data/test_folder");
+ QString source = QFINDTESTDATA("testdata/test_folder");
// sanity check
QVERIFY(!source.endsWith('/'));
@@ -32,8 +65,11 @@ slots:
{
QString folder = source;
QTemporaryDir tempDir;
- ModFolderModel m(tempDir.path());
+ QEventLoop loop;
+ ModFolderModel m(tempDir.path(), true);
+ connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
m.installMod(folder);
+ loop.exec();
verify(tempDir.path());
}
@@ -41,8 +77,11 @@ slots:
{
QString folder = source + '/';
QTemporaryDir tempDir;
- ModFolderModel m(tempDir.path());
+ QEventLoop loop;
+ ModFolderModel m(tempDir.path(), true);
+ connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
m.installMod(folder);
+ loop.exec();
verify(tempDir.path());
}
}
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
index f3d7f566..276804ed 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+*
+* 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 <https://www.gnu.org/licenses/>.
+*
+* 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 "ResourcePackFolderModel.h"
ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) {
diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp
index d5956da1..e3a22219 100644
--- a/launcher/minecraft/mod/TexturePackFolderModel.cpp
+++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+*
+* 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 <https://www.gnu.org/licenses/>.
+*
+* 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 "TexturePackFolderModel.h"
TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir) {
diff --git a/launcher/minecraft/mod/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
index a7bec5ae..3354732b 100644
--- a/launcher/minecraft/mod/LocalModParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
@@ -35,7 +35,6 @@ std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
details->name = name;
}
details->version = firstObj.value("version").toString();
- details->updateurl = firstObj.value("updateUrl").toString();
auto homeurl = firstObj.value("url").toString().trimmed();
if(!homeurl.isEmpty())
{
@@ -57,7 +56,6 @@ std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
{
details->authors.append(author.toString());
}
- details->credits = firstObj.value("credits").toString();
return details;
};
QJsonParseError jsonError;
@@ -168,27 +166,9 @@ std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents)
}
if(!authors.isEmpty())
{
- // author information is stored as a string now, not a list
details->authors.append(authors);
}
- // is credits even used anywhere? including this for completion/parity with old data version
- toml_datum_t creditsDatum = toml_string_in(tomlData, "credits");
- QString credits = "";
- if(creditsDatum.ok)
- {
- authors = creditsDatum.u.s;
- free(creditsDatum.u.s);
- }
- else
- {
- creditsDatum = toml_string_in(tomlModsTable0, "credits");
- if(creditsDatum.ok)
- {
- credits = creditsDatum.u.s;
- free(creditsDatum.u.s);
- }
- }
- details->credits = credits;
+
toml_datum_t homeurlDatum = toml_string_in(tomlData, "displayURL");
QString homeurl = "";
if(homeurlDatum.ok)
diff --git a/launcher/minecraft/mod/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h
index 0f119ba6..ed92394c 100644
--- a/launcher/minecraft/mod/LocalModParseTask.h
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h
@@ -1,9 +1,11 @@
#pragma once
-#include <QRunnable>
+
#include <QDebug>
#include <QObject>
-#include "Mod.h"
-#include "ModDetails.h"
+#include <QRunnable>
+
+#include "minecraft/mod/Mod.h"
+#include "minecraft/mod/ModDetails.h"
class LocalModParseTask : public QObject, public QRunnable
{
diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp
new file mode 100644
index 00000000..1bdecb8c
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+*
+* 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 <https://www.gnu.org/licenses/>.
+*/
+
+#include "LocalModUpdateTask.h"
+
+#include "Application.h"
+#include "FileSystem.h"
+#include "minecraft/mod/MetadataHandler.h"
+
+#ifdef Q_OS_WIN32
+#include <windows.h>
+#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)
+{
+ // 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));
+ }
+
+#ifdef Q_OS_WIN32
+ SetFileAttributesA(index_dir.path().toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
+#endif
+}
+
+void LocalModUpdateTask::executeTask()
+{
+ setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name));
+
+ auto pw_mod = Metadata::create(m_index_dir, m_mod, m_mod_version);
+ Metadata::update(m_index_dir, pw_mod);
+
+ emitSucceeded();
+}
+
+auto LocalModUpdateTask::abort() -> bool
+{
+ emitAborted();
+ return true;
+}
diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.h b/launcher/minecraft/mod/tasks/LocalModUpdateTask.h
new file mode 100644
index 00000000..2db183e0
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.h
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <QDir>
+
+#include "modplatform/ModIndex.h"
+#include "tasks/Task.h"
+
+class LocalModUpdateTask : public Task {
+ Q_OBJECT
+ public:
+ using Ptr = shared_qobject_ptr<LocalModUpdateTask>;
+
+ explicit LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version);
+
+ auto canAbort() const -> bool override { return true; }
+ auto abort() -> bool override;
+
+ protected slots:
+ //! Entry point for tasks.
+ void executeTask() override;
+
+ private:
+ QDir m_index_dir;
+ ModPlatform::IndexedPack& m_mod;
+ ModPlatform::IndexedVersion& m_mod_version;
+};
diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
new file mode 100644
index 00000000..276414e4
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+*
+* 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 <https://www.gnu.org/licenses/>.
+*
+* 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 "ModFolderLoadTask.h"
+
+#include "Application.h"
+#include "minecraft/mod/MetadataHandler.h"
+
+ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed)
+ : m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_result(new Result())
+{}
+
+void ModFolderLoadTask::run()
+{
+ if (m_is_indexed) {
+ // Read metadata first
+ getFromMetadata();
+ }
+
+ // Read JAR files that don't have metadata
+ m_mods_dir.refresh();
+ for (auto entry : m_mods_dir.entryInfoList()) {
+ Mod mod(entry);
+
+ if (mod.enabled()) {
+ if (m_result->mods.contains(mod.internal_id())) {
+ m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed);
+ }
+ else {
+ m_result->mods[mod.internal_id()] = mod;
+ m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata);
+ }
+ }
+ else {
+ QString chopped_id = mod.internal_id().chopped(9);
+ if (m_result->mods.contains(chopped_id)) {
+ m_result->mods[mod.internal_id()] = mod;
+
+ auto metadata = m_result->mods[chopped_id].metadata();
+ if (metadata) {
+ mod.setMetadata(new Metadata::ModStruct(*metadata));
+
+ m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed);
+ m_result->mods.remove(chopped_id);
+ }
+ }
+ else {
+ m_result->mods[mod.internal_id()] = mod;
+ m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata);
+ }
+ }
+ }
+
+ emit succeeded();
+}
+
+void ModFolderLoadTask::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()){
+ return;
+ }
+
+ Mod mod(m_mods_dir, metadata);
+ mod.setStatus(ModStatus::NotInstalled);
+ m_result->mods[mod.internal_id()] = mod;
+ }
+}
diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h
new file mode 100644
index 00000000..088f873e
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+*
+* 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 <https://www.gnu.org/licenses/>.
+*
+* 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 <QDir>
+#include <QMap>
+#include <QObject>
+#include <QRunnable>
+#include <memory>
+#include "minecraft/mod/Mod.h"
+
+class ModFolderLoadTask : public QObject, public QRunnable
+{
+ Q_OBJECT
+public:
+ struct Result {
+ QMap<QString, Mod> mods;
+ };
+ using ResultPtr = std::shared_ptr<Result>;
+ ResultPtr result() const {
+ return m_result;
+ }
+
+public:
+ ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed);
+ void run();
+signals:
+ void succeeded();
+
+private:
+ void getFromMetadata();
+
+private:
+ QDir& m_mods_dir, m_index_dir;
+ bool m_is_indexed;
+ ResultPtr m_result;
+};
diff --git a/launcher/minecraft/mod/testdata/test_folder/assets/minecraft/textures/blah.txt b/launcher/minecraft/mod/testdata/test_folder/assets/minecraft/textures/blah.txt
new file mode 100644
index 00000000..8d1c8b69
--- /dev/null
+++ b/launcher/minecraft/mod/testdata/test_folder/assets/minecraft/textures/blah.txt
@@ -0,0 +1 @@
+
diff --git a/launcher/minecraft/mod/testdata/test_folder/pack.mcmeta b/launcher/minecraft/mod/testdata/test_folder/pack.mcmeta
new file mode 100644
index 00000000..67ee0434
--- /dev/null
+++ b/launcher/minecraft/mod/testdata/test_folder/pack.mcmeta
@@ -0,0 +1,6 @@
+{
+ "pack": {
+ "pack_format": 1,
+ "description": "Some resource pack maybe"
+ }
+}
diff --git a/launcher/minecraft/mod/testdata/test_folder/pack.nfo b/launcher/minecraft/mod/testdata/test_folder/pack.nfo
new file mode 100644
index 00000000..8d1c8b69
--- /dev/null
+++ b/launcher/minecraft/mod/testdata/test_folder/pack.nfo
@@ -0,0 +1 @@
+
diff --git a/launcher/minecraft/services/CapeChange.cpp b/launcher/minecraft/services/CapeChange.cpp
index e49c166a..c73a11b6 100644
--- a/launcher/minecraft/services/CapeChange.cpp
+++ b/launcher/minecraft/services/CapeChange.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "CapeChange.h"
#include <QNetworkRequest>
@@ -34,7 +69,11 @@ void CapeChange::clearCape() {
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
+#else
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
+#endif
connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
}
diff --git a/launcher/minecraft/services/SkinDelete.cpp b/launcher/minecraft/services/SkinDelete.cpp
index cce8364e..921bd094 100644
--- a/launcher/minecraft/services/SkinDelete.cpp
+++ b/launcher/minecraft/services/SkinDelete.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "SkinDelete.h"
#include <QNetworkRequest>
@@ -19,7 +54,11 @@ void SkinDelete::executeTask()
setStatus(tr("Deleting skin"));
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
+#else
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
+#endif
connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
}
diff --git a/launcher/minecraft/services/SkinUpload.cpp b/launcher/minecraft/services/SkinUpload.cpp
index 7c2e8337..c7987875 100644
--- a/launcher/minecraft/services/SkinUpload.cpp
+++ b/launcher/minecraft/services/SkinUpload.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "SkinUpload.h"
#include <QNetworkRequest>
@@ -44,7 +79,11 @@ void SkinUpload::executeTask()
setStatus(tr("Uploading skin"));
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
+#else
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
+#endif
connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
}
diff --git a/launcher/minecraft/update/AssetUpdateTask.cpp b/launcher/minecraft/update/AssetUpdateTask.cpp
index c4bddb08..dd246665 100644
--- a/launcher/minecraft/update/AssetUpdateTask.cpp
+++ b/launcher/minecraft/update/AssetUpdateTask.cpp
@@ -43,6 +43,7 @@ void AssetUpdateTask::executeTask()
connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::assetIndexFinished);
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed);
+ connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
qDebug() << m_inst->name() << ": Starting asset index download";
@@ -80,6 +81,7 @@ void AssetUpdateTask::assetIndexFinished()
downloadJob = job;
connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::emitSucceeded);
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed);
+ connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
downloadJob->start();
return;
diff --git a/launcher/minecraft/update/FMLLibrariesTask.cpp b/launcher/minecraft/update/FMLLibrariesTask.cpp
index 58141991..b6238ce9 100644
--- a/launcher/minecraft/update/FMLLibrariesTask.cpp
+++ b/launcher/minecraft/update/FMLLibrariesTask.cpp
@@ -72,6 +72,7 @@ void FMLLibrariesTask::executeTask()
connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished);
connect(dljob, &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed);
+ connect(dljob, &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
connect(dljob, &NetJob::progress, this, &FMLLibrariesTask::progress);
downloadJob.reset(dljob);
downloadJob->start();
diff --git a/launcher/minecraft/update/LibrariesTask.cpp b/launcher/minecraft/update/LibrariesTask.cpp
index 26679110..aa2bf407 100644
--- a/launcher/minecraft/update/LibrariesTask.cpp
+++ b/launcher/minecraft/update/LibrariesTask.cpp
@@ -68,6 +68,7 @@ void LibrariesTask::executeTask()
connect(downloadJob.get(), &NetJob::succeeded, this, &LibrariesTask::emitSucceeded);
connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed);
+ connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress);
downloadJob->start();
}
diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h
index 8e6cd45c..cf116353 100644
--- a/launcher/modplatform/ModAPI.h
+++ b/launcher/modplatform/ModAPI.h
@@ -1,12 +1,49 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QString>
#include <QList>
+#include <list>
#include "Version.h"
namespace ModPlatform {
class ListModel;
+struct IndexedPack;
}
class ModAPI {
@@ -16,24 +53,32 @@ class ModAPI {
public:
virtual ~ModAPI() = default;
- // https://docs.curseforge.com/?http#tocS_ModLoaderType
- enum ModLoaderType { Unspecified = 0, Forge = 1, Cauldron = 2, LiteLoader = 3, Fabric = 4, Quilt = 5 };
+ enum ModLoaderType {
+ Unspecified = 0,
+ Forge = 1 << 0,
+ Cauldron = 1 << 1,
+ LiteLoader = 1 << 2,
+ Fabric = 1 << 3,
+ Quilt = 1 << 4
+ };
+ Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
struct SearchArgs {
int offset;
QString search;
QString sorting;
- ModLoaderType mod_loader;
+ ModLoaderTypes loaders;
std::list<Version> versions;
};
virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0;
+ virtual void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) = 0;
struct VersionSearchArgs {
QString addonId;
std::list<Version> mcVersions;
- ModLoaderType loader;
+ ModLoaderTypes loaders;
};
virtual void getVersions(CallerType* caller, VersionSearchArgs&& args) const = 0;
@@ -61,7 +106,7 @@ class ModAPI {
{
QString s;
for(auto& ver : mcVersions){
- s += QString("%1,").arg(ver.toString());
+ s += QString("\"%1\",").arg(ver.toString());
}
s.remove(s.length() - 1, 1); //remove last comma
return s;
diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp
new file mode 100644
index 00000000..3c4b7887
--- /dev/null
+++ b/launcher/modplatform/ModIndex.cpp
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+*/
+
+#include "modplatform/ModIndex.h"
+
+#include <QCryptographicHash>
+
+namespace ModPlatform {
+
+auto ProviderCapabilities::name(Provider p) -> const char*
+{
+ switch (p) {
+ case Provider::MODRINTH:
+ return "modrinth";
+ case Provider::FLAME:
+ return "curseforge";
+ }
+ return {};
+}
+auto ProviderCapabilities::readableName(Provider p) -> QString
+{
+ switch (p) {
+ case Provider::MODRINTH:
+ return "Modrinth";
+ case Provider::FLAME:
+ return "CurseForge";
+ }
+ return {};
+}
+auto ProviderCapabilities::hashType(Provider p) -> QStringList
+{
+ switch (p) {
+ case Provider::MODRINTH:
+ return { "sha512", "sha1" };
+ case Provider::FLAME:
+ // Try newer formats first, fall back to old format
+ return { "sha1", "md5", "murmur2" };
+ }
+ return {};
+}
+auto ProviderCapabilities::hash(Provider p, QByteArray& data, QString type) -> QByteArray
+{
+ switch (p) {
+ case Provider::MODRINTH: {
+ // NOTE: Data is the result of reading the entire JAR file!
+
+ // If 'type' was specified, we use that
+ if (!type.isEmpty() && hashType(p).contains(type)) {
+ if (type == "sha512")
+ return QCryptographicHash::hash(data, QCryptographicHash::Sha512);
+ else if (type == "sha1")
+ return QCryptographicHash::hash(data, QCryptographicHash::Sha1);
+ }
+
+ return QCryptographicHash::hash(data, QCryptographicHash::Sha512);
+ }
+ case Provider::FLAME:
+ // If 'type' was specified, we use that
+ if (!type.isEmpty() && hashType(p).contains(type)) {
+ if(type == "sha1")
+ return QCryptographicHash::hash(data, QCryptographicHash::Sha1);
+ else if (type == "md5")
+ return QCryptographicHash::hash(data, QCryptographicHash::Md5);
+ }
+
+ break;
+ }
+ return {};
+}
+
+} // namespace ModPlatform
diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h
index 7e1cf254..c27643af 100644
--- a/launcher/modplatform/ModIndex.h
+++ b/launcher/modplatform/ModIndex.h
@@ -1,3 +1,21 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+*/
+
#pragma once
#include <QList>
@@ -8,11 +26,30 @@
namespace ModPlatform {
+enum class Provider {
+ MODRINTH,
+ FLAME
+};
+
+class ProviderCapabilities {
+ public:
+ auto name(Provider) -> const char*;
+ auto readableName(Provider) -> QString;
+ auto hashType(Provider) -> QStringList;
+ auto hash(Provider, QByteArray&, QString type = "") -> QByteArray;
+};
+
struct ModpackAuthor {
QString name;
QString url;
};
+struct DonationData {
+ QString id;
+ QString platform;
+ QString url;
+};
+
struct IndexedVersion {
QVariant addonId;
QVariant fileId;
@@ -22,10 +59,22 @@ struct IndexedVersion {
QString date;
QString fileName;
QVector<QString> loaders = {};
+ QString hash_type;
+ QString hash;
+};
+
+struct ExtraPackData {
+ QList<DonationData> donate;
+
+ QString issuesUrl;
+ QString sourceUrl;
+ QString wikiUrl;
+ QString discordUrl;
};
struct IndexedPack {
QVariant addonId;
+ Provider provider;
QString name;
QString description;
QList<ModpackAuthor> authors;
@@ -35,8 +84,13 @@ struct IndexedPack {
bool versionsLoaded = false;
QVector<IndexedVersion> versions;
+
+ // Don't load by default, since some modplatform don't have that info
+ bool extraDataLoaded = true;
+ ExtraPackData extraData;
};
} // namespace ModPlatform
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
+Q_DECLARE_METATYPE(ModPlatform::Provider)
diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
index 9dcb3504..0ed0ad29 100644
--- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
+++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
@@ -1,23 +1,42 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright 2021 Petr Mrazek <peterix@gmail.com>
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright 2021 Petr Mrazek <peterix@gmail.com>
+ *
+ * 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 "ATLPackInstallTask.h"
-#include <QtConcurrent/QtConcurrent>
+#include <QtConcurrent>
#include <quazip/quazip.h>
@@ -39,10 +58,13 @@
namespace ATLauncher {
-PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString pack, QString version)
+static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version);
+
+PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version)
{
m_support = support;
- m_pack = pack;
+ m_pack_name = packName;
+ m_pack_safe_name = packName.replace(QRegularExpression("[^A-Za-z0-9]"), "");
m_version_name = version;
}
@@ -60,7 +82,7 @@ void PackInstallTask::executeTask()
qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId();
auto *netJob = new NetJob("ATLauncher::VersionFetch", APPLICATION->network());
auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json")
- .arg(m_pack).arg(m_version_name);
+ .arg(m_pack_safe_name).arg(m_version_name);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
jobPtr = netJob;
jobPtr->start();
@@ -74,14 +96,13 @@ void PackInstallTask::onDownloadSucceeded()
qDebug() << "PackInstallTask::onDownloadSucceeded: " << QThread::currentThreadId();
jobPtr.reset();
- QJsonParseError parse_error;
+ QJsonParseError parse_error {};
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << "Error while parsing JSON response from ATLauncher at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response;
return;
}
-
auto obj = doc.object();
ATLauncher::PackVersion version;
@@ -96,19 +117,15 @@ void PackInstallTask::onDownloadSucceeded()
}
m_version = version;
- auto vlist = APPLICATION->metadataIndex()->get("net.minecraft");
- if(!vlist)
- {
- emitFailed(tr("Failed to get local metadata index for %1").arg("net.minecraft"));
- return;
- }
+ // Display install message if one exists
+ if (!m_version.messages.install.isEmpty())
+ m_support->displayMessage(m_version.messages.install);
- auto ver = vlist->getVersion(m_version.minecraft);
+ auto ver = getComponentVersion("net.minecraft", m_version.minecraft);
if (!ver) {
- emitFailed(tr("Failed to get local metadata index for '%1' v%2").arg("net.minecraft").arg(m_version.minecraft));
+ emitFailed(tr("Failed to get local metadata index for '%1' v%2").arg("net.minecraft", m_version.minecraft));
return;
}
- ver->load(Net::Mode::Online);
minecraftVersion = ver;
if(m_version.noConfigs) {
@@ -303,9 +320,50 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared
auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");
auto f = std::make_shared<VersionFile>();
- f->name = m_pack + " " + m_version_name + " (libraries)";
+ f->name = m_pack_name + " " + m_version_name + " (libraries)";
+
+ const static QMap<QString, QString> liteLoaderMap = {
+ { "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" },
+ { "1420785ecbfed5aff4a586c5c9dd97eb", "1.12.2-SNAPSHOT" },
+ { "073f68e2fcb518b91fd0d99462441714", "1.6.2_03" },
+ { "10a15b52fc59b1bfb9c05b56de1097d6", "1.6.2_02" },
+ { "b52f90f08303edd3d4c374e268a5acf1", "1.6.2_04" },
+ { "ea747e24e03e24b7cad5bc8a246e0319", "1.6.2_01" },
+ { "55785ccc82c07ff0ba038fe24be63ea2", "1.7.10_01" },
+ { "63ada46e033d0cb6782bada09ad5ca4e", "1.7.10_04" },
+ { "7983e4b28217c9ae8569074388409c86", "1.7.10_03" },
+ { "c09882458d74fe0697c7681b8993097e", "1.7.10_02" },
+ { "db7235aefd407ac1fde09a7baba50839", "1.7.10_00" },
+ { "6e9028816027f53957bd8fcdfabae064", "1.8" },
+ { "5e732dc446f9fe2abe5f9decaec40cde", "1.10-SNAPSHOT" },
+ { "3a98b5ed95810bf164e71c1a53be568d", "1.11.2-SNAPSHOT" },
+ { "ba8e6285966d7d988a96496f48cbddaa", "1.8.9-SNAPSHOT" },
+ { "8524af3ac3325a82444cc75ae6e9112f", "1.11-SNAPSHOT" },
+ { "53639d52340479ccf206a04f5e16606f", "1.5.2_01" },
+ { "1fcdcf66ce0a0806b7ad8686afdce3f7", "1.6.4_00" },
+ { "531c116f71ae2b11033f9a11a0f8e668", "1.6.4_01" },
+ { "4009eeb99c9068f608d3483a6439af88", "1.7.2_03" },
+ { "66f343354b8417abce1a10d557d2c6e9", "1.7.2_04" },
+ { "ab554c21f28fbc4ae9b098bcb5f4cceb", "1.7.2_05" },
+ { "e1d76a05a3723920e2f80a5e66c45f16", "1.7.2_02" },
+ { "00318cb0c787934d523f63cdfe8ddde4", "1.9-SNAPSHOT" },
+ { "986fd1ee9525cb0dcab7609401cef754", "1.9.4-SNAPSHOT" },
+ { "571ad5e6edd5ff40259570c9be588bb5", "1.9.4" },
+ { "1cdd72f7232e45551f16cc8ffd27ccf3", "1.10.2-SNAPSHOT" },
+ { "8a7c21f32d77ee08b393dd3921ced8eb", "1.10.2" },
+ { "b9bef8abc8dc309069aeba6fbbe58980", "1.12.1-SNAPSHOT" }
+ };
for(const auto & lib : m_version.libraries) {
+ // If the library is LiteLoader, we need to ignore it and handle it separately.
+ if (liteLoaderMap.contains(lib.md5)) {
+ auto ver = getComponentVersion("com.mumfrey.liteloader", liteLoaderMap.value(lib.md5));
+ if (ver) {
+ componentsToInstall.insert("com.mumfrey.liteloader", ver);
+ continue;
+ }
+ }
+
auto libName = detectLibrary(lib);
GradleSpecifier libSpecifier(libName);
@@ -357,7 +415,31 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared
bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile)
{
- if(m_version.mainClass == QString() && m_version.extraArguments == QString()) {
+ if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.arguments.isEmpty()) {
+ return true;
+ }
+
+ auto mainClass = m_version.mainClass.mainClass;
+ auto extraArguments = m_version.extraArguments.arguments;
+
+ auto hasMainClassDepends = !m_version.mainClass.depends.isEmpty();
+ auto hasExtraArgumentsDepends = !m_version.extraArguments.depends.isEmpty();
+ if (hasMainClassDepends || hasExtraArgumentsDepends) {
+ QSet<QString> mods;
+ for (const auto& item : m_version.mods) {
+ mods.insert(item.name);
+ }
+
+ if (hasMainClassDepends && !mods.contains(m_version.mainClass.depends)) {
+ mainClass = "";
+ }
+
+ if (hasExtraArgumentsDepends && !mods.contains(m_version.extraArguments.depends)) {
+ extraArguments = "";
+ }
+ }
+
+ if (mainClass.isEmpty() && extraArguments.isEmpty()) {
return true;
}
@@ -384,13 +466,13 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<
}
auto f = std::make_shared<VersionFile>();
- f->name = m_pack + " " + m_version_name;
- if(m_version.mainClass != QString() && !mainClasses.contains(m_version.mainClass)) {
- f->mainClass = m_version.mainClass;
+ f->name = m_pack_name + " " + m_version_name;
+ if (!mainClass.isEmpty() && !mainClasses.contains(mainClass)) {
+ f->mainClass = mainClass;
}
// Parse out tweakers
- auto args = m_version.extraArguments.split(" ");
+ auto args = extraArguments.split(" ");
QString previous;
for(auto arg : args) {
if(arg.startsWith("--tweakClass=") || previous == "--tweakClass") {
@@ -426,9 +508,9 @@ void PackInstallTask::installConfigs()
setStatus(tr("Downloading configs..."));
jobPtr = new NetJob(tr("Config download"), APPLICATION->network());
- auto path = QString("Configs/%1/%2.zip").arg(m_pack).arg(m_version_name);
+ auto path = QString("Configs/%1/%2.zip").arg(m_pack_safe_name).arg(m_version_name);
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.zip")
- .arg(m_pack).arg(m_version_name);
+ .arg(m_pack_safe_name).arg(m_version_name);
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path);
entry->setStale(true);
@@ -475,7 +557,11 @@ void PackInstallTask::extractConfigs()
return;
}
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/minecraft");
+#else
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/minecraft");
+#endif
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, [&]()
{
downloadMods();
@@ -502,7 +588,7 @@ void PackInstallTask::downloadMods()
QVector<QString> selectedMods;
if (!optionalMods.isEmpty()) {
setStatus(tr("Selecting optional mods..."));
- selectedMods = m_support->chooseOptionalMods(optionalMods);
+ selectedMods = m_support->chooseOptionalMods(m_version, optionalMods);
}
setStatus(tr("Downloading mods..."));
@@ -574,19 +660,12 @@ void PackInstallTask::downloadMods()
jobPtr->addNetAction(dl);
auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file);
- qDebug() << "Will download" << url << "to" << path;
- modsToCopy[entry->getFullPath()] = path;
if(mod.type == ModType::Forge) {
- auto vlist = APPLICATION->metadataIndex()->get("net.minecraftforge");
- if(vlist)
- {
- auto ver = vlist->getVersion(mod.version);
- if(ver) {
- ver->load(Net::Mode::Online);
- componentsToInstall.insert("net.minecraftforge", ver);
- continue;
- }
+ auto ver = getComponentVersion("net.minecraftforge", mod.version);
+ if (ver) {
+ componentsToInstall.insert("net.minecraftforge", ver);
+ continue;
}
qDebug() << "Jarmod: " + path;
@@ -597,6 +676,10 @@ void PackInstallTask::downloadMods()
qDebug() << "Jarmod: " + path;
jarmods.push_back(path);
}
+
+ // Download after Forge handling, to avoid downloading Forge twice.
+ qDebug() << "Will download" << url << "to" << path;
+ modsToCopy[entry->getFullPath()] = path;
}
}
@@ -623,7 +706,11 @@ void PackInstallTask::onModsDownloaded() {
jobPtr.reset();
if(!modsToExtract.empty() || !modsToDecomp.empty() || !modsToCopy.empty()) {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ m_modExtractFuture = QtConcurrent::run(QThreadPool::globalInstance(), &PackInstallTask::extractMods, this, modsToExtract, modsToDecomp, modsToCopy);
+#else
m_modExtractFuture = QtConcurrent::run(QThreadPool::globalInstance(), this, &PackInstallTask::extractMods, modsToExtract, modsToDecomp, modsToCopy);
+#endif
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onModsExtracted);
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]()
{
@@ -675,7 +762,7 @@ bool PackInstallTask::extractMods(
QString folderToExtract = "";
if(mod.type == ModType::Extract) {
folderToExtract = mod.extractFolder;
- folderToExtract.remove(QRegExp("^/"));
+ folderToExtract.remove(QRegularExpression("^/"));
}
qDebug() << "Extracting " + mod.file + " to " + extractToDir;
@@ -703,6 +790,17 @@ bool PackInstallTask::extractMods(
for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) {
auto &from = iter.key();
auto &to = iter.value();
+
+ // If the file already exists, assume the mod is the correct copy - and remove
+ // the copy from the Configs.zip
+ QFileInfo fileInfo(to);
+ if (fileInfo.exists()) {
+ if (!QFile::remove(to)) {
+ qWarning() << "Failed to delete" << to;
+ return false;
+ }
+ }
+
FS::copy fileCopyOperation(from, to);
if(!fileCopyOperation()) {
qWarning() << "Failed to copy" << from << "to" << to;
@@ -740,14 +838,14 @@ void PackInstallTask::install()
auto version = getVersionForLoader("net.minecraftforge");
if(version == Q_NULLPTR) return;
- components->setComponentVersion("net.minecraftforge", version, true);
+ components->setComponentVersion("net.minecraftforge", version);
}
else if(m_version.loader.type == QString("fabric"))
{
auto version = getVersionForLoader("net.fabricmc.fabric-loader");
if(version == Q_NULLPTR) return;
- components->setComponentVersion("net.fabricmc.fabric-loader", version, true);
+ components->setComponentVersion("net.fabricmc.fabric-loader", version);
}
else if(m_version.loader.type != QString())
{
@@ -773,10 +871,30 @@ void PackInstallTask::install()
instance.setName(m_instName);
instance.setIconKey(m_instIcon);
+ instance.setManagedPack("atlauncher", m_pack_safe_name, m_pack_name, m_version_name, m_version_name);
instanceSettings->resumeSave();
jarmods.clear();
emitSucceeded();
}
+static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version)
+{
+ auto vlist = APPLICATION->metadataIndex()->get(uid);
+ if (!vlist)
+ return {};
+
+ if (!vlist->isLoaded())
+ vlist->load(Net::Mode::Online);
+
+ auto ver = vlist->getVersion(version);
+ if (!ver)
+ return {};
+
+ if (!ver->isLoaded())
+ ver->load(Net::Mode::Online);
+
+ return ver;
+}
+
}
diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h
index 783ec19b..f55873e9 100644
--- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h
+++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h
@@ -1,18 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright 2021 Petr Mrazek <peterix@gmail.com>
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright 2021 Petr Mrazek <peterix@gmail.com>
+ *
+ * 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
@@ -37,7 +56,7 @@ public:
/**
* Requests a user interaction to select which optional mods should be installed.
*/
- virtual QVector<QString> chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) = 0;
+ virtual QVector<QString> chooseOptionalMods(PackVersion version, QVector<ATLauncher::VersionMod> mods) = 0;
/**
* Requests a user interaction to select a component version from a given version list
@@ -45,6 +64,10 @@ public:
*/
virtual QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) = 0;
+ /**
+ * Requests a user interaction to display a message.
+ */
+ virtual void displayMessage(QString message) = 0;
};
class PackInstallTask : public InstanceTask
@@ -52,7 +75,7 @@ class PackInstallTask : public InstanceTask
Q_OBJECT
public:
- explicit PackInstallTask(UserInteractionSupport *support, QString pack, QString version);
+ explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version);
virtual ~PackInstallTask(){}
bool canAbort() const override { return true; }
@@ -94,7 +117,8 @@ private:
NetJob::Ptr jobPtr;
QByteArray response;
- QString m_pack;
+ QString m_pack_name;
+ QString m_pack_safe_name;
QString m_version_name;
PackVersion m_version;
diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp
index 40be6d53..3af02a09 100644
--- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp
+++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp
@@ -1,18 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright 2021 Petr Mrazek <peterix@gmail.com>
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright 2021 Petr Mrazek <peterix@gmail.com>
+ *
+ * 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 "ATLPackManifest.h"
@@ -178,6 +197,8 @@ static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) {
p.depends.append(Json::requireString(depends));
}
}
+ p.colour = Json::ensureString(obj, QString("colour"), "");
+ p.warning = Json::ensureString(obj, QString("warning"), "");
p.client = Json::ensureBoolean(obj, QString("client"), false);
@@ -185,6 +206,24 @@ static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) {
p.effectively_hidden = p.hidden || p.library;
}
+static void loadVersionMessages(ATLauncher::VersionMessages& m, QJsonObject& obj)
+{
+ m.install = Json::ensureString(obj, "install", "");
+ m.update = Json::ensureString(obj, "update", "");
+}
+
+static void loadVersionMainClass(ATLauncher::PackVersionMainClass& m, QJsonObject& obj)
+{
+ m.mainClass = Json::ensureString(obj, "mainClass", "");
+ m.depends = Json::ensureString(obj, "depends", "");
+}
+
+static void loadVersionExtraArguments(ATLauncher::PackVersionExtraArguments& a, QJsonObject& obj)
+{
+ a.arguments = Json::ensureString(obj, "arguments", "");
+ a.depends = Json::ensureString(obj, "depends", "");
+}
+
void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
{
v.version = Json::requireString(obj, "version");
@@ -193,12 +232,12 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
if(obj.contains("mainClass")) {
auto main = Json::requireObject(obj, "mainClass");
- v.mainClass = Json::ensureString(main, "mainClass", "");
+ loadVersionMainClass(v.mainClass, main);
}
if(obj.contains("extraArguments")) {
auto arguments = Json::requireObject(obj, "extraArguments");
- v.extraArguments = Json::ensureString(arguments, "arguments", "");
+ loadVersionExtraArguments(v.extraArguments, arguments);
}
if(obj.contains("loader")) {
@@ -232,4 +271,17 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
auto configsObj = Json::requireObject(obj, "configs");
loadVersionConfigs(v.configs, configsObj);
}
+
+ auto colourObj = Json::ensureObject(obj, "colours");
+ for (const auto &key : colourObj.keys()) {
+ v.colours[key] = Json::requireString(colourObj.value(key), "colour");
+ }
+
+ auto warningsObj = Json::ensureObject(obj, "warnings");
+ for (const auto &key : warningsObj.keys()) {
+ v.warnings[key] = Json::requireString(warningsObj.value(key), "warning");
+ }
+
+ auto messages = Json::ensureObject(obj, "messages");
+ loadVersionMessages(v.messages, messages);
}
diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h
index 673f2f8b..43510c50 100644
--- a/launcher/modplatform/atlauncher/ATLPackManifest.h
+++ b/launcher/modplatform/atlauncher/ATLPackManifest.h
@@ -1,24 +1,44 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2020 Jamie Mansfield <jmansfield@cadixdev.org>
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2020 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * 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 <QJsonObject>
+#include <QMap>
#include <QString>
#include <QVector>
-#include <QJsonObject>
namespace ATLauncher
{
@@ -109,6 +129,8 @@ struct VersionMod
bool library;
QString group;
QVector<QString> depends;
+ QString colour;
+ QString warning;
bool client;
@@ -122,18 +144,40 @@ struct VersionConfigs
QString sha1;
};
+struct VersionMessages
+{
+ QString install;
+ QString update;
+};
+
+struct PackVersionMainClass
+{
+ QString mainClass;
+ QString depends;
+};
+
+struct PackVersionExtraArguments
+{
+ QString arguments;
+ QString depends;
+};
+
struct PackVersion
{
QString version;
QString minecraft;
bool noConfigs;
- QString mainClass;
- QString extraArguments;
+ PackVersionMainClass mainClass;
+ PackVersionExtraArguments extraArguments;
VersionLoader loader;
QVector<VersionLibrary> libraries;
QVector<VersionMod> mods;
VersionConfigs configs;
+
+ QMap<QString, QString> colours;
+ QMap<QString, QString> warnings;
+ VersionMessages messages;
};
void loadVersion(PackVersion & v, QJsonObject & obj);
diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp
index 95924a68..c1f56658 100644
--- a/launcher/modplatform/flame/FileResolvingTask.cpp
+++ b/launcher/modplatform/flame/FileResolvingTask.cpp
@@ -1,49 +1,129 @@
#include "FileResolvingTask.h"
+
#include "Json.h"
+#include "net/Upload.h"
-Flame::FileResolvingTask::FileResolvingTask(shared_qobject_ptr<QNetworkAccessManager> network, Flame::Manifest& toProcess)
+Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess)
: m_network(network), m_toProcess(toProcess)
{}
void Flame::FileResolvingTask::executeTask()
{
setStatus(tr("Resolving mod IDs..."));
- setProgress(0, m_toProcess.files.size());
+ setProgress(0, 3);
m_dljob = new NetJob("Mod id resolver", m_network);
- results.resize(m_toProcess.files.size());
- int index = 0;
- for (auto& file : m_toProcess.files) {
- auto projectIdStr = QString::number(file.projectId);
- auto fileIdStr = QString::number(file.fileId);
- QString metaurl = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(projectIdStr, fileIdStr);
- auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]);
- m_dljob->addNetAction(dl);
- index++;
- }
+ result.reset(new QByteArray());
+ //build json data to send
+ QJsonObject object;
+
+ object["fileIds"] = QJsonArray::fromVariantList(std::accumulate(m_toProcess.files.begin(), m_toProcess.files.end(), QVariantList(), [](QVariantList& l, const File& s) {
+ l.push_back(s.fileId);
+ return l;
+ }));
+ QByteArray data = Json::toText(object);
+ auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result.get(), data);
+ m_dljob->addNetAction(dl);
connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished);
m_dljob->start();
}
void Flame::FileResolvingTask::netJobFinished()
{
- bool failed = false;
+ setProgress(1, 3);
int index = 0;
- for (auto& bytes : results) {
- auto& out = m_toProcess.files[index];
+ // job to check modrinth for blocked projects
+ auto job = new NetJob("Modrinth check", m_network);
+ blockedProjects = QMap<File *,QByteArray *>();
+ auto doc = Json::requireDocument(*result);
+ auto array = Json::requireArray(doc.object()["data"]);
+ for (QJsonValueRef file : array) {
+ auto fileid = Json::requireInteger(Json::requireObject(file)["id"]);
+ auto& out = m_toProcess.files[fileid];
try {
- failed &= (!out.parseFromBytes(bytes));
+ out.parseFromObject(Json::requireObject(file));
} catch (const JSONValidationError& e) {
- qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:";
- qCritical() << e.cause();
- qCritical() << "JSON:";
- qCritical() << bytes;
- failed = true;
+ qDebug() << "Blocked mod on curseforge" << out.fileName;
+ auto hash = out.hash;
+ if(!hash.isEmpty()) {
+ auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
+ auto output = new QByteArray();
+ auto dl = Net::Download::makeByteArray(QUrl(url), output);
+ QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() {
+ out.resolved = true;
+ });
+
+ job->addNetAction(dl);
+ blockedProjects.insert(&out, output);
+ }
}
index++;
}
- if (!failed) {
- emitSucceeded();
+ connect(job, &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished);
+
+ job->start();
+}
+
+void Flame::FileResolvingTask::modrinthCheckFinished() {
+ setProgress(2, 3);
+ qDebug() << "Finished with blocked mods : " << blockedProjects.size();
+
+ for (auto it = blockedProjects.keyBegin(); it != blockedProjects.keyEnd(); it++) {
+ auto &out = *it;
+ auto bytes = blockedProjects[out];
+ if (!out->resolved) {
+ delete bytes;
+ continue;
+ }
+ QJsonDocument doc = QJsonDocument::fromJson(*bytes);
+ auto obj = doc.object();
+ auto array = Json::requireArray(obj,"files");
+ for (auto file: array) {
+ auto fileObj = Json::requireObject(file);
+ auto primary = Json::requireBoolean(fileObj,"primary");
+ if (primary) {
+ out->url = Json::requireUrl(fileObj,"url");
+ qDebug() << "Found alternative on modrinth " << out->fileName;
+ break;
+ }
+ }
+ delete bytes;
+ }
+ //copy to an output list and filter out projects found on modrinth
+ auto block = new QList<File *>();
+ auto it = blockedProjects.keys();
+ std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) {
+ return !f->resolved;
+ });
+ //Display not found mods early
+ if (!block->empty()) {
+ //blocked mods found, we need the slug for displaying.... we need another job :D !
+ auto slugJob = new NetJob("Slug Job", m_network);
+ auto slugs = QVector<QByteArray>(block->size());
+ auto index = 0;
+ for (auto fileInfo: *block) {
+ auto projectId = fileInfo->projectId;
+ slugs[index] = QByteArray();
+ auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
+ auto dl = Net::Download::makeByteArray(url, &slugs[index]);
+ slugJob->addNetAction(dl);
+ index++;
+ }
+ connect(slugJob, &NetJob::succeeded, this, [slugs, this, slugJob, block]() {
+ slugJob->deleteLater();
+ auto index = 0;
+ for (const auto &slugResult: slugs) {
+ auto json = QJsonDocument::fromJson(slugResult);
+ auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"),
+ "websiteUrl");
+ auto mod = block->at(index);
+ auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId));
+ mod->websiteUrl = link;
+ index++;
+ }
+ emitSucceeded();
+ });
+ slugJob->start();
} else {
- emitFailed(tr("Some mod ID resolving tasks failed."));
+ emitSucceeded();
}
}
diff --git a/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h
index 5e5adcd7..87981f0a 100644
--- a/launcher/modplatform/flame/FileResolvingTask.h
+++ b/launcher/modplatform/flame/FileResolvingTask.h
@@ -10,7 +10,7 @@ class FileResolvingTask : public Task
{
Q_OBJECT
public:
- explicit FileResolvingTask(shared_qobject_ptr<QNetworkAccessManager> network, Flame::Manifest &toProcess);
+ explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest &toProcess);
virtual ~FileResolvingTask() {};
const Flame::Manifest &getResults() const
@@ -27,7 +27,11 @@ protected slots:
private: /* data */
shared_qobject_ptr<QNetworkAccessManager> m_network;
Flame::Manifest m_toProcess;
- QVector<QByteArray> results;
+ std::shared_ptr<QByteArray> result;
NetJob::Ptr m_dljob;
+
+ void modrinthCheckFinished();
+
+ QMap<File *, QByteArray *> blockedProjects;
};
}
diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h
index 61628e60..aea76ff1 100644
--- a/launcher/modplatform/flame/FlameAPI.h
+++ b/launcher/modplatform/flame/FlameAPI.h
@@ -1,5 +1,6 @@
#pragma once
+#include "modplatform/ModIndex.h"
#include "modplatform/helpers/NetworkModAPI.h"
class FlameAPI : public NetworkModAPI {
@@ -37,14 +38,19 @@ class FlameAPI : public NetworkModAPI {
.arg(args.offset)
.arg(args.search)
.arg(getSortFieldInt(args.sorting))
- .arg(getMappedModLoader(args.mod_loader))
+ .arg(getMappedModLoader(args.loaders))
.arg(gameVersionStr);
};
+ inline auto getModInfoURL(QString& id) const -> QString override
+ {
+ return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
+ };
+
inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override
{
QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : "";
- QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loader));
+ QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loaders));
return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&%2%3")
.arg(args.addonId)
@@ -53,11 +59,16 @@ class FlameAPI : public NetworkModAPI {
};
public:
- static auto getMappedModLoader(const ModLoaderType type) -> const ModLoaderType
+ static auto getMappedModLoader(const ModLoaderTypes loaders) -> int
{
+ // https://docs.curseforge.com/?http#tocS_ModLoaderType
+ if (loaders & Forge)
+ return 1;
+ if (loaders & Fabric)
+ return 4;
// TODO: remove this once Quilt drops official Fabric support
- if (type == Quilt) // NOTE: Most if not all Fabric mods should work *currently*
- return Fabric;
- return type;
+ if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently*
+ return 4; // Quilt would probably be 5
+ return 0;
}
};
diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp
index ba0824cf..b99bfb26 100644
--- a/launcher/modplatform/flame/FlameModIndex.cpp
+++ b/launcher/modplatform/flame/FlameModIndex.cpp
@@ -6,9 +6,12 @@
#include "modplatform/flame/FlameAPI.h"
#include "net/NetJob.h"
+static ModPlatform::ProviderCapabilities ProviderCaps;
+
void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
pack.addonId = Json::requireInteger(obj, "id");
+ pack.provider = ModPlatform::Provider::FLAME;
pack.name = Json::requireString(obj, "name");
pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", "");
pack.description = Json::ensureString(obj, "summary", "");
@@ -25,6 +28,38 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
packAuthor.url = Json::requireString(author, "url");
pack.authors.append(packAuthor);
}
+
+ loadExtraPackData(pack, obj);
+}
+
+void FlameMod::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj)
+{
+ auto links_obj = Json::ensureObject(obj, "links");
+
+ pack.extraData.issuesUrl = Json::ensureString(links_obj, "issuesUrl");
+ if(pack.extraData.issuesUrl.endsWith('/'))
+ pack.extraData.issuesUrl.chop(1);
+
+ pack.extraData.sourceUrl = Json::ensureString(links_obj, "sourceUrl");
+ if(pack.extraData.sourceUrl.endsWith('/'))
+ pack.extraData.sourceUrl.chop(1);
+
+ pack.extraData.wikiUrl = Json::ensureString(links_obj, "wikiUrl");
+ if(pack.extraData.wikiUrl.endsWith('/'))
+ pack.extraData.wikiUrl.chop(1);
+
+ pack.extraDataLoaded = true;
+}
+
+static QString enumToString(int hash_algorithm)
+{
+ switch(hash_algorithm){
+ default:
+ case 1:
+ return "sha1";
+ case 2:
+ return "md5";
+ }
}
void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
@@ -38,28 +73,13 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
+
+ auto file = loadIndexedPackVersion(obj);
+ if(!file.addonId.isValid())
+ file.addonId = pack.addonId;
- auto versionArray = Json::requireArray(obj, "gameVersions");
- if (versionArray.isEmpty()) {
- continue;
- }
-
- ModPlatform::IndexedVersion file;
- for (auto mcVer : versionArray) {
- auto str = mcVer.toString();
-
- if (str.contains('.'))
- file.mcVersion.append(str);
- }
-
- file.addonId = pack.addonId;
- file.fileId = Json::requireInteger(obj, "id");
- file.date = Json::requireString(obj, "fileDate");
- file.version = Json::requireString(obj, "displayName");
- file.downloadUrl = Json::requireString(obj, "downloadUrl");
- file.fileName = Json::requireString(obj, "fileName");
-
- unsortedVersions.append(file);
+ 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 {
@@ -70,3 +90,39 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
pack.versions = unsortedVersions;
pack.versionsLoaded = true;
}
+
+auto FlameMod::loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion
+{
+ auto versionArray = Json::requireArray(obj, "gameVersions");
+ if (versionArray.isEmpty()) {
+ return {};
+ }
+
+ ModPlatform::IndexedVersion file;
+ for (auto mcVer : versionArray) {
+ auto str = mcVer.toString();
+
+ if (str.contains('.'))
+ file.mcVersion.append(str);
+ }
+
+ file.addonId = Json::requireInteger(obj, "modId");
+ file.fileId = Json::requireInteger(obj, "id");
+ file.date = Json::requireString(obj, "fileDate");
+ file.version = Json::requireString(obj, "displayName");
+ file.downloadUrl = Json::requireString(obj, "downloadUrl");
+ file.fileName = Json::requireString(obj, "fileName");
+
+ auto hash_list = Json::ensureArray(obj, "hashes");
+ for (auto h : hash_list) {
+ auto hash_entry = Json::ensureObject(h);
+ auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::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");
+ file.hash_type = hash_algo;
+ break;
+ }
+ }
+ return file;
+}
diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h
index d3171d94..9c6c1c6c 100644
--- a/launcher/modplatform/flame/FlameModIndex.h
+++ b/launcher/modplatform/flame/FlameModIndex.h
@@ -12,9 +12,11 @@
namespace FlameMod {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
+void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
BaseInstance* inst);
+auto loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion;
} // namespace FlameMod
diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp
index ac24c647..ad48b7b6 100644
--- a/launcher/modplatform/flame/FlamePackIndex.cpp
+++ b/launcher/modplatform/flame/FlamePackIndex.cpp
@@ -6,7 +6,6 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)
{
pack.addonId = Json::requireInteger(obj, "id");
pack.name = Json::requireString(obj, "name");
- pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", "");
pack.description = Json::ensureString(obj, "summary", "");
auto logo = Json::requireObject(obj, "logo");
@@ -46,6 +45,32 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)
if (!found) {
throw JSONValidationError(QString("Pack with no good file, skipping: %1").arg(pack.name));
}
+
+ loadIndexedInfo(pack, obj);
+}
+
+void Flame::loadIndexedInfo(IndexedPack& pack, QJsonObject& obj)
+{
+ auto links_obj = Json::ensureObject(obj, "links");
+
+ pack.extra.websiteUrl = Json::ensureString(links_obj, "websiteUrl");
+ if(pack.extra.websiteUrl.endsWith('/'))
+ pack.extra.websiteUrl.chop(1);
+
+ pack.extra.issuesUrl = Json::ensureString(links_obj, "issuesUrl");
+ if(pack.extra.issuesUrl.endsWith('/'))
+ pack.extra.issuesUrl.chop(1);
+
+ pack.extra.sourceUrl = Json::ensureString(links_obj, "sourceUrl");
+ if(pack.extra.sourceUrl.endsWith('/'))
+ pack.extra.sourceUrl.chop(1);
+
+ pack.extra.wikiUrl = Json::ensureString(links_obj, "wikiUrl");
+ if(pack.extra.wikiUrl.endsWith('/'))
+ pack.extra.wikiUrl.chop(1);
+
+ pack.extraInfoLoaded = true;
+
}
void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr)
@@ -65,8 +90,12 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr)
// pick the latest version supported
file.mcVersion = versionArray[0].toString();
file.version = Json::requireString(version, "displayName");
- file.downloadUrl = Json::requireString(version, "downloadUrl");
- unsortedVersions.append(file);
+ file.downloadUrl = Json::ensureString(version, "downloadUrl");
+
+ // only add if we have a download URL (third party distribution is enabled)
+ if (!file.downloadUrl.isEmpty()) {
+ unsortedVersions.append(file);
+ }
}
auto orderSortPredicate = [](const IndexedVersion& a, const IndexedVersion& b) -> bool { return a.fileId > b.fileId; };
diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h
index 7ffa29c3..1ca0fc0e 100644
--- a/launcher/modplatform/flame/FlamePackIndex.h
+++ b/launcher/modplatform/flame/FlamePackIndex.h
@@ -20,6 +20,13 @@ struct IndexedVersion {
QString downloadUrl;
};
+struct ModpackExtra {
+ QString websiteUrl;
+ QString wikiUrl;
+ QString issuesUrl;
+ QString sourceUrl;
+};
+
struct IndexedPack
{
int addonId;
@@ -28,13 +35,16 @@ struct IndexedPack
QList<ModpackAuthor> authors;
QString logoName;
QString logoUrl;
- QString websiteUrl;
bool versionsLoaded = false;
QVector<IndexedVersion> versions;
+
+ bool extraInfoLoaded = false;
+ ModpackExtra extra;
};
void loadIndexedPack(IndexedPack & m, QJsonObject & obj);
+void loadIndexedInfo(IndexedPack&, QJsonObject&);
void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr);
}
diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp
index e4f90c1a..12a4b990 100644
--- a/launcher/modplatform/flame/PackManifest.cpp
+++ b/launcher/modplatform/flame/PackManifest.cpp
@@ -41,7 +41,7 @@ static void loadManifestV1(Flame::Manifest& m, QJsonObject& manifest)
auto obj = Json::requireObject(item);
Flame::File file;
loadFileV1(file, obj);
- m.files.append(file);
+ m.files.insert(file.fileId,file);
}
m.overrides = Json::ensureString(manifest, "overrides", "overrides");
}
@@ -61,21 +61,9 @@ void Flame::loadManifest(Flame::Manifest& m, const QString& filepath)
loadManifestV1(m, obj);
}
-bool Flame::File::parseFromBytes(const QByteArray& bytes)
+bool Flame::File::parseFromObject(const QJsonObject& obj)
{
- auto doc = Json::requireDocument(bytes);
- if (!doc.isObject()) {
- throw JSONValidationError(QString("data is not an object? that's not supposed to happen"));
- }
- auto obj = Json::ensureObject(doc.object(), "data");
-
fileName = Json::requireString(obj, "fileName");
-
- QString rawUrl = Json::requireString(obj, "downloadUrl");
- url = QUrl(rawUrl, QUrl::TolerantMode);
- if (!url.isValid()) {
- throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
- }
// This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
// It is also optional
type = File::Type::SingleFile;
@@ -87,6 +75,25 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes)
// this is probably a mod, dunno what else could modpacks download
targetFolder = "mods";
}
+ // get the hash
+ hash = QString();
+ auto hashes = Json::ensureArray(obj, "hashes");
+ for(QJsonValueRef item : hashes) {
+ auto hobj = Json::requireObject(item);
+ auto algo = Json::requireInteger(hobj, "algo");
+ auto value = Json::requireString(hobj, "value");
+ if (algo == 1) {
+ hash = value;
+ }
+ }
+
+
+ // may throw, if the project is blocked
+ QString rawUrl = Json::ensureString(obj, "downloadUrl");
+ url = QUrl(rawUrl, QUrl::TolerantMode);
+ if (!url.isValid()) {
+ throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
+ }
resolved = true;
return true;
diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h
index 02f39f0e..677db1c3 100644
--- a/launcher/modplatform/flame/PackManifest.h
+++ b/launcher/modplatform/flame/PackManifest.h
@@ -1,26 +1,66 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QString>
#include <QVector>
+#include <QMap>
#include <QUrl>
+#include <QJsonObject>
namespace Flame
{
struct File
{
// NOTE: throws JSONValidationError
- bool parseFromBytes(const QByteArray &bytes);
+ bool parseFromObject(const QJsonObject& object);
int projectId = 0;
int fileId = 0;
// NOTE: the opposite to 'optional'. This is at the time of writing unused.
bool required = true;
+ QString hash;
+ // NOTE: only set on blocked files ! Empty otherwise.
+ QString websiteUrl;
// our
bool resolved = false;
QString fileName;
QUrl url;
- QString targetFolder = QLatin1Literal("mods");
+ QString targetFolder = QStringLiteral("mods");
enum class Type
{
Unknown,
@@ -54,7 +94,8 @@ struct Manifest
QString name;
QString version;
QString author;
- QVector<Flame::File> files;
+ //File id -> File
+ QMap<int,Flame::File> files;
QString overrides;
};
diff --git a/launcher/modplatform/helpers/NetworkModAPI.cpp b/launcher/modplatform/helpers/NetworkModAPI.cpp
index 6829b837..d7abd10f 100644
--- a/launcher/modplatform/helpers/NetworkModAPI.cpp
+++ b/launcher/modplatform/helpers/NetworkModAPI.cpp
@@ -31,6 +31,31 @@ void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const
netJob->start();
}
+void NetworkModAPI::getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack)
+{
+ auto id_str = pack.addonId.toString();
+ auto netJob = new NetJob(QString("%1::ModInfo").arg(id_str), APPLICATION->network());
+ auto searchUrl = getModInfoURL(id_str);
+
+ auto response = new QByteArray();
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
+
+ QObject::connect(netJob, &NetJob::succeeded, [response, &pack, caller] {
+ QJsonParseError parse_error{};
+ auto doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response for " << pack.name << " at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qWarning() << *response;
+ return;
+ }
+
+ caller->infoRequestFinished(doc, pack);
+ });
+
+ netJob->start();
+}
+
void NetworkModAPI::getVersions(CallerType* caller, VersionSearchArgs&& args) const
{
auto netJob = new NetJob(QString("%1::ModVersions(%2)").arg(caller->debugName()).arg(args.addonId), APPLICATION->network());
diff --git a/launcher/modplatform/helpers/NetworkModAPI.h b/launcher/modplatform/helpers/NetworkModAPI.h
index 000620b2..87d77ad1 100644
--- a/launcher/modplatform/helpers/NetworkModAPI.h
+++ b/launcher/modplatform/helpers/NetworkModAPI.h
@@ -5,9 +5,11 @@
class NetworkModAPI : public ModAPI {
public:
void searchMods(CallerType* caller, SearchArgs&& args) const override;
+ void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) override;
void getVersions(CallerType* caller, VersionSearchArgs&& args) const override;
protected:
virtual auto getModSearchURL(SearchArgs& args) const -> QString = 0;
+ virtual auto getModInfoURL(QString& id) const -> QString = 0;
virtual auto getVersionsURL(VersionSearchArgs& args) const -> QString = 0;
};
diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp
index 961fe868..4da6a866 100644
--- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp
+++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "PackFetchTask.h"
#include "PrivatePackManager.h"
@@ -103,7 +138,7 @@ bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, Modpac
if(!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol))
{
- auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:3d!").arg(errorMsg, errorLine, errorCol);
+ auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:%3!").arg(errorMsg).arg(errorLine).arg(errorCol);
qWarning() << fullErrMsg;
data.clear();
return false;
diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
index c63a9f1e..83e14969 100644
--- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
+++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "PackInstallTask.h"
#include <QtConcurrent>
@@ -88,7 +123,11 @@ void PackInstallTask::unzip()
return;
}
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/unzip");
+#else
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip");
+#endif
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished);
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled);
m_extractFutureWatcher.setFuture(m_extractFuture);
diff --git a/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp b/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp
index 501e6003..1a81f026 100644
--- a/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp
+++ b/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "PrivatePackManager.h"
#include <QDebug>
@@ -10,7 +45,13 @@ void PrivatePackManager::load()
{
try
{
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ auto foo = QString::fromUtf8(FS::read(m_filename)).split('\n', Qt::SkipEmptyParts);
+ currentPacks = QSet<QString>(foo.begin(), foo.end());
+#else
currentPacks = QString::fromUtf8(FS::read(m_filename)).split('\n', QString::SkipEmptyParts).toSet();
+#endif
+
dirty = false;
}
catch(...)
@@ -28,7 +69,7 @@ void PrivatePackManager::save() const
}
try
{
- QStringList list = currentPacks.toList();
+ QStringList list = currentPacks.values();
FS::write(m_filename, list.join('\n').toUtf8());
dirty = false;
}
diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
index 33df6fa4..cac432cd 100644
--- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
+++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
@@ -1,18 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
+ *
+ * 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 "FTBPackInstallTask.h"
@@ -80,7 +99,7 @@ void PackInstallTask::onDownloadSucceeded()
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response;
return;
}
@@ -197,10 +216,10 @@ void PackInstallTask::install()
if(target.type != "modloader") continue;
if(target.name == "forge") {
- components->setComponentVersion("net.minecraftforge", target.version, true);
+ components->setComponentVersion("net.minecraftforge", target.version);
}
else if(target.name == "fabric") {
- components->setComponentVersion("net.fabricmc.fabric-loader", target.version, true);
+ components->setComponentVersion("net.fabricmc.fabric-loader", target.version);
}
}
@@ -220,6 +239,7 @@ void PackInstallTask::install()
instance.setName(m_instName);
instance.setIconKey(m_instIcon);
+ instance.setManagedPack("modpacksch", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name);
instanceSettings->resumeSave();
emitSucceeded();
diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h
index 6d642b5e..89e52d6c 100644
--- a/launcher/modplatform/modrinth/ModrinthAPI.h
+++ b/launcher/modplatform/modrinth/ModrinthAPI.h
@@ -20,6 +20,7 @@
#include "BuildConfig.h"
#include "modplatform/ModAPI.h"
+#include "modplatform/ModIndex.h"
#include "modplatform/helpers/NetworkModAPI.h"
#include <QDebug>
@@ -28,30 +29,25 @@ class ModrinthAPI : public NetworkModAPI {
public:
inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; };
- static auto getModLoaderStrings(ModLoaderType type) -> const QStringList
+ static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
{
QStringList l;
- switch (type)
+ for (auto loader : {Forge, Fabric, Quilt})
{
- case Unspecified:
- for (auto loader : {Forge, Fabric, Quilt})
- {
- l << ModAPI::getModLoaderString(loader);
- }
- break;
-
- case Quilt:
- l << ModAPI::getModLoaderString(Fabric);
- default:
- l << ModAPI::getModLoaderString(type);
+ if ((types & loader) || types == Unspecified)
+ {
+ l << ModAPI::getModLoaderString(loader);
+ }
}
+ if ((types & Quilt) && (~types & Fabric)) // Add Fabric if Quilt is in use, if Fabric isn't already there
+ l << ModAPI::getModLoaderString(Fabric);
return l;
}
- static auto getModLoaderFilters(ModLoaderType type) -> const QString
+ static auto getModLoaderFilters(ModLoaderTypes types) -> const QString
{
QStringList l;
- for (auto loader : getModLoaderStrings(type))
+ for (auto loader : getModLoaderStrings(types))
{
l << QString("\"categories:%1\"").arg(loader);
}
@@ -61,7 +57,7 @@ class ModrinthAPI : public NetworkModAPI {
private:
inline auto getModSearchURL(SearchArgs& args) const -> QString override
{
- if (!validateModLoader(args.mod_loader)) {
+ if (!validateModLoaders(args.loaders)) {
qWarning() << "Modrinth only have Forge and Fabric-compatible mods!";
return "";
}
@@ -76,19 +72,24 @@ class ModrinthAPI : public NetworkModAPI {
.arg(args.offset)
.arg(args.search)
.arg(args.sorting)
- .arg(getModLoaderFilters(args.mod_loader))
+ .arg(getModLoaderFilters(args.loaders))
.arg(getGameVersionsArray(args.versions));
};
+ inline auto getModInfoURL(QString& id) const -> QString override
+ {
+ return BuildConfig.MODRINTH_PROD_URL + "/project/" + id;
+ };
+
inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override
{
return QString(BuildConfig.MODRINTH_PROD_URL +
"/project/%1/version?"
- "game_versions=[%2]"
+ "game_versions=[%2]&"
"loaders=[\"%3\"]")
- .arg(args.addonId)
- .arg(getGameVersionsString(args.mcVersions))
- .arg(getModLoaderStrings(args.loader).join("\",\""));
+ .arg(args.addonId,
+ getGameVersionsString(args.mcVersions),
+ getModLoaderStrings(args.loaders).join("\",\""));
};
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString
@@ -101,9 +102,9 @@ class ModrinthAPI : public NetworkModAPI {
return s.isEmpty() ? QString() : QString("[%1],").arg(s);
}
- inline auto validateModLoader(ModLoaderType modLoader) const -> bool
+ inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool
{
- return modLoader == Unspecified || modLoader == Forge || modLoader == Fabric || modLoader == Quilt;
+ return (loaders == Unspecified) || (loaders & (Forge | Fabric | Quilt));
}
};
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
index f7fa9864..b6f5490a 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
@@ -1,19 +1,20 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
- *
- * 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 <https://www.gnu.org/licenses/>.
- */
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+*/
#include "ModrinthPackIndex.h"
#include "ModrinthAPI.h"
@@ -24,10 +25,12 @@
#include "net/NetJob.h"
static ModrinthAPI api;
+static ModPlatform::ProviderCapabilities ProviderCaps;
void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
pack.addonId = Json::requireString(obj, "project_id");
+ pack.provider = ModPlatform::Provider::MODRINTH;
pack.name = Json::requireString(obj, "title");
QString slug = Json::ensureString(obj, "slug", "");
@@ -45,6 +48,43 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
modAuthor.name = Json::requireString(obj, "author");
modAuthor.url = api.getAuthorURL(modAuthor.name);
pack.authors.append(modAuthor);
+
+ // Modrinth can have more data than what's provided by the basic search :)
+ pack.extraDataLoaded = false;
+}
+
+void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj)
+{
+ pack.extraData.issuesUrl = Json::ensureString(obj, "issues_url");
+ if(pack.extraData.issuesUrl.endsWith('/'))
+ pack.extraData.issuesUrl.chop(1);
+
+ pack.extraData.sourceUrl = Json::ensureString(obj, "source_url");
+ if(pack.extraData.sourceUrl.endsWith('/'))
+ pack.extraData.sourceUrl.chop(1);
+
+ pack.extraData.wikiUrl = Json::ensureString(obj, "wiki_url");
+ if(pack.extraData.wikiUrl.endsWith('/'))
+ pack.extraData.wikiUrl.chop(1);
+
+ pack.extraData.discordUrl = Json::ensureString(obj, "discord_url");
+ if(pack.extraData.discordUrl.endsWith('/'))
+ pack.extraData.discordUrl.chop(1);
+
+ auto donate_arr = Json::ensureArray(obj, "donation_urls");
+ for(auto d : donate_arr){
+ auto d_obj = Json::requireObject(d);
+
+ ModPlatform::DonationData donate;
+
+ donate.id = Json::ensureString(d_obj, "id");
+ donate.platform = Json::ensureString(d_obj, "platform");
+ donate.url = Json::ensureString(d_obj, "url");
+
+ pack.extraData.donate.append(donate);
+ }
+
+ pack.extraDataLoaded = true;
}
void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
@@ -57,46 +97,10 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
- ModPlatform::IndexedVersion file;
- file.addonId = Json::requireString(obj, "project_id");
- file.fileId = Json::requireString(obj, "id");
- file.date = Json::requireString(obj, "date_published");
- auto versionArray = Json::requireArray(obj, "game_versions");
- if (versionArray.empty()) { continue; }
- for (auto mcVer : versionArray) {
- file.mcVersion.append(mcVer.toString());
- }
- auto loaders = Json::requireArray(obj, "loaders");
- for (auto loader : loaders) {
- file.loaders.append(loader.toString());
- }
- file.version = Json::requireString(obj, "name");
-
- auto files = Json::requireArray(obj, "files");
- int i = 0;
-
- // Find correct file (needed in cases where one version may have multiple files)
- // Will default to the last one if there's no primary (though I think Modrinth requires that
- // at least one file is primary, idk)
- // NOTE: files.count() is 1-indexed, so we need to subtract 1 to become 0-indexed
- while (i < files.count() - 1){
- auto parent = files[i].toObject();
- auto fileName = Json::requireString(parent, "filename");
-
- // Grab the primary file, if available
- if(Json::requireBoolean(parent, "primary"))
- break;
-
- i++;
- }
-
- auto parent = files[i].toObject();
- if (parent.contains("url")) {
- file.downloadUrl = Json::requireString(parent, "url");
- file.fileName = Json::requireString(parent, "filename");
+ auto file = loadIndexedPackVersion(obj);
+ 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 {
// dates are in RFC 3339 format
@@ -106,3 +110,61 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
pack.versions = unsortedVersions;
pack.versionsLoaded = true;
}
+
+auto Modrinth::loadIndexedPackVersion(QJsonObject &obj) -> ModPlatform::IndexedVersion
+{
+ ModPlatform::IndexedVersion file;
+
+ file.addonId = Json::requireString(obj, "project_id");
+ file.fileId = Json::requireString(obj, "id");
+ file.date = Json::requireString(obj, "date_published");
+ auto versionArray = Json::requireArray(obj, "game_versions");
+ if (versionArray.empty()) {
+ return {};
+ }
+ for (auto mcVer : versionArray) {
+ file.mcVersion.append(mcVer.toString());
+ }
+ auto loaders = Json::requireArray(obj, "loaders");
+ for (auto loader : loaders) {
+ file.loaders.append(loader.toString());
+ }
+ file.version = Json::requireString(obj, "name");
+
+ auto files = Json::requireArray(obj, "files");
+ int i = 0;
+
+ // Find correct file (needed in cases where one version may have multiple files)
+ // Will default to the last one if there's no primary (though I think Modrinth requires that
+ // at least one file is primary, idk)
+ // NOTE: files.count() is 1-indexed, so we need to subtract 1 to become 0-indexed
+ while (i < files.count() - 1) {
+ auto parent = files[i].toObject();
+ auto fileName = Json::requireString(parent, "filename");
+
+ // Grab the primary file, if available
+ if (Json::requireBoolean(parent, "primary"))
+ break;
+
+ i++;
+ }
+
+ auto parent = files[i].toObject();
+ if (parent.contains("url")) {
+ file.downloadUrl = Json::requireString(parent, "url");
+ file.fileName = Json::requireString(parent, "filename");
+ auto hash_list = Json::requireObject(parent, "hashes");
+ auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH);
+ for (auto& hash_type : hash_types) {
+ if (hash_list.contains(hash_type)) {
+ file.hash = Json::requireString(hash_list, hash_type);
+ file.hash_type = hash_type;
+ break;
+ }
+ }
+
+ return file;
+ }
+
+ return {};
+}
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h
index 7f306f25..b7936204 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.h
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h
@@ -25,9 +25,11 @@
namespace Modrinth {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
+void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
BaseInstance* inst);
+auto loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion;
} // namespace Modrinth
diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp
index f1ad39ce..a4620df9 100644
--- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp
@@ -42,6 +42,8 @@
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
+#include <QSet>
+
static ModrinthAPI api;
namespace Modrinth {
@@ -62,8 +64,35 @@ void loadIndexedInfo(Modpack& pack, QJsonObject& obj)
{
pack.extra.body = Json::ensureString(obj, "body");
pack.extra.projectUrl = QString("https://modrinth.com/modpack/%1").arg(Json::ensureString(obj, "slug"));
+
+ pack.extra.issuesUrl = Json::ensureString(obj, "issues_url");
+ if(pack.extra.issuesUrl.endsWith('/'))
+ pack.extra.issuesUrl.chop(1);
+
pack.extra.sourceUrl = Json::ensureString(obj, "source_url");
+ if(pack.extra.sourceUrl.endsWith('/'))
+ pack.extra.sourceUrl.chop(1);
+
pack.extra.wikiUrl = Json::ensureString(obj, "wiki_url");
+ if(pack.extra.wikiUrl.endsWith('/'))
+ pack.extra.wikiUrl.chop(1);
+
+ pack.extra.discordUrl = Json::ensureString(obj, "discord_url");
+ if(pack.extra.discordUrl.endsWith('/'))
+ pack.extra.discordUrl.chop(1);
+
+ auto donate_arr = Json::ensureArray(obj, "donation_urls");
+ for(auto d : donate_arr){
+ auto d_obj = Json::requireObject(d);
+
+ DonationData donate;
+
+ donate.id = Json::ensureString(d_obj, "id");
+ donate.platform = Json::ensureString(d_obj, "platform");
+ donate.url = Json::ensureString(d_obj, "url");
+
+ pack.extra.donate.append(donate);
+ }
pack.extraInfoLoaded = true;
}
@@ -93,23 +122,6 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
pack.versionsLoaded = true;
}
-auto validateDownloadUrl(QUrl url) -> bool
-{
- auto domain = url.host();
- if(domain == "cdn.modrinth.com")
- return true;
- if(domain == "edge.forgecdn.net")
- return true;
- if(domain == "media.forgecdn.net")
- return true;
- if(domain == "github.com")
- return true;
- if(domain == "raw.githubusercontent.com")
- return true;
-
- return false;
-}
-
auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
{
ModpackVersion file;
@@ -139,9 +151,6 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
auto url = Json::requireString(parent, "url");
- if(!validateDownloadUrl(url))
- continue;
-
file.download_url = url;
if(is_primary)
break;
diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h
index e5fc9a70..035dc62e 100644
--- a/launcher/modplatform/modrinth/ModrinthPackManifest.h
+++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h
@@ -40,6 +40,7 @@
#include <QByteArray>
#include <QCryptographicHash>
+#include <QQueue>
#include <QString>
#include <QUrl>
#include <QVector>
@@ -48,22 +49,32 @@ class MinecraftInstance;
namespace Modrinth {
-struct File
-{
+struct File {
QString path;
QCryptographicHash::Algorithm hashAlgorithm;
QByteArray hash;
- // TODO: should this support multiple download URLs, like the JSON does?
- QUrl download;
+ QQueue<QUrl> downloads;
+};
+
+struct DonationData {
+ QString id;
+ QString platform;
+ QString url;
};
struct ModpackExtra {
QString body;
QString projectUrl;
+
+ QString issuesUrl;
QString sourceUrl;
QString wikiUrl;
+ QString discordUrl;
+
+ QList<DonationData> donate;
+
};
struct ModpackVersion {
diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp
new file mode 100644
index 00000000..0782b9f4
--- /dev/null
+++ b/launcher/modplatform/packwiz/Packwiz.cpp
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+*/
+
+#include "Packwiz.h"
+
+#include <QDebug>
+#include <QDir>
+#include <QObject>
+
+#include "toml.h"
+#include "FileSystem.h"
+
+#include "minecraft/mod/Mod.h"
+#include "modplatform/ModIndex.h"
+
+namespace Packwiz {
+
+auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString
+{
+ QFile index_file(index_dir.absoluteFilePath(normalized_fname));
+
+ QString real_fname = normalized_fname;
+ if (!index_file.exists()) {
+ // Tries to get similar entries
+ for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) {
+ if (!QString::compare(normalized_fname, file_name, Qt::CaseInsensitive)) {
+ real_fname = file_name;
+ break;
+ }
+ }
+
+ if(should_find_match && !QString::compare(normalized_fname, real_fname, Qt::CaseSensitive)){
+ qCritical() << "Could not find a match for a valid metadata file!";
+ qCritical() << "File: " << normalized_fname;
+ return {};
+ }
+ }
+
+ return real_fname;
+}
+
+// Helpers
+static inline auto indexFileName(QString const& mod_name) -> QString
+{
+ if(mod_name.endsWith(".pw.toml"))
+ return mod_name;
+ return QString("%1.pw.toml").arg(mod_name);
+}
+
+static ModPlatform::ProviderCapabilities ProviderCaps;
+
+// Helper functions for extracting data from the TOML file
+auto stringEntry(toml_table_t* parent, const char* entry_name) -> QString
+{
+ toml_datum_t var = toml_string_in(parent, entry_name);
+ if (!var.ok) {
+ qCritical() << QString("Failed to read str property '%1' in mod metadata.").arg(entry_name);
+ return {};
+ }
+
+ QString tmp = var.u.s;
+ free(var.u.s);
+
+ return tmp;
+}
+
+auto intEntry(toml_table_t* parent, const char* entry_name) -> int
+{
+ toml_datum_t var = toml_int_in(parent, entry_name);
+ if (!var.ok) {
+ qCritical() << QString("Failed to read int property '%1' in mod metadata.").arg(entry_name);
+ return {};
+ }
+
+ return var.u.i;
+}
+
+
+auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod
+{
+ Mod mod;
+
+ mod.name = mod_pack.name;
+ mod.filename = mod_version.fileName;
+
+ if(mod_pack.provider == ModPlatform::Provider::FLAME){
+ mod.mode = "metadata:curseforge";
+ }
+ else {
+ mod.mode = "url";
+ mod.url = mod_version.downloadUrl;
+ }
+
+ mod.hash_format = mod_version.hash_type;
+ mod.hash = mod_version.hash;
+
+ mod.provider = mod_pack.provider;
+ mod.file_id = mod_version.fileId;
+ mod.project_id = mod_pack.addonId;
+
+ return mod;
+}
+
+auto V1::createModFormat(QDir& index_dir, ::Mod& internal_mod) -> Mod
+{
+ auto mod_name = internal_mod.name();
+
+ // Try getting metadata if it exists
+ Mod mod { getIndexForMod(index_dir, mod_name) };
+ if(mod.isValid())
+ return mod;
+
+ qWarning() << QString("Tried to create mod metadata with a Mod without metadata!");
+
+ return {};
+}
+
+void V1::updateModIndex(QDir& index_dir, Mod& mod)
+{
+ if(!mod.isValid()){
+ qCritical() << QString("Tried to update metadata of an invalid mod!");
+ return;
+ }
+
+ // Ensure the corresponding mod's info exists, and create it if not
+
+ auto normalized_fname = indexFileName(mod.name);
+ auto real_fname = getRealIndexName(index_dir, normalized_fname);
+
+ QFile index_file(index_dir.absoluteFilePath(real_fname));
+
+ // There's already data on there!
+ // TODO: We should do more stuff here, as the user is likely trying to
+ // override a file. In this case, check versions and ask the user what
+ // they want to do!
+ if (index_file.exists()) { index_file.remove(); }
+
+ if (!index_file.open(QIODevice::ReadWrite)) {
+ qCritical() << QString("Could not open file %1!").arg(indexFileName(mod.name));
+ return;
+ }
+
+ // Put TOML data into the file
+ QTextStream in_stream(&index_file);
+ auto addToStream = [&in_stream](QString&& key, QString value) { in_stream << QString("%1 = \"%2\"\n").arg(key, value); };
+
+ {
+ addToStream("name", mod.name);
+ addToStream("filename", mod.filename);
+ addToStream("side", mod.side);
+
+ in_stream << QString("\n[download]\n");
+ addToStream("mode", mod.mode);
+ addToStream("url", mod.url.toString());
+ addToStream("hash-format", mod.hash_format);
+ addToStream("hash", mod.hash);
+
+ in_stream << QString("\n[update]\n");
+ in_stream << QString("[update.%1]\n").arg(ProviderCaps.name(mod.provider));
+ switch(mod.provider){
+ case(ModPlatform::Provider::FLAME):
+ in_stream << QString("file-id = %1\n").arg(mod.file_id.toString());
+ in_stream << QString("project-id = %1\n").arg(mod.project_id.toString());
+ break;
+ case(ModPlatform::Provider::MODRINTH):
+ addToStream("mod-id", mod.mod_id().toString());
+ addToStream("version", mod.version().toString());
+ break;
+ }
+ }
+
+ index_file.close();
+}
+
+void V1::deleteModIndex(QDir& index_dir, QString& mod_name)
+{
+ auto normalized_fname = indexFileName(mod_name);
+ auto real_fname = getRealIndexName(index_dir, normalized_fname);
+ if (real_fname.isEmpty())
+ return;
+
+ QFile index_file(index_dir.absoluteFilePath(real_fname));
+
+ if(!index_file.exists()){
+ qWarning() << QString("Tried to delete non-existent mod metadata for %1!").arg(mod_name);
+ return;
+ }
+
+ if(!index_file.remove()){
+ qWarning() << QString("Failed to remove metadata for mod %1!").arg(mod_name);
+ }
+}
+
+auto V1::getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod
+{
+ Mod mod;
+
+ auto normalized_fname = indexFileName(index_file_name);
+ auto real_fname = getRealIndexName(index_dir, normalized_fname, true);
+ if (real_fname.isEmpty())
+ return {};
+
+ QFile index_file(index_dir.absoluteFilePath(real_fname));
+
+ if (!index_file.open(QIODevice::ReadOnly)) {
+ qWarning() << QString("Failed to open mod metadata for %1").arg(index_file_name);
+ return {};
+ }
+
+ toml_table_t* table = nullptr;
+
+ // NOLINTNEXTLINE(modernize-avoid-c-arrays)
+ char errbuf[200];
+ auto file_bytearray = index_file.readAll();
+ table = toml_parse(file_bytearray.data(), errbuf, sizeof(errbuf));
+
+ index_file.close();
+
+ if (!table) {
+ qWarning() << QString("Could not open file %1!").arg(indexFileName(index_file_name));
+ qWarning() << "Reason: " << QString(errbuf);
+ return {};
+ }
+
+ { // Basic info
+ mod.name = stringEntry(table, "name");
+ mod.filename = stringEntry(table, "filename");
+ mod.side = stringEntry(table, "side");
+ }
+
+ { // [download] info
+ toml_table_t* download_table = toml_table_in(table, "download");
+ if (!download_table) {
+ qCritical() << QString("No [download] section found on mod metadata!");
+ return {};
+ }
+
+ mod.mode = stringEntry(download_table, "mode");
+ mod.url = stringEntry(download_table, "url");
+ mod.hash_format = stringEntry(download_table, "hash-format");
+ mod.hash = stringEntry(download_table, "hash");
+ }
+
+ { // [update] info
+ using Provider = ModPlatform::Provider;
+
+ toml_table_t* update_table = toml_table_in(table, "update");
+ if (!update_table) {
+ qCritical() << QString("No [update] section found on mod metadata!");
+ return {};
+ }
+
+ toml_table_t* mod_provider_table = nullptr;
+ if ((mod_provider_table = toml_table_in(update_table, ProviderCaps.name(Provider::FLAME)))) {
+ 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 = toml_table_in(update_table, ProviderCaps.name(Provider::MODRINTH)))) {
+ mod.provider = Provider::MODRINTH;
+ mod.mod_id() = stringEntry(mod_provider_table, "mod-id");
+ mod.version() = stringEntry(mod_provider_table, "version");
+ } else {
+ qCritical() << QString("No mod provider on mod metadata!");
+ return {};
+ }
+
+ }
+
+ toml_free(table);
+
+ return mod;
+}
+
+} // namespace Packwiz
diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h
new file mode 100644
index 00000000..3c99769c
--- /dev/null
+++ b/launcher/modplatform/packwiz/Packwiz.h
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+* PolyMC - Minecraft Launcher
+* Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include "modplatform/ModIndex.h"
+
+#include <QString>
+#include <QUrl>
+#include <QVariant>
+
+struct toml_table_t;
+class QDir;
+
+// Mod from launcher/minecraft/mod/Mod.h
+class Mod;
+
+namespace Packwiz {
+
+auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString;
+
+auto stringEntry(toml_table_t* parent, const char* entry_name) -> QString;
+auto intEntry(toml_table_t* parent, const char* entry_name) -> int;
+
+class V1 {
+ public:
+ struct Mod {
+ QString name {};
+ QString filename {};
+ // FIXME: make side an enum
+ QString side {"both"};
+
+ // [download]
+ QString mode {};
+ QUrl url {};
+ QString hash_format {};
+ QString hash {};
+
+ // [update]
+ ModPlatform::Provider provider {};
+ QVariant file_id {};
+ QVariant project_id {};
+
+ public:
+ // This is a totally heuristic, but should work for now.
+ auto isValid() const -> bool { return !name.isEmpty() && !project_id.isNull(); }
+
+ // Different providers can use different names for the same thing
+ // Modrinth-specific
+ auto mod_id() -> QVariant& { return project_id; }
+ auto version() -> QVariant& { return file_id; }
+ };
+
+ /* 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;
+ /* Generates the object representing the information in a mod.pw.toml file via
+ * its common representation in the launcher.
+ * */
+ static auto createModFormat(QDir& index_dir, ::Mod& internal_mod) -> 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);
+
+ /* Deletes the metadata for the mod with the given name. If the metadata doesn't exist, it does nothing. */
+ static void deleteModIndex(QDir& index_dir, QString& mod_name);
+
+ /* Gets the metadata for a mod with a particular name.
+ * If the mod doesn't have a metadata, it simply returns an empty Mod object.
+ * */
+ static auto getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod;
+};
+
+} // namespace Packwiz
diff --git a/launcher/modplatform/packwiz/Packwiz_test.cpp b/launcher/modplatform/packwiz/Packwiz_test.cpp
new file mode 100644
index 00000000..d6251148
--- /dev/null
+++ b/launcher/modplatform/packwiz/Packwiz_test.cpp
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <QTemporaryDir>
+#include <QTest>
+
+#include "Packwiz.h"
+
+class PackwizTest : public QObject {
+ Q_OBJECT
+
+ private slots:
+ // Files taken from https://github.com/packwiz/packwiz-example-pack
+ void loadFromFile_Modrinth()
+ {
+ QString source = QFINDTESTDATA("testdata");
+
+ QDir index_dir(source);
+ QString name_mod("borderless-mining.pw.toml");
+ QVERIFY(index_dir.entryList().contains(name_mod));
+
+ auto metadata = Packwiz::V1::getIndexForMod(index_dir, name_mod);
+
+ QVERIFY(metadata.isValid());
+
+ QCOMPARE(metadata.name, "Borderless Mining");
+ QCOMPARE(metadata.filename, "borderless-mining-1.1.1+1.18.jar");
+ QCOMPARE(metadata.side, "client");
+
+ QCOMPARE(metadata.url, QUrl("https://cdn.modrinth.com/data/kYq5qkSL/versions/1.1.1+1.18/borderless-mining-1.1.1+1.18.jar"));
+ QCOMPARE(metadata.hash_format, "sha512");
+ QCOMPARE(metadata.hash, "c8fe6e15ddea32668822dddb26e1851e5f03834be4bcb2eff9c0da7fdc086a9b6cead78e31a44d3bc66335cba11144ee0337c6d5346f1ba63623064499b3188d");
+
+ QCOMPARE(metadata.provider, ModPlatform::Provider::MODRINTH);
+ QCOMPARE(metadata.version(), "ug2qKTPR");
+ QCOMPARE(metadata.mod_id(), "kYq5qkSL");
+ }
+
+ void loadFromFile_Curseforge()
+ {
+ QString source = QFINDTESTDATA("testdata");
+
+ QDir index_dir(source);
+ QString name_mod("screenshot-to-clipboard-fabric.pw.toml");
+ QVERIFY(index_dir.entryList().contains(name_mod));
+
+ // Try without the .pw.toml at the end
+ name_mod.chop(8);
+
+ auto metadata = Packwiz::V1::getIndexForMod(index_dir, name_mod);
+
+ QVERIFY(metadata.isValid());
+
+ QCOMPARE(metadata.name, "Screenshot to Clipboard (Fabric)");
+ QCOMPARE(metadata.filename, "screenshot-to-clipboard-1.0.7-fabric.jar");
+ QCOMPARE(metadata.side, "both");
+
+ QCOMPARE(metadata.url, QUrl("https://edge.forgecdn.net/files/3509/43/screenshot-to-clipboard-1.0.7-fabric.jar"));
+ QCOMPARE(metadata.hash_format, "murmur2");
+ QCOMPARE(metadata.hash, "1781245820");
+
+ QCOMPARE(metadata.provider, ModPlatform::Provider::FLAME);
+ QCOMPARE(metadata.file_id, 3509043);
+ QCOMPARE(metadata.project_id, 327154);
+ }
+};
+
+QTEST_GUILESS_MAIN(PackwizTest)
+
+#include "Packwiz_test.moc"
diff --git a/launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml b/launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml
new file mode 100644
index 00000000..16545fd4
--- /dev/null
+++ b/launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml
@@ -0,0 +1,13 @@
+name = "Borderless Mining"
+filename = "borderless-mining-1.1.1+1.18.jar"
+side = "client"
+
+[download]
+url = "https://cdn.modrinth.com/data/kYq5qkSL/versions/1.1.1+1.18/borderless-mining-1.1.1+1.18.jar"
+hash-format = "sha512"
+hash = "c8fe6e15ddea32668822dddb26e1851e5f03834be4bcb2eff9c0da7fdc086a9b6cead78e31a44d3bc66335cba11144ee0337c6d5346f1ba63623064499b3188d"
+
+[update]
+[update.modrinth]
+mod-id = "kYq5qkSL"
+version = "ug2qKTPR"
diff --git a/launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml b/launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml
new file mode 100644
index 00000000..87d70ada
--- /dev/null
+++ b/launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml
@@ -0,0 +1,13 @@
+name = "Screenshot to Clipboard (Fabric)"
+filename = "screenshot-to-clipboard-1.0.7-fabric.jar"
+side = "both"
+
+[download]
+url = "https://edge.forgecdn.net/files/3509/43/screenshot-to-clipboard-1.0.7-fabric.jar"
+hash-format = "murmur2"
+hash = "1781245820"
+
+[update]
+[update.curseforge]
+file-id = 3509043
+project-id = 327154
diff --git a/launcher/modplatform/technic/SolderPackManifest.cpp b/launcher/modplatform/technic/SolderPackManifest.cpp
index 16fe0b0e..e52a7ec0 100644
--- a/launcher/modplatform/technic/SolderPackManifest.cpp
+++ b/launcher/modplatform/technic/SolderPackManifest.cpp
@@ -37,7 +37,7 @@ void loadPack(Pack& v, QJsonObject& obj)
static void loadPackBuildMod(PackBuildMod& b, QJsonObject& obj)
{
b.name = Json::requireString(obj, "name");
- b.version = Json::requireString(obj, "version");
+ b.version = Json::ensureString(obj, "version", "");
b.md5 = Json::requireString(obj, "md5");
b.url = Json::requireString(obj, "url");
}
diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp
index 782fb9b2..95feb4b2 100644
--- a/launcher/modplatform/technic/TechnicPackProcessor.cpp
+++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp
@@ -185,13 +185,22 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1));
}
}
- else if (libraryName.startsWith("net.minecraftforge:minecraftforge:"))
+ else
{
- components->setComponentVersion("net.minecraftforge", libraryName.section(':', 2));
- }
- else if (libraryName.startsWith("net.fabricmc:fabric-loader:"))
- {
- components->setComponentVersion("net.fabricmc.fabric-loader", libraryName.section(':', 2));
+ // <Technic library name prefix> -> <our component name>
+ static QMap<QString, QString> loaderMap {
+ {"net.minecraftforge:minecraftforge:", "net.minecraftforge"},
+ {"net.fabricmc:fabric-loader:", "net.fabricmc.fabric-loader"},
+ {"org.quiltmc:quilt-loader:", "org.quiltmc.quilt-loader"}
+ };
+ for (const auto& loader : loaderMap.keys())
+ {
+ if (libraryName.startsWith(loader))
+ {
+ components->setComponentVersion(loaderMap.value(loader), libraryName.section(':', 2));
+ break;
+ }
+ }
}
}
}
diff --git a/launcher/mojang/PackageManifest_test.cpp b/launcher/mojang/PackageManifest_test.cpp
index d4c55c5a..e8da4266 100644
--- a/launcher/mojang/PackageManifest_test.cpp
+++ b/launcher/mojang/PackageManifest_test.cpp
@@ -1,6 +1,5 @@
#include <QTest>
#include <QDebug>
-#include "TestUtil.h"
#include "mojang/PackageManifest.h"
diff --git a/launcher/net/ByteArraySink.h b/launcher/net/ByteArraySink.h
index 20e6764c..501318a1 100644
--- a/launcher/net/ByteArraySink.h
+++ b/launcher/net/ByteArraySink.h
@@ -1,62 +1,89 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "Sink.h"
namespace Net {
+
/*
* Sink object for downloads that uses an external QByteArray it doesn't own as a target.
+ * FIXME: It is possible that the QByteArray is freed while we're doing some operation on it,
+ * causing a segmentation fault.
*/
-class ByteArraySink : public Sink
-{
-public:
- ByteArraySink(QByteArray *output)
- :m_output(output)
- {
- // nil
- };
+class ByteArraySink : public Sink {
+ public:
+ ByteArraySink(QByteArray* output) : m_output(output){};
- virtual ~ByteArraySink()
- {
- // nil
- }
+ virtual ~ByteArraySink() = default;
-public:
- JobStatus init(QNetworkRequest & request) override
+ public:
+ auto init(QNetworkRequest& request) -> Task::State override
{
m_output->clear();
- if(initAllValidators(request))
- return Job_InProgress;
- return Job_Failed;
+ if (initAllValidators(request))
+ return Task::State::Running;
+ return Task::State::Failed;
};
- JobStatus write(QByteArray & data) override
+ auto write(QByteArray& data) -> Task::State override
{
m_output->append(data);
- if(writeAllValidators(data))
- return Job_InProgress;
- return Job_Failed;
+ if (writeAllValidators(data))
+ return Task::State::Running;
+ return Task::State::Failed;
}
- JobStatus abort() override
+ auto abort() -> Task::State override
{
m_output->clear();
failAllValidators();
- return Job_Failed;
+ return Task::State::Failed;
}
- JobStatus finalize(QNetworkReply &reply) override
+ auto finalize(QNetworkReply& reply) -> Task::State override
{
- if(finalizeAllValidators(reply))
- return Job_Finished;
- return Job_Failed;
+ if (finalizeAllValidators(reply))
+ return Task::State::Succeeded;
+ return Task::State::Failed;
}
- bool hasLocalData() override
- {
- return false;
- }
+ auto hasLocalData() -> bool override { return false; }
-private:
- QByteArray * m_output;
+ private:
+ QByteArray* m_output;
};
-}
+} // namespace Net
diff --git a/launcher/net/ChecksumValidator.h b/launcher/net/ChecksumValidator.h
index 0d6b19c2..a2ca2c7a 100644
--- a/launcher/net/ChecksumValidator.h
+++ b/launcher/net/ChecksumValidator.h
@@ -1,55 +1,82 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "Validator.h"
+
#include <QCryptographicHash>
-#include <memory>
#include <QFile>
namespace Net {
-class ChecksumValidator: public Validator
-{
-public: /* con/des */
+class ChecksumValidator : public Validator {
+ public:
ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray())
- :m_checksum(algorithm), m_expected(expected)
- {
- };
- virtual ~ChecksumValidator() {};
+ : m_checksum(algorithm), m_expected(expected){};
+ virtual ~ChecksumValidator() = default;
-public: /* methods */
- bool init(QNetworkRequest &) override
+ public:
+ auto init(QNetworkRequest&) -> bool override
{
m_checksum.reset();
return true;
}
- bool write(QByteArray & data) override
+
+ auto write(QByteArray& data) -> bool override
{
m_checksum.addData(data);
return true;
}
- bool abort() override
- {
- return true;
- }
- bool validate(QNetworkReply &) override
+
+ auto abort() -> bool override { return true; }
+
+ auto validate(QNetworkReply&) -> bool override
{
- if(m_expected.size() && m_expected != hash())
- {
+ if (m_expected.size() && m_expected != hash()) {
qWarning() << "Checksum mismatch, download is bad.";
return false;
}
return true;
}
- QByteArray hash()
- {
- return m_checksum.result();
- }
- void setExpected(QByteArray expected)
- {
- m_expected = expected;
- }
-private: /* data */
+ auto hash() -> QByteArray { return m_checksum.result(); }
+
+ void setExpected(QByteArray expected) { m_expected = expected; }
+
+ private:
QCryptographicHash m_checksum;
QByteArray m_expected;
};
-} \ No newline at end of file
+} // namespace Net
diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp
index 65cc8f67..3061e32e 100644
--- a/launcher/net/Download.cpp
+++ b/launcher/net/Download.cpp
@@ -1,22 +1,42 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "Download.h"
#include <QDateTime>
-#include <QDebug>
#include <QFileInfo>
#include "ByteArraySink.h"
@@ -25,38 +45,38 @@
#include "MetaCacheSink.h"
#include "BuildConfig.h"
+#include "Application.h"
namespace Net {
Download::Download() : NetAction()
{
- m_status = Job_NotStarted;
+ m_state = State::Inactive;
}
-Download::Ptr Download::makeCached(QUrl url, MetaEntryPtr entry, Options options)
+auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
{
- Download* dl = new Download();
+ auto* dl = new Download();
dl->m_url = url;
dl->m_options = options;
auto md5Node = new ChecksumValidator(QCryptographicHash::Md5);
auto cachedNode = new MetaCacheSink(entry, md5Node);
dl->m_sink.reset(cachedNode);
- dl->m_target_path = entry->getFullPath();
return dl;
}
-Download::Ptr Download::makeByteArray(QUrl url, QByteArray* output, Options options)
+auto Download::makeByteArray(QUrl url, QByteArray* output, Options options) -> Download::Ptr
{
- Download* dl = new Download();
+ auto* dl = new Download();
dl->m_url = url;
dl->m_options = options;
dl->m_sink.reset(new ByteArraySink(output));
return dl;
}
-Download::Ptr Download::makeFile(QUrl url, QString path, Options options)
+auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Ptr
{
- Download* dl = new Download();
+ auto* dl = new Download();
dl->m_url = url;
dl->m_options = options;
dl->m_sink.reset(new FileSink(path));
@@ -68,69 +88,74 @@ void Download::addValidator(Validator* v)
m_sink->addValidator(v);
}
-void Download::startImpl()
+void Download::executeTask()
{
- if (m_status == Job_Aborted) {
+ setStatus(tr("Downloading %1").arg(m_url.toString()));
+
+ if (getState() == Task::State::AbortedByUser) {
qWarning() << "Attempt to start an aborted Download:" << m_url.toString();
- emit aborted(m_index_within_job);
+ emitAborted();
return;
}
+
QNetworkRequest request(m_url);
- m_status = m_sink->init(request);
- switch (m_status) {
- case Job_Finished:
- emit succeeded(m_index_within_job);
+ m_state = m_sink->init(request);
+ switch (m_state) {
+ case State::Succeeded:
+ emit succeeded();
qDebug() << "Download cache hit " << m_url.toString();
return;
- case Job_InProgress:
+ case State::Running:
qDebug() << "Downloading " << m_url.toString();
break;
- case Job_Failed_Proceed: // this is meaningless in this context. We do need a sink.
- case Job_NotStarted:
- case Job_Failed:
- emit failed(m_index_within_job);
+ case State::Inactive:
+ case State::Failed:
+ emitFailed();
return;
- case Job_Aborted:
+ case State::AbortedByUser:
+ emitAborted();
return;
}
- request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT);
+ request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());
if (request.url().host().contains("api.curseforge.com")) {
- request.setRawHeader("x-api-key", BuildConfig.CURSEFORGE_API_KEY.toUtf8());
+ request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8());
};
QNetworkReply* rep = m_network->get(request);
m_reply.reset(rep);
- connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)));
- connect(rep, SIGNAL(finished()), SLOT(downloadFinished()));
+ connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress);
+ connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
+#else
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
+#endif
connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors);
connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead);
}
void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
- m_total_progress = bytesTotal;
- m_progress = bytesReceived;
- emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal);
+ setProgress(bytesReceived, bytesTotal);
}
void Download::downloadError(QNetworkReply::NetworkError error)
{
if (error == QNetworkReply::OperationCanceledError) {
qCritical() << "Aborted " << m_url.toString();
- m_status = Job_Aborted;
+ m_state = State::AbortedByUser;
} else {
if (m_options & Option::AcceptLocalFiles) {
if (m_sink->hasLocalData()) {
- m_status = Job_Failed_Proceed;
+ m_state = State::Succeeded;
return;
}
}
// error happened during download.
qCritical() << "Failed " << m_url.toString() << " with reason " << error;
- m_status = Job_Failed;
+ m_state = State::Failed;
}
}
@@ -145,7 +170,7 @@ void Download::sslErrors(const QList<QSslError>& errors)
}
}
-bool Download::handleRedirect()
+auto Download::handleRedirect() -> bool
{
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
if (!redirect.isValid()) {
@@ -194,7 +219,8 @@ bool Download::handleRedirect()
m_url = QUrl(redirect.toString());
qDebug() << "Following redirect to " << m_url.toString();
- start(m_network);
+ startAction(m_network);
+
return true;
}
@@ -207,74 +233,71 @@ void Download::downloadFinished()
}
// if the download failed before this point ...
- if (m_status == Job_Failed_Proceed) {
+ if (m_state == State::Succeeded) // pretend to succeed so we continue processing :)
+ {
qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString();
m_sink->abort();
m_reply.reset();
- emit succeeded(m_index_within_job);
+ emit succeeded();
return;
- } else if (m_status == Job_Failed) {
+ } else if (m_state == State::Failed) {
qDebug() << "Download failed in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
- emit failed(m_index_within_job);
+ emit failed("");
return;
- } else if (m_status == Job_Aborted) {
+ } else if (m_state == State::AbortedByUser) {
qDebug() << "Download aborted in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
- emit aborted(m_index_within_job);
+ emit aborted();
return;
}
// make sure we got all the remaining data, if any
auto data = m_reply->readAll();
if (data.size()) {
- qDebug() << "Writing extra" << data.size() << "bytes to" << m_target_path;
- m_status = m_sink->write(data);
+ qDebug() << "Writing extra" << data.size() << "bytes";
+ m_state = m_sink->write(data);
}
// otherwise, finalize the whole graph
- m_status = m_sink->finalize(*m_reply.get());
- if (m_status != Job_Finished) {
+ m_state = m_sink->finalize(*m_reply.get());
+ if (m_state != State::Succeeded) {
qDebug() << "Download failed to finalize:" << m_url.toString();
m_sink->abort();
m_reply.reset();
- emit failed(m_index_within_job);
+ emit failed("");
return;
}
+
m_reply.reset();
qDebug() << "Download succeeded:" << m_url.toString();
- emit succeeded(m_index_within_job);
+ emit succeeded();
}
void Download::downloadReadyRead()
{
- if (m_status == Job_InProgress) {
+ if (m_state == State::Running) {
auto data = m_reply->readAll();
- m_status = m_sink->write(data);
- if (m_status == Job_Failed) {
- qCritical() << "Failed to process response chunk for " << m_target_path;
+ m_state = m_sink->write(data);
+ if (m_state == State::Failed) {
+ qCritical() << "Failed to process response chunk";
}
// qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes";
} else {
- qCritical() << "Cannot write to " << m_target_path << ", illegal status" << m_status;
+ qCritical() << "Cannot write download data! illegal status " << m_status;
}
}
} // namespace Net
-bool Net::Download::abort()
+auto Net::Download::abort() -> bool
{
if (m_reply) {
m_reply->abort();
} else {
- m_status = Job_Aborted;
+ m_state = State::AbortedByUser;
}
return true;
}
-
-bool Net::Download::canAbort()
-{
- return true;
-}
diff --git a/launcher/net/Download.h b/launcher/net/Download.h
index 0f9bfe7f..20932944 100644
--- a/launcher/net/Download.h
+++ b/launcher/net/Download.h
@@ -1,77 +1,88 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "NetAction.h"
#include "HttpMetaCache.h"
-#include "Validator.h"
+#include "NetAction.h"
#include "Sink.h"
+#include "Validator.h"
#include "QObjectPtr.h"
namespace Net {
-class Download : public NetAction
-{
+class Download : public NetAction {
Q_OBJECT
-public: /* types */
- typedef shared_qobject_ptr<class Download> Ptr;
- enum class Option
- {
- NoOptions = 0,
- AcceptLocalFiles = 1
- };
+ public:
+ using Ptr = shared_qobject_ptr<class Download>;
+ enum class Option { NoOptions = 0, AcceptLocalFiles = 1 };
Q_DECLARE_FLAGS(Options, Option)
-protected: /* con/des */
+ protected:
explicit Download();
-public:
- virtual ~Download(){};
- static Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions);
- static Download::Ptr makeByteArray(QUrl url, QByteArray *output, Options options = Option::NoOptions);
- static Download::Ptr makeFile(QUrl url, QString path, Options options = Option::NoOptions);
-public: /* methods */
- QString getTargetFilepath()
- {
- return m_target_path;
- }
- void addValidator(Validator * v);
- bool abort() override;
- bool canAbort() override;
+ public:
+ ~Download() override = default;
+
+ static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
+ static auto makeByteArray(QUrl url, QByteArray* output, Options options = Option::NoOptions) -> Download::Ptr;
+ static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
+
+ public:
+ void addValidator(Validator* v);
+ auto abort() -> bool override;
+ auto canAbort() const -> bool override { return true; };
-private: /* methods */
- bool handleRedirect();
+ private:
+ auto handleRedirect() -> bool;
-protected slots:
+ protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
- void sslErrors(const QList<QSslError> & errors);
+ void sslErrors(const QList<QSslError>& errors);
void downloadFinished() override;
void downloadReadyRead() override;
-public slots:
- void startImpl() override;
+ public slots:
+ void executeTask() override;
-private: /* data */
- // FIXME: remove this, it has no business being here.
- QString m_target_path;
+ private:
std::unique_ptr<Sink> m_sink;
Options m_options;
};
-}
+} // namespace Net
Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options)
diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp
index 7e9b8929..ba0caf6c 100644
--- a/launcher/net/FileSink.cpp
+++ b/launcher/net/FileSink.cpp
@@ -1,109 +1,131 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "FileSink.h"
-#include <QFile>
-#include <QFileInfo>
+
#include "FileSystem.h"
namespace Net {
-FileSink::FileSink(QString filename)
- :m_filename(filename)
-{
- // nil
-}
-
-FileSink::~FileSink()
-{
- // nil
-}
-
-JobStatus FileSink::init(QNetworkRequest& request)
+Task::State FileSink::init(QNetworkRequest& request)
{
auto result = initCache(request);
- if(result != Job_InProgress)
- {
+ if (result != Task::State::Running) {
return result;
}
+
// create a new save file and open it for writing
- if (!FS::ensureFilePathExists(m_filename))
- {
+ if (!FS::ensureFilePathExists(m_filename)) {
qCritical() << "Could not create folder for " + m_filename;
- return Job_Failed;
+ return Task::State::Failed;
}
+
wroteAnyData = false;
m_output_file.reset(new QSaveFile(m_filename));
- if (!m_output_file->open(QIODevice::WriteOnly))
- {
+ if (!m_output_file->open(QIODevice::WriteOnly)) {
qCritical() << "Could not open " + m_filename + " for writing";
- return Job_Failed;
+ return Task::State::Failed;
}
- if(initAllValidators(request))
- return Job_InProgress;
- return Job_Failed;
+ if (initAllValidators(request))
+ return Task::State::Running;
+ return Task::State::Failed;
}
-JobStatus FileSink::initCache(QNetworkRequest &)
+Task::State FileSink::write(QByteArray& data)
{
- return Job_InProgress;
-}
-
-JobStatus FileSink::write(QByteArray& data)
-{
- if (!writeAllValidators(data) || m_output_file->write(data) != data.size())
- {
+ if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) {
qCritical() << "Failed writing into " + m_filename;
m_output_file->cancelWriting();
m_output_file.reset();
wroteAnyData = false;
- return Job_Failed;
+ return Task::State::Failed;
}
+
wroteAnyData = true;
- return Job_InProgress;
+ return Task::State::Running;
}
-JobStatus FileSink::abort()
+Task::State FileSink::abort()
{
m_output_file->cancelWriting();
failAllValidators();
- return Job_Failed;
+ return Task::State::Failed;
}
-JobStatus FileSink::finalize(QNetworkReply& reply)
+Task::State FileSink::finalize(QNetworkReply& reply)
{
bool gotFile = false;
QVariant statusCodeV = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute);
bool validStatus = false;
int statusCode = statusCodeV.toInt(&validStatus);
- if(validStatus)
- {
+ if (validStatus) {
// this leaves out 304 Not Modified
gotFile = statusCode == 200 || statusCode == 203;
}
+
// if we wrote any data to the save file, we try to commit the data to the real file.
// if it actually got a proper file, we write it even if it was empty
- if (gotFile || wroteAnyData)
- {
+ if (gotFile || wroteAnyData) {
// ask validators for data consistency
// we only do this for actual downloads, not 'your data is still the same' cache hits
- if(!finalizeAllValidators(reply))
- return Job_Failed;
+ if (!finalizeAllValidators(reply))
+ return Task::State::Failed;
+
// nothing went wrong...
- if (!m_output_file->commit())
- {
+ if (!m_output_file->commit()) {
qCritical() << "Failed to commit changes to " << m_filename;
m_output_file->cancelWriting();
- return Job_Failed;
+ return Task::State::Failed;
}
}
+
// then get rid of the save file
m_output_file.reset();
return finalizeCache(reply);
}
-JobStatus FileSink::finalizeCache(QNetworkReply &)
+Task::State FileSink::initCache(QNetworkRequest&)
{
- return Job_Finished;
+ return Task::State::Running;
+}
+
+Task::State FileSink::finalizeCache(QNetworkReply&)
+{
+ return Task::State::Succeeded;
}
bool FileSink::hasLocalData()
@@ -111,4 +133,4 @@ bool FileSink::hasLocalData()
QFileInfo info(m_filename);
return info.exists() && info.size() != 0;
}
-}
+} // namespace Net
diff --git a/launcher/net/FileSink.h b/launcher/net/FileSink.h
index 875fe511..dffbdca6 100644
--- a/launcher/net/FileSink.h
+++ b/launcher/net/FileSink.h
@@ -1,28 +1,65 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "Sink.h"
+
#include <QSaveFile>
+#include "Sink.h"
+
namespace Net {
-class FileSink : public Sink
-{
-public: /* con/des */
- FileSink(QString filename);
- virtual ~FileSink();
-
-public: /* methods */
- JobStatus init(QNetworkRequest & request) override;
- JobStatus write(QByteArray & data) override;
- JobStatus abort() override;
- JobStatus finalize(QNetworkReply & reply) override;
- bool hasLocalData() override;
-
-protected: /* methods */
- virtual JobStatus initCache(QNetworkRequest &);
- virtual JobStatus finalizeCache(QNetworkReply &reply);
-
-protected: /* data */
+class FileSink : public Sink {
+ public:
+ FileSink(QString filename) : m_filename(filename){};
+ virtual ~FileSink() = default;
+
+ public:
+ auto init(QNetworkRequest& request) -> Task::State override;
+ auto write(QByteArray& data) -> Task::State override;
+ auto abort() -> Task::State override;
+ auto finalize(QNetworkReply& reply) -> Task::State override;
+
+ auto hasLocalData() -> bool override;
+
+ protected:
+ virtual auto initCache(QNetworkRequest&) -> Task::State;
+ virtual auto finalizeCache(QNetworkReply& reply) -> Task::State;
+
+ protected:
QString m_filename;
bool wroteAnyData = false;
std::unique_ptr<QSaveFile> m_output_file;
};
-}
+} // namespace Net
diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp
index 8734e0bf..4d86c0b8 100644
--- a/launcher/net/HttpMetaCache.cpp
+++ b/launcher/net/HttpMetaCache.cpp
@@ -1,43 +1,60 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "HttpMetaCache.h"
#include "FileSystem.h"
+#include "Json.h"
-#include <QFileInfo>
-#include <QFile>
-#include <QDateTime>
#include <QCryptographicHash>
+#include <QDateTime>
+#include <QFile>
+#include <QFileInfo>
#include <QDebug>
-#include <QJsonDocument>
-#include <QJsonArray>
-#include <QJsonObject>
-
-QString MetaEntry::getFullPath()
+auto MetaEntry::getFullPath() -> QString
{
// FIXME: make local?
return FS::PathCombine(basePath, relativePath);
}
-HttpMetaCache::HttpMetaCache(QString path) : QObject()
+HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path)
{
- m_index_file = path;
saveBatchingTimer.setSingleShot(true);
saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer);
+
connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow()));
}
@@ -47,45 +64,42 @@ HttpMetaCache::~HttpMetaCache()
SaveNow();
}
-MetaEntryPtr HttpMetaCache::getEntry(QString base, QString resource_path)
+auto HttpMetaCache::getEntry(QString base, QString resource_path) -> MetaEntryPtr
{
// no base. no base path. can't store
- if (!m_entries.contains(base))
- {
+ if (!m_entries.contains(base)) {
// TODO: log problem
- return MetaEntryPtr();
+ return {};
}
- EntryMap &map = m_entries[base];
- if (map.entry_list.contains(resource_path))
- {
+
+ EntryMap& map = m_entries[base];
+ if (map.entry_list.contains(resource_path)) {
return map.entry_list[resource_path];
}
- return MetaEntryPtr();
+
+ return {};
}
-MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag)
+auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag) -> MetaEntryPtr
{
auto entry = getEntry(base, resource_path);
// it's not present? generate a default stale entry
- if (!entry)
- {
+ if (!entry) {
return staleEntry(base, resource_path);
}
- auto &selected_base = m_entries[base];
+ auto& selected_base = m_entries[base];
QString real_path = FS::PathCombine(selected_base.base_path, resource_path);
QFileInfo finfo(real_path);
// is the file really there? if not -> stale
- if (!finfo.isFile() || !finfo.isReadable())
- {
+ if (!finfo.isFile() || !finfo.isReadable()) {
// if the file doesn't exist, we disown the entry
selected_base.entry_list.remove(resource_path);
return staleEntry(base, resource_path);
}
- if (!expected_etag.isEmpty() && expected_etag != entry->etag)
- {
+ if (!expected_etag.isEmpty() && expected_etag != entry->etag) {
// if the etag doesn't match expected, we disown the entry
selected_base.entry_list.remove(resource_path);
return staleEntry(base, resource_path);
@@ -93,18 +107,15 @@ MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QS
// if the file changed, check md5sum
qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch();
- if (file_last_changed != entry->local_changed_timestamp)
- {
+ if (file_last_changed != entry->local_changed_timestamp) {
QFile input(real_path);
input.open(QIODevice::ReadOnly);
- QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5)
- .toHex()
- .constData();
- if (entry->md5sum != md5sum)
- {
+ QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData();
+ if (entry->md5sum != md5sum) {
selected_base.entry_list.remove(resource_path);
return staleEntry(base, resource_path);
}
+
// md5sums matched... keep entry and save the new state to file
entry->local_changed_timestamp = file_last_changed;
SaveEventually();
@@ -115,42 +126,42 @@ MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QS
return entry;
}
-bool HttpMetaCache::updateEntry(MetaEntryPtr stale_entry)
+auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool
{
- if (!m_entries.contains(stale_entry->baseId))
- {
- qCritical() << "Cannot add entry with unknown base: "
- << stale_entry->baseId.toLocal8Bit();
+ if (!m_entries.contains(stale_entry->baseId)) {
+ qCritical() << "Cannot add entry with unknown base: " << stale_entry->baseId.toLocal8Bit();
return false;
}
- if (stale_entry->stale)
- {
+
+ if (stale_entry->stale) {
qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit();
return false;
}
+
m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry;
SaveEventually();
+
return true;
}
-bool HttpMetaCache::evictEntry(MetaEntryPtr entry)
+auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool
{
- if(entry)
- {
- entry->stale = true;
- SaveEventually();
- return true;
- }
- return false;
+ if (!entry)
+ return false;
+
+ entry->stale = true;
+ SaveEventually();
+ return true;
}
-MetaEntryPtr HttpMetaCache::staleEntry(QString base, QString resource_path)
+auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr
{
auto foo = new MetaEntry();
foo->baseId = base;
foo->basePath = getBasePath(base);
foo->relativePath = resource_path;
foo->stale = true;
+
return MetaEntryPtr(foo);
}
@@ -159,24 +170,25 @@ void HttpMetaCache::addBase(QString base, QString base_root)
// TODO: report error
if (m_entries.contains(base))
return;
+
// TODO: check if the base path is valid
EntryMap foo;
foo.base_path = base_root;
m_entries[base] = foo;
}
-QString HttpMetaCache::getBasePath(QString base)
+auto HttpMetaCache::getBasePath(QString base) -> QString
{
- if (m_entries.contains(base))
- {
+ if (m_entries.contains(base)) {
return m_entries[base].base_path;
}
- return QString();
+
+ return {};
}
void HttpMetaCache::Load()
{
- if(m_index_file.isNull())
+ if (m_index_file.isNull())
return;
QFile index(m_index_file);
@@ -184,41 +196,35 @@ void HttpMetaCache::Load()
return;
QJsonDocument json = QJsonDocument::fromJson(index.readAll());
- if (!json.isObject())
- return;
- auto root = json.object();
+
+ auto root = Json::requireObject(json, "HttpMetaCache root");
+
// check file version first
- auto version_val = root.value("version");
- if (!version_val.isString())
- return;
- if (version_val.toString() != "1")
+ auto version_val = Json::ensureString(root, "version");
+ if (version_val != "1")
return;
// read the entry array
- auto entries_val = root.value("entries");
- if (!entries_val.isArray())
- return;
- QJsonArray array = entries_val.toArray();
- for (auto element : array)
- {
- if (!element.isObject())
- return;
- auto element_obj = element.toObject();
- QString base = element_obj.value("base").toString();
+ auto array = Json::ensureArray(root, "entries");
+ for (auto element : array) {
+ auto element_obj = Json::ensureObject(element);
+ auto base = Json::ensureString(element_obj, "base");
if (!m_entries.contains(base))
continue;
- auto &entrymap = m_entries[base];
+
+ auto& entrymap = m_entries[base];
+
auto foo = new MetaEntry();
foo->baseId = base;
- QString path = foo->relativePath = element_obj.value("path").toString();
- foo->md5sum = element_obj.value("md5sum").toString();
- foo->etag = element_obj.value("etag").toString();
- foo->local_changed_timestamp = element_obj.value("last_changed_timestamp").toDouble();
- foo->remote_changed_timestamp =
- element_obj.value("remote_changed_timestamp").toString();
+ foo->relativePath = Json::ensureString(element_obj, "path");
+ foo->md5sum = Json::ensureString(element_obj, "md5sum");
+ foo->etag = Json::ensureString(element_obj, "etag");
+ foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
+ foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
// presumed innocent until closer examination
foo->stale = false;
- entrymap.entry_list[path] = MetaEntryPtr(foo);
+
+ entrymap.entry_list[foo->relativePath] = MetaEntryPtr(foo);
}
}
@@ -231,42 +237,36 @@ void HttpMetaCache::SaveEventually()
void HttpMetaCache::SaveNow()
{
- if(m_index_file.isNull())
+ if (m_index_file.isNull())
return;
+
QJsonObject toplevel;
- toplevel.insert("version", QJsonValue(QString("1")));
+ Json::writeString(toplevel, "version", "1");
+
QJsonArray entriesArr;
- for (auto group : m_entries)
- {
- for (auto entry : group.entry_list)
- {
+ for (auto group : m_entries) {
+ for (auto entry : group.entry_list) {
// do not save stale entries. they are dead.
- if(entry->stale)
- {
+ if (entry->stale) {
continue;
}
+
QJsonObject entryObj;
- entryObj.insert("base", QJsonValue(entry->baseId));
- entryObj.insert("path", QJsonValue(entry->relativePath));
- entryObj.insert("md5sum", QJsonValue(entry->md5sum));
- entryObj.insert("etag", QJsonValue(entry->etag));
- entryObj.insert("last_changed_timestamp",
- QJsonValue(double(entry->local_changed_timestamp)));
+ Json::writeString(entryObj, "base", entry->baseId);
+ Json::writeString(entryObj, "path", entry->relativePath);
+ Json::writeString(entryObj, "md5sum", entry->md5sum);
+ Json::writeString(entryObj, "etag", entry->etag);
+ entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp)));
if (!entry->remote_changed_timestamp.isEmpty())
- entryObj.insert("remote_changed_timestamp",
- QJsonValue(entry->remote_changed_timestamp));
+ entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp));
entriesArr.append(entryObj);
}
}
toplevel.insert("entries", entriesArr);
- QJsonDocument doc(toplevel);
- try
- {
- FS::write(m_index_file, doc.toJson());
- }
- catch (const Exception &e)
- {
+ try {
+ Json::write(toplevel, m_index_file);
+ } catch (const Exception& e) {
qWarning() << e.what();
}
}
diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h
index 1c10e8c7..e944b3d5 100644
--- a/launcher/net/HttpMetaCache.h
+++ b/launcher/net/HttpMetaCache.h
@@ -1,122 +1,122 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QString>
+
#include <QMap>
-#include <qtimer.h>
+#include <QString>
+#include <QTimer>
#include <memory>
class HttpMetaCache;
-class MetaEntry
-{
-friend class HttpMetaCache;
-protected:
- MetaEntry() {}
-public:
- bool isStale()
- {
- return stale;
- }
- void setStale(bool stale)
- {
- this->stale = stale;
- }
- QString getFullPath();
- QString getRemoteChangedTimestamp()
- {
- return remote_changed_timestamp;
- }
- void setRemoteChangedTimestamp(QString remote_changed_timestamp)
- {
- this->remote_changed_timestamp = remote_changed_timestamp;
- }
- void setLocalChangedTimestamp(qint64 timestamp)
- {
- local_changed_timestamp = timestamp;
- }
- QString getETag()
- {
- return etag;
- }
- void setETag(QString etag)
- {
- this->etag = etag;
- }
- QString getMD5Sum()
- {
- return md5sum;
- }
- void setMD5Sum(QString md5sum)
- {
- this->md5sum = md5sum;
- }
-protected:
+class MetaEntry {
+ friend class HttpMetaCache;
+
+ protected:
+ MetaEntry() = default;
+
+ public:
+ auto isStale() -> bool { return stale; }
+ void setStale(bool stale) { this->stale = stale; }
+
+ auto getFullPath() -> QString;
+
+ auto getRemoteChangedTimestamp() -> QString { return remote_changed_timestamp; }
+ void setRemoteChangedTimestamp(QString remote_changed_timestamp) { this->remote_changed_timestamp = remote_changed_timestamp; }
+ void setLocalChangedTimestamp(qint64 timestamp) { local_changed_timestamp = timestamp; }
+
+ auto getETag() -> QString { return etag; }
+ void setETag(QString etag) { this->etag = etag; }
+
+ auto getMD5Sum() -> QString { return md5sum; }
+ void setMD5Sum(QString md5sum) { this->md5sum = md5sum; }
+
+ protected:
QString baseId;
QString basePath;
QString relativePath;
QString md5sum;
QString etag;
qint64 local_changed_timestamp = 0;
- QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time
+ QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time
bool stale = true;
};
-typedef std::shared_ptr<MetaEntry> MetaEntryPtr;
+using MetaEntryPtr = std::shared_ptr<MetaEntry>;
-class HttpMetaCache : public QObject
-{
+class HttpMetaCache : public QObject {
Q_OBJECT
-public:
+ public:
// supply path to the cache index file
HttpMetaCache(QString path = QString());
- ~HttpMetaCache();
+ ~HttpMetaCache() override;
// get the entry solely from the cache
// you probably don't want this, unless you have some specific caching needs.
- MetaEntryPtr getEntry(QString base, QString resource_path);
+ auto getEntry(QString base, QString resource_path) -> MetaEntryPtr;
// get the entry from cache and verify that it isn't stale (within reason)
- MetaEntryPtr resolveEntry(QString base, QString resource_path,
- QString expected_etag = QString());
+ auto resolveEntry(QString base, QString resource_path, QString expected_etag = QString()) -> MetaEntryPtr;
// add a previously resolved stale entry
- bool updateEntry(MetaEntryPtr stale_entry);
+ auto updateEntry(MetaEntryPtr stale_entry) -> bool;
// evict selected entry from cache
- bool evictEntry(MetaEntryPtr entry);
+ auto evictEntry(MetaEntryPtr entry) -> bool;
void addBase(QString base, QString base_root);
// (re)start a timer that calls SaveNow later.
void SaveEventually();
void Load();
- QString getBasePath(QString base);
-public
-slots:
+
+ auto getBasePath(QString base) -> QString;
+
+ public slots:
void SaveNow();
-private:
+ private:
// create a new stale entry, given the parameters
- MetaEntryPtr staleEntry(QString base, QString resource_path);
- struct EntryMap
- {
+ auto staleEntry(QString base, QString resource_path) -> MetaEntryPtr;
+
+ struct EntryMap {
QString base_path;
QMap<QString, MetaEntryPtr> entry_list;
};
+
QMap<QString, EntryMap> m_entries;
QString m_index_file;
QTimer saveBatchingTimer;
diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp
index 5cdf0460..f86dd870 100644
--- a/launcher/net/MetaCacheSink.cpp
+++ b/launcher/net/MetaCacheSink.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "MetaCacheSink.h"
#include <QFile>
#include <QFileInfo>
@@ -12,17 +47,13 @@ MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum)
addValidator(md5sum);
}
-MetaCacheSink::~MetaCacheSink()
-{
- // nil
-}
-
-JobStatus MetaCacheSink::initCache(QNetworkRequest& request)
+Task::State MetaCacheSink::initCache(QNetworkRequest& request)
{
if (!m_entry->isStale())
{
- return Job_Finished;
+ return Task::State::Succeeded;
}
+
// check if file exists, if it does, use its information for the request
QFile current(m_filename);
if(current.exists() && current.size() != 0)
@@ -36,25 +67,31 @@ JobStatus MetaCacheSink::initCache(QNetworkRequest& request)
request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1());
}
}
- return Job_InProgress;
+
+ return Task::State::Running;
}
-JobStatus MetaCacheSink::finalizeCache(QNetworkReply & reply)
+Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply)
{
QFileInfo output_file_info(m_filename);
+
if(wroteAnyData)
{
m_entry->setMD5Sum(m_md5Node->hash().toHex().constData());
}
+
m_entry->setETag(reply.rawHeader("ETag").constData());
+
if (reply.hasRawHeader("Last-Modified"))
{
m_entry->setRemoteChangedTimestamp(reply.rawHeader("Last-Modified").constData());
}
+
m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch());
m_entry->setStale(false);
APPLICATION->metacache()->updateEntry(m_entry);
- return Job_Finished;
+
+ return Task::State::Succeeded;
}
bool MetaCacheSink::hasLocalData()
diff --git a/launcher/net/MetaCacheSink.h b/launcher/net/MetaCacheSink.h
index edcf7ad1..c9f7edfe 100644
--- a/launcher/net/MetaCacheSink.h
+++ b/launcher/net/MetaCacheSink.h
@@ -1,22 +1,58 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "FileSink.h"
+
#include "ChecksumValidator.h"
+#include "FileSink.h"
#include "net/HttpMetaCache.h"
namespace Net {
-class MetaCacheSink : public FileSink
-{
-public: /* con/des */
- MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum);
- virtual ~MetaCacheSink();
- bool hasLocalData() override;
+class MetaCacheSink : public FileSink {
+ public:
+ MetaCacheSink(MetaEntryPtr entry, ChecksumValidator* md5sum);
+ virtual ~MetaCacheSink() = default;
+
+ auto hasLocalData() -> bool override;
-protected: /* methods */
- JobStatus initCache(QNetworkRequest & request) override;
- JobStatus finalizeCache(QNetworkReply & reply) override;
+ protected:
+ auto initCache(QNetworkRequest& request) -> Task::State override;
+ auto finalizeCache(QNetworkReply& reply) -> Task::State override;
-private: /* data */
+ private:
MetaEntryPtr m_entry;
- ChecksumValidator * m_md5Node;
+ ChecksumValidator* m_md5Node;
};
-}
+} // namespace Net
diff --git a/launcher/net/Mode.h b/launcher/net/Mode.h
index 9a95f5ad..3d75981f 100644
--- a/launcher/net/Mode.h
+++ b/launcher/net/Mode.h
@@ -1,10 +1,5 @@
#pragma once
-namespace Net
-{
-enum class Mode
-{
- Offline,
- Online
-};
+namespace Net {
+enum class Mode { Offline, Online };
}
diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h
index efb20953..729d4132 100644
--- a/launcher/net/NetAction.h
+++ b/launcher/net/NetAction.h
@@ -1,108 +1,76 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QObject>
-#include <QUrl>
-#include <memory>
#include <QNetworkReply>
-#include <QObjectPtr.h>
+#include <QUrl>
-enum JobStatus
-{
- Job_NotStarted,
- Job_InProgress,
- Job_Finished,
- Job_Failed,
- Job_Aborted,
- /*
- * FIXME: @NUKE this confuses the task failing with us having a fallback in the form of local data. Clear up the confusion.
- * Same could be true for aborted task - the presence of pre-existing result is a separate concern
- */
- Job_Failed_Proceed
-};
+#include "QObjectPtr.h"
+#include "tasks/Task.h"
-class NetAction : public QObject
-{
+class NetAction : public Task {
Q_OBJECT
-protected:
- explicit NetAction() : QObject(nullptr) {};
+ protected:
+ explicit NetAction() : Task() {};
-public:
+ public:
using Ptr = shared_qobject_ptr<NetAction>;
- virtual ~NetAction() {};
+ virtual ~NetAction() = default;
- bool isRunning() const
- {
- return m_status == Job_InProgress;
- }
- bool isFinished() const
- {
- return m_status >= Job_Finished;
- }
- bool wasSuccessful() const
- {
- return m_status == Job_Finished || m_status == Job_Failed_Proceed;
- }
+ QUrl url() { return m_url; }
+ auto index() -> int { return m_index_within_job; }
- qint64 totalProgress() const
- {
- return m_total_progress;
- }
- qint64 currentProgress() const
- {
- return m_progress;
- }
- virtual bool abort()
- {
- return false;
- }
- virtual bool canAbort()
- {
- return false;
- }
- QUrl url()
- {
- return m_url;
- }
-
-signals:
- void started(int index);
- void netActionProgress(int index, qint64 current, qint64 total);
- void succeeded(int index);
- void failed(int index);
- void aborted(int index);
-
-protected slots:
+ 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;
-public slots:
- void start(shared_qobject_ptr<QNetworkAccessManager> network) {
+ public slots:
+ void startAction(shared_qobject_ptr<QNetworkAccessManager> network)
+ {
m_network = network;
- startImpl();
+ executeTask();
}
-protected:
- virtual void startImpl() = 0;
+ protected:
+ void executeTask() override {};
-public:
+ public:
shared_qobject_ptr<QNetworkAccessManager> m_network;
/// index within the parent job, FIXME: nuke
@@ -113,10 +81,4 @@ public:
/// source URL
QUrl m_url;
-
- qint64 m_progress = 0;
- qint64 m_total_progress = 1;
-
-protected:
- JobStatus m_status = Job_NotStarted;
};
diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp
index 9bad89ed..bab35fa5 100644
--- a/launcher/net/NetJob.cpp
+++ b/launcher/net/NetJob.cpp
@@ -1,79 +1,179 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "NetJob.h"
#include "Download.h"
-#include <QDebug>
+auto NetJob::addNetAction(NetAction::Ptr action) -> bool
+{
+ action->m_index_within_job = m_downloads.size();
+ m_downloads.append(action);
+ part_info pi;
+ m_parts_progress.append(pi);
+
+ partProgress(m_parts_progress.count() - 1, action->getProgress(), action->getTotalProgress());
+
+ if (action->isRunning()) {
+ connect(action.get(), &NetAction::succeeded, [this, action]{ partSucceeded(action->index()); });
+ connect(action.get(), &NetAction::failed, [this, action](QString){ partFailed(action->index()); });
+ connect(action.get(), &NetAction::aborted, [this, action](){ partAborted(action->index()); });
+ connect(action.get(), &NetAction::progress, [this, action](qint64 done, qint64 total) { partProgress(action->index(), done, total); });
+ connect(action.get(), &NetAction::status, this, &NetJob::status);
+ } else {
+ m_todo.append(m_parts_progress.size() - 1);
+ }
+
+ return true;
+}
+
+auto NetJob::canAbort() const -> bool
+{
+ bool canFullyAbort = true;
+
+ // can abort the downloads on the queue?
+ for (auto index : m_todo) {
+ auto part = m_downloads[index];
+ canFullyAbort &= part->canAbort();
+ }
+ // can abort the active downloads?
+ for (auto index : m_doing) {
+ auto part = m_downloads[index];
+ canFullyAbort &= part->canAbort();
+ }
+
+ return canFullyAbort;
+}
+
+void NetJob::executeTask()
+{
+ // hack that delays early failures so they can be caught easier
+ QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection);
+}
+
+auto NetJob::getFailedFiles() -> QStringList
+{
+ QStringList failed;
+ for (auto index : m_failed) {
+ failed.push_back(m_downloads[index]->url().toString());
+ }
+ failed.sort();
+ return failed;
+}
+
+auto NetJob::abort() -> bool
+{
+ bool fullyAborted = true;
+
+ // fail all downloads on the queue
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ QSet<int> todoSet(m_todo.begin(), m_todo.end());
+ m_failed.unite(todoSet);
+#else
+ m_failed.unite(m_todo.toSet());
+#endif
+ m_todo.clear();
+
+ // abort active downloads
+ auto toKill = m_doing.values();
+ for (auto index : toKill) {
+ auto part = m_downloads[index];
+ fullyAborted &= part->abort();
+ }
+
+ return fullyAborted;
+}
void NetJob::partSucceeded(int index)
{
// do progress. all slots are 1 in size at least
- auto &slot = parts_progress[index];
+ auto& slot = m_parts_progress[index];
partProgress(index, slot.total_progress, slot.total_progress);
m_doing.remove(index);
m_done.insert(index);
- downloads[index].get()->disconnect(this);
+ m_downloads[index].get()->disconnect(this);
+
startMoreParts();
}
void NetJob::partFailed(int index)
{
m_doing.remove(index);
- auto &slot = parts_progress[index];
- if (slot.failures == 3)
- {
+
+ auto& slot = m_parts_progress[index];
+ // Can try 3 times before failing by definitive
+ if (slot.failures == 3) {
m_failed.insert(index);
- }
- else
- {
+ } else {
slot.failures++;
m_todo.enqueue(index);
}
- downloads[index].get()->disconnect(this);
+
+ m_downloads[index].get()->disconnect(this);
+
startMoreParts();
}
void NetJob::partAborted(int index)
{
m_aborted = true;
+
m_doing.remove(index);
m_failed.insert(index);
- downloads[index].get()->disconnect(this);
+ m_downloads[index].get()->disconnect(this);
+
startMoreParts();
}
void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal)
{
- auto &slot = parts_progress[index];
+ auto& slot = m_parts_progress[index];
slot.current_progress = bytesReceived;
slot.total_progress = bytesTotal;
int done = m_done.size();
int doing = m_doing.size();
- int all = parts_progress.size();
+ int all = m_parts_progress.size();
qint64 bytesAll = 0;
qint64 bytesTotalAll = 0;
- for(auto & partIdx: m_doing)
- {
- auto part = parts_progress[partIdx];
+ for (auto& partIdx : m_doing) {
+ auto part = m_parts_progress[partIdx];
// do not count parts with unknown/nonsensical total size
- if(part.total_progress <= 0)
- {
+ if (part.total_progress <= 0) {
continue;
}
bytesAll += part.current_progress;
@@ -85,134 +185,54 @@ void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal)
auto current_total = all * 1000;
// HACK: make sure it never jumps backwards.
// FAIL: This breaks if the size is not known (or is it something else?) and jumps to 1000, so if it is 1000 reset it to inprogress
- if(m_current_progress == 1000) {
+ if (m_current_progress == 1000) {
m_current_progress = inprogress;
}
- if(m_current_progress > current)
- {
+ if (m_current_progress > current) {
current = m_current_progress;
}
m_current_progress = current;
setProgress(current, current_total);
}
-void NetJob::executeTask()
-{
- // hack that delays early failures so they can be caught easier
- QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection);
-}
-
void NetJob::startMoreParts()
{
- if(!isRunning())
- {
- // this actually makes sense. You can put running downloads into a NetJob and then not start it until much later.
+ if (!isRunning()) {
+ // this actually makes sense. You can put running m_downloads into a NetJob and then not start it until much later.
return;
}
+
// OK. We are actively processing tasks, proceed.
// Check for final conditions if there's nothing in the queue.
- if(!m_todo.size())
- {
- if(!m_doing.size())
- {
- if(!m_failed.size())
- {
+ if (!m_todo.size()) {
+ if (!m_doing.size()) {
+ if (!m_failed.size()) {
emitSucceeded();
- }
- else if(m_aborted)
- {
+ } else if (m_aborted) {
emitAborted();
- }
- else
- {
+ } else {
emitFailed(tr("Job '%1' failed to process:\n%2").arg(objectName()).arg(getFailedFiles().join("\n")));
}
}
return;
}
- // There's work to do, try to start more parts.
- while (m_doing.size() < 6)
- {
- if(!m_todo.size())
+
+ // There's work to do, try to start more parts, to a maximum of 6 concurrent ones.
+ while (m_doing.size() < 6) {
+ if (m_todo.size() == 0)
return;
int doThis = m_todo.dequeue();
m_doing.insert(doThis);
- auto part = downloads[doThis];
- // connect signals :D
- connect(part.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int)));
- connect(part.get(), SIGNAL(failed(int)), SLOT(partFailed(int)));
- connect(part.get(), SIGNAL(aborted(int)), SLOT(partAborted(int)));
- connect(part.get(), SIGNAL(netActionProgress(int, qint64, qint64)),
- SLOT(partProgress(int, qint64, qint64)));
- part->start(m_network);
- }
-}
-
-
-QStringList NetJob::getFailedFiles()
-{
- QStringList failed;
- for (auto index: m_failed)
- {
- failed.push_back(downloads[index]->url().toString());
- }
- failed.sort();
- return failed;
-}
-bool NetJob::canAbort() const
-{
- bool canFullyAbort = true;
- // can abort the waiting?
- for(auto index: m_todo)
- {
- auto part = downloads[index];
- canFullyAbort &= part->canAbort();
- }
- // can abort the active?
- for(auto index: m_doing)
- {
- auto part = downloads[index];
- canFullyAbort &= part->canAbort();
- }
- return canFullyAbort;
-}
+ auto part = m_downloads[doThis];
-bool NetJob::abort()
-{
- bool fullyAborted = true;
- // fail all waiting
- m_failed.unite(m_todo.toSet());
- m_todo.clear();
- // abort active
- auto toKill = m_doing.toList();
- for(auto index: toKill)
- {
- auto part = downloads[index];
- fullyAborted &= part->abort();
- }
- return fullyAborted;
-}
+ // connect signals :D
+ connect(part.get(), &NetAction::succeeded, this, [this, part]{ partSucceeded(part->index()); });
+ connect(part.get(), &NetAction::failed, this, [this, part](QString){ partFailed(part->index()); });
+ connect(part.get(), &NetAction::aborted, this, [this, part]{ partAborted(part->index()); });
+ connect(part.get(), &NetAction::progress, this, [this, part](qint64 done, qint64 total) { partProgress(part->index(), done, total); });
+ connect(part.get(), &NetAction::status, this, &NetJob::status);
-bool NetJob::addNetAction(NetAction::Ptr action)
-{
- action->m_index_within_job = downloads.size();
- downloads.append(action);
- part_info pi;
- parts_progress.append(pi);
- partProgress(parts_progress.count() - 1, action->currentProgress(), action->totalProgress());
-
- if(action->isRunning())
- {
- connect(action.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int)));
- connect(action.get(), SIGNAL(failed(int)), SLOT(partFailed(int)));
- connect(action.get(), SIGNAL(netActionProgress(int, qint64, qint64)), SLOT(partProgress(int, qint64, qint64)));
+ part->startAction(m_network);
}
- else
- {
- m_todo.append(parts_progress.size() - 1);
- }
- return true;
}
-
-NetJob::~NetJob() = default;
diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h
index fdea710f..63c1cf51 100644
--- a/launcher/net/NetJob.h
+++ b/launcher/net/NetJob.h
@@ -1,88 +1,98 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QtNetwork>
+
+#include <QObject>
#include "NetAction.h"
-#include "Download.h"
-#include "HttpMetaCache.h"
#include "tasks/Task.h"
-#include "QObjectPtr.h"
-class NetJob;
+// Those are included so that they are also included by anyone using NetJob
+#include "net/Download.h"
+#include "net/HttpMetaCache.h"
-class NetJob : public Task
-{
+class NetJob : public Task {
Q_OBJECT
-public:
+
+ public:
using Ptr = shared_qobject_ptr<NetJob>;
explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network) : Task(), m_network(network)
{
setObjectName(job_name);
}
- virtual ~NetJob();
+ virtual ~NetJob() = default;
- bool addNetAction(NetAction::Ptr action);
+ void executeTask() override;
- NetAction::Ptr operator[](int index)
- {
- return downloads[index];
- }
- const NetAction::Ptr at(const int index)
- {
- return downloads.at(index);
- }
- NetAction::Ptr first()
- {
- if (downloads.size())
- return downloads[0];
- return NetAction::Ptr();
- }
- int size() const
- {
- return downloads.size();
- }
- QStringList getFailedFiles();
+ auto canAbort() const -> bool override;
- bool canAbort() const override;
+ auto addNetAction(NetAction::Ptr action) -> bool;
-private slots:
- void startMoreParts();
+ auto operator[](int index) -> NetAction::Ptr { return m_downloads[index]; }
+ auto at(int index) -> const NetAction::Ptr { return m_downloads.at(index); }
+ auto size() const -> int { return m_downloads.size(); }
+ auto first() -> NetAction::Ptr { return m_downloads.size() != 0 ? m_downloads[0] : NetAction::Ptr{}; }
-public slots:
- virtual void executeTask() override;
- virtual bool abort() override;
+ auto getFailedFiles() -> QStringList;
+
+ public slots:
+ // Qt can't handle auto at the start for some reason?
+ bool abort() override;
+
+ private slots:
+ void startMoreParts();
-private slots:
void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal);
void partSucceeded(int index);
void partFailed(int index);
void partAborted(int index);
-private:
+ private:
shared_qobject_ptr<QNetworkAccessManager> m_network;
- struct part_info
- {
+ struct part_info {
qint64 current_progress = 0;
qint64 total_progress = 1;
int failures = 0;
};
- QList<NetAction::Ptr> downloads;
- QList<part_info> parts_progress;
+
+ QList<NetAction::Ptr> m_downloads;
+ QList<part_info> m_parts_progress;
QQueue<int> m_todo;
QSet<int> m_doing;
QSet<int> m_done;
diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp
index 52b82a0e..76b86743 100644
--- a/launcher/net/PasteUpload.cpp
+++ b/launcher/net/PasteUpload.cpp
@@ -1,3 +1,40 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
+ * Copyright (C) 2022 Swirl <swurl@swurl.xyz>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "PasteUpload.h"
#include "BuildConfig.h"
#include "Application.h"
@@ -8,8 +45,22 @@
#include <QJsonDocument>
#include <QFile>
-PasteUpload::PasteUpload(QWidget *window, QString text, QString url) : m_window(window), m_uploadUrl(url), m_text(text.toUtf8())
+std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = {
+ {{"0x0.st", "https://0x0.st", ""},
+ {"hastebin", "https://hst.sh", "/documents"},
+ {"paste.gg", "https://paste.gg", "/api/v1/pastes"},
+ {"mclo.gs", "https://api.mclo.gs", "/1/log"}}};
+
+PasteUpload::PasteUpload(QWidget *window, QString text, QString baseUrl, PasteType pasteType) : m_window(window), m_baseUrl(baseUrl), m_pasteType(pasteType), m_text(text.toUtf8())
{
+ if (m_baseUrl == "")
+ m_baseUrl = PasteTypes.at(pasteType).defaultBase;
+
+ // HACK: Paste's docs say the standard API path is at /api/<version> but the official instance paste.gg doesn't follow that??
+ if (pasteType == PasteGG && m_baseUrl == PasteTypes.at(pasteType).defaultBase)
+ m_uploadUrl = "https://api.paste.gg/v1/pastes";
+ else
+ m_uploadUrl = m_baseUrl + PasteTypes.at(pasteType).endpointPath;
}
PasteUpload::~PasteUpload()
@@ -19,26 +70,76 @@ PasteUpload::~PasteUpload()
void PasteUpload::executeTask()
{
QNetworkRequest request{QUrl(m_uploadUrl)};
- request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
+ QNetworkReply *rep{};
- QHttpMultiPart *multiPart = new QHttpMultiPart{QHttpMultiPart::FormDataType};
+ request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
- QHttpPart filePart;
- filePart.setBody(m_text);
- filePart.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");
- filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"file\"; filename=\"log.txt\"");
+ switch (m_pasteType) {
+ case NullPointer: {
+ QHttpMultiPart *multiPart =
+ new QHttpMultiPart{QHttpMultiPart::FormDataType};
- multiPart->append(filePart);
+ QHttpPart filePart;
+ filePart.setBody(m_text);
+ filePart.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");
+ filePart.setHeader(QNetworkRequest::ContentDispositionHeader,
+ "form-data; name=\"file\"; filename=\"log.txt\"");
+ multiPart->append(filePart);
- QNetworkReply *rep = APPLICATION->network()->post(request, multiPart);
- multiPart->setParent(rep);
+ rep = APPLICATION->network()->post(request, multiPart);
+ multiPart->setParent(rep);
- m_reply = std::shared_ptr<QNetworkReply>(rep);
- setStatus(tr("Uploading to %1").arg(m_uploadUrl));
+ break;
+ }
+ case Hastebin: {
+ request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
+ rep = APPLICATION->network()->post(request, m_text);
+ break;
+ }
+ case Mclogs: {
+ QUrlQuery postData;
+ postData.addQueryItem("content", m_text);
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
+ rep = APPLICATION->network()->post(request, postData.toString().toUtf8());
+ break;
+ }
+ case PasteGG: {
+ QJsonObject obj;
+ QJsonDocument doc;
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+
+ obj.insert("expires", QDateTime::currentDateTimeUtc().addDays(100).toString(Qt::DateFormat::ISODate));
+
+ QJsonArray files;
+ QJsonObject logFileInfo;
+ QJsonObject logFileContentInfo;
+ logFileContentInfo.insert("format", "text");
+ logFileContentInfo.insert("value", QString::fromUtf8(m_text));
+ logFileInfo.insert("name", "log.txt");
+ logFileInfo.insert("content", logFileContentInfo);
+ files.append(logFileInfo);
+
+ obj.insert("files", files);
+
+ doc.setObject(obj);
+ rep = APPLICATION->network()->post(request, doc.toJson());
+ break;
+ }
+ }
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
- connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
- connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
+ connect(rep, &QNetworkReply::finished, this, &PasteUpload::downloadFinished);
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
+ connect(rep, &QNetworkReply::errorOccurred, this, &PasteUpload::downloadError);
+#else
+ connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &PasteUpload::downloadError);
+#endif
+
+
+ m_reply = std::shared_ptr<QNetworkReply>(rep);
+
+ setStatus(tr("Uploading to %1").arg(m_uploadUrl));
}
void PasteUpload::downloadError(QNetworkReply::NetworkError error)
@@ -68,6 +169,82 @@ void PasteUpload::downloadFinished()
return;
}
- m_pasteLink = QString::fromUtf8(data).trimmed();
+ switch (m_pasteType)
+ {
+ case NullPointer:
+ m_pasteLink = QString::fromUtf8(data).trimmed();
+ break;
+ case Hastebin: {
+ QJsonDocument jsonDoc{QJsonDocument::fromJson(data)};
+ QJsonObject jsonObj{jsonDoc.object()};
+ if (jsonObj.contains("key") && jsonObj["key"].isString())
+ {
+ QString key = jsonDoc.object()["key"].toString();
+ m_pasteLink = m_baseUrl + "/" + key;
+ }
+ else
+ {
+ emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
+ qCritical() << m_uploadUrl << " returned malformed response body: " << data;
+ return;
+ }
+ break;
+ }
+ case Mclogs: {
+ QJsonDocument jsonDoc{QJsonDocument::fromJson(data)};
+ QJsonObject jsonObj{jsonDoc.object()};
+ if (jsonObj.contains("success") && jsonObj["success"].isBool())
+ {
+ bool success = jsonObj["success"].toBool();
+ if (success)
+ {
+ m_pasteLink = jsonObj["url"].toString();
+ }
+ else
+ {
+ QString error = jsonObj["error"].toString();
+ emitFailed(tr("Error: %1 returned an error: %2").arg(m_uploadUrl, error));
+ qCritical() << m_uploadUrl << " returned error: " << error;
+ qCritical() << "Response body: " << data;
+ return;
+ }
+ }
+ else
+ {
+ emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
+ qCritical() << m_uploadUrl << " returned malformed response body: " << data;
+ return;
+ }
+ break;
+ }
+ case PasteGG:
+ QJsonDocument jsonDoc{QJsonDocument::fromJson(data)};
+ QJsonObject jsonObj{jsonDoc.object()};
+ if (jsonObj.contains("status") && jsonObj["status"].isString())
+ {
+ QString status = jsonObj["status"].toString();
+ if (status == "success")
+ {
+ m_pasteLink = m_baseUrl + "/p/anonymous/" + jsonObj["result"].toObject()["id"].toString();
+ }
+ else
+ {
+ QString error = jsonObj["error"].toString();
+ QString message = (jsonObj.contains("message") && jsonObj["message"].isString()) ? jsonObj["message"].toString() : "none";
+ emitFailed(tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_uploadUrl, error, message));
+ qCritical() << m_uploadUrl << " returned error: " << error;
+ qCritical() << "Error message: " << message;
+ qCritical() << "Response body: " << data;
+ return;
+ }
+ }
+ else
+ {
+ emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
+ qCritical() << m_uploadUrl << " returned malformed response body: " << data;
+ return;
+ }
+ break;
+ }
emitSucceeded();
}
diff --git a/launcher/net/PasteUpload.h b/launcher/net/PasteUpload.h
index 62b2dc36..eb315c2b 100644
--- a/launcher/net/PasteUpload.h
+++ b/launcher/net/PasteUpload.h
@@ -1,14 +1,74 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "tasks/Task.h"
#include <QNetworkReply>
+#include <QString>
#include <QBuffer>
#include <memory>
+#include <array>
class PasteUpload : public Task
{
Q_OBJECT
public:
- PasteUpload(QWidget *window, QString text, QString url);
+ enum PasteType : int {
+ // 0x0.st
+ NullPointer,
+ // hastebin.com
+ Hastebin,
+ // paste.gg
+ PasteGG,
+ // mclo.gs
+ Mclogs,
+ // Helpful to get the range of valid values on the enum for input sanitisation:
+ First = NullPointer,
+ Last = Mclogs
+ };
+
+ struct PasteTypeInfo {
+ const QString name;
+ const QString defaultBase;
+ const QString endpointPath;
+ };
+
+ static std::array<PasteTypeInfo, 4> PasteTypes;
+
+ PasteUpload(QWidget *window, QString text, QString url, PasteType pasteType);
virtual ~PasteUpload();
QString pasteLink()
@@ -21,7 +81,9 @@ protected:
private:
QWidget *m_window;
QString m_pasteLink;
+ QString m_baseUrl;
QString m_uploadUrl;
+ PasteType m_pasteType;
QByteArray m_text;
std::shared_ptr<QNetworkReply> m_reply;
public
diff --git a/launcher/net/Sink.h b/launcher/net/Sink.h
index d367fb15..3870f29b 100644
--- a/launcher/net/Sink.h
+++ b/launcher/net/Sink.h
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "net/NetAction.h"
@@ -5,33 +40,39 @@
#include "Validator.h"
namespace Net {
-class Sink
-{
-public: /* con/des */
- Sink() {};
- virtual ~Sink() {};
+class Sink {
+ public:
+ Sink() = default;
+ virtual ~Sink() = default;
+
+ public:
+ virtual auto init(QNetworkRequest& request) -> Task::State = 0;
+ virtual auto write(QByteArray& data) -> Task::State = 0;
+ virtual auto abort() -> Task::State = 0;
+ virtual auto finalize(QNetworkReply& reply) -> Task::State = 0;
-public: /* methods */
- virtual JobStatus init(QNetworkRequest & request) = 0;
- virtual JobStatus write(QByteArray & data) = 0;
- virtual JobStatus abort() = 0;
- virtual JobStatus finalize(QNetworkReply & reply) = 0;
- virtual bool hasLocalData() = 0;
+ virtual auto hasLocalData() -> bool = 0;
- void addValidator(Validator * validator)
+ void addValidator(Validator* validator)
{
- if(validator)
- {
+ if (validator) {
validators.push_back(std::shared_ptr<Validator>(validator));
}
}
-protected: /* methods */
- bool finalizeAllValidators(QNetworkReply & reply)
+ protected:
+ bool initAllValidators(QNetworkRequest& request)
+ {
+ for (auto& validator : validators) {
+ if (!validator->init(request))
+ return false;
+ }
+ return true;
+ }
+ bool finalizeAllValidators(QNetworkReply& reply)
{
- for(auto & validator: validators)
- {
- if(!validator->validate(reply))
+ for (auto& validator : validators) {
+ if (!validator->validate(reply))
return false;
}
return true;
@@ -39,32 +80,21 @@ protected: /* methods */
bool failAllValidators()
{
bool success = true;
- for(auto & validator: validators)
- {
+ for (auto& validator : validators) {
success &= validator->abort();
}
return success;
}
- bool initAllValidators(QNetworkRequest & request)
- {
- for(auto & validator: validators)
- {
- if(!validator->init(request))
- return false;
- }
- return true;
- }
- bool writeAllValidators(QByteArray & data)
+ bool writeAllValidators(QByteArray& data)
{
- for(auto & validator: validators)
- {
- if(!validator->write(data))
+ for (auto& validator : validators) {
+ if (!validator->write(data))
return false;
}
return true;
}
-protected: /* data */
+ protected:
std::vector<std::shared_ptr<Validator>> validators;
};
-}
+} // namespace Net
diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp
new file mode 100644
index 00000000..c9942a8d
--- /dev/null
+++ b/launcher/net/Upload.cpp
@@ -0,0 +1,199 @@
+//
+// Created by timoreo on 20/05/22.
+//
+
+#include "Upload.h"
+
+#include <utility>
+#include "ByteArraySink.h"
+#include "BuildConfig.h"
+#include "Application.h"
+
+namespace Net {
+
+ void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
+ setProgress(bytesReceived, bytesTotal);
+ }
+
+ void Upload::downloadError(QNetworkReply::NetworkError error) {
+ if (error == QNetworkReply::OperationCanceledError) {
+ qCritical() << "Aborted " << m_url.toString();
+ m_state = State::AbortedByUser;
+ } else {
+ // error happened during download.
+ qCritical() << "Failed " << m_url.toString() << " with reason " << error;
+ m_state = State::Failed;
+ }
+ }
+
+ void Upload::sslErrors(const QList<QSslError> &errors) {
+ int i = 1;
+ for (const auto& error : errors) {
+ qCritical() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString();
+ auto cert = error.certificate();
+ qCritical() << "Certificate in question:\n" << cert.toText();
+ i++;
+ }
+ }
+
+ bool Upload::handleRedirect()
+ {
+ QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
+ if (!redirect.isValid()) {
+ if (!m_reply->hasRawHeader("Location")) {
+ // no redirect -> it's fine to continue
+ return false;
+ }
+ // there is a Location header, but it's not correct. we need to apply some workarounds...
+ QByteArray redirectBA = m_reply->rawHeader("Location");
+ if (redirectBA.size() == 0) {
+ // empty, yet present redirect header? WTF?
+ return false;
+ }
+ QString redirectStr = QString::fromUtf8(redirectBA);
+
+ if (redirectStr.startsWith("//")) {
+ /*
+ * IF the URL begins with //, we need to insert the URL scheme.
+ * See: https://bugreports.qt.io/browse/QTBUG-41061
+ * See: http://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ redirectStr = m_reply->url().scheme() + ":" + redirectStr;
+ } else if (redirectStr.startsWith("/")) {
+ /*
+ * IF the URL begins with /, we need to process it as a relative URL
+ */
+ auto url = m_reply->url();
+ url.setPath(redirectStr, QUrl::TolerantMode);
+ redirectStr = url.toString();
+ }
+
+ /*
+ * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
+ * FIXME: report Qt bug for this
+ */
+ redirect = QUrl(redirectStr, QUrl::TolerantMode);
+ if (!redirect.isValid()) {
+ qWarning() << "Failed to parse redirect URL:" << redirectStr;
+ downloadError(QNetworkReply::ProtocolFailure);
+ return false;
+ }
+ qDebug() << "Fixed location header:" << redirect;
+ } else {
+ qDebug() << "Location header:" << redirect;
+ }
+
+ m_url = QUrl(redirect.toString());
+ qDebug() << "Following redirect to " << m_url.toString();
+ startAction(m_network);
+ return true;
+ }
+
+ void Upload::downloadFinished() {
+ // handle HTTP redirection first
+ // very unlikely for post requests, still can happen
+ if (handleRedirect()) {
+ qDebug() << "Upload redirected:" << m_url.toString();
+ return;
+ }
+
+ // if the download failed before this point ...
+ if (m_state == State::Succeeded) {
+ qDebug() << "Upload failed but we are allowed to proceed:" << m_url.toString();
+ m_sink->abort();
+ m_reply.reset();
+ emit succeeded();
+ return;
+ } else if (m_state == State::Failed) {
+ qDebug() << "Upload failed in previous step:" << m_url.toString();
+ m_sink->abort();
+ m_reply.reset();
+ emit failed("");
+ return;
+ } else if (m_state == State::AbortedByUser) {
+ qDebug() << "Upload aborted in previous step:" << m_url.toString();
+ m_sink->abort();
+ m_reply.reset();
+ emit aborted();
+ return;
+ }
+
+ // make sure we got all the remaining data, if any
+ auto data = m_reply->readAll();
+ if (data.size()) {
+ qDebug() << "Writing extra" << data.size() << "bytes";
+ m_state = m_sink->write(data);
+ }
+
+ // otherwise, finalize the whole graph
+ m_state = m_sink->finalize(*m_reply.get());
+ if (m_state != State::Succeeded) {
+ qDebug() << "Upload failed to finalize:" << m_url.toString();
+ m_sink->abort();
+ m_reply.reset();
+ emit failed("");
+ return;
+ }
+ m_reply.reset();
+ qDebug() << "Upload succeeded:" << m_url.toString();
+ emit succeeded();
+ }
+
+ void Upload::downloadReadyRead() {
+ if (m_state == State::Running) {
+ auto data = m_reply->readAll();
+ m_state = m_sink->write(data);
+ }
+ }
+
+ void Upload::executeTask() {
+ setStatus(tr("Uploading %1").arg(m_url.toString()));
+
+ if (m_state == State::AbortedByUser) {
+ qWarning() << "Attempt to start an aborted Upload:" << m_url.toString();
+ emit aborted();
+ return;
+ }
+ QNetworkRequest request(m_url);
+ m_state = m_sink->init(request);
+ switch (m_state) {
+ case State::Succeeded:
+ emitSucceeded();
+ qDebug() << "Upload cache hit " << m_url.toString();
+ return;
+ case State::Running:
+ qDebug() << "Uploading " << m_url.toString();
+ break;
+ case State::Inactive:
+ case State::Failed:
+ emitFailed("");
+ return;
+ case State::AbortedByUser:
+ emitAborted();
+ return;
+ }
+
+ request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());
+ if (request.url().host().contains("api.curseforge.com")) {
+ request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8());
+ }
+ //TODO other types of post requests ?
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+ QNetworkReply* rep = m_network->post(request, m_post_data);
+
+ m_reply.reset(rep);
+ connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)));
+ connect(rep, SIGNAL(finished()), SLOT(downloadFinished()));
+ connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
+ connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors);
+ connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead);
+ }
+
+ Upload::Ptr Upload::makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data) {
+ auto* up = new Upload();
+ up->m_url = std::move(url);
+ up->m_sink.reset(new ByteArraySink(output));
+ up->m_post_data = std::move(m_post_data);
+ return up;
+ }
+} // Net
diff --git a/launcher/net/Upload.h b/launcher/net/Upload.h
new file mode 100644
index 00000000..ee784c6e
--- /dev/null
+++ b/launcher/net/Upload.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "NetAction.h"
+#include "Sink.h"
+
+namespace Net {
+
+ class Upload : public NetAction {
+ Q_OBJECT
+
+ public:
+ static Upload::Ptr makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data);
+
+ protected slots:
+ void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
+ void downloadError(QNetworkReply::NetworkError error) override;
+ void sslErrors(const QList<QSslError> & errors);
+ void downloadFinished() override;
+ void downloadReadyRead() override;
+
+ public slots:
+ void executeTask() override;
+ private:
+ std::unique_ptr<Sink> m_sink;
+ QByteArray m_post_data;
+
+ bool handleRedirect();
+ };
+
+} // Net
+
diff --git a/launcher/net/Validator.h b/launcher/net/Validator.h
index 59b72a0b..6b3d4635 100644
--- a/launcher/net/Validator.h
+++ b/launcher/net/Validator.h
@@ -1,3 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "net/NetAction.h"
diff --git a/launcher/news/NewsChecker.cpp b/launcher/news/NewsChecker.cpp
index 6724950f..3b969732 100644
--- a/launcher/news/NewsChecker.cpp
+++ b/launcher/news/NewsChecker.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "NewsChecker.h"
@@ -61,7 +81,7 @@ void NewsChecker::rssDownloadFinished()
// Parse the XML.
if (!doc.setContent(newsData, false, &errorMsg, &errorLine, &errorCol))
{
- QString fullErrorMsg = QString("Error parsing RSS feed XML. %s at %d:%d.").arg(errorMsg, errorLine, errorCol);
+ QString fullErrorMsg = QString("Error parsing RSS feed XML. %1 at %2:%3.").arg(errorMsg).arg(errorLine).arg(errorCol);
fail(fullErrorMsg);
newsData.clear();
return;
diff --git a/launcher/news/NewsEntry.cpp b/launcher/news/NewsEntry.cpp
index 137703d1..cfe07e86 100644
--- a/launcher/news/NewsEntry.cpp
+++ b/launcher/news/NewsEntry.cpp
@@ -54,7 +54,7 @@ inline QString childValue(const QDomElement& element, const QString& childName,
bool NewsEntry::fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg)
{
QString title = childValue(element, "title", tr("Untitled"));
- QString content = childValue(element, "description", tr("No content."));
+ QString content = childValue(element, "content", tr("No content."));
QString link = childValue(element, "id");
entry->title = title;
diff --git a/launcher/resources/multimc/128x128/instances/flame.png b/launcher/resources/multimc/128x128/instances/flame.png
index 8a50a0b4..6482975c 100644
--- a/launcher/resources/multimc/128x128/instances/flame.png
+++ b/launcher/resources/multimc/128x128/instances/flame.png
Binary files differ
diff --git a/launcher/resources/multimc/32x32/instances/flame.png b/launcher/resources/multimc/32x32/instances/flame.png
deleted file mode 100644
index d8987338..00000000
--- a/launcher/resources/multimc/32x32/instances/flame.png
+++ /dev/null
Binary files differ
diff --git a/launcher/resources/multimc/multimc.qrc b/launcher/resources/multimc/multimc.qrc
index e22fe7ee..2337acd6 100644
--- a/launcher/resources/multimc/multimc.qrc
+++ b/launcher/resources/multimc/multimc.qrc
@@ -6,8 +6,7 @@
<!-- REDDIT logo icon, needs reddit license! -->
<file>scalable/reddit-alien.svg</file>
- <!-- Icon for CurseForge. Unknown license? -->
- <file alias="32x32/flame.png">32x32/instances/flame.png</file>
+ <!-- Icon for CurseForge. CC0 -->
<file alias="128x128/flame.png">128x128/instances/flame.png</file>
<!-- launcher settings page -->
@@ -272,7 +271,6 @@
<file>32x32/instances/ftb_logo.png</file>
<file>128x128/instances/ftb_logo.png</file>
- <file>32x32/instances/flame.png</file>
<file>128x128/instances/flame.png</file>
<file>32x32/instances/gear.png</file>
diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp
index d5de302a..a72c32d3 100644
--- a/launcher/screenshots/ImgurAlbumCreation.cpp
+++ b/launcher/screenshots/ImgurAlbumCreation.cpp
@@ -1,3 +1,39 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "ImgurAlbumCreation.h"
#include <QNetworkRequest>
@@ -13,14 +49,14 @@
ImgurAlbumCreation::ImgurAlbumCreation(QList<ScreenShot::Ptr> screenshots) : NetAction(), m_screenshots(screenshots)
{
m_url = BuildConfig.IMGUR_BASE_URL + "album.json";
- m_status = Job_NotStarted;
+ m_state = State::Inactive;
}
-void ImgurAlbumCreation::startImpl()
+void ImgurAlbumCreation::executeTask()
{
- m_status = Job_InProgress;
+ m_state = State::Running;
QNetworkRequest request(m_url);
- request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
+ request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str());
request.setRawHeader("Accept", "application/json");
@@ -38,16 +74,20 @@ void ImgurAlbumCreation::startImpl()
m_reply.reset(rep);
connect(rep, &QNetworkReply::uploadProgress, this, &ImgurAlbumCreation::downloadProgress);
connect(rep, &QNetworkReply::finished, this, &ImgurAlbumCreation::downloadFinished);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
+#else
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
+#endif
}
void ImgurAlbumCreation::downloadError(QNetworkReply::NetworkError error)
{
qDebug() << m_reply->errorString();
- m_status = Job_Failed;
+ m_state = State::Failed;
}
void ImgurAlbumCreation::downloadFinished()
{
- if (m_status != Job_Failed)
+ if (m_state != State::Failed)
{
QByteArray data = m_reply->readAll();
m_reply.reset();
@@ -56,33 +96,32 @@ void ImgurAlbumCreation::downloadFinished()
if (jsonError.error != QJsonParseError::NoError)
{
qDebug() << jsonError.errorString();
- emit failed(m_index_within_job);
+ emitFailed();
return;
}
auto object = doc.object();
if (!object.value("success").toBool())
{
qDebug() << doc.toJson();
- emit failed(m_index_within_job);
+ emitFailed();
return;
}
m_deleteHash = object.value("data").toObject().value("deletehash").toString();
m_id = object.value("data").toObject().value("id").toString();
- m_status = Job_Finished;
- emit succeeded(m_index_within_job);
+ m_state = State::Succeeded;
+ emit succeeded();
return;
}
else
{
qDebug() << m_reply->readAll();
m_reply.reset();
- emit failed(m_index_within_job);
+ emitFailed();
return;
}
}
void ImgurAlbumCreation::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
- m_total_progress = bytesTotal;
- m_progress = bytesReceived;
- emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal);
+ setProgress(bytesReceived, bytesTotal);
+ emit progress(bytesReceived, bytesTotal);
}
diff --git a/launcher/screenshots/ImgurAlbumCreation.h b/launcher/screenshots/ImgurAlbumCreation.h
index cb048a23..0228b6e4 100644
--- a/launcher/screenshots/ImgurAlbumCreation.h
+++ b/launcher/screenshots/ImgurAlbumCreation.h
@@ -1,7 +1,42 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "net/NetAction.h"
#include "Screenshot.h"
-#include "QObjectPtr.h"
typedef shared_qobject_ptr<class ImgurAlbumCreation> ImgurAlbumCreationPtr;
class ImgurAlbumCreation : public NetAction
@@ -24,16 +59,14 @@ public:
protected
slots:
- virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
- virtual void downloadError(QNetworkReply::NetworkError error);
- virtual void downloadFinished();
- virtual void downloadReadyRead()
- {
- }
+ void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
+ void downloadError(QNetworkReply::NetworkError error) override;
+ void downloadFinished() override;
+ void downloadReadyRead() override {}
public
slots:
- virtual void startImpl();
+ void executeTask() override;
private:
QList<ScreenShot::Ptr> m_screenshots;
diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp
index 76a84947..f8ac9bc2 100644
--- a/launcher/screenshots/ImgurUpload.cpp
+++ b/launcher/screenshots/ImgurUpload.cpp
@@ -1,5 +1,42 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "ImgurUpload.h"
#include "BuildConfig.h"
+#include "Application.h"
#include <QNetworkRequest>
#include <QHttpMultiPart>
@@ -13,22 +50,22 @@
ImgurUpload::ImgurUpload(ScreenShot::Ptr shot) : NetAction(), m_shot(shot)
{
m_url = BuildConfig.IMGUR_BASE_URL + "upload.json";
- m_status = Job_NotStarted;
+ m_state = State::Inactive;
}
-void ImgurUpload::startImpl()
+void ImgurUpload::executeTask()
{
finished = false;
- m_status = Job_InProgress;
+ m_state = Task::State::Running;
QNetworkRequest request(m_url);
- request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
+ request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str());
request.setRawHeader("Accept", "application/json");
QFile f(m_shot->m_file.absoluteFilePath());
if (!f.open(QFile::ReadOnly))
{
- emit failed(m_index_within_job);
+ emitFailed();
return;
}
@@ -52,8 +89,11 @@ void ImgurUpload::startImpl()
m_reply.reset(rep);
connect(rep, &QNetworkReply::uploadProgress, this, &ImgurUpload::downloadProgress);
connect(rep, &QNetworkReply::finished, this, &ImgurUpload::downloadFinished);
- connect(rep, SIGNAL(error(QNetworkReply::NetworkError)),
- SLOT(downloadError(QNetworkReply::NetworkError)));
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
+#else
+ connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
+#endif
}
void ImgurUpload::downloadError(QNetworkReply::NetworkError error)
{
@@ -63,10 +103,10 @@ void ImgurUpload::downloadError(QNetworkReply::NetworkError error)
qCritical() << "Double finished ImgurUpload!";
return;
}
- m_status = Job_Failed;
+ m_state = Task::State::Failed;
finished = true;
m_reply.reset();
- emit failed(m_index_within_job);
+ emitFailed();
}
void ImgurUpload::downloadFinished()
{
@@ -84,7 +124,7 @@ void ImgurUpload::downloadFinished()
qDebug() << "imgur server did not reply with JSON" << jsonError.errorString();
finished = true;
m_reply.reset();
- emit failed(m_index_within_job);
+ emitFailed();
return;
}
auto object = doc.object();
@@ -93,20 +133,19 @@ void ImgurUpload::downloadFinished()
qDebug() << "Screenshot upload not successful:" << doc.toJson();
finished = true;
m_reply.reset();
- emit failed(m_index_within_job);
+ emitFailed();
return;
}
m_shot->m_imgurId = object.value("data").toObject().value("id").toString();
m_shot->m_url = object.value("data").toObject().value("link").toString();
m_shot->m_imgurDeleteHash = object.value("data").toObject().value("deletehash").toString();
- m_status = Job_Finished;
+ m_state = Task::State::Succeeded;
finished = true;
- emit succeeded(m_index_within_job);
+ emit succeeded();
return;
}
void ImgurUpload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
- m_total_progress = bytesTotal;
- m_progress = bytesReceived;
- emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal);
+ setProgress(bytesReceived, bytesTotal);
+ emit progress(bytesReceived, bytesTotal);
}
diff --git a/launcher/screenshots/ImgurUpload.h b/launcher/screenshots/ImgurUpload.h
index cf54f58d..404dc876 100644
--- a/launcher/screenshots/ImgurUpload.h
+++ b/launcher/screenshots/ImgurUpload.h
@@ -1,5 +1,40 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "QObjectPtr.h"
+
#include "net/NetAction.h"
#include "Screenshot.h"
@@ -21,7 +56,7 @@ slots:
public
slots:
- void startImpl() override;
+ void executeTask() override;
private:
ScreenShot::Ptr m_shot;
diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp
index 6a3c801d..733cd444 100644
--- a/launcher/settings/INIFile.cpp
+++ b/launcher/settings/INIFile.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "settings/INIFile.h"
@@ -29,7 +49,7 @@ INIFile::INIFile()
QString INIFile::unescape(QString orig)
{
QString out;
- QChar prev = 0;
+ QChar prev = QChar::Null;
for(auto c: orig)
{
if(prev == '\\')
@@ -42,7 +62,7 @@ QString INIFile::unescape(QString orig)
out += '#';
else
out += c;
- prev = 0;
+ prev = QChar::Null;
}
else
{
@@ -52,7 +72,7 @@ QString INIFile::unescape(QString orig)
continue;
}
out += c;
- prev = 0;
+ prev = QChar::Null;
}
}
return out;
@@ -117,7 +137,9 @@ bool INIFile::loadFile(QString fileName)
bool INIFile::loadFile(QByteArray file)
{
QTextStream in(file);
+#if QT_VERSION <= QT_VERSION_CHECK(6, 0, 0)
in.setCodec("UTF-8");
+#endif
QStringList lines = in.readAll().split('\n');
for (int i = 0; i < lines.count(); i++)
diff --git a/launcher/settings/INIFile_test.cpp b/launcher/settings/INIFile_test.cpp
index 08c2155e..d23f9fdf 100644
--- a/launcher/settings/INIFile_test.cpp
+++ b/launcher/settings/INIFile_test.cpp
@@ -1,5 +1,4 @@
#include <QTest>
-#include "TestUtil.h"
#include "settings/INIFile.h"
diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp
new file mode 100644
index 00000000..b88cfb13
--- /dev/null
+++ b/launcher/tasks/ConcurrentTask.cpp
@@ -0,0 +1,144 @@
+#include "ConcurrentTask.h"
+
+#include <QDebug>
+
+ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concurrent)
+ : Task(parent), m_name(task_name), m_total_max_size(max_concurrent)
+{}
+
+ConcurrentTask::~ConcurrentTask()
+{
+ for (auto task : m_queue) {
+ if (task)
+ task->deleteLater();
+ }
+}
+
+auto ConcurrentTask::getStepProgress() const -> qint64
+{
+ return m_stepProgress;
+}
+
+auto ConcurrentTask::getStepTotalProgress() const -> qint64
+{
+ return m_stepTotalProgress;
+}
+
+void ConcurrentTask::addTask(Task::Ptr task)
+{
+ if (!isRunning())
+ m_queue.append(task);
+ else
+ qWarning() << "Tried to add a task to a running concurrent task!";
+}
+
+void ConcurrentTask::executeTask()
+{
+ m_total_size = m_queue.size();
+
+ for (int i = 0; i < m_total_max_size; i++)
+ startNext();
+}
+
+bool ConcurrentTask::abort()
+{
+ if (m_doing.isEmpty()) {
+ // Don't call emitAborted() here, we want to bypass the 'is the task running' check
+ emit aborted();
+ emit finished();
+
+ m_aborted = true;
+ return true;
+ }
+
+ m_queue.clear();
+
+ m_aborted = true;
+ for (auto task : m_doing)
+ m_aborted &= task->abort();
+
+ if (m_aborted)
+ emitAborted();
+
+ return m_aborted;
+}
+
+void ConcurrentTask::startNext()
+{
+ if (m_aborted || m_doing.count() > m_total_max_size)
+ return;
+
+ if (m_queue.isEmpty() && m_doing.isEmpty()) {
+ emitSucceeded();
+ return;
+ }
+
+ if (m_queue.isEmpty())
+ return;
+
+ Task::Ptr next = m_queue.dequeue();
+
+ connect(next.get(), &Task::succeeded, this, [this, next] { subTaskSucceeded(next); });
+ connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); });
+
+ connect(next.get(), &Task::status, this, &ConcurrentTask::subTaskStatus);
+ connect(next.get(), &Task::stepStatus, this, &ConcurrentTask::subTaskStatus);
+
+ connect(next.get(), &Task::progress, this, &ConcurrentTask::subTaskProgress);
+
+ m_doing.insert(next.get(), next);
+
+ setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus());
+ updateState();
+
+ next->start();
+}
+
+void ConcurrentTask::subTaskSucceeded(Task::Ptr task)
+{
+ m_done.insert(task.get(), task);
+ m_doing.remove(task.get());
+
+ disconnect(task.get(), 0, this, 0);
+
+ updateState();
+
+ startNext();
+}
+
+void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg)
+{
+ m_done.insert(task.get(), task);
+ m_failed.insert(task.get(), task);
+
+ m_doing.remove(task.get());
+
+ disconnect(task.get(), 0, this, 0);
+
+ updateState();
+
+ startNext();
+}
+
+void ConcurrentTask::subTaskStatus(const QString& msg)
+{
+ setStepStatus(msg);
+}
+
+void ConcurrentTask::subTaskProgress(qint64 current, qint64 total)
+{
+ if (total == 0) {
+ setProgress(0, 100);
+ return;
+ }
+
+ m_stepProgress = current;
+ m_stepTotalProgress = total;
+}
+
+void ConcurrentTask::updateState()
+{
+ setProgress(m_done.count(), m_total_size);
+ 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(m_total_size)));
+}
diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h
new file mode 100644
index 00000000..5898899d
--- /dev/null
+++ b/launcher/tasks/ConcurrentTask.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <QQueue>
+#include <QSet>
+
+#include "tasks/Task.h"
+
+class ConcurrentTask : public Task {
+ Q_OBJECT
+public:
+ explicit ConcurrentTask(QObject* parent = nullptr, QString task_name = "", int max_concurrent = 6);
+ virtual ~ConcurrentTask();
+
+ inline auto isMultiStep() const -> bool override { return m_queue.size() > 1; };
+ auto getStepProgress() const -> qint64 override;
+ auto getStepTotalProgress() const -> qint64 override;
+
+ inline auto getStepStatus() const -> QString override { return m_step_status; }
+
+ void addTask(Task::Ptr task);
+
+public slots:
+ bool abort() override;
+
+protected
+slots:
+ void executeTask() override;
+
+ virtual void startNext();
+
+ void subTaskSucceeded(Task::Ptr);
+ void subTaskFailed(Task::Ptr, const QString &msg);
+ void subTaskStatus(const QString &msg);
+ void subTaskProgress(qint64 current, qint64 total);
+
+protected:
+ void setStepStatus(QString status) { m_step_status = status; emit stepStatus(status); };
+
+ virtual void updateState();
+
+protected:
+ QString m_name;
+ QString m_step_status;
+
+ QQueue<Task::Ptr> m_queue;
+
+ QHash<Task*, Task::Ptr> m_doing;
+ QHash<Task*, Task::Ptr> m_done;
+ QHash<Task*, Task::Ptr> m_failed;
+
+ int m_total_max_size;
+ int m_total_size;
+
+ qint64 m_stepProgress = 0;
+ qint64 m_stepTotalProgress = 100;
+
+ bool m_aborted = false;
+};
diff --git a/launcher/tasks/SequentialTask.cpp b/launcher/tasks/SequentialTask.cpp
index 1573e476..7f03ad2e 100644
--- a/launcher/tasks/SequentialTask.cpp
+++ b/launcher/tasks/SequentialTask.cpp
@@ -33,11 +33,22 @@ void SequentialTask::executeTask()
bool SequentialTask::abort()
{
- bool succeeded = true;
- for (auto& task : m_queue) {
- if (!task->abort()) succeeded = false;
+ if(m_currentIndex == -1 || m_currentIndex >= m_queue.size()) {
+ if(m_currentIndex == -1) {
+ // Don't call emitAborted() here, we want to bypass the 'is the task running' check
+ emit aborted();
+ emit finished();
+ }
+ m_queue.clear();
+ return true;
}
+ bool succeeded = m_queue[m_currentIndex]->abort();
+ m_queue.clear();
+
+ if(succeeded)
+ emitAborted();
+
return succeeded;
}
@@ -53,12 +64,18 @@ void SequentialTask::startNext()
return;
}
Task::Ptr next = m_queue[m_currentIndex];
+
connect(next.get(), SIGNAL(failed(QString)), this, SLOT(subTaskFailed(QString)));
+ connect(next.get(), SIGNAL(succeeded()), this, SLOT(startNext()));
+
connect(next.get(), SIGNAL(status(QString)), this, SLOT(subTaskStatus(QString)));
+ connect(next.get(), SIGNAL(stepStatus(QString)), this, SLOT(subTaskStatus(QString)));
+
connect(next.get(), SIGNAL(progress(qint64, qint64)), this, SLOT(subTaskProgress(qint64, qint64)));
- connect(next.get(), SIGNAL(succeeded()), this, SLOT(startNext()));
setStatus(tr("Executing task %1 out of %2").arg(m_currentIndex + 1).arg(m_queue.size()));
+ setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus());
+
next->start();
}
@@ -68,7 +85,7 @@ void SequentialTask::subTaskFailed(const QString& msg)
}
void SequentialTask::subTaskStatus(const QString& msg)
{
- setStepStatus(m_queue[m_currentIndex]->getStatus());
+ setStepStatus(msg);
}
void SequentialTask::subTaskProgress(qint64 current, qint64 total)
{
@@ -76,7 +93,7 @@ void SequentialTask::subTaskProgress(qint64 current, qint64 total)
setProgress(0, 100);
return;
}
- setProgress(m_currentIndex, m_queue.count());
+ setProgress(m_currentIndex + 1, m_queue.count());
m_stepProgress = current;
m_stepTotalProgress = total;
diff --git a/launcher/tasks/SequentialTask.h b/launcher/tasks/SequentialTask.h
index 5b3c0111..e10cb6f7 100644
--- a/launcher/tasks/SequentialTask.h
+++ b/launcher/tasks/SequentialTask.h
@@ -32,13 +32,10 @@ slots:
void subTaskStatus(const QString &msg);
void subTaskProgress(qint64 current, qint64 total);
-signals:
- void stepStatus(QString status);
+protected:
+ void setStepStatus(QString status) { m_step_status = status; emit stepStatus(status); };
-private:
- void setStepStatus(QString status) { m_step_status = status; };
-
-private:
+protected:
QString m_name;
QString m_step_status;
diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp
index 57307b43..bb71b98c 100644
--- a/launcher/tasks/Task.cpp
+++ b/launcher/tasks/Task.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "Task.h"
@@ -99,7 +119,7 @@ void Task::emitAborted()
m_state = State::AbortedByUser;
m_failReason = "Aborted.";
qDebug() << "Task" << describe() << "aborted.";
- emit failed(m_failReason);
+ emit aborted();
emit finished();
}
diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h
index 344a024e..aafaf68c 100644
--- a/launcher/tasks/Task.h
+++ b/launcher/tasks/Task.h
@@ -1,24 +1,40 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QObject>
-#include <QString>
-#include <QStringList>
-
#include "QObjectPtr.h"
class Task : public QObject {
@@ -52,6 +68,8 @@ class Task : public QObject {
virtual bool canAbort() const { return false; }
+ auto getState() const -> State { return m_state; }
+
QString getStatus() { return m_status; }
virtual auto getStepStatus() const -> QString { return m_status; }
@@ -68,15 +86,17 @@ class Task : public QObject {
signals:
void started();
- virtual void progress(qint64 current, qint64 total);
+ void progress(qint64 current, qint64 total);
void finished();
void succeeded();
+ void aborted();
void failed(QString reason);
void status(QString status);
+ void stepStatus(QString status);
public slots:
virtual void start();
- virtual bool abort() { return false; };
+ virtual bool abort() { if(canAbort()) emitAborted(); return canAbort(); };
protected:
virtual void executeTask() = 0;
@@ -84,13 +104,13 @@ class Task : public QObject {
protected slots:
virtual void emitSucceeded();
virtual void emitAborted();
- virtual void emitFailed(QString reason);
+ virtual void emitFailed(QString reason = "");
public slots:
void setStatus(const QString& status);
void setProgress(qint64 current, qint64 total);
- private:
+ protected:
State m_state = State::Inactive;
QStringList m_Warnings;
QString m_failReason = "";
diff --git a/launcher/tasks/Task_test.cpp b/launcher/tasks/Task_test.cpp
index 9b6cc2e5..ef153a6a 100644
--- a/launcher/tasks/Task_test.cpp
+++ b/launcher/tasks/Task_test.cpp
@@ -1,5 +1,4 @@
#include <QTest>
-#include "TestUtil.h"
#include "Task.h"
diff --git a/launcher/translations/POTranslator.cpp b/launcher/translations/POTranslator.cpp
index 1ffcb9a4..c77ae45d 100644
--- a/launcher/translations/POTranslator.cpp
+++ b/launcher/translations/POTranslator.cpp
@@ -329,6 +329,11 @@ POTranslator::POTranslator(const QString& filename, QObject* parent) : QTranslat
d->reload();
}
+POTranslator::~POTranslator()
+{
+ delete d;
+}
+
QString POTranslator::translate(const char* context, const char* sourceText, const char* disambiguation, int n) const
{
if(disambiguation)
diff --git a/launcher/translations/POTranslator.h b/launcher/translations/POTranslator.h
index 6d518560..1634018c 100644
--- a/launcher/translations/POTranslator.h
+++ b/launcher/translations/POTranslator.h
@@ -9,6 +9,7 @@ class POTranslator : public QTranslator
Q_OBJECT
public:
explicit POTranslator(const QString& filename, QObject * parent = nullptr);
+ virtual ~POTranslator();
QString translate(const char * context, const char * sourceText, const char * disambiguation, int n) const override;
bool isEmpty() const override;
private:
diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp
index 250854d3..848b4d19 100644
--- a/launcher/translations/TranslationsModel.cpp
+++ b/launcher/translations/TranslationsModel.cpp
@@ -1,3 +1,39 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "TranslationsModel.h"
#include <QCoreApplication>
@@ -17,7 +53,7 @@
#include "Application.h"
-const static QLatin1Literal defaultLangCode("en_US");
+const static QLatin1String defaultLangCode("en_US");
enum class FileType
{
@@ -396,9 +432,7 @@ QVariant TranslationsModel::data(const QModelIndex& index, int role) const
}
case Column::Completeness:
{
- QString text;
- text.sprintf("%3.1f %%", lang.percentTranslated());
- return text;
+ return QString("%1%").arg(lang.percentTranslated(), 3, 'f', 1);
}
}
}
@@ -667,7 +701,7 @@ void TranslationsModel::downloadTranslation(QString key)
auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + lang->file_name), entry);
auto rawHash = QByteArray::fromHex(lang->file_sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
- dl->m_total_progress = lang->file_size;
+ dl->setProgress(dl->getProgress(), lang->file_size);
d->m_dl_job = new NetJob("Translation for " + key, APPLICATION->network());
d->m_dl_job->addNetAction(dl);
diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp
index 9eb658e2..5a62e4d0 100644
--- a/launcher/ui/GuiUtil.cpp
+++ b/launcher/ui/GuiUtil.cpp
@@ -1,3 +1,39 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "GuiUtil.h"
#include <QClipboard>
@@ -16,8 +52,9 @@
QString GuiUtil::uploadPaste(const QString &text, QWidget *parentWidget)
{
ProgressDialog dialog(parentWidget);
- auto pasteUrlSetting = APPLICATION->settings()->get("PastebinURL").toString();
- std::unique_ptr<PasteUpload> paste(new PasteUpload(parentWidget, text, pasteUrlSetting));
+ auto pasteTypeSetting = static_cast<PasteUpload::PasteType>(APPLICATION->settings()->get("PastebinType").toInt());
+ auto pasteCustomAPIBaseSetting = APPLICATION->settings()->get("PastebinCustomAPIBase").toString();
+ std::unique_ptr<PasteUpload> paste(new PasteUpload(parentWidget, text, pasteCustomAPIBaseSetting, pasteTypeSetting));
dialog.execWithTask(paste.get());
if (!paste->wasSuccessful())
diff --git a/launcher/ui/InstanceWindow.cpp b/launcher/ui/InstanceWindow.cpp
index ae765c3c..0ad8c594 100644
--- a/launcher/ui/InstanceWindow.cpp
+++ b/launcher/ui/InstanceWindow.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "InstanceWindow.h"
@@ -97,9 +117,9 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent)
// set up instance and launch process recognition
{
auto launchTask = m_instance->getLaunchTask();
- on_InstanceLaunchTask_changed(launchTask);
- connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &InstanceWindow::on_InstanceLaunchTask_changed);
- connect(m_instance.get(), &BaseInstance::runningStatusChanged, this, &InstanceWindow::on_RunningState_changed);
+ instanceLaunchTaskChanged(launchTask);
+ connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &InstanceWindow::instanceLaunchTaskChanged);
+ connect(m_instance.get(), &BaseInstance::runningStatusChanged, this, &InstanceWindow::runningStateChanged);
}
// set up instance destruction detection
@@ -152,12 +172,12 @@ void InstanceWindow::on_btnLaunchMinecraftOffline_clicked()
APPLICATION->launch(m_instance, false, nullptr);
}
-void InstanceWindow::on_InstanceLaunchTask_changed(shared_qobject_ptr<LaunchTask> proc)
+void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc)
{
m_proc = proc;
}
-void InstanceWindow::on_RunningState_changed(bool running)
+void InstanceWindow::runningStateChanged(bool running)
{
updateLaunchButtons();
m_container->refreshContainer();
diff --git a/launcher/ui/InstanceWindow.h b/launcher/ui/InstanceWindow.h
index 1acf684e..aec07868 100644
--- a/launcher/ui/InstanceWindow.h
+++ b/launcher/ui/InstanceWindow.h
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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
@@ -55,8 +75,8 @@ slots:
void on_btnKillMinecraft_clicked();
void on_btnLaunchMinecraftOffline_clicked();
- void on_InstanceLaunchTask_changed(shared_qobject_ptr<LaunchTask> proc);
- void on_RunningState_changed(bool running);
+ void instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc);
+ void runningStateChanged(bool running);
void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus);
protected:
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 951fcccf..d58f158e 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -1,51 +1,72 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
*
- * Authors: Andrew Okin
- * Peterix
- * Orochimarufan <orochimarufan.x3@gmail.com>
+ * 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
+ * Authors: Andrew Okin
+ * Peterix
+ * Orochimarufan <orochimarufan.x3@gmail.com>
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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
*
- * 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.
+ * 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 "Application.h"
#include "BuildConfig.h"
#include "MainWindow.h"
-#include <QtCore/QVariant>
-#include <QtCore/QUrl>
-#include <QtCore/QDir>
-#include <QtCore/QFileInfo>
-
-#include <QtGui/QKeyEvent>
-
-#include <QtWidgets/QAction>
-#include <QtWidgets/QApplication>
-#include <QtWidgets/QButtonGroup>
-#include <QtWidgets/QHBoxLayout>
-#include <QtWidgets/QHeaderView>
-#include <QtWidgets/QMainWindow>
-#include <QtWidgets/QStatusBar>
-#include <QtWidgets/QToolBar>
-#include <QtWidgets/QWidget>
-#include <QtWidgets/QMenu>
-#include <QtWidgets/QMenuBar>
-#include <QtWidgets/QMessageBox>
-#include <QtWidgets/QInputDialog>
-#include <QtWidgets/QLabel>
-#include <QtWidgets/QToolButton>
-#include <QtWidgets/QWidgetAction>
-#include <QtWidgets/QProgressDialog>
-#include <QtWidgets/QShortcut>
+#include <QVariant>
+#include <QUrl>
+#include <QDir>
+#include <QFileInfo>
+
+#include <QKeyEvent>
+#include <QAction>
+
+#include <QApplication>
+#include <QButtonGroup>
+#include <QHBoxLayout>
+#include <QHeaderView>
+#include <QMainWindow>
+#include <QStatusBar>
+#include <QToolBar>
+#include <QWidget>
+#include <QMenu>
+#include <QMenuBar>
+#include <QMessageBox>
+#include <QInputDialog>
+#include <QLabel>
+#include <QToolButton>
+#include <QWidgetAction>
+#include <QProgressDialog>
+#include <QShortcut>
#include <BaseInstance.h>
#include <InstanceList.h>
@@ -74,6 +95,7 @@
#include "ui/instanceview/InstanceDelegate.h"
#include "ui/widgets/LabeledToolButton.h"
#include "ui/dialogs/NewInstanceDialog.h"
+#include "ui/dialogs/NewsDialog.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/AboutDialog.h"
#include "ui/dialogs/VersionSelectDialog.h"
@@ -203,6 +225,7 @@ public:
TranslatedAction actionMoreNews;
TranslatedAction actionManageAccounts;
TranslatedAction actionLaunchInstance;
+ TranslatedAction actionKillInstance;
TranslatedAction actionRenameInstance;
TranslatedAction actionChangeInstGroup;
TranslatedAction actionChangeInstIcon;
@@ -212,7 +235,6 @@ public:
TranslatedAction actionMods;
TranslatedAction actionViewSelectedInstFolder;
TranslatedAction actionViewSelectedMCFolder;
- TranslatedAction actionViewSelectedModsFolder;
TranslatedAction actionDeleteInstance;
TranslatedAction actionConfig_Folder;
TranslatedAction actionCAT;
@@ -261,27 +283,6 @@ public:
TranslatedToolbar instanceToolBar;
TranslatedToolbar newsToolBar;
QVector<TranslatedToolbar *> all_toolbars;
- bool m_kill = false;
-
- void updateLaunchAction()
- {
- if(m_kill)
- {
- actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Kill"));
- actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Kill the running instance"));
- }
- else
- {
- actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Launch"));
- actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance."));
- }
- actionLaunchInstance.retranslate();
- }
- void setLaunchAction(bool kill)
- {
- m_kill = kill;
- updateLaunchAction();
- }
void createMainToolbarActions(MainWindow *MainWindow)
{
@@ -482,9 +483,12 @@ public:
menuBar->setVisible(APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool());
fileMenu = menuBar->addMenu(tr("&File"));
+ // Workaround for QTBUG-94802 (https://bugreports.qt.io/browse/QTBUG-94802); also present for other menus
+ fileMenu->setSeparatorsCollapsible(false);
fileMenu->addAction(actionAddInstance);
fileMenu->addAction(actionLaunchInstance);
fileMenu->addAction(actionLaunchInstanceOffline);
+ fileMenu->addAction(actionKillInstance);
fileMenu->addAction(actionCloseWindow);
fileMenu->addSeparator();
fileMenu->addAction(actionEditInstance);
@@ -505,15 +509,18 @@ public:
fileMenu->addAction(actionSettings);
viewMenu = menuBar->addMenu(tr("&View"));
+ viewMenu->setSeparatorsCollapsible(false);
viewMenu->addAction(actionCAT);
viewMenu->addSeparator();
menuBar->addMenu(foldersMenu);
profileMenu = menuBar->addMenu(tr("&Profiles"));
+ profileMenu->setSeparatorsCollapsible(false);
profileMenu->addAction(actionManageAccounts);
helpMenu = menuBar->addMenu(tr("&Help"));
+ helpMenu->setSeparatorsCollapsible(false);
helpMenu->addAction(actionAbout);
helpMenu->addAction(actionOpenWiki);
helpMenu->addAction(actionNewsMenuBar);
@@ -559,10 +566,9 @@ public:
}
// "Instance actions" are actions that require an instance to be selected (i.e. "new instance" is not here)
+ // Actions that also require other conditions (e.g. a running instance) won't be changed.
void setInstanceActionsEnabled(bool enabled)
{
- actionLaunchInstance->setEnabled(enabled);
- actionLaunchInstanceOffline->setEnabled(enabled);
actionEditInstance->setEnabled(enabled);
actionEditInstNotes->setEnabled(enabled);
actionMods->setEnabled(enabled);
@@ -649,6 +655,14 @@ public:
actionLaunchInstanceOffline.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in offline mode."));
all_actions.append(&actionLaunchInstanceOffline);
+ actionKillInstance = TranslatedAction(MainWindow);
+ actionKillInstance->setObjectName(QStringLiteral("actionKillInstance"));
+ actionKillInstance->setDisabled(true);
+ actionKillInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Kill"));
+ actionKillInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Kill the running instance"));
+ actionKillInstance->setShortcut(QKeySequence(tr("Ctrl+K")));
+ all_actions.append(&actionKillInstance);
+
actionEditInstance = TranslatedAction(MainWindow);
actionEditInstance->setObjectName(QStringLiteral("actionEditInstance"));
actionEditInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Inst&ance..."));
@@ -694,14 +708,6 @@ public:
actionViewSelectedMCFolder->setShortcut(QKeySequence(tr("Ctrl+M")));
all_actions.append(&actionViewSelectedMCFolder);
- /*
- actionViewSelectedModsFolder = TranslatedAction(MainWindow);
- actionViewSelectedModsFolder->setObjectName(QStringLiteral("actionViewSelectedModsFolder"));
- actionViewSelectedModsFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Mods Folder"));
- actionViewSelectedModsFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's mods folder in a file browser."));
- all_actions.append(&actionViewSelectedModsFolder);
- */
-
actionConfig_Folder = TranslatedAction(MainWindow);
actionConfig_Folder->setObjectName(QStringLiteral("actionConfig_Folder"));
actionConfig_Folder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Confi&g Folder"));
@@ -764,6 +770,7 @@ public:
instanceToolBar->addAction(actionLaunchInstance);
instanceToolBar->addAction(actionLaunchInstanceOffline);
+ instanceToolBar->addAction(actionKillInstance);
instanceToolBar->addSeparator();
@@ -777,9 +784,6 @@ public:
instanceToolBar->addSeparator();
instanceToolBar->addAction(actionViewSelectedMCFolder);
- /*
- instanceToolBar->addAction(actionViewSelectedModsFolder);
- */
instanceToolBar->addAction(actionConfig_Folder);
instanceToolBar->addAction(actionViewSelectedInstFolder);
@@ -801,7 +805,7 @@ public:
}
MainWindow->resize(800, 600);
MainWindow->setWindowIcon(APPLICATION->getThemedIcon("logo"));
- MainWindow->setWindowTitle(BuildConfig.LAUNCHER_DISPLAYNAME);
+ MainWindow->setWindowTitle(APPLICATION->applicationDisplayName());
#ifndef QT_NO_ACCESSIBILITY
MainWindow->setAccessibleName(BuildConfig.LAUNCHER_NAME);
#endif
@@ -836,8 +840,6 @@ public:
void retranslateUi(MainWindow *MainWindow)
{
- QString winTitle = tr("%1 - Version %2", "Launcher - Version X").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString());
- MainWindow->setWindowTitle(winTitle);
// all the actions
for(auto * item: all_actions)
{
@@ -1169,14 +1171,10 @@ void MainWindow::updateToolsMenu()
QToolButton *launchButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance));
QToolButton *launchOfflineButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline));
- if(m_selectedInstance && m_selectedInstance->isRunning())
- {
- ui->actionLaunchInstance->setMenu(nullptr);
- ui->actionLaunchInstanceOffline->setMenu(nullptr);
- launchButton->setPopupMode(QToolButton::InstantPopup);
- launchOfflineButton->setPopupMode(QToolButton::InstantPopup);
- return;
- }
+ bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning();
+
+ ui->actionLaunchInstance->setDisabled(!m_selectedInstance || currentInstanceRunning);
+ ui->actionLaunchInstanceOffline->setDisabled(!m_selectedInstance || currentInstanceRunning);
QMenu *launchMenu = ui->actionLaunchInstance->menu();
QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu();
@@ -1204,6 +1202,9 @@ void MainWindow::updateToolsMenu()
normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O")));
if (m_selectedInstance)
{
+ normalLaunch->setEnabled(m_selectedInstance->canLaunch());
+ normalLaunchOffline->setEnabled(m_selectedInstance->canLaunch());
+
connect(normalLaunch, &QAction::triggered, [this]() {
APPLICATION->launch(m_selectedInstance, true);
});
@@ -1234,6 +1235,9 @@ void MainWindow::updateToolsMenu()
}
else if (m_selectedInstance)
{
+ profilerAction->setEnabled(m_selectedInstance->canLaunch());
+ profilerOfflineAction->setEnabled(m_selectedInstance->canLaunch());
+
connect(profilerAction, &QAction::triggered, [this, profiler]()
{
APPLICATION->launch(m_selectedInstance, true, profiler.get());
@@ -1486,7 +1490,11 @@ void MainWindow::updateNotAvailable()
QList<int> stringToIntList(const QString &string)
{
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ QStringList split = string.split(',', Qt::SkipEmptyParts);
+#else
QStringList split = string.split(',', QString::SkipEmptyParts);
+#endif
QList<int> out;
for (int i = 0; i < split.size(); ++i)
{
@@ -1876,14 +1884,12 @@ void MainWindow::globalSettingsClosed()
updateMainToolBar();
updateToolsMenu();
updateStatusCenter();
+ // This needs to be done to prevent UI elements disappearing in the event the config is changed
+ // but PolyMC exits abnormally, causing the window state to never be saved:
+ APPLICATION->settings()->set("MainWindowState", saveState().toBase64());
update();
}
-void MainWindow::on_actionInstanceSettings_triggered()
-{
- APPLICATION->showInstanceWindow(m_selectedInstance, "settings");
-}
-
void MainWindow::on_actionEditInstNotes_triggered()
{
APPLICATION->showInstanceWindow(m_selectedInstance, "notes");
@@ -1926,20 +1932,17 @@ void MainWindow::on_actionOpenWiki_triggered()
void MainWindow::on_actionMoreNews_triggered()
{
- DesktopServices::openUrl(QUrl(BuildConfig.NEWS_OPEN_URL));
+ auto entries = m_newsChecker->getNewsEntries();
+ NewsDialog news_dialog(entries, this);
+ news_dialog.exec();
}
void MainWindow::newsButtonClicked()
{
- QList<NewsEntryPtr> entries = m_newsChecker->getNewsEntries();
- if (entries.count() > 0)
- {
- DesktopServices::openUrl(QUrl(entries[0]->link));
- }
- else
- {
- DesktopServices::openUrl(QUrl(BuildConfig.NEWS_OPEN_URL));
- }
+ auto entries = m_newsChecker->getNewsEntries();
+ NewsDialog news_dialog(entries, this);
+ news_dialog.toggleArticleList();
+ news_dialog.exec();
}
void MainWindow::on_actionAbout_triggered()
@@ -2009,20 +2012,6 @@ void MainWindow::on_actionViewSelectedMCFolder_triggered()
}
}
-void MainWindow::on_actionViewSelectedModsFolder_triggered()
-{
- if (m_selectedInstance)
- {
- QString str = m_selectedInstance->modsRoot();
- if (!FS::ensureFilePathExists(str))
- {
- // TODO: report error
- return;
- }
- DesktopServices::openDirectory(QDir(str).absolutePath());
- }
-}
-
void MainWindow::closeEvent(QCloseEvent *event)
{
// Save the window state and geometry.
@@ -2055,15 +2044,7 @@ void MainWindow::instanceActivated(QModelIndex index)
void MainWindow::on_actionLaunchInstance_triggered()
{
- if (!m_selectedInstance)
- {
- return;
- }
- if(m_selectedInstance->isRunning())
- {
- APPLICATION->kill(m_selectedInstance);
- }
- else
+ if(m_selectedInstance && !m_selectedInstance->isRunning())
{
APPLICATION->launch(m_selectedInstance);
}
@@ -2082,6 +2063,14 @@ void MainWindow::on_actionLaunchInstanceOffline_triggered()
}
}
+void MainWindow::on_actionKillInstance_triggered()
+{
+ if(m_selectedInstance && m_selectedInstance->isRunning())
+ {
+ APPLICATION->kill(m_selectedInstance);
+ }
+}
+
void MainWindow::taskEnd()
{
QObject *sender = QObject::sender();
@@ -2106,23 +2095,18 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
selectionBad();
return;
}
+ if (m_selectedInstance) {
+ disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
+ }
QString id = current.data(InstanceList::InstanceIDRole).toString();
m_selectedInstance = APPLICATION->instances()->getInstanceById(id);
if (m_selectedInstance)
{
ui->instanceToolBar->setEnabled(true);
ui->setInstanceActionsEnabled(true);
- if(m_selectedInstance->isRunning())
- {
- ui->actionLaunchInstance->setEnabled(true);
- ui->setLaunchAction(true);
- }
- else
- {
- ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch());
- ui->setLaunchAction(false);
- }
+ ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch());
ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch());
+ ui->actionKillInstance->setEnabled(m_selectedInstance->isRunning());
ui->actionExportInstance->setEnabled(m_selectedInstance->canExport());
ui->renameButton->setText(m_selectedInstance->name());
m_statusLeft->setText(m_selectedInstance->getStatusbarDescription());
@@ -2132,11 +2116,16 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
updateToolsMenu();
APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id());
+
+ connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
}
else
{
ui->instanceToolBar->setEnabled(false);
ui->setInstanceActionsEnabled(false);
+ ui->actionLaunchInstance->setEnabled(false);
+ ui->actionLaunchInstanceOffline->setEnabled(false);
+ ui->actionKillInstance->setEnabled(false);
APPLICATION->settings()->set("SelectedInstance", QString());
selectionBad();
return;
@@ -2166,6 +2155,7 @@ void MainWindow::selectionBad()
statusBar()->clearMessage();
ui->instanceToolBar->setEnabled(false);
ui->setInstanceActionsEnabled(false);
+ updateToolsMenu();
ui->renameButton->setText(tr("Rename Instance"));
updateInstanceToolIcon("grass");
@@ -2221,3 +2211,9 @@ void MainWindow::updateStatusCenter()
m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed)));
}
}
+
+void MainWindow::refreshCurrentInstance(bool running)
+{
+ auto current = view->selectionModel()->currentIndex();
+ instanceChanged(current, current);
+}
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index 2032acba..d7930b5a 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -1,16 +1,40 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Authors: Andrew Okin
+ * Peterix
+ * Orochimarufan <orochimarufan.x3@gmail.com>
+ *
+ * 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
@@ -94,8 +118,6 @@ private slots:
void on_actionViewSelectedMCFolder_triggered();
- void on_actionViewSelectedModsFolder_triggered();
-
void refreshInstances();
void on_actionViewCentralModsFolder_triggered();
@@ -104,8 +126,6 @@ private slots:
void on_actionSettings_triggered();
- void on_actionInstanceSettings_triggered();
-
void on_actionManageAccounts_triggered();
void on_actionReportBug_triggered();
@@ -120,6 +140,8 @@ private slots:
void on_actionLaunchInstanceOffline_triggered();
+ void on_actionKillInstance_triggered();
+
void on_actionDeleteInstance_triggered();
void deleteGroup();
@@ -192,6 +214,8 @@ private slots:
void keyReleaseEvent(QKeyEvent *event) override;
#endif
+ void refreshCurrentInstance(bool running);
+
private:
void retranslateUi();
diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp
index 8dadb755..c5367d5b 100644
--- a/launcher/ui/dialogs/AboutDialog.cpp
+++ b/launcher/ui/dialogs/AboutDialog.cpp
@@ -64,7 +64,9 @@ QString getCreditsHtml()
{
QString output;
QTextStream stream(&output);
+#if QT_VERSION <= QT_VERSION_CHECK(6, 0, 0)
stream.setCodec(QTextCodec::codecForName("UTF-8"));
+#endif
stream << "<center>\n";
//: %1 is the name of the launcher, determined at build time, e.g. "PolyMC Developers"
diff --git a/launcher/ui/dialogs/AboutDialog.ui b/launcher/ui/dialogs/AboutDialog.ui
index 70c5009d..6323992b 100644
--- a/launcher/ui/dialogs/AboutDialog.ui
+++ b/launcher/ui/dialogs/AboutDialog.ui
@@ -89,9 +89,15 @@
</item>
<item>
<widget class="QLabel" name="versionLabel">
+ <property name="cursor">
+ <cursorShape>IBeamCursor</cursorShape>
+ </property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
+ <property name="textInteractionFlags">
+ <set>Qt::TextSelectableByMouse</set>
+ </property>
</widget>
</item>
<item>
@@ -133,6 +139,9 @@
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
+ </property>
</widget>
</item>
<item>
@@ -160,32 +169,50 @@
</item>
<item>
<widget class="QLabel" name="platformLabel">
+ <property name="cursor">
+ <cursorShape>IBeamCursor</cursorShape>
+ </property>
<property name="text">
<string>Platform:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
+ <property name="textInteractionFlags">
+ <set>Qt::TextSelectableByMouse</set>
+ </property>
</widget>
</item>
<item>
<widget class="QLabel" name="buildNumLabel">
+ <property name="cursor">
+ <cursorShape>IBeamCursor</cursorShape>
+ </property>
<property name="text">
<string>Build Number:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
+ <property name="textInteractionFlags">
+ <set>Qt::TextSelectableByMouse</set>
+ </property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelLabel">
+ <property name="cursor">
+ <cursorShape>IBeamCursor</cursorShape>
+ </property>
<property name="text">
<string>Channel:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
+ <property name="textInteractionFlags">
+ <set>Qt::TextSelectableByMouse</set>
+ </property>
</widget>
</item>
<item>
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index e5113981..9ec341bc 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QLayout>
@@ -39,8 +59,14 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
ui->instNameTextBox->setText(original->name());
ui->instNameTextBox->setFocus();
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ auto groupList = APPLICATION->instances()->getGroups();
+ QSet<QString> groups(groupList.begin(), groupList.end());
+ groupList = QStringList(groups.values());
+#else
auto groups = APPLICATION->instances()->getGroups().toSet();
auto groupList = QStringList(groups.toList());
+#endif
groupList.sort(Qt::CaseInsensitive);
groupList.removeOne("");
groupList.push_front("");
diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp
index 8631edf6..9f32dd8e 100644
--- a/launcher/ui/dialogs/ExportInstanceDialog.cpp
+++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp
@@ -488,7 +488,11 @@ void ExportInstanceDialog::loadPackIgnore()
}
auto data = ignoreFile.readAll();
auto string = QString::fromUtf8(data);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ proxyModel->setBlockedPaths(string.split('\n', Qt::SkipEmptyParts));
+#else
proxyModel->setBlockedPaths(string.split('\n', QString::SkipEmptyParts));
+#endif
}
void ExportInstanceDialog::savePackIgnore()
diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp
index 305e85c0..f01c9c07 100644
--- a/launcher/ui/dialogs/ModDownloadDialog.cpp
+++ b/launcher/ui/dialogs/ModDownloadDialog.cpp
@@ -77,18 +77,20 @@ void ModDownloadDialog::confirm()
auto keys = modTask.keys();
keys.sort(Qt::CaseInsensitive);
- auto confirm_dialog = ReviewMessageBox::create(
- this,
- tr("Confirm mods to download")
- );
+ auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm mods to download"));
- for(auto& task : keys){
- confirm_dialog->appendMod(task, modTask.find(task).value()->getFilename());
+ for (auto& task : keys) {
+ confirm_dialog->appendMod({ task, modTask.find(task).value()->getFilename() });
}
- connect(confirm_dialog, &QDialog::accepted, this, &ModDownloadDialog::accept);
+ if (confirm_dialog->exec()) {
+ auto deselected = confirm_dialog->deselectedMods();
+ for (auto name : deselected) {
+ modTask.remove(name);
+ }
- confirm_dialog->open();
+ this->accept();
+ }
}
void ModDownloadDialog::accept()
@@ -132,6 +134,12 @@ bool ModDownloadDialog::isModSelected(const QString &name, const QString& filena
return iter != modTask.end() && (iter.value()->getFilename() == filename);
}
+bool ModDownloadDialog::isModSelected(const QString &name) const
+{
+ auto iter = modTask.find(name);
+ return iter != modTask.end();
+}
+
ModDownloadDialog::~ModDownloadDialog()
{
}
diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h
index 782dc361..5c565ad3 100644
--- a/launcher/ui/dialogs/ModDownloadDialog.h
+++ b/launcher/ui/dialogs/ModDownloadDialog.h
@@ -32,6 +32,7 @@ public:
void addSelectedMod(const QString & name = QString(), ModDownloadTask * task = nullptr);
void removeSelectedMod(const QString & name = QString());
bool isModSelected(const QString & name, const QString & filename) const;
+ bool isModSelected(const QString & name) const;
const QList<ModDownloadTask*> getTasks();
const std::shared_ptr<ModFolderModel> &mods;
@@ -41,8 +42,6 @@ public slots:
void accept() override;
void reject() override;
-//private slots:
-
private:
Ui::ModDownloadDialog *ui = nullptr;
PageContainer * m_container = nullptr;
diff --git a/launcher/ui/dialogs/NewComponentDialog.cpp b/launcher/ui/dialogs/NewComponentDialog.cpp
index 1bbafb0c..ea790e8c 100644
--- a/launcher/ui/dialogs/NewComponentDialog.cpp
+++ b/launcher/ui/dialogs/NewComponentDialog.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "Application.h"
@@ -46,7 +66,6 @@ NewComponentDialog::NewComponentDialog(const QString & initialName, const QStrin
connect(ui->nameTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState);
connect(ui->uidTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState);
- auto groups = APPLICATION->instances()->getGroups().toSet();
ui->nameTextBox->setFocus();
originalPlaceholderText = ui->uidTextBox->placeholderText();
diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp
index 05ea091d..5b8ecc5b 100644
--- a/launcher/ui/dialogs/NewInstanceDialog.cpp
+++ b/launcher/ui/dialogs/NewInstanceDialog.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "Application.h"
@@ -54,8 +74,14 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString
InstIconKey = "default";
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ auto groupList = APPLICATION->instances()->getGroups();
+ auto groups = QSet<QString>(groupList.begin(), groupList.end());
+ groupList = groups.values();
+#else
auto groups = APPLICATION->instances()->getGroups().toSet();
auto groupList = QStringList(groups.toList());
+#endif
groupList.sort(Qt::CaseInsensitive);
groupList.removeOne("");
groupList.push_front(initialGroup);
diff --git a/launcher/ui/dialogs/NewsDialog.cpp b/launcher/ui/dialogs/NewsDialog.cpp
new file mode 100644
index 00000000..d3b21627
--- /dev/null
+++ b/launcher/ui/dialogs/NewsDialog.cpp
@@ -0,0 +1,49 @@
+#include "NewsDialog.h"
+#include "ui_NewsDialog.h"
+
+NewsDialog::NewsDialog(QList<NewsEntryPtr> entries, QWidget* parent) : QDialog(parent), ui(new Ui::NewsDialog())
+{
+ ui->setupUi(this);
+
+ for (auto entry : entries) {
+ ui->articleListWidget->addItem(entry->title);
+ m_entries.insert(entry->title, entry);
+ }
+
+ connect(ui->articleListWidget, &QListWidget::currentTextChanged, this, &NewsDialog::selectedArticleChanged);
+ connect(ui->toggleListButton, &QPushButton::clicked, this, &NewsDialog::toggleArticleList);
+
+ m_article_list_hidden = ui->articleListWidget->isHidden();
+
+ auto first_item = ui->articleListWidget->item(0);
+ first_item->setSelected(true);
+
+ auto article_entry = m_entries.constFind(first_item->text()).value();
+ ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, first_item->text()));
+ ui->currentArticleContentBrowser->setText(article_entry->content);
+}
+
+NewsDialog::~NewsDialog()
+{
+ delete ui;
+}
+
+void NewsDialog::selectedArticleChanged(const QString& new_title)
+{
+ auto const& article_entry = m_entries.constFind(new_title).value();
+
+ ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, new_title));
+ ui->currentArticleContentBrowser->setText(article_entry->content);
+}
+
+void NewsDialog::toggleArticleList()
+{
+ m_article_list_hidden = !m_article_list_hidden;
+
+ ui->articleListWidget->setHidden(m_article_list_hidden);
+
+ if (m_article_list_hidden)
+ ui->toggleListButton->setText(tr("Show article list"));
+ else
+ ui->toggleListButton->setText(tr("Hide article list"));
+}
diff --git a/launcher/ui/dialogs/NewsDialog.h b/launcher/ui/dialogs/NewsDialog.h
new file mode 100644
index 00000000..add6b8dd
--- /dev/null
+++ b/launcher/ui/dialogs/NewsDialog.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <QDialog>
+#include <QHash>
+
+#include "news/NewsEntry.h"
+
+namespace Ui {
+class NewsDialog;
+}
+
+class NewsDialog : public QDialog {
+ Q_OBJECT
+
+ public:
+ NewsDialog(QList<NewsEntryPtr> entries, QWidget* parent = nullptr);
+ ~NewsDialog();
+
+ public slots:
+ void toggleArticleList();
+
+ private slots:
+ void selectedArticleChanged(const QString& new_title);
+
+ private:
+ Ui::NewsDialog* ui;
+
+ QHash<QString, NewsEntryPtr> m_entries;
+ bool m_article_list_hidden = false;
+};
diff --git a/launcher/ui/dialogs/NewsDialog.ui b/launcher/ui/dialogs/NewsDialog.ui
new file mode 100644
index 00000000..2aaa08f1
--- /dev/null
+++ b/launcher/ui/dialogs/NewsDialog.ui
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>NewsDialog</class>
+ <widget class="QDialog" name="NewsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>500</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>News</string>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="leftVerticalLayout">
+ <item>
+ <widget class="QListWidget" name="articleListWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="rightVerticalLayout">
+ <item>
+ <widget class="QLabel" name="articleTitleLabel">
+ <property name="text">
+ <string notr="true">Placeholder</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTextBrowser" name="currentArticleContentBrowser">
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QPushButton" name="closeButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>10</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QPushButton" name="toggleListButton">
+ <property name="text">
+ <string>Hide article list</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>closeButton</sender>
+ <signal>clicked()</signal>
+ <receiver>NewsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>199</x>
+ <y>277</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>199</x>
+ <y>149</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/launcher/ui/dialogs/ProfileSetupDialog.cpp b/launcher/ui/dialogs/ProfileSetupDialog.cpp
index 76b6af49..64c0b924 100644
--- a/launcher/ui/dialogs/ProfileSetupDialog.cpp
+++ b/launcher/ui/dialogs/ProfileSetupDialog.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "ProfileSetupDialog.h"
@@ -18,7 +38,7 @@
#include <QPushButton>
#include <QAction>
-#include <QRegExpValidator>
+#include <QRegularExpressionValidator>
#include <QJsonDocument>
#include <QDebug>
@@ -39,9 +59,9 @@ ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidg
yellowIcon = APPLICATION->getThemedIcon("status-yellow");
badIcon = APPLICATION->getThemedIcon("status-bad");
- QRegExp permittedNames("[a-zA-Z0-9_]{3,16}");
+ QRegularExpression permittedNames("[a-zA-Z0-9_]{3,16}");
auto nameEdit = ui->nameEdit;
- nameEdit->setValidator(new QRegExpValidator(permittedNames));
+ nameEdit->setValidator(new QRegularExpressionValidator(permittedNames));
nameEdit->setClearButtonEnabled(true);
validityAction = nameEdit->addAction(yellowIcon, QLineEdit::LeadingPosition);
connect(nameEdit, &QLineEdit::textEdited, this, &ProfileSetupDialog::nameEdited);
diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp
index 648bd88b..e5226016 100644
--- a/launcher/ui/dialogs/ProgressDialog.cpp
+++ b/launcher/ui/dialogs/ProgressDialog.cpp
@@ -16,12 +16,12 @@
#include "ProgressDialog.h"
#include "ui_ProgressDialog.h"
-#include <QKeyEvent>
#include <QDebug>
+#include <QKeyEvent>
#include "tasks/Task.h"
-ProgressDialog::ProgressDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ProgressDialog)
+ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ProgressDialog)
{
ui->setupUi(this);
this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
@@ -44,6 +44,7 @@ void ProgressDialog::on_skipButton_clicked(bool checked)
{
Q_UNUSED(checked);
task->abort();
+ QDialog::reject();
}
ProgressDialog::~ProgressDialog()
@@ -53,24 +54,22 @@ ProgressDialog::~ProgressDialog()
void ProgressDialog::updateSize()
{
- QSize qSize = QSize(480, minimumSizeHint().height());
+ QSize qSize = QSize(480, minimumSizeHint().height());
resize(qSize);
- setFixedSize(qSize);
+ setFixedSize(qSize);
}
-int ProgressDialog::execWithTask(Task *task)
+int ProgressDialog::execWithTask(Task* task)
{
this->task = task;
QDialog::DialogCode result;
- if(!task)
- {
+ if (!task) {
qDebug() << "Programmer error: progress dialog created with null task.";
return Accepted;
}
- if(handleImmediateResult(result))
- {
+ if (handleImmediateResult(result)) {
return result;
}
@@ -78,58 +77,51 @@ int ProgressDialog::execWithTask(Task *task)
connect(task, SIGNAL(started()), SLOT(onTaskStarted()));
connect(task, SIGNAL(failed(QString)), SLOT(onTaskFailed(QString)));
connect(task, SIGNAL(succeeded()), SLOT(onTaskSucceeded()));
- connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString &)));
+ connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString&)));
+ connect(task, SIGNAL(stepStatus(QString)), SLOT(changeStatus(const QString&)));
connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64)));
+ connect(task, &Task::aborted, [this] { onTaskFailed(tr("Aborted by user")); });
+
m_is_multi_step = task->isMultiStep();
- if(!m_is_multi_step){
+ if (!m_is_multi_step) {
ui->globalStatusLabel->setHidden(true);
ui->globalProgressBar->setHidden(true);
}
// if this didn't connect to an already running task, invoke start
- if(!task->isRunning())
- {
+ if (!task->isRunning()) {
task->start();
}
- if(task->isRunning())
- {
+ if (task->isRunning()) {
changeProgress(task->getProgress(), task->getTotalProgress());
changeStatus(task->getStatus());
return QDialog::exec();
- }
- else if(handleImmediateResult(result))
- {
+ } else if (handleImmediateResult(result)) {
return result;
- }
- else
- {
+ } else {
return QDialog::Rejected;
}
}
// TODO: only provide the unique_ptr overloads
-int ProgressDialog::execWithTask(std::unique_ptr<Task> &&task)
+int ProgressDialog::execWithTask(std::unique_ptr<Task>&& task)
{
connect(this, &ProgressDialog::destroyed, task.get(), &Task::deleteLater);
return execWithTask(task.release());
}
-int ProgressDialog::execWithTask(std::unique_ptr<Task> &task)
+int ProgressDialog::execWithTask(std::unique_ptr<Task>& task)
{
connect(this, &ProgressDialog::destroyed, task.get(), &Task::deleteLater);
return execWithTask(task.release());
}
-bool ProgressDialog::handleImmediateResult(QDialog::DialogCode &result)
+bool ProgressDialog::handleImmediateResult(QDialog::DialogCode& result)
{
- if(task->isFinished())
- {
- if(task->wasSuccessful())
- {
+ if (task->isFinished()) {
+ if (task->wasSuccessful()) {
result = QDialog::Accepted;
- }
- else
- {
+ } else {
result = QDialog::Rejected;
}
return true;
@@ -137,14 +129,12 @@ bool ProgressDialog::handleImmediateResult(QDialog::DialogCode &result)
return false;
}
-Task *ProgressDialog::getTask()
+Task* ProgressDialog::getTask()
{
return task;
}
-void ProgressDialog::onTaskStarted()
-{
-}
+void ProgressDialog::onTaskStarted() {}
void ProgressDialog::onTaskFailed(QString failure)
{
@@ -156,10 +146,11 @@ void ProgressDialog::onTaskSucceeded()
accept();
}
-void ProgressDialog::changeStatus(const QString &status)
+void ProgressDialog::changeStatus(const QString& status)
{
+ ui->globalStatusLabel->setText(task->getStatus());
ui->statusLabel->setText(task->getStepStatus());
- ui->globalStatusLabel->setText(status);
+
updateSize();
}
@@ -168,27 +159,22 @@ void ProgressDialog::changeProgress(qint64 current, qint64 total)
ui->globalProgressBar->setMaximum(total);
ui->globalProgressBar->setValue(current);
- if(!m_is_multi_step){
+ if (!m_is_multi_step) {
ui->taskProgressBar->setMaximum(total);
ui->taskProgressBar->setValue(current);
- }
- else{
+ } else {
ui->taskProgressBar->setMaximum(task->getStepProgress());
ui->taskProgressBar->setValue(task->getStepTotalProgress());
}
}
-void ProgressDialog::keyPressEvent(QKeyEvent *e)
+void ProgressDialog::keyPressEvent(QKeyEvent* e)
{
- if(ui->skipButton->isVisible())
- {
- if (e->key() == Qt::Key_Escape)
- {
+ if (ui->skipButton->isVisible()) {
+ if (e->key() == Qt::Key_Escape) {
on_skipButton_clicked(true);
return;
- }
- else if(e->key() == Qt::Key_Tab)
- {
+ } else if (e->key() == Qt::Key_Tab) {
ui->skipButton->setFocusPolicy(Qt::StrongFocus);
ui->skipButton->setFocus();
ui->skipButton->setAutoDefault(true);
@@ -199,14 +185,11 @@ void ProgressDialog::keyPressEvent(QKeyEvent *e)
QDialog::keyPressEvent(e);
}
-void ProgressDialog::closeEvent(QCloseEvent *e)
+void ProgressDialog::closeEvent(QCloseEvent* e)
{
- if (task && task->isRunning())
- {
+ if (task && task->isRunning()) {
e->ignore();
- }
- else
- {
+ } else {
QDialog::closeEvent(e);
}
}
diff --git a/launcher/ui/dialogs/ProgressDialog.ui b/launcher/ui/dialogs/ProgressDialog.ui
index bf119a78..34ab71e3 100644
--- a/launcher/ui/dialogs/ProgressDialog.ui
+++ b/launcher/ui/dialogs/ProgressDialog.ui
@@ -40,6 +40,12 @@
</item>
<item row="2" column="0">
<widget class="QLabel" name="statusLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<property name="text">
<string>Task Status...</string>
</property>
diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp
index 2bfd02e0..c92234a4 100644
--- a/launcher/ui/dialogs/ReviewMessageBox.cpp
+++ b/launcher/ui/dialogs/ReviewMessageBox.cpp
@@ -5,6 +5,9 @@ ReviewMessageBox::ReviewMessageBox(QWidget* parent, QString const& title, QStrin
: QDialog(parent), ui(new Ui::ReviewMessageBox)
{
ui->setupUi(this);
+
+ connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &ReviewMessageBox::accept);
+ connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ReviewMessageBox::reject);
}
ReviewMessageBox::~ReviewMessageBox()
@@ -17,15 +20,33 @@ auto ReviewMessageBox::create(QWidget* parent, QString&& title, QString&& icon)
return new ReviewMessageBox(parent, title, icon);
}
-void ReviewMessageBox::appendMod(const QString& name, const QString& filename)
+void ReviewMessageBox::appendMod(ModInformation&& info)
{
auto itemTop = new QTreeWidgetItem(ui->modTreeWidget);
- itemTop->setText(0, name);
+ itemTop->setCheckState(0, Qt::CheckState::Checked);
+ itemTop->setText(0, info.name);
auto filenameItem = new QTreeWidgetItem(itemTop);
- filenameItem->setText(0, tr("Filename: %1").arg(filename));
+ filenameItem->setText(0, tr("Filename: %1").arg(info.filename));
itemTop->insertChildren(0, { filenameItem });
ui->modTreeWidget->addTopLevelItem(itemTop);
}
+
+auto ReviewMessageBox::deselectedMods() -> QStringList
+{
+ QStringList list;
+
+ auto* item = ui->modTreeWidget->topLevelItem(0);
+
+ for (int i = 0; item != nullptr; ++i) {
+ if (item->checkState(0) == Qt::CheckState::Unchecked) {
+ list.append(item->text(0));
+ }
+
+ item = ui->modTreeWidget->topLevelItem(i);
+ }
+
+ return list;
+}
diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h
index 48742cd9..9cfa679a 100644
--- a/launcher/ui/dialogs/ReviewMessageBox.h
+++ b/launcher/ui/dialogs/ReviewMessageBox.h
@@ -6,17 +6,23 @@ namespace Ui {
class ReviewMessageBox;
}
-class ReviewMessageBox final : public QDialog {
+class ReviewMessageBox : public QDialog {
Q_OBJECT
public:
static auto create(QWidget* parent, QString&& title, QString&& icon = "") -> ReviewMessageBox*;
- void appendMod(const QString& name, const QString& filename);
+ using ModInformation = struct {
+ QString name;
+ QString filename;
+ };
+
+ void appendMod(ModInformation&& info);
+ auto deselectedMods() -> QStringList;
~ReviewMessageBox();
- private:
+ protected:
ReviewMessageBox(QWidget* parent, const QString& title, const QString& icon);
Ui::ReviewMessageBox* ui;
diff --git a/launcher/ui/dialogs/ReviewMessageBox.ui b/launcher/ui/dialogs/ReviewMessageBox.ui
index d04f3b3f..ab3bcc2f 100644
--- a/launcher/ui/dialogs/ReviewMessageBox.ui
+++ b/launcher/ui/dialogs/ReviewMessageBox.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>400</width>
- <height>300</height>
+ <width>500</width>
+ <height>350</height>
</rect>
</property>
<property name="windowTitle">
@@ -20,24 +20,7 @@
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>You're about to download the following mods:</string>
- </property>
- </widget>
- </item>
<item row="2" column="0">
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
<widget class="QTreeWidget" name="modTreeWidget">
<property name="alternatingRowColors">
<bool>true</bool>
@@ -58,41 +41,33 @@
</column>
</widget>
</item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="explainLabel">
+ <property name="text">
+ <string>You're about to download the following mods:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0" rowspan="2">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="onlyCheckedLabel">
+ <property name="text">
+ <string>Only mods with a check will be downloaded!</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
<resources/>
- <connections>
- <connection>
- <sender>buttonBox</sender>
- <signal>accepted()</signal>
- <receiver>ReviewMessageBox</receiver>
- <slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>200</x>
- <y>265</y>
- </hint>
- <hint type="destinationlabel">
- <x>199</x>
- <y>149</y>
- </hint>
- </hints>
- </connection>
- <connection>
- <sender>buttonBox</sender>
- <signal>rejected()</signal>
- <receiver>ReviewMessageBox</receiver>
- <slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>200</x>
- <y>265</y>
- </hint>
- <hint type="destinationlabel">
- <x>199</x>
- <y>149</y>
- </hint>
- </hints>
- </connection>
- </connections>
+ <connections/>
</ui>
diff --git a/launcher/ui/dialogs/ScrollMessageBox.cpp b/launcher/ui/dialogs/ScrollMessageBox.cpp
new file mode 100644
index 00000000..afdc4bae
--- /dev/null
+++ b/launcher/ui/dialogs/ScrollMessageBox.cpp
@@ -0,0 +1,15 @@
+#include "ScrollMessageBox.h"
+#include "ui_ScrollMessageBox.h"
+
+
+ScrollMessageBox::ScrollMessageBox(QWidget *parent, const QString &title, const QString &text, const QString &body) :
+ QDialog(parent), ui(new Ui::ScrollMessageBox) {
+ ui->setupUi(this);
+ this->setWindowTitle(title);
+ ui->label->setText(text);
+ ui->textBrowser->setText(body);
+}
+
+ScrollMessageBox::~ScrollMessageBox() {
+ delete ui;
+}
diff --git a/launcher/ui/dialogs/ScrollMessageBox.h b/launcher/ui/dialogs/ScrollMessageBox.h
new file mode 100644
index 00000000..84aa253a
--- /dev/null
+++ b/launcher/ui/dialogs/ScrollMessageBox.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <QDialog>
+
+
+QT_BEGIN_NAMESPACE
+namespace Ui { class ScrollMessageBox; }
+QT_END_NAMESPACE
+
+class ScrollMessageBox : public QDialog {
+Q_OBJECT
+
+public:
+ ScrollMessageBox(QWidget *parent, const QString &title, const QString &text, const QString &body);
+
+ ~ScrollMessageBox() override;
+
+private:
+ Ui::ScrollMessageBox *ui;
+};
diff --git a/launcher/ui/dialogs/ScrollMessageBox.ui b/launcher/ui/dialogs/ScrollMessageBox.ui
new file mode 100644
index 00000000..299d2ecc
--- /dev/null
+++ b/launcher/ui/dialogs/ScrollMessageBox.ui
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ScrollMessageBox</class>
+ <widget class="QDialog" name="ScrollMessageBox">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>455</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string notr="true">ScrollMessageBox</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QTextBrowser" name="textBrowser">
+ <property name="acceptRichText">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ScrollMessageBox</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>199</x>
+ <y>425</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>199</x>
+ <y>227</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ScrollMessageBox</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>199</x>
+ <y>425</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>199</x>
+ <y>227</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/launcher/ui/dialogs/SkinUploadDialog.cpp b/launcher/ui/dialogs/SkinUploadDialog.cpp
index 8d137afc..b5b78690 100644
--- a/launcher/ui/dialogs/SkinUploadDialog.cpp
+++ b/launcher/ui/dialogs/SkinUploadDialog.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QFileInfo>
#include <QFileDialog>
#include <QPainter>
@@ -22,10 +57,10 @@ void SkinUploadDialog::on_buttonBox_accepted()
{
QString fileName;
QString input = ui->skinPathTextBox->text();
- QRegExp urlPrefixMatcher("^([a-z]+)://.+$");
+ QRegularExpression urlPrefixMatcher(QRegularExpression::anchoredPattern("^([a-z]+)://.+$"));
bool isLocalFile = false;
// it has an URL prefix -> it is an URL
- if(urlPrefixMatcher.exactMatch(input))
+ if(urlPrefixMatcher.match(input).hasMatch())
{
QUrl fileURL = input;
if(fileURL.isValid())
diff --git a/launcher/ui/dialogs/UpdateDialog.cpp b/launcher/ui/dialogs/UpdateDialog.cpp
index ec77d146..e0c5a495 100644
--- a/launcher/ui/dialogs/UpdateDialog.cpp
+++ b/launcher/ui/dialogs/UpdateDialog.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "UpdateDialog.h"
#include "ui_UpdateDialog.h"
#include <QDebug>
@@ -58,7 +93,7 @@ QString reprocessMarkdown(QByteArray markdown)
QString output = hoedown.process(markdown);
// HACK: easier than customizing hoedown
- output.replace(QRegExp("GH-([0-9]+)"), "<a href=\"https://github.com/PolyMC/PolyMC/issues/\\1\">GH-\\1</a>");
+ output.replace(QRegularExpression("GH-([0-9]+)"), "<a href=\"https://github.com/PolyMC/PolyMC/issues/\\1\">GH-\\1</a>");
qDebug() << output;
return output;
}
diff --git a/launcher/ui/instanceview/InstanceDelegate.cpp b/launcher/ui/instanceview/InstanceDelegate.cpp
index b446e39d..137cc8d5 100644
--- a/launcher/ui/instanceview/InstanceDelegate.cpp
+++ b/launcher/ui/instanceview/InstanceDelegate.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "InstanceDelegate.h"
@@ -24,7 +44,7 @@
#include "InstanceView.h"
#include "BaseInstance.h"
#include "InstanceList.h"
-#include <xdgicon.h>
+#include <QIcon>
#include <QTextEdit>
// Origin: Qt
@@ -61,7 +81,7 @@ void drawSelectionRect(QPainter *painter, const QStyleOptionViewItem &option,
painter->fillRect(rect, option.palette.brush(QPalette::Highlight));
else
{
- QColor backgroundColor = option.palette.color(QPalette::Background);
+ QColor backgroundColor = option.palette.color(QPalette::Window);
backgroundColor.setAlpha(160);
painter->fillRect(rect, QBrush(backgroundColor));
}
@@ -142,7 +162,7 @@ void drawBadges(QPainter *painter, const QStyleOptionViewItem &option, BaseInsta
return;
}
// FIXME: inject this.
- auto icon = XdgIcon::fromTheme(it.next());
+ auto icon = QIcon::fromTheme(it.next());
// opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state);
const QPixmap pixmap;
// itemSide
diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp
index 25aec1ab..fbeffe35 100644
--- a/launcher/ui/instanceview/InstanceView.cpp
+++ b/launcher/ui/instanceview/InstanceView.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "InstanceView.h"
@@ -425,7 +445,12 @@ void InstanceView::mouseReleaseEvent(QMouseEvent *event)
{
emit clicked(index);
}
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ QStyleOptionViewItem option;
+ initViewItemOption(&option);
+#else
QStyleOptionViewItem option = viewOptions();
+#endif
if (m_pressedAlreadySelected)
{
option.state |= QStyle::State_Selected;
@@ -461,7 +486,12 @@ void InstanceView::mouseDoubleClickEvent(QMouseEvent *event)
QPersistentModelIndex persistent = index;
emit doubleClicked(persistent);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ QStyleOptionViewItem option;
+ initViewItemOption(&option);
+#else
QStyleOptionViewItem option = viewOptions();
+#endif
if ((model()->flags(index) & Qt::ItemIsEnabled) && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this))
{
emit activated(index);
@@ -474,7 +504,12 @@ void InstanceView::paintEvent(QPaintEvent *event)
QPainter painter(this->viewport());
- QStyleOptionViewItem option(viewOptions());
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ QStyleOptionViewItem option;
+ initViewItemOption(&option);
+#else
+ QStyleOptionViewItem option = viewOptions();
+#endif
option.widget = this;
int wpWidth = viewport()->width();
@@ -528,9 +563,9 @@ void InstanceView::paintEvent(QPaintEvent *event)
#if 0
if (!m_lastDragPosition.isNull())
{
- QPair<Group *, int> pair = rowDropPos(m_lastDragPosition);
- Group *category = pair.first;
- int row = pair.second;
+ std::pair<VisualGroup *, VisualGroup::HitResults> pair = rowDropPos(m_lastDragPosition);
+ VisualGroup *category = pair.first;
+ VisualGroup::HitResults row = pair.second;
if (category)
{
int internalRow = row - category->firstItemIndex;
@@ -618,7 +653,7 @@ void InstanceView::dropEvent(QDropEvent *event)
{
if(event->possibleActions() & Qt::MoveAction)
{
- QPair<VisualGroup *, VisualGroup::HitResults> dropPos = rowDropPos(event->pos());
+ std::pair<VisualGroup *, VisualGroup::HitResults> dropPos = rowDropPos(event->pos());
const VisualGroup *group = dropPos.first;
auto hitresult = dropPos.second;
@@ -709,10 +744,18 @@ QRect InstanceView::geometryRect(const QModelIndex &index) const
int x = pos.first;
// int y = pos.second;
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ QStyleOptionViewItem option;
+ initViewItemOption(&option);
+#else
+ QStyleOptionViewItem option = viewOptions();
+#endif
+
QRect out;
out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + cat->rowTopOf(index));
out.setLeft(m_spacing + x * (itemWidth() + m_spacing));
- out.setSize(itemDelegate()->sizeHint(viewOptions(), index));
+ out.setSize(itemDelegate()->sizeHint(option, index));
geometryCache.insert(row, new QRect(out));
return out;
}
@@ -759,7 +802,12 @@ QPixmap InstanceView::renderToPixmap(const QModelIndexList &indices, QRect *r) c
QPixmap pixmap(r->size());
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ QStyleOptionViewItem option;
+ initViewItemOption(&option);
+#else
QStyleOptionViewItem option = viewOptions();
+#endif
option.state |= QStyle::State_Selected;
for (int j = 0; j < paintPairs.count(); ++j)
{
@@ -770,16 +818,16 @@ QPixmap InstanceView::renderToPixmap(const QModelIndexList &indices, QRect *r) c
return pixmap;
}
-QList<QPair<QRect, QModelIndex>> InstanceView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const
+QList<std::pair<QRect, QModelIndex>> InstanceView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const
{
Q_ASSERT(r);
QRect &rect = *r;
- QList<QPair<QRect, QModelIndex>> ret;
+ QList<std::pair<QRect, QModelIndex>> ret;
for (int i = 0; i < indices.count(); ++i)
{
const QModelIndex &index = indices.at(i);
const QRect current = geometryRect(index);
- ret += qMakePair(current, index);
+ ret += std::make_pair(current, index);
rect |= current;
}
return ret;
@@ -790,11 +838,11 @@ bool InstanceView::isDragEventAccepted(QDropEvent *event)
return true;
}
-QPair<VisualGroup *, VisualGroup::HitResults> InstanceView::rowDropPos(const QPoint &pos)
+std::pair<VisualGroup *, VisualGroup::HitResults> InstanceView::rowDropPos(const QPoint &pos)
{
VisualGroup::HitResults hitresult;
auto group = categoryAt(pos + offset(), hitresult);
- return qMakePair<VisualGroup*, int>(group, hitresult);
+ return std::make_pair(group, hitresult);
}
QPoint InstanceView::offset() const
diff --git a/launcher/ui/instanceview/InstanceView.h b/launcher/ui/instanceview/InstanceView.h
index 406362e6..ac338274 100644
--- a/launcher/ui/instanceview/InstanceView.h
+++ b/launcher/ui/instanceview/InstanceView.h
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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
@@ -143,11 +163,11 @@ private: /* methods */
int calculateItemsPerRow() const;
int verticalScrollToValue(const QModelIndex &index, const QRect &rect, QListView::ScrollHint hint) const;
QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const;
- QList<QPair<QRect, QModelIndex>> draggablePaintPairs(const QModelIndexList &indices, QRect *r) const;
+ QList<std::pair<QRect, QModelIndex>> draggablePaintPairs(const QModelIndexList &indices, QRect *r) const;
bool isDragEventAccepted(QDropEvent *event);
- QPair<VisualGroup *, VisualGroup::HitResults> rowDropPos(const QPoint &pos);
+ std::pair<VisualGroup *, VisualGroup::HitResults> rowDropPos(const QPoint &pos);
QPoint offset() const;
};
diff --git a/launcher/ui/instanceview/VisualGroup.cpp b/launcher/ui/instanceview/VisualGroup.cpp
index 8991fb2d..e6bca17d 100644
--- a/launcher/ui/instanceview/VisualGroup.cpp
+++ b/launcher/ui/instanceview/VisualGroup.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "VisualGroup.h"
@@ -55,7 +75,14 @@ void VisualGroup::update()
positionInRow = 0;
maxRowHeight = 0;
}
- auto itemHeight = view->itemDelegate()->sizeHint(view->viewOptions(), item).height();
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ QStyleOptionViewItem viewItemOption;
+ view->initViewItemOption(&viewItemOption);
+#else
+ QStyleOptionViewItem viewItemOption = view->viewOptions();
+#endif
+
+ auto itemHeight = view->itemDelegate()->sizeHint(viewItemOption, item).height();
if(itemHeight > maxRowHeight)
{
maxRowHeight = itemHeight;
diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp
index 287eb74f..b889e6f7 100644
--- a/launcher/ui/pages/global/APIPage.cpp
+++ b/launcher/ui/pages/global/APIPage.cpp
@@ -3,6 +3,7 @@
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (c) 2022 Lenny McLennington <lenny@sneed.church>
*
* 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
@@ -46,16 +47,43 @@
#include "settings/SettingsObject.h"
#include "tools/BaseProfiler.h"
#include "Application.h"
+#include "net/PasteUpload.h"
+#include "BuildConfig.h"
APIPage::APIPage(QWidget *parent) :
QWidget(parent),
ui(new Ui::APIPage)
{
+ // This is here so you can reorder the entries in the combobox without messing stuff up
+ int comboBoxEntries[] = {
+ PasteUpload::PasteType::Mclogs,
+ PasteUpload::PasteType::NullPointer,
+ PasteUpload::PasteType::PasteGG,
+ PasteUpload::PasteType::Hastebin
+ };
+
static QRegularExpression validUrlRegExp("https?://.+");
+
ui->setupUi(this);
- ui->urlChoices->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->urlChoices));
- ui->tabWidget->tabBar()->hide();\
+
+ for (auto pasteType : comboBoxEntries) {
+ ui->pasteTypeComboBox->addItem(PasteUpload::PasteTypes.at(pasteType).name, pasteType);
+ }
+
+ void (QComboBox::*currentIndexChangedSignal)(int) (&QComboBox::currentIndexChanged);
+ connect(ui->pasteTypeComboBox, currentIndexChangedSignal, this, &APIPage::updateBaseURLPlaceholder);
+ // This function needs to be called even when the ComboBox's index is still in its default state.
+ updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex());
+ ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry));
+
+ ui->metaURL->setPlaceholderText(BuildConfig.META_URL);
+ ui->userAgentLineEdit->setPlaceholderText(BuildConfig.USER_AGENT);
+
loadSettings();
+
+ resetBaseURLNote();
+ connect(ui->pasteTypeComboBox, currentIndexChangedSignal, this, &APIPage::updateBaseURLNote);
+ connect(ui->baseURLEntry, &QLineEdit::textEdited, this, &APIPage::resetBaseURLNote);
}
APIPage::~APIPage()
@@ -63,22 +91,85 @@ APIPage::~APIPage()
delete ui;
}
+void APIPage::resetBaseURLNote()
+{
+ ui->baseURLNote->hide();
+ baseURLPasteType = ui->pasteTypeComboBox->currentIndex();
+}
+
+void APIPage::updateBaseURLNote(int index)
+{
+ if (baseURLPasteType == index)
+ {
+ ui->baseURLNote->hide();
+ }
+ else if (!ui->baseURLEntry->text().isEmpty())
+ {
+ ui->baseURLNote->show();
+ }
+}
+
+void APIPage::updateBaseURLPlaceholder(int index)
+{
+ int pasteType = ui->pasteTypeComboBox->itemData(index).toInt();
+ QString pasteDefaultURL = PasteUpload::PasteTypes.at(pasteType).defaultBase;
+ ui->baseURLEntry->setPlaceholderText(pasteDefaultURL);
+}
+
void APIPage::loadSettings()
{
auto s = APPLICATION->settings();
- QString pastebinURL = s->get("PastebinURL").toString();
- ui->urlChoices->setCurrentText(pastebinURL);
+
+ int pasteType = s->get("PastebinType").toInt();
+ QString pastebinURL = s->get("PastebinCustomAPIBase").toString();
+
+ ui->baseURLEntry->setText(pastebinURL);
+ int pasteTypeIndex = ui->pasteTypeComboBox->findData(pasteType);
+ if (pasteTypeIndex == -1)
+ {
+ pasteTypeIndex = ui->pasteTypeComboBox->findData(PasteUpload::PasteType::Mclogs);
+ ui->baseURLEntry->clear();
+ }
+
+ ui->pasteTypeComboBox->setCurrentIndex(pasteTypeIndex);
+
QString msaClientID = s->get("MSAClientIDOverride").toString();
ui->msaClientID->setText(msaClientID);
+ QString metaURL = s->get("MetaURLOverride").toString();
+ ui->metaURL->setText(metaURL);
+ QString curseKey = s->get("CFKeyOverride").toString();
+ ui->curseKey->setText(curseKey);
+ QString customUserAgent = s->get("UserAgentOverride").toString();
+ ui->userAgentLineEdit->setText(customUserAgent);
}
void APIPage::applySettings()
{
auto s = APPLICATION->settings();
- QString pastebinURL = ui->urlChoices->currentText();
- s->set("PastebinURL", pastebinURL);
+
+ s->set("PastebinType", ui->pasteTypeComboBox->currentData().toInt());
+ s->set("PastebinCustomAPIBase", ui->baseURLEntry->text());
+
QString msaClientID = ui->msaClientID->text();
s->set("MSAClientIDOverride", msaClientID);
+ QUrl metaURL = ui->metaURL->text();
+ // Add required trailing slash
+ if (!metaURL.isEmpty() && !metaURL.path().endsWith('/'))
+ {
+ QString path = metaURL.path();
+ path.append('/');
+ metaURL.setPath(path);
+ }
+ // Don't allow HTTP, since meta is basically RCE with all the jar files.
+ if(!metaURL.isEmpty() && metaURL.scheme() == "http")
+ {
+ metaURL.setScheme("https");
+ }
+
+ s->set("MetaURLOverride", metaURL);
+ QString curseKey = ui->curseKey->text();
+ s->set("CFKeyOverride", curseKey);
+ s->set("UserAgentOverride", ui->userAgentLineEdit->text());
}
bool APIPage::apply()
diff --git a/launcher/ui/pages/global/APIPage.h b/launcher/ui/pages/global/APIPage.h
index 20356009..17e62ae7 100644
--- a/launcher/ui/pages/global/APIPage.h
+++ b/launcher/ui/pages/global/APIPage.h
@@ -3,6 +3,7 @@
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (c) 2022 Lenny McLennington <lenny@sneed.church>
*
* 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
@@ -73,6 +74,10 @@ public:
void retranslate() override;
private:
+ int baseURLPasteType;
+ void resetBaseURLNote();
+ void updateBaseURLNote(int index);
+ void updateBaseURLPlaceholder(int index);
void loadSettings();
void applySettings();
diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui
index acde9aef..5327771c 100644
--- a/launcher/ui/pages/global/APIPage.ui
+++ b/launcher/ui/pages/global/APIPage.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>491</width>
- <height>474</height>
+ <width>800</width>
+ <height>600</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -30,56 +30,91 @@
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
- <string notr="true">Tab 1</string>
+ <string notr="true">Services</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_paste">
<property name="title">
- <string>&amp;Pastebin URL</string>
+ <string>&amp;Pastebin Service</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
- <widget class="Line" name="line">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <widget class="QLabel" name="pasteServiceTypeLabel">
+ <property name="text">
+ <string>Paste Service &amp;Type</string>
+ </property>
+ <property name="buddy">
+ <cstring>pasteTypeComboBox</cstring>
</property>
</widget>
</item>
<item>
- <widget class="QLabel" name="label_2">
- <property name="font">
- <font>
- <pointsize>10</pointsize>
- </font>
+ <widget class="QComboBox" name="pasteTypeComboBox"/>
+ </item>
+ <item>
+ <widget class="QLabel" name="baseURLLabel">
+ <property name="text">
+ <string>Base &amp;URL</string>
+ </property>
+ <property name="buddy">
+ <cstring>baseURLEntry</cstring>
</property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="baseURLEntry">
+ <property name="placeholderText">
+ <string/>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="baseURLNote">
<property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Note: only input that starts with &lt;span style=&quot; font-weight:600;&quot;&gt;http://&lt;/span&gt; or &lt;span style=&quot; font-weight:600;&quot;&gt;https://&lt;/span&gt; will be accepted.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>Note: you probably want to change or clear the Base URL after changing the paste service type.</string>
</property>
- <property name="scaledContents">
- <bool>false</bool>
+ <property name="wordWrap">
+ <bool>true</bool>
</property>
</widget>
</item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_meta">
+ <property name="title">
+ <string>Meta&amp;data Server</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
<item>
- <widget class="QComboBox" name="urlChoices">
- <property name="editable">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>You can set this to a third-party metadata server to use patched libraries or other hacks.</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ <property name="wordWrap">
<bool>true</bool>
</property>
- <property name="insertPolicy">
- <enum>QComboBox::NoInsert</enum>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="metaURL">
+ <property name="placeholderText">
+ <string/>
</property>
- <item>
- <property name="text">
- <string notr="true">https://0x0.st</string>
- </property>
- </item>
</widget>
</item>
<item>
- <widget class="QLabel" name="label">
+ <widget class="QLabel" name="label_6">
<property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Here you can choose from a predefined list of paste services, or input the URL of a different paste service of your choice, provided it supports the same protocol as 0x0.st, that is POST a file parameter to the URL and return a link in the response body.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>Enter a custom URL for meta here.</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
@@ -96,19 +131,32 @@
</widget>
</item>
<item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>API Keys</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <item>
<widget class="QGroupBox" name="groupBox_msa">
<property name="title">
<string>&amp;Microsoft Authentication</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
- <widget class="Line" name="line_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Note: you probably don't need to set this if logging in via Microsoft Authentication already works.</string>
@@ -148,6 +196,51 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="groupBox_curse">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>&amp;CurseForge Core API</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Note: you probably don't need to set this if CurseForge already works.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>Enter a custom API Key for CurseForge here. </string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLineEdit" name="curseKey">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="placeholderText">
+ <string>(Default)</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -162,13 +255,55 @@
</item>
</layout>
</widget>
+ <widget class="QWidget" name="tab_3">
+ <attribute name="title">
+ <string>Miscellaneous</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QGroupBox" name="groupBox_ua">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>User Agent</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <item>
+ <widget class="QLineEdit" name="userAgentLineEdit"/>
+ </item>
+ <item>
+ <widget class="QLabel" name="userAgentLabel">
+ <property name="text">
+ <string>Enter a custom User Agent here. The special string $LAUNCHER_VER will be replaced with the version of the launcher.</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
</widget>
</item>
</layout>
</widget>
- <tabstops>
- <tabstop>tabWidget</tabstop>
- </tabstops>
<resources/>
<connections/>
</ui>
diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp
index 6e1e2183..a608771e 100644
--- a/launcher/ui/pages/global/AccountListPage.cpp
+++ b/launcher/ui/pages/global/AccountListPage.cpp
@@ -73,9 +73,11 @@ AccountListPage::AccountListPage(QWidget *parent)
m_accounts = APPLICATION->accounts();
ui->listView->setModel(m_accounts.get());
- ui->listView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
- ui->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch);
- ui->listView->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
+ ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::ProfileNameColumn, QHeaderView::Stretch);
+ ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::NameColumn, QHeaderView::Stretch);
+ ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::MigrationColumn, QHeaderView::ResizeToContents);
+ ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::TypeColumn, QHeaderView::ResizeToContents);
+ ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::StatusColumn, QHeaderView::ResizeToContents);
ui->listView->setSelectionMode(QAbstractItemView::SingleSelection);
// Expand the account column
@@ -253,19 +255,21 @@ void AccountListPage::updateButtonStates()
{
// If there is no selection, disable buttons that require something selected.
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
- bool hasSelection = selection.size() > 0;
+ bool hasSelection = !selection.empty();
bool accountIsReady = false;
+ bool accountIsOnline = false;
if (hasSelection)
{
QModelIndex selected = selection.first();
MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value<MinecraftAccountPtr>();
accountIsReady = !account->isActive();
+ accountIsOnline = !account->isOffline();
}
ui->actionRemove->setEnabled(accountIsReady);
ui->actionSetDefault->setEnabled(accountIsReady);
- ui->actionUploadSkin->setEnabled(accountIsReady);
- ui->actionDeleteSkin->setEnabled(accountIsReady);
- ui->actionRefresh->setEnabled(accountIsReady);
+ ui->actionUploadSkin->setEnabled(accountIsReady && accountIsOnline);
+ ui->actionDeleteSkin->setEnabled(accountIsReady && accountIsOnline);
+ ui->actionRefresh->setEnabled(accountIsReady && accountIsOnline);
if(m_accounts->defaultAccount().get() == nullptr) {
ui->actionNoDefault->setEnabled(false);
diff --git a/launcher/ui/pages/global/CustomCommandsPage.cpp b/launcher/ui/pages/global/CustomCommandsPage.cpp
index 436d766e..df1420ca 100644
--- a/launcher/ui/pages/global/CustomCommandsPage.cpp
+++ b/launcher/ui/pages/global/CustomCommandsPage.cpp
@@ -2,7 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp
index b5e8de6c..2cee15bf 100644
--- a/launcher/ui/pages/global/JavaPage.cpp
+++ b/launcher/ui/pages/global/JavaPage.cpp
@@ -95,7 +95,7 @@ void JavaPage::applySettings()
// Java Settings
s->set("JavaPath", ui->javaPathTextBox->text());
- s->set("JvmArgs", ui->jvmArgsTextBox->text());
+ s->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " "));
s->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked());
s->set("IgnoreJavaWizard", ui->skipJavaWizardCheckbox->isChecked());
JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget());
@@ -120,13 +120,18 @@ void JavaPage::loadSettings()
// Java Settings
ui->javaPathTextBox->setText(s->get("JavaPath").toString());
- ui->jvmArgsTextBox->setText(s->get("JvmArgs").toString());
+ ui->jvmArgsTextBox->setPlainText(s->get("JvmArgs").toString());
ui->skipCompatibilityCheckbox->setChecked(s->get("IgnoreJavaCompatibility").toBool());
ui->skipJavaWizardCheckbox->setChecked(s->get("IgnoreJavaWizard").toBool());
}
void JavaPage::on_javaDetectBtn_clicked()
{
+ if (JavaUtils::getJavaCheckPath().isEmpty()) {
+ JavaCommon::javaCheckNotFound(this);
+ return;
+ }
+
JavaInstallPtr java;
VersionSelectDialog vselect(APPLICATION->javalist().get(), tr("Select a Java version"), this, true);
@@ -166,7 +171,7 @@ void JavaPage::on_javaTestBtn_clicked()
return;
}
checker.reset(new JavaCommon::TestCheck(
- this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->text(),
+ this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->toPlainText().replace("\n", " "),
ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value()));
connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished()));
checker->run();
diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui
index 3e4b12a1..6ccffed4 100644
--- a/launcher/ui/pages/global/JavaPage.ui
+++ b/launcher/ui/pages/global/JavaPage.ui
@@ -150,19 +150,16 @@
<string>Java Runtime</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
- <item row="0" column="0">
- <widget class="QLabel" name="labelJavaPath">
+ <item row="3" column="1">
+ <widget class="QPushButton" name="javaDetectBtn">
<property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
- <string>&amp;Java path:</string>
- </property>
- <property name="buddy">
- <cstring>javaPathTextBox</cstring>
+ <string>&amp;Auto-detect...</string>
</property>
</widget>
</item>
@@ -175,31 +172,31 @@
</sizepolicy>
</property>
<property name="text">
- <string>J&amp;VM arguments:</string>
+ <string>JVM arguments:</string>
</property>
- <property name="buddy">
- <cstring>jvmArgsTextBox</cstring>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
- <item row="4" column="1">
- <widget class="QCheckBox" name="skipCompatibilityCheckbox">
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelJavaPath">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="toolTip">
- <string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
- </property>
<property name="text">
- <string>&amp;Skip Java compatibility checks</string>
+ <string>&amp;Java path:</string>
+ </property>
+ <property name="buddy">
+ <cstring>javaPathTextBox</cstring>
</property>
</widget>
</item>
- <item row="3" column="1">
- <widget class="QPushButton" name="javaDetectBtn">
+ <item row="3" column="2">
+ <widget class="QPushButton" name="javaTestBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -207,7 +204,7 @@
</sizepolicy>
</property>
<property name="text">
- <string>&amp;Auto-detect...</string>
+ <string>&amp;Test</string>
</property>
</widget>
</item>
@@ -237,22 +234,22 @@
</item>
</layout>
</item>
- <item row="3" column="2">
- <widget class="QPushButton" name="javaTestBtn">
+ <item row="4" column="1">
+ <widget class="QCheckBox" name="skipCompatibilityCheckbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
+ <property name="toolTip">
+ <string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
+ </property>
<property name="text">
- <string>&amp;Test</string>
+ <string>&amp;Skip Java compatibility checks</string>
</property>
</widget>
</item>
- <item row="2" column="1" colspan="2">
- <widget class="QLineEdit" name="jvmArgsTextBox"/>
- </item>
<item row="5" column="1">
<widget class="QCheckBox" name="skipJavaWizardCheckbox">
<property name="toolTip">
@@ -263,6 +260,25 @@
</property>
</widget>
</item>
+ <item row="2" column="1" colspan="2">
+ <widget class="QPlainTextEdit" name="jvmArgsTextBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>100</height>
+ </size>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -291,7 +307,6 @@
<tabstop>permGenSpinBox</tabstop>
<tabstop>javaBrowseBtn</tabstop>
<tabstop>javaPathTextBox</tabstop>
- <tabstop>jvmArgsTextBox</tabstop>
<tabstop>javaDetectBtn</tabstop>
<tabstop>javaTestBtn</tabstop>
<tabstop>tabWidget</tabstop>
diff --git a/launcher/ui/pages/global/LanguagePage.cpp b/launcher/ui/pages/global/LanguagePage.cpp
index 485d7fd4..fcd174bd 100644
--- a/launcher/ui/pages/global/LanguagePage.cpp
+++ b/launcher/ui/pages/global/LanguagePage.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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/global/LanguagePage.h b/launcher/ui/pages/global/LanguagePage.h
index 9b321170..2fd4ab0d 100644
--- a/launcher/ui/pages/global/LanguagePage.h
+++ b/launcher/ui/pages/global/LanguagePage.h
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp
index b244b039..73ef0024 100644
--- a/launcher/ui/pages/global/LauncherPage.cpp
+++ b/launcher/ui/pages/global/LauncherPage.cpp
@@ -191,6 +191,11 @@ void LauncherPage::on_modsDirBrowseBtn_clicked()
}
}
+void LauncherPage::on_metadataDisableBtn_clicked()
+{
+ ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
+}
+
void LauncherPage::refreshUpdateChannelList()
{
// Stop listening for selection changes. It's going to change a lot while we update it and
@@ -354,6 +359,9 @@ void LauncherPage::applySettings()
s->set("InstSortMode", "Name");
break;
}
+
+ // Mods
+ s->set("ModMetadataDisabled", ui->metadataDisableBtn->isChecked());
}
void LauncherPage::loadSettings()
{
@@ -465,6 +473,10 @@ void LauncherPage::loadSettings()
{
ui->sortByNameBtn->setChecked(true);
}
+
+ // Mods
+ ui->metadataDisableBtn->setChecked(s->get("ModMetadataDisabled").toBool());
+ ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
}
void LauncherPage::refreshFontPreview()
diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h
index bbf5d2fe..f38c922e 100644
--- a/launcher/ui/pages/global/LauncherPage.h
+++ b/launcher/ui/pages/global/LauncherPage.h
@@ -88,6 +88,7 @@ slots:
void on_instDirBrowseBtn_clicked();
void on_modsDirBrowseBtn_clicked();
void on_iconsDirBrowseBtn_clicked();
+ void on_metadataDisableBtn_clicked();
/*!
* Updates the list of update channels in the combo box.
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index a306a91b..645f7ef6 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -94,19 +94,13 @@
<string>Folders</string>
</property>
<layout class="QGridLayout" name="foldersBoxLayout">
- <item row="0" column="0">
- <widget class="QLabel" name="labelInstDir">
+ <item row="1" column="2">
+ <widget class="QToolButton" name="modsDirBrowseBtn">
<property name="text">
- <string>I&amp;nstances:</string>
- </property>
- <property name="buddy">
- <cstring>instDirTextBox</cstring>
+ <string notr="true">...</string>
</property>
</widget>
</item>
- <item row="0" column="1">
- <widget class="QLineEdit" name="instDirTextBox"/>
- </item>
<item row="0" column="2">
<widget class="QToolButton" name="instDirBrowseBtn">
<property name="text">
@@ -114,43 +108,78 @@
</property>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QLabel" name="labelModsDir">
+ <item row="2" column="2">
+ <widget class="QToolButton" name="iconsDirBrowseBtn">
<property name="text">
- <string>&amp;Mods:</string>
+ <string notr="true">...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="instDirTextBox"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="labelIconsDir">
+ <property name="text">
+ <string>&amp;Icons:</string>
</property>
<property name="buddy">
- <cstring>modsDirTextBox</cstring>
+ <cstring>iconsDirTextBox</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="modsDirTextBox"/>
</item>
- <item row="1" column="2">
- <widget class="QToolButton" name="modsDirBrowseBtn">
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelInstDir">
<property name="text">
- <string notr="true">...</string>
+ <string>I&amp;nstances:</string>
+ </property>
+ <property name="buddy">
+ <cstring>instDirTextBox</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="iconsDirTextBox"/>
</item>
- <item row="2" column="0">
- <widget class="QLabel" name="labelIconsDir">
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelModsDir">
<property name="text">
- <string>&amp;Icons:</string>
+ <string>&amp;Mods:</string>
</property>
<property name="buddy">
- <cstring>iconsDirTextBox</cstring>
+ <cstring>modsDirTextBox</cstring>
</property>
</widget>
</item>
- <item row="2" column="2">
- <widget class="QToolButton" name="iconsDirBrowseBtn">
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="modsBox">
+ <property name="title">
+ <string>Mods</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QCheckBox" name="metadataDisableBtn">
+ <property name="toolTip">
+ <string>Disable using metadata provided by mod providers (like Modrinth or Curseforge) for mods.</string>
+ </property>
<property name="text">
- <string notr="true">...</string>
+ <string>Disable using metadata for mods?</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="metadataWarningLabel">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; color:#f5c211;&quot;&gt;Warning&lt;/span&gt;&lt;span style=&quot; color:#f5c211;&quot;&gt;: Disabling mod metadata may also disable some upcoming QoL features, such as mod updating!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
</property>
</widget>
</item>
diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp
index f49f5a92..e3ac7e7c 100644
--- a/launcher/ui/pages/global/MinecraftPage.cpp
+++ b/launcher/ui/pages/global/MinecraftPage.cpp
@@ -87,6 +87,11 @@ void MinecraftPage::applySettings()
s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
s->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked());
+ // Peformance related options
+ s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked());
+ s->set("EnableMangoHud", ui->enableMangoHud->isChecked());
+ s->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked());
+
// Game time
s->set("ShowGameTime", ui->showGameTime->isChecked());
s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked());
@@ -109,6 +114,14 @@ void MinecraftPage::loadSettings()
ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool());
ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").toBool());
+ ui->enableFeralGamemodeCheck->setChecked(s->get("EnableFeralGamemode").toBool());
+ ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool());
+ ui->useDiscreteGpuCheck->setChecked(s->get("UseDiscreteGpu").toBool());
+
+#if !defined(Q_OS_LINUX)
+ ui->perfomanceGroupBox->setVisible(false);
+#endif
+
ui->showGameTime->setChecked(s->get("ShowGameTime").toBool());
ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool());
ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool());
diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui
index 353390bd..640f436d 100644
--- a/launcher/ui/pages/global/MinecraftPage.ui
+++ b/launcher/ui/pages/global/MinecraftPage.ui
@@ -135,6 +135,45 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="perfomanceGroupBox">
+ <property name="title">
+ <string>Performance</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="enableFeralGamemodeCheck">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable Feral Interactive's GameMode, to potentially improve gaming performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Enable Feral GameMode</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="enableMangoHud">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable MangoHud's advanced performance overlay.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Enable MangoHud</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useDiscreteGpuCheck">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use the discrete GPU instead of the primary GPU.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Use discrete GPU</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<widget class="QGroupBox" name="gameTimeGroupBox">
<property name="title">
<string>Game time</string>
@@ -181,15 +220,15 @@
</widget>
</item>
<item>
- <widget class="QCheckBox" name="quitAfterGameStopCheck">
- <property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The launcher will automatically quit after the game exits or crashes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="text">
- <string>&amp;Quit the launcher after game window closes</string>
- </property>
- </widget>
- </item>
+ <widget class="QCheckBox" name="quitAfterGameStopCheck">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The launcher will automatically quit after the game exits or crashes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>&amp;Quit the launcher after game window closes</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -218,6 +257,9 @@
<tabstop>windowHeightSpinBox</tabstop>
<tabstop>useNativeGLFWCheck</tabstop>
<tabstop>useNativeOpenALCheck</tabstop>
+ <tabstop>enableFeralGamemodeCheck</tabstop>
+ <tabstop>enableMangoHud</tabstop>
+ <tabstop>useDiscreteGpuCheck</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/launcher/ui/pages/global/ProxyPage.cpp b/launcher/ui/pages/global/ProxyPage.cpp
index aefd1e74..ffff8456 100644
--- a/launcher/ui/pages/global/ProxyPage.cpp
+++ b/launcher/ui/pages/global/ProxyPage.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -36,11 +37,11 @@
#include "ProxyPage.h"
#include "ui_ProxyPage.h"
+#include <QButtonGroup>
#include <QTabBar>
#include "settings/SettingsObject.h"
#include "Application.h"
-#include "Application.h"
ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage)
{
@@ -49,7 +50,8 @@ ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage)
loadSettings();
updateCheckboxStuff();
- connect(ui->proxyGroup, SIGNAL(buttonClicked(int)), SLOT(proxyChanged(int)));
+ connect(ui->proxyGroup, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked),
+ this, &ProxyPage::proxyGroupChanged);
}
ProxyPage::~ProxyPage()
@@ -65,13 +67,13 @@ bool ProxyPage::apply()
void ProxyPage::updateCheckboxStuff()
{
- ui->proxyAddrBox->setEnabled(!ui->proxyNoneBtn->isChecked() &&
- !ui->proxyDefaultBtn->isChecked());
- ui->proxyAuthBox->setEnabled(!ui->proxyNoneBtn->isChecked() &&
- !ui->proxyDefaultBtn->isChecked());
+ bool enableEditing = ui->proxyHTTPBtn->isChecked()
+ || ui->proxySOCKS5Btn->isChecked();
+ ui->proxyAddrBox->setEnabled(enableEditing);
+ ui->proxyAuthBox->setEnabled(enableEditing);
}
-void ProxyPage::proxyChanged(int)
+void ProxyPage::proxyGroupChanged(QAbstractButton *button)
{
updateCheckboxStuff();
}
diff --git a/launcher/ui/pages/global/ProxyPage.h b/launcher/ui/pages/global/ProxyPage.h
index e3677774..279a9029 100644
--- a/launcher/ui/pages/global/ProxyPage.h
+++ b/launcher/ui/pages/global/ProxyPage.h
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -36,6 +37,7 @@
#pragma once
#include <memory>
+#include <QAbstractButton>
#include <QDialog>
#include "ui/pages/BasePage.h"
@@ -73,15 +75,14 @@ public:
bool apply() override;
void retranslate() override;
+private slots:
+ void proxyGroupChanged(QAbstractButton *button);
+
private:
void updateCheckboxStuff();
void applySettings();
void loadSettings();
-private
-slots:
- void proxyChanged(int);
-
private:
Ui::ProxyPage *ui;
};
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
new file mode 100644
index 00000000..d06f412b
--- /dev/null
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
@@ -0,0 +1,297 @@
+#include "ExternalResourcesPage.h"
+#include "ui_ExternalResourcesPage.h"
+
+#include "DesktopServices.h"
+#include "Version.h"
+#include "minecraft/mod/ModFolderModel.h"
+#include "ui/GuiUtil.h"
+
+#include <QKeyEvent>
+#include <QMenu>
+
+namespace {
+// FIXME: wasteful
+void RemoveThePrefix(QString& string)
+{
+ QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption);
+ string.remove(regex);
+ string = string.trimmed();
+}
+} // namespace
+
+class SortProxy : public QSortFilterProxyModel {
+ public:
+ explicit SortProxy(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {}
+
+ protected:
+ bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override
+ {
+ ModFolderModel* model = qobject_cast<ModFolderModel*>(sourceModel());
+ if (!model)
+ return false;
+
+ const auto& mod = model->at(source_row);
+
+ if (mod.name().contains(filterRegularExpression()))
+ return true;
+ if (mod.description().contains(filterRegularExpression()))
+ return true;
+
+ for (auto& author : mod.authors()) {
+ if (author.contains(filterRegularExpression())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override
+ {
+ ModFolderModel* model = qobject_cast<ModFolderModel*>(sourceModel());
+ if (!model || !source_left.isValid() || !source_right.isValid() || source_left.column() != source_right.column()) {
+ return QSortFilterProxyModel::lessThan(source_left, source_right);
+ }
+
+ // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and
+ // proceed.
+
+ auto column = (ModFolderModel::Columns) source_left.column();
+ bool invert = false;
+ switch (column) {
+ // GH-2550 - sort by enabled/disabled
+ case ModFolderModel::ActiveColumn: {
+ auto dataL = source_left.data(Qt::CheckStateRole).toBool();
+ auto dataR = source_right.data(Qt::CheckStateRole).toBool();
+ if (dataL != dataR)
+ return dataL > dataR;
+
+ // fallthrough
+ invert = sortOrder() == Qt::DescendingOrder;
+ }
+ // GH-2722 - sort mod names in a way that discards "The" prefixes
+ case ModFolderModel::NameColumn: {
+ auto dataL = model->data(model->index(source_left.row(), ModFolderModel::NameColumn)).toString();
+ RemoveThePrefix(dataL);
+ auto dataR = model->data(model->index(source_right.row(), ModFolderModel::NameColumn)).toString();
+ RemoveThePrefix(dataR);
+
+ auto less = dataL.compare(dataR, sortCaseSensitivity());
+ if (less != 0)
+ return invert ? (less > 0) : (less < 0);
+
+ // fallthrough
+ invert = sortOrder() == Qt::DescendingOrder;
+ }
+ // GH-2762 - sort versions by parsing them as versions
+ case ModFolderModel::VersionColumn: {
+ auto dataL = Version(model->data(model->index(source_left.row(), ModFolderModel::VersionColumn)).toString());
+ auto dataR = Version(model->data(model->index(source_right.row(), ModFolderModel::VersionColumn)).toString());
+ return invert ? (dataL > dataR) : (dataL < dataR);
+ }
+ default: {
+ return QSortFilterProxyModel::lessThan(source_left, source_right);
+ }
+ }
+ }
+};
+
+ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared_ptr<ModFolderModel> model, QWidget* parent)
+ : QMainWindow(parent), m_instance(instance), ui(new Ui::ExternalResourcesPage), m_model(model)
+{
+ ui->setupUi(this);
+
+ runningStateChanged(m_instance && m_instance->isRunning());
+
+ ui->actionsToolbar->insertSpacer(ui->actionViewConfigs);
+
+ m_filterModel = new SortProxy(this);
+ m_filterModel->setDynamicSortFilter(true);
+ m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+ m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
+ m_filterModel->setSourceModel(m_model.get());
+ m_filterModel->setFilterKeyColumn(-1);
+ ui->treeView->setModel(m_filterModel);
+
+ ui->treeView->installEventFilter(this);
+ ui->treeView->sortByColumn(1, Qt::AscendingOrder);
+ ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
+
+ // The default function names by Qt are pretty ugly, so let's just connect the actions manually,
+ // to make it easier to read :)
+ connect(ui->actionAddItem, &QAction::triggered, this, &ExternalResourcesPage::addItem);
+ connect(ui->actionRemoveItem, &QAction::triggered, this, &ExternalResourcesPage::removeItem);
+ connect(ui->actionEnableItem, &QAction::triggered, this, &ExternalResourcesPage::enableItem);
+ connect(ui->actionDisableItem, &QAction::triggered, this, &ExternalResourcesPage::disableItem);
+ connect(ui->actionViewConfigs, &QAction::triggered, this, &ExternalResourcesPage::viewConfigs);
+ connect(ui->actionViewFolder, &QAction::triggered, this, &ExternalResourcesPage::viewFolder);
+
+ connect(ui->treeView, &ModListView::customContextMenuRequested, this, &ExternalResourcesPage::ShowContextMenu);
+ connect(ui->treeView, &ModListView::activated, this, &ExternalResourcesPage::itemActivated);
+
+ auto selection_model = ui->treeView->selectionModel();
+ connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current);
+ connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged);
+ connect(m_instance, &BaseInstance::runningStatusChanged, this, &ExternalResourcesPage::runningStateChanged);
+}
+
+ExternalResourcesPage::~ExternalResourcesPage()
+{
+ m_model->stopWatching();
+ delete ui;
+}
+
+void ExternalResourcesPage::itemActivated(const QModelIndex&)
+{
+ if (!m_controlsEnabled)
+ return;
+
+ auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
+ m_model->setModStatus(selection.indexes(), ModFolderModel::Toggle);
+}
+
+QMenu* ExternalResourcesPage::createPopupMenu()
+{
+ QMenu* filteredMenu = QMainWindow::createPopupMenu();
+ filteredMenu->removeAction(ui->actionsToolbar->toggleViewAction());
+ return filteredMenu;
+}
+
+void ExternalResourcesPage::ShowContextMenu(const QPoint& pos)
+{
+ auto menu = ui->actionsToolbar->createContextMenu(this, tr("Context menu"));
+ menu->exec(ui->treeView->mapToGlobal(pos));
+ delete menu;
+}
+
+void ExternalResourcesPage::openedImpl()
+{
+ m_model->startWatching();
+}
+
+void ExternalResourcesPage::closedImpl()
+{
+ m_model->stopWatching();
+}
+
+void ExternalResourcesPage::retranslate()
+{
+ ui->retranslateUi(this);
+}
+
+void ExternalResourcesPage::filterTextChanged(const QString& newContents)
+{
+ m_viewFilter = newContents;
+ m_filterModel->setFilterFixedString(m_viewFilter);
+}
+
+void ExternalResourcesPage::runningStateChanged(bool running)
+{
+ if (m_controlsEnabled == !running)
+ return;
+
+ m_controlsEnabled = !running;
+ ui->actionAddItem->setEnabled(m_controlsEnabled);
+ ui->actionDisableItem->setEnabled(m_controlsEnabled);
+ ui->actionEnableItem->setEnabled(m_controlsEnabled);
+ ui->actionRemoveItem->setEnabled(m_controlsEnabled);
+}
+
+bool ExternalResourcesPage::shouldDisplay() const
+{
+ return true;
+}
+
+bool ExternalResourcesPage::listFilter(QKeyEvent* keyEvent)
+{
+ switch (keyEvent->key()) {
+ case Qt::Key_Delete:
+ removeItem();
+ return true;
+ case Qt::Key_Plus:
+ addItem();
+ return true;
+ default:
+ break;
+ }
+ return QWidget::eventFilter(ui->treeView, keyEvent);
+}
+
+bool ExternalResourcesPage::eventFilter(QObject* obj, QEvent* ev)
+{
+ if (ev->type() != QEvent::KeyPress)
+ return QWidget::eventFilter(obj, ev);
+
+ QKeyEvent* keyEvent = static_cast<QKeyEvent*>(ev);
+ if (obj == ui->treeView)
+ return listFilter(keyEvent);
+
+ return QWidget::eventFilter(obj, ev);
+}
+
+void ExternalResourcesPage::addItem()
+{
+ if (!m_controlsEnabled)
+ return;
+
+
+ auto list = GuiUtil::BrowseForFiles(
+ helpPage(), tr("Select %1", "Select whatever type of files the page contains. Example: 'Loader Mods'").arg(displayName()),
+ m_fileSelectionFilter.arg(displayName()), APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
+
+ if (!list.isEmpty()) {
+ for (auto filename : list) {
+ m_model->installMod(filename);
+ }
+ }
+}
+
+void ExternalResourcesPage::removeItem()
+{
+ if (!m_controlsEnabled)
+ return;
+
+ auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
+ m_model->deleteMods(selection.indexes());
+}
+
+void ExternalResourcesPage::enableItem()
+{
+ if (!m_controlsEnabled)
+ return;
+
+ auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
+ m_model->setModStatus(selection.indexes(), ModFolderModel::Enable);
+}
+
+void ExternalResourcesPage::disableItem()
+{
+ if (!m_controlsEnabled)
+ return;
+
+ auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
+ m_model->setModStatus(selection.indexes(), ModFolderModel::Disable);
+}
+
+void ExternalResourcesPage::viewConfigs()
+{
+ DesktopServices::openDirectory(m_instance->instanceConfigFolder(), true);
+}
+
+void ExternalResourcesPage::viewFolder()
+{
+ DesktopServices::openDirectory(m_model->dir().absolutePath(), true);
+}
+
+void ExternalResourcesPage::current(const QModelIndex& current, const QModelIndex& previous)
+{
+ if (!current.isValid()) {
+ ui->frame->clear();
+ return;
+ }
+
+ auto sourceCurrent = m_filterModel->mapToSource(current);
+ int row = sourceCurrent.row();
+ Mod& m = m_model->operator[](row);
+ ui->frame->updateWithMod(m);
+}
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h
new file mode 100644
index 00000000..41237139
--- /dev/null
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.h
@@ -0,0 +1,73 @@
+#pragma once
+
+#include <QMainWindow>
+#include <QSortFilterProxyModel>
+
+#include "Application.h"
+#include "minecraft/MinecraftInstance.h"
+#include "ui/pages/BasePage.h"
+
+class ModFolderModel;
+
+namespace Ui {
+class ExternalResourcesPage;
+}
+
+/* This page is used as a base for pages in which the user can manage external resources
+ * related to the game, such as mods, shaders or resource packs. */
+class ExternalResourcesPage : public QMainWindow, public BasePage {
+ Q_OBJECT
+
+ public:
+ // FIXME: Switch to different model (or change the name of this one)
+ explicit ExternalResourcesPage(BaseInstance* instance, std::shared_ptr<ModFolderModel> model, QWidget* parent = nullptr);
+ virtual ~ExternalResourcesPage();
+
+ virtual QString displayName() const override = 0;
+ virtual QIcon icon() const override = 0;
+ virtual QString id() const override = 0;
+ virtual QString helpPage() const override = 0;
+
+ virtual bool shouldDisplay() const override = 0;
+
+ void openedImpl() override;
+ void closedImpl() override;
+
+ void retranslate() override;
+
+ protected:
+ bool eventFilter(QObject* obj, QEvent* ev) override;
+ bool listFilter(QKeyEvent* ev);
+ QMenu* createPopupMenu() override;
+
+ public slots:
+ void current(const QModelIndex& current, const QModelIndex& previous);
+
+ protected slots:
+ void itemActivated(const QModelIndex& index);
+ void filterTextChanged(const QString& newContents);
+ void runningStateChanged(bool running);
+
+ virtual void addItem();
+ virtual void removeItem();
+
+ virtual void enableItem();
+ virtual void disableItem();
+
+ virtual void viewFolder();
+ virtual void viewConfigs();
+
+ void ShowContextMenu(const QPoint& pos);
+
+ protected:
+ BaseInstance* m_instance = nullptr;
+
+ Ui::ExternalResourcesPage* ui = nullptr;
+ std::shared_ptr<ModFolderModel> m_model;
+ QSortFilterProxyModel* m_filterModel = nullptr;
+
+ QString m_fileSelectionFilter;
+ QString m_viewFilter;
+
+ bool m_controlsEnabled = true;
+};
diff --git a/launcher/ui/pages/instance/ModFolderPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui
index ab59b0df..17bf455a 100644
--- a/launcher/ui/pages/instance/ModFolderPage.ui
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>ModFolderPage</class>
- <widget class="QMainWindow" name="ModFolderPage">
+ <class>ExternalResourcesPage</class>
+ <widget class="QMainWindow" name="ExternalResourcesPage">
<property name="geometry">
<rect>
<x>0</x>
@@ -53,7 +53,7 @@
</widget>
</item>
<item row="1" column="1" colspan="3">
- <widget class="ModListView" name="modTreeView">
+ <widget class="ModListView" name="treeView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@@ -83,15 +83,15 @@
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
- <addaction name="actionAdd"/>
+ <addaction name="actionAddItem"/>
<addaction name="separator"/>
- <addaction name="actionRemove"/>
- <addaction name="actionEnable"/>
- <addaction name="actionDisable"/>
- <addaction name="actionView_configs"/>
- <addaction name="actionView_Folder"/>
+ <addaction name="actionRemoveItem"/>
+ <addaction name="actionEnableItem"/>
+ <addaction name="actionDisableItem"/>
+ <addaction name="actionViewConfigs"/>
+ <addaction name="actionViewFolder"/>
</widget>
- <action name="actionAdd">
+ <action name="actionAddItem">
<property name="text">
<string>&amp;Add</string>
</property>
@@ -99,31 +99,31 @@
<string>Add</string>
</property>
</action>
- <action name="actionRemove">
+ <action name="actionRemoveItem">
<property name="text">
<string>&amp;Remove</string>
</property>
<property name="toolTip">
- <string>Remove selected mods</string>
+ <string>Remove selected item</string>
</property>
</action>
- <action name="actionEnable">
+ <action name="actionEnableItem">
<property name="text">
<string>&amp;Enable</string>
</property>
<property name="toolTip">
- <string>Enable selected mods</string>
+ <string>Enable selected item</string>
</property>
</action>
- <action name="actionDisable">
+ <action name="actionDisableItem">
<property name="text">
<string>&amp;Disable</string>
</property>
<property name="toolTip">
- <string>Disable selected mods</string>
+ <string>Disable selected item</string>
</property>
</action>
- <action name="actionView_configs">
+ <action name="actionViewConfigs">
<property name="text">
<string>View &amp;Configs</string>
</property>
@@ -131,11 +131,22 @@
<string>Open the 'config' folder in the system file manager.</string>
</property>
</action>
- <action name="actionView_Folder">
+ <action name="actionViewFolder">
<property name="text">
<string>View &amp;Folder</string>
</property>
</action>
+ <action name="actionDownloadItem">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&amp;Download</string>
+ </property>
+ <property name="toolTip">
+ <string>Download a new resource</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
@@ -156,7 +167,7 @@
</customwidget>
</customwidgets>
<tabstops>
- <tabstop>modTreeView</tabstop>
+ <tabstop>treeView</tabstop>
<tabstop>filterEdit</tabstop>
</tabstops>
<resources/>
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
index b4562843..fcc110de 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
@@ -2,7 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -50,6 +50,7 @@
#include "Application.h"
#include "java/JavaInstallList.h"
+#include "java/JavaUtils.h"
#include "FileSystem.h"
@@ -232,6 +233,22 @@ void InstanceSettingsPage::applySettings()
m_settings->reset("UseNativeGLFW");
}
+ // Performance
+ bool performance = ui->perfomanceGroupBox->isChecked();
+ m_settings->set("OverridePerformance", performance);
+ if(performance)
+ {
+ m_settings->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked());
+ m_settings->set("EnableMangoHud", ui->enableMangoHud->isChecked());
+ m_settings->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked());
+ }
+ else
+ {
+ m_settings->reset("EnableFeralGamemode");
+ m_settings->reset("EnableMangoHud");
+ m_settings->reset("UseDiscreteGpu");
+ }
+
// Game time
bool gameTime = ui->gameTimeGroupBox->isChecked();
m_settings->set("OverrideGameTime", gameTime);
@@ -325,6 +342,16 @@ void InstanceSettingsPage::loadSettings()
ui->useNativeGLFWCheck->setChecked(m_settings->get("UseNativeGLFW").toBool());
ui->useNativeOpenALCheck->setChecked(m_settings->get("UseNativeOpenAL").toBool());
+ // Performance
+ ui->perfomanceGroupBox->setChecked(m_settings->get("OverridePerformance").toBool());
+ ui->enableFeralGamemodeCheck->setChecked(m_settings->get("EnableFeralGamemode").toBool());
+ ui->enableMangoHud->setChecked(m_settings->get("EnableMangoHud").toBool());
+ ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool());
+
+ #if !defined(Q_OS_LINUX)
+ ui->perfomanceGroupBox->setVisible(false);
+ #endif
+
// Miscellanous
ui->gameTimeGroupBox->setChecked(m_settings->get("OverrideGameTime").toBool());
ui->showGameTime->setChecked(m_settings->get("ShowGameTime").toBool());
@@ -336,6 +363,11 @@ void InstanceSettingsPage::loadSettings()
void InstanceSettingsPage::on_javaDetectBtn_clicked()
{
+ if (JavaUtils::getJavaCheckPath().isEmpty()) {
+ JavaCommon::javaCheckNotFound(this);
+ return;
+ }
+
JavaInstallPtr java;
VersionSelectDialog vselect(APPLICATION->javalist().get(), tr("Select a Java version"), this, true);
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui
index cb66b3ce..8b3c3370 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.ui
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui
@@ -455,6 +455,74 @@
</item>
</layout>
</widget>
+ <widget class="QWidget" name="performancePage">
+ <attribute name="title">
+ <string>Performance</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_14">
+ <item>
+ <widget class="QGroupBox" name="perfomanceGroupBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Performance</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_13">
+ <item>
+ <widget class="QCheckBox" name="enableFeralGamemodeCheck">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable Feral Interactive's GameMode, to potentially improve gaming performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Enable Feral GameMode</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="enableMangoHud">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable MangoHud's advanced performance overlay.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Enable MangoHud</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useDiscreteGpuCheck">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use the discrete GPU instead of the primary GPU.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Use discrete GPU</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
<widget class="QWidget" name="miscellaneousPage">
<attribute name="title">
<string>Miscellaneous</string>
diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp
index 30a8735f..3d9fb025 100644
--- a/launcher/ui/pages/instance/LogPage.cpp
+++ b/launcher/ui/pages/instance/LogPage.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -62,7 +63,7 @@ public:
{
case Qt::FontRole:
return m_font;
- case Qt::TextColorRole:
+ case Qt::ForegroundRole:
{
MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt();
return m_colors->getFront(level);
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp
index 8113fe85..4432ccc8 100644
--- a/launcher/ui/pages/instance/ModFolderPage.cpp
+++ b/launcher/ui/pages/instance/ModFolderPage.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -34,409 +35,123 @@
*/
#include "ModFolderPage.h"
-#include "ui_ModFolderPage.h"
+#include "ui_ExternalResourcesPage.h"
-#include <QMessageBox>
+#include <QAbstractItemModel>
#include <QEvent>
#include <QKeyEvent>
-#include <QAbstractItemModel>
#include <QMenu>
+#include <QMessageBox>
#include <QSortFilterProxyModel>
#include "Application.h"
+#include "ui/GuiUtil.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ModDownloadDialog.h"
-#include "ui/GuiUtil.h"
#include "DesktopServices.h"
-#include "minecraft/mod/ModFolderModel.h"
-#include "minecraft/mod/Mod.h"
-#include "minecraft/VersionFilterData.h"
#include "minecraft/PackProfile.h"
+#include "minecraft/VersionFilterData.h"
+#include "minecraft/mod/Mod.h"
+#include "minecraft/mod/ModFolderModel.h"
#include "modplatform/ModAPI.h"
#include "Version.h"
+#include "tasks/ConcurrentTask.h"
#include "ui/dialogs/ProgressDialog.h"
-#include "tasks/SequentialTask.h"
-
-namespace {
- // FIXME: wasteful
- void RemoveThePrefix(QString & string) {
- QRegularExpression regex(QStringLiteral("^(([Tt][Hh][eE])|([Tt][eE][Hh])) +"));
- string.remove(regex);
- string = string.trimmed();
- }
-}
-
-class ModSortProxy : public QSortFilterProxyModel
-{
-public:
- explicit ModSortProxy(QObject *parent = 0) : QSortFilterProxyModel(parent)
- {
- }
-
-protected:
- bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override {
- ModFolderModel *model = qobject_cast<ModFolderModel *>(sourceModel());
- if(!model) {
- return false;
- }
- const auto &mod = model->at(source_row);
- if(mod.name().contains(filterRegExp())) {
- return true;
- }
- if(mod.description().contains(filterRegExp())) {
- return true;
- }
- for(auto & author: mod.authors()) {
- if (author.contains(filterRegExp())) {
- return true;
- }
- }
- return false;
- }
-
- bool lessThan(const QModelIndex & source_left, const QModelIndex & source_right) const override
- {
- ModFolderModel *model = qobject_cast<ModFolderModel *>(sourceModel());
- if(
- !model ||
- !source_left.isValid() ||
- !source_right.isValid() ||
- source_left.column() != source_right.column()
- ) {
- return QSortFilterProxyModel::lessThan(source_left, source_right);
- }
-
- // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and proceed.
- auto column = (ModFolderModel::Columns) source_left.column();
- bool invert = false;
- switch(column) {
- // GH-2550 - sort by enabled/disabled
- case ModFolderModel::ActiveColumn: {
- auto dataL = source_left.data(Qt::CheckStateRole).toBool();
- auto dataR = source_right.data(Qt::CheckStateRole).toBool();
- if(dataL != dataR) {
- return dataL > dataR;
- }
- // fallthrough
- invert = sortOrder() == Qt::DescendingOrder;
- }
- // GH-2722 - sort mod names in a way that discards "The" prefixes
- case ModFolderModel::NameColumn: {
- auto dataL = model->data(model->index(source_left.row(), ModFolderModel::NameColumn)).toString();
- RemoveThePrefix(dataL);
- auto dataR = model->data(model->index(source_right.row(), ModFolderModel::NameColumn)).toString();
- RemoveThePrefix(dataR);
-
- auto less = dataL.compare(dataR, sortCaseSensitivity());
- if(less != 0) {
- return invert ? (less > 0) : (less < 0);
- }
- // fallthrough
- invert = sortOrder() == Qt::DescendingOrder;
- }
- // GH-2762 - sort versions by parsing them as versions
- case ModFolderModel::VersionColumn: {
- auto dataL = Version(model->data(model->index(source_left.row(), ModFolderModel::VersionColumn)).toString());
- auto dataR = Version(model->data(model->index(source_right.row(), ModFolderModel::VersionColumn)).toString());
- return invert ? (dataL > dataR) : (dataL < dataR);
- }
- default: {
- return QSortFilterProxyModel::lessThan(source_left, source_right);
- }
- }
- }
-};
-
-ModFolderPage::ModFolderPage(
- BaseInstance *inst,
- std::shared_ptr<ModFolderModel> mods,
- QString id,
- QString iconName,
- QString displayName,
- QString helpPage,
- QWidget *parent
-) :
- QMainWindow(parent),
- ui(new Ui::ModFolderPage)
+ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
+ : ExternalResourcesPage(inst, mods, parent)
{
- ui->setupUi(this);
-
// This is structured like that so that these changes
- // do not affect the Resouce pack and Shader pack tabs
- if(id == "mods") {
- auto act = new QAction(tr("Download mods"), this);
- act->setToolTip(tr("Download mods from online mod platforms"));
- ui->actionsToolbar->insertActionBefore(ui->actionAdd, act);
- connect(act, &QAction::triggered, this, &ModFolderPage::on_actionInstall_mods_triggered);
-
- ui->actionAdd->setText(tr("Add .jar"));
- ui->actionAdd->setToolTip(tr("Add mods via local file"));
- }
-
- ui->actionsToolbar->insertSpacer(ui->actionView_configs);
-
- m_inst = inst;
- on_RunningState_changed(m_inst && m_inst->isRunning());
- m_mods = mods;
- m_id = id;
- m_displayName = displayName;
- m_iconName = iconName;
- m_helpName = helpPage;
- m_fileSelectionFilter = "%1 (*.zip *.jar)";
- m_filterModel = new ModSortProxy(this);
- m_filterModel->setDynamicSortFilter(true);
- m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
- m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
- m_filterModel->setSourceModel(m_mods.get());
- m_filterModel->setFilterKeyColumn(-1);
- ui->modTreeView->setModel(m_filterModel);
- ui->modTreeView->installEventFilter(this);
- ui->modTreeView->sortByColumn(1, Qt::AscendingOrder);
- ui->modTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(ui->modTreeView, &ModListView::customContextMenuRequested, this, &ModFolderPage::ShowContextMenu);
- connect(ui->modTreeView, &ModListView::activated, this, &ModFolderPage::modItemActivated);
+ // 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"));
- auto smodel = ui->modTreeView->selectionModel();
- connect(smodel, &QItemSelectionModel::currentChanged, this, &ModFolderPage::modCurrent);
- connect(ui->filterEdit, &QLineEdit::textChanged, this, &ModFolderPage::on_filterTextChanged);
- connect(m_inst, &BaseInstance::runningStatusChanged, this, &ModFolderPage::on_RunningState_changed);
-}
+ ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
-void ModFolderPage::modItemActivated(const QModelIndex&)
-{
- if(!m_controlsEnabled) {
- return;
+ connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::installMods);
}
- auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
- m_mods->setModStatus(selection.indexes(), ModFolderModel::Toggle);
-}
-
-QMenu * ModFolderPage::createPopupMenu()
-{
- QMenu* filteredMenu = QMainWindow::createPopupMenu();
- filteredMenu->removeAction(ui->actionsToolbar->toggleViewAction() );
- return filteredMenu;
-}
-
-void ModFolderPage::ShowContextMenu(const QPoint& pos)
-{
- auto menu = ui->actionsToolbar->createContextMenu(this, tr("Context menu"));
- menu->exec(ui->modTreeView->mapToGlobal(pos));
- delete menu;
-}
-
-void ModFolderPage::openedImpl()
-{
- m_mods->startWatching();
-}
-
-void ModFolderPage::closedImpl()
-{
- m_mods->stopWatching();
-}
-
-void ModFolderPage::on_filterTextChanged(const QString& newContents)
-{
- m_viewFilter = newContents;
- m_filterModel->setFilterFixedString(m_viewFilter);
-}
-
-
-CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModFolderModel> mods,
- QString id, QString iconName, QString displayName,
- QString helpPage, QWidget *parent)
- : ModFolderPage(inst, mods, id, iconName, displayName, helpPage, parent)
-{
}
-ModFolderPage::~ModFolderPage()
-{
- m_mods->stopWatching();
- delete ui;
-}
-
-void ModFolderPage::on_RunningState_changed(bool running)
-{
- if(m_controlsEnabled == !running) {
- return;
- }
- m_controlsEnabled = !running;
- ui->actionsToolbar->setEnabled(m_controlsEnabled);
-}
+CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
+ : ModFolderPage(inst, mods, parent)
+{}
bool ModFolderPage::shouldDisplay() const
{
return true;
}
-void ModFolderPage::retranslate()
-{
- ui->retranslateUi(this);
-}
-
bool CoreModFolderPage::shouldDisplay() const
{
- if (ModFolderPage::shouldDisplay())
- {
- auto inst = dynamic_cast<MinecraftInstance *>(m_inst);
+ if (ModFolderPage::shouldDisplay()) {
+ auto inst = dynamic_cast<MinecraftInstance*>(m_instance);
if (!inst)
return true;
+
auto version = inst->getPackProfile();
+
if (!version)
return true;
- if(!version->getComponent("net.minecraftforge"))
- {
+ if (!version->getComponent("net.minecraftforge"))
return false;
- }
- if(!version->getComponent("net.minecraft"))
- {
+ if (!version->getComponent("net.minecraft"))
return false;
- }
- if(version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
- {
+ if (version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
return true;
- }
+
}
return false;
}
-bool ModFolderPage::modListFilter(QKeyEvent *keyEvent)
-{
- switch (keyEvent->key())
- {
- case Qt::Key_Delete:
- on_actionRemove_triggered();
- return true;
- case Qt::Key_Plus:
- on_actionAdd_triggered();
- return true;
- default:
- break;
- }
- return QWidget::eventFilter(ui->modTreeView, keyEvent);
-}
-
-bool ModFolderPage::eventFilter(QObject *obj, QEvent *ev)
-{
- if (ev->type() != QEvent::KeyPress)
- {
- return QWidget::eventFilter(obj, ev);
- }
- QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
- if (obj == ui->modTreeView)
- return modListFilter(keyEvent);
- return QWidget::eventFilter(obj, ev);
-}
-
-void ModFolderPage::on_actionAdd_triggered()
-{
- if(!m_controlsEnabled) {
- return;
- }
- auto list = GuiUtil::BrowseForFiles(
- m_helpName,
- tr("Select %1",
- "Select whatever type of files the page contains. Example: 'Loader Mods'")
- .arg(m_displayName),
- m_fileSelectionFilter.arg(m_displayName), APPLICATION->settings()->get("CentralModsDir").toString(),
- this->parentWidget());
- if (!list.empty())
- {
- for (auto filename : list)
- {
- m_mods->installMod(filename);
- }
- }
-}
-
-void ModFolderPage::on_actionEnable_triggered()
-{
- if(!m_controlsEnabled) {
- return;
- }
- auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
- m_mods->setModStatus(selection.indexes(), ModFolderModel::Enable);
-}
-
-void ModFolderPage::on_actionDisable_triggered()
+void ModFolderPage::installMods()
{
- if(!m_controlsEnabled) {
+ if (!m_controlsEnabled)
return;
- }
- auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
- m_mods->setModStatus(selection.indexes(), ModFolderModel::Disable);
-}
-
-void ModFolderPage::on_actionRemove_triggered()
-{
- if(!m_controlsEnabled) {
+ if (m_instance->typeName() != "Minecraft")
+ return; // this is a null instance or a legacy instance
+
+ auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
+ if (profile->getModLoaders() == ModAPI::Unspecified) {
+ QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!"));
return;
}
- auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
- m_mods->deleteMods(selection.indexes());
-}
-void ModFolderPage::on_actionInstall_mods_triggered()
-{
- if(!m_controlsEnabled) {
- return;
- }
- if(m_inst->typeName() != "Minecraft"){
- return; //this is a null instance or a legacy instance
- }
- auto profile = ((MinecraftInstance *)m_inst)->getPackProfile();
- if (profile->getModLoader() == ModAPI::Unspecified) {
- QMessageBox::critical(this,tr("Error"),tr("Please install a mod loader first!"));
- return;
- }
- ModDownloadDialog mdownload(m_mods, this, m_inst);
+ ModDownloadDialog mdownload(m_model, this, m_instance);
if (mdownload.exec()) {
- SequentialTask* tasks = new SequentialTask(this);
+ ConcurrentTask* tasks = new ConcurrentTask(this);
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(); }
+ if (warnings.count())
+ CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
+
tasks->deleteLater();
});
- for (auto task : mdownload.getTasks()) {
+ for (auto& task : mdownload.getTasks()) {
tasks->addTask(task);
}
+
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
- m_mods->update();
- }
-}
-
-void ModFolderPage::on_actionView_configs_triggered()
-{
- DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true);
-}
-
-void ModFolderPage::on_actionView_Folder_triggered()
-{
- DesktopServices::openDirectory(m_mods->dir().absolutePath(), true);
-}
-void ModFolderPage::modCurrent(const QModelIndex &current, const QModelIndex &previous)
-{
- if (!current.isValid())
- {
- ui->frame->clear();
- return;
+ m_model->update();
}
- auto sourceCurrent = m_filterModel->mapToSource(current);
- int row = sourceCurrent.row();
- Mod &m = m_mods->operator[](row);
- ui->frame->updateWithMod(m);
}
diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h
index 72e2d404..19caa732 100644
--- a/launcher/ui/pages/instance/ModFolderPage.h
+++ b/launcher/ui/pages/instance/ModFolderPage.h
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -35,108 +36,31 @@
#pragma once
-#include <QMainWindow>
+#include "ExternalResourcesPage.h"
-#include "minecraft/MinecraftInstance.h"
-#include "ui/pages/BasePage.h"
-
-#include <Application.h>
-
-class ModFolderModel;
-namespace Ui
-{
-class ModFolderPage;
-}
-
-class ModFolderPage : public QMainWindow, public BasePage
-{
+class ModFolderPage : public ExternalResourcesPage {
Q_OBJECT
-public:
- explicit ModFolderPage(
- BaseInstance *inst,
- std::shared_ptr<ModFolderModel> mods,
- QString id,
- QString iconName,
- QString displayName,
- QString helpPage = "",
- QWidget *parent = 0
- );
- virtual ~ModFolderPage();
-
- void setFilter(const QString & filter)
- {
- m_fileSelectionFilter = filter;
- }
+ public:
+ explicit ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent = nullptr);
+ virtual ~ModFolderPage() = default;
- virtual QString displayName() const override
- {
- return m_displayName;
- }
- virtual QIcon icon() const override
- {
- return APPLICATION->getThemedIcon(m_iconName);
- }
- virtual QString id() const override
- {
- return m_id;
- }
- virtual QString helpPage() const override
- {
- return m_helpName;
- }
- virtual bool shouldDisplay() const override;
- void retranslate() override;
-
- virtual void openedImpl() override;
- virtual void closedImpl() override;
-protected:
- bool eventFilter(QObject *obj, QEvent *ev) override;
- bool modListFilter(QKeyEvent *ev);
- QMenu * createPopupMenu() override;
+ void setFilter(const QString& filter) { m_fileSelectionFilter = filter; }
-protected:
- BaseInstance *m_inst = nullptr;
+ virtual QString displayName() const override { return tr("Mods"); }
+ virtual QIcon icon() const override { return APPLICATION->getThemedIcon("loadermods"); }
+ virtual QString id() const override { return "mods"; }
+ virtual QString helpPage() const override { return "Loader-mods"; }
-protected:
- Ui::ModFolderPage *ui = nullptr;
- std::shared_ptr<ModFolderModel> m_mods;
- QSortFilterProxyModel *m_filterModel = nullptr;
- QString m_iconName;
- QString m_id;
- QString m_displayName;
- QString m_helpName;
- QString m_fileSelectionFilter;
- QString m_viewFilter;
- bool m_controlsEnabled = true;
-
-public
-slots:
- void modCurrent(const QModelIndex &current, const QModelIndex &previous);
+ virtual bool shouldDisplay() const override;
-private
-slots:
- void modItemActivated(const QModelIndex &index);
- void on_filterTextChanged(const QString & newContents);
- void on_RunningState_changed(bool running);
- void on_actionAdd_triggered();
- void on_actionRemove_triggered();
- void on_actionEnable_triggered();
- void on_actionDisable_triggered();
- void on_actionInstall_mods_triggered();
- void on_actionView_Folder_triggered();
- void on_actionView_configs_triggered();
- void ShowContextMenu(const QPoint &pos);
+ private slots:
+ void installMods();
};
-class CoreModFolderPage : public ModFolderPage
-{
-public:
- explicit CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModFolderModel> mods, QString id,
- QString iconName, QString displayName, QString helpPage = "",
- QWidget *parent = 0);
- virtual ~CoreModFolderPage()
- {
- }
+class CoreModFolderPage : public ModFolderPage {
+ public:
+ explicit CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent = 0);
+ virtual ~CoreModFolderPage() = default;
virtual bool shouldDisplay() const;
};
diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h
index 8054926c..a6c9fdd3 100644
--- a/launcher/ui/pages/instance/ResourcePackPage.h
+++ b/launcher/ui/pages/instance/ResourcePackPage.h
@@ -35,24 +35,28 @@
#pragma once
-#include "ModFolderPage.h"
-#include "ui_ModFolderPage.h"
+#include "ExternalResourcesPage.h"
+#include "ui_ExternalResourcesPage.h"
-class ResourcePackPage : public ModFolderPage
+class ResourcePackPage : public ExternalResourcesPage
{
Q_OBJECT
public:
explicit ResourcePackPage(MinecraftInstance *instance, QWidget *parent = 0)
- : ModFolderPage(instance, instance->resourcePackList(), "resourcepacks",
- "resourcepacks", tr("Resource packs"), "Resource-packs", parent)
+ : ExternalResourcesPage(instance, instance->resourcePackList(), parent)
{
- ui->actionView_configs->setVisible(false);
+ ui->actionViewConfigs->setVisible(false);
}
virtual ~ResourcePackPage() {}
+ QString displayName() const override { return tr("Resource packs"); }
+ QIcon icon() const override { return APPLICATION->getThemedIcon("resourcepacks"); }
+ QString id() const override { return "resourcepacks"; }
+ QString helpPage() const override { return "Resource-packs"; }
+
virtual bool shouldDisplay() const override
{
- return !m_inst->traits().contains("no-texturepacks") &&
- !m_inst->traits().contains("texturepacks");
+ return !m_instance->traits().contains("no-texturepacks") &&
+ !m_instance->traits().contains("texturepacks");
}
};
diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp
index 2cf17b32..c97253e4 100644
--- a/launcher/ui/pages/instance/ScreenshotsPage.cpp
+++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -49,6 +50,7 @@
#include <QClipboard>
#include <QKeyEvent>
#include <QMenu>
+#include <QRegularExpression>
#include <Application.h>
@@ -153,7 +155,7 @@ public:
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
QVariant result = sourceModel()->data(mapToSource(proxyIndex), role);
- return result.toString().remove(QRegExp("\\.png$"));
+ return result.toString().remove(QRegularExpression("\\.png$"));
}
if (role == Qt::DecorationRole)
{
@@ -269,7 +271,7 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent)
ui->listView->setViewMode(QListView::IconMode);
ui->listView->setResizeMode(QListView::Adjust);
ui->listView->installEventFilter(this);
- ui->listView->setEditTriggers(0);
+ ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->listView->setItemDelegate(new CenteredEditingDelegate(this));
ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::ShowContextMenu);
diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp
index 2af6164c..e5cce96c 100644
--- a/launcher/ui/pages/instance/ServersPage.cpp
+++ b/launcher/ui/pages/instance/ServersPage.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -287,7 +288,11 @@ public:
return false;
}
beginMoveRows(QModelIndex(), row, row, QModelIndex(), row - 1);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
+ m_servers.swapItemsAt(row-1, row);
+#else
m_servers.swap(row-1, row);
+#endif
endMoveRows();
scheduleSave();
return true;
@@ -305,7 +310,11 @@ public:
return false;
}
beginMoveRows(QModelIndex(), row, row, QModelIndex(), row + 2);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
+ m_servers.swapItemsAt(row+1, row);
+#else
m_servers.swap(row+1, row);
+#endif
endMoveRows();
scheduleSave();
return true;
@@ -614,7 +623,7 @@ ServersPage::ServersPage(InstancePtr inst, QWidget* parent)
auto selectionModel = ui->serversView->selectionModel();
connect(selectionModel, &QItemSelectionModel::currentChanged, this, &ServersPage::currentChanged);
- connect(m_inst.get(), &MinecraftInstance::runningStatusChanged, this, &ServersPage::on_RunningState_changed);
+ connect(m_inst.get(), &MinecraftInstance::runningStatusChanged, this, &ServersPage::runningStateChanged);
connect(ui->nameLine, &QLineEdit::textEdited, this, &ServersPage::nameEdited);
connect(ui->addressLine, &QLineEdit::textEdited, this, &ServersPage::addressEdited);
connect(ui->resourceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(resourceIndexChanged(int)));
@@ -654,7 +663,7 @@ QMenu * ServersPage::createPopupMenu()
return filteredMenu;
}
-void ServersPage::on_RunningState_changed(bool running)
+void ServersPage::runningStateChanged(bool running)
{
if(m_locked == running)
{
diff --git a/launcher/ui/pages/instance/ServersPage.h b/launcher/ui/pages/instance/ServersPage.h
index 5173712c..37399d49 100644
--- a/launcher/ui/pages/instance/ServersPage.h
+++ b/launcher/ui/pages/instance/ServersPage.h
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -97,7 +98,7 @@ private slots:
void on_actionMove_Down_triggered();
void on_actionJoin_triggered();
- void on_RunningState_changed(bool running);
+ void runningStateChanged(bool running);
void nameEdited(const QString & name);
void addressEdited(const QString & address);
diff --git a/launcher/ui/pages/instance/ShaderPackPage.h b/launcher/ui/pages/instance/ShaderPackPage.h
index 7d4f5074..2cc056c8 100644
--- a/launcher/ui/pages/instance/ShaderPackPage.h
+++ b/launcher/ui/pages/instance/ShaderPackPage.h
@@ -35,21 +35,25 @@
#pragma once
-#include "ModFolderPage.h"
-#include "ui_ModFolderPage.h"
+#include "ExternalResourcesPage.h"
+#include "ui_ExternalResourcesPage.h"
-class ShaderPackPage : public ModFolderPage
+class ShaderPackPage : public ExternalResourcesPage
{
Q_OBJECT
public:
explicit ShaderPackPage(MinecraftInstance *instance, QWidget *parent = 0)
- : ModFolderPage(instance, instance->shaderPackList(), "shaderpacks",
- "shaderpacks", tr("Shader packs"), "Resource-packs", parent)
+ : ExternalResourcesPage(instance, instance->shaderPackList(), parent)
{
- ui->actionView_configs->setVisible(false);
+ ui->actionViewConfigs->setVisible(false);
}
virtual ~ShaderPackPage() {}
+ QString displayName() const override { return tr("Shader packs"); }
+ QIcon icon() const override { return APPLICATION->getThemedIcon("shaderpacks"); }
+ QString id() const override { return "shaderpacks"; }
+ QString helpPage() const override { return "Resource-packs"; }
+
virtual bool shouldDisplay() const override
{
return true;
diff --git a/launcher/ui/pages/instance/TexturePackPage.h b/launcher/ui/pages/instance/TexturePackPage.h
index e8cefe6e..f550a5bc 100644
--- a/launcher/ui/pages/instance/TexturePackPage.h
+++ b/launcher/ui/pages/instance/TexturePackPage.h
@@ -35,23 +35,27 @@
#pragma once
-#include "ModFolderPage.h"
-#include "ui_ModFolderPage.h"
+#include "ExternalResourcesPage.h"
+#include "ui_ExternalResourcesPage.h"
-class TexturePackPage : public ModFolderPage
+class TexturePackPage : public ExternalResourcesPage
{
Q_OBJECT
public:
explicit TexturePackPage(MinecraftInstance *instance, QWidget *parent = 0)
- : ModFolderPage(instance, instance->texturePackList(), "texturepacks", "resourcepacks",
- tr("Texture packs"), "Texture-packs", parent)
+ : ExternalResourcesPage(instance, instance->texturePackList(), parent)
{
- ui->actionView_configs->setVisible(false);
+ ui->actionViewConfigs->setVisible(false);
}
virtual ~TexturePackPage() {}
+ QString displayName() const override { return tr("Texture packs"); }
+ QIcon icon() const override { return APPLICATION->getThemedIcon("resourcepacks"); }
+ QString id() const override { return "texturepacks"; }
+ QString helpPage() const override { return "Texture-packs"; }
+
virtual bool shouldDisplay() const override
{
- return m_inst->traits().contains("texturepacks");
+ return m_instance->traits().contains("texturepacks");
}
};
diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp
index 23e2367b..468ff35c 100644
--- a/launcher/ui/pages/instance/VersionPage.cpp
+++ b/launcher/ui/pages/instance/VersionPage.cpp
@@ -2,7 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp
index 76725539..647b04a7 100644
--- a/launcher/ui/pages/instance/WorldListPage.cpp
+++ b/launcher/ui/pages/instance/WorldListPage.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp
index c7bc13d8..30196aad 100644
--- a/launcher/ui/pages/modplatform/ImportPage.cpp
+++ b/launcher/ui/pages/modplatform/ImportPage.cpp
@@ -2,7 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -110,14 +110,16 @@ void ImportPage::updateState()
{
// FIXME: actually do some validation of what's inside here... this is fake AF
QFileInfo fi(input);
- // mrpack is a modrinth pack
// Allow non-latin people to use ZIP files!
- auto zip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip");
- if(fi.exists() && (zip || fi.suffix() == "mrpack"))
+ bool isZip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip");
+ // mrpack is a modrinth pack
+ bool isMRPack = fi.suffix() == "mrpack";
+
+ if(fi.exists() && (isZip || isMRPack))
{
QFileInfo fi(url.fileName());
- dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
+ dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this));
dialog->setSuggestedIcon("default");
}
}
@@ -130,7 +132,7 @@ void ImportPage::updateState()
}
// hook, line and sinker.
QFileInfo fi(url.fileName());
- dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
+ dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this));
dialog->setSuggestedIcon("default");
}
}
@@ -149,7 +151,8 @@ void ImportPage::setUrl(const QString& url)
void ImportPage::on_modpackBtn_clicked()
{
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
- filter += ";;" + tr("Modrinth pack (*.mrpack)");
+ //: Option for filtering for *.mrpack files when importing
+ filter += ";;" + tr("Modrinth pack") + " (*.mrpack)";
const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter);
if (url.isValid())
{
diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp
index 540ee2fd..94b1f099 100644
--- a/launcher/ui/pages/modplatform/ModModel.cpp
+++ b/launcher/ui/pages/modplatform/ModModel.cpp
@@ -38,27 +38,48 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant
}
ModPlatform::IndexedPack pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name;
- } else if (role == Qt::ToolTipRole) {
- if (pack.description.length() > 100) {
- // some magic to prevent to long tooltips and replace html linebreaks
- QString edit = pack.description.left(97);
- edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
- return edit;
+ switch (role) {
+ case Qt::DisplayRole: {
+ return pack.name;
}
- return pack.description;
- } else if (role == Qt::DecorationRole) {
- if (m_logoMap.contains(pack.logoName)) {
- return (m_logoMap.value(pack.logoName));
+ case Qt::ToolTipRole: {
+ if (pack.description.length() > 100) {
+ // some magic to prevent to long tooltips and replace html linebreaks
+ QString edit = pack.description.left(97);
+ edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+ }
+ return pack.description;
}
- QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
- ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
- return icon;
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
+ case Qt::DecorationRole: {
+ if (m_logoMap.contains(pack.logoName)) {
+ auto icon = m_logoMap.value(pack.logoName);
+ // FIXME: This doesn't really belong here, but Qt doesn't offer a good way right now ;(
+ auto icon_scaled = QIcon(icon.pixmap(48, 48).scaledToWidth(48));
+
+ return icon_scaled;
+ }
+ QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
+ // un-const-ify this
+ ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
+ return icon;
+ }
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+ case Qt::FontRole: {
+ QFont font;
+ if (m_parent->getDialog()->isModSelected(pack.name)) {
+ font.setBold(true);
+ font.setUnderline(true);
+ }
+
+ return font;
+ }
+ default:
+ break;
}
return {};
@@ -68,7 +89,7 @@ void ListModel::requestModVersions(ModPlatform::IndexedPack const& current)
{
auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile();
- m_parent->apiProvider()->getVersions(this, { current.addonId.toString(), getMineVersions(), profile->getModLoader() });
+ m_parent->apiProvider()->getVersions(this, { current.addonId.toString(), getMineVersions(), profile->getModLoaders() });
}
void ListModel::performPaginatedSearch()
@@ -76,7 +97,12 @@ void ListModel::performPaginatedSearch()
auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile();
m_parent->apiProvider()->searchMods(
- this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoader(), getMineVersions() });
+ this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() });
+}
+
+void ListModel::requestModInfo(ModPlatform::IndexedPack& current)
+{
+ m_parent->apiProvider()->getModInfo(this, current);
}
void ListModel::refresh()
@@ -193,6 +219,10 @@ void ListModel::searchRequestFinished(QJsonDocument& doc)
searchState = CanPossiblyFetchMore;
}
+ // When you have a Qt build with assertions turned on, proceeding here will abort the application
+ if (newList.size() == 0)
+ return;
+
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
modpacks.append(newList);
endInsertRows();
@@ -225,6 +255,21 @@ void ListModel::searchRequestFailed(QString reason)
}
}
+void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack)
+{
+ qDebug() << "Loading mod info";
+
+ try {
+ auto obj = Json::requireObject(doc);
+ loadExtraPackInfo(pack, obj);
+ } catch (const JSONValidationError& e) {
+ qDebug() << doc;
+ qWarning() << "Error while reading " << debugName() << " mod info: " << e.cause();
+ }
+
+ m_parent->updateUi();
+}
+
void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId)
{
auto& current = m_parent->getCurrent();
diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h
index d460cff2..dd22407c 100644
--- a/launcher/ui/pages/modplatform/ModModel.h
+++ b/launcher/ui/pages/modplatform/ModModel.h
@@ -36,9 +36,11 @@ class ListModel : public QAbstractListModel {
void fetchMore(const QModelIndex& parent) override;
void refresh();
void searchWithTerm(const QString& term, const int sort, const bool filter_changed);
+ void requestModInfo(ModPlatform::IndexedPack& current);
void requestModVersions(const ModPlatform::IndexedPack& current);
virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0;
+ virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) {};
virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0;
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
@@ -49,6 +51,8 @@ class ListModel : public QAbstractListModel {
void searchRequestFinished(QJsonDocument& doc);
void searchRequestFailed(QString reason);
+ void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack);
+
void versionRequestSucceeded(QJsonDocument doc, QString addonId);
protected slots:
diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp
index 6dd3a453..200fe59e 100644
--- a/launcher/ui/pages/modplatform/ModPage.cpp
+++ b/launcher/ui/pages/modplatform/ModPage.cpp
@@ -1,4 +1,40 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "ModPage.h"
+#include "Application.h"
#include "ui_ModPage.h"
#include <QKeyEvent>
@@ -94,28 +130,6 @@ void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second)
if (!first.isValid()) { return; }
current = listModel->data(first, Qt::UserRole).value<ModPlatform::IndexedPack>();
- QString text = "";
- QString name = current.name;
-
- if (current.websiteUrl.isEmpty())
- text = name;
- else
- text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
-
- if (!current.authors.empty()) {
- auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString {
- if (author.url.isEmpty()) { return author.name; }
- return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
- };
- QStringList authorStrs;
- for (auto& author : current.authors) {
- authorStrs.push_back(authorToStr(author));
- }
- text += "<br>" + tr(" by ") + authorStrs.join(", ");
- }
- text += "<br><br>";
-
- ui->packDescription->setHtml(text + current.description);
if (!current.versionsLoaded) {
qDebug() << QString("Loading %1 mod versions").arg(debugName());
@@ -132,6 +146,13 @@ void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second)
updateSelectionButton();
}
+
+ if(!current.extraDataLoaded){
+ qDebug() << QString("Loading %1 mod info").arg(debugName());
+ listModel->requestModInfo(current);
+ }
+
+ updateUi();
}
void ModPage::onVersionSelectionChanged(QString data)
@@ -150,7 +171,8 @@ void ModPage::onModSelected()
if (dialog->isModSelected(current.name, version.fileName)) {
dialog->removeSelectedMod(current.name);
} else {
- dialog->addSelectedMod(current.name, new ModDownloadTask(version.downloadUrl, version.fileName, dialog->mods));
+ bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
+ dialog->addSelectedMod(current.name, new ModDownloadTask(current, version, dialog->mods, is_indexed));
}
updateSelectionButton();
@@ -175,7 +197,7 @@ void ModPage::updateModVersions(int prev_count)
bool valid = false;
for(auto& mcVer : m_filter->versions){
//NOTE: Flame doesn't care about loader, so passing it changes nothing.
- if (validateVersion(version, mcVer.toString(), packProfile->getModLoader())) {
+ if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) {
valid = true;
break;
}
@@ -207,3 +229,61 @@ void ModPage::updateSelectionButton()
ui->modSelectionButton->setText(tr("Deselect mod for download"));
}
}
+
+void ModPage::updateUi()
+{
+ QString text = "";
+ QString name = current.name;
+
+ if (current.websiteUrl.isEmpty())
+ text = name;
+ else
+ text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
+
+ if (!current.authors.empty()) {
+ auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString {
+ if (author.url.isEmpty()) { return author.name; }
+ return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
+ };
+ QStringList authorStrs;
+ for (auto& author : current.authors) {
+ authorStrs.push_back(authorToStr(author));
+ }
+ text += "<br>" + tr(" by ") + authorStrs.join(", ");
+ }
+
+
+ if(current.extraDataLoaded) {
+ if (!current.extraData.donate.isEmpty()) {
+ text += "<br><br>" + tr("Donate information: ");
+ auto donateToStr = [](ModPlatform::DonationData& donate) -> QString {
+ return QString("<a href=\"%1\">%2</a>").arg(donate.url, donate.platform);
+ };
+ QStringList donates;
+ for (auto& donate : current.extraData.donate) {
+ donates.append(donateToStr(donate));
+ }
+ text += donates.join(", ");
+ }
+
+ if (!current.extraData.issuesUrl.isEmpty()
+ || !current.extraData.sourceUrl.isEmpty()
+ || !current.extraData.wikiUrl.isEmpty()
+ || !current.extraData.discordUrl.isEmpty()) {
+ text += "<br><br>" + tr("External links:") + "<br>";
+ }
+
+ if (!current.extraData.issuesUrl.isEmpty())
+ text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current.extraData.issuesUrl) + "<br>";
+ if (!current.extraData.wikiUrl.isEmpty())
+ text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current.extraData.wikiUrl) + "<br>";
+ if (!current.extraData.sourceUrl.isEmpty())
+ text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extraData.sourceUrl) + "<br>";
+ if (!current.extraData.discordUrl.isEmpty())
+ text += "- " + tr("Discord: <a href=%1>%1</a>").arg(current.extraData.discordUrl) + "<br>";
+ }
+
+ text += "<hr>";
+
+ ui->packDescription->setHtml(text + current.description);
+}
diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h
index eb89b0e2..cf00e16e 100644
--- a/launcher/ui/pages/modplatform/ModPage.h
+++ b/launcher/ui/pages/modplatform/ModPage.h
@@ -36,11 +36,14 @@ class ModPage : public QWidget, public BasePage {
void retranslate() override;
+ void updateUi();
+
auto shouldDisplay() const -> bool override = 0;
- virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool = 0;
+ virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool = 0;
- auto apiProvider() const -> const ModAPI* { return api.get(); };
+ auto apiProvider() -> ModAPI* { return api.get(); };
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
+ auto getDialog() const -> const ModDownloadDialog* { return dialog; }
auto getCurrent() -> ModPlatform::IndexedPack& { return current; }
void updateModVersions(int prev_count = -1);
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
index 26aa60af..004fdc57 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
@@ -43,8 +43,11 @@
#include "modplatform/atlauncher/ATLShareCode.h"
#include "Application.h"
-AtlOptionalModListModel::AtlOptionalModListModel(QWidget *parent, QVector<ATLauncher::VersionMod> mods)
- : QAbstractListModel(parent), m_mods(mods) {
+AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
+ : QAbstractListModel(parent)
+ , m_version(version)
+ , m_mods(mods)
+{
// fill mod index
for (int i = 0; i < m_mods.size(); i++) {
auto mod = m_mods.at(i);
@@ -97,6 +100,11 @@ QVariant AtlOptionalModListModel::data(const QModelIndex &index, int role) const
return mod.description;
}
}
+ else if (role == Qt::ForegroundRole) {
+ if (!mod.colour.isEmpty() && m_version.colours.contains(mod.colour)) {
+ return QColor(QString("#%1").arg(m_version.colours[mod.colour]));
+ }
+ }
else if (role == Qt::CheckStateRole) {
if (index.column() == EnabledColumn) {
return m_selection[mod.name] ? Qt::Checked : Qt::Unchecked;
@@ -223,7 +231,21 @@ void AtlOptionalModListModel::clearAll() {
}
void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index) {
- setMod(mod, index, !m_selection[mod.name]);
+ auto enable = !m_selection[mod.name];
+
+ // If there is a warning for the mod, display that first (if we would be enabling the mod)
+ if (enable && !mod.warning.isEmpty() && m_version.warnings.contains(mod.warning)) {
+ auto message = QString("%1<br><br>%2")
+ .arg(m_version.warnings[mod.warning], tr("Are you sure that you want to enable this mod?"));
+
+ // fixme: avoid casting here
+ auto result = QMessageBox::warning((QWidget*) this->parent(), tr("Warning"), message, QMessageBox::Yes | QMessageBox::No);
+ if (result != QMessageBox::Yes) {
+ return;
+ }
+ }
+
+ setMod(mod, index, enable);
}
void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit) {
@@ -287,12 +309,13 @@ void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool
}
}
-
-AtlOptionalModDialog::AtlOptionalModDialog(QWidget *parent, QVector<ATLauncher::VersionMod> mods)
- : QDialog(parent), ui(new Ui::AtlOptionalModDialog) {
+AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
+ : QDialog(parent)
+ , ui(new Ui::AtlOptionalModDialog)
+{
ui->setupUi(this);
- listModel = new AtlOptionalModListModel(this, mods);
+ listModel = new AtlOptionalModListModel(this, version, mods);
ui->treeView->setModel(listModel);
ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
index 953b288e..8e02444e 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
@@ -56,7 +56,7 @@ public:
DescriptionColumn,
};
- AtlOptionalModListModel(QWidget *parent, QVector<ATLauncher::VersionMod> mods);
+ AtlOptionalModListModel(QWidget *parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods);
QVector<QString> getResult();
@@ -86,7 +86,9 @@ private:
NetJob::Ptr m_jobPtr;
QByteArray m_response;
+ ATLauncher::PackVersion m_version;
QVector<ATLauncher::VersionMod> m_mods;
+
QMap<QString, bool> m_selection;
QMap<QString, int> m_index;
QMap<QString, QVector<QString>> m_dependants;
@@ -96,7 +98,7 @@ class AtlOptionalModDialog : public QDialog {
Q_OBJECT
public:
- AtlOptionalModDialog(QWidget *parent, QVector<ATLauncher::VersionMod> mods);
+ AtlOptionalModDialog(QWidget *parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods);
~AtlOptionalModDialog() override;
QVector<QString> getResult() {
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
index df9b9207..8de5211c 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
@@ -45,8 +45,12 @@
#include <BuildConfig.h>
-AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget *parent)
- : QWidget(parent), ui(new Ui::AtlPage), dialog(dialog)
+#include <QMessageBox>
+
+AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget* parent)
+ : QWidget(parent)
+ , ui(new Ui::AtlPage)
+ , dialog(dialog)
{
ui->setupUi(this);
@@ -113,7 +117,7 @@ void AtlPage::suggestCurrent()
return;
}
- dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.safeName, selectedVersion));
+ dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.name, selectedVersion));
auto editedLogoName = selected.safeName;
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower());
listModel->getLogo(selected.safeName, url, [this, editedLogoName](QString logo)
@@ -169,8 +173,9 @@ void AtlPage::onVersionSelectionChanged(QString data)
suggestCurrent();
}
-QVector<QString> AtlPage::chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) {
- AtlOptionalModDialog optionalModDialog(this, mods);
+QVector<QString> AtlPage::chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
+{
+ AtlOptionalModDialog optionalModDialog(this, version, mods);
optionalModDialog.exec();
return optionalModDialog.getResult();
}
@@ -210,3 +215,8 @@ QString AtlPage::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVers
vselect.exec();
return vselect.selectedVersion()->descriptor();
}
+
+void AtlPage::displayMessage(QString message)
+{
+ QMessageBox::information(this, tr("Installing"), message);
+}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h
index c95b0127..aa6d5da1 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h
@@ -84,7 +84,8 @@ private:
void suggestCurrent();
QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override;
- QVector<QString> chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) override;
+ QVector<QString> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override;
+ void displayMessage(QString message) override;
private slots:
void triggerSearch();
diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp
index 70759994..10d34218 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -61,9 +61,9 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance)
connect(ui->modSelectionButton, &QPushButton::clicked, this, &FlameModPage::onModSelected);
}
-auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader) const -> bool
+auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool
{
- Q_UNUSED(loader);
+ Q_UNUSED(loaders);
return ver.mcVersion.contains(mineVer);
}
diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h
index 27cbdb8c..445d0368 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModPage.h
+++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -55,7 +55,7 @@ class FlameModPage : public ModPage {
inline auto debugName() const -> QString override { return "Flame"; }
inline auto metaEntryBase() const -> QString override { return "FlameMods"; };
- auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool override;
+ auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override;
auto shouldDisplay() const -> bool override;
};
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
index f97536e8..b9804681 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
@@ -57,6 +57,17 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
return QVariant();
}
+bool ListModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ int pos = index.row();
+ if (pos >= modpacks.size() || pos < 0 || !index.isValid())
+ return false;
+
+ modpacks[pos] = value.value<Flame::IndexedPack>();
+
+ return true;
+}
+
void ListModel::logoLoaded(QString logo, QIcon out)
{
m_loadingLogos.removeAll(logo);
@@ -210,6 +221,11 @@ void Flame::ListModel::searchRequestFinished()
nextSearchOffset += 25;
searchState = CanPossiblyFetchMore;
}
+
+ // When you have a Qt build with assertions turned on, proceeding here will abort the application
+ if (newList.size() == 0)
+ return;
+
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
modpacks.append(newList);
endInsertRows();
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.h b/launcher/ui/pages/modplatform/flame/FlameModel.h
index 536f6add..cab666cc 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.h
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.h
@@ -34,6 +34,7 @@ public:
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool canFetchMore(const QModelIndex & parent) const override;
void fetchMore(const QModelIndex & parent) override;
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
index ec774621..7d2ba2e2 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
@@ -107,41 +107,18 @@ void FlamePage::triggerSearch()
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
}
-void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)
+void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
{
ui->versionSelectionBox->clear();
- if (!first.isValid()) {
+ if (!curr.isValid()) {
if (isOpened) {
dialog->setSuggestedPack();
}
return;
}
- current = listModel->data(first, Qt::UserRole).value<Flame::IndexedPack>();
- QString text = "";
- QString name = current.name;
-
- if (current.websiteUrl.isEmpty())
- text = name;
- else
- text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
- if (!current.authors.empty()) {
- auto authorToStr = [](Flame::ModpackAuthor& author) {
- if (author.url.isEmpty()) {
- return author.name;
- }
- return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
- };
- QStringList authorStrs;
- for (auto& author : current.authors) {
- authorStrs.push_back(authorToStr(author));
- }
- text += "<br>" + tr(" by ") + authorStrs.join(", ");
- }
- text += "<br><br>";
-
- ui->packDescription->setHtml(text + current.description);
+ current = listModel->data(curr, Qt::UserRole).value<Flame::IndexedPack>();
if (current.versionsLoaded == false) {
qDebug() << "Loading flame modpack versions";
@@ -150,7 +127,7 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)
int addonId = current.addonId;
netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response));
- QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId] {
+ QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId, curr] {
if (addonId != current.addonId) {
return; // wrong request
}
@@ -174,6 +151,16 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)
ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl));
}
+ QVariant current_updated;
+ current_updated.setValue(current);
+
+ if (!listModel->setData(curr, current_updated, Qt::UserRole))
+ qWarning() << "Failed to cache versions for the current pack!";
+
+ // TODO: Check whether it's a connection issue or the project disabled 3rd-party distribution.
+ if (current.versionsLoaded && ui->versionSelectionBox->count() < 1) {
+ ui->versionSelectionBox->addItem(tr("No version is available!"), -1);
+ }
suggestCurrent();
});
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
@@ -188,6 +175,13 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)
suggestCurrent();
}
+
+ // TODO: Check whether it's a connection issue or the project disabled 3rd-party distribution.
+ if (current.versionsLoaded && ui->versionSelectionBox->count() < 1) {
+ ui->versionSelectionBox->addItem(tr("No version is available!"), -1);
+ }
+
+ updateUi();
}
void FlamePage::suggestCurrent()
@@ -196,12 +190,12 @@ void FlamePage::suggestCurrent()
return;
}
- if (selectedVersion.isEmpty()) {
+ if (selectedVersion.isEmpty() || selectedVersion == "-1") {
dialog->setSuggestedPack();
return;
}
- dialog->setSuggestedPack(current.name, new InstanceImportTask(selectedVersion));
+ dialog->setSuggestedPack(current.name, new InstanceImportTask(selectedVersion,this));
QString editedLogoName;
editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0);
listModel->getLogo(current.logoName, current.logoUrl,
@@ -217,3 +211,46 @@ void FlamePage::onVersionSelectionChanged(QString data)
selectedVersion = ui->versionSelectionBox->currentData().toString();
suggestCurrent();
}
+
+void FlamePage::updateUi()
+{
+ QString text = "";
+ QString name = current.name;
+
+ if (current.extra.websiteUrl.isEmpty())
+ text = name;
+ else
+ text = "<a href=\"" + current.extra.websiteUrl + "\">" + name + "</a>";
+ if (!current.authors.empty()) {
+ auto authorToStr = [](Flame::ModpackAuthor& author) {
+ if (author.url.isEmpty()) {
+ return author.name;
+ }
+ return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
+ };
+ QStringList authorStrs;
+ for (auto& author : current.authors) {
+ authorStrs.push_back(authorToStr(author));
+ }
+ text += "<br>" + tr(" by ") + authorStrs.join(", ");
+ }
+
+ if(current.extraInfoLoaded) {
+ if (!current.extra.issuesUrl.isEmpty()
+ || !current.extra.sourceUrl.isEmpty()
+ || !current.extra.wikiUrl.isEmpty()) {
+ text += "<br><br>" + tr("External links:") + "<br>";
+ }
+
+ if (!current.extra.issuesUrl.isEmpty())
+ text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current.extra.issuesUrl) + "<br>";
+ if (!current.extra.wikiUrl.isEmpty())
+ text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current.extra.wikiUrl) + "<br>";
+ if (!current.extra.sourceUrl.isEmpty())
+ text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extra.sourceUrl) + "<br>";
+ }
+
+ text += "<hr>";
+
+ ui->packDescription->setHtml(text + current.description);
+}
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.h b/launcher/ui/pages/modplatform/flame/FlamePage.h
index baac57c9..8130e416 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.h
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.h
@@ -79,6 +79,8 @@ public:
virtual bool shouldDisplay() const override;
void retranslate() override;
+ void updateUi();
+
void openedImpl() override;
bool eventFilter(QObject * watched, QEvent * event) override;
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui
index 6d8d8e10..aab16421 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.ui
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui
@@ -1,90 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>FlamePage</class>
- <widget class="QWidget" name="FlamePage">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>837</width>
- <height>685</height>
- </rect>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="1" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="1" column="0">
- <widget class="QListView" name="packView">
- <property name="iconSize">
- <size>
- <width>48</width>
- <height>48</height>
- </size>
- </property>
- <property name="horizontalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOff</enum>
- </property>
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QTextBrowser" name="packDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- <property name="openLinks">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="2" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
- <item row="0" column="2">
- <widget class="QComboBox" name="versionSelectionBox"/>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Version selected:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QComboBox" name="sortByBox"/>
- </item>
- </layout>
- </item>
- <item row="0" column="1">
- <widget class="QPushButton" name="searchButton">
- <property name="text">
- <string>Search</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLineEdit" name="searchEdit">
- <property name="placeholderText">
- <string>Search and filter...</string>
- </property>
- </widget>
- </item>
+ <class>FlamePage</class>
+ <widget class="QWidget" name="FlamePage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>Note: CurseForge allows creators to block access to third-party tools like PolyMC. As such, you may need to manually download some mods to be able to install a modpack.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="searchButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
</layout>
- </widget>
- <tabstops>
- <tabstop>searchEdit</tabstop>
- <tabstop>searchButton</tabstop>
- <tabstop>packView</tabstop>
- <tabstop>packDescription</tabstop>
- <tabstop>sortByBox</tabstop>
- <tabstop>versionSelectionBox</tabstop>
- </tabstops>
- <resources/>
- <connections/>
+ </item>
+ <item row="2" column="0">
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QListView" name="packView">
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTextBrowser" name="packDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0">
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>packView</tabstop>
+ <tabstop>packDescription</tabstop>
+ <tabstop>sortByBox</tabstop>
+ <tabstop>versionSelectionBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
</ui>
diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
index 37244fed..ad15b6e6 100644
--- a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
+++ b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
@@ -122,10 +122,10 @@ void ListModel::requestFinished()
jobPtr.reset();
remainingPacks.clear();
- QJsonParseError parse_error;
+ QJsonParseError parse_error {};
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response;
return;
}
@@ -169,7 +169,7 @@ void ListModel::packRequestFinished()
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response;
return;
}
@@ -184,7 +184,7 @@ void ListModel::packRequestFinished()
catch (const JSONValidationError &e)
{
qDebug() << QString::fromUtf8(response);
- qWarning() << "Error while reading pack manifest from FTB: " << e.cause();
+ qWarning() << "Error while reading pack manifest from ModpacksCH: " << e.cause();
return;
}
@@ -192,7 +192,7 @@ void ListModel::packRequestFinished()
// ignore those "dud" packs.
if (pack.versions.empty())
{
- qWarning() << "FTB Pack " << pack.id << " ignored. reason: lacking any versions";
+ qWarning() << "ModpacksCH Pack " << pack.id << " ignored. reason: lacking any versions";
}
else
{
@@ -270,7 +270,7 @@ void ListModel::requestLogo(QString logo, QString url)
bool stale = entry->isStale();
- NetJob *job = new NetJob(QString("FTB Icon Download %1").arg(logo), APPLICATION->network());
+ NetJob *job = new NetJob(QString("ModpacksCH Icon Download %1").arg(logo), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath();
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
index 63b944c4..2d135e59 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "ListModel.h"
#include "Application.h"
@@ -133,7 +168,7 @@ QVariant ListModel::data(const QModelIndex &index, int role) const
((ListModel *)this)->requestLogo(pack.logo);
return icon;
}
- else if(role == Qt::TextColorRole)
+ else if(role == Qt::ForegroundRole)
{
if(pack.broken)
{
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
index 27a12cda..6ffbd312 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -151,7 +152,7 @@ void Page::openedImpl()
ftbFetchTask->fetch();
ftbPrivatePacks->load();
- ftbFetchTask->fetchPrivate(ftbPrivatePacks->getCurrentPackCodes().toList());
+ ftbFetchTask->fetchPrivate(ftbPrivatePacks->getCurrentPackCodes().values());
initialized = true;
}
suggestCurrent();
@@ -175,7 +176,7 @@ void Page::suggestCurrent()
return;
}
- dialog->setSuggestedPack(selected.name, new PackInstallTask(APPLICATION->network(), selected, selectedVersion));
+ dialog->setSuggestedPack(selected.name + " " + selectedVersion, new PackInstallTask(APPLICATION->network(), selected, selectedVersion));
QString editedLogoName;
if(selected.logo.toLower().startsWith("ftb"))
{
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
index 15e5d432..f4231d8d 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
@@ -25,7 +25,7 @@
<widget class="QTreeView" name="publicPackList">
<property name="maximumSize">
<size>
- <width>250</width>
+ <width>16777215</width>
<height>16777215</height>
</size>
</property>
@@ -51,7 +51,7 @@
<widget class="QTreeView" name="thirdPartyPackList">
<property name="maximumSize">
<size>
- <width>250</width>
+ <width>16777215</width>
<height>16777215</height>
</size>
</property>
@@ -71,7 +71,7 @@
<widget class="QTreeView" name="privatePackList">
<property name="maximumSize">
<size>
- <width>250</width>
+ <width>16777215</width>
<height>16777215</height>
</size>
</property>
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp
index 1d9f4d60..af92e63e 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp
@@ -30,6 +30,11 @@ void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
Modrinth::loadIndexedPack(m, obj);
}
+void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ Modrinth::loadExtraPackData(m, obj);
+}
+
void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance);
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h
index ae7b0bdd..386897fd 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h
@@ -31,6 +31,7 @@ class ListModel : public ModPlatform::ListModel {
private:
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;
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
index d3a1f859..5fa00b9b 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -61,9 +61,9 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instan
connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected);
}
-auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader) const -> bool
+auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool
{
- auto loaderStrings = ModrinthAPI::getModLoaderStrings(loader);
+ auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders);
auto loaderCompatible = false;
for (auto remoteLoader : ver.loaders)
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h
index b1e72bfe..94985f63 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -55,7 +55,7 @@ class ModrinthModPage : public ModPage {
inline auto debugName() const -> QString override { return "Modrinth"; }
inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; };
- auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool override;
+ auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override;
auto shouldDisplay() const -> bool override;
};
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
index 7cacf37a..3633d575 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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
@@ -86,6 +87,7 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian
} else if (role == Qt::DecorationRole) {
if (m_logoMap.contains(pack.iconName)) {
auto icon = m_logoMap.value(pack.iconName);
+ // FIXME: This doesn't really belong here, but Qt doesn't offer a good way right now ;(
auto icon_scaled = QIcon(icon.pixmap(48, 48).scaledToWidth(48));
return icon_scaled;
@@ -102,6 +104,17 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian
return {};
}
+bool ModpackListModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ int pos = index.row();
+ if (pos >= modpacks.size() || pos < 0 || !index.isValid())
+ return false;
+
+ modpacks[pos] = value.value<Modrinth::Modpack>();
+
+ return true;
+}
+
void ModpackListModel::performPaginatedSearch()
{
// TODO: Move to standalone API
@@ -159,15 +172,15 @@ static auto sortFromIndex(int index) -> QString
{
switch(index){
default:
- case 1:
+ case 0:
return "relevance";
- case 2:
+ case 1:
return "downloads";
- case 3:
+ case 2:
return "follows";
- case 4:
+ case 3:
return "newest";
- case 5:
+ case 4:
return "updated";
}
@@ -277,6 +290,10 @@ void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all)
searchState = CanPossiblyFetchMore;
}
+ // When you have a Qt build with assertions turned on, proceeding here will abort the application
+ if (newList.size() == 0)
+ return;
+
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
modpacks.append(newList);
endInsertRows();
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
index 14aa6747..6f33e11e 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
@@ -63,6 +63,7 @@ class ModpackListModel : public QAbstractListModel {
/* Retrieve information from the model at a given index with the given role */
auto data(const QModelIndex& index, int role) const -> QVariant override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role) override;
inline void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; }
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
index 9bd24b57..df29c0c3 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
@@ -101,18 +101,18 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
return QObject::eventFilter(watched, event);
}
-void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)
+void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
{
ui->versionSelectionBox->clear();
- if (!first.isValid()) {
+ if (!curr.isValid()) {
if (isOpened) {
dialog->setSuggestedPack();
}
return;
}
- current = m_model->data(first, Qt::UserRole).value<Modrinth::Modpack>();
+ current = m_model->data(curr, Qt::UserRole).value<Modrinth::Modpack>();
auto name = current.name;
if (!current.extraInfoLoaded) {
@@ -125,7 +125,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)
netJob->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
- QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id] {
+ QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] {
if (id != current.id) {
return; // wrong request?
}
@@ -149,6 +149,13 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)
}
updateUI();
+
+ QVariant current_updated;
+ current_updated.setValue(current);
+
+ if (!m_model->setData(curr, current_updated, Qt::UserRole))
+ qWarning() << "Failed to cache extra info for the current pack!";
+
suggestCurrent();
});
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
@@ -170,7 +177,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)
netJob->addNetAction(
Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
- QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id] {
+ QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] {
if (id != current.id) {
return; // wrong request?
}
@@ -192,9 +199,18 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)
}
for (auto version : current.versions) {
- ui->versionSelectionBox->addItem(version.version, QVariant(version.id));
+ if (!version.name.contains(version.version))
+ ui->versionSelectionBox->addItem(QString("%1 — %2").arg(version.name, version.version), QVariant(version.id));
+ else
+ ui->versionSelectionBox->addItem(version.name, QVariant(version.id));
}
+ QVariant current_updated;
+ current_updated.setValue(current);
+
+ if (!m_model->setData(curr, current_updated, Qt::UserRole))
+ qWarning() << "Failed to cache versions for the current pack!";
+
suggestCurrent();
});
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
@@ -205,7 +221,10 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)
} else {
for (auto version : current.versions) {
- ui->versionSelectionBox->addItem(QString("%1 - %2").arg(version.name, version.version), QVariant(version.id));
+ if (!version.name.contains(version.version))
+ ui->versionSelectionBox->addItem(QString("%1 - %2").arg(version.name, version.version), QVariant(version.id));
+ else
+ ui->versionSelectionBox->addItem(version.name, QVariant(version.id));
}
suggestCurrent();
@@ -224,7 +243,37 @@ void ModrinthPage::updateUI()
// TODO: Implement multiple authors with links
text += "<br>" + tr(" by ") + QString("<a href=%1>%2</a>").arg(std::get<1>(current.author).toString(), std::get<0>(current.author));
- text += "<br>";
+ if (current.extraInfoLoaded) {
+ if (!current.extra.donate.isEmpty()) {
+ text += "<br><br>" + tr("Donate information: ");
+ auto donateToStr = [](Modrinth::DonationData& donate) -> QString {
+ return QString("<a href=\"%1\">%2</a>").arg(donate.url, donate.platform);
+ };
+ QStringList donates;
+ for (auto& donate : current.extra.donate) {
+ donates.append(donateToStr(donate));
+ }
+ text += donates.join(", ");
+ }
+
+ if (!current.extra.issuesUrl.isEmpty()
+ || !current.extra.sourceUrl.isEmpty()
+ || !current.extra.wikiUrl.isEmpty()
+ || !current.extra.discordUrl.isEmpty()) {
+ text += "<br><br>" + tr("External links:") + "<br>";
+ }
+
+ if (!current.extra.issuesUrl.isEmpty())
+ text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current.extra.issuesUrl) + "<br>";
+ if (!current.extra.wikiUrl.isEmpty())
+ text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current.extra.wikiUrl) + "<br>";
+ if (!current.extra.sourceUrl.isEmpty())
+ text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extra.sourceUrl) + "<br>";
+ if (!current.extra.discordUrl.isEmpty())
+ text += "- " + tr("Discord: <a href=%1>%1</a>").arg(current.extra.discordUrl) + "<br>";
+ }
+
+ text += "<hr>";
HoeDown h;
text += h.process(current.extra.body.toUtf8());
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
index 4fb59cdf..6a34701d 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>837</width>
- <height>685</height>
+ <width>800</width>
+ <height>600</height>
</rect>
</property>
<layout class="QVBoxLayout">
@@ -24,6 +24,9 @@
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
</widget>
</item>
<item>
@@ -60,9 +63,6 @@
<height>48</height>
</size>
</property>
- <property name="uniformItemSizes">
- <bool>true</bool>
- </property>
</widget>
</item>
<item>
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
index 9c9d1e75..742f4f2a 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
@@ -217,6 +217,11 @@ void Technic::ListModel::searchRequestFinished()
return;
}
searchState = Finished;
+
+ // When you have a Qt build with assertions turned on, proceeding here will abort the application
+ if (newList.size() == 0)
+ return;
+
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
modpacks.append(newList);
endInsertRows();
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.ui b/launcher/ui/pages/modplatform/technic/TechnicPage.ui
index ca6a9b7e..15bf645f 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.ui
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.ui
@@ -60,7 +60,11 @@
</widget>
</item>
<item row="0" column="1">
- <widget class="QTextBrowser" name="packDescription"/>
+ <widget class="QTextBrowser" name="packDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
</layout>
</item>
diff --git a/launcher/ui/setupwizard/PasteWizardPage.cpp b/launcher/ui/setupwizard/PasteWizardPage.cpp
new file mode 100644
index 00000000..0f47da4b
--- /dev/null
+++ b/launcher/ui/setupwizard/PasteWizardPage.cpp
@@ -0,0 +1,42 @@
+#include "PasteWizardPage.h"
+#include "ui_PasteWizardPage.h"
+
+#include "Application.h"
+#include "net/PasteUpload.h"
+
+PasteWizardPage::PasteWizardPage(QWidget *parent) :
+ BaseWizardPage(parent),
+ ui(new Ui::PasteWizardPage)
+{
+ ui->setupUi(this);
+}
+
+PasteWizardPage::~PasteWizardPage()
+{
+ delete ui;
+}
+
+void PasteWizardPage::initializePage()
+{
+}
+
+bool PasteWizardPage::validatePage()
+{
+ auto s = APPLICATION->settings();
+ QString prevPasteURL = s->get("PastebinURL").toString();
+ s->reset("PastebinURL");
+ if (ui->previousSettingsRadioButton->isChecked())
+ {
+ bool usingDefaultBase = prevPasteURL == PasteUpload::PasteTypes.at(PasteUpload::PasteType::NullPointer).defaultBase;
+ s->set("PastebinType", PasteUpload::PasteType::NullPointer);
+ if (!usingDefaultBase)
+ s->set("PastebinCustomAPIBase", prevPasteURL);
+ }
+
+ return true;
+}
+
+void PasteWizardPage::retranslate()
+{
+ ui->retranslateUi(this);
+}
diff --git a/launcher/ui/setupwizard/PasteWizardPage.h b/launcher/ui/setupwizard/PasteWizardPage.h
new file mode 100644
index 00000000..513a14cb
--- /dev/null
+++ b/launcher/ui/setupwizard/PasteWizardPage.h
@@ -0,0 +1,27 @@
+#ifndef PASTEDEFAULTSCONFIRMATIONWIZARD_H
+#define PASTEDEFAULTSCONFIRMATIONWIZARD_H
+
+#include <QWidget>
+#include "BaseWizardPage.h"
+
+namespace Ui {
+class PasteWizardPage;
+}
+
+class PasteWizardPage : public BaseWizardPage
+{
+ Q_OBJECT
+
+public:
+ explicit PasteWizardPage(QWidget *parent = nullptr);
+ ~PasteWizardPage();
+
+ void initializePage() override;
+ bool validatePage() override;
+ void retranslate() override;
+
+private:
+ Ui::PasteWizardPage *ui;
+};
+
+#endif // PASTEDEFAULTSCONFIRMATIONWIZARD_H
diff --git a/launcher/ui/setupwizard/PasteWizardPage.ui b/launcher/ui/setupwizard/PasteWizardPage.ui
new file mode 100644
index 00000000..247d3a75
--- /dev/null
+++ b/launcher/ui/setupwizard/PasteWizardPage.ui
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PasteWizardPage</class>
+ <widget class="QWidget" name="PasteWizardPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>The default paste service has changed to mclo.gs, please choose what you want to do with your settings.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="defaultSettingsRadioButton">
+ <property name="text">
+ <string>Use new default service</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">buttonGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="previousSettingsRadioButton">
+ <property name="text">
+ <string>Keep previous settings</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">buttonGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>156</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+ <buttongroups>
+ <buttongroup name="buttonGroup"/>
+ </buttongroups>
+</ui>
diff --git a/launcher/ui/widgets/CustomCommands.cpp b/launcher/ui/widgets/CustomCommands.cpp
index 5a718b54..5ab90395 100644
--- a/launcher/ui/widgets/CustomCommands.cpp
+++ b/launcher/ui/widgets/CustomCommands.cpp
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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/CustomCommands.h b/launcher/ui/widgets/CustomCommands.h
index 4a7a17ef..ed10ba95 100644
--- a/launcher/ui/widgets/CustomCommands.h
+++ b/launcher/ui/widgets/CustomCommands.h
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* 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/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp
index 340518b1..f0765909 100644
--- a/launcher/ui/widgets/JavaSettingsWidget.cpp
+++ b/launcher/ui/widgets/JavaSettingsWidget.cpp
@@ -11,6 +11,7 @@
#include <sys.h>
+#include "JavaCommon.h"
#include "java/JavaInstall.h"
#include "java/JavaUtils.h"
#include "FileSystem.h"
@@ -133,6 +134,10 @@ void JavaSettingsWidget::initialize()
void JavaSettingsWidget::refresh()
{
+ if (JavaUtils::getJavaCheckPath().isEmpty()) {
+ JavaCommon::javaCheckNotFound(this);
+ return;
+ }
m_versionWidget->loadList();
}
diff --git a/launcher/ui/widgets/LabeledToolButton.cpp b/launcher/ui/widgets/LabeledToolButton.cpp
index ab2d3278..f52e49c9 100644
--- a/launcher/ui/widgets/LabeledToolButton.cpp
+++ b/launcher/ui/widgets/LabeledToolButton.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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
+ * 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
+ * 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.
+ * 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 <QLabel>
@@ -80,9 +100,7 @@ QSize LabeledToolButton::sizeHint() const
if (popupMode() == MenuButtonPopup)
w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this);
- QSize rawSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this);
- QSize sizeHint = rawSize.expandedTo(QApplication::globalStrut());
- return sizeHint;
+ return style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this);
}
diff --git a/launcher/ui/widgets/LogView.cpp b/launcher/ui/widgets/LogView.cpp
index 26a2a527..9c46438d 100644
--- a/launcher/ui/widgets/LogView.cpp
+++ b/launcher/ui/widgets/LogView.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ * 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 "LogView.h"
#include <QTextBlock>
#include <QScrollBar>
@@ -102,7 +137,7 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last)
{
format.setFont(font.value<QFont>());
}
- auto fg = m_model->data(idx, Qt::TextColorRole);
+ auto fg = m_model->data(idx, Qt::ForegroundRole);
if(fg.isValid())
{
format.setForeground(fg.value<QColor>());
diff --git a/launcher/ui/widgets/MCModInfoFrame.cpp b/launcher/ui/widgets/MCModInfoFrame.cpp
index 8c4bd690..7d78006b 100644
--- a/launcher/ui/widgets/MCModInfoFrame.cpp
+++ b/launcher/ui/widgets/MCModInfoFrame.cpp
@@ -32,7 +32,7 @@ void MCModInfoFrame::updateWithMod(Mod &m)
QString text = "";
QString name = "";
if (m.name().isEmpty())
- name = m.mmc_id();
+ name = m.internal_id();
else
name = m.name();
diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp
index 2af7d731..419ccb66 100644
--- a/launcher/ui/widgets/PageContainer.cpp
+++ b/launcher/ui/widgets/PageContainer.cpp
@@ -66,7 +66,7 @@ public:
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
- const QString pattern = filterRegExp().pattern();
+ const QString pattern = filterRegularExpression().pattern();
const auto model = static_cast<PageModel *>(sourceModel());
const auto page = model->pages().at(sourceRow);
if (!page->shouldDisplay())
@@ -171,7 +171,7 @@ void PageContainer::createUI()
headerHLayout->addSpacerItem(new QSpacerItem(rightMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored));
headerHLayout->setContentsMargins(0, 6, 0, 0);
- m_pageStack->setMargin(0);
+ m_pageStack->setContentsMargins(0, 0, 0, 0);
m_pageStack->addWidget(new QWidget(this));
m_layout = new QGridLayout;
diff --git a/launcher/ui/widgets/VersionListView.cpp b/launcher/ui/widgets/VersionListView.cpp
index aba0b1a1..0e126c65 100644
--- a/launcher/ui/widgets/VersionListView.cpp
+++ b/launcher/ui/widgets/VersionListView.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QHeaderView>
@@ -136,7 +156,7 @@ void VersionListView::paintInfoLabel(QPaintEvent *event) const
auto innerBounds = bounds;
innerBounds.adjust(10, 10, -10, -10);
- QColor background = QApplication::palette().color(QPalette::Foreground);
+ QColor background = QApplication::palette().color(QPalette::WindowText);
QColor foreground = QApplication::palette().color(QPalette::Base);
foreground.setAlpha(190);
painter.setFont(font);
diff --git a/launcher/updater/DownloadTask_test.cpp b/launcher/updater/DownloadTask_test.cpp
deleted file mode 100644
index 8e823a63..00000000
--- a/launcher/updater/DownloadTask_test.cpp
+++ /dev/null
@@ -1,196 +0,0 @@
-#include <QTest>
-#include <QSignalSpy>
-
-#include "TestUtil.h"
-
-#include "updater/GoUpdate.h"
-#include "updater/DownloadTask.h"
-#include "updater/UpdateChecker.h"
-#include <FileSystem.h>
-
-using namespace GoUpdate;
-
-FileSourceList encodeBaseFile(const char *suffix)
-{
- auto base = QDir::currentPath();
- QUrl localFile = QUrl::fromLocalFile(base + suffix);
- QString localUrlString = localFile.toString(QUrl::FullyEncoded);
- auto item = FileSource("http", localUrlString);
- return FileSourceList({item});
-}
-
-Q_DECLARE_METATYPE(VersionFileList)
-Q_DECLARE_METATYPE(Operation)
-
-QDebug operator<<(QDebug dbg, const FileSource &f)
-{
- dbg.nospace() << "FileSource(type=" << f.type << " url=" << f.url
- << " comp=" << f.compressionType << ")";
- return dbg.maybeSpace();
-}
-
-QDebug operator<<(QDebug dbg, const VersionFileEntry &v)
-{
- dbg.nospace() << "VersionFileEntry(path=" << v.path << " mode=" << v.mode
- << " md5=" << v.md5 << " sources=" << v.sources << ")";
- return dbg.maybeSpace();
-}
-
-QDebug operator<<(QDebug dbg, const Operation::Type &t)
-{
- switch (t)
- {
- case Operation::OP_REPLACE:
- dbg << "OP_COPY";
- break;
- case Operation::OP_DELETE:
- dbg << "OP_DELETE";
- break;
- }
- return dbg.maybeSpace();
-}
-
-QDebug operator<<(QDebug dbg, const Operation &u)
-{
- dbg.nospace() << "Operation(type=" << u.type << " file=" << u.source
- << " dest=" << u.destination << " mode=" << u.destinationMode << ")";
- return dbg.maybeSpace();
-}
-
-class DownloadTaskTest : public QObject
-{
- Q_OBJECT
-private
-slots:
- void initTestCase()
- {
- }
- void cleanupTestCase()
- {
- }
-
- void test_parseVersionInfo_data()
- {
- QTest::addColumn<QByteArray>("data");
- QTest::addColumn<VersionFileList>("list");
- QTest::addColumn<QString>("error");
- QTest::addColumn<bool>("ret");
-
- QTest::newRow("one")
- << GET_TEST_FILE("data/1.json")
- << (VersionFileList()
- << VersionFileEntry{"fileOne",
- 493,
- encodeBaseFile("/data/fileOneA"),
- "9eb84090956c484e32cb6c08455a667b"}
- << VersionFileEntry{"fileTwo",
- 644,
- encodeBaseFile("/data/fileTwo"),
- "38f94f54fa3eb72b0ea836538c10b043"}
- << VersionFileEntry{"fileThree",
- 750,
- encodeBaseFile("/data/fileThree"),
- "f12df554b21e320be6471d7154130e70"})
- << QString() << true;
- QTest::newRow("two")
- << GET_TEST_FILE("data/2.json")
- << (VersionFileList()
- << VersionFileEntry{"fileOne",
- 493,
- encodeBaseFile("/data/fileOneB"),
- "42915a71277c9016668cce7b82c6b577"}
- << VersionFileEntry{"fileTwo",
- 644,
- encodeBaseFile("/data/fileTwo"),
- "38f94f54fa3eb72b0ea836538c10b043"})
- << QString() << true;
- }
- void test_parseVersionInfo()
- {
- QFETCH(QByteArray, data);
- QFETCH(VersionFileList, list);
- QFETCH(QString, error);
- QFETCH(bool, ret);
-
- VersionFileList outList;
- QString outError;
- bool outRet = parseVersionInfo(data, outList, outError);
- QCOMPARE(outRet, ret);
- QCOMPARE(outList, list);
- QCOMPARE(outError, error);
- }
-
- void test_processFileLists_data()
- {
- QTest::addColumn<QString>("tempFolder");
- QTest::addColumn<VersionFileList>("currentVersion");
- QTest::addColumn<VersionFileList>("newVersion");
- QTest::addColumn<OperationList>("expectedOperations");
-
- QTemporaryDir tempFolderObj;
- QString tempFolder = tempFolderObj.path();
- // update fileOne, keep fileTwo, remove fileThree
- QTest::newRow("test 1")
- << tempFolder << (VersionFileList()
- << VersionFileEntry{
- "data/fileOne", 493,
- FileSourceList()
- << FileSource(
- "http", "http://host/path/fileOne-1"),
- "9eb84090956c484e32cb6c08455a667b"}
- << VersionFileEntry{
- "data/fileTwo", 644,
- FileSourceList()
- << FileSource(
- "http", "http://host/path/fileTwo-1"),
- "38f94f54fa3eb72b0ea836538c10b043"}
- << VersionFileEntry{
- "data/fileThree", 420,
- FileSourceList()
- << FileSource(
- "http", "http://host/path/fileThree-1"),
- "f12df554b21e320be6471d7154130e70"})
- << (VersionFileList()
- << VersionFileEntry{
- "data/fileOne", 493,
- FileSourceList()
- << FileSource("http",
- "http://host/path/fileOne-2"),
- "42915a71277c9016668cce7b82c6b577"}
- << VersionFileEntry{
- "data/fileTwo", 644,
- FileSourceList()
- << FileSource("http",
- "http://host/path/fileTwo-2"),
- "38f94f54fa3eb72b0ea836538c10b043"})
- << (OperationList()
- << Operation::DeleteOp("data/fileThree")
- << Operation::CopyOp(
- FS::PathCombine(tempFolder,
- QString("data/fileOne").replace("/", "_")),
- "data/fileOne", 493));
- }
- void test_processFileLists()
- {
- QFETCH(QString, tempFolder);
- QFETCH(VersionFileList, currentVersion);
- QFETCH(VersionFileList, newVersion);
- QFETCH(OperationList, expectedOperations);
-
- OperationList operations;
-
- shared_qobject_ptr<QNetworkAccessManager> network = new QNetworkAccessManager();
- processFileLists(currentVersion, newVersion, QDir::currentPath(), tempFolder, new NetJob("Dummy", network), operations);
- qDebug() << (operations == expectedOperations);
- qDebug() << operations;
- qDebug() << expectedOperations;
- QCOMPARE(operations, expectedOperations);
- }
-};
-
-extern "C"
-{
- QTEST_GUILESS_MAIN(DownloadTaskTest)
-}
-
-#include "DownloadTask_test.moc"
diff --git a/launcher/updater/UpdateChecker_test.cpp b/launcher/updater/UpdateChecker_test.cpp
deleted file mode 100644
index ec55a40e..00000000
--- a/launcher/updater/UpdateChecker_test.cpp
+++ /dev/null
@@ -1,149 +0,0 @@
-#include <QTest>
-#include <QSignalSpy>
-
-#include "TestUtil.h"
-#include "updater/UpdateChecker.h"
-
-Q_DECLARE_METATYPE(UpdateChecker::ChannelListEntry)
-
-bool operator==(const UpdateChecker::ChannelListEntry &e1, const UpdateChecker::ChannelListEntry &e2)
-{
- qDebug() << e1.url << "vs" << e2.url;
- return e1.id == e2.id &&
- e1.name == e2.name &&
- e1.description == e2.description &&
- e1.url == e2.url;
-}
-
-QDebug operator<<(QDebug dbg, const UpdateChecker::ChannelListEntry &c)
-{
- dbg.nospace() << "ChannelListEntry(id=" << c.id << " name=" << c.name << " description=" << c.description << " url=" << c.url << ")";
- return dbg.maybeSpace();
-}
-
-QString findTestDataUrl(const char *file)
-{
- return QUrl::fromLocalFile(QFINDTESTDATA(file)).toString();
-}
-
-class UpdateCheckerTest : public QObject
-{
- Q_OBJECT
-private
-slots:
- void initTestCase()
- {
-
- }
- void cleanupTestCase()
- {
-
- }
-
- void tst_ChannelListParsing_data()
- {
- QTest::addColumn<QString>("channel");
- QTest::addColumn<QString>("channelUrl");
- QTest::addColumn<bool>("hasChannels");
- QTest::addColumn<bool>("valid");
- QTest::addColumn<QList<UpdateChecker::ChannelListEntry> >("result");
-
- QTest::newRow("garbage")
- << QString()
- << findTestDataUrl("data/garbageChannels.json")
- << false
- << false
- << QList<UpdateChecker::ChannelListEntry>();
- QTest::newRow("errors")
- << QString()
- << findTestDataUrl("data/errorChannels.json")
- << false
- << true
- << QList<UpdateChecker::ChannelListEntry>();
- QTest::newRow("no channels")
- << QString()
- << findTestDataUrl("data/noChannels.json")
- << false
- << true
- << QList<UpdateChecker::ChannelListEntry>();
- QTest::newRow("one channel")
- << QString("develop")
- << findTestDataUrl("data/oneChannel.json")
- << true
- << true
- << (QList<UpdateChecker::ChannelListEntry>() << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", "http://example.org/stuff"});
- QTest::newRow("several channels")
- << QString("develop")
- << findTestDataUrl("data/channels.json")
- << true
- << true
- << (QList<UpdateChecker::ChannelListEntry>()
- << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", findTestDataUrl("data")}
- << UpdateChecker::ChannelListEntry{"stable", "Stable", "It's stable at least", findTestDataUrl("data")}
- << UpdateChecker::ChannelListEntry{"42", "The Channel", "This is the channel that is going to answer all of your questions", "https://dent.me/tea"});
- }
- void tst_ChannelListParsing()
- {
-
- QFETCH(QString, channel);
- QFETCH(QString, channelUrl);
- QFETCH(bool, hasChannels);
- QFETCH(bool, valid);
- QFETCH(QList<UpdateChecker::ChannelListEntry>, result);
-
- shared_qobject_ptr<QNetworkAccessManager> nam = new QNetworkAccessManager();
- UpdateChecker checker(nam, channelUrl, channel, 0);
-
- QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded()));
- QVERIFY(channelListLoadedSpy.isValid());
-
- checker.updateChanList(false);
-
- if (valid)
- {
- QVERIFY(channelListLoadedSpy.wait());
- QCOMPARE(channelListLoadedSpy.size(), 1);
- }
- else
- {
- channelListLoadedSpy.wait();
- QCOMPARE(channelListLoadedSpy.size(), 0);
- }
-
- QCOMPARE(checker.hasChannels(), hasChannels);
- QCOMPARE(checker.getChannelList(), result);
- }
-
- void tst_UpdateChecking()
- {
- QString channel = "develop";
- QString channelUrl = findTestDataUrl("data/channels.json");
- int currentBuild = 2;
-
- shared_qobject_ptr<QNetworkAccessManager> nam = new QNetworkAccessManager();
- UpdateChecker checker(nam, channelUrl, channel, currentBuild);
-
- QSignalSpy updateAvailableSpy(&checker, SIGNAL(updateAvailable(GoUpdate::Status)));
- QVERIFY(updateAvailableSpy.isValid());
- QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded()));
- QVERIFY(channelListLoadedSpy.isValid());
-
- checker.updateChanList(false);
- QVERIFY(channelListLoadedSpy.wait());
-
- qDebug() << "CWD:" << QDir::current().absolutePath();
- checker.m_channels[0].url = findTestDataUrl("data/");
- checker.checkForUpdate(channel, false);
-
- QVERIFY(updateAvailableSpy.wait());
-
- auto status = updateAvailableSpy.first().first().value<GoUpdate::Status>();
- QCOMPARE(checker.m_channels[0].url, status.newRepoUrl);
- QCOMPARE(3, status.newVersionId);
- QCOMPARE(currentBuild, status.currentVersionId);
- }
-};
-
-QTEST_GUILESS_MAIN(UpdateCheckerTest)
-
-#include "UpdateChecker_test.moc"
diff --git a/launcher/updater/testdata/1.json b/launcher/updater/testdata/1.json
deleted file mode 100644
index 7af7e52d..00000000
--- a/launcher/updater/testdata/1.json
+++ /dev/null
@@ -1,43 +0,0 @@
-{
- "ApiVersion": 0,
- "Id": 1,
- "Name": "1.0.1",
- "Files": [
- {
- "Path": "fileOne",
- "Sources": [
- {
- "SourceType": "http",
- "Url": "@TEST_DATA_URL@/fileOneA"
- }
- ],
- "Executable": true,
- "Perms": 493,
- "MD5": "9eb84090956c484e32cb6c08455a667b"
- },
- {
- "Path": "fileTwo",
- "Sources": [
- {
- "SourceType": "http",
- "Url": "@TEST_DATA_URL@/fileTwo"
- }
- ],
- "Executable": false,
- "Perms": 644,
- "MD5": "38f94f54fa3eb72b0ea836538c10b043"
- },
- {
- "Path": "fileThree",
- "Sources": [
- {
- "SourceType": "http",
- "Url": "@TEST_DATA_URL@/fileThree"
- }
- ],
- "Executable": false,
- "Perms": "750",
- "MD5": "f12df554b21e320be6471d7154130e70"
- }
- ]
-}
diff --git a/launcher/updater/testdata/2.json b/launcher/updater/testdata/2.json
deleted file mode 100644
index 96d430d5..00000000
--- a/launcher/updater/testdata/2.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "ApiVersion": 0,
- "Id": 1,
- "Name": "1.0.1",
- "Files": [
- {
- "Path": "fileOne",
- "Sources": [
- {
- "SourceType": "http",
- "Url": "@TEST_DATA_URL@/fileOneB"
- }
- ],
- "Executable": true,
- "Perms": 493,
- "MD5": "42915a71277c9016668cce7b82c6b577"
- },
- {
- "Path": "fileTwo",
- "Sources": [
- {
- "SourceType": "http",
- "Url": "@TEST_DATA_URL@/fileTwo"
- }
- ],
- "Executable": false,
- "Perms": 644,
- "MD5": "38f94f54fa3eb72b0ea836538c10b043"
- }
- ]
-}
diff --git a/launcher/updater/testdata/channels.json b/launcher/updater/testdata/channels.json
deleted file mode 100644
index 5c6e42cb..00000000
--- a/launcher/updater/testdata/channels.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "format_version": 0,
- "channels": [
- {
- "id": "develop",
- "name": "Develop",
- "description": "The channel called \"develop\"",
- "url": "@TEST_DATA_URL@"
- },
- {
- "id": "stable",
- "name": "Stable",
- "description": "It's stable at least",
- "url": "@TEST_DATA_URL@"
- },
- {
- "id": "42",
- "name": "The Channel",
- "description": "This is the channel that is going to answer all of your questions",
- "url": "https://dent.me/tea"
- }
- ]
-}
diff --git a/launcher/updater/testdata/errorChannels.json b/launcher/updater/testdata/errorChannels.json
deleted file mode 100644
index a2cb2165..00000000
--- a/launcher/updater/testdata/errorChannels.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "format_version": 0,
- "channels": [
- {
- "id": "",
- "name": "Develop",
- "description": "The channel called \"develop\"",
- "url": "http://example.org/stuff"
- },
- {
- "id": "stable",
- "name": "",
- "description": "It's stable at least",
- "url": "ftp://username@host/path/to/stuff"
- },
- {
- "id": "42",
- "name": "The Channel",
- "description": "This is the channel that is going to answer all of your questions",
- "url": ""
- }
- ]
-}
diff --git a/launcher/updater/testdata/fileOneA b/launcher/updater/testdata/fileOneA
deleted file mode 100644
index f2e41136..00000000
--- a/launcher/updater/testdata/fileOneA
+++ /dev/null
@@ -1 +0,0 @@
-stuff
diff --git a/launcher/updater/testdata/fileOneB b/launcher/updater/testdata/fileOneB
deleted file mode 100644
index f9aba922..00000000
--- a/launcher/updater/testdata/fileOneB
+++ /dev/null
@@ -1,3 +0,0 @@
-stuff
-
-more stuff that came in the new version
diff --git a/launcher/updater/testdata/fileThree b/launcher/updater/testdata/fileThree
deleted file mode 100644
index 6353ff16..00000000
--- a/launcher/updater/testdata/fileThree
+++ /dev/null
@@ -1 +0,0 @@
-this is yet another file
diff --git a/launcher/updater/testdata/fileTwo b/launcher/updater/testdata/fileTwo
deleted file mode 100644
index aad9a93a..00000000
--- a/launcher/updater/testdata/fileTwo
+++ /dev/null
@@ -1 +0,0 @@
-some other stuff
diff --git a/launcher/updater/testdata/garbageChannels.json b/launcher/updater/testdata/garbageChannels.json
deleted file mode 100644
index 34437451..00000000
--- a/launcher/updater/testdata/garbageChannels.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "format_version": 0,
- "channels": [
- {
- "id": "develop",
- "name": "Develop",
- "description": "The channel called \"develop\"",
-aa "url": "http://example.org/stuff"
- },
-a "id": "stable",
- "name": "Stable",
- "description": "It's stable at least",
- "url": "ftp://username@host/path/to/stuff"
- },
- {
- "id": "42"f
- "name": "The Channel",
- "description": "This is the channel that is going to answer all of your questions",
- "url": "https://dent.me/tea"
- }
- ]
-}
diff --git a/launcher/updater/testdata/index.json b/launcher/updater/testdata/index.json
deleted file mode 100644
index 867bdcfb..00000000
--- a/launcher/updater/testdata/index.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "ApiVersion": 0,
- "Versions": [
- { "Id": 0, "Name": "1.0.0" },
- { "Id": 1, "Name": "1.0.1" },
- { "Id": 2, "Name": "1.0.2" },
- { "Id": 3, "Name": "1.0.3" }
- ]
-}
diff --git a/launcher/updater/testdata/noChannels.json b/launcher/updater/testdata/noChannels.json
deleted file mode 100644
index 76988982..00000000
--- a/launcher/updater/testdata/noChannels.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "format_version": 0,
- "channels": [
- ]
-}
diff --git a/launcher/updater/testdata/oneChannel.json b/launcher/updater/testdata/oneChannel.json
deleted file mode 100644
index cc8ed255..00000000
--- a/launcher/updater/testdata/oneChannel.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "format_version": 0,
- "channels": [
- {
- "id": "develop",
- "name": "Develop",
- "description": "The channel called \"develop\"",
- "url": "http://example.org/stuff"
- }
- ]
-}
diff --git a/launcher/updater/testdata/tst_DownloadTask-test_writeInstallScript.xml b/launcher/updater/testdata/tst_DownloadTask-test_writeInstallScript.xml
deleted file mode 100644
index 38ecc809..00000000
--- a/launcher/updater/testdata/tst_DownloadTask-test_writeInstallScript.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<update version="3">
- <install>
- <file>
- <source>sourceOne</source>
- <dest>destOne</dest>
- <mode>0777</mode>
- </file>
- <file>
- <source>PolyMC.exe</source>
- <dest>P/o/l/y/M/C/e/x/e</dest>
- <mode>0644</mode>
- </file>
- </install>
- <uninstall>
- <file>toDelete.abc</file>
- </uninstall>
-</update>