aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml2
-rw-r--r--CMakeLists.txt30
-rw-r--r--README.md30
-rw-r--r--default.nix2
-rw-r--r--flake.lock31
-rw-r--r--flake.nix17
-rw-r--r--launcher/Application.cpp52
-rw-r--r--launcher/Application.h15
-rw-r--r--launcher/BaseInstance.cpp6
-rw-r--r--launcher/CMakeLists.txt66
-rw-r--r--launcher/FileSystem.cpp43
-rw-r--r--launcher/FileSystem.h4
-rw-r--r--launcher/InstanceImportTask.cpp96
-rw-r--r--launcher/InstanceList.cpp16
-rw-r--r--launcher/LaunchController.cpp5
-rw-r--r--launcher/MMCZip.cpp14
-rw-r--r--launcher/ModDownloadTask.cpp47
-rw-r--r--launcher/ModDownloadTask.h49
-rw-r--r--launcher/UpdateController.cpp2
-rw-r--r--launcher/icons/IconList.cpp11
-rw-r--r--launcher/icons/IconList.h1
-rw-r--r--launcher/main.cpp4
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp18
-rw-r--r--launcher/minecraft/launch/LauncherPartLaunch.cpp41
-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.cpp89
-rw-r--r--launcher/minecraft/mod/ModFolderModel.h63
-rw-r--r--launcher/minecraft/mod/ModFolderModel_test.cpp44
-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.cpp49
-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/modplatform/ModAPI.h4
-rw-r--r--launcher/modplatform/ModIndex.cpp86
-rw-r--r--launcher/modplatform/ModIndex.h54
-rw-r--r--launcher/modplatform/flame/FlameAPI.h6
-rw-r--r--launcher/modplatform/flame/FlameModIndex.cpp98
-rw-r--r--launcher/modplatform/flame/FlameModIndex.h2
-rw-r--r--launcher/modplatform/flame/FlamePackIndex.cpp27
-rw-r--r--launcher/modplatform/flame/FlamePackIndex.h12
-rw-r--r--launcher/modplatform/helpers/NetworkModAPI.cpp25
-rw-r--r--launcher/modplatform/helpers/NetworkModAPI.h2
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.h14
-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.cpp87
-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/TechnicPackProcessor.cpp21
-rw-r--r--launcher/net/Download.cpp2
-rw-r--r--launcher/net/PasteUpload.cpp7
-rw-r--r--launcher/net/Upload.cpp2
-rw-r--r--launcher/screenshots/ImgurAlbumCreation.cpp2
-rw-r--r--launcher/screenshots/ImgurUpload.cpp3
-rw-r--r--launcher/tasks/SequentialTask.cpp29
-rw-r--r--launcher/tasks/SequentialTask.h9
-rw-r--r--launcher/tasks/Task.h1
-rw-r--r--launcher/tools/MCEditTool.cpp2
-rw-r--r--launcher/ui/GuiUtil.cpp2
-rw-r--r--launcher/ui/MainWindow.cpp68
-rw-r--r--launcher/ui/MainWindow.h54
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.cpp24
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.h3
-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.ui2
-rw-r--r--launcher/ui/pages/global/APIPage.cpp5
-rw-r--r--launcher/ui/pages/global/APIPage.ui129
-rw-r--r--launcher/ui/pages/global/JavaPage.cpp6
-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.cpp21
-rw-r--r--launcher/ui/pages/global/LauncherPage.h6
-rw-r--r--launcher/ui/pages/global/LauncherPage.ui78
-rw-r--r--launcher/ui/pages/instance/LogPage.cpp2
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp6
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.h1
-rw-r--r--launcher/ui/pages/instance/ScreenshotsPage.cpp1
-rw-r--r--launcher/ui/pages/instance/ScreenshotsPage.h1
-rw-r--r--launcher/ui/pages/instance/ServersPage.cpp2
-rw-r--r--launcher/ui/pages/instance/WorldListPage.cpp2
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.cpp11
-rw-r--r--launcher/ui/pages/modplatform/ModModel.cpp75
-rw-r--r--launcher/ui/pages/modplatform/ModModel.h4
-rw-r--r--launcher/ui/pages/modplatform/ModPage.cpp126
-rw-r--r--launcher/ui/pages/modplatform/ModPage.h5
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.cpp68
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.h2
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp37
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.cpp2
-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/ModrinthModel.cpp1
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.h1
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp32
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.ui6
-rw-r--r--launcher/ui/widgets/MCModInfoFrame.cpp2
-rw-r--r--libraries/launcher/net/minecraft/Launcher.java65
-rw-r--r--nix/NIX.md (renamed from packages/nix/NIX.md)0
-rw-r--r--nix/default.nix (renamed from packages/nix/polymc/default.nix)43
-rw-r--r--nix/flake-compat.nix (renamed from packages/nix/flake-compat.nix)4
-rw-r--r--program_info/CMakeLists.txt6
-rw-r--r--program_info/org.polymc.PolyMC.desktop.in2
-rw-r--r--program_info/polymc.manifest.in (renamed from program_info/polymc.manifest)6
-rw-r--r--program_info/polymc.rc.in (renamed from program_info/polymc.rc)6
-rw-r--r--program_info/win_install.nsi.in (renamed from program_info/win_install.nsi)52
123 files changed, 3183 insertions, 928 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 6cbd5c21..db7bd653 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -206,7 +206,7 @@ jobs:
shell: msys2 {0}
run: |
cd ${{ env.INSTALL_DIR }}
- makensis -NOCD "-DVERSION=${{ env.VERSION }}" "-DMUI_ICON=${{ github.workspace }}/program_info/polymc.ico" "-XOutFile ${{ github.workspace }}/PolyMC-Setup.exe" "${{ github.workspace }}/program_info/win_install.nsi"
+ makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
- name: Package (Linux)
if: runner.os == 'Linux' && matrix.appimage != true
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fcc2512d..b09e7fd2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -45,8 +45,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00")
# set CXXFLAGS for build targets
-set(CMAKE_CXX_FLAGS_DEBUG "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
-set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
+set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
option(ENABLE_LTO "Enable Link Time Optimization" off)
@@ -73,7 +72,7 @@ set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 1)
-set(Launcher_VERSION_MINOR 3)
+set(Launcher_VERSION_MINOR 4)
set(Launcher_VERSION_HOTFIX 0)
# Build number
@@ -91,13 +90,6 @@ set(Launcher_META_URL "https://meta.polymc.org/v1/" CACHE STRING "URL to fetch L
# Imgur API Client ID
set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application")
-# MSA Client ID
-set(Launcher_MSA_CLIENT_ID "549033b2-1532-4d4e-ae77-1bbaa46f9d74" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application")
-
-# CurseForge API Key
-# CHANGE THIS IF YOU FORK THIS PROJECT!
-set(Launcher_CURSEFORGE_API_KEY "$2a$10$1Oqr2MX3O4n/ilhFGc597u8tfI3L2Hyr9/rtWDAMRjghSQV2QUuxq" CACHE STRING "CurseForge API Key")
-
# Bug tracker URL
set(Launcher_BUG_TRACKER_URL "https://github.com/PolyMC/PolyMC/issues" CACHE STRING "URL for the bug tracker.")
@@ -119,6 +111,22 @@ set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PolyMCLauncher/" CACHE STRI
set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
set(Launcher_QT_VERSION_MAJOR "5" CACHE STRING "Major Qt version to build against")
+# API Keys
+# NOTE: These API keys are here for convenience. If you rebrand this software or intend to break the terms of service
+# of these platforms, please change these API keys beforehand.
+# Be aware that if you were to use these API keys for malicious purposes they might get revoked, which might cause
+# breakage to thousands of users.
+# If you don't plan to use these features of this software, you can just remove these values.
+
+# By using this key in your builds you accept the terms of use laid down in
+# https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use
+set(Launcher_MSA_CLIENT_ID "549033b2-1532-4d4e-ae77-1bbaa46f9d74" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application")
+
+# By using this key in your builds you accept the terms and conditions laid down in
+# https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions
+# NOTE: CurseForge requires you to change this if you make any kind of derivative work.
+set(Launcher_CURSEFORGE_API_KEY "$2a$10$1Oqr2MX3O4n/ilhFGc597u8tfI3L2Hyr9/rtWDAMRjghSQV2QUuxq" CACHE STRING "CurseForge API Key")
+
#### Check the current Git commit and branch
include(GetGitRevisionDescription)
@@ -128,6 +136,8 @@ message(STATUS "Git commit: ${Launcher_GIT_COMMIT}")
message(STATUS "Git refspec: ${Launcher_GIT_REFSPEC}")
set(Launcher_RELEASE_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}")
+set(Launcher_RELEASE_VERSION_NAME4 "${Launcher_RELEASE_VERSION_NAME}.0")
+set(Launcher_RELEASE_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},${Launcher_VERSION_HOTFIX},0")
string(TIMESTAMP TODAY "%Y-%m-%d")
set(Launcher_RELEASE_TIMESTAMP "${TODAY}")
diff --git a/README.md b/README.md
index c493293d..1e4e5caf 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ If there are any issues with the space or you are using a client that does not s
[![Support](https://img.shields.io/matrix/polymc-support:matrix.org?label=PolyMC%20Support)](https://matrix.to/#/#polymc-support:matrix.org)
[![Voice](https://img.shields.io/matrix/polymc-voice:matrix.org?label=PolyMC%20Voice)](https://matrix.to/#/#polymc-voice:matrix.org)
-we also have a subreddit you can post your issues and suggestions on:
+We also have a subreddit you can post your issues and suggestions on:
[r/PolyMCLauncher](https://www.reddit.com/r/PolyMCLauncher/)
@@ -80,10 +80,32 @@ To modify download information or change packaging information send a pull reque
## Forking/Redistributing/Custom builds policy
-Do whatever you want, we don't care. Just follow the license. If you have any questions about this feel free to ask in an issue.
+We don't care what you do with your fork/custom build as long as you do the following as a basic courtesy:
+- Follow the terms of the [license](LICENSE) (not just a courtesy, but also a legal responsibility)
+- Make it clear that your fork is not PolyMC and is not endorsed by or affiliated with the PolyMC project (https://polymc.org).
+- Go through [CMakeLists.txt](CMakeLists.txt) and change PolyMC's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled).
+
+If you have any questions or want any clarification on the above conditions please make an issue and ask us.
+
+Be aware that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions:
+ - [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use)
+ - [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions)
+
+If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file by setting them to an empty string (`""`).
All launcher code is available under the GPL-3.0-only license.
-[Source for the website](https://github.com/PolyMC/polymc.github.io) is hosted under the AGPL-3.0-or-later License.
-
The logo and related assets are under the CC BY-SA 4.0 license.
+
+## Sponsors
+Thank you to all our generous backers over at Open Collective! Support PolyMC by [becoming a backer](https://opencollective.com/polymc).
+
+[![OpenCollective Backers](https://opencollective.com/polymc/backers.svg?width=890&limit=1000)](https://opencollective.com/polymc#backers)
+
+Also, thanks to JetBrains for providing us a few licenses for all their products, as part of their [Open Source program](https://www.jetbrains.com/opensource/).
+
+[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/opensource/)
+
+Additionally, thanks to the awesome people over at [MacStadium](https://www.macstadium.com/), for providing M1-Macs for development purposes!
+
+<a href="https://www.macstadium.com"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="Powered by MacStadium" width="300"></a>
diff --git a/default.nix b/default.nix
index 5abfc1bd..146942d5 100644
--- a/default.nix
+++ b/default.nix
@@ -1 +1 @@
-(import packages/nix/flake-compat.nix).defaultNix
+(import nix/flake-compat.nix).defaultNix
diff --git a/flake.lock b/flake.lock
index e3c490fd..120b11c5 100644
--- a/flake.lock
+++ b/flake.lock
@@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
- "lastModified": 1648199409,
- "narHash": "sha256-JwPKdC2PoVBkG6E+eWw3j6BMR6sL3COpYWfif7RVb8Y=",
+ "lastModified": 1650374568,
+ "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
"owner": "edolstra",
"repo": "flake-compat",
- "rev": "64a525ee38886ab9028e6f61790de0832aa3ef03",
+ "rev": "b4a34015c698c7793d592d66adbab377907a2be8",
"type": "github"
},
"original": {
@@ -34,11 +34,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1648219316,
- "narHash": "sha256-Ctij+dOi0ZZIfX5eMhgwugfvB+WZSrvVNAyAuANOsnQ=",
+ "lastModified": 1654665288,
+ "narHash": "sha256-7blJpfoZEu7GKb84uh3io/5eSJNdaagXD9d15P9iQMs=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "30d3d79b7d3607d56546dd2a6b49e156ba0ec634",
+ "rev": "43ecbe7840d155fa933ee8a500fb00dbbc651fc8",
"type": "github"
},
"original": {
@@ -48,28 +48,11 @@
"type": "github"
}
},
- "quazip": {
- "flake": false,
- "locked": {
- "lastModified": 1643049383,
- "narHash": "sha256-LcJY6yd6GyeL7X5MP4L94diceM1TYespWByliBsjK98=",
- "owner": "stachenov",
- "repo": "quazip",
- "rev": "09ec1d10c6d627f895109b21728dda000cbfa7d1",
- "type": "github"
- },
- "original": {
- "owner": "stachenov",
- "repo": "quazip",
- "type": "github"
- }
- },
"root": {
"inputs": {
"flake-compat": "flake-compat",
"libnbtplusplus": "libnbtplusplus",
- "nixpkgs": "nixpkgs",
- "quazip": "quazip"
+ "nixpkgs": "nixpkgs"
}
}
},
diff --git a/flake.nix b/flake.nix
index e59d6be8..b378fbb0 100644
--- a/flake.nix
+++ b/flake.nix
@@ -5,10 +5,9 @@
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
libnbtplusplus = { url = "github:multimc/libnbtplusplus"; flake = false; };
- quazip = { url = "github:stachenov/quazip"; flake = false; };
};
- outputs = { self, nixpkgs, libnbtplusplus, quazip, ... }:
+ outputs = { self, nixpkgs, libnbtplusplus, ... }:
let
# Generate a user-friendly version number.
version = builtins.substring 0 8 self.lastModifiedDate;
@@ -23,11 +22,17 @@
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
in
{
- packages = forAllSystems (system: { polymc = pkgs.${system}.libsForQt5.callPackage ./packages/nix/polymc { inherit version self quazip libnbtplusplus; }; });
- defaultPackage = forAllSystems (system: self.packages.${system}.polymc);
+ packages = forAllSystems (system: rec {
+ polymc = pkgs.${system}.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; };
+ polymc-qt6 = pkgs.${system}.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; };
+
+ default = polymc;
+ });
- apps = forAllSystems (system: { polymc = { type = "app"; program = "${self.defaultPackage.${system}}/bin/polymc"; }; });
- defaultApp = forAllSystems (system: self.apps.${system}.polymc);
+ defaultPackage = forAllSystems (system: self.packages.${system}.default);
+
+ apps = forAllSystems (system: rec { polymc = { type = "app"; program = "${self.defaultPackage.${system}}/bin/polymc"; }; default = polymc; });
+ defaultApp = forAllSystems (system: self.apps.${system}.default);
overlay = final: prev: { polymc = self.defaultPackage.${final.system}; };
};
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index ba4096b6..ab3110a3 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -154,6 +154,7 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt
fflush(stderr);
}
+#ifdef LAUNCHER_WITH_UPDATER
QString getIdealPlatform(QString currentPlatform) {
auto info = Sys::getKernelInfo();
switch(info.kernelType) {
@@ -192,6 +193,7 @@ QString getIdealPlatform(QString currentPlatform) {
}
return currentPlatform;
}
+#endif
}
@@ -643,6 +645,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", "");
@@ -708,6 +713,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Custom MSA credentials
m_settings->registerSetting("MSAClientIDOverride", "");
m_settings->registerSetting("CFKeyOverride", "");
+ m_settings->registerSetting("UserAgentOverride", "");
// Init page provider
{
@@ -750,6 +756,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Translations loaded.";
}
+#ifdef LAUNCHER_WITH_UPDATER
// initialize the updater
if(BuildConfig.UPDATER_ENABLED)
{
@@ -759,6 +766,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD));
qDebug() << "<> Updater started.";
}
+#endif
// Instance icons
{
@@ -871,6 +879,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)
{
@@ -954,6 +968,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;
@@ -1383,7 +1412,9 @@ MainWindow* Application::showMainWindow(bool minimized)
}
m_mainWindow->checkInstancePathForProblems();
+#ifdef LAUNCHER_WITH_UPDATER
connect(this, &Application::updateAllowedChanged, m_mainWindow, &MainWindow::updatesAllowedChanged);
+#endif
connect(m_mainWindow, &MainWindow::isClosing, this, &Application::on_windowClose);
m_openWindows++;
}
@@ -1553,3 +1584,24 @@ QString Application::getCurseKey()
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 3129b4fb..09007160 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -42,7 +42,10 @@
#include <QIcon>
#include <QDateTime>
#include <QUrl>
+
+#ifdef LAUNCHER_WITH_UPDATER
#include <updater/GoUpdate.h>
+#endif
#include <BaseInstance.h>
@@ -94,6 +97,8 @@ public:
Application(int &argc, char **argv);
virtual ~Application();
+ bool event(QEvent* event) override;
+
std::shared_ptr<SettingsObject> settings() const {
return m_settings;
}
@@ -156,6 +161,8 @@ public:
QString getMSAClientID();
QString getCurseKey();
+ QString getUserAgent();
+ QString getUserAgentUncached();
/// this is the root of the 'installation'. Used for automatic updates
const QString &root() {
@@ -181,6 +188,10 @@ signals:
void globalSettingsAboutToOpen();
void globalSettingsClosed();
+#ifdef Q_OS_MACOS
+ void clickedOnDock();
+#endif
+
public slots:
bool launch(
InstancePtr instance,
@@ -238,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/BaseInstance.cpp b/launcher/BaseInstance.cpp
index c9394d3f..0efbdddc 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -60,7 +60,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);
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index b3af12a6..b8db803b 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -156,27 +156,29 @@ set(LAUNCH_SOURCES
launch/LogModel.h
)
-# Old update system
-set(UPDATE_SOURCES
- updater/GoUpdate.h
- updater/GoUpdate.cpp
- updater/UpdateChecker.h
- updater/UpdateChecker.cpp
- updater/DownloadTask.h
- updater/DownloadTask.cpp
-)
-
-add_unit_test(UpdateChecker
- SOURCES updater/UpdateChecker_test.cpp
- LIBS Launcher_logic
- DATA updater/testdata
+if (Launcher_UPDATER_BASE)
+ set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_WITH_UPDATER ${Launcher_APP_BINARY_DEFS}")
+ # Old update system
+ set(UPDATE_SOURCES
+ updater/GoUpdate.h
+ updater/GoUpdate.cpp
+ updater/UpdateChecker.h
+ updater/UpdateChecker.cpp
+ updater/DownloadTask.h
+ updater/DownloadTask.cpp
)
-add_unit_test(DownloadTask
- SOURCES updater/DownloadTask_test.cpp
- LIBS Launcher_logic
- DATA updater/testdata
+ 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
)
+endif()
# Backend for the news bar... there's usually no news.
set(NEWS_SOURCES
@@ -324,19 +326,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
@@ -499,6 +504,9 @@ set(META_SOURCES
)
set(API_SOURCES
+ modplatform/ModIndex.h
+ modplatform/ModIndex.cpp
+
modplatform/ModAPI.h
modplatform/flame/FlameAPI.h
@@ -545,6 +553,17 @@ set(MODPACKSCH_SOURCES
modplatform/modpacksch/FTBPackManifest.cpp
)
+set(PACKWIZ_SOURCES
+ modplatform/packwiz/Packwiz.h
+ modplatform/packwiz/Packwiz.cpp
+)
+
+add_unit_test(Packwiz
+ SOURCES modplatform/packwiz/Packwiz_test.cpp
+ DATA modplatform/packwiz/testdata
+ LIBS Launcher_logic
+ )
+
set(TECHNIC_SOURCES
modplatform/technic/SingleZipPackInstallTask.h
modplatform/technic/SingleZipPackInstallTask.cpp
@@ -598,6 +617,7 @@ set(LOGIC_SOURCES
${FLAME_SOURCES}
${MODRINTH_SOURCES}
${MODPACKSCH_SOURCES}
+ ${PACKWIZ_SOURCES}
${TECHNIC_SOURCES}
${ATLAUNCHER_SOURCES}
)
@@ -963,7 +983,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
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 6de20de6..3837d75f 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -454,4 +454,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..bc942ab3 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -124,4 +124,8 @@ QString getDesktopDir();
// 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/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index 68a497cc..d5684805 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -164,14 +164,14 @@ void InstanceImportTask::processZipPack()
QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
- if (!mmcRoot.isEmpty())
+ if (!mmcRoot.isNull())
{
// process as MultiMC instance/pack
qDebug() << "MultiMC:" << mmcRoot;
root = mmcRoot;
m_modpackType = ModpackType::MultiMC;
}
- else if(!flameRoot.isEmpty())
+ else if(!flameRoot.isNull())
{
// process as Flame pack
qDebug() << "Flame:" << flameRoot;
@@ -582,6 +582,7 @@ void InstanceImportTask::processMultiMC()
emitSucceeded();
}
+// https://docs.modrinth.com/docs/modpacks/format_definition/
void InstanceImportTask::processModrinth()
{
std::vector<Modrinth::File> files;
@@ -599,26 +600,30 @@ void InstanceImportTask::processModrinth()
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
bool had_optional = false;
- for (auto& modInfo : jsonFiles) {
+ for (auto modInfo : jsonFiles) {
Modrinth::File file;
file.path = Json::requireString(modInfo, "path");
auto env = Json::ensureObject(modInfo, "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();
- }
+ // '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(modInfo, "hashes");
@@ -639,12 +644,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(modInfo, "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);
}
@@ -675,16 +696,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);
@@ -709,13 +740,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/InstanceList.cpp b/launcher/InstanceList.cpp
index 847d897e..3e3c81f7 100644
--- a/launcher/InstanceList.cpp
+++ b/launcher/InstanceList.cpp
@@ -547,8 +547,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/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/MMCZip.cpp b/launcher/MMCZip.cpp
index 8591fcc0..627ceaf1 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;
}
}
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/UpdateController.cpp b/launcher/UpdateController.cpp
index 646f8e57..c27fe772 100644
--- a/launcher/UpdateController.cpp
+++ b/launcher/UpdateController.cpp
@@ -93,7 +93,7 @@ void UpdateController::installUpdates()
qDebug() << "Installing updates.";
#ifdef Q_OS_WIN
QString finishCmd = QApplication::applicationFilePath();
-#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined (Q_OS_OPENBSD)
+#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
QString finishCmd = FS::PathCombine(m_root, BuildConfig.LAUNCHER_NAME);
#elif defined Q_OS_MAC
QString finishCmd = QApplication::applicationFilePath();
diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp
index 0ddfae55..d426aa80 100644
--- a/launcher/icons/IconList.cpp
+++ b/launcher/icons/IconList.cpp
@@ -56,6 +56,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);
@@ -141,6 +150,8 @@ void IconList::directoryChanged(const QString &path)
emit iconUpdated(key);
}
}
+
+ sortIconList();
}
void IconList::fileChanged(const QString &path)
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/main.cpp b/launcher/main.cpp
index 85c5fdee..3d25b4ff 100644
--- a/launcher/main.cpp
+++ b/launcher/main.cpp
@@ -27,10 +27,6 @@ int main(int argc, char *argv[])
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
Application app(argc, argv);
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 61326fac..7e72601f 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -168,6 +168,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));
}
@@ -659,23 +661,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)";
}
}
@@ -1013,7 +1015,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 +1027,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/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp
index d7010355..427bc32b 100644
--- a/launcher/minecraft/launch/LauncherPartLaunch.cpp
+++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp
@@ -1,21 +1,42 @@
-/* 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"
#include <QStandardPaths>
+#include <QRegularExpression>
#include "launch/LaunchTask.h"
#include "minecraft/MinecraftInstance.h"
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..ded2d3a2 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,10 +102,14 @@ 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;
}
@@ -153,7 +180,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 +201,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++;
@@ -333,8 +360,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 +412,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 +467,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..34a3b83a 100644
--- a/launcher/minecraft/mod/ModFolderModel_test.cpp
+++ b/launcher/minecraft/mod/ModFolderModel_test.cpp
@@ -1,3 +1,37 @@
+// 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>
@@ -32,8 +66,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 +78,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..018bc6e3
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp
@@ -0,0 +1,49 @@
+// 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"
+
+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));
+ }
+}
+
+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/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h
index 4230df0b..91b760df 100644
--- a/launcher/modplatform/ModAPI.h
+++ b/launcher/modplatform/ModAPI.h
@@ -7,6 +7,7 @@
namespace ModPlatform {
class ListModel;
+struct IndexedPack;
}
class ModAPI {
@@ -35,6 +36,7 @@ class ModAPI {
};
virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0;
+ virtual void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) = 0;
struct VersionSearchArgs {
@@ -68,7 +70,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/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h
index 8bb33d47..424153d2 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 {
@@ -41,6 +42,11 @@ class FlameAPI : public NetworkModAPI {
.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()) : "";
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 bece7843..ba1622d1 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, "issuesUrl");
+ 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)
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/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/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h
index 79bc5175..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>
@@ -75,15 +76,20 @@ class ModrinthAPI : public NetworkModAPI {
.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.loaders).join("\",\""));
+ .arg(args.addonId,
+ getGameVersionsString(args.mcVersions),
+ getModLoaderStrings(args.loaders).join("\",\""));
};
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString
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..3d47f9f7
--- /dev/null
+++ b/launcher/modplatform/packwiz/Packwiz_test.cpp
@@ -0,0 +1,87 @@
+// 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"
+#include "TestUtil.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/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp
index 782fb9b2..471b4a2f 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));
+ static QStringList possibleLoaders{
+ "net.minecraftforge:minecraftforge:",
+ "net.fabricmc:fabric-loader:",
+ "org.quiltmc:quilt-loader:"
+ };
+ for (const auto& loader : possibleLoaders)
+ {
+ if (libraryName.startsWith(loader))
+ {
+ auto loaderComponent = loader.chopped(1).replace(":", ".");
+ components->setComponentVersion(loaderComponent, libraryName.section(':', 2));
+ break;
+ }
+ }
}
}
}
diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp
index 966d4126..d93eb088 100644
--- a/launcher/net/Download.cpp
+++ b/launcher/net/Download.cpp
@@ -116,7 +116,7 @@ void Download::executeTask()
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", APPLICATION->getCurseKey().toUtf8());
};
diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp
index 3855190a..7438e1a1 100644
--- a/launcher/net/PasteUpload.cpp
+++ b/launcher/net/PasteUpload.cpp
@@ -3,6 +3,7 @@
* 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
@@ -43,6 +44,8 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QFile>
+#include <QHttpPart>
+#include <QUrlQuery>
std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = {
{{"0x0.st", "https://0x0.st", ""},
@@ -71,7 +74,7 @@ void PasteUpload::executeTask()
QNetworkRequest request{QUrl(m_uploadUrl)};
QNetworkReply *rep{};
- request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
+ request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
switch (m_pasteType) {
case NullPointer: {
@@ -91,7 +94,7 @@ void PasteUpload::executeTask()
break;
}
case Hastebin: {
- request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
+ request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());
rep = APPLICATION->network()->post(request, m_text);
break;
}
diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp
index bbd27390..c9942a8d 100644
--- a/launcher/net/Upload.cpp
+++ b/launcher/net/Upload.cpp
@@ -173,7 +173,7 @@ namespace Net {
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", APPLICATION->getCurseKey().toUtf8());
}
diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp
index 7afdc5cc..04e26ea2 100644
--- a/launcher/screenshots/ImgurAlbumCreation.cpp
+++ b/launcher/screenshots/ImgurAlbumCreation.cpp
@@ -55,7 +55,7 @@ void ImgurAlbumCreation::executeTask()
{
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");
diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp
index fbcfb95f..9aeb6fb8 100644
--- a/launcher/screenshots/ImgurUpload.cpp
+++ b/launcher/screenshots/ImgurUpload.cpp
@@ -35,6 +35,7 @@
#include "ImgurUpload.h"
#include "BuildConfig.h"
+#include "Application.h"
#include <QNetworkRequest>
#include <QHttpMultiPart>
@@ -56,7 +57,7 @@ void ImgurUpload::executeTask()
finished = false;
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");
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.h b/launcher/tasks/Task.h
index f7765c3d..aafaf68c 100644
--- a/launcher/tasks/Task.h
+++ b/launcher/tasks/Task.h
@@ -92,6 +92,7 @@ class Task : public QObject {
void aborted();
void failed(QString reason);
void status(QString status);
+ void stepStatus(QString status);
public slots:
virtual void start();
diff --git a/launcher/tools/MCEditTool.cpp b/launcher/tools/MCEditTool.cpp
index 2c1ec613..21e1a3b0 100644
--- a/launcher/tools/MCEditTool.cpp
+++ b/launcher/tools/MCEditTool.cpp
@@ -52,7 +52,7 @@ QString MCEditTool::getProgramPath()
#else
const QString mceditPath = path();
QDir mceditDir(mceditPath);
-#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
+#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (mceditDir.exists("mcedit.sh"))
{
return mceditDir.absoluteFilePath("mcedit.sh");
diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp
index 320f1502..b1ea5ee9 100644
--- a/launcher/ui/GuiUtil.cpp
+++ b/launcher/ui/GuiUtil.cpp
@@ -2,6 +2,7 @@
/*
* 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
@@ -38,6 +39,7 @@
#include <QClipboard>
#include <QApplication>
#include <QFileDialog>
+#include <QStandardPaths>
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/CustomMessageBox.h"
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 7e152b96..210442df 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -1,21 +1,42 @@
-/* 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"
@@ -1010,6 +1031,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
}
+#ifdef LAUNCHER_WITH_UPDATER
if(BuildConfig.UPDATER_ENABLED)
{
bool updatesAllowed = APPLICATION->updatesAreAllowed();
@@ -1028,6 +1050,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), false);
}
}
+#endif
setSelectedInstanceById(APPLICATION->settings()->get("SelectedInstance").toString());
@@ -1337,6 +1360,7 @@ void MainWindow::repopulateAccountsMenu()
ui->profileMenu->addAction(ui->actionManageAccounts);
}
+#ifdef LAUNCHER_WITH_UPDATER
void MainWindow::updatesAllowedChanged(bool allowed)
{
if(!BuildConfig.UPDATER_ENABLED)
@@ -1345,6 +1369,7 @@ void MainWindow::updatesAllowedChanged(bool allowed)
}
ui->actionCheckUpdate->setEnabled(allowed);
}
+#endif
/*
* Assumes the sender is a QAction
@@ -1450,6 +1475,7 @@ void MainWindow::updateNewsLabel()
}
}
+#ifdef LAUNCHER_WITH_UPDATER
void MainWindow::updateAvailable(GoUpdate::Status status)
{
if(!APPLICATION->updatesAreAllowed())
@@ -1475,6 +1501,7 @@ void MainWindow::updateNotAvailable()
UpdateDialog dlg(false, this);
dlg.exec();
}
+#endif
QList<int> stringToIntList(const QString &string)
{
@@ -1496,6 +1523,7 @@ QString intListToString(const QList<int> &list)
return slist.join(',');
}
+#ifdef LAUNCHER_WITH_UPDATER
void MainWindow::downloadUpdates(GoUpdate::Status status)
{
if(!APPLICATION->updatesAreAllowed())
@@ -1529,6 +1557,7 @@ void MainWindow::downloadUpdates(GoUpdate::Status status)
CustomMessageBox::selectable(this, tr("Error"), updateTask.failReason(), QMessageBox::Warning)->show();
}
}
+#endif
void MainWindow::onCatToggled(bool state)
{
@@ -1841,6 +1870,7 @@ void MainWindow::on_actionConfig_Folder_triggered()
}
}
+#ifdef LAUNCHER_WITH_UPDATER
void MainWindow::checkForUpdates()
{
if(BuildConfig.UPDATER_ENABLED)
@@ -1853,6 +1883,7 @@ void MainWindow::checkForUpdates()
qWarning() << "Updater not set up. Cannot check for updates.";
}
}
+#endif
void MainWindow::on_actionSettings_triggered()
{
@@ -2101,6 +2132,9 @@ 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)
@@ -2127,6 +2161,8 @@ 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
{
@@ -2216,3 +2252,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..6c64756f 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
@@ -54,7 +78,9 @@ public:
void checkInstancePathForProblems();
+#ifdef LAUNCHER_WITH_UPDATER
void updatesAllowedChanged(bool allowed);
+#endif
void droppedURLs(QList<QUrl> urls);
signals:
@@ -100,7 +126,9 @@ private slots:
void on_actionViewCentralModsFolder_triggered();
+#ifdef LAUNCHER_WITH_UPDATER
void checkForUpdates();
+#endif
void on_actionSettings_triggered();
@@ -167,9 +195,11 @@ private slots:
void startTask(Task *task);
+#ifdef LAUNCHER_WITH_UPDATER
void updateAvailable(GoUpdate::Status status);
void updateNotAvailable();
+#endif
void defaultAccountChanged();
@@ -179,10 +209,12 @@ private slots:
void updateNewsLabel();
+#ifdef LAUNCHER_WITH_UPDATER
/*!
* Runs the DownloadTask and installs updates.
*/
void downloadUpdates(GoUpdate::Status status);
+#endif
void konamiTriggered();
@@ -192,6 +224,8 @@ private slots:
void keyReleaseEvent(QKeyEvent *event) override;
#endif
+ void refreshCurrentInstance(bool running);
+
private:
void retranslateUi();
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/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.ui b/launcher/ui/dialogs/ScrollMessageBox.ui
index 885fbfd2..299d2ecc 100644
--- a/launcher/ui/dialogs/ScrollMessageBox.ui
+++ b/launcher/ui/dialogs/ScrollMessageBox.ui
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
- <string>ScrollMessageBox</string>
+ <string notr="true">ScrollMessageBox</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp
index 5d812d07..b889e6f7 100644
--- a/launcher/ui/pages/global/APIPage.cpp
+++ b/launcher/ui/pages/global/APIPage.cpp
@@ -75,9 +75,9 @@ APIPage::APIPage(QWidget *parent) :
// 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->tabWidget->tabBar()->hide();
ui->metaURL->setPlaceholderText(BuildConfig.META_URL);
+ ui->userAgentLineEdit->setPlaceholderText(BuildConfig.USER_AGENT);
loadSettings();
@@ -139,6 +139,8 @@ void APIPage::loadSettings()
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()
@@ -167,6 +169,7 @@ void APIPage::applySettings()
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.ui b/launcher/ui/pages/global/APIPage.ui
index 5c927391..5327771c 100644
--- a/launcher/ui/pages/global/APIPage.ui
+++ b/launcher/ui/pages/global/APIPage.ui
@@ -30,7 +30,7 @@
</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>
@@ -86,15 +86,15 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_msa">
+ <widget class="QGroupBox" name="groupBox_meta">
<property name="title">
- <string>&amp;Microsoft Authentication</string>
+ <string>Meta&amp;data Server</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_4">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
<item>
- <widget class="QLabel" name="label_3">
+ <widget class="QLabel" name="label_5">
<property name="text">
- <string>Note: you probably don't need to set this if logging in via Microsoft Authentication already works.</string>
+ <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>
@@ -105,16 +105,16 @@
</widget>
</item>
<item>
- <widget class="QLineEdit" name="msaClientID">
+ <widget class="QLineEdit" name="metaURL">
<property name="placeholderText">
- <string>(Default)</string>
+ <string/>
</property>
</widget>
</item>
<item>
- <widget class="QLabel" name="label_4">
+ <widget class="QLabel" name="label_6">
<property name="text">
- <string>Enter a custom client ID for Microsoft Authentication here. </string>
+ <string>Enter a custom URL for meta here.</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
@@ -131,15 +131,35 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_meta">
+ <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>Meta&amp;data Server</string>
+ <string>&amp;Microsoft Authentication</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
<item>
- <widget class="QLabel" name="label_5">
+ <widget class="QLabel" name="label_3">
<property name="text">
- <string>You can set this to a third-party metadata server to use patched libraries or other hacks.</string>
+ <string>Note: you probably don't need to set this if logging in via Microsoft Authentication already works.</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
@@ -150,16 +170,16 @@
</widget>
</item>
<item>
- <widget class="QLineEdit" name="metaURL">
+ <widget class="QLineEdit" name="msaClientID">
<property name="placeholderText">
- <string/>
+ <string>(Default)</string>
</property>
</widget>
</item>
<item>
- <widget class="QLabel" name="label_6">
+ <widget class="QLabel" name="label_4">
<property name="text">
- <string>Enter a custom URL for meta here.</string>
+ <string>Enter a custom client ID for Microsoft Authentication here. </string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
@@ -183,25 +203,15 @@
<property name="title">
<string>&amp;CurseForge Core API</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_6">
- <item>
+ <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>
- <widget class="QLineEdit" name="curseKey">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="placeholderText">
- <string>(Default)</string>
- </property>
- </widget>
- </item>
- <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>
@@ -217,6 +227,16 @@
</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>
@@ -235,6 +255,51 @@
</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>
diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp
index b5e8de6c..025771e8 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,7 +120,7 @@ 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());
}
@@ -166,7 +166,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 af2e2cd1..edbf609f 100644
--- a/launcher/ui/pages/global/LauncherPage.cpp
+++ b/launcher/ui/pages/global/LauncherPage.cpp
@@ -78,6 +78,7 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch
m_languageModel = APPLICATION->translations();
loadSettings();
+#ifdef LAUNCHER_WITH_UPDATER
if(BuildConfig.UPDATER_ENABLED)
{
QObject::connect(APPLICATION->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &LauncherPage::refreshUpdateChannelList);
@@ -90,11 +91,9 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch
{
APPLICATION->updateChecker()->updateChanList(false);
}
+ ui->updateSettingsBox->setHidden(false);
}
- else
- {
- ui->updateSettingsBox->setHidden(true);
- }
+#endif
connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview()));
connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview()));
}
@@ -184,6 +183,12 @@ void LauncherPage::on_modsDirBrowseBtn_clicked()
}
}
+void LauncherPage::on_metadataDisableBtn_clicked()
+{
+ ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
+}
+
+#ifdef LAUNCHER_WITH_UPDATER
void LauncherPage::refreshUpdateChannelList()
{
// Stop listening for selection changes. It's going to change a lot while we update it and
@@ -255,6 +260,7 @@ void LauncherPage::refreshUpdateChannelDesc()
m_currentUpdateChannel = selected.id;
}
}
+#endif
void LauncherPage::applySettings()
{
@@ -338,6 +344,9 @@ void LauncherPage::applySettings()
s->set("InstSortMode", "Name");
break;
}
+
+ // Mods
+ s->set("ModMetadataDisabled", ui->metadataDisableBtn->isChecked());
}
void LauncherPage::loadSettings()
{
@@ -440,6 +449,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..ccfd7e9e 100644
--- a/launcher/ui/pages/global/LauncherPage.h
+++ b/launcher/ui/pages/global/LauncherPage.h
@@ -88,7 +88,9 @@ slots:
void on_instDirBrowseBtn_clicked();
void on_modsDirBrowseBtn_clicked();
void on_iconsDirBrowseBtn_clicked();
+ void on_metadataDisableBtn_clicked();
+#ifdef LAUNCHER_WITH_UPDATER
/*!
* Updates the list of update channels in the combo box.
*/
@@ -99,13 +101,13 @@ slots:
*/
void refreshUpdateChannelDesc();
+ void updateChannelSelectionChanged(int index);
+#endif
/*!
* Updates the font preview
*/
void refreshFontPreview();
- void updateChannelSelectionChanged(int index);
-
private:
Ui::LauncherPage *ui;
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index ae7eb73f..ceb68c5b 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -50,6 +50,9 @@
<property name="title">
<string>Update Settings</string>
</property>
+ <property name="visible">
+ <bool>false</bool>
+ </property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QCheckBox" name="autoUpdateCheckBox">
@@ -94,19 +97,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 +111,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/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp
index 30a8735f..a6c98c08 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
@@ -39,6 +40,7 @@
#include "Application.h"
#include <QIcon>
+#include <QIdentityProxyModel>
#include <QScrollBar>
#include <QShortcut>
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp
index 5574f9d2..d929a0ea 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
@@ -402,6 +403,10 @@ void ModFolderPage::on_actionInstall_mods_triggered()
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(); }
@@ -411,6 +416,7 @@ void ModFolderPage::on_actionInstall_mods_triggered()
for (auto task : mdownload.getTasks()) {
tasks->addTask(task);
}
+
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h
index 72e2d404..2dd44e85 100644
--- a/launcher/ui/pages/instance/ModFolderPage.h
+++ b/launcher/ui/pages/instance/ModFolderPage.h
@@ -41,6 +41,7 @@
#include "ui/pages/BasePage.h"
#include <Application.h>
+#include <QSortFilterProxyModel>
class ModFolderModel;
namespace Ui
diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp
index 2cf17b32..51163e28 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
diff --git a/launcher/ui/pages/instance/ScreenshotsPage.h b/launcher/ui/pages/instance/ScreenshotsPage.h
index c22706af..c34c9755 100644
--- a/launcher/ui/pages/instance/ScreenshotsPage.h
+++ b/launcher/ui/pages/instance/ScreenshotsPage.h
@@ -35,6 +35,7 @@
#pragma once
+#include <QItemSelection>
#include <QMainWindow>
#include "ui/pages/BasePage.h"
diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp
index 2af6164c..c3bde612 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
@@ -47,6 +48,7 @@
#include <QFileSystemWatcher>
#include <QMenu>
+#include <QTimer>
static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things.
diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp
index 76725539..ff30dd82 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
@@ -46,6 +47,7 @@
#include <QInputDialog>
#include <QProcess>
#include <Qt>
+#include <QSortFilterProxyModel>
#include "tools/MCEditTool.h"
#include "FileSystem.h"
diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp
index b3ed1b73..0b8577b1 100644
--- a/launcher/ui/pages/modplatform/ImportPage.cpp
+++ b/launcher/ui/pages/modplatform/ImportPage.cpp
@@ -110,11 +110,13 @@ 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,this));
@@ -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 9dd8f737..98eec31c 100644
--- a/launcher/ui/pages/modplatform/ModModel.cpp
+++ b/launcher/ui/pages/modplatform/ModModel.cpp
@@ -38,27 +38,44 @@ 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)) {
+ return (m_logoMap.value(pack.logoName));
+ }
+ 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 {};
@@ -79,6 +96,11 @@ void ListModel::performPaginatedSearch()
this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() });
}
+void ListModel::requestModInfo(ModPlatform::IndexedPack& current)
+{
+ m_parent->apiProvider()->getModInfo(this, current);
+}
+
void ListModel::refresh()
{
if (jobPtr) {
@@ -225,6 +247,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 ad36cf2f..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();
@@ -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 0e658a8d..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::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/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
index 7e90af47..b65ace6b 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
@@ -119,29 +119,6 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)
}
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);
if (current.versionsLoaded == false) {
qDebug() << "Loading flame modpack versions";
@@ -188,6 +165,8 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)
suggestCurrent();
}
+
+ updateUi();
}
void FlamePage::suggestCurrent()
@@ -217,3 +196,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/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
index 63b944c4..06e9db4f 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"
@@ -11,6 +46,8 @@
#include <BuildConfig.h>
+#include <net/NetJob.h>
+
namespace LegacyFTB {
FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent)
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
index 27a12cda..7667d169 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
@@ -175,7 +175,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/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/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
index 7cacf37a..07d1687c 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
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
index 14aa6747..1b4d8da4 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
@@ -39,6 +39,7 @@
#include "modplatform/modrinth/ModrinthPackManifest.h"
#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
+#include "net/NetJob.h"
class ModPage;
class Version;
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
index 9bd24b57..d8500674 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
@@ -224,7 +224,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/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/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/libraries/launcher/net/minecraft/Launcher.java b/libraries/launcher/net/minecraft/Launcher.java
index 265fa66a..6bf671be 100644
--- a/libraries/launcher/net/minecraft/Launcher.java
+++ b/libraries/launcher/net/minecraft/Launcher.java
@@ -24,24 +24,65 @@ import java.net.URL;
import java.util.Map;
import java.util.TreeMap;
+/*
+ * WARNING: This class is reflectively accessed by legacy Forge versions.
+ * Changing field and method declarations without further testing is not recommended.
+ */
public final class Launcher extends Applet implements AppletStub {
private final Map<String, String> params = new TreeMap<>();
- private final Applet wrappedApplet;
+ private Applet wrappedApplet;
+
+ private URL documentBase;
private boolean active = false;
public Launcher(Applet applet) {
+ this(applet, null);
+ }
+
+ public Launcher(Applet applet, URL documentBase) {
this.setLayout(new BorderLayout());
this.add(applet, "Center");
this.wrappedApplet = applet;
+
+ try {
+ if (documentBase != null) {
+ this.documentBase = documentBase;
+ } else if (applet.getClass().getPackage().getName().startsWith("com.mojang")) {
+ // Special case only for Classic versions
+
+ this.documentBase = new URL("http", "www.minecraft.net", 80, "/game/");
+ } else {
+ this.documentBase = new URL("http://www.minecraft.net/game/");
+ }
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void replace(Applet applet) {
+ this.wrappedApplet = applet;
+
+ applet.setStub(this);
+ applet.setSize(getWidth(), getHeight());
+
+ this.setLayout(new BorderLayout());
+ this.add(applet, "Center");
+
+ applet.init();
+
+ active = true;
+
+ applet.start();
+
+ validate();
}
- public void setParameter(String name, String value)
- {
+ public void setParameter(String name, String value) {
params.put(name, value);
}
@@ -54,7 +95,7 @@ public final class Launcher extends Applet implements AppletStub {
try {
return super.getParameter(name);
- } catch (Exception ignore) {}
+ } catch (Exception ignored) {}
return null;
}
@@ -108,25 +149,13 @@ public final class Launcher extends Applet implements AppletStub {
try {
return new URL("http://www.minecraft.net/game/");
} catch (MalformedURLException e) {
- e.printStackTrace();
+ throw new RuntimeException(e);
}
-
- return null;
}
@Override
public URL getDocumentBase() {
- try {
- // Special case only for Classic versions
- if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang"))
- return new URL("http", "www.minecraft.net", 80, "/game/");
-
- return new URL("http://www.minecraft.net/game/");
- } catch (MalformedURLException e) {
- e.printStackTrace();
- }
-
- return null;
+ return documentBase;
}
@Override
diff --git a/packages/nix/NIX.md b/nix/NIX.md
index 1ceba9a3..1ceba9a3 100644
--- a/packages/nix/NIX.md
+++ b/nix/NIX.md
diff --git a/packages/nix/polymc/default.nix b/nix/default.nix
index e352209a..d6aa370c 100644
--- a/packages/nix/polymc/default.nix
+++ b/nix/default.nix
@@ -1,5 +1,5 @@
-{ lib
-, mkDerivation
+{ stdenv
+, lib
, fetchFromGitHub
, cmake
, ninja
@@ -7,18 +7,20 @@
, jdk
, zlib
, file
-, makeWrapper
+, wrapQtAppsHook
, xorg
, libpulseaudio
, qtbase
+, quazip
, libGL
, msaClientID ? ""
+, extraJDKs ? [ ]
# flake
, self
, version
, libnbtplusplus
-, quazip
+, enableLTO ? false
}:
let
@@ -35,45 +37,40 @@ let
# This variable will be passed to Minecraft by PolyMC
gameLibraryPath = libpath + ":/run/opengl-driver/lib";
+
+ javaPaths = lib.makeSearchPath "bin/java" ([ jdk jdk8 ] ++ extraJDKs);
in
-mkDerivation rec {
+stdenv.mkDerivation rec {
pname = "polymc";
inherit version;
src = lib.cleanSource self;
- nativeBuildInputs = [ cmake ninja file makeWrapper ];
- buildInputs = [ qtbase jdk zlib ];
+ nativeBuildInputs = [ cmake ninja jdk file wrapQtAppsHook ];
+ buildInputs = [ qtbase quazip zlib ];
dontWrapQtApps = true;
- postPatch = lib.optionalString (msaClientID != "") ''
- # add client ID
- substituteInPlace CMakeLists.txt \
- --replace '17b47edd-c884-4997-926d-9e7f9a6b4647' '${msaClientID}'
- '';
-
postUnpack = ''
- # Copy submodules inputs
- rm -rf source/libraries/{libnbtplusplus,quazip}
- mkdir source/libraries/{libnbtplusplus,quazip}
+ # Copy libnbtplusplus
+ rm -rf source/libraries/libnbtplusplus
+ mkdir source/libraries/libnbtplusplus
cp -a ${libnbtplusplus}/* source/libraries/libnbtplusplus
- cp -a ${quazip}/* source/libraries/quazip
- chmod a+r+w source/libraries/{libnbtplusplus,quazip}/*
+ chmod a+r+w source/libraries/libnbtplusplus/*
'';
cmakeFlags = [
"-GNinja"
- "-DLauncher_PORTABLE=OFF"
- ];
+ "-DLauncher_QT_VERSION_MAJOR=${lib.versions.major qtbase.version}"
+ ] ++ lib.optionals enableLTO [ "-DENABLE_LTO=on" ]
+ ++ lib.optionals (msaClientID != "") [ "-DLauncher_MSA_CLIENT_ID=${msaClientID}" ];
postInstall = ''
# xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128
- wrapProgram $out/bin/polymc \
- "''${qtWrapperArgs[@]}" \
+ wrapQtApp $out/bin/polymc \
--set GAME_LIBRARY_PATH ${gameLibraryPath} \
- --prefix POLYMC_JAVA_PATHS : ${jdk}/lib/openjdk/bin/java:${jdk8}/lib/openjdk/bin/java \
+ --prefix POLYMC_JAVA_PATHS : ${javaPaths} \
--prefix PATH : ${lib.makeBinPath [ xorg.xrandr ]}
'';
diff --git a/packages/nix/flake-compat.nix b/nix/flake-compat.nix
index bb7ee13e..8b6cb99c 100644
--- a/packages/nix/flake-compat.nix
+++ b/nix/flake-compat.nix
@@ -1,9 +1,9 @@
let
- lock = builtins.fromJSON (builtins.readFile ../../flake.lock);
+ lock = builtins.fromJSON (builtins.readFile ../flake.lock);
inherit (lock.nodes.flake-compat.locked) rev narHash;
flake-compat = fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${rev}.tar.gz";
sha256 = narHash;
};
in
-import flake-compat { src = ../..; }
+import flake-compat { src = ../.; }
diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt
index 60549d8d..1000be23 100644
--- a/program_info/CMakeLists.txt
+++ b/program_info/CMakeLists.txt
@@ -14,6 +14,8 @@ set(Launcher_MetaInfo "program_info/org.polymc.PolyMC.metainfo.xml" PARENT_SCOPE
set(Launcher_ManPage "program_info/polymc.6.txt" PARENT_SCOPE)
set(Launcher_SVG "program_info/org.polymc.PolyMC.svg" PARENT_SCOPE)
set(Launcher_Branding_ICNS "program_info/polymc.icns" PARENT_SCOPE)
+set(Launcher_Branding_ICO "program_info/polymc.ico")
+set(Launcher_Branding_ICO "${Launcher_Branding_ICO}" PARENT_SCOPE)
set(Launcher_Branding_WindowsRC "program_info/polymc.rc" PARENT_SCOPE)
set(Launcher_Branding_LogoQRC "program_info/polymc.qrc" PARENT_SCOPE)
@@ -21,3 +23,7 @@ set(Launcher_Portable_File "program_info/portable.txt" PARENT_SCOPE)
configure_file(org.polymc.PolyMC.desktop.in org.polymc.PolyMC.desktop)
configure_file(org.polymc.PolyMC.metainfo.xml.in org.polymc.PolyMC.metainfo.xml)
+configure_file(polymc.rc.in polymc.rc @ONLY)
+configure_file(polymc.manifest.in polymc.manifest @ONLY)
+configure_file(polymc.ico polymc.ico COPYONLY)
+configure_file(win_install.nsi.in win_install.nsi @ONLY)
diff --git a/program_info/org.polymc.PolyMC.desktop.in b/program_info/org.polymc.PolyMC.desktop.in
index 2d9e7103..e6d88909 100644
--- a/program_info/org.polymc.PolyMC.desktop.in
+++ b/program_info/org.polymc.PolyMC.desktop.in
@@ -8,5 +8,5 @@ Exec=@Launcher_APP_BINARY_NAME@
StartupNotify=true
Icon=org.polymc.PolyMC
Categories=Game;
-Keywords=game;minecraft;launcher;
+Keywords=game;minecraft;launcher;mc;
StartupWMClass=PolyMC
diff --git a/program_info/polymc.manifest b/program_info/polymc.manifest.in
index 2d9eb165..0eefacac 100644
--- a/program_info/polymc.manifest
+++ b/program_info/polymc.manifest.in
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
- <assemblyIdentity name="PolyMC.Application.1" type="win32" version="1.0.0.0" />
+ <assemblyIdentity name="PolyMC.Application.1" type="win32" version="@Launcher_RELEASE_VERSION_NAME4@" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
@@ -16,15 +16,13 @@
<description>Custom Minecraft launcher for managing multiple installs.</description>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
- <!--The ID below indicates app support for Windows Vista -->
- <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!--The ID below indicates app support for Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!--The ID below indicates app support for Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!--The ID below indicates app support for Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
- <!--The ID below indicates app support for Windows 10 -->
+ <!--The ID below indicates app support for Windows 10/11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
diff --git a/program_info/polymc.rc b/program_info/polymc.rc.in
index 011e944b..0ea9b73a 100644
--- a/program_info/polymc.rc
+++ b/program_info/polymc.rc.in
@@ -7,7 +7,7 @@ IDI_ICON1 ICON DISCARDABLE "polymc.ico"
1 RT_MANIFEST "polymc.manifest"
VS_VERSION_INFO VERSIONINFO
-FILEVERSION 1,0,0,0
+FILEVERSION @Launcher_RELEASE_VERSION_NAME4_COMMA@
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
BEGIN
@@ -17,9 +17,9 @@ BEGIN
BEGIN
VALUE "CompanyName", "MultiMC & PolyMC Contributors"
VALUE "FileDescription", "PolyMC"
- VALUE "FileVersion", "1.0.0.0"
+ VALUE "FileVersion", "@Launcher_RELEASE_VERSION_NAME4@"
VALUE "ProductName", "PolyMC"
- VALUE "ProductVersion", "1"
+ VALUE "ProductVersion", "@Launcher_RELEASE_VERSION_NAME4@"
END
END
BLOCK "VarFileInfo"
diff --git a/program_info/win_install.nsi b/program_info/win_install.nsi.in
index cb4c8d1d..e5687de7 100644
--- a/program_info/win_install.nsi
+++ b/program_info/win_install.nsi.in
@@ -4,10 +4,13 @@
Unicode true
-Name "PolyMC"
-InstallDir "$LOCALAPPDATA\Programs\PolyMC"
-InstallDirRegKey HKCU "Software\PolyMC" "InstallDir"
+Name "@Launcher_CommonName@"
+InstallDir "$LOCALAPPDATA\Programs\@Launcher_CommonName@"
+InstallDirRegKey HKCU "Software\@Launcher_CommonName@" "InstallDir"
RequestExecutionLevel user
+OutFile "../@Launcher_CommonName@-Setup.exe"
+
+!define MUI_ICON "../@Launcher_Branding_ICO@"
;--------------------------------
@@ -18,7 +21,7 @@ RequestExecutionLevel user
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
-!define MUI_FINISHPAGE_RUN "$InstDir\polymc.exe"
+!define MUI_FINISHPAGE_RUN "$InstDir\@Launcher_APP_BINARY_NAME@.exe"
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
@@ -98,16 +101,23 @@ RequestExecutionLevel user
;--------------------------------
+; Version info
+VIProductVersion "@Launcher_RELEASE_VERSION_NAME4@"
+VIFileVersion "@Launcher_RELEASE_VERSION_NAME4@"
+VIAddVersionKey "FileVersion" "@Launcher_RELEASE_VERSION_NAME4@"
+
+;--------------------------------
+
; The stuff to install
-Section "PolyMC"
+Section "@Launcher_CommonName@"
SectionIn RO
- nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F'
+ nsExec::Exec /TIMEOUT=2000 'TaskKill /IM @Launcher_APP_BINARY_NAME@.exe /F'
SetOutPath $INSTDIR
- File "polymc.exe"
+ File "@Launcher_APP_BINARY_NAME@.exe"
File "qt.conf"
File *.dll
File /r "iconengines"
@@ -117,20 +127,20 @@ Section "PolyMC"
File /r "styles"
; Write the installation path into the registry
- WriteRegStr HKCU Software\PolyMC "InstallDir" "$INSTDIR"
+ WriteRegStr HKCU Software\@Launcher_CommonName@ "InstallDir" "$INSTDIR"
; Write the uninstall keys for Windows
${GetParameters} $R0
${GetOptions} $R0 "/NoUninstaller" $R1
${If} ${Errors}
- !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC"
- WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "PolyMC"
- WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\polymc.exe"
+ !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@"
+ WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "@Launcher_CommonName@"
+ WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe"
WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S'
WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR"
- WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "PolyMC Contributors"
- WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "${VERSION}"
+ WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_CommonName@ Contributors"
+ WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "@Launcher_RELEASE_VERSION_NAME4@"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0"
@@ -143,13 +153,13 @@ SectionEnd
Section "Start Menu Shortcut" SM_SHORTCUTS
- CreateShortcut "$SMPROGRAMS\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0
+ CreateShortcut "$SMPROGRAMS\@Launcher_CommonName@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0
SectionEnd
Section "Desktop Shortcut" DESKTOP_SHORTCUTS
- CreateShortcut "$DESKTOP\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0
+ CreateShortcut "$DESKTOP\@Launcher_CommonName@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0
SectionEnd
@@ -159,12 +169,12 @@ SectionEnd
Section "Uninstall"
- nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F'
+ nsExec::Exec /TIMEOUT=2000 'TaskKill /IM @Launcher_APP_BINARY_NAME@.exe /F'
- DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC"
- DeleteRegKey HKCU SOFTWARE\PolyMC
+ DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@"
+ DeleteRegKey HKCU SOFTWARE\@Launcher_CommonName@
- Delete $INSTDIR\polymc.exe
+ Delete $INSTDIR\@Launcher_APP_BINARY_NAME@.exe
Delete $INSTDIR\uninstall.exe
Delete $INSTDIR\portable.txt
@@ -220,8 +230,8 @@ Section "Uninstall"
RMDir /r $INSTDIR\platforms
RMDir /r $INSTDIR\styles
- Delete "$SMPROGRAMS\PolyMC.lnk"
- Delete "$DESKTOP\PolyMC.lnk"
+ Delete "$SMPROGRAMS\@Launcher_CommonName@.lnk"
+ Delete "$DESKTOP\@Launcher_CommonName@.lnk"
RMDir "$INSTDIR"