Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into netjob_retry
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
		
							
								
								
									
										2
									
								
								.github/workflows/backport.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/backport.yml
									
									
									
									
										vendored
									
									
								
							| @@ -20,7 +20,7 @@ jobs: | ||||
|     if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name)) | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|       - uses: actions/checkout@v4 | ||||
|         with: | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
|       - name: Create backport PRs | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -125,7 +125,7 @@ jobs: | ||||
|       # PREPARE | ||||
|       ## | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           submodules: 'true' | ||||
|  | ||||
| @@ -164,7 +164,7 @@ jobs: | ||||
|  | ||||
|       - name: Retrieve ccache cache (Windows MinGW-w64) | ||||
|         if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' | ||||
|         uses: actions/cache@v3.3.1 | ||||
|         uses: actions/cache@v3.3.2 | ||||
|         with: | ||||
|           path: '${{ github.workspace }}\.ccache' | ||||
|           key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} | ||||
| @@ -620,7 +620,7 @@ jobs: | ||||
|       options: --privileged | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
|         uses: actions/checkout@v4 | ||||
|         if: inputs.build_type == 'Debug' | ||||
|         with: | ||||
|           submodules: 'true' | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							| @@ -8,7 +8,7 @@ jobs: | ||||
|      | ||||
|     steps: | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@v3 | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           submodules: 'true' | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/trigger_release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/trigger_release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -26,7 +26,7 @@ jobs: | ||||
|       upload_url: ${{ steps.create_release.outputs.upload_url }} | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           submodules: 'true' | ||||
|           path: 'PrismLauncher-source' | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/update-flake.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/update-flake.yml
									
									
									
									
										vendored
									
									
								
							| @@ -16,10 +16,10 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|       - uses: cachix/install-nix-action@v22 | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23 | ||||
|  | ||||
|       - uses: DeterminateSystems/update-flake-lock@v19 | ||||
|       - uses: DeterminateSystems/update-flake-lock@v20 | ||||
|         with: | ||||
|           commit-msg: "chore(nix): update lockfile" | ||||
|           pr-title: "chore(nix): update lockfile" | ||||
|   | ||||
| @@ -216,6 +216,18 @@ set(Launcher_SUBREDDIT_URL "https://prismlauncher.org/reddit" CACHE STRING "URL | ||||
| set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules") | ||||
| set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against") | ||||
|  | ||||
| # Native libraries | ||||
| if(UNIX AND APPLE) | ||||
|     set(Launcher_GLFW_LIBRARY_NAME "libglfw.dylib" CACHE STRING "Name of native glfw library") | ||||
|     set(Launcher_OPENAL_LIBRARY_NAME "libopenal.dylib" CACHE STRING "Name of native openal library") | ||||
| elseif(UNIX) | ||||
|     set(Launcher_GLFW_LIBRARY_NAME "libglfw.so" CACHE STRING "Name of native glfw library") | ||||
|     set(Launcher_OPENAL_LIBRARY_NAME "libopenal.so" CACHE STRING "Name of native openal library") | ||||
| elseif(WIN32) | ||||
|     set(Launcher_GLFW_LIBRARY_NAME "glfw.dll" CACHE STRING "Name of native glfw library") | ||||
|     set(Launcher_OPENAL_LIBRARY_NAME "OpenAL.dll" CACHE STRING "Name of native openal library") | ||||
| endif() | ||||
|  | ||||
| # 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. | ||||
|   | ||||
| @@ -110,6 +110,9 @@ Config::Config() | ||||
|     FLAME_API_KEY = "@Launcher_CURSEFORGE_API_KEY@"; | ||||
|     META_URL = "@Launcher_META_URL@"; | ||||
|  | ||||
|     GLFW_LIBRARY_NAME = "@Launcher_GLFW_LIBRARY_NAME@"; | ||||
|     OPENAL_LIBRARY_NAME = "@Launcher_OPENAL_LIBRARY_NAME@"; | ||||
|  | ||||
|     BUG_TRACKER_URL = "@Launcher_BUG_TRACKER_URL@"; | ||||
|     TRANSLATIONS_URL = "@Launcher_TRANSLATIONS_URL@"; | ||||
|     MATRIX_URL = "@Launcher_MATRIX_URL@"; | ||||
|   | ||||
| @@ -134,6 +134,9 @@ class Config { | ||||
|      */ | ||||
|     QString META_URL; | ||||
|  | ||||
|     QString GLFW_LIBRARY_NAME; | ||||
|     QString OPENAL_LIBRARY_NAME; | ||||
|  | ||||
|     QString BUG_TRACKER_URL; | ||||
|     QString TRANSLATIONS_URL; | ||||
|     QString MATRIX_URL; | ||||
|   | ||||
| @@ -75,7 +75,6 @@ function( | ||||
|     set(CLANG_WARNINGS | ||||
|         -Wall | ||||
|         -Wextra # reasonable and standard | ||||
|         -Wextra-semi # Warn about semicolon after in-class function definition. | ||||
|         -Wshadow # warn the user if a variable declaration shadows one from a parent context | ||||
|         -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps | ||||
|         # catch hard to track down memory errors | ||||
|   | ||||
							
								
								
									
										40
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										40
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							| @@ -21,11 +21,11 @@ | ||||
|         "nixpkgs-lib": "nixpkgs-lib" | ||||
|       }, | ||||
|       "locked": { | ||||
|         "lastModified": 1690933134, | ||||
|         "narHash": "sha256-ab989mN63fQZBFrkk4Q8bYxQCktuHmBIBqUG1jl6/FQ=", | ||||
|         "lastModified": 1693611461, | ||||
|         "narHash": "sha256-aPODl8vAgGQ0ZYFIRisxYG5MOGSkIczvu2Cd8Gb9+1Y=", | ||||
|         "owner": "hercules-ci", | ||||
|         "repo": "flake-parts", | ||||
|         "rev": "59cf3f1447cfc75087e7273b04b31e689a8599fb", | ||||
|         "rev": "7f53fdb7bdc5bb237da7fefef12d099e4fd611ca", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
| @@ -89,13 +89,28 @@ | ||||
|         "type": "github" | ||||
|       } | ||||
|     }, | ||||
|     "nix-filter": { | ||||
|       "locked": { | ||||
|         "lastModified": 1694857738, | ||||
|         "narHash": "sha256-bxxNyLHjhu0N8T3REINXQ2ZkJco0ABFPn6PIe2QUfqo=", | ||||
|         "owner": "numtide", | ||||
|         "repo": "nix-filter", | ||||
|         "rev": "41fd48e00c22b4ced525af521ead8792402de0ea", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
|         "owner": "numtide", | ||||
|         "repo": "nix-filter", | ||||
|         "type": "github" | ||||
|       } | ||||
|     }, | ||||
|     "nixpkgs": { | ||||
|       "locked": { | ||||
|         "lastModified": 1691853136, | ||||
|         "narHash": "sha256-wTzDsRV4HN8A2Sl0SVQY0q8ILs90CD43Ha//7gNZE+E=", | ||||
|         "lastModified": 1695318763, | ||||
|         "narHash": "sha256-FHVPDRP2AfvsxAdc+AsgFJevMz5VBmnZglFUMlxBkcY=", | ||||
|         "owner": "nixos", | ||||
|         "repo": "nixpkgs", | ||||
|         "rev": "f0451844bbdf545f696f029d1448de4906c7f753", | ||||
|         "rev": "e12483116b3b51a185a33a272bf351e357ba9a99", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
| @@ -108,11 +123,11 @@ | ||||
|     "nixpkgs-lib": { | ||||
|       "locked": { | ||||
|         "dir": "lib", | ||||
|         "lastModified": 1690881714, | ||||
|         "narHash": "sha256-h/nXluEqdiQHs1oSgkOOWF+j8gcJMWhwnZ9PFabN6q0=", | ||||
|         "lastModified": 1693471703, | ||||
|         "narHash": "sha256-0l03ZBL8P1P6z8MaSDS/MvuU8E75rVxe5eE1N6gxeTo=", | ||||
|         "owner": "NixOS", | ||||
|         "repo": "nixpkgs", | ||||
|         "rev": "9e1960bc196baf6881340d53dccb203a951745a2", | ||||
|         "rev": "3e52e76b70d5508f3cec70b882a29199f4d1ee85", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
| @@ -138,11 +153,11 @@ | ||||
|         ] | ||||
|       }, | ||||
|       "locked": { | ||||
|         "lastModified": 1691747570, | ||||
|         "narHash": "sha256-J3fnIwJtHVQ0tK2JMBv4oAmII+1mCdXdpeCxtIsrL2A=", | ||||
|         "lastModified": 1694364351, | ||||
|         "narHash": "sha256-oadhSCqopYXxURwIA6/Anpe5IAG11q2LhvTJNP5zE6o=", | ||||
|         "owner": "cachix", | ||||
|         "repo": "pre-commit-hooks.nix", | ||||
|         "rev": "c5ac3aa3324bd8aebe8622a3fc92eeb3975d317a", | ||||
|         "rev": "4f883a76282bc28eb952570afc3d8a1bf6f481d7", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
| @@ -156,6 +171,7 @@ | ||||
|         "flake-compat": "flake-compat", | ||||
|         "flake-parts": "flake-parts", | ||||
|         "libnbtplusplus": "libnbtplusplus", | ||||
|         "nix-filter": "nix-filter", | ||||
|         "nixpkgs": "nixpkgs", | ||||
|         "pre-commit-hooks": "pre-commit-hooks" | ||||
|       } | ||||
|   | ||||
							
								
								
									
										25
									
								
								flake.nix
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								flake.nix
									
									
									
									
									
								
							| @@ -4,6 +4,7 @@ | ||||
|   inputs = { | ||||
|     nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; | ||||
|     flake-parts.url = "github:hercules-ci/flake-parts"; | ||||
|     nix-filter.url = "github:numtide/nix-filter"; | ||||
|     pre-commit-hooks = { | ||||
|       url = "github:cachix/pre-commit-hooks.nix"; | ||||
|       inputs.nixpkgs.follows = "nixpkgs"; | ||||
| @@ -20,8 +21,24 @@ | ||||
|     }; | ||||
|   }; | ||||
|  | ||||
|   outputs = inputs: | ||||
|     inputs.flake-parts.lib.mkFlake | ||||
|     {inherit inputs;} | ||||
|     {imports = [./nix];}; | ||||
|   outputs = { | ||||
|     flake-parts, | ||||
|     pre-commit-hooks, | ||||
|     ... | ||||
|   } @ inputs: | ||||
|     flake-parts.lib.mkFlake {inherit inputs;} { | ||||
|       imports = [ | ||||
|         pre-commit-hooks.flakeModule | ||||
|  | ||||
|         ./nix/dev.nix | ||||
|         ./nix/distribution.nix | ||||
|       ]; | ||||
|  | ||||
|       systems = [ | ||||
|         "x86_64-linux" | ||||
|         "aarch64-linux" | ||||
|         "x86_64-darwin" | ||||
|         "aarch64-darwin" | ||||
|       ]; | ||||
|     }; | ||||
| } | ||||
|   | ||||
| @@ -9,7 +9,6 @@ | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> | ||||
|  *  Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> | ||||
|  *  Copyright (C) 2023 seth <getchoo at tuta dot io> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -504,6 +503,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) | ||||
|  | ||||
|         m_settings->registerSetting("MenuBarInsteadOfToolBar", false); | ||||
|  | ||||
|         m_settings->registerSetting("NumberOfConcurrentTasks", 10); | ||||
|         m_settings->registerSetting("NumberOfConcurrentDownloads", 6); | ||||
|  | ||||
|         QString defaultMonospace; | ||||
|         int defaultSize = 11; | ||||
| #ifdef Q_OS_WIN32 | ||||
| @@ -580,12 +582,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) | ||||
|         m_settings->registerSetting("IgnoreJavaCompatibility", false); | ||||
|         m_settings->registerSetting("IgnoreJavaWizard", false); | ||||
|  | ||||
|         // Mod loader settings | ||||
|         m_settings->registerSetting("DisableQuiltBeacon", false); | ||||
|  | ||||
|         // Native library workarounds | ||||
|         m_settings->registerSetting("UseNativeOpenAL", false); | ||||
|         m_settings->registerSetting("CustomOpenALPath", ""); | ||||
|         m_settings->registerSetting("UseNativeGLFW", false); | ||||
|         m_settings->registerSetting("CustomGLFWPath", ""); | ||||
|  | ||||
|         // Peformance related options | ||||
|         m_settings->registerSetting("EnableFeralGamemode", false); | ||||
| @@ -596,6 +597,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) | ||||
|         m_settings->registerSetting("ShowGameTime", true); | ||||
|         m_settings->registerSetting("ShowGlobalGameTime", true); | ||||
|         m_settings->registerSetting("RecordGameTime", true); | ||||
|         m_settings->registerSetting("ShowGameTimeWithoutDays", false); | ||||
|  | ||||
|         // Minecraft mods | ||||
|         m_settings->registerSetting("ModMetadataDisabled", false); | ||||
| @@ -845,6 +847,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) | ||||
|  | ||||
|     updateCapabilities(); | ||||
|  | ||||
|     detectLibraries(); | ||||
|  | ||||
|     if (createSetupWizard()) { | ||||
|         return; | ||||
|     } | ||||
| @@ -964,7 +968,7 @@ void Application::performMainStartupAction() | ||||
|                 qDebug() << "   Launching with account" << m_profileToUse; | ||||
|             } | ||||
|  | ||||
|             launch(inst, true, false, nullptr, serverToJoin, accountToUse); | ||||
|             launch(inst, true, false, serverToJoin, accountToUse); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| @@ -1063,7 +1067,7 @@ void Application::messageReceived(const QByteArray& message) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         launch(instance, true, false, nullptr, serverObject, accountObject); | ||||
|         launch(instance, true, false, serverObject, accountObject); | ||||
|     } else { | ||||
|         qWarning() << "Received invalid message" << message; | ||||
|     } | ||||
| @@ -1104,7 +1108,6 @@ bool Application::openJsonEditor(const QString& filename) | ||||
| bool Application::launch(InstancePtr instance, | ||||
|                          bool online, | ||||
|                          bool demo, | ||||
|                          BaseProfilerFactory* profiler, | ||||
|                          MinecraftServerTargetPtr serverToJoin, | ||||
|                          MinecraftAccountPtr accountToUse) | ||||
| { | ||||
| @@ -1112,7 +1115,7 @@ bool Application::launch(InstancePtr instance, | ||||
|         qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed."; | ||||
|     } else if (instance->canLaunch()) { | ||||
|         auto& extras = m_instanceExtras[instance->id()]; | ||||
|         auto& window = extras.window; | ||||
|         auto window = extras.window; | ||||
|         if (window) { | ||||
|             if (!window->saveAll()) { | ||||
|                 return false; | ||||
| @@ -1123,7 +1126,7 @@ bool Application::launch(InstancePtr instance, | ||||
|         controller->setInstance(instance); | ||||
|         controller->setOnline(online); | ||||
|         controller->setDemo(demo); | ||||
|         controller->setProfiler(profiler); | ||||
|         controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get()); | ||||
|         controller->setServerToJoin(serverToJoin); | ||||
|         controller->setAccountToUse(accountToUse); | ||||
|         if (window) { | ||||
| @@ -1415,6 +1418,15 @@ void Application::updateCapabilities() | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void Application::detectLibraries() | ||||
| { | ||||
| #ifdef Q_OS_LINUX | ||||
|     m_detectedGLFWPath = MangoHud::findLibrary(BuildConfig.GLFW_LIBRARY_NAME); | ||||
|     m_detectedOpenALPath = MangoHud::findLibrary(BuildConfig.OPENAL_LIBRARY_NAME); | ||||
|     qDebug() << "Detected native libraries:" << m_detectedGLFWPath << m_detectedOpenALPath; | ||||
| #endif | ||||
| } | ||||
|  | ||||
| QString Application::getJarPath(QString jarFile) | ||||
| { | ||||
|     QStringList potentialPaths = { | ||||
|   | ||||
| @@ -142,6 +142,8 @@ class Application : public QApplication { | ||||
|  | ||||
|     void updateCapabilities(); | ||||
|  | ||||
|     void detectLibraries(); | ||||
|  | ||||
|     /*! | ||||
|      * Finds and returns the full path to a jar file. | ||||
|      * Returns a null-string if it could not be found. | ||||
| @@ -193,7 +195,6 @@ class Application : public QApplication { | ||||
|     bool launch(InstancePtr instance, | ||||
|                 bool online = true, | ||||
|                 bool demo = false, | ||||
|                 BaseProfilerFactory* profiler = nullptr, | ||||
|                 MinecraftServerTargetPtr serverToJoin = nullptr, | ||||
|                 MinecraftAccountPtr accountToUse = nullptr); | ||||
|     bool kill(InstancePtr instance); | ||||
| @@ -277,6 +278,8 @@ class Application : public QApplication { | ||||
|     SetupWizard* m_setupWizard = nullptr; | ||||
|  | ||||
|    public: | ||||
|     QString m_detectedGLFWPath; | ||||
|     QString m_detectedOpenALPath; | ||||
|     QString m_instanceIdToLaunch; | ||||
|     QString m_serverToJoin; | ||||
|     QString m_profileToUse; | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||
|  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> | ||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -100,6 +101,8 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s | ||||
|     m_settings->registerSetting("ManagedPackName", ""); | ||||
|     m_settings->registerSetting("ManagedPackVersionID", ""); | ||||
|     m_settings->registerSetting("ManagedPackVersionName", ""); | ||||
|  | ||||
|     m_settings->registerSetting("Profiler", ""); | ||||
| } | ||||
|  | ||||
| QString BaseInstance::getPreLaunchCommand() | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||
|  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> | ||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> | ||||
|  * | ||||
|  *  This 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 <cassert> | ||||
|  | ||||
| #include <QDateTime> | ||||
| #include <QMenu> | ||||
| #include <QObject> | ||||
| #include <QProcess> | ||||
| #include <QSet> | ||||
| @@ -246,6 +248,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns | ||||
|     virtual bool canEdit() const = 0; | ||||
|     virtual bool canExport() const = 0; | ||||
|  | ||||
|     virtual void populateLaunchMenu(QMenu* menu) = 0; | ||||
|  | ||||
|     bool reloadSettings(); | ||||
|  | ||||
|     /** | ||||
| @@ -282,6 +286,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns | ||||
|  | ||||
|     void runningStatusChanged(bool running); | ||||
|  | ||||
|     void profilerChanged(); | ||||
|  | ||||
|     void statusChanged(Status from, Status to); | ||||
|  | ||||
|    protected slots: | ||||
|   | ||||
| @@ -1137,6 +1137,9 @@ include(CompilerWarnings) | ||||
|  | ||||
| # Add executable | ||||
| add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES}) | ||||
| if(BUILD_TESTING) | ||||
| target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_TEST) | ||||
| endif() | ||||
| set_project_warnings(Launcher_logic | ||||
|     "${Launcher_MSVC_WARNINGS}" | ||||
|     "${Launcher_CLANG_WARNINGS}" | ||||
|   | ||||
| @@ -267,10 +267,7 @@ bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceP | ||||
|  | ||||
| bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const | ||||
| { | ||||
|     auto fileName = fileInfo.fileName(); | ||||
|     auto path = relPath(fileInfo.absoluteFilePath()); | ||||
|     return std::any_of(m_ignoreFiles.cbegin(), m_ignoreFiles.cend(), [fileName](auto iFileName) { return fileName == iFileName; }) || | ||||
|            m_ignoreFilePaths.covers(path); | ||||
|     return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath())); | ||||
| } | ||||
|  | ||||
| bool FileIgnoreProxy::filterFile(const QString& fileName) const | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -361,22 +362,21 @@ void LaunchController::readyForLaunch() | ||||
|     QString error; | ||||
|     if (!m_profiler->check(&error)) { | ||||
|         m_launcher->abort(); | ||||
|         QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't start profiler: %1").arg(error)); | ||||
|         emitFailed("Profiler startup failed!"); | ||||
|         QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Profiler check for %1 failed: %2").arg(m_profiler->name(), error)); | ||||
|         return; | ||||
|     } | ||||
|     BaseProfiler* profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this); | ||||
|  | ||||
|     connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString& message) { | ||||
|         QMessageBox msg; | ||||
|         QMessageBox msg(m_parentWidget); | ||||
|         msg.setText(tr("The game launch is delayed until you press the " | ||||
|                        "button. This is the right time to setup the profiler, as the " | ||||
|                        "profiler server is running now.\n\n%1") | ||||
|                         .arg(message)); | ||||
|         msg.setWindowTitle(tr("Waiting.")); | ||||
|         msg.setIcon(QMessageBox::Information); | ||||
|         msg.addButton(tr("Launch"), QMessageBox::AcceptRole); | ||||
|         msg.setModal(true); | ||||
|         msg.addButton(tr("&Launch"), QMessageBox::AcceptRole); | ||||
|         msg.exec(); | ||||
|         m_launcher->proceed(); | ||||
|     }); | ||||
|   | ||||
| @@ -16,19 +16,20 @@ | ||||
|  */ | ||||
|  | ||||
| #include <MMCTime.h> | ||||
| #include <qobject.h> | ||||
|  | ||||
| #include <QDateTime> | ||||
| #include <QObject> | ||||
| #include <QTextStream> | ||||
|  | ||||
| QString Time::prettifyDuration(int64_t duration) | ||||
| QString Time::prettifyDuration(int64_t duration, bool noDays) | ||||
| { | ||||
|     int seconds = (int)(duration % 60); | ||||
|     duration /= 60; | ||||
|     int minutes = (int)(duration % 60); | ||||
|     duration /= 60; | ||||
|     int hours = (int)(duration % 24); | ||||
|     int days = (int)(duration / 24); | ||||
|     int hours = (int)(noDays ? duration : (duration % 24)); | ||||
|     int days = (int)(noDays ? 0 : (duration / 24)); | ||||
|     if ((hours == 0) && (days == 0)) { | ||||
|         return QObject::tr("%1min %2s").arg(minutes).arg(seconds); | ||||
|     } | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
|  | ||||
| namespace Time { | ||||
|  | ||||
| QString prettifyDuration(int64_t duration); | ||||
| QString prettifyDuration(int64_t duration, bool noDays = false); | ||||
|  | ||||
| /** | ||||
|  * @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`. | ||||
|   | ||||
| @@ -16,6 +16,7 @@ | ||||
|  *  along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| #include <QDebug> | ||||
| #include <QDir> | ||||
| #include <QString> | ||||
| #include <QStringList> | ||||
| @@ -26,6 +27,15 @@ | ||||
| #include "Json.h" | ||||
| #include "MangoHud.h" | ||||
|  | ||||
| #ifdef __GLIBC__ | ||||
| #ifndef _GNU_SOURCE | ||||
| #define _GNU_SOURCE | ||||
| #define UNDEF_GNU_SOURCE | ||||
| #endif | ||||
| #include <dlfcn.h> | ||||
| #include <linux/limits.h> | ||||
| #endif | ||||
|  | ||||
| namespace MangoHud { | ||||
|  | ||||
| QString getLibraryString() | ||||
| @@ -106,4 +116,37 @@ QString getLibraryString() | ||||
|  | ||||
|     return QString(); | ||||
| } | ||||
|  | ||||
| QString findLibrary(QString libName) | ||||
| { | ||||
| #ifdef __GLIBC__ | ||||
|     const char* library = libName.toLocal8Bit().constData(); | ||||
|  | ||||
|     void* handle = dlopen(library, RTLD_NOW); | ||||
|     if (!handle) { | ||||
|         qCritical() << "dlopen() failed:" << dlerror(); | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     char path[PATH_MAX]; | ||||
|     if (dlinfo(handle, RTLD_DI_ORIGIN, path) == -1) { | ||||
|         qCritical() << "dlinfo() failed:" << dlerror(); | ||||
|         dlclose(handle); | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     auto fullPath = FS::PathCombine(QString(path), libName); | ||||
|  | ||||
|     dlclose(handle); | ||||
|     return fullPath; | ||||
| #else | ||||
|     qWarning() << "MangoHud::findLibrary is not implemented on this platform"; | ||||
|     return {}; | ||||
| #endif | ||||
| } | ||||
| }  // namespace MangoHud | ||||
|  | ||||
| #ifdef UNDEF_GNU_SOURCE | ||||
| #undef _GNU_SOURCE | ||||
| #undef UNDEF_GNU_SOURCE | ||||
| #endif | ||||
|   | ||||
| @@ -24,4 +24,6 @@ | ||||
| namespace MangoHud { | ||||
|  | ||||
| QString getLibraryString(); | ||||
| } | ||||
|  | ||||
| QString findLibrary(QString libName); | ||||
| }  // namespace MangoHud | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -62,6 +63,7 @@ class NullInstance : public BaseInstance { | ||||
|     bool canExport() const override { return false; } | ||||
|     bool canEdit() const override { return false; } | ||||
|     bool canLaunch() const override { return false; } | ||||
|     void populateLaunchMenu(QMenu* menu) override {} | ||||
|     QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override | ||||
|     { | ||||
|         QStringList out; | ||||
|   | ||||
| @@ -103,14 +103,8 @@ class Version { | ||||
|  | ||||
|         QString m_fullString; | ||||
|  | ||||
|         [[nodiscard]] inline bool isAppendix() const | ||||
|         { | ||||
|             return m_stringPart.startsWith('+'); | ||||
|         } | ||||
|         [[nodiscard]] inline bool isPreRelease() const | ||||
|         { | ||||
|             return m_stringPart.startsWith('-') && m_stringPart.length() > 1; | ||||
|         } | ||||
|         [[nodiscard]] inline bool isAppendix() const { return m_stringPart.startsWith('+'); } | ||||
|         [[nodiscard]] inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; } | ||||
|  | ||||
|         inline bool operator==(const Section& other) const | ||||
|         { | ||||
| @@ -156,14 +150,8 @@ class Version { | ||||
|             return m_fullString < other.m_fullString; | ||||
|         } | ||||
|  | ||||
|         inline bool operator!=(const Section& other) const | ||||
|         { | ||||
|             return !(*this == other); | ||||
|         } | ||||
|         inline bool operator>(const Section& other) const | ||||
|         { | ||||
|             return !(*this < other || *this == other); | ||||
|         } | ||||
|         inline bool operator!=(const Section& other) const { return !(*this == other); } | ||||
|         inline bool operator>(const Section& other) const { return !(*this < other || *this == other); } | ||||
|     }; | ||||
|  | ||||
|    private: | ||||
|   | ||||
| @@ -2,14 +2,14 @@ | ||||
|  | ||||
| #include "Component.h" | ||||
| #include "ComponentUpdateTask_p.h" | ||||
| #include "OneSixVersionFormat.h" | ||||
| #include "PackProfile.h" | ||||
| #include "PackProfile_p.h" | ||||
| #include "Version.h" | ||||
| #include "cassert" | ||||
| #include "meta/Index.h" | ||||
| #include "meta/Version.h" | ||||
| #include "meta/VersionList.h" | ||||
| #include "minecraft/OneSixVersionFormat.h" | ||||
| #include "minecraft/ProfileUtils.h" | ||||
| #include "net/Mode.h" | ||||
|  | ||||
| #include "Application.h" | ||||
|   | ||||
| @@ -3,8 +3,7 @@ | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||
|  *  Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org> | ||||
|  *  Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> | ||||
|  *  Copyright (c) 2023 seth <getchoo at tuta dot io> | ||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -88,6 +87,10 @@ | ||||
| #include "minecraft/gameoptions/GameOptions.h" | ||||
| #include "minecraft/update/FoldersTask.h" | ||||
|  | ||||
| #include "tools/BaseProfiler.h" | ||||
|  | ||||
| #include <QActionGroup> | ||||
|  | ||||
| #ifdef Q_OS_LINUX | ||||
| #include "MangoHud.h" | ||||
| #endif | ||||
| @@ -166,7 +169,9 @@ void MinecraftInstance::loadSpecificSettings() | ||||
|         // Native library workarounds | ||||
|         auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false); | ||||
|         m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride); | ||||
|         m_settings->registerOverride(global_settings->getSetting("CustomOpenALPath"), nativeLibraryWorkaroundsOverride); | ||||
|         m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride); | ||||
|         m_settings->registerOverride(global_settings->getSetting("CustomGLFWPath"), nativeLibraryWorkaroundsOverride); | ||||
|  | ||||
|         // Peformance related options | ||||
|         auto performanceOverride = m_settings->registerSetting("OverridePerformance", false); | ||||
| @@ -179,10 +184,6 @@ void MinecraftInstance::loadSpecificSettings() | ||||
|         m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride); | ||||
|         m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride); | ||||
|  | ||||
|         // Mod loader specific options | ||||
|         auto modLoaderSettings = m_settings->registerSetting("OverrideModLoaderSettings", false); | ||||
|         m_settings->registerOverride(global_settings->getSetting("DisableQuiltBeacon"), modLoaderSettings); | ||||
|  | ||||
|         m_settings->set("InstanceType", "OneSix"); | ||||
|     } | ||||
|  | ||||
| @@ -194,6 +195,12 @@ void MinecraftInstance::loadSpecificSettings() | ||||
|     m_settings->registerSetting("UseAccountForInstance", false); | ||||
|     m_settings->registerSetting("InstanceAccountId", ""); | ||||
|  | ||||
|     m_settings->registerSetting("ExportName", ""); | ||||
|     m_settings->registerSetting("ExportVersion", "1.0.0"); | ||||
|     m_settings->registerSetting("ExportSummary", ""); | ||||
|     m_settings->registerSetting("ExportAuthor", ""); | ||||
|     m_settings->registerSetting("ExportOptionalFiles", true); | ||||
|  | ||||
|     qDebug() << "Instance-type specific settings were loaded!"; | ||||
|  | ||||
|     setSpecificSettingsLoaded(true); | ||||
| @@ -229,6 +236,50 @@ QSet<QString> MinecraftInstance::traits() const | ||||
|     return profile->getTraits(); | ||||
| } | ||||
|  | ||||
| // FIXME: move UI code out of MinecraftInstance | ||||
| void MinecraftInstance::populateLaunchMenu(QMenu* menu) | ||||
| { | ||||
|     QAction* normalLaunch = menu->addAction(tr("&Launch")); | ||||
|     normalLaunch->setShortcut(QKeySequence::Open); | ||||
|     QAction* normalLaunchOffline = menu->addAction(tr("Launch &Offline")); | ||||
|     normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); | ||||
|     QAction* normalLaunchDemo = menu->addAction(tr("Launch &Demo")); | ||||
|     normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O"))); | ||||
|  | ||||
|     normalLaunchDemo->setEnabled(supportsDemo()); | ||||
|  | ||||
|     connect(normalLaunch, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this()); }); | ||||
|     connect(normalLaunchOffline, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, false); }); | ||||
|     connect(normalLaunchDemo, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, true); }); | ||||
|  | ||||
|     QString profilersTitle = tr("Profilers"); | ||||
|     menu->addSeparator()->setText(profilersTitle); | ||||
|  | ||||
|     auto profilers = new QActionGroup(menu); | ||||
|     profilers->setExclusive(true); | ||||
|     connect(profilers, &QActionGroup::triggered, [this](QAction* action) { | ||||
|         settings()->set("Profiler", action->data()); | ||||
|         emit profilerChanged(); | ||||
|     }); | ||||
|  | ||||
|     QAction* noProfilerAction = menu->addAction(tr("&No Profiler")); | ||||
|     noProfilerAction->setData(""); | ||||
|     noProfilerAction->setCheckable(true); | ||||
|     noProfilerAction->setChecked(true); | ||||
|     profilers->addAction(noProfilerAction); | ||||
|  | ||||
|     for (auto profiler = APPLICATION->profilers().begin(); profiler != APPLICATION->profilers().end(); profiler++) { | ||||
|         QAction* profilerAction = menu->addAction(profiler.value()->name()); | ||||
|         profilers->addAction(profilerAction); | ||||
|         profilerAction->setData(profiler.key()); | ||||
|         profilerAction->setCheckable(true); | ||||
|         profilerAction->setChecked(settings()->get("Profiler").toString() == profiler.key()); | ||||
|  | ||||
|         QString error; | ||||
|         profilerAction->setEnabled(profiler.value()->check(&error)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| QString MinecraftInstance::gameRoot() const | ||||
| { | ||||
|     QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); | ||||
| @@ -260,7 +311,7 @@ QString MinecraftInstance::getLocalLibraryPath() const | ||||
| bool MinecraftInstance::supportsDemo() const | ||||
| { | ||||
|     Version instance_ver{ getPackProfile()->getComponentVersion("net.minecraft") }; | ||||
|     // Demo mode was introduced in 1.3.1: https://minecraft.fandom.com/wiki/Demo_mode#History | ||||
|     // Demo mode was introduced in 1.3.1: https://minecraft.wiki/w/Demo_mode#History | ||||
|     // FIXME: Due to Version constraints atm, this can't handle well non-release versions | ||||
|     return instance_ver >= Version("1.3.1"); | ||||
| } | ||||
| @@ -385,10 +436,31 @@ QStringList MinecraftInstance::extraArguments() | ||||
|     } | ||||
|  | ||||
|     { | ||||
|         const auto loaders = version->getModLoaders(); | ||||
|         if (loaders.has_value() && loaders.value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool()) | ||||
|             list.append("-Dloader.disable_beacon=true"); | ||||
|         QString openALPath; | ||||
|         QString glfwPath; | ||||
|  | ||||
|         if (settings()->get("UseNativeOpenAL").toBool()) { | ||||
|             openALPath = APPLICATION->m_detectedOpenALPath; | ||||
|             auto customPath = settings()->get("CustomOpenALPath").toString(); | ||||
|             if (!customPath.isEmpty()) | ||||
|                 openALPath = customPath; | ||||
|         } | ||||
|         if (settings()->get("UseNativeGLFW").toBool()) { | ||||
|             glfwPath = APPLICATION->m_detectedGLFWPath; | ||||
|             auto customPath = settings()->get("CustomGLFWPath").toString(); | ||||
|             if (!customPath.isEmpty()) | ||||
|                 glfwPath = customPath; | ||||
|         } | ||||
|  | ||||
|         QFileInfo openALInfo(openALPath); | ||||
|         QFileInfo glfwInfo(glfwPath); | ||||
|  | ||||
|         if (!openALPath.isEmpty() && openALInfo.exists()) | ||||
|             list.append("-Dorg.lwjgl.openal.libname=" + openALInfo.absoluteFilePath()); | ||||
|         if (!glfwPath.isEmpty() && glfwInfo.exists()) | ||||
|             list.append("-Dorg.lwjgl.glfw.libname=" + glfwInfo.absoluteFilePath()); | ||||
|     } | ||||
|  | ||||
|     return list; | ||||
| } | ||||
|  | ||||
| @@ -868,13 +940,16 @@ QString MinecraftInstance::getStatusbarDescription() | ||||
|     if (m_settings->get("ShowGameTime").toBool()) { | ||||
|         if (lastTimePlayed() > 0) { | ||||
|             QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch()); | ||||
|             description.append(tr(", last played on %1 for %2") | ||||
|             description.append( | ||||
|                 tr(", last played on %1 for %2") | ||||
|                     .arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat)) | ||||
|                                    .arg(Time::prettifyDuration(lastTimePlayed()))); | ||||
|                     .arg(Time::prettifyDuration(lastTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool()))); | ||||
|         } | ||||
|  | ||||
|         if (totalTimePlayed() > 0) { | ||||
|             description.append(tr(", total played for %1").arg(Time::prettifyDuration(totalTimePlayed()))); | ||||
|             description.append( | ||||
|                 tr(", total played for %1") | ||||
|                     .arg(Time::prettifyDuration(totalTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool()))); | ||||
|         } | ||||
|     } | ||||
|     if (hasCrashed()) { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||
|  *  Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> | ||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -70,6 +70,8 @@ class MinecraftInstance : public BaseInstance { | ||||
|  | ||||
|     bool canExport() const override { return true; } | ||||
|  | ||||
|     void populateLaunchMenu(QMenu* menu) override; | ||||
|  | ||||
|     ////// Directories and files ////// | ||||
|     QString jarModsDir() const; | ||||
|     QString resourcePacksDir() const; | ||||
|   | ||||
| @@ -58,14 +58,14 @@ | ||||
| #include "ComponentUpdateTask.h" | ||||
| #include "PackProfile.h" | ||||
| #include "PackProfile_p.h" | ||||
| #include "minecraft/mod/Mod.h" | ||||
| #include "modplatform/ModIndex.h" | ||||
|  | ||||
| #include "Application.h" | ||||
| #include "modplatform/ResourceAPI.h" | ||||
|  | ||||
| static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ { "net.minecraftforge", ResourceAPI::Forge }, | ||||
|                                                                          { "net.fabricmc.fabric-loader", ResourceAPI::Fabric }, | ||||
|                                                                          { "org.quiltmc.quilt-loader", ResourceAPI::Quilt }, | ||||
|                                                                          { "com.mumfrey.liteloader", ResourceAPI::LiteLoader } }; | ||||
| static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge }, | ||||
|                                                                          { "net.minecraftforge", ModPlatform::Forge }, | ||||
|                                                                          { "net.fabricmc.fabric-loader", ModPlatform::Fabric }, | ||||
|                                                                          { "org.quiltmc.quilt-loader", ModPlatform::Quilt }, | ||||
|                                                                          { "com.mumfrey.liteloader", ModPlatform::LiteLoader } }; | ||||
|  | ||||
| PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel() | ||||
| { | ||||
| @@ -989,12 +989,12 @@ void PackProfile::disableInteraction(bool disable) | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders() | ||||
| std::optional<ModPlatform::ModLoaderTypes> PackProfile::getModLoaders() | ||||
| { | ||||
|     ResourceAPI::ModLoaderTypes result; | ||||
|     ModPlatform::ModLoaderTypes result; | ||||
|     bool has_any_loader = false; | ||||
|  | ||||
|     QMapIterator<QString, ResourceAPI::ModLoaderType> i(modloaderMapping); | ||||
|     QMapIterator<QString, ModPlatform::ModLoaderType> i(modloaderMapping); | ||||
|  | ||||
|     while (i.hasNext()) { | ||||
|         i.next(); | ||||
| @@ -1008,3 +1008,18 @@ std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders() | ||||
|         return {}; | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| std::optional<ModPlatform::ModLoaderTypes> PackProfile::getSupportedModLoaders() | ||||
| { | ||||
|     auto loadersOpt = getModLoaders(); | ||||
|     if (!loadersOpt.has_value()) | ||||
|         return loadersOpt; | ||||
|     auto loaders = loadersOpt.value(); | ||||
|     // TODO: remove this or add version condition once Quilt drops official Fabric support | ||||
|     if (loaders & ModPlatform::Quilt) | ||||
|         loaders |= ModPlatform::Fabric; | ||||
|     // TODO: remove this or add version condition once NeoForge drops official Forge support | ||||
|     if (loaders & ModPlatform::NeoForge) | ||||
|         loaders |= ModPlatform::Forge; | ||||
|     return loaders; | ||||
| } | ||||
|   | ||||
| @@ -44,14 +44,11 @@ | ||||
| #include <QList> | ||||
| #include <QString> | ||||
| #include <memory> | ||||
| #include <optional> | ||||
|  | ||||
| #include "BaseVersion.h" | ||||
| #include "Component.h" | ||||
| #include "LaunchProfile.h" | ||||
| #include "Library.h" | ||||
| #include "MojangDownloadInfo.h" | ||||
| #include "ProfileUtils.h" | ||||
| #include "modplatform/ResourceAPI.h" | ||||
| #include "modplatform/ModIndex.h" | ||||
| #include "net/Mode.h" | ||||
|  | ||||
| class MinecraftInstance; | ||||
| @@ -146,7 +143,9 @@ class PackProfile : public QAbstractListModel { | ||||
|     // todo(merged): is this the best approach | ||||
|     void appendComponent(ComponentPtr component); | ||||
|  | ||||
|     std::optional<ResourceAPI::ModLoaderTypes> getModLoaders(); | ||||
|     std::optional<ModPlatform::ModLoaderTypes> getModLoaders(); | ||||
|     // this returns aditional loaders(Quilt supports fabric and NeoForge supports Forge) | ||||
|     std::optional<ModPlatform::ModLoaderTypes> getSupportedModLoaders(); | ||||
|  | ||||
|    private: | ||||
|     void scheduleSave(); | ||||
|   | ||||
| @@ -415,7 +415,7 @@ Qt::ItemFlags AccountList::flags(const QModelIndex& index) const | ||||
|  | ||||
| bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int role) | ||||
| { | ||||
|     if (idx.row() < 0 || idx.row() >= rowCount(idx) || !idx.isValid()) { | ||||
|     if (idx.row() < 0 || idx.row() >= rowCount(idx.parent()) || !idx.isValid()) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| @@ -423,7 +423,8 @@ bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int rol | ||||
|         if (value == Qt::Checked) { | ||||
|             MinecraftAccountPtr account = at(idx.row()); | ||||
|             setDefaultAccount(account); | ||||
|         } | ||||
|         } else if (m_defaultAccount == at(idx.row())) | ||||
|             setDefaultAccount(nullptr); | ||||
|     } | ||||
|  | ||||
|     emit dataChanged(idx, index(idx.row(), columnCount(QModelIndex()) - 1)); | ||||
|   | ||||
| @@ -39,7 +39,7 @@ static QString replaceSuffix(QString target, const QString& suffix, const QStrin | ||||
|     return target + replacement; | ||||
| } | ||||
|  | ||||
| static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack, bool nativeOpenAL, bool nativeGLFW) | ||||
| static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack) | ||||
| { | ||||
|     QuaZip zip(source); | ||||
|     if (!zip.open(QuaZip::mdUnzip)) { | ||||
| @@ -52,12 +52,6 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH | ||||
|     do { | ||||
|         QString name = zip.getCurrentFileName(); | ||||
|         auto lowercase = name.toLower(); | ||||
|         if (nativeGLFW && name.contains("glfw")) { | ||||
|             continue; | ||||
|         } | ||||
|         if (nativeOpenAL && name.contains("openal")) { | ||||
|             continue; | ||||
|         } | ||||
|         if (applyJnilibHack) { | ||||
|             name = replaceSuffix(name, ".jnilib", ".dylib"); | ||||
|         } | ||||
| @@ -83,14 +77,12 @@ void ExtractNatives::executeTask() | ||||
|         return; | ||||
|     } | ||||
|     auto settings = minecraftInstance->settings(); | ||||
|     bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool(); | ||||
|     bool nativeGLFW = settings->get("UseNativeGLFW").toBool(); | ||||
|  | ||||
|     auto outputPath = minecraftInstance->getNativePath(); | ||||
|     auto javaVersion = minecraftInstance->getJavaVersion(); | ||||
|     bool jniHackEnabled = javaVersion.major() >= 8; | ||||
|     for (const auto& source : toExtract) { | ||||
|         if (!unzipNatives(source, outputPath, jniHackEnabled, nativeOpenAL, nativeGLFW)) { | ||||
|         if (!unzipNatives(source, outputPath, jniHackEnabled)) { | ||||
|             const char* reason = QT_TR_NOOP("Couldn't extract native jar '%1' to destination '%2'"); | ||||
|             emit logLine(QString(reason).arg(source, outputPath), MessageLevel::Fatal); | ||||
|             emitFailed(tr(reason).arg(source, outputPath)); | ||||
|   | ||||
| @@ -28,7 +28,7 @@ | ||||
| #include "Version.h" | ||||
|  | ||||
| // Values taken from: | ||||
| // https://minecraft.fandom.com/wiki/Tutorials/Creating_a_data_pack#%22pack_format%22 | ||||
| // https://minecraft.wiki/w/Tutorials/Creating_a_data_pack#%22pack_format%22 | ||||
| static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = { | ||||
|     { 4, { Version("1.13"), Version("1.14.4") } },    { 5, { Version("1.15"), Version("1.16.1") } }, | ||||
|     { 6, { Version("1.16.2"), Version("1.16.5") } },  { 7, { Version("1.17"), Version("1.17.1") } }, | ||||
|   | ||||
| @@ -63,7 +63,7 @@ class DataPack : public Resource { | ||||
|     mutable QMutex m_data_lock; | ||||
|  | ||||
|     /* The 'version' of a data pack, as defined in the pack.mcmeta file. | ||||
|      * See https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta | ||||
|      * See https://minecraft.wiki/w/Data_pack#pack.mcmeta | ||||
|      */ | ||||
|     int m_pack_format = 0; | ||||
|  | ||||
|   | ||||
| @@ -132,15 +132,21 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) - | ||||
|     if (!preserve_metadata) { | ||||
|         qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name()); | ||||
|  | ||||
|         destroyMetadata(index_dir); | ||||
|     } | ||||
|  | ||||
|     return Resource::destroy(attempt_trash); | ||||
| } | ||||
|  | ||||
| void Mod::destroyMetadata(QDir& index_dir) | ||||
| { | ||||
|     if (metadata()) { | ||||
|         Metadata::remove(index_dir, metadata()->slug); | ||||
|     } else { | ||||
|         auto n = name(); | ||||
|         Metadata::remove(index_dir, n); | ||||
|     } | ||||
|     } | ||||
|  | ||||
|     return Resource::destroy(attempt_trash); | ||||
|     m_local_details.metadata = nullptr; | ||||
| } | ||||
|  | ||||
| auto Mod::details() const -> const ModDetails& | ||||
| @@ -246,7 +252,8 @@ void Mod::setIcon(QImage new_image) const | ||||
|         PixmapCache::remove(m_pack_image_cache_key.key); | ||||
|  | ||||
|     // scale the image to avoid flooding the pixmapcache | ||||
|     auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding)); | ||||
|     auto pixmap = | ||||
|         QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); | ||||
|  | ||||
|     m_pack_image_cache_key.key = PixmapCache::insert(pixmap); | ||||
|     m_pack_image_cache_key.was_ever_used = true; | ||||
| @@ -259,7 +266,7 @@ QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const | ||||
|     if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) { | ||||
|         if (size.isNull()) | ||||
|             return cached_image; | ||||
|         return cached_image.scaled(size, mode); | ||||
|         return cached_image.scaled(size, mode, Qt::SmoothTransformation); | ||||
|     } | ||||
|  | ||||
|     // No valid image we can get | ||||
|   | ||||
| @@ -93,6 +93,8 @@ class Mod : public Resource { | ||||
|  | ||||
|     // Delete all the files of this mod | ||||
|     auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool; | ||||
|     // Delete the metadata only | ||||
|     void destroyMetadata(QDir& index_dir); | ||||
|  | ||||
|     void finishResolvingWithDetails(ModDetails&& details); | ||||
|  | ||||
|   | ||||
| @@ -51,8 +51,13 @@ | ||||
|  | ||||
| #include "Application.h" | ||||
|  | ||||
| #include "Json.h" | ||||
| #include "minecraft/mod/tasks/LocalModParseTask.h" | ||||
| #include "minecraft/mod/tasks/LocalModUpdateTask.h" | ||||
| #include "minecraft/mod/tasks/ModFolderLoadTask.h" | ||||
| #include "modplatform/ModIndex.h" | ||||
| #include "modplatform/flame/FlameAPI.h" | ||||
| #include "modplatform/flame/FlameModIndex.h" | ||||
|  | ||||
| ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir) | ||||
|     : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) | ||||
| @@ -228,6 +233,25 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes) | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool ModFolderModel::deleteModsMetadata(const QModelIndexList& indexes) | ||||
| { | ||||
|     if (indexes.isEmpty()) | ||||
|         return true; | ||||
|  | ||||
|     for (auto i : indexes) { | ||||
|         if (i.column() != 0) { | ||||
|             continue; | ||||
|         } | ||||
|         auto m = at(i.row()); | ||||
|         auto index_dir = indexDir(); | ||||
|         m->destroyMetadata(index_dir); | ||||
|     } | ||||
|  | ||||
|     update(); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool ModFolderModel::isValid() | ||||
| { | ||||
|     return m_dir.exists() && m_dir.isReadable(); | ||||
| @@ -309,3 +333,47 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) | ||||
|  | ||||
|     emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); | ||||
| } | ||||
|  | ||||
| static const FlameAPI flameAPI; | ||||
| bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers) | ||||
| { | ||||
|     if (vers.addonId.isValid()) { | ||||
|         ModPlatform::IndexedPack pack{ | ||||
|             vers.addonId, | ||||
|             ModPlatform::ResourceProvider::FLAME, | ||||
|         }; | ||||
|  | ||||
|         QEventLoop loop; | ||||
|  | ||||
|         auto response = std::make_shared<QByteArray>(); | ||||
|         auto job = flameAPI.getProject(vers.addonId.toString(), response); | ||||
|  | ||||
|         QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); }); | ||||
|         QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit); | ||||
|         QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] { | ||||
|             QJsonParseError parse_error{}; | ||||
|             QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); | ||||
|             if (parse_error.error != QJsonParseError::NoError) { | ||||
|                 qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset | ||||
|                            << " reason: " << parse_error.errorString(); | ||||
|                 qDebug() << *response; | ||||
|                 return; | ||||
|             } | ||||
|             try { | ||||
|                 auto obj = Json::requireObject(Json::requireObject(doc), "data"); | ||||
|                 FlameMod::loadIndexedPack(pack, obj); | ||||
|             } catch (const JSONValidationError& e) { | ||||
|                 qDebug() << doc; | ||||
|                 qWarning() << "Error while reading mod info: " << e.cause(); | ||||
|             } | ||||
|             LocalModUpdateTask update_metadata(indexDir(), pack, vers); | ||||
|             QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit); | ||||
|             update_metadata.start(); | ||||
|         }); | ||||
|  | ||||
|         job->start(); | ||||
|  | ||||
|         loop.exec(); | ||||
|     } | ||||
|     return ResourceFolderModel::installResource(file_path); | ||||
| } | ||||
|   | ||||
| @@ -48,6 +48,7 @@ | ||||
|  | ||||
| #include "minecraft/mod/tasks/LocalModParseTask.h" | ||||
| #include "minecraft/mod/tasks/ModFolderLoadTask.h" | ||||
| #include "modplatform/ModIndex.h" | ||||
|  | ||||
| class LegacyInstance; | ||||
| class BaseInstance; | ||||
| @@ -75,10 +76,12 @@ class ModFolderModel : public ResourceFolderModel { | ||||
|     [[nodiscard]] Task* createParseTask(Resource&) override; | ||||
|  | ||||
|     bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); } | ||||
|     bool installMod(QString file_path, ModPlatform::IndexedVersion& vers); | ||||
|     bool uninstallMod(const QString& filename, bool preserve_metadata = false); | ||||
|  | ||||
|     /// Deletes all the selected mods | ||||
|     bool deleteMods(const QModelIndexList& indexes); | ||||
|     bool deleteModsMetadata(const QModelIndexList& indexes); | ||||
|  | ||||
|     bool isValid(); | ||||
|  | ||||
|   | ||||
| @@ -33,6 +33,10 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObje | ||||
|  | ||||
|     connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged); | ||||
|     connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this] { m_helper_thread_task.clear(); }); | ||||
| #ifndef LAUNCHER_TEST | ||||
|     // in tests the application macro doesn't work | ||||
|     m_helper_thread_task.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| ResourceFolderModel::~ResourceFolderModel() | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
| #include "minecraft/mod/tasks/LocalResourcePackParseTask.h" | ||||
|  | ||||
| // Values taken from: | ||||
| // https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta | ||||
| // https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta | ||||
| static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = { | ||||
|     { 1, { Version("1.6.1"), Version("1.8.9") } },    { 2, { Version("1.9"), Version("1.10.2") } }, | ||||
|     { 3, { Version("1.11"), Version("1.12.2") } },    { 4, { Version("1.13"), Version("1.14.4") } }, | ||||
| @@ -50,7 +50,8 @@ void ResourcePack::setImage(QImage new_image) const | ||||
|         PixmapCache::instance().remove(m_pack_image_cache_key.key); | ||||
|  | ||||
|     // scale the image to avoid flooding the pixmapcache | ||||
|     auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding)); | ||||
|     auto pixmap = | ||||
|         QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); | ||||
|  | ||||
|     m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap); | ||||
|     m_pack_image_cache_key.was_ever_used = true; | ||||
| @@ -68,7 +69,7 @@ QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const | ||||
|     if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) { | ||||
|         if (size.isNull()) | ||||
|             return cached_image; | ||||
|         return cached_image.scaled(size, mode); | ||||
|         return cached_image.scaled(size, mode, Qt::SmoothTransformation); | ||||
|     } | ||||
|  | ||||
|     // No valid image we can get | ||||
|   | ||||
| @@ -51,7 +51,7 @@ class ResourcePack : public Resource { | ||||
|     mutable QMutex m_data_lock; | ||||
|  | ||||
|     /* The 'version' of a resource pack, as defined in the pack.mcmeta file. | ||||
|      * See https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta | ||||
|      * See https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta | ||||
|      */ | ||||
|     int m_pack_format = 0; | ||||
|  | ||||
|   | ||||
| @@ -44,7 +44,8 @@ void TexturePack::setImage(QImage new_image) const | ||||
|         PixmapCache::remove(m_pack_image_cache_key.key); | ||||
|  | ||||
|     // scale the image to avoid flooding the pixmapcache | ||||
|     auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding)); | ||||
|     auto pixmap = | ||||
|         QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); | ||||
|  | ||||
|     m_pack_image_cache_key.key = PixmapCache::insert(pixmap); | ||||
|     m_pack_image_cache_key.was_ever_used = true; | ||||
| @@ -56,7 +57,7 @@ QPixmap TexturePack::image(QSize size, Qt::AspectRatioMode mode) const | ||||
|     if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) { | ||||
|         if (size.isNull()) | ||||
|             return cached_image; | ||||
|         return cached_image.scaled(size, mode); | ||||
|         return cached_image.scaled(size, mode, Qt::SmoothTransformation); | ||||
|     } | ||||
|  | ||||
|     // No valid image we can get | ||||
|   | ||||
| @@ -39,9 +39,9 @@ static Version mcVersion(BaseInstance* inst) | ||||
|     return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion(); | ||||
| } | ||||
|  | ||||
| static ResourceAPI::ModLoaderTypes mcLoaders(BaseInstance* inst) | ||||
| static ModPlatform::ModLoaderTypes mcLoaders(BaseInstance* inst) | ||||
| { | ||||
|     return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders().value(); | ||||
|     return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders().value(); | ||||
| } | ||||
|  | ||||
| GetModDependenciesTask::GetModDependenciesTask(QObject* parent, | ||||
| @@ -75,7 +75,7 @@ void GetModDependenciesTask::prepare() | ||||
| ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep, | ||||
|                                                             const ModPlatform::ResourceProvider providerName) | ||||
| { | ||||
|     if (auto isQuilt = m_loaderType & ResourceAPI::Quilt; isQuilt || m_loaderType & ResourceAPI::Fabric) { | ||||
|     if (auto isQuilt = m_loaderType & ModPlatform::Quilt; isQuilt || m_loaderType & ModPlatform::Fabric) { | ||||
|         auto overide = ModPlatform::getOverrideDeps(); | ||||
|         auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) { | ||||
|             return o.provider == providerName && dep.addonId == (isQuilt ? o.fabric : o.quilt); | ||||
| @@ -191,7 +191,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen | ||||
|             } | ||||
|             pDep->version = provider.mod->loadDependencyVersions(dep, arr); | ||||
|             if (!pDep->version.addonId.isValid()) { | ||||
|                 if (m_loaderType & ResourceAPI::Quilt) {  // falback for quilt | ||||
|                 if (m_loaderType & ModPlatform::Quilt) {  // falback for quilt | ||||
|                     auto overide = ModPlatform::getOverrideDeps(); | ||||
|                     auto over = std::find_if(overide.cbegin(), overide.cend(), | ||||
|                                              [dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; }); | ||||
| @@ -201,6 +201,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|                 removePack(dep.addonId); | ||||
|                 qWarning() << "Error while reading mod version empty "; | ||||
|                 qDebug() << doc; | ||||
|                 return; | ||||
| @@ -250,3 +251,32 @@ void GetModDependenciesTask::removePack(const QVariant addonId) | ||||
|             ++it; | ||||
| #endif | ||||
| } | ||||
|  | ||||
| QHash<QString, QStringList> GetModDependenciesTask::getRequiredBy() | ||||
| { | ||||
|     QHash<QString, QStringList> rby; | ||||
|     auto fullList = m_selected + m_pack_dependencies; | ||||
|     for (auto& mod : fullList) { | ||||
|         auto addonId = mod->pack->addonId; | ||||
|         auto provider = mod->pack->provider; | ||||
|         auto version = mod->version.fileId; | ||||
|         auto req = QStringList(); | ||||
|         for (auto& smod : fullList) { | ||||
|             if (provider != smod->pack->provider) | ||||
|                 continue; | ||||
|             auto deps = smod->version.dependencies; | ||||
|             if (auto dep = std::find_if(deps.begin(), deps.end(), | ||||
|                                         [addonId, provider, version](const ModPlatform::Dependency& d) { | ||||
|                                             return d.type == ModPlatform::DependencyType::REQUIRED && | ||||
|                                                    (provider == ModPlatform::ResourceProvider::MODRINTH && d.addonId.toString().isEmpty() | ||||
|                                                         ? version == d.version | ||||
|                                                         : d.addonId == addonId); | ||||
|                                         }); | ||||
|                 dep != deps.end()) { | ||||
|                 req.append(smod->pack->name); | ||||
|             } | ||||
|         } | ||||
|         rby[addonId.toString()] = req; | ||||
|     } | ||||
|     return rby; | ||||
| } | ||||
| @@ -62,6 +62,7 @@ class GetModDependenciesTask : public SequentialTask { | ||||
|                                     QList<std::shared_ptr<PackDependency>> selected); | ||||
|  | ||||
|     auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; } | ||||
|     QHash<QString, QStringList> getRequiredBy(); | ||||
|  | ||||
|    protected slots: | ||||
|     Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider, int); | ||||
| @@ -80,5 +81,5 @@ class GetModDependenciesTask : public SequentialTask { | ||||
|     Provider m_modrinth_provider; | ||||
|  | ||||
|     Version m_version; | ||||
|     ResourceAPI::ModLoaderTypes m_loaderType; | ||||
|     ModPlatform::ModLoaderTypes m_loaderType; | ||||
| }; | ||||
|   | ||||
| @@ -133,7 +133,7 @@ bool processZIP(DataPack& pack, ProcessingLevel level) | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| // https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta | ||||
| // https://minecraft.wiki/w/Data_pack#pack.mcmeta | ||||
| bool processMCMeta(DataPack& pack, QByteArray&& raw_data) | ||||
| { | ||||
|     try { | ||||
|   | ||||
| @@ -178,7 +178,7 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level) | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| // https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta | ||||
| // https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta | ||||
| bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) | ||||
| { | ||||
|     try { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "minecraft/mod/Mod.h" | ||||
| #include "minecraft/mod/tasks/GetModDependenciesTask.h" | ||||
| #include "modplatform/ModIndex.h" | ||||
| #include "modplatform/ResourceAPI.h" | ||||
| #include "tasks/Task.h" | ||||
| @@ -14,7 +15,7 @@ class CheckUpdateTask : public Task { | ||||
|    public: | ||||
|     CheckUpdateTask(QList<Mod*>& mods, | ||||
|                     std::list<Version>& mcVersions, | ||||
|                     std::optional<ResourceAPI::ModLoaderTypes> loaders, | ||||
|                     std::optional<ModPlatform::ModLoaderTypes> loaders, | ||||
|                     std::shared_ptr<ModFolderModel> mods_folder) | ||||
|         : Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder){}; | ||||
|  | ||||
| @@ -40,6 +41,7 @@ class CheckUpdateTask : public Task { | ||||
|     }; | ||||
|  | ||||
|     auto getUpdatable() -> std::vector<UpdatableMod>&& { return std::move(m_updatable); } | ||||
|     auto getDependencies() -> QList<std::shared_ptr<GetModDependenciesTask::PackDependency>>&& { return std::move(m_deps); } | ||||
|  | ||||
|    public slots: | ||||
|     bool abort() override = 0; | ||||
| @@ -53,8 +55,9 @@ class CheckUpdateTask : public Task { | ||||
|    protected: | ||||
|     QList<Mod*>& m_mods; | ||||
|     std::list<Version>& m_game_versions; | ||||
|     std::optional<ResourceAPI::ModLoaderTypes> m_loaders; | ||||
|     std::optional<ModPlatform::ModLoaderTypes> m_loaders; | ||||
|     std::shared_ptr<ModFolderModel> m_mods_folder; | ||||
|  | ||||
|     std::vector<UpdatableMod> m_updatable; | ||||
|     QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> m_deps; | ||||
| }; | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| #include <MurmurHash2.h> | ||||
| #include <QDebug> | ||||
|  | ||||
| #include "Application.h" | ||||
| #include "Json.h" | ||||
|  | ||||
| #include "minecraft/mod/Mod.h" | ||||
| @@ -33,7 +34,7 @@ EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Resource | ||||
| EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::ResourceProvider prov) | ||||
|     : Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) | ||||
| { | ||||
|     m_hashing_task.reset(new ConcurrentTask(this, "MakeHashesTask", 10)); | ||||
|     m_hashing_task.reset(new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); | ||||
|     for (auto* mod : mods) { | ||||
|         auto hash_task = createNewHash(mod); | ||||
|         if (!hash_task) | ||||
|   | ||||
| @@ -83,4 +83,25 @@ QString getMetaURL(ResourceProvider provider, QVariant projectID) | ||||
|            projectID.toString(); | ||||
| } | ||||
|  | ||||
| auto getModLoaderString(ModLoaderType type) -> const QString | ||||
| { | ||||
|     switch (type) { | ||||
|         case NeoForge: | ||||
|             return "neoforge"; | ||||
|         case Forge: | ||||
|             return "forge"; | ||||
|         case Cauldron: | ||||
|             return "cauldron"; | ||||
|         case LiteLoader: | ||||
|             return "liteloader"; | ||||
|         case Fabric: | ||||
|             return "fabric"; | ||||
|         case Quilt: | ||||
|             return "quilt"; | ||||
|         default: | ||||
|             break; | ||||
|     } | ||||
|     return ""; | ||||
| } | ||||
|  | ||||
| }  // namespace ModPlatform | ||||
|   | ||||
| @@ -30,6 +30,9 @@ class QIODevice; | ||||
|  | ||||
| namespace ModPlatform { | ||||
|  | ||||
| enum ModLoaderType { NeoForge = 1 << 0, Forge = 1 << 1, Cauldron = 1 << 2, LiteLoader = 1 << 3, Fabric = 1 << 4, Quilt = 1 << 5 }; | ||||
| Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) | ||||
|  | ||||
| enum class ResourceProvider { MODRINTH, FLAME }; | ||||
|  | ||||
| enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK }; | ||||
| @@ -70,7 +73,7 @@ struct IndexedVersion { | ||||
|     QString downloadUrl; | ||||
|     QString date; | ||||
|     QString fileName; | ||||
|     QStringList loaders = {}; | ||||
|     ModLoaderTypes loaders = {}; | ||||
|     QString hash_type; | ||||
|     QString hash; | ||||
|     bool is_preferred = true; | ||||
| @@ -128,7 +131,6 @@ struct IndexedPack { | ||||
|         return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; }); | ||||
|     } | ||||
| }; | ||||
| QString getMetaURL(ResourceProvider provider, QVariant projectID); | ||||
|  | ||||
| struct OverrideDep { | ||||
|     QString quilt; | ||||
| @@ -148,6 +150,14 @@ inline auto getOverrideDeps() -> QList<OverrideDep> | ||||
|  | ||||
| QString getMetaURL(ResourceProvider provider, QVariant projectID); | ||||
|  | ||||
| auto getModLoaderString(ModLoaderType type) -> const QString; | ||||
|  | ||||
| constexpr bool hasSingleModLoaderSelected(ModLoaderTypes l) noexcept | ||||
| { | ||||
|     auto x = static_cast<int>(l); | ||||
|     return x && !(x & (x - 1)); | ||||
| } | ||||
|  | ||||
| }  // namespace ModPlatform | ||||
|  | ||||
| Q_DECLARE_METATYPE(ModPlatform::IndexedPack) | ||||
|   | ||||
| @@ -54,9 +54,6 @@ class ResourceAPI { | ||||
|    public: | ||||
|     virtual ~ResourceAPI() = default; | ||||
|  | ||||
|     enum ModLoaderType { Forge = 1 << 0, Cauldron = 1 << 1, LiteLoader = 1 << 2, Fabric = 1 << 3, Quilt = 1 << 4 }; | ||||
|     Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) | ||||
|  | ||||
|     struct SortingMethod { | ||||
|         // The index of the sorting method. Used to allow for arbitrary ordering in the list of methods. | ||||
|         // Used by Flame in the API request. | ||||
| @@ -74,7 +71,7 @@ class ResourceAPI { | ||||
|  | ||||
|         std::optional<QString> search; | ||||
|         std::optional<SortingMethod> sorting; | ||||
|         std::optional<ModLoaderTypes> loaders; | ||||
|         std::optional<ModPlatform::ModLoaderTypes> loaders; | ||||
|         std::optional<std::list<Version> > versions; | ||||
|     }; | ||||
|     struct SearchCallbacks { | ||||
| @@ -87,7 +84,7 @@ class ResourceAPI { | ||||
|         ModPlatform::IndexedPack pack; | ||||
|  | ||||
|         std::optional<std::list<Version> > mcVersions; | ||||
|         std::optional<ModLoaderTypes> loaders; | ||||
|         std::optional<ModPlatform::ModLoaderTypes> loaders; | ||||
|  | ||||
|         VersionSearchArgs(VersionSearchArgs const&) = default; | ||||
|         void operator=(VersionSearchArgs other) | ||||
| @@ -108,13 +105,15 @@ class ResourceAPI { | ||||
|         void operator=(ProjectInfoArgs other) { pack = other.pack; } | ||||
|     }; | ||||
|     struct ProjectInfoCallbacks { | ||||
|         std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed; | ||||
|         std::function<void(QJsonDocument&, const ModPlatform::IndexedPack&)> on_succeed; | ||||
|         std::function<void(QString const& reason)> on_fail; | ||||
|         std::function<void()> on_abort; | ||||
|     }; | ||||
|  | ||||
|     struct DependencySearchArgs { | ||||
|         ModPlatform::Dependency dependency; | ||||
|         Version mcVersion; | ||||
|         ModLoaderTypes loader; | ||||
|         ModPlatform::ModLoaderTypes loader; | ||||
|     }; | ||||
|  | ||||
|     struct DependencySearchCallbacks { | ||||
| @@ -161,25 +160,6 @@ class ResourceAPI { | ||||
|         return nullptr; | ||||
|     } | ||||
|  | ||||
|     static auto getModLoaderString(ModLoaderType type) -> const QString | ||||
|     { | ||||
|         switch (type) { | ||||
|             case Forge: | ||||
|                 return "forge"; | ||||
|             case Cauldron: | ||||
|                 return "cauldron"; | ||||
|             case LiteLoader: | ||||
|                 return "liteloader"; | ||||
|             case Fabric: | ||||
|                 return "fabric"; | ||||
|             case Quilt: | ||||
|                 return "quilt"; | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
|         return ""; | ||||
|     } | ||||
|  | ||||
|    protected: | ||||
|     [[nodiscard]] inline QString debugName() const { return "External resource API"; } | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #include "FileResolvingTask.h" | ||||
|  | ||||
| #include "Json.h" | ||||
| #include "modplatform/ModIndex.h" | ||||
| #include "net/ApiDownload.h" | ||||
| #include "net/ApiUpload.h" | ||||
| #include "net/Upload.h" | ||||
| @@ -102,7 +103,7 @@ void Flame::FileResolvingTask::netJobFinished() | ||||
|                 auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash); | ||||
|                 auto output = std::make_shared<QByteArray>(); | ||||
|                 auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output); | ||||
|                 QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; }); | ||||
|                 QObject::connect(dl.get(), &Net::ApiDownload::succeeded, [&out]() { out.resolved = true; }); | ||||
|  | ||||
|                 m_checkJob->addNetAction(dl); | ||||
|                 blockedProjects.insert(&out, output); | ||||
| @@ -153,7 +154,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished() | ||||
|         // If there's more than one mod loader for this version, we can't know for sure | ||||
|         // which file is relative to each loader, so it's best to not use any one and | ||||
|         // let the user download it manually. | ||||
|         if (file.loaders.size() <= 1) { | ||||
|         if (!file.loaders || hasSingleModLoaderSelected(file.loaders)) { | ||||
|             out->url = file.downloadUrl; | ||||
|             qDebug() << "Found alternative on modrinth " << out->fileName; | ||||
|         } else { | ||||
| @@ -175,7 +176,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished() | ||||
|             auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId); | ||||
|             auto dl = Net::ApiDownload::makeByteArray(url, output); | ||||
|             qDebug() << "Fetching url slug for file:" << mod->fileName; | ||||
|             QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() { | ||||
|             QObject::connect(dl.get(), &Net::ApiDownload::succeeded, [block, index, output]() { | ||||
|                 auto mod = block->at(index);  // use the shared_ptr so it is captured and only freed when we are done | ||||
|                 auto json = QJsonDocument::fromJson(*output); | ||||
|                 auto base = | ||||
|   | ||||
| @@ -6,7 +6,6 @@ | ||||
| #include "FlameModIndex.h" | ||||
|  | ||||
| #include "Application.h" | ||||
| #include "BuildConfig.h" | ||||
| #include "Json.h" | ||||
| #include "net/ApiDownload.h" | ||||
| #include "net/ApiUpload.h" | ||||
| @@ -131,19 +130,13 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe | ||||
|             auto obj = Json::requireObject(doc); | ||||
|             auto arr = Json::requireArray(obj, "data"); | ||||
|  | ||||
|             QJsonObject latest_file_obj; | ||||
|             ModPlatform::IndexedVersion ver_tmp; | ||||
|  | ||||
|             for (auto file : arr) { | ||||
|                 auto file_obj = Json::requireObject(file); | ||||
|                 auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj); | ||||
|                 if (file_tmp.date > ver_tmp.date) { | ||||
|                     ver_tmp = file_tmp; | ||||
|                     latest_file_obj = file_obj; | ||||
|                 } | ||||
|                 if (file_tmp.date > ver.date && (!args.loaders.has_value() || !file_tmp.loaders || args.loaders.value() & file_tmp.loaders)) | ||||
|                     ver = file_tmp; | ||||
|             } | ||||
|  | ||||
|             ver = FlameMod::loadIndexedPackVersion(latest_file_obj); | ||||
|         } catch (Json::JsonException& e) { | ||||
|             qCritical() << "Failed to parse response from a version request."; | ||||
|             qCritical() << e.what(); | ||||
|   | ||||
| @@ -24,7 +24,10 @@ class FlameAPI : public NetworkResourceAPI { | ||||
|  | ||||
|     [[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override; | ||||
|  | ||||
|     static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (Forge | Fabric | Quilt); } | ||||
|     static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool | ||||
|     { | ||||
|         return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt); | ||||
|     } | ||||
|  | ||||
|    private: | ||||
|     static int getClassId(ModPlatform::ResourceType type) | ||||
| @@ -35,22 +38,47 @@ class FlameAPI : public NetworkResourceAPI { | ||||
|                 return 6; | ||||
|             case ModPlatform::ResourceType::RESOURCE_PACK: | ||||
|                 return 12; | ||||
|             case ModPlatform::ResourceType::SHADER_PACK: | ||||
|                 return 6552; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static int getMappedModLoader(ModLoaderTypes loaders) | ||||
|     static int getMappedModLoader(ModPlatform::ModLoaderType loaders) | ||||
|     { | ||||
|         // https://docs.curseforge.com/?http#tocS_ModLoaderType | ||||
|         if (loaders & Forge) | ||||
|         switch (loaders) { | ||||
|             case ModPlatform::Forge: | ||||
|                 return 1; | ||||
|         if (loaders & Fabric) | ||||
|             case ModPlatform::Cauldron: | ||||
|                 return 2; | ||||
|             case ModPlatform::LiteLoader: | ||||
|                 return 3; | ||||
|             case ModPlatform::Fabric: | ||||
|                 return 4; | ||||
|         // TODO: remove this once Quilt drops official Fabric support | ||||
|         if (loaders & Quilt)  // NOTE: Most if not all Fabric mods should work *currently* | ||||
|             return 4;         // Quilt would probably be 5 | ||||
|             case ModPlatform::Quilt: | ||||
|                 return 5; | ||||
|             case ModPlatform::NeoForge: | ||||
|                 return 6; | ||||
|         } | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList | ||||
|     { | ||||
|         QStringList l; | ||||
|         for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt }) { | ||||
|             if (types & loader) { | ||||
|                 l << QString::number(getMappedModLoader(loader)); | ||||
|             } | ||||
|         } | ||||
|         return l; | ||||
|     } | ||||
|  | ||||
|     static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString | ||||
|     { | ||||
|         return "[" + getModLoaderStrings(types).join(',') + "]"; | ||||
|     } | ||||
|  | ||||
|    private: | ||||
|     [[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override | ||||
|     { | ||||
| @@ -67,7 +95,7 @@ class FlameAPI : public NetworkResourceAPI { | ||||
|             get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index)); | ||||
|         get_arguments.append("sortOrder=desc"); | ||||
|         if (args.loaders.has_value()) | ||||
|             get_arguments.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value()))); | ||||
|             get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(args.loaders.value()))); | ||||
|         get_arguments.append(gameVersionStr); | ||||
|  | ||||
|         return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); | ||||
| @@ -81,47 +109,27 @@ class FlameAPI : public NetworkResourceAPI { | ||||
|     [[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override | ||||
|     { | ||||
|         auto addonId = args.pack.addonId.toString(); | ||||
|         QString url{ QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(addonId) }; | ||||
|         QString url = QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000").arg(addonId); | ||||
|  | ||||
|         QStringList get_parameters; | ||||
|         if (args.mcVersions.has_value()) | ||||
|             get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString())); | ||||
|             url += QString("&gameVersion=%1").arg(args.mcVersions.value().front().toString()); | ||||
|  | ||||
|         if (args.loaders.has_value()) { | ||||
|             int mappedModLoader = getMappedModLoader(args.loaders.value()); | ||||
|  | ||||
|             if (args.loaders.value() & Quilt) { | ||||
|                 auto overide = ModPlatform::getOverrideDeps(); | ||||
|                 auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) { | ||||
|                     return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt; | ||||
|                 }); | ||||
|                 if (over != overide.cend()) { | ||||
|                     mappedModLoader = 5; | ||||
|         if (args.loaders.has_value() && ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) { | ||||
|             int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loaders.value()))); | ||||
|             url += QString("&modLoaderType=%1").arg(mappedModLoader); | ||||
|         } | ||||
|             } | ||||
|  | ||||
|             get_parameters.append(QString("modLoaderType=%1").arg(mappedModLoader)); | ||||
|         } | ||||
|  | ||||
|         return url + get_parameters.join('&'); | ||||
|         return url; | ||||
|     }; | ||||
|  | ||||
|     [[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override | ||||
|     { | ||||
|         auto mappedModLoader = getMappedModLoader(args.loader); | ||||
|         auto addonId = args.dependency.addonId.toString(); | ||||
|         if (args.loader & Quilt) { | ||||
|             auto overide = ModPlatform::getOverrideDeps(); | ||||
|             auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) { | ||||
|                 return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt; | ||||
|             }); | ||||
|             if (over != overide.cend()) { | ||||
|                 mappedModLoader = 5; | ||||
|         auto url = | ||||
|             QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2").arg(addonId, args.mcVersion.toString()); | ||||
|         if (args.loader && ModPlatform::hasSingleModLoaderSelected(args.loader)) { | ||||
|             int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loader))); | ||||
|             url += QString("&modLoaderType=%1").arg(mappedModLoader); | ||||
|         } | ||||
|         } | ||||
|         return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2&modLoaderType=%3") | ||||
|             .arg(addonId) | ||||
|             .arg(args.mcVersion.toString()) | ||||
|             .arg(mappedModLoader); | ||||
|         return url; | ||||
|     }; | ||||
| }; | ||||
|   | ||||
| @@ -5,13 +5,12 @@ | ||||
| #include <MurmurHash2.h> | ||||
| #include <memory> | ||||
|  | ||||
| #include "FileSystem.h" | ||||
| #include "Json.h" | ||||
|  | ||||
| #include "ResourceDownloadTask.h" | ||||
|  | ||||
| #include "minecraft/mod/ModFolderModel.h" | ||||
| #include "minecraft/mod/ResourceFolderModel.h" | ||||
| #include "minecraft/mod/tasks/GetModDependenciesTask.h" | ||||
|  | ||||
| #include "net/ApiDownload.h" | ||||
|  | ||||
| @@ -156,7 +155,6 @@ void FlameCheckUpdate::executeTask() | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         if (!latest_ver.hash.isEmpty() && (mod->metadata()->hash != latest_ver.hash || mod->status() == ModStatus::NotInstalled)) { | ||||
|         // Fake pack with the necessary info to pass to the download task :) | ||||
|         auto pack = std::make_shared<ModPlatform::IndexedPack>(); | ||||
|         pack->name = mod->name(); | ||||
| @@ -167,7 +165,7 @@ void FlameCheckUpdate::executeTask() | ||||
|             pack->authors.append({ author }); | ||||
|         pack->description = mod->description(); | ||||
|         pack->provider = ModPlatform::ResourceProvider::FLAME; | ||||
|  | ||||
|         if (!latest_ver.hash.isEmpty() && (mod->metadata()->hash != latest_ver.hash || mod->status() == ModStatus::NotInstalled)) { | ||||
|             auto old_version = mod->version(); | ||||
|             if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) { | ||||
|                 auto current_ver = getFileInfo(latest_ver.addonId.toInt(), mod->metadata()->file_id.toInt()); | ||||
| @@ -179,6 +177,7 @@ void FlameCheckUpdate::executeTask() | ||||
|                                      api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), | ||||
|                                      ModPlatform::ResourceProvider::FLAME, download_task); | ||||
|         } | ||||
|         m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver)); | ||||
|     } | ||||
|  | ||||
|     emitSucceeded(); | ||||
|   | ||||
| @@ -10,7 +10,7 @@ class FlameCheckUpdate : public CheckUpdateTask { | ||||
|    public: | ||||
|     FlameCheckUpdate(QList<Mod*>& mods, | ||||
|                      std::list<Version>& mcVersions, | ||||
|                      std::optional<ResourceAPI::ModLoaderTypes> loaders, | ||||
|                      std::optional<ModPlatform::ModLoaderTypes> loaders, | ||||
|                      std::shared_ptr<ModFolderModel> mods_folder) | ||||
|         : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) | ||||
|     {} | ||||
|   | ||||
| @@ -284,7 +284,7 @@ QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType, | ||||
|             // filter by minecraft version, if the loader depends on a certain version. | ||||
|             // not all mod loaders depend on a given Minecraft version, so we won't do this | ||||
|             // filtering for those loaders. | ||||
|             if (loaderType == "forge") { | ||||
|             if (loaderType == "forge" || loaderType == "neoforge") { | ||||
|                 auto iter = std::find_if(reqs.begin(), reqs.end(), [mcVersion](const Meta::Require& req) { | ||||
|                     return req.uid == "net.minecraft" && req.equalsVersion == mcVersion; | ||||
|                 }); | ||||
| @@ -350,7 +350,11 @@ bool FlameCreationTask::createInstance() | ||||
|  | ||||
|     for (auto& loader : m_pack.minecraft.modLoaders) { | ||||
|         auto id = loader.id; | ||||
|         if (id.startsWith("forge-")) { | ||||
|         if (id.startsWith("neoforge-")) { | ||||
|             id.remove("neoforge-"); | ||||
|             loaderType = "neoforge"; | ||||
|             loaderUid = "net.neoforged"; | ||||
|         } else if (id.startsWith("forge-")) { | ||||
|             id.remove("forge-"); | ||||
|             loaderType = "forge"; | ||||
|             loaderUid = "net.minecraftforge"; | ||||
|   | ||||
| @@ -81,6 +81,7 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, | ||||
|     QVector<ModPlatform::IndexedVersion> unsortedVersions; | ||||
|     auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile(); | ||||
|     QString mcVersion = profile->getComponentVersion("net.minecraft"); | ||||
|     auto loaders = profile->getSupportedModLoaders(); | ||||
|  | ||||
|     for (auto versionIter : arr) { | ||||
|         auto obj = versionIter.toObject(); | ||||
| @@ -89,7 +90,8 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, | ||||
|         if (!file.addonId.isValid()) | ||||
|             file.addonId = pack.addonId; | ||||
|  | ||||
|         if (file.fileId.isValid())  // Heuristic to check if the returned value is valid | ||||
|         if (file.fileId.isValid() && | ||||
|             (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders))  // Heuristic to check if the returned value is valid | ||||
|             unsortedVersions.append(file); | ||||
|     } | ||||
|  | ||||
| @@ -115,6 +117,19 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> | ||||
|  | ||||
|         if (str.contains('.')) | ||||
|             file.mcVersion.append(str); | ||||
|         auto loader = str.toLower(); | ||||
|         if (loader == "neoforge") | ||||
|             file.loaders |= ModPlatform::NeoForge; | ||||
|         if (loader == "forge") | ||||
|             file.loaders |= ModPlatform::Forge; | ||||
|         if (loader == "cauldron") | ||||
|             file.loaders |= ModPlatform::Cauldron; | ||||
|         if (loader == "liteloader") | ||||
|             file.loaders |= ModPlatform::LiteLoader; | ||||
|         if (loader == "fabric") | ||||
|             file.loaders |= ModPlatform::Fabric; | ||||
|         if (loader == "quilt") | ||||
|             file.loaders |= ModPlatform::Quilt; | ||||
|     } | ||||
|  | ||||
|     file.addonId = Json::requireInteger(obj, "modId"); | ||||
| @@ -173,8 +188,11 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> | ||||
|     return file; | ||||
| } | ||||
|  | ||||
| ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) | ||||
| ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) | ||||
| { | ||||
|     auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile(); | ||||
|     QString mcVersion = profile->getComponentVersion("net.minecraft"); | ||||
|     auto loaders = profile->getSupportedModLoaders(); | ||||
|     QVector<ModPlatform::IndexedVersion> versions; | ||||
|     for (auto versionIter : arr) { | ||||
|         auto obj = versionIter.toObject(); | ||||
| @@ -183,7 +201,8 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform:: | ||||
|         if (!file.addonId.isValid()) | ||||
|             file.addonId = m.addonId; | ||||
|  | ||||
|         if (file.fileId.isValid())  // Heuristic to check if the returned value is valid | ||||
|         if (file.fileId.isValid() && | ||||
|             (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders))  // Heuristic to check if the returned value is valid | ||||
|             versions.append(file); | ||||
|     } | ||||
|  | ||||
| @@ -192,5 +211,7 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform:: | ||||
|         return a.date > b.date; | ||||
|     }; | ||||
|     std::sort(versions.begin(), versions.end(), orderSortPredicate); | ||||
|     if (versions.size() != 0) | ||||
|         return versions.front(); | ||||
|     return {}; | ||||
| } | ||||
|   | ||||
| @@ -19,5 +19,5 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, | ||||
|                              const shared_qobject_ptr<QNetworkAccessManager>& network, | ||||
|                              const BaseInstance* inst); | ||||
| auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion; | ||||
| auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion; | ||||
| auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion; | ||||
| }  // namespace FlameMod | ||||
| @@ -28,6 +28,7 @@ | ||||
| #include <algorithm> | ||||
| #include <iterator> | ||||
| #include <memory> | ||||
| #include "Application.h" | ||||
| #include "Json.h" | ||||
| #include "MMCZip.h" | ||||
| #include "minecraft/PackProfile.h" | ||||
| @@ -43,12 +44,14 @@ const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" }); | ||||
| FlamePackExportTask::FlamePackExportTask(const QString& name, | ||||
|                                          const QString& version, | ||||
|                                          const QString& author, | ||||
|                                          bool optionalFiles, | ||||
|                                          InstancePtr instance, | ||||
|                                          const QString& output, | ||||
|                                          MMCZip::FilterFunction filter) | ||||
|     : name(name) | ||||
|     , version(version) | ||||
|     , author(author) | ||||
|     , optionalFiles(optionalFiles) | ||||
|     , instance(instance) | ||||
|     , mcInstance(dynamic_cast<MinecraftInstance*>(instance.get())) | ||||
|     , gameRoot(instance->gameRoot()) | ||||
| @@ -100,7 +103,8 @@ void FlamePackExportTask::collectHashes() | ||||
|     setStatus(tr("Finding file hashes...")); | ||||
|     setProgress(1, 5); | ||||
|     auto allMods = mcInstance->loaderModList()->allMods(); | ||||
|     ConcurrentTask::Ptr hashingTask(new ConcurrentTask(this, "MakeHashesTask", 10)); | ||||
|     ConcurrentTask::Ptr hashingTask( | ||||
|         new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); | ||||
|     task.reset(hashingTask); | ||||
|     for (const QFileInfo& file : files) { | ||||
|         const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); | ||||
| @@ -381,6 +385,7 @@ QByteArray FlamePackExportTask::generateIndex() | ||||
|         const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); | ||||
|         const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); | ||||
|         const ComponentPtr forge = profile->getComponent("net.minecraftforge"); | ||||
|         const ComponentPtr neoforge = profile->getComponent("net.neoforged"); | ||||
|  | ||||
|         // convert all available components to mrpack dependencies | ||||
|         if (minecraft != nullptr) | ||||
| @@ -392,6 +397,8 @@ QByteArray FlamePackExportTask::generateIndex() | ||||
|             id = "fabric-" + fabric->getVersion(); | ||||
|         else if (forge != nullptr) | ||||
|             id = "forge-" + forge->getVersion(); | ||||
|         else if (neoforge != nullptr) | ||||
|             id = "neoforge-" + neoforge->getVersion(); | ||||
|         version["modLoaders"] = QJsonArray(); | ||||
|         if (!id.isEmpty()) { | ||||
|             QJsonObject loader; | ||||
| @@ -407,7 +414,7 @@ QByteArray FlamePackExportTask::generateIndex() | ||||
|         QJsonObject file; | ||||
|         file["projectID"] = mod.addonId; | ||||
|         file["fileID"] = mod.version; | ||||
|         file["required"] = mod.enabled; | ||||
|         file["required"] = mod.enabled || !optionalFiles; | ||||
|         files << file; | ||||
|     } | ||||
|     obj["files"] = files; | ||||
|   | ||||
| @@ -30,6 +30,7 @@ class FlamePackExportTask : public Task { | ||||
|     FlamePackExportTask(const QString& name, | ||||
|                         const QString& version, | ||||
|                         const QString& author, | ||||
|                         bool optionalFiles, | ||||
|                         InstancePtr instance, | ||||
|                         const QString& output, | ||||
|                         MMCZip::FilterFunction filter); | ||||
| @@ -44,6 +45,7 @@ class FlamePackExportTask : public Task { | ||||
|  | ||||
|     // inputs | ||||
|     const QString name, version, author; | ||||
|     const bool optionalFiles; | ||||
|     const InstancePtr instance; | ||||
|     MinecraftInstance* mcInstance; | ||||
|     const QDir gameRoot; | ||||
|   | ||||
| @@ -72,7 +72,8 @@ Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfo | ||||
|  | ||||
|         callbacks.on_succeed(doc, args.pack); | ||||
|     }); | ||||
|  | ||||
|     QObject::connect(job.get(), &NetJob::failed, [callbacks](QString reason) { callbacks.on_fail(reason); }); | ||||
|     QObject::connect(job.get(), &NetJob::aborted, [callbacks] { callbacks.on_abort(); }); | ||||
|     return job; | ||||
| } | ||||
|  | ||||
| @@ -131,7 +132,7 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args, | ||||
|     auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network()); | ||||
|     auto response = std::make_shared<QByteArray>(); | ||||
|  | ||||
|     netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); | ||||
|     netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response)); | ||||
|  | ||||
|     QObject::connect(netJob.get(), &NetJob::succeeded, [=] { | ||||
|         QJsonParseError parse_error{}; | ||||
|   | ||||
| @@ -59,16 +59,20 @@ Modpack parseDirectory(QString path) | ||||
|             auto obj = Json::requireObject(target, "target"); | ||||
|             auto name = Json::requireString(obj, "name", "name"); | ||||
|             auto version = Json::requireString(obj, "version", "version"); | ||||
|             if (name == "forge") { | ||||
|                 modpack.loaderType = ResourceAPI::Forge; | ||||
|             if (name == "neoforge") { | ||||
|                 modpack.loaderType = ModPlatform::NeoForge; | ||||
|                 modpack.version = version; | ||||
|                 break; | ||||
|             } else if (name == "forge") { | ||||
|                 modpack.loaderType = ModPlatform::Forge; | ||||
|                 modpack.version = version; | ||||
|                 break; | ||||
|             } else if (name == "fabric") { | ||||
|                 modpack.loaderType = ResourceAPI::Fabric; | ||||
|                 modpack.loaderType = ModPlatform::Fabric; | ||||
|                 modpack.version = version; | ||||
|                 break; | ||||
|             } else if (name == "quilt") { | ||||
|                 modpack.loaderType = ResourceAPI::Quilt; | ||||
|                 modpack.loaderType = ModPlatform::Quilt; | ||||
|                 modpack.version = version; | ||||
|                 break; | ||||
|             } | ||||
|   | ||||
| @@ -39,7 +39,7 @@ struct Modpack { | ||||
|     // not needed for instance creation | ||||
|     QVariant jvmArgs; | ||||
|  | ||||
|     std::optional<ResourceAPI::ModLoaderType> loaderType; | ||||
|     std::optional<ModPlatform::ModLoaderType> loaderType; | ||||
|     QString loaderVersion; | ||||
|  | ||||
|     QIcon icon; | ||||
|   | ||||
| @@ -68,21 +68,25 @@ void PackInstallTask::copySettings() | ||||
|     auto modloader = m_pack.loaderType; | ||||
|     if (modloader.has_value()) | ||||
|         switch (modloader.value()) { | ||||
|             case ResourceAPI::Forge: { | ||||
|             case ModPlatform::NeoForge: { | ||||
|                 components->setComponentVersion("net.neoforged", m_pack.version, true); | ||||
|                 break; | ||||
|             } | ||||
|             case ModPlatform::Forge: { | ||||
|                 components->setComponentVersion("net.minecraftforge", m_pack.version, true); | ||||
|                 break; | ||||
|             } | ||||
|             case ResourceAPI::Fabric: { | ||||
|             case ModPlatform::Fabric: { | ||||
|                 components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.version, true); | ||||
|                 break; | ||||
|             } | ||||
|             case ResourceAPI::Quilt: { | ||||
|             case ModPlatform::Quilt: { | ||||
|                 components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.version, true); | ||||
|                 break; | ||||
|             } | ||||
|             case ResourceAPI::Cauldron: | ||||
|             case ModPlatform::Cauldron: | ||||
|                 break; | ||||
|             case ResourceAPI::LiteLoader: | ||||
|             case ModPlatform::LiteLoader: | ||||
|                 break; | ||||
|         } | ||||
|     components->saveNow(); | ||||
|   | ||||
| @@ -41,7 +41,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f | ||||
| Task::Ptr ModrinthAPI::latestVersion(QString hash, | ||||
|                                      QString hash_format, | ||||
|                                      std::optional<std::list<Version>> mcVersions, | ||||
|                                      std::optional<ModLoaderTypes> loaders, | ||||
|                                      std::optional<ModPlatform::ModLoaderTypes> loaders, | ||||
|                                      std::shared_ptr<QByteArray> response) | ||||
| { | ||||
|     auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); | ||||
| @@ -71,7 +71,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash, | ||||
| Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes, | ||||
|                                       QString hash_format, | ||||
|                                       std::optional<std::list<Version>> mcVersions, | ||||
|                                       std::optional<ModLoaderTypes> loaders, | ||||
|                                       std::optional<ModPlatform::ModLoaderTypes> loaders, | ||||
|                                       std::shared_ptr<QByteArray> response) | ||||
| { | ||||
|     auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); | ||||
|   | ||||
| @@ -19,13 +19,13 @@ class ModrinthAPI : public NetworkResourceAPI { | ||||
|     auto latestVersion(QString hash, | ||||
|                        QString hash_format, | ||||
|                        std::optional<std::list<Version>> mcVersions, | ||||
|                        std::optional<ModLoaderTypes> loaders, | ||||
|                        std::optional<ModPlatform::ModLoaderTypes> loaders, | ||||
|                        std::shared_ptr<QByteArray> response) -> Task::Ptr; | ||||
|  | ||||
|     auto latestVersions(const QStringList& hashes, | ||||
|                         QString hash_format, | ||||
|                         std::optional<std::list<Version>> mcVersions, | ||||
|                         std::optional<ModLoaderTypes> loaders, | ||||
|                         std::optional<ModPlatform::ModLoaderTypes> loaders, | ||||
|                         std::shared_ptr<QByteArray> response) -> Task::Ptr; | ||||
|  | ||||
|     Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override; | ||||
| @@ -35,20 +35,19 @@ class ModrinthAPI : public NetworkResourceAPI { | ||||
|  | ||||
|     inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; }; | ||||
|  | ||||
|     static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList | ||||
|     static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList | ||||
|     { | ||||
|         QStringList l; | ||||
|         for (auto loader : { Forge, Fabric, Quilt, LiteLoader }) { | ||||
|         for (auto loader : | ||||
|              { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt, ModPlatform::LiteLoader }) { | ||||
|             if (types & loader) { | ||||
|                 l << getModLoaderString(loader); | ||||
|             } | ||||
|         } | ||||
|         if ((types & Quilt) && (~types & Fabric))  // Add Fabric if Quilt is in use, if Fabric isn't already there | ||||
|             l << getModLoaderString(Fabric); | ||||
|         return l; | ||||
|     } | ||||
|  | ||||
|     static auto getModLoaderFilters(ModLoaderTypes types) -> const QString | ||||
|     static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString | ||||
|     { | ||||
|         QStringList l; | ||||
|         for (auto loader : getModLoaderStrings(types)) { | ||||
| @@ -141,7 +140,10 @@ class ModrinthAPI : public NetworkResourceAPI { | ||||
|         return s.isEmpty() ? QString() : s; | ||||
|     } | ||||
|  | ||||
|     static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (Forge | Fabric | Quilt | LiteLoader); } | ||||
|     static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool | ||||
|     { | ||||
|         return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt | ModPlatform::LiteLoader); | ||||
|     } | ||||
|  | ||||
|     [[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override | ||||
|     { | ||||
|   | ||||
| @@ -11,7 +11,6 @@ | ||||
| #include "tasks/ConcurrentTask.h" | ||||
|  | ||||
| #include "minecraft/mod/ModFolderModel.h" | ||||
| #include "minecraft/mod/ResourceFolderModel.h" | ||||
|  | ||||
| static ModrinthAPI api; | ||||
| static ModPlatform::ProviderCapabilities ProviderCaps; | ||||
| @@ -39,7 +38,7 @@ void ModrinthCheckUpdate::executeTask() | ||||
|     QStringList hashes; | ||||
|     auto best_hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); | ||||
|  | ||||
|     ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", 10); | ||||
|     ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); | ||||
|     for (auto* mod : m_mods) { | ||||
|         if (!mod->enabled()) { | ||||
|             emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!")); | ||||
| @@ -111,11 +110,11 @@ void ModrinthCheckUpdate::executeTask() | ||||
|                 // so we may want to filter it | ||||
|                 QString loader_filter; | ||||
|                 if (m_loaders.has_value()) { | ||||
|                     static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric, | ||||
|                                           ResourceAPI::ModLoaderType::Quilt }; | ||||
|                     static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge, | ||||
|                                           ModPlatform::ModLoaderType::Fabric, ModPlatform::ModLoaderType::Quilt }; | ||||
|                     for (auto flag : flags) { | ||||
|                         if (m_loaders.value().testFlag(flag)) { | ||||
|                             loader_filter = api.getModLoaderString(flag); | ||||
|                             loader_filter = ModPlatform::getModLoaderString(flag); | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
| @@ -145,9 +144,6 @@ void ModrinthCheckUpdate::executeTask() | ||||
|                 auto mod = *mod_iter; | ||||
|  | ||||
|                 auto key = project_ver.hash; | ||||
|                 if ((key != hash && project_ver.is_preferred) || (mod->status() == ModStatus::NotInstalled)) { | ||||
|                     if (mod->version() == project_ver.version_number) | ||||
|                         continue; | ||||
|  | ||||
|                 // Fake pack with the necessary info to pass to the download task :) | ||||
|                 auto pack = std::make_shared<ModPlatform::IndexedPack>(); | ||||
| @@ -159,12 +155,16 @@ void ModrinthCheckUpdate::executeTask() | ||||
|                     pack->authors.append({ author }); | ||||
|                 pack->description = mod->description(); | ||||
|                 pack->provider = ModPlatform::ResourceProvider::MODRINTH; | ||||
|                 if ((key != hash && project_ver.is_preferred) || (mod->status() == ModStatus::NotInstalled)) { | ||||
|                     if (mod->version() == project_ver.version_number) | ||||
|                         continue; | ||||
|  | ||||
|                     auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder); | ||||
|  | ||||
|                     m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.changelog, | ||||
|                                              ModPlatform::ResourceProvider::MODRINTH, download_task); | ||||
|                 } | ||||
|                 m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver)); | ||||
|             } | ||||
|         } catch (Json::JsonException& e) { | ||||
|             failed(e.cause() + " : " + e.what()); | ||||
|   | ||||
| @@ -10,7 +10,7 @@ class ModrinthCheckUpdate : public CheckUpdateTask { | ||||
|    public: | ||||
|     ModrinthCheckUpdate(QList<Mod*>& mods, | ||||
|                         std::list<Version>& mcVersions, | ||||
|                         std::optional<ResourceAPI::ModLoaderTypes> loaders, | ||||
|                         std::optional<ModPlatform::ModLoaderTypes> loaders, | ||||
|                         std::shared_ptr<ModFolderModel> mods_folder) | ||||
|         : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) | ||||
|     {} | ||||
|   | ||||
| @@ -211,6 +211,8 @@ bool ModrinthCreationTask::createInstance() | ||||
|         components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version); | ||||
|     if (!m_forge_version.isEmpty()) | ||||
|         components->setComponentVersion("net.minecraftforge", m_forge_version); | ||||
|     if (!m_neoForge_version.isEmpty()) | ||||
|         components->setComponentVersion("net.neoforged", m_neoForge_version); | ||||
|  | ||||
|     if (m_instIcon != "default") { | ||||
|         instance.setIconKey(m_instIcon); | ||||
| @@ -398,6 +400,8 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, | ||||
|                         m_quilt_version = Json::requireString(*it, "Quilt Loader version"); | ||||
|                     } else if (name == "forge") { | ||||
|                         m_forge_version = Json::requireString(*it, "Forge version"); | ||||
|                     } else if (name == "neoforge") { | ||||
|                         m_neoForge_version = Json::requireString(*it, "NeoForge version"); | ||||
|                     } else { | ||||
|                         throw JSONValidationError("Unknown dependency type: " + name); | ||||
|                     } | ||||
|   | ||||
| @@ -39,7 +39,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { | ||||
|    private: | ||||
|     QWidget* m_parent = nullptr; | ||||
|  | ||||
|     QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version; | ||||
|     QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version, m_neoForge_version; | ||||
|     QString m_managed_id, m_managed_version_id, m_managed_name; | ||||
|  | ||||
|     std::vector<Modrinth::File> m_files; | ||||
|   | ||||
| @@ -33,12 +33,14 @@ const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "z | ||||
| ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, | ||||
|                                                const QString& version, | ||||
|                                                const QString& summary, | ||||
|                                                bool optionalFiles, | ||||
|                                                InstancePtr instance, | ||||
|                                                const QString& output, | ||||
|                                                MMCZip::FilterFunction filter) | ||||
|     : name(name) | ||||
|     , version(version) | ||||
|     , summary(summary) | ||||
|     , optionalFiles(optionalFiles) | ||||
|     , instance(instance) | ||||
|     , mcInstance(dynamic_cast<MinecraftInstance*>(instance.get())) | ||||
|     , gameRoot(instance->gameRoot()) | ||||
| @@ -245,6 +247,7 @@ QByteArray ModrinthPackExportTask::generateIndex() | ||||
|         const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); | ||||
|         const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); | ||||
|         const ComponentPtr forge = profile->getComponent("net.minecraftforge"); | ||||
|         const ComponentPtr neoForge = profile->getComponent("net.neoforged"); | ||||
|  | ||||
|         // convert all available components to mrpack dependencies | ||||
|         QJsonObject dependencies; | ||||
| @@ -256,6 +259,8 @@ QByteArray ModrinthPackExportTask::generateIndex() | ||||
|             dependencies["fabric-loader"] = fabric->m_version; | ||||
|         if (forge != nullptr) | ||||
|             dependencies["forge"] = forge->m_version; | ||||
|         if (neoForge != nullptr) | ||||
|             dependencies["neoforge"] = neoForge->m_version; | ||||
|  | ||||
|         out["dependencies"] = dependencies; | ||||
|     } | ||||
| @@ -267,6 +272,7 @@ QByteArray ModrinthPackExportTask::generateIndex() | ||||
|         QString path = iterator.key(); | ||||
|         const ResolvedFile& value = iterator.value(); | ||||
|  | ||||
|         if (optionalFiles) { | ||||
|             // detect disabled mod | ||||
|             const QFileInfo pathInfo(path); | ||||
|             if (pathInfo.suffix() == "disabled") { | ||||
| @@ -278,6 +284,7 @@ QByteArray ModrinthPackExportTask::generateIndex() | ||||
|                 env["server"] = "optional"; | ||||
|                 fileOut["env"] = env; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         fileOut["path"] = path; | ||||
|         fileOut["downloads"] = QJsonArray{ iterator.value().url }; | ||||
|   | ||||
| @@ -31,6 +31,7 @@ class ModrinthPackExportTask : public Task { | ||||
|     ModrinthPackExportTask(const QString& name, | ||||
|                            const QString& version, | ||||
|                            const QString& summary, | ||||
|                            bool optionalFiles, | ||||
|                            InstancePtr instance, | ||||
|                            const QString& output, | ||||
|                            MMCZip::FilterFunction filter); | ||||
| @@ -50,6 +51,7 @@ class ModrinthPackExportTask : public Task { | ||||
|  | ||||
|     // inputs | ||||
|     const QString name, version, summary; | ||||
|     const bool optionalFiles; | ||||
|     const InstancePtr instance; | ||||
|     MinecraftInstance* mcInstance; | ||||
|     const QDir gameRoot; | ||||
|   | ||||
| @@ -93,19 +93,19 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob | ||||
|     pack.extraDataLoaded = true; | ||||
| } | ||||
|  | ||||
| void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, | ||||
|                                        QJsonArray& arr, | ||||
|                                        [[maybe_unused]] const shared_qobject_ptr<QNetworkAccessManager>& network, | ||||
|                                        const BaseInstance* inst) | ||||
| void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst) | ||||
| { | ||||
|     QVector<ModPlatform::IndexedVersion> unsortedVersions; | ||||
|     QString mcVersion = (static_cast<const MinecraftInstance*>(inst))->getPackProfile()->getComponentVersion("net.minecraft"); | ||||
|     auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile(); | ||||
|     QString mcVersion = profile->getComponentVersion("net.minecraft"); | ||||
|     auto loaders = profile->getSupportedModLoaders(); | ||||
|  | ||||
|     for (auto versionIter : arr) { | ||||
|         auto obj = versionIter.toObject(); | ||||
|         auto file = loadIndexedPackVersion(obj); | ||||
|  | ||||
|         if (file.fileId.isValid())  // Heuristic to check if the returned value is valid | ||||
|         if (file.fileId.isValid() && | ||||
|             (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders))  // Heuristic to check if the returned value is valid | ||||
|             unsortedVersions.append(file); | ||||
|     } | ||||
|     auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { | ||||
| @@ -134,7 +134,18 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t | ||||
|     } | ||||
|     auto loaders = Json::requireArray(obj, "loaders"); | ||||
|     for (auto loader : loaders) { | ||||
|         file.loaders.append(loader.toString()); | ||||
|         if (loader == "neoforge") | ||||
|             file.loaders |= ModPlatform::NeoForge; | ||||
|         if (loader == "forge") | ||||
|             file.loaders |= ModPlatform::Forge; | ||||
|         if (loader == "cauldron") | ||||
|             file.loaders |= ModPlatform::Cauldron; | ||||
|         if (loader == "liteloader") | ||||
|             file.loaders |= ModPlatform::LiteLoader; | ||||
|         if (loader == "fabric") | ||||
|             file.loaders |= ModPlatform::Fabric; | ||||
|         if (loader == "quilt") | ||||
|             file.loaders |= ModPlatform::Quilt; | ||||
|     } | ||||
|     file.version = Json::requireString(obj, "name"); | ||||
|     file.version_number = Json::requireString(obj, "version_number"); | ||||
| @@ -218,15 +229,20 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t | ||||
|     return {}; | ||||
| } | ||||
|  | ||||
| auto Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion | ||||
| auto Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) | ||||
|     -> ModPlatform::IndexedVersion | ||||
| { | ||||
|     QVector<ModPlatform::IndexedVersion> versions; | ||||
|     auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile(); | ||||
|     QString mcVersion = profile->getComponentVersion("net.minecraft"); | ||||
|     auto loaders = profile->getSupportedModLoaders(); | ||||
|  | ||||
|     QVector<ModPlatform::IndexedVersion> versions; | ||||
|     for (auto versionIter : arr) { | ||||
|         auto obj = versionIter.toObject(); | ||||
|         auto file = loadIndexedPackVersion(obj); | ||||
|  | ||||
|         if (file.fileId.isValid())  // Heuristic to check if the returned value is valid | ||||
|         if (file.fileId.isValid() && | ||||
|             (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders))  // Heuristic to check if the returned value is valid | ||||
|             versions.append(file); | ||||
|     } | ||||
|     auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { | ||||
|   | ||||
| @@ -26,11 +26,8 @@ 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, | ||||
|                              const BaseInstance* inst); | ||||
| void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst); | ||||
| auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion; | ||||
| auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion; | ||||
| auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion; | ||||
|  | ||||
| }  // namespace Modrinth | ||||
|   | ||||
| @@ -218,9 +218,24 @@ void HttpMetaCache::Load() | ||||
|     if (!index.open(QIODevice::ReadOnly)) | ||||
|         return; | ||||
|  | ||||
|     QJsonDocument json = QJsonDocument::fromJson(index.readAll()); | ||||
|     QJsonParseError parseError; | ||||
|     QJsonDocument json = QJsonDocument::fromJson(index.readAll(), &parseError); | ||||
|  | ||||
|     auto root = Json::requireObject(json, "HttpMetaCache root"); | ||||
|     // Fail if the JSON is invalid. | ||||
|     if (parseError.error != QJsonParseError::NoError) { | ||||
|         qCritical() << QString("Failed to parse HttpMetaCache file: %1 at offset %2") | ||||
|                            .arg(parseError.errorString(), QString::number(parseError.offset)) | ||||
|                            .toUtf8(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Make sure the root is an object. | ||||
|     if (!json.isObject()) { | ||||
|         qCritical() << "HttpMetaCache root should be an object."; | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     auto root = json.object(); | ||||
|  | ||||
|     // check file version first | ||||
|     auto version_val = Json::ensureString(root, "version"); | ||||
|   | ||||
| @@ -36,9 +36,14 @@ | ||||
|  */ | ||||
|  | ||||
| #include "NetJob.h" | ||||
| #include "Application.h" | ||||
| #include "tasks/ConcurrentTask.h" | ||||
| #include "ui/dialogs/CustomMessageBox.h" | ||||
|  | ||||
| NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network) | ||||
|     : ConcurrentTask(nullptr, job_name, APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()), m_network(network) | ||||
| {} | ||||
|  | ||||
| auto NetJob::addNetAction(NetAction::Ptr action) -> bool | ||||
| { | ||||
|     action->setNetwork(m_network); | ||||
|   | ||||
| @@ -52,9 +52,7 @@ class NetJob : public ConcurrentTask { | ||||
|    public: | ||||
|     using Ptr = shared_qobject_ptr<NetJob>; | ||||
|  | ||||
|     explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network) | ||||
|         : ConcurrentTask(nullptr, job_name), m_network(network) | ||||
|     {} | ||||
|     explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network); | ||||
|     ~NetJob() override = default; | ||||
|  | ||||
|     void startNext() override; | ||||
|   | ||||
| @@ -350,6 +350,7 @@ | ||||
|  | ||||
|         <file>scalable/instances/quiltmc.svg</file>  <!-- CC0 QuiltMC --> | ||||
|         <file>scalable/instances/fabricmc.svg</file>  <!-- CC0 unascribed, https://github.com/FabricMC/community/blob/main/media/unascribed/README.md --> | ||||
|         <file>scalable/instances/neoforged.svg</file> | ||||
|         <file>128x128/instances/forge.png</file>  <!-- LGPL3 Forge Development LLC --> | ||||
|         <file>128x128/instances/liteloader.png</file>  <!-- CC-BY-SA 4.0 LiteLoader --> | ||||
|     </qresource> | ||||
|   | ||||
| @@ -0,0 +1,3 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||||
| <svg width="272" height="272" version="1.1" viewBox="0 0 272 272" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><g transform="matrix(1.25,0,0,1.25,-544,-34)" stroke-width="0"><path d="m512 224v16h64v-16h32v-16h16v-32h16v-16h-16l-8-8v-32l8-8v-32l-8-8-16 16h-16l-24-24h-32l-24 24h-16l-16-16-8 8v32l8 8v32l-8 8h-16v16h16v32h16v16z" fill="#a44e37"/><path d="m480 176v16l8 8h112l8-8v-16h16v-16l-8-8 16-16-32-32h-16l-8-8h-16l-8 8v16l-16 16v-32l-8-8h-16l-8 8h-16l-32 32 16 16-8 8v16z" fill="#d7742f"/><path d="m528 144h16v-16h16v-32l-8-8h-16l-8 8z" fill="#e68c37"/><path d="m576 96h32v16h-32z" fill="#bf6134"/><path d="m528 80h32v16h-32z" fill="#bf6134"/><path d="m480 96h32v16h-32z" fill="#bb5f33"/><g fill="#bf6134"><path d="m528 160h32v16h-32z"/><path d="m480 192h32v16h-32z"/><path d="m576 192h32v16h-32z"/></g><path d="m512 192v32h16l8-8h16l8 8h16v-32h-16l-8 8h-16l-8-8z" fill="#f9f4f4"/><path d="m528 208v16h32v-16z" fill="#e7d9d3"/><path d="m528 208v-16h32v16z" fill="#13151a"/><path d="m480 128h16l8 8v24l-8 8h-8l-8-8z" fill="#f9f4f4"/><path d="m480 160h16l8 8-8 8h-16z" fill="#e7d9d3"/><path d="m512 128v16l-8 8-8-8v-16z" fill="#e7d9d3"/><path d="m512 144v16l-8 8-8-8v-16z" fill="#262a33"/><path d="m512 160v16h-16v-16z" fill="#13151a"/><g transform="matrix(-1,0,0,1,1088,0)"><path d="m480 128h16l8 8v24l-8 8h-8l-8-8z" fill="#f9f4f4"/><path d="m480 160h16l8 8-8 8h-16z" fill="#e7d9d3"/><path d="m512 128v16l-8 8-8-8v-16z" fill="#e7d9d3"/><path d="m512 144v16l-8 8-8-8v-16z" fill="#262a33"/><path d="m512 160v16h-16v-16z" fill="#13151a"/></g><path d="m608 96v-16h16v-16h-16v-16h-16v-16h-32v48h16l16 16z" fill="#66534d"/><path d="m608 64v16h-16l-16-16v-16h16v16z" fill="#8d7168"/><path d="m584 88-8-8v-16h16v16z" fill="#e7d9d3"/><path d="m576 80v16h16v-16z" fill="#c7a3b9"/><g transform="matrix(-1,0,0,1,1088,0)"><path d="m608 96v-16h16v-16h-16v-16h-16v-16h-32v48h16l16 16z" fill="#66534d"/><path d="m608 64v16h-16l-16-16v-16h16v16z" fill="#8d7168"/><path d="m584 88-8-8v-16h16v16z" fill="#e7d9d3"/><path d="m576 80v16h16v-16z" fill="#c7a3b9"/></g><g fill="#bb5f33"><path d="m480 112h-16v16h16z"/><path d="m464 128h-16v16h16z"/><path d="m480 144h-16v16h16z"/><path d="m624 144h-16v16h16z"/><path d="m640 128h-16v16h16z"/><path d="m624 112h-16v16h16z"/></g></g><metadata><rdf:RDF><cc:Work rdf:about=""><dc:creator><cc:Agent><dc:title>Sefa Eyeoglu <contact@scrumplex.net></dc:title></cc:Agent></dc:creator></cc:Work></rdf:RDF></metadata></svg> | ||||
| After Width: | Height: | Size: 2.6 KiB | 
| @@ -51,6 +51,9 @@ class ConcurrentTask : public Task { | ||||
|     explicit ConcurrentTask(QObject* parent = nullptr, QString task_name = "", int max_concurrent = 6); | ||||
|     ~ConcurrentTask() override; | ||||
|  | ||||
|     // safe to call before starting the task | ||||
|     void setMaxConcurrent(int max_concurrent) { m_total_max_size = max_concurrent; } | ||||
|  | ||||
|     bool canAbort() const override { return true; } | ||||
|  | ||||
|     inline auto isMultiStep() const -> bool override { return totalSize() > 1; } | ||||
|   | ||||
| @@ -44,8 +44,6 @@ | ||||
| #include <QPushButton> | ||||
| #include <QScrollBar> | ||||
|  | ||||
| #include "ui/dialogs/CustomMessageBox.h" | ||||
| #include "ui/dialogs/ProgressDialog.h" | ||||
| #include "ui/widgets/PageContainer.h" | ||||
|  | ||||
| #include "InstancePageProvider.h" | ||||
| @@ -76,40 +74,44 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget* parent) : QMainWin | ||||
|  | ||||
|     // Add custom buttons to the page container layout. | ||||
|     { | ||||
|         auto horizontalLayout = new QHBoxLayout(); | ||||
|         auto horizontalLayout = new QHBoxLayout(this); | ||||
|         horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); | ||||
|         horizontalLayout->setContentsMargins(6, -1, 6, -1); | ||||
|  | ||||
|         auto btnHelp = new QPushButton(); | ||||
|         auto btnHelp = new QPushButton(this); | ||||
|         btnHelp->setText(tr("Help")); | ||||
|         horizontalLayout->addWidget(btnHelp); | ||||
|         connect(btnHelp, SIGNAL(clicked(bool)), m_container, SLOT(help())); | ||||
|         connect(btnHelp, &QPushButton::clicked, m_container, &PageContainer::help); | ||||
|  | ||||
|         auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); | ||||
|         horizontalLayout->addSpacerItem(spacer); | ||||
|  | ||||
|         m_killButton = new QPushButton(); | ||||
|         m_launchButton = new QToolButton(this); | ||||
|         m_launchButton->setText(tr("&Launch")); | ||||
|         m_launchButton->setToolTip(tr("Launch the instance")); | ||||
|         m_launchButton->setPopupMode(QToolButton::MenuButtonPopup); | ||||
|         m_launchButton->setMinimumWidth(80);  // HACK!! | ||||
|         horizontalLayout->addWidget(m_launchButton); | ||||
|         connect(m_launchButton, &QPushButton::clicked, this, [this] { APPLICATION->launch(m_instance); }); | ||||
|  | ||||
|         m_killButton = new QPushButton(this); | ||||
|         m_killButton->setText(tr("&Kill")); | ||||
|         m_killButton->setToolTip(tr("Kill the running instance")); | ||||
|         m_killButton->setShortcut(QKeySequence(tr("Ctrl+K"))); | ||||
|         horizontalLayout->addWidget(m_killButton); | ||||
|         connect(m_killButton, SIGNAL(clicked(bool)), SLOT(on_btnKillMinecraft_clicked())); | ||||
|         connect(m_killButton, &QPushButton::clicked, this, [this] { APPLICATION->kill(m_instance); }); | ||||
|  | ||||
|         m_launchOfflineButton = new QPushButton(); | ||||
|         horizontalLayout->addWidget(m_launchOfflineButton); | ||||
|         m_launchOfflineButton->setText(tr("Launch Offline")); | ||||
|         updateButtons(); | ||||
|  | ||||
|         m_launchDemoButton = new QPushButton(); | ||||
|         horizontalLayout->addWidget(m_launchDemoButton); | ||||
|         m_launchDemoButton->setText(tr("Launch Demo")); | ||||
|  | ||||
|         updateLaunchButtons(); | ||||
|         connect(m_launchOfflineButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftOffline_clicked())); | ||||
|         connect(m_launchDemoButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftDemo_clicked())); | ||||
|  | ||||
|         m_closeButton = new QPushButton(); | ||||
|         m_closeButton = new QPushButton(this); | ||||
|         m_closeButton->setText(tr("Close")); | ||||
|         horizontalLayout->addWidget(m_closeButton); | ||||
|         connect(m_closeButton, SIGNAL(clicked(bool)), SLOT(on_closeButton_clicked())); | ||||
|         connect(m_closeButton, &QPushButton::clicked, this, &QMainWindow::close); | ||||
|  | ||||
|         m_container->addButtons(horizontalLayout); | ||||
|  | ||||
|         connect(m_instance.get(), &BaseInstance::profilerChanged, this, &InstanceWindow::updateButtons); | ||||
|         connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceWindow::updateButtons); | ||||
|     } | ||||
|  | ||||
|     // restore window state | ||||
| @@ -149,47 +151,18 @@ void InstanceWindow::on_instanceStatusChanged(BaseInstance::Status, BaseInstance | ||||
|     } | ||||
| } | ||||
|  | ||||
| void InstanceWindow::updateLaunchButtons() | ||||
| void InstanceWindow::updateButtons() | ||||
| { | ||||
|     if (m_instance->isRunning()) { | ||||
|         m_launchOfflineButton->setEnabled(false); | ||||
|         m_launchDemoButton->setEnabled(false); | ||||
|         m_killButton->setText(tr("Kill")); | ||||
|         m_killButton->setObjectName("killButton"); | ||||
|         m_killButton->setToolTip(tr("Kill the running instance")); | ||||
|     } else if (!m_instance->canLaunch()) { | ||||
|         m_launchOfflineButton->setEnabled(false); | ||||
|         m_launchDemoButton->setEnabled(false); | ||||
|         m_killButton->setText(tr("Launch")); | ||||
|         m_killButton->setObjectName("launchButton"); | ||||
|         m_killButton->setToolTip(tr("Launch the instance")); | ||||
|         m_killButton->setEnabled(false); | ||||
|     } else { | ||||
|         m_launchOfflineButton->setEnabled(true); | ||||
|     m_launchButton->setEnabled(m_instance->canLaunch()); | ||||
|     m_killButton->setEnabled(m_instance->isRunning()); | ||||
|  | ||||
|         // Disable demo-mode if not available. | ||||
|         auto instance = dynamic_cast<MinecraftInstance*>(m_instance.get()); | ||||
|         if (instance) { | ||||
|             m_launchDemoButton->setEnabled(instance->supportsDemo()); | ||||
|         } | ||||
|  | ||||
|         m_killButton->setText(tr("Launch")); | ||||
|         m_killButton->setObjectName("launchButton"); | ||||
|         m_killButton->setToolTip(tr("Launch the instance")); | ||||
|     } | ||||
|     // NOTE: this is a hack to force the button to recalculate its style | ||||
|     m_killButton->setStyleSheet("/* */"); | ||||
|     m_killButton->setStyleSheet(QString()); | ||||
| } | ||||
|  | ||||
| void InstanceWindow::on_btnLaunchMinecraftOffline_clicked() | ||||
| { | ||||
|     APPLICATION->launch(m_instance, false, false, nullptr); | ||||
| } | ||||
|  | ||||
| void InstanceWindow::on_btnLaunchMinecraftDemo_clicked() | ||||
| { | ||||
|     APPLICATION->launch(m_instance, false, true, nullptr); | ||||
|     QMenu* launchMenu = m_launchButton->menu(); | ||||
|     if (launchMenu) | ||||
|         launchMenu->clear(); | ||||
|     else | ||||
|         launchMenu = new QMenu(this); | ||||
|     m_instance->populateLaunchMenu(launchMenu); | ||||
|     m_launchButton->setMenu(launchMenu); | ||||
| } | ||||
|  | ||||
| void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc) | ||||
| @@ -199,18 +172,13 @@ void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> pr | ||||
|  | ||||
| void InstanceWindow::runningStateChanged(bool running) | ||||
| { | ||||
|     updateLaunchButtons(); | ||||
|     updateButtons(); | ||||
|     m_container->refreshContainer(); | ||||
|     if (running) { | ||||
|         selectPage("log"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void InstanceWindow::on_closeButton_clicked() | ||||
| { | ||||
|     close(); | ||||
| } | ||||
|  | ||||
| void InstanceWindow::closeEvent(QCloseEvent* event) | ||||
| { | ||||
|     bool proceed = true; | ||||
| @@ -233,15 +201,6 @@ bool InstanceWindow::saveAll() | ||||
|     return m_container->saveAll(); | ||||
| } | ||||
|  | ||||
| void InstanceWindow::on_btnKillMinecraft_clicked() | ||||
| { | ||||
|     if (m_instance->isRunning()) { | ||||
|         APPLICATION->kill(m_instance); | ||||
|     } else { | ||||
|         APPLICATION->launch(m_instance, true, false, nullptr); | ||||
|     } | ||||
| } | ||||
|  | ||||
| QString InstanceWindow::instanceId() | ||||
| { | ||||
|     return m_instance->id(); | ||||
| @@ -252,17 +211,15 @@ bool InstanceWindow::selectPage(QString pageId) | ||||
|     return m_container->selectPage(pageId); | ||||
| } | ||||
|  | ||||
| BasePage* InstanceWindow::selectedPage() const | ||||
| { | ||||
|     return m_container->selectedPage(); | ||||
| } | ||||
|  | ||||
| void InstanceWindow::refreshContainer() | ||||
| { | ||||
|     m_container->refreshContainer(); | ||||
| } | ||||
|  | ||||
| InstanceWindow::~InstanceWindow() {} | ||||
| BasePage* InstanceWindow::selectedPage() const | ||||
| { | ||||
|     return m_container->selectedPage(); | ||||
| } | ||||
|  | ||||
| bool InstanceWindow::requestClose() | ||||
| { | ||||
|   | ||||
| @@ -38,6 +38,7 @@ | ||||
|  | ||||
| #include <QMainWindow> | ||||
| #include <QSystemTrayIcon> | ||||
| #include <QToolButton> | ||||
|  | ||||
| #include "LaunchController.h" | ||||
| #include "launch/LaunchTask.h" | ||||
| @@ -53,7 +54,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer { | ||||
|  | ||||
|    public: | ||||
|     explicit InstanceWindow(InstancePtr proc, QWidget* parent = 0); | ||||
|     virtual ~InstanceWindow(); | ||||
|     virtual ~InstanceWindow() = default; | ||||
|  | ||||
|     bool selectPage(QString pageId) override; | ||||
|     BasePage* selectedPage() const override; | ||||
| @@ -71,11 +72,6 @@ class InstanceWindow : public QMainWindow, public BasePageContainer { | ||||
|     void isClosing(); | ||||
|  | ||||
|    private slots: | ||||
|     void on_closeButton_clicked(); | ||||
|     void on_btnKillMinecraft_clicked(); | ||||
|     void on_btnLaunchMinecraftOffline_clicked(); | ||||
|     void on_btnLaunchMinecraftDemo_clicked(); | ||||
|  | ||||
|     void instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc); | ||||
|     void runningStateChanged(bool running); | ||||
|     void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus); | ||||
| @@ -84,7 +80,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer { | ||||
|     void closeEvent(QCloseEvent*) override; | ||||
|  | ||||
|    private: | ||||
|     void updateLaunchButtons(); | ||||
|     void updateButtons(); | ||||
|  | ||||
|    private: | ||||
|     shared_qobject_ptr<LaunchTask> m_proc; | ||||
| @@ -92,7 +88,6 @@ class InstanceWindow : public QMainWindow, public BasePageContainer { | ||||
|     bool m_doNotSave = false; | ||||
|     PageContainer* m_container = nullptr; | ||||
|     QPushButton* m_closeButton = nullptr; | ||||
|     QToolButton* m_launchButton = nullptr; | ||||
|     QPushButton* m_killButton = nullptr; | ||||
|     QPushButton* m_launchOfflineButton = nullptr; | ||||
|     QPushButton* m_launchDemoButton = nullptr; | ||||
| }; | ||||
|   | ||||
| @@ -43,7 +43,6 @@ | ||||
| #include "FileSystem.h" | ||||
|  | ||||
| #include "MainWindow.h" | ||||
| #include "ui/dialogs/ExportToModListDialog.h" | ||||
| #include "ui_MainWindow.h" | ||||
|  | ||||
| #include <QDir> | ||||
| @@ -90,17 +89,14 @@ | ||||
| #include <news/NewsChecker.h> | ||||
| #include <tools/BaseProfiler.h> | ||||
| #include <updater/ExternalUpdater.h> | ||||
| #include "InstancePageProvider.h" | ||||
| #include "InstanceWindow.h" | ||||
| #include "JavaCommon.h" | ||||
| #include "LaunchController.h" | ||||
|  | ||||
| #include "ui/dialogs/AboutDialog.h" | ||||
| #include "ui/dialogs/CopyInstanceDialog.h" | ||||
| #include "ui/dialogs/CustomMessageBox.h" | ||||
| #include "ui/dialogs/EditAccountDialog.h" | ||||
| #include "ui/dialogs/ExportInstanceDialog.h" | ||||
| #include "ui/dialogs/ExportPackDialog.h" | ||||
| #include "ui/dialogs/ExportToModListDialog.h" | ||||
| #include "ui/dialogs/IconPickerDialog.h" | ||||
| #include "ui/dialogs/ImportResourceDialog.h" | ||||
| #include "ui/dialogs/NewInstanceDialog.h" | ||||
| @@ -113,17 +109,22 @@ | ||||
| #include "ui/themes/ThemeManager.h" | ||||
| #include "ui/widgets/LabeledToolButton.h" | ||||
|  | ||||
| #include "minecraft/PackProfile.h" | ||||
| #include "minecraft/VersionFile.h" | ||||
| #include "minecraft/WorldList.h" | ||||
| #include "minecraft/mod/ModFolderModel.h" | ||||
| #include "minecraft/mod/ResourcePackFolderModel.h" | ||||
| #include "minecraft/mod/ShaderPackFolderModel.h" | ||||
| #include "minecraft/mod/TexturePackFolderModel.h" | ||||
| #include "minecraft/mod/tasks/LocalResourceParse.h" | ||||
|  | ||||
| #include "modplatform/ModIndex.h" | ||||
| #include "modplatform/flame/FlameAPI.h" | ||||
| #include "modplatform/flame/FlameModIndex.h" | ||||
|  | ||||
| #include "KonamiCode.h" | ||||
|  | ||||
| #include "InstanceCopyTask.h" | ||||
| #include "InstanceImportTask.h" | ||||
|  | ||||
| #include "Json.h" | ||||
|  | ||||
| @@ -553,71 +554,15 @@ void MainWindow::updateMainToolBar() | ||||
|     ui->mainToolBar->setVisible(ui->menuBar->isNativeMenuBar() || !APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()); | ||||
| } | ||||
|  | ||||
| void MainWindow::updateToolsMenu() | ||||
| void MainWindow::updateLaunchButton() | ||||
| { | ||||
|     bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning(); | ||||
|  | ||||
|     ui->actionLaunchInstance->setDisabled(!m_selectedInstance || currentInstanceRunning); | ||||
|     ui->actionLaunchInstanceOffline->setDisabled(!m_selectedInstance || currentInstanceRunning); | ||||
|     ui->actionLaunchInstanceDemo->setDisabled(!m_selectedInstance || currentInstanceRunning); | ||||
|  | ||||
|     QMenu* launchMenu = ui->actionLaunchInstance->menu(); | ||||
|     if (launchMenu) { | ||||
|     if (launchMenu) | ||||
|         launchMenu->clear(); | ||||
|     } else { | ||||
|     else | ||||
|         launchMenu = new QMenu(this); | ||||
|     } | ||||
|     QAction* normalLaunch = launchMenu->addAction(tr("Launch")); | ||||
|     normalLaunch->setShortcut(QKeySequence::Open); | ||||
|     QAction* normalLaunchOffline = launchMenu->addAction(tr("Launch Offline")); | ||||
|     normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); | ||||
|     QAction* normalLaunchDemo = launchMenu->addAction(tr("Launch Demo")); | ||||
|     normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O"))); | ||||
|     if (m_selectedInstance) { | ||||
|         normalLaunch->setEnabled(m_selectedInstance->canLaunch()); | ||||
|         normalLaunchOffline->setEnabled(m_selectedInstance->canLaunch()); | ||||
|         normalLaunchDemo->setEnabled(m_selectedInstance->canLaunch()); | ||||
|  | ||||
|         connect(normalLaunch, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, true, false); }); | ||||
|         connect(normalLaunchOffline, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, false, false); }); | ||||
|         connect(normalLaunchDemo, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, false, true); }); | ||||
|     } else { | ||||
|         normalLaunch->setDisabled(true); | ||||
|         normalLaunchOffline->setDisabled(true); | ||||
|         normalLaunchDemo->setDisabled(true); | ||||
|     } | ||||
|  | ||||
|     // Disable demo-mode if not available. | ||||
|     auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get()); | ||||
|     if (instance) { | ||||
|         normalLaunchDemo->setEnabled(instance->supportsDemo()); | ||||
|     } | ||||
|  | ||||
|     QString profilersTitle = tr("Profilers"); | ||||
|     launchMenu->addSeparator()->setText(profilersTitle); | ||||
|     for (auto profiler : APPLICATION->profilers().values()) { | ||||
|         QAction* profilerAction = launchMenu->addAction(profiler->name()); | ||||
|         QAction* profilerOfflineAction = launchMenu->addAction(tr("%1 Offline").arg(profiler->name())); | ||||
|         QString error; | ||||
|         if (!profiler->check(&error)) { | ||||
|             profilerAction->setDisabled(true); | ||||
|             profilerOfflineAction->setDisabled(true); | ||||
|             QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\"."); | ||||
|             profilerAction->setToolTip(profilerToolTip); | ||||
|             profilerOfflineAction->setToolTip(profilerToolTip); | ||||
|         } else if (m_selectedInstance) { | ||||
|             profilerAction->setEnabled(m_selectedInstance->canLaunch()); | ||||
|             profilerOfflineAction->setEnabled(m_selectedInstance->canLaunch()); | ||||
|  | ||||
|             connect(profilerAction, &QAction::triggered, | ||||
|                     [this, profiler]() { APPLICATION->launch(m_selectedInstance, true, false, profiler.get()); }); | ||||
|             connect(profilerOfflineAction, &QAction::triggered, | ||||
|                     [this, profiler]() { APPLICATION->launch(m_selectedInstance, false, false, profiler.get()); }); | ||||
|         } else { | ||||
|             profilerAction->setDisabled(true); | ||||
|             profilerOfflineAction->setDisabled(true); | ||||
|         } | ||||
|     } | ||||
|     if (m_selectedInstance) | ||||
|         m_selectedInstance->populateLaunchMenu(launchMenu); | ||||
|     ui->actionLaunchInstance->setMenu(launchMenu); | ||||
| } | ||||
|  | ||||
| @@ -927,7 +872,7 @@ void MainWindow::finalizeInstance(InstancePtr inst) | ||||
|     } else { | ||||
|         CustomMessageBox::selectable(this, tr("Error"), | ||||
|                                      tr("The launcher cannot download Minecraft or update instances unless you have at least " | ||||
|                                         "one account added.\nPlease add your Mojang or Minecraft account."), | ||||
|                                         "one account added.\nPlease add your Microsoft or Mojang account."), | ||||
|                                      QMessageBox::Warning) | ||||
|             ->show(); | ||||
|     } | ||||
| @@ -980,6 +925,7 @@ void MainWindow::processURLs(QList<QUrl> urls) | ||||
|         if (url.scheme().isEmpty()) | ||||
|             url.setScheme("file"); | ||||
|  | ||||
|         ModPlatform::IndexedVersion version; | ||||
|         QMap<QString, QString> extra_info; | ||||
|         QUrl local_url; | ||||
|         if (!url.isLocalFile()) {  // download the remote resource and identify | ||||
| @@ -989,6 +935,11 @@ void MainWindow::processURLs(QList<QUrl> urls) | ||||
|                 // format of url curseforge://install?addonId=IDHERE&fileId=IDHERE | ||||
|                 QUrlQuery query(url); | ||||
|  | ||||
|                 if (query.allQueryItemValues("addonId").isEmpty() || query.allQueryItemValues("fileId").isEmpty()) { | ||||
|                     qDebug() << "Invalid curseforge link:" << url; | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 auto addonId = query.allQueryItemValues("addonId")[0]; | ||||
|                 auto fileId = query.allQueryItemValues("fileId")[0]; | ||||
|  | ||||
| @@ -1000,20 +951,19 @@ void MainWindow::processURLs(QList<QUrl> urls) | ||||
|                 auto api = FlameAPI(); | ||||
|                 auto job = api.getFile(addonId, fileId, array); | ||||
|  | ||||
|                 QString resource_name; | ||||
|  | ||||
|                 connect(job.get(), &Task::failed, this, | ||||
|                         [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); | ||||
|                 connect(job.get(), &Task::succeeded, this, [this, array, addonId, fileId, &dl_url, &resource_name] { | ||||
|                 connect(job.get(), &Task::succeeded, this, [this, array, addonId, fileId, &dl_url, &version] { | ||||
|                     qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str(); | ||||
|                     auto doc = Json::requireDocument(*array); | ||||
|                     auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data"); | ||||
|                     // No way to find out if it's a mod or a modpack before here | ||||
|                     // And also we need to check if it ends with .zip, instead of any better way | ||||
|                     auto fileName = Json::ensureString(data, "fileName"); | ||||
|                     version = FlameMod::loadIndexedPackVersion(data); | ||||
|                     auto fileName = version.fileName; | ||||
|  | ||||
|                     // Have to use ensureString then use QUrl to get proper url encoding | ||||
|                     dl_url = QUrl(Json::ensureString(data, "downloadUrl", "", "downloadUrl")); | ||||
|                     dl_url = QUrl(version.downloadUrl); | ||||
|                     if (!dl_url.isValid()) { | ||||
|                         CustomMessageBox::selectable( | ||||
|                             this, tr("Error"), | ||||
| @@ -1024,7 +974,6 @@ void MainWindow::processURLs(QList<QUrl> urls) | ||||
|                     } | ||||
|  | ||||
|                     QFileInfo dl_file(dl_url.fileName()); | ||||
|                     resource_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName"); | ||||
|                 }); | ||||
|  | ||||
|                 {  // drop stack | ||||
| @@ -1099,7 +1048,7 @@ void MainWindow::processURLs(QList<QUrl> urls) | ||||
|                 qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName; | ||||
|                 break; | ||||
|             case PackedResourceType::Mod: | ||||
|                 minecraftInst->loaderModList()->installMod(localFileName); | ||||
|                 minecraftInst->loaderModList()->installMod(localFileName, version); | ||||
|                 break; | ||||
|             case PackedResourceType::ShaderPack: | ||||
|                 minecraftInst->shaderPackList()->installResource(localFileName); | ||||
| @@ -1278,7 +1227,7 @@ void MainWindow::globalSettingsClosed() | ||||
|     proxymodel->invalidate(); | ||||
|     proxymodel->sort(0); | ||||
|     updateMainToolBar(); | ||||
|     updateToolsMenu(); | ||||
|     updateLaunchButton(); | ||||
|     updateThemeMenu(); | ||||
|     updateStatusCenter(); | ||||
|     // This needs to be done to prevent UI elements disappearing in the event the config is changed | ||||
| @@ -1408,11 +1357,12 @@ void MainWindow::on_actionDeleteInstance_triggered() | ||||
|  | ||||
|     if (APPLICATION->instances()->trashInstance(id)) { | ||||
|         ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     } else { | ||||
|         APPLICATION->instances()->deleteInstance(id); | ||||
|     } | ||||
|     APPLICATION->settings()->set("SelectedInstance", QString()); | ||||
|     selectionBad(); | ||||
| } | ||||
|  | ||||
| void MainWindow::on_actionExportInstanceZip_triggered() | ||||
| { | ||||
| @@ -1513,20 +1463,6 @@ void MainWindow::activateInstance(InstancePtr instance) | ||||
|     APPLICATION->launch(instance); | ||||
| } | ||||
|  | ||||
| void MainWindow::on_actionLaunchInstanceOffline_triggered() | ||||
| { | ||||
|     if (m_selectedInstance) { | ||||
|         APPLICATION->launch(m_selectedInstance, false); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void MainWindow::on_actionLaunchInstanceDemo_triggered() | ||||
| { | ||||
|     if (m_selectedInstance) { | ||||
|         APPLICATION->launch(m_selectedInstance, false, true); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void MainWindow::on_actionKillInstance_triggered() | ||||
| { | ||||
|     if (m_selectedInstance && m_selectedInstance->isRunning()) { | ||||
| @@ -1700,6 +1636,7 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co | ||||
|     } | ||||
|     if (m_selectedInstance) { | ||||
|         disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); | ||||
|         disconnect(m_selectedInstance.get(), &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance); | ||||
|     } | ||||
|     QString id = current.data(InstanceList::InstanceIDRole).toString(); | ||||
|     m_selectedInstance = APPLICATION->instances()->getInstanceById(id); | ||||
| @@ -1707,14 +1644,6 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co | ||||
|         ui->instanceToolBar->setEnabled(true); | ||||
|         setInstanceActionsEnabled(true); | ||||
|         ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); | ||||
|         ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch()); | ||||
|         ui->actionLaunchInstanceDemo->setEnabled(m_selectedInstance->canLaunch()); | ||||
|  | ||||
|         // Disable demo-mode if not available. | ||||
|         auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get()); | ||||
|         if (instance) { | ||||
|             ui->actionLaunchInstanceDemo->setEnabled(instance->supportsDemo()); | ||||
|         } | ||||
|  | ||||
|         ui->actionKillInstance->setEnabled(m_selectedInstance->isRunning()); | ||||
|         ui->actionExportInstance->setEnabled(m_selectedInstance->canExport()); | ||||
| @@ -1723,18 +1652,13 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co | ||||
|         updateStatusCenter(); | ||||
|         updateInstanceToolIcon(m_selectedInstance->iconKey()); | ||||
|  | ||||
|         updateToolsMenu(); | ||||
|         updateLaunchButton(); | ||||
|  | ||||
|         APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id()); | ||||
|  | ||||
|         connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); | ||||
|         connect(m_selectedInstance.get(), &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance); | ||||
|     } else { | ||||
|         ui->instanceToolBar->setEnabled(false); | ||||
|         setInstanceActionsEnabled(false); | ||||
|         ui->actionLaunchInstance->setEnabled(false); | ||||
|         ui->actionLaunchInstanceOffline->setEnabled(false); | ||||
|         ui->actionLaunchInstanceDemo->setEnabled(false); | ||||
|         ui->actionKillInstance->setEnabled(false); | ||||
|         APPLICATION->settings()->set("SelectedInstance", QString()); | ||||
|         selectionBad(); | ||||
|         return; | ||||
| @@ -1759,11 +1683,12 @@ void MainWindow::selectionBad() | ||||
| { | ||||
|     // start by reseting everything... | ||||
|     m_selectedInstance = nullptr; | ||||
|     m_statusLeft->setText(tr("No instance selected")); | ||||
|  | ||||
|     statusBar()->clearMessage(); | ||||
|     ui->instanceToolBar->setEnabled(false); | ||||
|     setInstanceActionsEnabled(false); | ||||
|     updateToolsMenu(); | ||||
|     updateLaunchButton(); | ||||
|     renameButton->setText(tr("Rename Instance")); | ||||
|     updateInstanceToolIcon("grass"); | ||||
|  | ||||
| @@ -1810,7 +1735,9 @@ void MainWindow::updateStatusCenter() | ||||
|  | ||||
|     int timePlayed = APPLICATION->instances()->getTotalPlayTime(); | ||||
|     if (timePlayed > 0) { | ||||
|         m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed))); | ||||
|         m_statusCenter->setText( | ||||
|             tr("Total playtime: %1") | ||||
|                 .arg(Time::prettifyDuration(timePlayed, APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool()))); | ||||
|     } | ||||
| } | ||||
| // "Instance actions" are actions that require an instance to be selected (i.e. "new instance" is not here) | ||||
| @@ -1826,7 +1753,7 @@ void MainWindow::setInstanceActionsEnabled(bool enabled) | ||||
|     ui->actionCreateInstanceShortcut->setEnabled(enabled); | ||||
| } | ||||
|  | ||||
| void MainWindow::refreshCurrentInstance([[maybe_unused]] bool running) | ||||
| void MainWindow::refreshCurrentInstance() | ||||
| { | ||||
|     auto current = view->selectionModel()->currentIndex(); | ||||
|     instanceChanged(current, current); | ||||
|   | ||||
| @@ -144,10 +144,6 @@ class MainWindow : public QMainWindow { | ||||
|  | ||||
|     void on_actionLaunchInstance_triggered(); | ||||
|  | ||||
|     void on_actionLaunchInstanceOffline_triggered(); | ||||
|  | ||||
|     void on_actionLaunchInstanceDemo_triggered(); | ||||
|  | ||||
|     void on_actionKillInstance_triggered(); | ||||
|  | ||||
|     void on_actionDeleteInstance_triggered(); | ||||
| @@ -155,10 +151,7 @@ class MainWindow : public QMainWindow { | ||||
|     void deleteGroup(); | ||||
|     void undoTrashInstance(); | ||||
|  | ||||
|     inline void on_actionExportInstance_triggered() | ||||
|     { | ||||
|         on_actionExportInstanceZip_triggered(); | ||||
|     } | ||||
|     inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); } | ||||
|     void on_actionExportInstanceZip_triggered(); | ||||
|     void on_actionExportInstanceMrPack_triggered(); | ||||
|     void on_actionExportInstanceFlamePack_triggered(); | ||||
| @@ -181,7 +174,7 @@ class MainWindow : public QMainWindow { | ||||
|  | ||||
|     void updateMainToolBar(); | ||||
|  | ||||
|     void updateToolsMenu(); | ||||
|     void updateLaunchButton(); | ||||
|  | ||||
|     void updateThemeMenu(); | ||||
|  | ||||
| @@ -215,7 +208,7 @@ class MainWindow : public QMainWindow { | ||||
|     void keyReleaseEvent(QKeyEvent* event) override; | ||||
| #endif | ||||
|  | ||||
|     void refreshCurrentInstance(bool running); | ||||
|     void refreshCurrentInstance(); | ||||
|  | ||||
|    private: | ||||
|     void retranslateUi(); | ||||
|   | ||||
| @@ -440,22 +440,6 @@ | ||||
|     <string>Ctrl+D</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="actionLaunchInstanceOffline"> | ||||
|    <property name="text"> | ||||
|     <string>Launch &Offline</string> | ||||
|    </property> | ||||
|    <property name="toolTip"> | ||||
|     <string>Launch the selected instance in offline mode.</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="actionLaunchInstanceDemo"> | ||||
|    <property name="text"> | ||||
|     <string>Launch &Demo</string> | ||||
|    </property> | ||||
|    <property name="toolTip"> | ||||
|     <string>Launch the selected instance in demo mode.</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="actionExportInstance"> | ||||
|    <property name="icon"> | ||||
|     <iconset theme="export"> | ||||
|   | ||||
| @@ -44,7 +44,8 @@ | ||||
| BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods) | ||||
|     : QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods) | ||||
| { | ||||
|     m_hashing_task = shared_qobject_ptr<ConcurrentTask>(new ConcurrentTask(this, "MakeHashesTask", 10)); | ||||
|     m_hashing_task = shared_qobject_ptr<ConcurrentTask>( | ||||
|         new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); | ||||
|     connect(m_hashing_task.get(), &Task::finished, this, &BlockedModsDialog::hashTaskFinished); | ||||
|  | ||||
|     ui->setupUi(this); | ||||
|   | ||||
| @@ -37,15 +37,21 @@ | ||||
| ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider) | ||||
|     : QDialog(parent), instance(instance), ui(new Ui::ExportPackDialog), m_provider(provider) | ||||
| { | ||||
|     Q_ASSERT(m_provider == ModPlatform::ResourceProvider::MODRINTH || m_provider == ModPlatform::ResourceProvider::FLAME); | ||||
|  | ||||
|     ui->setupUi(this); | ||||
|     ui->name->setText(instance->name()); | ||||
|     ui->name->setPlaceholderText(instance->name()); | ||||
|     ui->name->setText(instance->settings()->get("ExportName").toString()); | ||||
|     ui->version->setText(instance->settings()->get("ExportVersion").toString()); | ||||
|     ui->optionalFiles->setChecked(instance->settings()->get("ExportOptionalFiles").toBool()); | ||||
|  | ||||
|     if (m_provider == ModPlatform::ResourceProvider::MODRINTH) { | ||||
|         ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]); | ||||
|         setWindowTitle("Export Modrinth Pack"); | ||||
|         setWindowTitle(tr("Export Modrinth Pack")); | ||||
|         ui->summary->setText(instance->settings()->get("ExportSummary").toString()); | ||||
|     } else { | ||||
|         setWindowTitle("Export CurseForge Pack"); | ||||
|         ui->version->setText(""); | ||||
|         ui->summaryLabel->setText("Author"); | ||||
|         setWindowTitle(tr("Export CurseForge Pack")); | ||||
|         ui->summaryLabel->setText(tr("&Author")); | ||||
|         ui->summary->setText(instance->settings()->get("ExportAuthor").toString()); | ||||
|     } | ||||
|  | ||||
|     // ensure a valid pack is generated | ||||
| @@ -75,20 +81,19 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla | ||||
|  | ||||
|     MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get()); | ||||
|     if (mcInstance) { | ||||
|         mcInstance->loaderModList()->update(); | ||||
|         const QDir index = mcInstance->loaderModList()->indexDir(); | ||||
|         if (index.exists()) | ||||
|             proxy->blockedPaths().insert(root.relativeFilePath(index.absolutePath())); | ||||
|             proxy->ignoreFilesWithPath().insert(root.relativeFilePath(index.absolutePath())); | ||||
|     } | ||||
|  | ||||
|     ui->treeView->setModel(proxy); | ||||
|     ui->treeView->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot()))); | ||||
|     ui->treeView->sortByColumn(0, Qt::AscendingOrder); | ||||
|     ui->files->setModel(proxy); | ||||
|     ui->files->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot()))); | ||||
|     ui->files->sortByColumn(0, Qt::AscendingOrder); | ||||
|  | ||||
|     model->setFilter(filter); | ||||
|     model->setRootPath(instance->gameRoot()); | ||||
|  | ||||
|     QHeaderView* headerView = ui->treeView->header(); | ||||
|     QHeaderView* headerView = ui->files->header(); | ||||
|     headerView->setSectionResizeMode(QHeaderView::ResizeToContents); | ||||
|     headerView->setSectionResizeMode(0, QHeaderView::Stretch); | ||||
| } | ||||
| @@ -100,26 +105,41 @@ ExportPackDialog::~ExportPackDialog() | ||||
|  | ||||
| void ExportPackDialog::done(int result) | ||||
| { | ||||
|     if (result == Accepted) { | ||||
|         const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text()); | ||||
|         QString output; | ||||
|         if (m_provider == ModPlatform::ResourceProvider::MODRINTH) | ||||
|             output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), | ||||
|                                                   FS::PathCombine(QDir::homePath(), filename + ".mrpack"), "Modrinth pack (*.mrpack *.zip)", | ||||
|                                                   nullptr); | ||||
|         else | ||||
|             output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), | ||||
|                                                   FS::PathCombine(QDir::homePath(), filename + ".zip"), "CurseForge pack (*.zip)", nullptr); | ||||
|     auto settings = instance->settings(); | ||||
|     settings->set("ExportName", ui->name->text()); | ||||
|     settings->set("ExportVersion", ui->version->text()); | ||||
|     settings->set(m_provider == ModPlatform::ResourceProvider::FLAME ? "ExportAuthor" : "ExportSummary", ui->summary->text()); | ||||
|     settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked()); | ||||
|  | ||||
|     if (result == Accepted) { | ||||
|         const QString name = ui->name->text().isEmpty() ? instance->name() : ui->name->text(); | ||||
|         const QString filename = FS::RemoveInvalidFilenameChars(name); | ||||
|  | ||||
|         QString output; | ||||
|         if (m_provider == ModPlatform::ResourceProvider::MODRINTH) { | ||||
|             output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".mrpack"), | ||||
|                                                   "Modrinth pack (*.mrpack *.zip)", nullptr); | ||||
|             if (output.isEmpty()) | ||||
|                 return; | ||||
|             if (!(output.endsWith(".zip") || output.endsWith(".mrpack"))) | ||||
|                 output.append(".mrpack"); | ||||
|         } else { | ||||
|             output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".zip"), | ||||
|                                                   "CurseForge pack (*.zip)", nullptr); | ||||
|             if (output.isEmpty()) | ||||
|                 return; | ||||
|             if (!output.endsWith(".zip")) | ||||
|                 output.append(".zip"); | ||||
|         } | ||||
|  | ||||
|         Task* task; | ||||
|         if (m_provider == ModPlatform::ResourceProvider::MODRINTH) | ||||
|             task = new ModrinthPackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, | ||||
|                                               std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); | ||||
|         else | ||||
|             task = new FlamePackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, | ||||
|         if (m_provider == ModPlatform::ResourceProvider::MODRINTH) { | ||||
|             task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance, | ||||
|                                               output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); | ||||
|         } else { | ||||
|             task = new FlamePackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance, output, | ||||
|                                            std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); | ||||
|         } | ||||
|  | ||||
|         connect(task, &Task::failed, | ||||
|                 [this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); | ||||
| @@ -140,7 +160,6 @@ void ExportPackDialog::done(int result) | ||||
|  | ||||
| void ExportPackDialog::validate() | ||||
| { | ||||
|     const bool invalid = | ||||
|         ui->name->text().isEmpty() || ((m_provider == ModPlatform::ResourceProvider::MODRINTH) && ui->version->text().isEmpty()); | ||||
|     ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(invalid); | ||||
|     ui->buttonBox->button(QDialogButtonBox::Ok) | ||||
|         ->setDisabled(m_provider == ModPlatform::ResourceProvider::MODRINTH && ui->version->text().isEmpty()); | ||||
| } | ||||
|   | ||||
| @@ -7,12 +7,9 @@ | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>650</width> | ||||
|     <height>413</height> | ||||
|     <height>510</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Export Pack</string> | ||||
|   </property> | ||||
|   <property name="sizeGripEnabled"> | ||||
|    <bool>true</bool> | ||||
|   </property> | ||||
| @@ -20,13 +17,16 @@ | ||||
|    <item> | ||||
|     <widget class="QGroupBox" name="information"> | ||||
|      <property name="title"> | ||||
|       <string>Information</string> | ||||
|       <string>&Description</string> | ||||
|      </property> | ||||
|      <layout class="QGridLayout" name="gridLayout"> | ||||
|       <item row="3" column="0"> | ||||
|        <widget class="QLabel" name="summaryLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Summary</string> | ||||
|          <string>&Summary</string> | ||||
|         </property> | ||||
|         <property name="buddy"> | ||||
|          <cstring>summary</cstring> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
| @@ -36,14 +36,20 @@ | ||||
|       <item row="0" column="0"> | ||||
|        <widget class="QLabel" name="nameLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Name</string> | ||||
|          <string>&Name</string> | ||||
|         </property> | ||||
|         <property name="buddy"> | ||||
|          <cstring>name</cstring> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="0"> | ||||
|        <widget class="QLabel" name="versionLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Version</string> | ||||
|          <string>&Version</string> | ||||
|         </property> | ||||
|         <property name="buddy"> | ||||
|          <cstring>version</cstring> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
| @@ -57,19 +63,27 @@ | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QGroupBox" name="options"> | ||||
|      <property name="title"> | ||||
|       <string>&Options</string> | ||||
|      </property> | ||||
|      <layout class="QVBoxLayout" name="verticalLayout"> | ||||
|       <item> | ||||
|        <widget class="QLabel" name="filesLabel"> | ||||
|         <property name="text"> | ||||
|       <string>Files</string> | ||||
|          <string>&Files</string> | ||||
|         </property> | ||||
|         <property name="buddy"> | ||||
|          <cstring>files</cstring> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|     <widget class="QTreeView" name="treeView"> | ||||
|        <widget class="QTreeView" name="files"> | ||||
|         <property name="alternatingRowColors"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
| @@ -84,6 +98,19 @@ | ||||
|         </attribute> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QCheckBox" name="optionalFiles"> | ||||
|         <property name="text"> | ||||
|          <string>&Mark disabled files as optional</string> | ||||
|         </property> | ||||
|         <property name="checked"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QDialogButtonBox" name="buttonBox"> | ||||
|      <property name="standardButtons"> | ||||
| @@ -97,7 +124,8 @@ | ||||
|   <tabstop>name</tabstop> | ||||
|   <tabstop>version</tabstop> | ||||
|   <tabstop>summary</tabstop> | ||||
|   <tabstop>treeView</tabstop> | ||||
|   <tabstop>files</tabstop> | ||||
|   <tabstop>optionalFiles</tabstop> | ||||
|  </tabstops> | ||||
|  <resources/> | ||||
|  <connections> | ||||
|   | ||||
| @@ -129,7 +129,9 @@ InstallLoaderDialog::InstallLoaderDialog(std::shared_ptr<PackProfile> profile, c | ||||
|  | ||||
| QList<BasePage*> InstallLoaderDialog::getPages() | ||||
| { | ||||
|     return { // Forge | ||||
|     return { // NeoForge | ||||
|              new InstallLoaderPage("net.neoforged", "neoforged", tr("NeoForge"), {}, profile), | ||||
|              // Forge | ||||
|              new InstallLoaderPage("net.minecraftforge", "forge", tr("Forge"), {}, profile), | ||||
|              // Fabric | ||||
|              new InstallLoaderPage("net.fabricmc.fabric-loader", "fabricmc", tr("Fabric"), Version("1.14"), profile), | ||||
|   | ||||
| @@ -3,10 +3,11 @@ | ||||
| #include "CustomMessageBox.h" | ||||
| #include "ProgressDialog.h" | ||||
| #include "ScrollMessageBox.h" | ||||
| #include "minecraft/mod/tasks/GetModDependenciesTask.h" | ||||
| #include "modplatform/ModIndex.h" | ||||
| #include "modplatform/flame/FlameAPI.h" | ||||
| #include "ui_ReviewMessageBox.h" | ||||
|  | ||||
| #include "FileSystem.h" | ||||
| #include "Json.h" | ||||
| #include "Markdown.h" | ||||
|  | ||||
| #include "tasks/ConcurrentTask.h" | ||||
| @@ -30,9 +31,9 @@ static std::list<Version> mcVersions(BaseInstance* inst) | ||||
|     return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; | ||||
| } | ||||
|  | ||||
| static std::optional<ResourceAPI::ModLoaderTypes> mcLoaders(BaseInstance* inst) | ||||
| static std::optional<ModPlatform::ModLoaderTypes> mcLoaders(BaseInstance* inst) | ||||
| { | ||||
|     return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders() }; | ||||
|     return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders() }; | ||||
| } | ||||
|  | ||||
| ModUpdateDialog::ModUpdateDialog(QWidget* parent, | ||||
| @@ -43,7 +44,8 @@ ModUpdateDialog::ModUpdateDialog(QWidget* parent, | ||||
|     , m_parent(parent) | ||||
|     , m_mod_model(mods) | ||||
|     , m_candidates(search_for) | ||||
|     , m_second_try_metadata(new ConcurrentTask()) | ||||
|     , m_second_try_metadata( | ||||
|           new ConcurrentTask(nullptr, "Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())) | ||||
|     , m_instance(instance) | ||||
| { | ||||
|     ReviewMessageBox::setGeometry(0, 0, 800, 600); | ||||
| @@ -126,6 +128,8 @@ void ModUpdateDialog::checkCandidates() | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> selectedVers; | ||||
|  | ||||
|     // Add found updates for Modrinth | ||||
|     if (m_modrinth_check_task) { | ||||
|         auto modrinth_updates = m_modrinth_check_task->getUpdatable(); | ||||
| @@ -135,6 +139,7 @@ void ModUpdateDialog::checkCandidates() | ||||
|             appendMod(updatable); | ||||
|             m_tasks.insert(updatable.name, updatable.download); | ||||
|         } | ||||
|         selectedVers.append(m_modrinth_check_task->getDependencies()); | ||||
|     } | ||||
|  | ||||
|     // Add found updated for Flame | ||||
| @@ -146,6 +151,7 @@ void ModUpdateDialog::checkCandidates() | ||||
|             appendMod(updatable); | ||||
|             m_tasks.insert(updatable.name, updatable.download); | ||||
|         } | ||||
|         selectedVers.append(m_flame_check_task->getDependencies()); | ||||
|     } | ||||
|  | ||||
|     // Report failed update checking | ||||
| @@ -180,6 +186,47 @@ void ModUpdateDialog::checkCandidates() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     {  // dependencies | ||||
|         auto depTask = makeShared<GetModDependenciesTask>(this, m_instance, m_mod_model.get(), selectedVers); | ||||
|  | ||||
|         connect(depTask.get(), &Task::failed, this, | ||||
|                 [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); | ||||
|  | ||||
|         connect(depTask.get(), &Task::succeeded, this, [&]() { | ||||
|             QStringList warnings = depTask->warnings(); | ||||
|             if (warnings.count()) { | ||||
|                 CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec(); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         ProgressDialog progress_dialog_deps(m_parent); | ||||
|         progress_dialog_deps.setSkipButton(true, tr("Abort")); | ||||
|         progress_dialog_deps.setWindowTitle(tr("Checking for dependencies...")); | ||||
|         auto dret = progress_dialog_deps.execWithTask(depTask.get()); | ||||
|  | ||||
|         // If the dialog was skipped / some download error happened | ||||
|         if (dret == QDialog::DialogCode::Rejected) { | ||||
|             m_aborted = true; | ||||
|             QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); | ||||
|             return; | ||||
|         } | ||||
|         static FlameAPI api; | ||||
|  | ||||
|         auto getRequiredBy = depTask->getRequiredBy(); | ||||
|  | ||||
|         for (auto dep : depTask->getDependecies()) { | ||||
|             auto changelog = dep->version.changelog; | ||||
|             if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) | ||||
|                 changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); | ||||
|             auto download_task = makeShared<ResourceDownloadTask>(dep->pack, dep->version, m_mod_model); | ||||
|             CheckUpdateTask::UpdatableMod updatable = { dep->pack->name, dep->version.hash,   "",           dep->version.version, | ||||
|                                                         changelog,       dep->pack->provider, download_task }; | ||||
|  | ||||
|             appendMod(updatable, getRequiredBy.value(dep->version.addonId.toString())); | ||||
|             m_tasks.insert(updatable.name, updatable.download); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // If there's no mod to be updated | ||||
|     if (ui->modTreeWidget->topLevelItemCount() == 0) { | ||||
|         m_no_updates = true; | ||||
| @@ -238,6 +285,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool | ||||
|         if (skip_rest) | ||||
|             continue; | ||||
|  | ||||
|         if (candidate->type() == ResourceType::FOLDER) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         if (confirm_rest) { | ||||
|             addToTmp(candidate, provider_rest); | ||||
|             should_try_others.insert(candidate->internal_id(), try_others_rest); | ||||
| @@ -348,7 +399,7 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::R | ||||
|     } | ||||
| } | ||||
|  | ||||
| void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info) | ||||
| void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStringList requiredBy) | ||||
| { | ||||
|     auto item_top = new QTreeWidgetItem(ui->modTreeWidget); | ||||
|     item_top->setCheckState(0, Qt::CheckState::Checked); | ||||
| @@ -364,6 +415,21 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info) | ||||
|     auto new_version_item = new QTreeWidgetItem(item_top); | ||||
|     new_version_item->setText(0, tr("New version: %1").arg(info.new_version)); | ||||
|  | ||||
|     if (!requiredBy.isEmpty()) { | ||||
|         auto requiredByItem = new QTreeWidgetItem(item_top); | ||||
|         if (requiredBy.length() == 1) { | ||||
|             requiredByItem->setText(0, tr("Required by: %1").arg(requiredBy.back())); | ||||
|         } else { | ||||
|             requiredByItem->setText(0, tr("Required by:")); | ||||
|             auto i = 0; | ||||
|             for (auto req : requiredBy) { | ||||
|                 auto reqItem = new QTreeWidgetItem(requiredByItem); | ||||
|                 reqItem->setText(0, req); | ||||
|                 reqItem->insertChildren(i++, { reqItem }); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     auto changelog_item = new QTreeWidgetItem(item_top); | ||||
|     changelog_item->setText(0, tr("Changelog of the latest version")); | ||||
|  | ||||
|   | ||||
| @@ -23,7 +23,7 @@ class ModUpdateDialog final : public ReviewMessageBox { | ||||
|  | ||||
|     void checkCandidates(); | ||||
|  | ||||
|     void appendMod(const CheckUpdateTask::UpdatableMod& info); | ||||
|     void appendMod(const CheckUpdateTask::UpdatableMod& info, QStringList requiredBy = {}); | ||||
|  | ||||
|     const QList<ResourceDownloadTask::Ptr> getTasks(); | ||||
|     auto indexDir() const -> QDir { return m_mod_model->indexDir(); } | ||||
|   | ||||
| @@ -127,35 +127,12 @@ void ResourceDownloadDialog::connectButtons() | ||||
|  | ||||
| static ModPlatform::ProviderCapabilities ProviderCaps; | ||||
|  | ||||
| QStringList getRequiredBy(QList<ResourceDownloadDialog::DownloadTaskPtr> tasks, ResourceDownloadDialog::DownloadTaskPtr pack) | ||||
| { | ||||
|     auto addonId = pack->getPack()->addonId; | ||||
|     auto provider = pack->getPack()->provider; | ||||
|     auto version = pack->getVersionID(); | ||||
|     auto req = QStringList(); | ||||
|     for (auto& task : tasks) { | ||||
|         if (provider != task->getPack()->provider) | ||||
|             continue; | ||||
|         auto deps = task->getVersion().dependencies; | ||||
|         if (auto dep = std::find_if(deps.begin(), deps.end(), | ||||
|                                     [addonId, provider, version](const ModPlatform::Dependency& d) { | ||||
|                                         return d.type == ModPlatform::DependencyType::REQUIRED && | ||||
|                                                (provider == ModPlatform::ResourceProvider::MODRINTH && d.addonId.toString().isEmpty() | ||||
|                                                     ? version == d.version | ||||
|                                                     : d.addonId == addonId); | ||||
|                                     }); | ||||
|             dep != deps.end()) { | ||||
|             req.append(task->getName()); | ||||
|         } | ||||
|     } | ||||
|     return req; | ||||
| } | ||||
|  | ||||
| void ResourceDownloadDialog::confirm() | ||||
| { | ||||
|     auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); | ||||
|     confirm_dialog->retranslateUi(resourcesString()); | ||||
|  | ||||
|     QHash<QString, QStringList> getRequiredBy; | ||||
|     if (auto task = getModDependenciesTask(); task) { | ||||
|         connect(task.get(), &Task::failed, this, | ||||
|                 [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); | ||||
| @@ -180,6 +157,7 @@ void ResourceDownloadDialog::confirm() | ||||
|         } else { | ||||
|             for (auto dep : task->getDependecies()) | ||||
|                 addResource(dep->pack, dep->version); | ||||
|             getRequiredBy = task->getRequiredBy(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -189,7 +167,8 @@ void ResourceDownloadDialog::confirm() | ||||
|     }); | ||||
|     for (auto& task : selected) { | ||||
|         confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(), | ||||
|                                          ProviderCaps.name(task->getProvider()), getRequiredBy(selected, task) }); | ||||
|                                          ProviderCaps.name(task->getProvider()), | ||||
|                                          getRequiredBy.value(task->getPack()->addonId.toString()) }); | ||||
|     } | ||||
|  | ||||
|     if (confirm_dialog->exec()) { | ||||
| @@ -279,7 +258,7 @@ QList<BasePage*> ModDownloadDialog::getPages() | ||||
| { | ||||
|     QList<BasePage*> pages; | ||||
|  | ||||
|     auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getModLoaders().value(); | ||||
|     auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getSupportedModLoaders().value(); | ||||
|  | ||||
|     if (ModrinthAPI::validateModLoaders(loaders)) | ||||
|         pages.append(ModrinthModPage::create(this, *m_instance)); | ||||
| @@ -370,6 +349,8 @@ QList<BasePage*> ShaderPackDownloadDialog::getPages() | ||||
| { | ||||
|     QList<BasePage*> pages; | ||||
|     pages.append(ModrinthShaderPackPage::create(this, *m_instance)); | ||||
|     if (APPLICATION->capabilities() & Application::SupportsFlame) | ||||
|         pages.append(FlameShaderPackPage::create(this, *m_instance)); | ||||
|     return pages; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -35,12 +35,6 @@ | ||||
|           <verstretch>0</verstretch> | ||||
|          </sizepolicy> | ||||
|         </property> | ||||
|         <property name="maximumSize"> | ||||
|          <size> | ||||
|           <width>28</width> | ||||
|           <height>16777215</height> | ||||
|          </size> | ||||
|         </property> | ||||
|         <property name="text"> | ||||
|          <string>Browse</string> | ||||
|         </property> | ||||
|   | ||||
| @@ -64,7 +64,8 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new | ||||
|     ui->setupUi(this); | ||||
|     ui->listView->setEmptyString( | ||||
|         tr("Welcome!\n" | ||||
|            "If you're new here, you can click the \"Add\" button to add your Mojang or Minecraft account.")); | ||||
|            "If you're new here, you can select the \"Add Microsoft\" or \"Add Mojang\" buttons to link your Microsoft and/or Mojang " | ||||
|            "accounts.")); | ||||
|     ui->listView->setEmptyMode(VersionListView::String); | ||||
|     ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); | ||||
|  | ||||
| @@ -85,6 +86,8 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new | ||||
|     connect(selectionModel, &QItemSelectionModel::selectionChanged, | ||||
|             [this]([[maybe_unused]] const QItemSelection& sel, [[maybe_unused]] const QItemSelection& dsel) { updateButtonStates(); }); | ||||
|     connect(ui->listView, &VersionListView::customContextMenuRequested, this, &AccountListPage::ShowContextMenu); | ||||
|     connect(ui->listView, &VersionListView::activated, this, | ||||
|             [this](const QModelIndex& index) { m_accounts->setDefaultAccount(m_accounts->at(index.row())); }); | ||||
|  | ||||
|     connect(m_accounts.get(), &AccountList::listChanged, this, &AccountListPage::listChanged); | ||||
|     connect(m_accounts.get(), &AccountList::listActivityChanged, this, &AccountListPage::listChanged); | ||||
|   | ||||
| @@ -185,6 +185,7 @@ void JavaPage::updateThresholds() | ||||
| { | ||||
|     auto sysMiB = Sys::getSystemRam() / Sys::mebibyte; | ||||
|     unsigned int maxMem = ui->maxMemSpinBox->value(); | ||||
|     unsigned int minMem = ui->minMemSpinBox->value(); | ||||
|  | ||||
|     QString iconName; | ||||
|  | ||||
| @@ -194,6 +195,9 @@ void JavaPage::updateThresholds() | ||||
|     } else if (maxMem > (sysMiB * 0.9)) { | ||||
|         iconName = "status-yellow"; | ||||
|         ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity.")); | ||||
|     } else if (maxMem < minMem) { | ||||
|         iconName = "status-yellow"; | ||||
|         ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value")); | ||||
|     } else { | ||||
|         iconName = "status-good"; | ||||
|         ui->labelMaxMemIcon->setToolTip(""); | ||||
|   | ||||
| @@ -189,6 +189,9 @@ void LauncherPage::applySettings() | ||||
|  | ||||
|     s->set("MenuBarInsteadOfToolBar", ui->preferMenuBarCheckBox->isChecked()); | ||||
|  | ||||
|     s->set("NumberOfConcurrentTasks", ui->numberOfConcurrentTasksSpinBox->value()); | ||||
|     s->set("NumberOfConcurrentDownloads", ui->numberOfConcurrentDownloadsSpinBox->value()); | ||||
|  | ||||
|     // Console settings | ||||
|     s->set("ShowConsole", ui->showConsoleCheck->isChecked()); | ||||
|     s->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked()); | ||||
| @@ -236,6 +239,9 @@ void LauncherPage::loadSettings() | ||||
| #endif | ||||
|     ui->preferMenuBarCheckBox->setChecked(s->get("MenuBarInsteadOfToolBar").toBool()); | ||||
|  | ||||
|     ui->numberOfConcurrentTasksSpinBox->setValue(s->get("NumberOfConcurrentTasks").toInt()); | ||||
|     ui->numberOfConcurrentDownloadsSpinBox->setValue(s->get("NumberOfConcurrentDownloads").toInt()); | ||||
|  | ||||
|     // Console settings | ||||
|     ui->showConsoleCheck->setChecked(s->get("ShowConsole").toBool()); | ||||
|     ui->autoCloseConsoleCheck->setChecked(s->get("AutoCloseConsole").toBool()); | ||||
|   | ||||
| @@ -189,6 +189,43 @@ | ||||
|          </layout> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item> | ||||
|         <widget class="QGroupBox" name="miscellaneousGroupBox"> | ||||
|          <property name="title"> | ||||
|           <string>Miscellaneous</string> | ||||
|          </property> | ||||
|          <layout class="QGridLayout" name="gridLayout"> | ||||
|           <item row="0" column="0"> | ||||
|            <widget class="QLabel" name="numberOfConcurrentTasksLabel"> | ||||
|             <property name="text"> | ||||
|              <string>Number of concurrent tasks</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|           <item row="0" column="1"> | ||||
|            <widget class="QSpinBox" name="numberOfConcurrentTasksSpinBox"> | ||||
|             <property name="minimum"> | ||||
|              <number>1</number> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|           <item row="1" column="0"> | ||||
|            <widget class="QLabel" name="numberOfConcurrentDownloadsLabel"> | ||||
|             <property name="text"> | ||||
|              <string>Number of concurrent downloads</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|           <item row="1" column="1"> | ||||
|            <widget class="QSpinBox" name="numberOfConcurrentDownloadsSpinBox"> | ||||
|             <property name="minimum"> | ||||
|              <number>1</number> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|          </layout> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item> | ||||
|         <spacer name="verticalSpacer_2"> | ||||
|          <property name="orientation"> | ||||
|   | ||||
| @@ -2,7 +2,6 @@ | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> | ||||
|  *  Copyright (C) 2023 seth <getchoo at tuta dot io> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -35,6 +34,7 @@ | ||||
|  */ | ||||
|  | ||||
| #include "MinecraftPage.h" | ||||
| #include "BuildConfig.h" | ||||
| #include "ui_MinecraftPage.h" | ||||
|  | ||||
| #include <QDir> | ||||
| @@ -44,9 +44,15 @@ | ||||
| #include "Application.h" | ||||
| #include "settings/SettingsObject.h" | ||||
|  | ||||
| #ifdef Q_OS_LINUX | ||||
| #include "MangoHud.h" | ||||
| #endif | ||||
|  | ||||
| MinecraftPage::MinecraftPage(QWidget* parent) : QWidget(parent), ui(new Ui::MinecraftPage) | ||||
| { | ||||
|     ui->setupUi(this); | ||||
|     connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &MinecraftPage::onUseNativeGLFWChanged); | ||||
|     connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &MinecraftPage::onUseNativeOpenALChanged); | ||||
|     loadSettings(); | ||||
|     updateCheckboxStuff(); | ||||
| } | ||||
| @@ -74,6 +80,16 @@ void MinecraftPage::on_maximizedCheckBox_clicked(bool checked) | ||||
|     updateCheckboxStuff(); | ||||
| } | ||||
|  | ||||
| void MinecraftPage::onUseNativeGLFWChanged(bool checked) | ||||
| { | ||||
|     ui->lineEditGLFWPath->setEnabled(checked); | ||||
| } | ||||
|  | ||||
| void MinecraftPage::onUseNativeOpenALChanged(bool checked) | ||||
| { | ||||
|     ui->lineEditOpenALPath->setEnabled(checked); | ||||
| } | ||||
|  | ||||
| void MinecraftPage::applySettings() | ||||
| { | ||||
|     auto s = APPLICATION->settings(); | ||||
| @@ -84,8 +100,10 @@ void MinecraftPage::applySettings() | ||||
|     s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); | ||||
|  | ||||
|     // Native library workarounds | ||||
|     s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked()); | ||||
|     s->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked()); | ||||
|     s->set("CustomGLFWPath", ui->lineEditGLFWPath->text()); | ||||
|     s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked()); | ||||
|     s->set("CustomOpenALPath", ui->lineEditOpenALPath->text()); | ||||
|  | ||||
|     // Peformance related options | ||||
|     s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked()); | ||||
| @@ -96,13 +114,11 @@ void MinecraftPage::applySettings() | ||||
|     s->set("ShowGameTime", ui->showGameTime->isChecked()); | ||||
|     s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked()); | ||||
|     s->set("RecordGameTime", ui->recordGameTime->isChecked()); | ||||
|     s->set("ShowGameTimeWithoutDays", ui->showGameTimeWithoutDays->isChecked()); | ||||
|  | ||||
|     // Miscellaneous | ||||
|     s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked()); | ||||
|     s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked()); | ||||
|  | ||||
|     // Mod loader settings | ||||
|     s->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked()); | ||||
| } | ||||
|  | ||||
| void MinecraftPage::loadSettings() | ||||
| @@ -114,8 +130,20 @@ void MinecraftPage::loadSettings() | ||||
|     ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt()); | ||||
|     ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt()); | ||||
|  | ||||
|     ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool()); | ||||
|     ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").toBool()); | ||||
|     ui->lineEditGLFWPath->setText(s->get("CustomGLFWPath").toString()); | ||||
|     ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.GLFW_LIBRARY_NAME)); | ||||
| #ifdef Q_OS_LINUX | ||||
|     if (!APPLICATION->m_detectedGLFWPath.isEmpty()) | ||||
|         ui->lineEditGLFWPath->setPlaceholderText(tr("Auto detected path: %1").arg(APPLICATION->m_detectedGLFWPath)); | ||||
| #endif | ||||
|     ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool()); | ||||
|     ui->lineEditOpenALPath->setText(s->get("CustomOpenALPath").toString()); | ||||
|     ui->lineEditOpenALPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME)); | ||||
| #ifdef Q_OS_LINUX | ||||
|     if (!APPLICATION->m_detectedOpenALPath.isEmpty()) | ||||
|         ui->lineEditOpenALPath->setPlaceholderText(tr("Auto detected path: %1").arg(APPLICATION->m_detectedOpenALPath)); | ||||
| #endif | ||||
|  | ||||
|     ui->enableFeralGamemodeCheck->setChecked(s->get("EnableFeralGamemode").toBool()); | ||||
|     ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool()); | ||||
| @@ -138,11 +166,10 @@ void MinecraftPage::loadSettings() | ||||
|     ui->showGameTime->setChecked(s->get("ShowGameTime").toBool()); | ||||
|     ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool()); | ||||
|     ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool()); | ||||
|     ui->showGameTimeWithoutDays->setChecked(s->get("ShowGameTimeWithoutDays").toBool()); | ||||
|  | ||||
|     ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool()); | ||||
|     ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool()); | ||||
|  | ||||
|     ui->disableQuiltBeaconCheckBox->setChecked(s->get("DisableQuiltBeacon").toBool()); | ||||
| } | ||||
|  | ||||
| void MinecraftPage::retranslate() | ||||
|   | ||||
| @@ -70,6 +70,9 @@ class MinecraftPage : public QWidget, public BasePage { | ||||
|    private slots: | ||||
|     void on_maximizedCheckBox_clicked(bool checked); | ||||
|  | ||||
|     void onUseNativeGLFWChanged(bool checked); | ||||
|     void onUseNativeOpenALChanged(bool checked); | ||||
|  | ||||
|    private: | ||||
|     Ui::MinecraftPage* ui; | ||||
| }; | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 Trial97
					Trial97