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)) |     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 |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v3 |       - uses: actions/checkout@v4 | ||||||
|         with: |         with: | ||||||
|           ref: ${{ github.event.pull_request.head.sha }} |           ref: ${{ github.event.pull_request.head.sha }} | ||||||
|       - name: Create backport PRs |       - name: Create backport PRs | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -125,7 +125,7 @@ jobs: | |||||||
|       # PREPARE |       # PREPARE | ||||||
|       ## |       ## | ||||||
|       - name: Checkout |       - name: Checkout | ||||||
|         uses: actions/checkout@v3 |         uses: actions/checkout@v4 | ||||||
|         with: |         with: | ||||||
|           submodules: 'true' |           submodules: 'true' | ||||||
|  |  | ||||||
| @@ -164,7 +164,7 @@ jobs: | |||||||
|  |  | ||||||
|       - name: Retrieve ccache cache (Windows MinGW-w64) |       - name: Retrieve ccache cache (Windows MinGW-w64) | ||||||
|         if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' |         if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' | ||||||
|         uses: actions/cache@v3.3.1 |         uses: actions/cache@v3.3.2 | ||||||
|         with: |         with: | ||||||
|           path: '${{ github.workspace }}\.ccache' |           path: '${{ github.workspace }}\.ccache' | ||||||
|           key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} |           key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} | ||||||
| @@ -620,7 +620,7 @@ jobs: | |||||||
|       options: --privileged |       options: --privileged | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout |       - name: Checkout | ||||||
|         uses: actions/checkout@v3 |         uses: actions/checkout@v4 | ||||||
|         if: inputs.build_type == 'Debug' |         if: inputs.build_type == 'Debug' | ||||||
|         with: |         with: | ||||||
|           submodules: 'true' |           submodules: 'true' | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							| @@ -8,7 +8,7 @@ jobs: | |||||||
|      |      | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout repository |       - name: Checkout repository | ||||||
|         uses: actions/checkout@v3 |         uses: actions/checkout@v4 | ||||||
|         with: |         with: | ||||||
|           submodules: 'true' |           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 }} |       upload_url: ${{ steps.create_release.outputs.upload_url }} | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout |       - name: Checkout | ||||||
|         uses: actions/checkout@v3 |         uses: actions/checkout@v4 | ||||||
|         with: |         with: | ||||||
|           submodules: 'true' |           submodules: 'true' | ||||||
|           path: 'PrismLauncher-source' |           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 |     runs-on: ubuntu-latest | ||||||
|  |  | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v3 |       - uses: actions/checkout@v4 | ||||||
|       - uses: cachix/install-nix-action@v22 |       - uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23 | ||||||
|  |  | ||||||
|       - uses: DeterminateSystems/update-flake-lock@v19 |       - uses: DeterminateSystems/update-flake-lock@v20 | ||||||
|         with: |         with: | ||||||
|           commit-msg: "chore(nix): update lockfile" |           commit-msg: "chore(nix): update lockfile" | ||||||
|           pr-title: "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_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") | 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 | # API Keys | ||||||
| # NOTE: These API keys are here for convenience. If you rebrand this software or intend to break the terms of service | # 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. | # of these platforms, please change these API keys beforehand. | ||||||
|   | |||||||
| @@ -110,6 +110,9 @@ Config::Config() | |||||||
|     FLAME_API_KEY = "@Launcher_CURSEFORGE_API_KEY@"; |     FLAME_API_KEY = "@Launcher_CURSEFORGE_API_KEY@"; | ||||||
|     META_URL = "@Launcher_META_URL@"; |     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@"; |     BUG_TRACKER_URL = "@Launcher_BUG_TRACKER_URL@"; | ||||||
|     TRANSLATIONS_URL = "@Launcher_TRANSLATIONS_URL@"; |     TRANSLATIONS_URL = "@Launcher_TRANSLATIONS_URL@"; | ||||||
|     MATRIX_URL = "@Launcher_MATRIX_URL@"; |     MATRIX_URL = "@Launcher_MATRIX_URL@"; | ||||||
|   | |||||||
| @@ -134,6 +134,9 @@ class Config { | |||||||
|      */ |      */ | ||||||
|     QString META_URL; |     QString META_URL; | ||||||
|  |  | ||||||
|  |     QString GLFW_LIBRARY_NAME; | ||||||
|  |     QString OPENAL_LIBRARY_NAME; | ||||||
|  |  | ||||||
|     QString BUG_TRACKER_URL; |     QString BUG_TRACKER_URL; | ||||||
|     QString TRANSLATIONS_URL; |     QString TRANSLATIONS_URL; | ||||||
|     QString MATRIX_URL; |     QString MATRIX_URL; | ||||||
|   | |||||||
| @@ -75,7 +75,6 @@ function( | |||||||
|     set(CLANG_WARNINGS |     set(CLANG_WARNINGS | ||||||
|         -Wall |         -Wall | ||||||
|         -Wextra # reasonable and standard |         -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 |         -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 |         -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 |         # catch hard to track down memory errors | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										40
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							| @@ -21,11 +21,11 @@ | |||||||
|         "nixpkgs-lib": "nixpkgs-lib" |         "nixpkgs-lib": "nixpkgs-lib" | ||||||
|       }, |       }, | ||||||
|       "locked": { |       "locked": { | ||||||
|         "lastModified": 1690933134, |         "lastModified": 1693611461, | ||||||
|         "narHash": "sha256-ab989mN63fQZBFrkk4Q8bYxQCktuHmBIBqUG1jl6/FQ=", |         "narHash": "sha256-aPODl8vAgGQ0ZYFIRisxYG5MOGSkIczvu2Cd8Gb9+1Y=", | ||||||
|         "owner": "hercules-ci", |         "owner": "hercules-ci", | ||||||
|         "repo": "flake-parts", |         "repo": "flake-parts", | ||||||
|         "rev": "59cf3f1447cfc75087e7273b04b31e689a8599fb", |         "rev": "7f53fdb7bdc5bb237da7fefef12d099e4fd611ca", | ||||||
|         "type": "github" |         "type": "github" | ||||||
|       }, |       }, | ||||||
|       "original": { |       "original": { | ||||||
| @@ -89,13 +89,28 @@ | |||||||
|         "type": "github" |         "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": { |     "nixpkgs": { | ||||||
|       "locked": { |       "locked": { | ||||||
|         "lastModified": 1691853136, |         "lastModified": 1695318763, | ||||||
|         "narHash": "sha256-wTzDsRV4HN8A2Sl0SVQY0q8ILs90CD43Ha//7gNZE+E=", |         "narHash": "sha256-FHVPDRP2AfvsxAdc+AsgFJevMz5VBmnZglFUMlxBkcY=", | ||||||
|         "owner": "nixos", |         "owner": "nixos", | ||||||
|         "repo": "nixpkgs", |         "repo": "nixpkgs", | ||||||
|         "rev": "f0451844bbdf545f696f029d1448de4906c7f753", |         "rev": "e12483116b3b51a185a33a272bf351e357ba9a99", | ||||||
|         "type": "github" |         "type": "github" | ||||||
|       }, |       }, | ||||||
|       "original": { |       "original": { | ||||||
| @@ -108,11 +123,11 @@ | |||||||
|     "nixpkgs-lib": { |     "nixpkgs-lib": { | ||||||
|       "locked": { |       "locked": { | ||||||
|         "dir": "lib", |         "dir": "lib", | ||||||
|         "lastModified": 1690881714, |         "lastModified": 1693471703, | ||||||
|         "narHash": "sha256-h/nXluEqdiQHs1oSgkOOWF+j8gcJMWhwnZ9PFabN6q0=", |         "narHash": "sha256-0l03ZBL8P1P6z8MaSDS/MvuU8E75rVxe5eE1N6gxeTo=", | ||||||
|         "owner": "NixOS", |         "owner": "NixOS", | ||||||
|         "repo": "nixpkgs", |         "repo": "nixpkgs", | ||||||
|         "rev": "9e1960bc196baf6881340d53dccb203a951745a2", |         "rev": "3e52e76b70d5508f3cec70b882a29199f4d1ee85", | ||||||
|         "type": "github" |         "type": "github" | ||||||
|       }, |       }, | ||||||
|       "original": { |       "original": { | ||||||
| @@ -138,11 +153,11 @@ | |||||||
|         ] |         ] | ||||||
|       }, |       }, | ||||||
|       "locked": { |       "locked": { | ||||||
|         "lastModified": 1691747570, |         "lastModified": 1694364351, | ||||||
|         "narHash": "sha256-J3fnIwJtHVQ0tK2JMBv4oAmII+1mCdXdpeCxtIsrL2A=", |         "narHash": "sha256-oadhSCqopYXxURwIA6/Anpe5IAG11q2LhvTJNP5zE6o=", | ||||||
|         "owner": "cachix", |         "owner": "cachix", | ||||||
|         "repo": "pre-commit-hooks.nix", |         "repo": "pre-commit-hooks.nix", | ||||||
|         "rev": "c5ac3aa3324bd8aebe8622a3fc92eeb3975d317a", |         "rev": "4f883a76282bc28eb952570afc3d8a1bf6f481d7", | ||||||
|         "type": "github" |         "type": "github" | ||||||
|       }, |       }, | ||||||
|       "original": { |       "original": { | ||||||
| @@ -156,6 +171,7 @@ | |||||||
|         "flake-compat": "flake-compat", |         "flake-compat": "flake-compat", | ||||||
|         "flake-parts": "flake-parts", |         "flake-parts": "flake-parts", | ||||||
|         "libnbtplusplus": "libnbtplusplus", |         "libnbtplusplus": "libnbtplusplus", | ||||||
|  |         "nix-filter": "nix-filter", | ||||||
|         "nixpkgs": "nixpkgs", |         "nixpkgs": "nixpkgs", | ||||||
|         "pre-commit-hooks": "pre-commit-hooks" |         "pre-commit-hooks": "pre-commit-hooks" | ||||||
|       } |       } | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								flake.nix
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								flake.nix
									
									
									
									
									
								
							| @@ -4,6 +4,7 @@ | |||||||
|   inputs = { |   inputs = { | ||||||
|     nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; |     nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; | ||||||
|     flake-parts.url = "github:hercules-ci/flake-parts"; |     flake-parts.url = "github:hercules-ci/flake-parts"; | ||||||
|  |     nix-filter.url = "github:numtide/nix-filter"; | ||||||
|     pre-commit-hooks = { |     pre-commit-hooks = { | ||||||
|       url = "github:cachix/pre-commit-hooks.nix"; |       url = "github:cachix/pre-commit-hooks.nix"; | ||||||
|       inputs.nixpkgs.follows = "nixpkgs"; |       inputs.nixpkgs.follows = "nixpkgs"; | ||||||
| @@ -20,8 +21,24 @@ | |||||||
|     }; |     }; | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   outputs = inputs: |   outputs = { | ||||||
|     inputs.flake-parts.lib.mkFlake |     flake-parts, | ||||||
|     {inherit inputs;} |     pre-commit-hooks, | ||||||
|     {imports = [./nix];}; |     ... | ||||||
|  |   } @ 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) 2022 Tayou <git@tayou.org> | ||||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> |  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> | ||||||
|  *  Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  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("MenuBarInsteadOfToolBar", false); | ||||||
|  |  | ||||||
|  |         m_settings->registerSetting("NumberOfConcurrentTasks", 10); | ||||||
|  |         m_settings->registerSetting("NumberOfConcurrentDownloads", 6); | ||||||
|  |  | ||||||
|         QString defaultMonospace; |         QString defaultMonospace; | ||||||
|         int defaultSize = 11; |         int defaultSize = 11; | ||||||
| #ifdef Q_OS_WIN32 | #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("IgnoreJavaCompatibility", false); | ||||||
|         m_settings->registerSetting("IgnoreJavaWizard", false); |         m_settings->registerSetting("IgnoreJavaWizard", false); | ||||||
|  |  | ||||||
|         // Mod loader settings |  | ||||||
|         m_settings->registerSetting("DisableQuiltBeacon", false); |  | ||||||
|  |  | ||||||
|         // Native library workarounds |         // Native library workarounds | ||||||
|         m_settings->registerSetting("UseNativeOpenAL", false); |         m_settings->registerSetting("UseNativeOpenAL", false); | ||||||
|  |         m_settings->registerSetting("CustomOpenALPath", ""); | ||||||
|         m_settings->registerSetting("UseNativeGLFW", false); |         m_settings->registerSetting("UseNativeGLFW", false); | ||||||
|  |         m_settings->registerSetting("CustomGLFWPath", ""); | ||||||
|  |  | ||||||
|         // Peformance related options |         // Peformance related options | ||||||
|         m_settings->registerSetting("EnableFeralGamemode", false); |         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("ShowGameTime", true); | ||||||
|         m_settings->registerSetting("ShowGlobalGameTime", true); |         m_settings->registerSetting("ShowGlobalGameTime", true); | ||||||
|         m_settings->registerSetting("RecordGameTime", true); |         m_settings->registerSetting("RecordGameTime", true); | ||||||
|  |         m_settings->registerSetting("ShowGameTimeWithoutDays", false); | ||||||
|  |  | ||||||
|         // Minecraft mods |         // Minecraft mods | ||||||
|         m_settings->registerSetting("ModMetadataDisabled", false); |         m_settings->registerSetting("ModMetadataDisabled", false); | ||||||
| @@ -845,6 +847,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) | |||||||
|  |  | ||||||
|     updateCapabilities(); |     updateCapabilities(); | ||||||
|  |  | ||||||
|  |     detectLibraries(); | ||||||
|  |  | ||||||
|     if (createSetupWizard()) { |     if (createSetupWizard()) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| @@ -964,7 +968,7 @@ void Application::performMainStartupAction() | |||||||
|                 qDebug() << "   Launching with account" << m_profileToUse; |                 qDebug() << "   Launching with account" << m_profileToUse; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             launch(inst, true, false, nullptr, serverToJoin, accountToUse); |             launch(inst, true, false, serverToJoin, accountToUse); | ||||||
|             return; |             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 { |     } else { | ||||||
|         qWarning() << "Received invalid message" << message; |         qWarning() << "Received invalid message" << message; | ||||||
|     } |     } | ||||||
| @@ -1104,7 +1108,6 @@ bool Application::openJsonEditor(const QString& filename) | |||||||
| bool Application::launch(InstancePtr instance, | bool Application::launch(InstancePtr instance, | ||||||
|                          bool online, |                          bool online, | ||||||
|                          bool demo, |                          bool demo, | ||||||
|                          BaseProfilerFactory* profiler, |  | ||||||
|                          MinecraftServerTargetPtr serverToJoin, |                          MinecraftServerTargetPtr serverToJoin, | ||||||
|                          MinecraftAccountPtr accountToUse) |                          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."; |         qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed."; | ||||||
|     } else if (instance->canLaunch()) { |     } else if (instance->canLaunch()) { | ||||||
|         auto& extras = m_instanceExtras[instance->id()]; |         auto& extras = m_instanceExtras[instance->id()]; | ||||||
|         auto& window = extras.window; |         auto window = extras.window; | ||||||
|         if (window) { |         if (window) { | ||||||
|             if (!window->saveAll()) { |             if (!window->saveAll()) { | ||||||
|                 return false; |                 return false; | ||||||
| @@ -1123,7 +1126,7 @@ bool Application::launch(InstancePtr instance, | |||||||
|         controller->setInstance(instance); |         controller->setInstance(instance); | ||||||
|         controller->setOnline(online); |         controller->setOnline(online); | ||||||
|         controller->setDemo(demo); |         controller->setDemo(demo); | ||||||
|         controller->setProfiler(profiler); |         controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get()); | ||||||
|         controller->setServerToJoin(serverToJoin); |         controller->setServerToJoin(serverToJoin); | ||||||
|         controller->setAccountToUse(accountToUse); |         controller->setAccountToUse(accountToUse); | ||||||
|         if (window) { |         if (window) { | ||||||
| @@ -1415,6 +1418,15 @@ void Application::updateCapabilities() | |||||||
| #endif | #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) | QString Application::getJarPath(QString jarFile) | ||||||
| { | { | ||||||
|     QStringList potentialPaths = { |     QStringList potentialPaths = { | ||||||
|   | |||||||
| @@ -142,6 +142,8 @@ class Application : public QApplication { | |||||||
|  |  | ||||||
|     void updateCapabilities(); |     void updateCapabilities(); | ||||||
|  |  | ||||||
|  |     void detectLibraries(); | ||||||
|  |  | ||||||
|     /*! |     /*! | ||||||
|      * Finds and returns the full path to a jar file. |      * Finds and returns the full path to a jar file. | ||||||
|      * Returns a null-string if it could not be found. |      * Returns a null-string if it could not be found. | ||||||
| @@ -193,7 +195,6 @@ class Application : public QApplication { | |||||||
|     bool launch(InstancePtr instance, |     bool launch(InstancePtr instance, | ||||||
|                 bool online = true, |                 bool online = true, | ||||||
|                 bool demo = false, |                 bool demo = false, | ||||||
|                 BaseProfilerFactory* profiler = nullptr, |  | ||||||
|                 MinecraftServerTargetPtr serverToJoin = nullptr, |                 MinecraftServerTargetPtr serverToJoin = nullptr, | ||||||
|                 MinecraftAccountPtr accountToUse = nullptr); |                 MinecraftAccountPtr accountToUse = nullptr); | ||||||
|     bool kill(InstancePtr instance); |     bool kill(InstancePtr instance); | ||||||
| @@ -277,6 +278,8 @@ class Application : public QApplication { | |||||||
|     SetupWizard* m_setupWizard = nullptr; |     SetupWizard* m_setupWizard = nullptr; | ||||||
|  |  | ||||||
|    public: |    public: | ||||||
|  |     QString m_detectedGLFWPath; | ||||||
|  |     QString m_detectedOpenALPath; | ||||||
|     QString m_instanceIdToLaunch; |     QString m_instanceIdToLaunch; | ||||||
|     QString m_serverToJoin; |     QString m_serverToJoin; | ||||||
|     QString m_profileToUse; |     QString m_profileToUse; | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|  *  Prism Launcher - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> |  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||||
|  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  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("ManagedPackName", ""); | ||||||
|     m_settings->registerSetting("ManagedPackVersionID", ""); |     m_settings->registerSetting("ManagedPackVersionID", ""); | ||||||
|     m_settings->registerSetting("ManagedPackVersionName", ""); |     m_settings->registerSetting("ManagedPackVersionName", ""); | ||||||
|  |  | ||||||
|  |     m_settings->registerSetting("Profiler", ""); | ||||||
| } | } | ||||||
|  |  | ||||||
| QString BaseInstance::getPreLaunchCommand() | QString BaseInstance::getPreLaunchCommand() | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|  *  Prism Launcher - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> |  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||||
|  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -38,6 +39,7 @@ | |||||||
| #include <cassert> | #include <cassert> | ||||||
|  |  | ||||||
| #include <QDateTime> | #include <QDateTime> | ||||||
|  | #include <QMenu> | ||||||
| #include <QObject> | #include <QObject> | ||||||
| #include <QProcess> | #include <QProcess> | ||||||
| #include <QSet> | #include <QSet> | ||||||
| @@ -246,6 +248,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns | |||||||
|     virtual bool canEdit() const = 0; |     virtual bool canEdit() const = 0; | ||||||
|     virtual bool canExport() const = 0; |     virtual bool canExport() const = 0; | ||||||
|  |  | ||||||
|  |     virtual void populateLaunchMenu(QMenu* menu) = 0; | ||||||
|  |  | ||||||
|     bool reloadSettings(); |     bool reloadSettings(); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -282,6 +286,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns | |||||||
|  |  | ||||||
|     void runningStatusChanged(bool running); |     void runningStatusChanged(bool running); | ||||||
|  |  | ||||||
|  |     void profilerChanged(); | ||||||
|  |  | ||||||
|     void statusChanged(Status from, Status to); |     void statusChanged(Status from, Status to); | ||||||
|  |  | ||||||
|    protected slots: |    protected slots: | ||||||
|   | |||||||
| @@ -1137,6 +1137,9 @@ include(CompilerWarnings) | |||||||
|  |  | ||||||
| # Add executable | # Add executable | ||||||
| add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES}) | 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 | set_project_warnings(Launcher_logic | ||||||
|     "${Launcher_MSVC_WARNINGS}" |     "${Launcher_MSVC_WARNINGS}" | ||||||
|     "${Launcher_CLANG_WARNINGS}" |     "${Launcher_CLANG_WARNINGS}" | ||||||
|   | |||||||
| @@ -267,10 +267,7 @@ bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceP | |||||||
|  |  | ||||||
| bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const | bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const | ||||||
| { | { | ||||||
|     auto fileName = fileInfo.fileName(); |     return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath())); | ||||||
|     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); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| bool FileIgnoreProxy::filterFile(const QString& fileName) const | bool FileIgnoreProxy::filterFile(const QString& fileName) const | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -361,22 +362,21 @@ void LaunchController::readyForLaunch() | |||||||
|     QString error; |     QString error; | ||||||
|     if (!m_profiler->check(&error)) { |     if (!m_profiler->check(&error)) { | ||||||
|         m_launcher->abort(); |         m_launcher->abort(); | ||||||
|         QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't start profiler: %1").arg(error)); |  | ||||||
|         emitFailed("Profiler startup failed!"); |         emitFailed("Profiler startup failed!"); | ||||||
|  |         QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Profiler check for %1 failed: %2").arg(m_profiler->name(), error)); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     BaseProfiler* profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this); |     BaseProfiler* profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this); | ||||||
|  |  | ||||||
|     connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString& message) { |     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 " |         msg.setText(tr("The game launch is delayed until you press the " | ||||||
|                        "button. This is the right time to setup the profiler, as the " |                        "button. This is the right time to setup the profiler, as the " | ||||||
|                        "profiler server is running now.\n\n%1") |                        "profiler server is running now.\n\n%1") | ||||||
|                         .arg(message)); |                         .arg(message)); | ||||||
|         msg.setWindowTitle(tr("Waiting.")); |         msg.setWindowTitle(tr("Waiting.")); | ||||||
|         msg.setIcon(QMessageBox::Information); |         msg.setIcon(QMessageBox::Information); | ||||||
|         msg.addButton(tr("Launch"), QMessageBox::AcceptRole); |         msg.addButton(tr("&Launch"), QMessageBox::AcceptRole); | ||||||
|         msg.setModal(true); |  | ||||||
|         msg.exec(); |         msg.exec(); | ||||||
|         m_launcher->proceed(); |         m_launcher->proceed(); | ||||||
|     }); |     }); | ||||||
|   | |||||||
| @@ -16,19 +16,20 @@ | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| #include <MMCTime.h> | #include <MMCTime.h> | ||||||
|  | #include <qobject.h> | ||||||
|  |  | ||||||
| #include <QDateTime> | #include <QDateTime> | ||||||
| #include <QObject> | #include <QObject> | ||||||
| #include <QTextStream> | #include <QTextStream> | ||||||
|  |  | ||||||
| QString Time::prettifyDuration(int64_t duration) | QString Time::prettifyDuration(int64_t duration, bool noDays) | ||||||
| { | { | ||||||
|     int seconds = (int)(duration % 60); |     int seconds = (int)(duration % 60); | ||||||
|     duration /= 60; |     duration /= 60; | ||||||
|     int minutes = (int)(duration % 60); |     int minutes = (int)(duration % 60); | ||||||
|     duration /= 60; |     duration /= 60; | ||||||
|     int hours = (int)(duration % 24); |     int hours = (int)(noDays ? duration : (duration % 24)); | ||||||
|     int days = (int)(duration / 24); |     int days = (int)(noDays ? 0 : (duration / 24)); | ||||||
|     if ((hours == 0) && (days == 0)) { |     if ((hours == 0) && (days == 0)) { | ||||||
|         return QObject::tr("%1min %2s").arg(minutes).arg(seconds); |         return QObject::tr("%1min %2s").arg(minutes).arg(seconds); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ | |||||||
|  |  | ||||||
| namespace Time { | 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`. |  * @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/>. |  *  along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
|  | #include <QDebug> | ||||||
| #include <QDir> | #include <QDir> | ||||||
| #include <QString> | #include <QString> | ||||||
| #include <QStringList> | #include <QStringList> | ||||||
| @@ -26,6 +27,15 @@ | |||||||
| #include "Json.h" | #include "Json.h" | ||||||
| #include "MangoHud.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 { | namespace MangoHud { | ||||||
|  |  | ||||||
| QString getLibraryString() | QString getLibraryString() | ||||||
| @@ -106,4 +116,37 @@ QString getLibraryString() | |||||||
|  |  | ||||||
|     return QString(); |     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 | }  // namespace MangoHud | ||||||
|  |  | ||||||
|  | #ifdef UNDEF_GNU_SOURCE | ||||||
|  | #undef _GNU_SOURCE | ||||||
|  | #undef UNDEF_GNU_SOURCE | ||||||
|  | #endif | ||||||
|   | |||||||
| @@ -24,4 +24,6 @@ | |||||||
| namespace MangoHud { | namespace MangoHud { | ||||||
|  |  | ||||||
| QString getLibraryString(); | QString getLibraryString(); | ||||||
| } |  | ||||||
|  | QString findLibrary(QString libName); | ||||||
|  | }  // namespace MangoHud | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  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 canExport() const override { return false; } | ||||||
|     bool canEdit() const override { return false; } |     bool canEdit() const override { return false; } | ||||||
|     bool canLaunch() const override { return false; } |     bool canLaunch() const override { return false; } | ||||||
|  |     void populateLaunchMenu(QMenu* menu) override {} | ||||||
|     QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override |     QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override | ||||||
|     { |     { | ||||||
|         QStringList out; |         QStringList out; | ||||||
|   | |||||||
| @@ -103,14 +103,8 @@ class Version { | |||||||
|  |  | ||||||
|         QString m_fullString; |         QString m_fullString; | ||||||
|  |  | ||||||
|         [[nodiscard]] inline bool isAppendix() const |         [[nodiscard]] inline bool isAppendix() const { return m_stringPart.startsWith('+'); } | ||||||
|         { |         [[nodiscard]] inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; } | ||||||
|             return m_stringPart.startsWith('+'); |  | ||||||
|         } |  | ||||||
|         [[nodiscard]] inline bool isPreRelease() const |  | ||||||
|         { |  | ||||||
|             return m_stringPart.startsWith('-') && m_stringPart.length() > 1; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         inline bool operator==(const Section& other) const |         inline bool operator==(const Section& other) const | ||||||
|         { |         { | ||||||
| @@ -156,14 +150,8 @@ class Version { | |||||||
|             return m_fullString < other.m_fullString; |             return m_fullString < other.m_fullString; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         inline bool operator!=(const Section& other) const |         inline bool operator!=(const Section& other) const { return !(*this == other); } | ||||||
|         { |         inline bool operator>(const Section& other) const { return !(*this < other || *this == other); } | ||||||
|             return !(*this == other); |  | ||||||
|         } |  | ||||||
|         inline bool operator>(const Section& other) const |  | ||||||
|         { |  | ||||||
|             return !(*this < other || *this == other); |  | ||||||
|         } |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|    private: |    private: | ||||||
|   | |||||||
| @@ -2,14 +2,14 @@ | |||||||
|  |  | ||||||
| #include "Component.h" | #include "Component.h" | ||||||
| #include "ComponentUpdateTask_p.h" | #include "ComponentUpdateTask_p.h" | ||||||
| #include "OneSixVersionFormat.h" |  | ||||||
| #include "PackProfile.h" | #include "PackProfile.h" | ||||||
| #include "PackProfile_p.h" | #include "PackProfile_p.h" | ||||||
| #include "Version.h" | #include "Version.h" | ||||||
| #include "cassert" | #include "cassert" | ||||||
| #include "meta/Index.h" | #include "meta/Index.h" | ||||||
| #include "meta/Version.h" | #include "meta/Version.h" | ||||||
| #include "meta/VersionList.h" | #include "minecraft/OneSixVersionFormat.h" | ||||||
|  | #include "minecraft/ProfileUtils.h" | ||||||
| #include "net/Mode.h" | #include "net/Mode.h" | ||||||
|  |  | ||||||
| #include "Application.h" | #include "Application.h" | ||||||
|   | |||||||
| @@ -3,8 +3,7 @@ | |||||||
|  *  Prism Launcher - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> |  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||||
|  *  Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org> |  *  Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org> | ||||||
|  *  Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> |  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> | ||||||
|  *  Copyright (c) 2023 seth <getchoo at tuta dot io> |  | ||||||
|  * |  * | ||||||
|  *  This program is free software: you can redistribute it and/or modify |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -88,6 +87,10 @@ | |||||||
| #include "minecraft/gameoptions/GameOptions.h" | #include "minecraft/gameoptions/GameOptions.h" | ||||||
| #include "minecraft/update/FoldersTask.h" | #include "minecraft/update/FoldersTask.h" | ||||||
|  |  | ||||||
|  | #include "tools/BaseProfiler.h" | ||||||
|  |  | ||||||
|  | #include <QActionGroup> | ||||||
|  |  | ||||||
| #ifdef Q_OS_LINUX | #ifdef Q_OS_LINUX | ||||||
| #include "MangoHud.h" | #include "MangoHud.h" | ||||||
| #endif | #endif | ||||||
| @@ -166,7 +169,9 @@ void MinecraftInstance::loadSpecificSettings() | |||||||
|         // Native library workarounds |         // Native library workarounds | ||||||
|         auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false); |         auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false); | ||||||
|         m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride); |         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("UseNativeGLFW"), nativeLibraryWorkaroundsOverride); | ||||||
|  |         m_settings->registerOverride(global_settings->getSetting("CustomGLFWPath"), nativeLibraryWorkaroundsOverride); | ||||||
|  |  | ||||||
|         // Peformance related options |         // Peformance related options | ||||||
|         auto performanceOverride = m_settings->registerSetting("OverridePerformance", false); |         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("CloseAfterLaunch"), miscellaneousOverride); | ||||||
|         m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), 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"); |         m_settings->set("InstanceType", "OneSix"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -194,6 +195,12 @@ void MinecraftInstance::loadSpecificSettings() | |||||||
|     m_settings->registerSetting("UseAccountForInstance", false); |     m_settings->registerSetting("UseAccountForInstance", false); | ||||||
|     m_settings->registerSetting("InstanceAccountId", ""); |     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!"; |     qDebug() << "Instance-type specific settings were loaded!"; | ||||||
|  |  | ||||||
|     setSpecificSettingsLoaded(true); |     setSpecificSettingsLoaded(true); | ||||||
| @@ -229,6 +236,50 @@ QSet<QString> MinecraftInstance::traits() const | |||||||
|     return profile->getTraits(); |     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 | QString MinecraftInstance::gameRoot() const | ||||||
| { | { | ||||||
|     QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); |     QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); | ||||||
| @@ -260,7 +311,7 @@ QString MinecraftInstance::getLocalLibraryPath() const | |||||||
| bool MinecraftInstance::supportsDemo() const | bool MinecraftInstance::supportsDemo() const | ||||||
| { | { | ||||||
|     Version instance_ver{ getPackProfile()->getComponentVersion("net.minecraft") }; |     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 |     // FIXME: Due to Version constraints atm, this can't handle well non-release versions | ||||||
|     return instance_ver >= Version("1.3.1"); |     return instance_ver >= Version("1.3.1"); | ||||||
| } | } | ||||||
| @@ -385,10 +436,31 @@ QStringList MinecraftInstance::extraArguments() | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     { |     { | ||||||
|         const auto loaders = version->getModLoaders(); |         QString openALPath; | ||||||
|         if (loaders.has_value() && loaders.value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool()) |         QString glfwPath; | ||||||
|             list.append("-Dloader.disable_beacon=true"); |  | ||||||
|  |         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; |     return list; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -868,13 +940,16 @@ QString MinecraftInstance::getStatusbarDescription() | |||||||
|     if (m_settings->get("ShowGameTime").toBool()) { |     if (m_settings->get("ShowGameTime").toBool()) { | ||||||
|         if (lastTimePlayed() > 0) { |         if (lastTimePlayed() > 0) { | ||||||
|             QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch()); |             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(QLocale().toString(lastLaunchTime, QLocale::ShortFormat)) | ||||||
|                                    .arg(Time::prettifyDuration(lastTimePlayed()))); |                     .arg(Time::prettifyDuration(lastTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool()))); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (totalTimePlayed() > 0) { |         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()) { |     if (hasCrashed()) { | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  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; } |     bool canExport() const override { return true; } | ||||||
|  |  | ||||||
|  |     void populateLaunchMenu(QMenu* menu) override; | ||||||
|  |  | ||||||
|     ////// Directories and files ////// |     ////// Directories and files ////// | ||||||
|     QString jarModsDir() const; |     QString jarModsDir() const; | ||||||
|     QString resourcePacksDir() const; |     QString resourcePacksDir() const; | ||||||
|   | |||||||
| @@ -58,14 +58,14 @@ | |||||||
| #include "ComponentUpdateTask.h" | #include "ComponentUpdateTask.h" | ||||||
| #include "PackProfile.h" | #include "PackProfile.h" | ||||||
| #include "PackProfile_p.h" | #include "PackProfile_p.h" | ||||||
|  | #include "minecraft/mod/Mod.h" | ||||||
|  | #include "modplatform/ModIndex.h" | ||||||
|  |  | ||||||
| #include "Application.h" | static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge }, | ||||||
| #include "modplatform/ResourceAPI.h" |                                                                          { "net.minecraftforge", ModPlatform::Forge }, | ||||||
|  |                                                                          { "net.fabricmc.fabric-loader", ModPlatform::Fabric }, | ||||||
| static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ { "net.minecraftforge", ResourceAPI::Forge }, |                                                                          { "org.quiltmc.quilt-loader", ModPlatform::Quilt }, | ||||||
|                                                                          { "net.fabricmc.fabric-loader", ResourceAPI::Fabric }, |                                                                          { "com.mumfrey.liteloader", ModPlatform::LiteLoader } }; | ||||||
|                                                                          { "org.quiltmc.quilt-loader", ResourceAPI::Quilt }, |  | ||||||
|                                                                          { "com.mumfrey.liteloader", ResourceAPI::LiteLoader } }; |  | ||||||
|  |  | ||||||
| PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel() | 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; |     bool has_any_loader = false; | ||||||
|  |  | ||||||
|     QMapIterator<QString, ResourceAPI::ModLoaderType> i(modloaderMapping); |     QMapIterator<QString, ModPlatform::ModLoaderType> i(modloaderMapping); | ||||||
|  |  | ||||||
|     while (i.hasNext()) { |     while (i.hasNext()) { | ||||||
|         i.next(); |         i.next(); | ||||||
| @@ -1008,3 +1008,18 @@ std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders() | |||||||
|         return {}; |         return {}; | ||||||
|     return result; |     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 <QList> | ||||||
| #include <QString> | #include <QString> | ||||||
| #include <memory> | #include <memory> | ||||||
|  | #include <optional> | ||||||
|  |  | ||||||
| #include "BaseVersion.h" |  | ||||||
| #include "Component.h" | #include "Component.h" | ||||||
| #include "LaunchProfile.h" | #include "LaunchProfile.h" | ||||||
| #include "Library.h" | #include "modplatform/ModIndex.h" | ||||||
| #include "MojangDownloadInfo.h" |  | ||||||
| #include "ProfileUtils.h" |  | ||||||
| #include "modplatform/ResourceAPI.h" |  | ||||||
| #include "net/Mode.h" | #include "net/Mode.h" | ||||||
|  |  | ||||||
| class MinecraftInstance; | class MinecraftInstance; | ||||||
| @@ -146,7 +143,9 @@ class PackProfile : public QAbstractListModel { | |||||||
|     // todo(merged): is this the best approach |     // todo(merged): is this the best approach | ||||||
|     void appendComponent(ComponentPtr component); |     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: |    private: | ||||||
|     void scheduleSave(); |     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) | 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; |         return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -423,7 +423,8 @@ bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int rol | |||||||
|         if (value == Qt::Checked) { |         if (value == Qt::Checked) { | ||||||
|             MinecraftAccountPtr account = at(idx.row()); |             MinecraftAccountPtr account = at(idx.row()); | ||||||
|             setDefaultAccount(account); |             setDefaultAccount(account); | ||||||
|         } |         } else if (m_defaultAccount == at(idx.row())) | ||||||
|  |             setDefaultAccount(nullptr); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     emit dataChanged(idx, index(idx.row(), columnCount(QModelIndex()) - 1)); |     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; |     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); |     QuaZip zip(source); | ||||||
|     if (!zip.open(QuaZip::mdUnzip)) { |     if (!zip.open(QuaZip::mdUnzip)) { | ||||||
| @@ -52,12 +52,6 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH | |||||||
|     do { |     do { | ||||||
|         QString name = zip.getCurrentFileName(); |         QString name = zip.getCurrentFileName(); | ||||||
|         auto lowercase = name.toLower(); |         auto lowercase = name.toLower(); | ||||||
|         if (nativeGLFW && name.contains("glfw")) { |  | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
|         if (nativeOpenAL && name.contains("openal")) { |  | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
|         if (applyJnilibHack) { |         if (applyJnilibHack) { | ||||||
|             name = replaceSuffix(name, ".jnilib", ".dylib"); |             name = replaceSuffix(name, ".jnilib", ".dylib"); | ||||||
|         } |         } | ||||||
| @@ -83,14 +77,12 @@ void ExtractNatives::executeTask() | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     auto settings = minecraftInstance->settings(); |     auto settings = minecraftInstance->settings(); | ||||||
|     bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool(); |  | ||||||
|     bool nativeGLFW = settings->get("UseNativeGLFW").toBool(); |  | ||||||
|  |  | ||||||
|     auto outputPath = minecraftInstance->getNativePath(); |     auto outputPath = minecraftInstance->getNativePath(); | ||||||
|     auto javaVersion = minecraftInstance->getJavaVersion(); |     auto javaVersion = minecraftInstance->getJavaVersion(); | ||||||
|     bool jniHackEnabled = javaVersion.major() >= 8; |     bool jniHackEnabled = javaVersion.major() >= 8; | ||||||
|     for (const auto& source : toExtract) { |     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'"); |             const char* reason = QT_TR_NOOP("Couldn't extract native jar '%1' to destination '%2'"); | ||||||
|             emit logLine(QString(reason).arg(source, outputPath), MessageLevel::Fatal); |             emit logLine(QString(reason).arg(source, outputPath), MessageLevel::Fatal); | ||||||
|             emitFailed(tr(reason).arg(source, outputPath)); |             emitFailed(tr(reason).arg(source, outputPath)); | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ | |||||||
| #include "Version.h" | #include "Version.h" | ||||||
|  |  | ||||||
| // Values taken from: | // 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 = { | 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") } }, |     { 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") } }, |     { 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; |     mutable QMutex m_data_lock; | ||||||
|  |  | ||||||
|     /* The 'version' of a data pack, as defined in the pack.mcmeta file. |     /* 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; |     int m_pack_format = 0; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -132,15 +132,21 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) - | |||||||
|     if (!preserve_metadata) { |     if (!preserve_metadata) { | ||||||
|         qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name()); |         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()) { |     if (metadata()) { | ||||||
|         Metadata::remove(index_dir, metadata()->slug); |         Metadata::remove(index_dir, metadata()->slug); | ||||||
|     } else { |     } else { | ||||||
|         auto n = name(); |         auto n = name(); | ||||||
|         Metadata::remove(index_dir, n); |         Metadata::remove(index_dir, n); | ||||||
|     } |     } | ||||||
|     } |     m_local_details.metadata = nullptr; | ||||||
|  |  | ||||||
|     return Resource::destroy(attempt_trash); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| auto Mod::details() const -> const ModDetails& | 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); |         PixmapCache::remove(m_pack_image_cache_key.key); | ||||||
|  |  | ||||||
|     // scale the image to avoid flooding the pixmapcache |     // 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.key = PixmapCache::insert(pixmap); | ||||||
|     m_pack_image_cache_key.was_ever_used = true; |     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 (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) { | ||||||
|         if (size.isNull()) |         if (size.isNull()) | ||||||
|             return cached_image; |             return cached_image; | ||||||
|         return cached_image.scaled(size, mode); |         return cached_image.scaled(size, mode, Qt::SmoothTransformation); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // No valid image we can get |     // No valid image we can get | ||||||
|   | |||||||
| @@ -93,6 +93,8 @@ class Mod : public Resource { | |||||||
|  |  | ||||||
|     // Delete all the files of this mod |     // Delete all the files of this mod | ||||||
|     auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool; |     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); |     void finishResolvingWithDetails(ModDetails&& details); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -51,8 +51,13 @@ | |||||||
|  |  | ||||||
| #include "Application.h" | #include "Application.h" | ||||||
|  |  | ||||||
|  | #include "Json.h" | ||||||
| #include "minecraft/mod/tasks/LocalModParseTask.h" | #include "minecraft/mod/tasks/LocalModParseTask.h" | ||||||
|  | #include "minecraft/mod/tasks/LocalModUpdateTask.h" | ||||||
| #include "minecraft/mod/tasks/ModFolderLoadTask.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) | 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) |     : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) | ||||||
| @@ -228,6 +233,25 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes) | |||||||
|     return true; |     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() | bool ModFolderModel::isValid() | ||||||
| { | { | ||||||
|     return m_dir.exists() && m_dir.isReadable(); |     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)); |     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/LocalModParseTask.h" | ||||||
| #include "minecraft/mod/tasks/ModFolderLoadTask.h" | #include "minecraft/mod/tasks/ModFolderLoadTask.h" | ||||||
|  | #include "modplatform/ModIndex.h" | ||||||
|  |  | ||||||
| class LegacyInstance; | class LegacyInstance; | ||||||
| class BaseInstance; | class BaseInstance; | ||||||
| @@ -75,10 +76,12 @@ class ModFolderModel : public ResourceFolderModel { | |||||||
|     [[nodiscard]] Task* createParseTask(Resource&) override; |     [[nodiscard]] Task* createParseTask(Resource&) override; | ||||||
|  |  | ||||||
|     bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); } |     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); |     bool uninstallMod(const QString& filename, bool preserve_metadata = false); | ||||||
|  |  | ||||||
|     /// Deletes all the selected mods |     /// Deletes all the selected mods | ||||||
|     bool deleteMods(const QModelIndexList& indexes); |     bool deleteMods(const QModelIndexList& indexes); | ||||||
|  |     bool deleteModsMetadata(const QModelIndexList& indexes); | ||||||
|  |  | ||||||
|     bool isValid(); |     bool isValid(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -33,6 +33,10 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObje | |||||||
|  |  | ||||||
|     connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged); |     connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged); | ||||||
|     connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this] { m_helper_thread_task.clear(); }); |     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() | ResourceFolderModel::~ResourceFolderModel() | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
| #include "minecraft/mod/tasks/LocalResourcePackParseTask.h" | #include "minecraft/mod/tasks/LocalResourcePackParseTask.h" | ||||||
|  |  | ||||||
| // Values taken from: | // 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 = { | 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") } }, |     { 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") } }, |     { 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); |         PixmapCache::instance().remove(m_pack_image_cache_key.key); | ||||||
|  |  | ||||||
|     // scale the image to avoid flooding the pixmapcache |     // 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.key = PixmapCache::instance().insert(pixmap); | ||||||
|     m_pack_image_cache_key.was_ever_used = true; |     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 (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) { | ||||||
|         if (size.isNull()) |         if (size.isNull()) | ||||||
|             return cached_image; |             return cached_image; | ||||||
|         return cached_image.scaled(size, mode); |         return cached_image.scaled(size, mode, Qt::SmoothTransformation); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // No valid image we can get |     // No valid image we can get | ||||||
|   | |||||||
| @@ -51,7 +51,7 @@ class ResourcePack : public Resource { | |||||||
|     mutable QMutex m_data_lock; |     mutable QMutex m_data_lock; | ||||||
|  |  | ||||||
|     /* The 'version' of a resource pack, as defined in the pack.mcmeta file. |     /* 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; |     int m_pack_format = 0; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -44,7 +44,8 @@ void TexturePack::setImage(QImage new_image) const | |||||||
|         PixmapCache::remove(m_pack_image_cache_key.key); |         PixmapCache::remove(m_pack_image_cache_key.key); | ||||||
|  |  | ||||||
|     // scale the image to avoid flooding the pixmapcache |     // 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.key = PixmapCache::insert(pixmap); | ||||||
|     m_pack_image_cache_key.was_ever_used = true; |     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 (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) { | ||||||
|         if (size.isNull()) |         if (size.isNull()) | ||||||
|             return cached_image; |             return cached_image; | ||||||
|         return cached_image.scaled(size, mode); |         return cached_image.scaled(size, mode, Qt::SmoothTransformation); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // No valid image we can get |     // 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(); |     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, | GetModDependenciesTask::GetModDependenciesTask(QObject* parent, | ||||||
| @@ -75,7 +75,7 @@ void GetModDependenciesTask::prepare() | |||||||
| ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep, | ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep, | ||||||
|                                                             const ModPlatform::ResourceProvider providerName) |                                                             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 overide = ModPlatform::getOverrideDeps(); | ||||||
|         auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) { |         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); |             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); |             pDep->version = provider.mod->loadDependencyVersions(dep, arr); | ||||||
|             if (!pDep->version.addonId.isValid()) { |             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 overide = ModPlatform::getOverrideDeps(); | ||||||
|                     auto over = std::find_if(overide.cbegin(), overide.cend(), |                     auto over = std::find_if(overide.cbegin(), overide.cend(), | ||||||
|                                              [dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; }); |                                              [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; |                         return; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |                 removePack(dep.addonId); | ||||||
|                 qWarning() << "Error while reading mod version empty "; |                 qWarning() << "Error while reading mod version empty "; | ||||||
|                 qDebug() << doc; |                 qDebug() << doc; | ||||||
|                 return; |                 return; | ||||||
| @@ -250,3 +251,32 @@ void GetModDependenciesTask::removePack(const QVariant addonId) | |||||||
|             ++it; |             ++it; | ||||||
| #endif | #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); |                                     QList<std::shared_ptr<PackDependency>> selected); | ||||||
|  |  | ||||||
|     auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; } |     auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; } | ||||||
|  |     QHash<QString, QStringList> getRequiredBy(); | ||||||
|  |  | ||||||
|    protected slots: |    protected slots: | ||||||
|     Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider, int); |     Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider, int); | ||||||
| @@ -80,5 +81,5 @@ class GetModDependenciesTask : public SequentialTask { | |||||||
|     Provider m_modrinth_provider; |     Provider m_modrinth_provider; | ||||||
|  |  | ||||||
|     Version m_version; |     Version m_version; | ||||||
|     ResourceAPI::ModLoaderTypes m_loaderType; |     ModPlatform::ModLoaderTypes m_loaderType; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -133,7 +133,7 @@ bool processZIP(DataPack& pack, ProcessingLevel level) | |||||||
|     return true; |     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) | bool processMCMeta(DataPack& pack, QByteArray&& raw_data) | ||||||
| { | { | ||||||
|     try { |     try { | ||||||
|   | |||||||
| @@ -178,7 +178,7 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level) | |||||||
|     return true; |     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) | bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) | ||||||
| { | { | ||||||
|     try { |     try { | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "minecraft/mod/Mod.h" | #include "minecraft/mod/Mod.h" | ||||||
|  | #include "minecraft/mod/tasks/GetModDependenciesTask.h" | ||||||
| #include "modplatform/ModIndex.h" | #include "modplatform/ModIndex.h" | ||||||
| #include "modplatform/ResourceAPI.h" | #include "modplatform/ResourceAPI.h" | ||||||
| #include "tasks/Task.h" | #include "tasks/Task.h" | ||||||
| @@ -14,7 +15,7 @@ class CheckUpdateTask : public Task { | |||||||
|    public: |    public: | ||||||
|     CheckUpdateTask(QList<Mod*>& mods, |     CheckUpdateTask(QList<Mod*>& mods, | ||||||
|                     std::list<Version>& mcVersions, |                     std::list<Version>& mcVersions, | ||||||
|                     std::optional<ResourceAPI::ModLoaderTypes> loaders, |                     std::optional<ModPlatform::ModLoaderTypes> loaders, | ||||||
|                     std::shared_ptr<ModFolderModel> mods_folder) |                     std::shared_ptr<ModFolderModel> mods_folder) | ||||||
|         : Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(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 getUpdatable() -> std::vector<UpdatableMod>&& { return std::move(m_updatable); } | ||||||
|  |     auto getDependencies() -> QList<std::shared_ptr<GetModDependenciesTask::PackDependency>>&& { return std::move(m_deps); } | ||||||
|  |  | ||||||
|    public slots: |    public slots: | ||||||
|     bool abort() override = 0; |     bool abort() override = 0; | ||||||
| @@ -53,8 +55,9 @@ class CheckUpdateTask : public Task { | |||||||
|    protected: |    protected: | ||||||
|     QList<Mod*>& m_mods; |     QList<Mod*>& m_mods; | ||||||
|     std::list<Version>& m_game_versions; |     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::shared_ptr<ModFolderModel> m_mods_folder; | ||||||
|  |  | ||||||
|     std::vector<UpdatableMod> m_updatable; |     std::vector<UpdatableMod> m_updatable; | ||||||
|  |     QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> m_deps; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
| #include <MurmurHash2.h> | #include <MurmurHash2.h> | ||||||
| #include <QDebug> | #include <QDebug> | ||||||
|  |  | ||||||
|  | #include "Application.h" | ||||||
| #include "Json.h" | #include "Json.h" | ||||||
|  |  | ||||||
| #include "minecraft/mod/Mod.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) | EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::ResourceProvider prov) | ||||||
|     : Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) |     : 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) { |     for (auto* mod : mods) { | ||||||
|         auto hash_task = createNewHash(mod); |         auto hash_task = createNewHash(mod); | ||||||
|         if (!hash_task) |         if (!hash_task) | ||||||
|   | |||||||
| @@ -83,4 +83,25 @@ QString getMetaURL(ResourceProvider provider, QVariant projectID) | |||||||
|            projectID.toString(); |            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 | }  // namespace ModPlatform | ||||||
|   | |||||||
| @@ -30,6 +30,9 @@ class QIODevice; | |||||||
|  |  | ||||||
| namespace ModPlatform { | 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 ResourceProvider { MODRINTH, FLAME }; | ||||||
|  |  | ||||||
| enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK }; | enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK }; | ||||||
| @@ -70,7 +73,7 @@ struct IndexedVersion { | |||||||
|     QString downloadUrl; |     QString downloadUrl; | ||||||
|     QString date; |     QString date; | ||||||
|     QString fileName; |     QString fileName; | ||||||
|     QStringList loaders = {}; |     ModLoaderTypes loaders = {}; | ||||||
|     QString hash_type; |     QString hash_type; | ||||||
|     QString hash; |     QString hash; | ||||||
|     bool is_preferred = true; |     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; }); |         return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; }); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| QString getMetaURL(ResourceProvider provider, QVariant projectID); |  | ||||||
|  |  | ||||||
| struct OverrideDep { | struct OverrideDep { | ||||||
|     QString quilt; |     QString quilt; | ||||||
| @@ -148,6 +150,14 @@ inline auto getOverrideDeps() -> QList<OverrideDep> | |||||||
|  |  | ||||||
| QString getMetaURL(ResourceProvider provider, QVariant projectID); | 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 | }  // namespace ModPlatform | ||||||
|  |  | ||||||
| Q_DECLARE_METATYPE(ModPlatform::IndexedPack) | Q_DECLARE_METATYPE(ModPlatform::IndexedPack) | ||||||
|   | |||||||
| @@ -54,9 +54,6 @@ class ResourceAPI { | |||||||
|    public: |    public: | ||||||
|     virtual ~ResourceAPI() = default; |     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 { |     struct SortingMethod { | ||||||
|         // The index of the sorting method. Used to allow for arbitrary ordering in the list of methods. |         // The index of the sorting method. Used to allow for arbitrary ordering in the list of methods. | ||||||
|         // Used by Flame in the API request. |         // Used by Flame in the API request. | ||||||
| @@ -74,7 +71,7 @@ class ResourceAPI { | |||||||
|  |  | ||||||
|         std::optional<QString> search; |         std::optional<QString> search; | ||||||
|         std::optional<SortingMethod> sorting; |         std::optional<SortingMethod> sorting; | ||||||
|         std::optional<ModLoaderTypes> loaders; |         std::optional<ModPlatform::ModLoaderTypes> loaders; | ||||||
|         std::optional<std::list<Version> > versions; |         std::optional<std::list<Version> > versions; | ||||||
|     }; |     }; | ||||||
|     struct SearchCallbacks { |     struct SearchCallbacks { | ||||||
| @@ -87,7 +84,7 @@ class ResourceAPI { | |||||||
|         ModPlatform::IndexedPack pack; |         ModPlatform::IndexedPack pack; | ||||||
|  |  | ||||||
|         std::optional<std::list<Version> > mcVersions; |         std::optional<std::list<Version> > mcVersions; | ||||||
|         std::optional<ModLoaderTypes> loaders; |         std::optional<ModPlatform::ModLoaderTypes> loaders; | ||||||
|  |  | ||||||
|         VersionSearchArgs(VersionSearchArgs const&) = default; |         VersionSearchArgs(VersionSearchArgs const&) = default; | ||||||
|         void operator=(VersionSearchArgs other) |         void operator=(VersionSearchArgs other) | ||||||
| @@ -108,13 +105,15 @@ class ResourceAPI { | |||||||
|         void operator=(ProjectInfoArgs other) { pack = other.pack; } |         void operator=(ProjectInfoArgs other) { pack = other.pack; } | ||||||
|     }; |     }; | ||||||
|     struct ProjectInfoCallbacks { |     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 { |     struct DependencySearchArgs { | ||||||
|         ModPlatform::Dependency dependency; |         ModPlatform::Dependency dependency; | ||||||
|         Version mcVersion; |         Version mcVersion; | ||||||
|         ModLoaderTypes loader; |         ModPlatform::ModLoaderTypes loader; | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     struct DependencySearchCallbacks { |     struct DependencySearchCallbacks { | ||||||
| @@ -161,25 +160,6 @@ class ResourceAPI { | |||||||
|         return nullptr; |         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: |    protected: | ||||||
|     [[nodiscard]] inline QString debugName() const { return "External resource API"; } |     [[nodiscard]] inline QString debugName() const { return "External resource API"; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| #include "FileResolvingTask.h" | #include "FileResolvingTask.h" | ||||||
|  |  | ||||||
| #include "Json.h" | #include "Json.h" | ||||||
|  | #include "modplatform/ModIndex.h" | ||||||
| #include "net/ApiDownload.h" | #include "net/ApiDownload.h" | ||||||
| #include "net/ApiUpload.h" | #include "net/ApiUpload.h" | ||||||
| #include "net/Upload.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 url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash); | ||||||
|                 auto output = std::make_shared<QByteArray>(); |                 auto output = std::make_shared<QByteArray>(); | ||||||
|                 auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output); |                 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); |                 m_checkJob->addNetAction(dl); | ||||||
|                 blockedProjects.insert(&out, output); |                 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 |         // 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 |         // which file is relative to each loader, so it's best to not use any one and | ||||||
|         // let the user download it manually. |         // let the user download it manually. | ||||||
|         if (file.loaders.size() <= 1) { |         if (!file.loaders || hasSingleModLoaderSelected(file.loaders)) { | ||||||
|             out->url = file.downloadUrl; |             out->url = file.downloadUrl; | ||||||
|             qDebug() << "Found alternative on modrinth " << out->fileName; |             qDebug() << "Found alternative on modrinth " << out->fileName; | ||||||
|         } else { |         } else { | ||||||
| @@ -175,7 +176,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished() | |||||||
|             auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId); |             auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId); | ||||||
|             auto dl = Net::ApiDownload::makeByteArray(url, output); |             auto dl = Net::ApiDownload::makeByteArray(url, output); | ||||||
|             qDebug() << "Fetching url slug for file:" << mod->fileName; |             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 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 json = QJsonDocument::fromJson(*output); | ||||||
|                 auto base = |                 auto base = | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ | |||||||
| #include "FlameModIndex.h" | #include "FlameModIndex.h" | ||||||
|  |  | ||||||
| #include "Application.h" | #include "Application.h" | ||||||
| #include "BuildConfig.h" |  | ||||||
| #include "Json.h" | #include "Json.h" | ||||||
| #include "net/ApiDownload.h" | #include "net/ApiDownload.h" | ||||||
| #include "net/ApiUpload.h" | #include "net/ApiUpload.h" | ||||||
| @@ -131,19 +130,13 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe | |||||||
|             auto obj = Json::requireObject(doc); |             auto obj = Json::requireObject(doc); | ||||||
|             auto arr = Json::requireArray(obj, "data"); |             auto arr = Json::requireArray(obj, "data"); | ||||||
|  |  | ||||||
|             QJsonObject latest_file_obj; |  | ||||||
|             ModPlatform::IndexedVersion ver_tmp; |  | ||||||
|  |  | ||||||
|             for (auto file : arr) { |             for (auto file : arr) { | ||||||
|                 auto file_obj = Json::requireObject(file); |                 auto file_obj = Json::requireObject(file); | ||||||
|                 auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj); |                 auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj); | ||||||
|                 if (file_tmp.date > ver_tmp.date) { |                 if (file_tmp.date > ver.date && (!args.loaders.has_value() || !file_tmp.loaders || args.loaders.value() & file_tmp.loaders)) | ||||||
|                     ver_tmp = file_tmp; |                     ver = file_tmp; | ||||||
|                     latest_file_obj = file_obj; |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             ver = FlameMod::loadIndexedPackVersion(latest_file_obj); |  | ||||||
|         } catch (Json::JsonException& e) { |         } catch (Json::JsonException& e) { | ||||||
|             qCritical() << "Failed to parse response from a version request."; |             qCritical() << "Failed to parse response from a version request."; | ||||||
|             qCritical() << e.what(); |             qCritical() << e.what(); | ||||||
|   | |||||||
| @@ -24,7 +24,10 @@ class FlameAPI : public NetworkResourceAPI { | |||||||
|  |  | ||||||
|     [[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override; |     [[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: |    private: | ||||||
|     static int getClassId(ModPlatform::ResourceType type) |     static int getClassId(ModPlatform::ResourceType type) | ||||||
| @@ -35,22 +38,47 @@ class FlameAPI : public NetworkResourceAPI { | |||||||
|                 return 6; |                 return 6; | ||||||
|             case ModPlatform::ResourceType::RESOURCE_PACK: |             case ModPlatform::ResourceType::RESOURCE_PACK: | ||||||
|                 return 12; |                 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 |         // https://docs.curseforge.com/?http#tocS_ModLoaderType | ||||||
|         if (loaders & Forge) |         switch (loaders) { | ||||||
|  |             case ModPlatform::Forge: | ||||||
|                 return 1; |                 return 1; | ||||||
|         if (loaders & Fabric) |             case ModPlatform::Cauldron: | ||||||
|  |                 return 2; | ||||||
|  |             case ModPlatform::LiteLoader: | ||||||
|  |                 return 3; | ||||||
|  |             case ModPlatform::Fabric: | ||||||
|                 return 4; |                 return 4; | ||||||
|         // TODO: remove this once Quilt drops official Fabric support |             case ModPlatform::Quilt: | ||||||
|         if (loaders & Quilt)  // NOTE: Most if not all Fabric mods should work *currently* |                 return 5; | ||||||
|             return 4;         // Quilt would probably be 5 |             case ModPlatform::NeoForge: | ||||||
|  |                 return 6; | ||||||
|  |         } | ||||||
|         return 0; |         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: |    private: | ||||||
|     [[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override |     [[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(QString("sortField=%1").arg(args.sorting.value().index)); | ||||||
|         get_arguments.append("sortOrder=desc"); |         get_arguments.append("sortOrder=desc"); | ||||||
|         if (args.loaders.has_value()) |         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); |         get_arguments.append(gameVersionStr); | ||||||
|  |  | ||||||
|         return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); |         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 |     [[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override | ||||||
|     { |     { | ||||||
|         auto addonId = args.pack.addonId.toString(); |         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()) |         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()) { |         if (args.loaders.has_value() && ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) { | ||||||
|             int mappedModLoader = getMappedModLoader(args.loaders.value()); |             int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loaders.value()))); | ||||||
|  |             url += QString("&modLoaderType=%1").arg(mappedModLoader); | ||||||
|             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; |  | ||||||
|         } |         } | ||||||
|             } |         return url; | ||||||
|  |  | ||||||
|             get_parameters.append(QString("modLoaderType=%1").arg(mappedModLoader)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return url + get_parameters.join('&'); |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     [[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override |     [[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override | ||||||
|     { |     { | ||||||
|         auto mappedModLoader = getMappedModLoader(args.loader); |  | ||||||
|         auto addonId = args.dependency.addonId.toString(); |         auto addonId = args.dependency.addonId.toString(); | ||||||
|         if (args.loader & Quilt) { |         auto url = | ||||||
|             auto overide = ModPlatform::getOverrideDeps(); |             QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2").arg(addonId, args.mcVersion.toString()); | ||||||
|             auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) { |         if (args.loader && ModPlatform::hasSingleModLoaderSelected(args.loader)) { | ||||||
|                 return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt; |             int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loader))); | ||||||
|             }); |             url += QString("&modLoaderType=%1").arg(mappedModLoader); | ||||||
|             if (over != overide.cend()) { |  | ||||||
|                 mappedModLoader = 5; |  | ||||||
|         } |         } | ||||||
|         } |         return url; | ||||||
|         return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2&modLoaderType=%3") |  | ||||||
|             .arg(addonId) |  | ||||||
|             .arg(args.mcVersion.toString()) |  | ||||||
|             .arg(mappedModLoader); |  | ||||||
|     }; |     }; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -5,13 +5,12 @@ | |||||||
| #include <MurmurHash2.h> | #include <MurmurHash2.h> | ||||||
| #include <memory> | #include <memory> | ||||||
|  |  | ||||||
| #include "FileSystem.h" |  | ||||||
| #include "Json.h" | #include "Json.h" | ||||||
|  |  | ||||||
| #include "ResourceDownloadTask.h" | #include "ResourceDownloadTask.h" | ||||||
|  |  | ||||||
| #include "minecraft/mod/ModFolderModel.h" | #include "minecraft/mod/ModFolderModel.h" | ||||||
| #include "minecraft/mod/ResourceFolderModel.h" | #include "minecraft/mod/tasks/GetModDependenciesTask.h" | ||||||
|  |  | ||||||
| #include "net/ApiDownload.h" | #include "net/ApiDownload.h" | ||||||
|  |  | ||||||
| @@ -156,7 +155,6 @@ void FlameCheckUpdate::executeTask() | |||||||
|             continue; |             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 :) |         // Fake pack with the necessary info to pass to the download task :) | ||||||
|         auto pack = std::make_shared<ModPlatform::IndexedPack>(); |         auto pack = std::make_shared<ModPlatform::IndexedPack>(); | ||||||
|         pack->name = mod->name(); |         pack->name = mod->name(); | ||||||
| @@ -167,7 +165,7 @@ void FlameCheckUpdate::executeTask() | |||||||
|             pack->authors.append({ author }); |             pack->authors.append({ author }); | ||||||
|         pack->description = mod->description(); |         pack->description = mod->description(); | ||||||
|         pack->provider = ModPlatform::ResourceProvider::FLAME; |         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(); |             auto old_version = mod->version(); | ||||||
|             if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) { |             if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) { | ||||||
|                 auto current_ver = getFileInfo(latest_ver.addonId.toInt(), mod->metadata()->file_id.toInt()); |                 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()), |                                      api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), | ||||||
|                                      ModPlatform::ResourceProvider::FLAME, download_task); |                                      ModPlatform::ResourceProvider::FLAME, download_task); | ||||||
|         } |         } | ||||||
|  |         m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     emitSucceeded(); |     emitSucceeded(); | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ class FlameCheckUpdate : public CheckUpdateTask { | |||||||
|    public: |    public: | ||||||
|     FlameCheckUpdate(QList<Mod*>& mods, |     FlameCheckUpdate(QList<Mod*>& mods, | ||||||
|                      std::list<Version>& mcVersions, |                      std::list<Version>& mcVersions, | ||||||
|                      std::optional<ResourceAPI::ModLoaderTypes> loaders, |                      std::optional<ModPlatform::ModLoaderTypes> loaders, | ||||||
|                      std::shared_ptr<ModFolderModel> mods_folder) |                      std::shared_ptr<ModFolderModel> mods_folder) | ||||||
|         : CheckUpdateTask(mods, mcVersions, loaders, 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. |             // 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 |             // not all mod loaders depend on a given Minecraft version, so we won't do this | ||||||
|             // filtering for those loaders. |             // 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) { |                 auto iter = std::find_if(reqs.begin(), reqs.end(), [mcVersion](const Meta::Require& req) { | ||||||
|                     return req.uid == "net.minecraft" && req.equalsVersion == mcVersion; |                     return req.uid == "net.minecraft" && req.equalsVersion == mcVersion; | ||||||
|                 }); |                 }); | ||||||
| @@ -350,7 +350,11 @@ bool FlameCreationTask::createInstance() | |||||||
|  |  | ||||||
|     for (auto& loader : m_pack.minecraft.modLoaders) { |     for (auto& loader : m_pack.minecraft.modLoaders) { | ||||||
|         auto id = loader.id; |         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-"); |             id.remove("forge-"); | ||||||
|             loaderType = "forge"; |             loaderType = "forge"; | ||||||
|             loaderUid = "net.minecraftforge"; |             loaderUid = "net.minecraftforge"; | ||||||
|   | |||||||
| @@ -81,6 +81,7 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, | |||||||
|     QVector<ModPlatform::IndexedVersion> unsortedVersions; |     QVector<ModPlatform::IndexedVersion> unsortedVersions; | ||||||
|     auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile(); |     auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile(); | ||||||
|     QString mcVersion = profile->getComponentVersion("net.minecraft"); |     QString mcVersion = profile->getComponentVersion("net.minecraft"); | ||||||
|  |     auto loaders = profile->getSupportedModLoaders(); | ||||||
|  |  | ||||||
|     for (auto versionIter : arr) { |     for (auto versionIter : arr) { | ||||||
|         auto obj = versionIter.toObject(); |         auto obj = versionIter.toObject(); | ||||||
| @@ -89,7 +90,8 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, | |||||||
|         if (!file.addonId.isValid()) |         if (!file.addonId.isValid()) | ||||||
|             file.addonId = pack.addonId; |             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); |             unsortedVersions.append(file); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -115,6 +117,19 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> | |||||||
|  |  | ||||||
|         if (str.contains('.')) |         if (str.contains('.')) | ||||||
|             file.mcVersion.append(str); |             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"); |     file.addonId = Json::requireInteger(obj, "modId"); | ||||||
| @@ -173,8 +188,11 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> | |||||||
|     return file; |     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; |     QVector<ModPlatform::IndexedVersion> versions; | ||||||
|     for (auto versionIter : arr) { |     for (auto versionIter : arr) { | ||||||
|         auto obj = versionIter.toObject(); |         auto obj = versionIter.toObject(); | ||||||
| @@ -183,7 +201,8 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform:: | |||||||
|         if (!file.addonId.isValid()) |         if (!file.addonId.isValid()) | ||||||
|             file.addonId = m.addonId; |             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); |             versions.append(file); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -192,5 +211,7 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform:: | |||||||
|         return a.date > b.date; |         return a.date > b.date; | ||||||
|     }; |     }; | ||||||
|     std::sort(versions.begin(), versions.end(), orderSortPredicate); |     std::sort(versions.begin(), versions.end(), orderSortPredicate); | ||||||
|  |     if (versions.size() != 0) | ||||||
|         return versions.front(); |         return versions.front(); | ||||||
|  |     return {}; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,5 +19,5 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, | |||||||
|                              const shared_qobject_ptr<QNetworkAccessManager>& network, |                              const shared_qobject_ptr<QNetworkAccessManager>& network, | ||||||
|                              const BaseInstance* inst); |                              const BaseInstance* inst); | ||||||
| auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion; | 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 | }  // namespace FlameMod | ||||||
| @@ -28,6 +28,7 @@ | |||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <iterator> | #include <iterator> | ||||||
| #include <memory> | #include <memory> | ||||||
|  | #include "Application.h" | ||||||
| #include "Json.h" | #include "Json.h" | ||||||
| #include "MMCZip.h" | #include "MMCZip.h" | ||||||
| #include "minecraft/PackProfile.h" | #include "minecraft/PackProfile.h" | ||||||
| @@ -43,12 +44,14 @@ const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" }); | |||||||
| FlamePackExportTask::FlamePackExportTask(const QString& name, | FlamePackExportTask::FlamePackExportTask(const QString& name, | ||||||
|                                          const QString& version, |                                          const QString& version, | ||||||
|                                          const QString& author, |                                          const QString& author, | ||||||
|  |                                          bool optionalFiles, | ||||||
|                                          InstancePtr instance, |                                          InstancePtr instance, | ||||||
|                                          const QString& output, |                                          const QString& output, | ||||||
|                                          MMCZip::FilterFunction filter) |                                          MMCZip::FilterFunction filter) | ||||||
|     : name(name) |     : name(name) | ||||||
|     , version(version) |     , version(version) | ||||||
|     , author(author) |     , author(author) | ||||||
|  |     , optionalFiles(optionalFiles) | ||||||
|     , instance(instance) |     , instance(instance) | ||||||
|     , mcInstance(dynamic_cast<MinecraftInstance*>(instance.get())) |     , mcInstance(dynamic_cast<MinecraftInstance*>(instance.get())) | ||||||
|     , gameRoot(instance->gameRoot()) |     , gameRoot(instance->gameRoot()) | ||||||
| @@ -100,7 +103,8 @@ void FlamePackExportTask::collectHashes() | |||||||
|     setStatus(tr("Finding file hashes...")); |     setStatus(tr("Finding file hashes...")); | ||||||
|     setProgress(1, 5); |     setProgress(1, 5); | ||||||
|     auto allMods = mcInstance->loaderModList()->allMods(); |     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); |     task.reset(hashingTask); | ||||||
|     for (const QFileInfo& file : files) { |     for (const QFileInfo& file : files) { | ||||||
|         const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); |         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 quilt = profile->getComponent("org.quiltmc.quilt-loader"); | ||||||
|         const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); |         const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); | ||||||
|         const ComponentPtr forge = profile->getComponent("net.minecraftforge"); |         const ComponentPtr forge = profile->getComponent("net.minecraftforge"); | ||||||
|  |         const ComponentPtr neoforge = profile->getComponent("net.neoforged"); | ||||||
|  |  | ||||||
|         // convert all available components to mrpack dependencies |         // convert all available components to mrpack dependencies | ||||||
|         if (minecraft != nullptr) |         if (minecraft != nullptr) | ||||||
| @@ -392,6 +397,8 @@ QByteArray FlamePackExportTask::generateIndex() | |||||||
|             id = "fabric-" + fabric->getVersion(); |             id = "fabric-" + fabric->getVersion(); | ||||||
|         else if (forge != nullptr) |         else if (forge != nullptr) | ||||||
|             id = "forge-" + forge->getVersion(); |             id = "forge-" + forge->getVersion(); | ||||||
|  |         else if (neoforge != nullptr) | ||||||
|  |             id = "neoforge-" + neoforge->getVersion(); | ||||||
|         version["modLoaders"] = QJsonArray(); |         version["modLoaders"] = QJsonArray(); | ||||||
|         if (!id.isEmpty()) { |         if (!id.isEmpty()) { | ||||||
|             QJsonObject loader; |             QJsonObject loader; | ||||||
| @@ -407,7 +414,7 @@ QByteArray FlamePackExportTask::generateIndex() | |||||||
|         QJsonObject file; |         QJsonObject file; | ||||||
|         file["projectID"] = mod.addonId; |         file["projectID"] = mod.addonId; | ||||||
|         file["fileID"] = mod.version; |         file["fileID"] = mod.version; | ||||||
|         file["required"] = mod.enabled; |         file["required"] = mod.enabled || !optionalFiles; | ||||||
|         files << file; |         files << file; | ||||||
|     } |     } | ||||||
|     obj["files"] = files; |     obj["files"] = files; | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ class FlamePackExportTask : public Task { | |||||||
|     FlamePackExportTask(const QString& name, |     FlamePackExportTask(const QString& name, | ||||||
|                         const QString& version, |                         const QString& version, | ||||||
|                         const QString& author, |                         const QString& author, | ||||||
|  |                         bool optionalFiles, | ||||||
|                         InstancePtr instance, |                         InstancePtr instance, | ||||||
|                         const QString& output, |                         const QString& output, | ||||||
|                         MMCZip::FilterFunction filter); |                         MMCZip::FilterFunction filter); | ||||||
| @@ -44,6 +45,7 @@ class FlamePackExportTask : public Task { | |||||||
|  |  | ||||||
|     // inputs |     // inputs | ||||||
|     const QString name, version, author; |     const QString name, version, author; | ||||||
|  |     const bool optionalFiles; | ||||||
|     const InstancePtr instance; |     const InstancePtr instance; | ||||||
|     MinecraftInstance* mcInstance; |     MinecraftInstance* mcInstance; | ||||||
|     const QDir gameRoot; |     const QDir gameRoot; | ||||||
|   | |||||||
| @@ -72,7 +72,8 @@ Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfo | |||||||
|  |  | ||||||
|         callbacks.on_succeed(doc, args.pack); |         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; |     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 netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network()); | ||||||
|     auto response = std::make_shared<QByteArray>(); |     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, [=] { |     QObject::connect(netJob.get(), &NetJob::succeeded, [=] { | ||||||
|         QJsonParseError parse_error{}; |         QJsonParseError parse_error{}; | ||||||
|   | |||||||
| @@ -59,16 +59,20 @@ Modpack parseDirectory(QString path) | |||||||
|             auto obj = Json::requireObject(target, "target"); |             auto obj = Json::requireObject(target, "target"); | ||||||
|             auto name = Json::requireString(obj, "name", "name"); |             auto name = Json::requireString(obj, "name", "name"); | ||||||
|             auto version = Json::requireString(obj, "version", "version"); |             auto version = Json::requireString(obj, "version", "version"); | ||||||
|             if (name == "forge") { |             if (name == "neoforge") { | ||||||
|                 modpack.loaderType = ResourceAPI::Forge; |                 modpack.loaderType = ModPlatform::NeoForge; | ||||||
|  |                 modpack.version = version; | ||||||
|  |                 break; | ||||||
|  |             } else if (name == "forge") { | ||||||
|  |                 modpack.loaderType = ModPlatform::Forge; | ||||||
|                 modpack.version = version; |                 modpack.version = version; | ||||||
|                 break; |                 break; | ||||||
|             } else if (name == "fabric") { |             } else if (name == "fabric") { | ||||||
|                 modpack.loaderType = ResourceAPI::Fabric; |                 modpack.loaderType = ModPlatform::Fabric; | ||||||
|                 modpack.version = version; |                 modpack.version = version; | ||||||
|                 break; |                 break; | ||||||
|             } else if (name == "quilt") { |             } else if (name == "quilt") { | ||||||
|                 modpack.loaderType = ResourceAPI::Quilt; |                 modpack.loaderType = ModPlatform::Quilt; | ||||||
|                 modpack.version = version; |                 modpack.version = version; | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ struct Modpack { | |||||||
|     // not needed for instance creation |     // not needed for instance creation | ||||||
|     QVariant jvmArgs; |     QVariant jvmArgs; | ||||||
|  |  | ||||||
|     std::optional<ResourceAPI::ModLoaderType> loaderType; |     std::optional<ModPlatform::ModLoaderType> loaderType; | ||||||
|     QString loaderVersion; |     QString loaderVersion; | ||||||
|  |  | ||||||
|     QIcon icon; |     QIcon icon; | ||||||
|   | |||||||
| @@ -68,21 +68,25 @@ void PackInstallTask::copySettings() | |||||||
|     auto modloader = m_pack.loaderType; |     auto modloader = m_pack.loaderType; | ||||||
|     if (modloader.has_value()) |     if (modloader.has_value()) | ||||||
|         switch (modloader.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); |                 components->setComponentVersion("net.minecraftforge", m_pack.version, true); | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             case ResourceAPI::Fabric: { |             case ModPlatform::Fabric: { | ||||||
|                 components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.version, true); |                 components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.version, true); | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             case ResourceAPI::Quilt: { |             case ModPlatform::Quilt: { | ||||||
|                 components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.version, true); |                 components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.version, true); | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             case ResourceAPI::Cauldron: |             case ModPlatform::Cauldron: | ||||||
|                 break; |                 break; | ||||||
|             case ResourceAPI::LiteLoader: |             case ModPlatform::LiteLoader: | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
|     components->saveNow(); |     components->saveNow(); | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f | |||||||
| Task::Ptr ModrinthAPI::latestVersion(QString hash, | Task::Ptr ModrinthAPI::latestVersion(QString hash, | ||||||
|                                      QString hash_format, |                                      QString hash_format, | ||||||
|                                      std::optional<std::list<Version>> mcVersions, |                                      std::optional<std::list<Version>> mcVersions, | ||||||
|                                      std::optional<ModLoaderTypes> loaders, |                                      std::optional<ModPlatform::ModLoaderTypes> loaders, | ||||||
|                                      std::shared_ptr<QByteArray> response) |                                      std::shared_ptr<QByteArray> response) | ||||||
| { | { | ||||||
|     auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); |     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, | Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes, | ||||||
|                                       QString hash_format, |                                       QString hash_format, | ||||||
|                                       std::optional<std::list<Version>> mcVersions, |                                       std::optional<std::list<Version>> mcVersions, | ||||||
|                                       std::optional<ModLoaderTypes> loaders, |                                       std::optional<ModPlatform::ModLoaderTypes> loaders, | ||||||
|                                       std::shared_ptr<QByteArray> response) |                                       std::shared_ptr<QByteArray> response) | ||||||
| { | { | ||||||
|     auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); |     auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); | ||||||
|   | |||||||
| @@ -19,13 +19,13 @@ class ModrinthAPI : public NetworkResourceAPI { | |||||||
|     auto latestVersion(QString hash, |     auto latestVersion(QString hash, | ||||||
|                        QString hash_format, |                        QString hash_format, | ||||||
|                        std::optional<std::list<Version>> mcVersions, |                        std::optional<std::list<Version>> mcVersions, | ||||||
|                        std::optional<ModLoaderTypes> loaders, |                        std::optional<ModPlatform::ModLoaderTypes> loaders, | ||||||
|                        std::shared_ptr<QByteArray> response) -> Task::Ptr; |                        std::shared_ptr<QByteArray> response) -> Task::Ptr; | ||||||
|  |  | ||||||
|     auto latestVersions(const QStringList& hashes, |     auto latestVersions(const QStringList& hashes, | ||||||
|                         QString hash_format, |                         QString hash_format, | ||||||
|                         std::optional<std::list<Version>> mcVersions, |                         std::optional<std::list<Version>> mcVersions, | ||||||
|                         std::optional<ModLoaderTypes> loaders, |                         std::optional<ModPlatform::ModLoaderTypes> loaders, | ||||||
|                         std::shared_ptr<QByteArray> response) -> Task::Ptr; |                         std::shared_ptr<QByteArray> response) -> Task::Ptr; | ||||||
|  |  | ||||||
|     Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override; |     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; }; |     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; |         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) { |             if (types & loader) { | ||||||
|                 l << getModLoaderString(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; |         return l; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     static auto getModLoaderFilters(ModLoaderTypes types) -> const QString |     static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString | ||||||
|     { |     { | ||||||
|         QStringList l; |         QStringList l; | ||||||
|         for (auto loader : getModLoaderStrings(types)) { |         for (auto loader : getModLoaderStrings(types)) { | ||||||
| @@ -141,7 +140,10 @@ class ModrinthAPI : public NetworkResourceAPI { | |||||||
|         return s.isEmpty() ? QString() : s; |         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 |     [[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ | |||||||
| #include "tasks/ConcurrentTask.h" | #include "tasks/ConcurrentTask.h" | ||||||
|  |  | ||||||
| #include "minecraft/mod/ModFolderModel.h" | #include "minecraft/mod/ModFolderModel.h" | ||||||
| #include "minecraft/mod/ResourceFolderModel.h" |  | ||||||
|  |  | ||||||
| static ModrinthAPI api; | static ModrinthAPI api; | ||||||
| static ModPlatform::ProviderCapabilities ProviderCaps; | static ModPlatform::ProviderCapabilities ProviderCaps; | ||||||
| @@ -39,7 +38,7 @@ void ModrinthCheckUpdate::executeTask() | |||||||
|     QStringList hashes; |     QStringList hashes; | ||||||
|     auto best_hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); |     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) { |     for (auto* mod : m_mods) { | ||||||
|         if (!mod->enabled()) { |         if (!mod->enabled()) { | ||||||
|             emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!")); |             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 |                 // so we may want to filter it | ||||||
|                 QString loader_filter; |                 QString loader_filter; | ||||||
|                 if (m_loaders.has_value()) { |                 if (m_loaders.has_value()) { | ||||||
|                     static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric, |                     static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge, | ||||||
|                                           ResourceAPI::ModLoaderType::Quilt }; |                                           ModPlatform::ModLoaderType::Fabric, ModPlatform::ModLoaderType::Quilt }; | ||||||
|                     for (auto flag : flags) { |                     for (auto flag : flags) { | ||||||
|                         if (m_loaders.value().testFlag(flag)) { |                         if (m_loaders.value().testFlag(flag)) { | ||||||
|                             loader_filter = api.getModLoaderString(flag); |                             loader_filter = ModPlatform::getModLoaderString(flag); | ||||||
|                             break; |                             break; | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
| @@ -145,9 +144,6 @@ void ModrinthCheckUpdate::executeTask() | |||||||
|                 auto mod = *mod_iter; |                 auto mod = *mod_iter; | ||||||
|  |  | ||||||
|                 auto key = project_ver.hash; |                 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 :) |                 // Fake pack with the necessary info to pass to the download task :) | ||||||
|                 auto pack = std::make_shared<ModPlatform::IndexedPack>(); |                 auto pack = std::make_shared<ModPlatform::IndexedPack>(); | ||||||
| @@ -159,12 +155,16 @@ void ModrinthCheckUpdate::executeTask() | |||||||
|                     pack->authors.append({ author }); |                     pack->authors.append({ author }); | ||||||
|                 pack->description = mod->description(); |                 pack->description = mod->description(); | ||||||
|                 pack->provider = ModPlatform::ResourceProvider::MODRINTH; |                 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); |                     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, |                     m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.changelog, | ||||||
|                                              ModPlatform::ResourceProvider::MODRINTH, download_task); |                                              ModPlatform::ResourceProvider::MODRINTH, download_task); | ||||||
|                 } |                 } | ||||||
|  |                 m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver)); | ||||||
|             } |             } | ||||||
|         } catch (Json::JsonException& e) { |         } catch (Json::JsonException& e) { | ||||||
|             failed(e.cause() + " : " + e.what()); |             failed(e.cause() + " : " + e.what()); | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ class ModrinthCheckUpdate : public CheckUpdateTask { | |||||||
|    public: |    public: | ||||||
|     ModrinthCheckUpdate(QList<Mod*>& mods, |     ModrinthCheckUpdate(QList<Mod*>& mods, | ||||||
|                         std::list<Version>& mcVersions, |                         std::list<Version>& mcVersions, | ||||||
|                         std::optional<ResourceAPI::ModLoaderTypes> loaders, |                         std::optional<ModPlatform::ModLoaderTypes> loaders, | ||||||
|                         std::shared_ptr<ModFolderModel> mods_folder) |                         std::shared_ptr<ModFolderModel> mods_folder) | ||||||
|         : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) |         : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) | ||||||
|     {} |     {} | ||||||
|   | |||||||
| @@ -211,6 +211,8 @@ bool ModrinthCreationTask::createInstance() | |||||||
|         components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version); |         components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version); | ||||||
|     if (!m_forge_version.isEmpty()) |     if (!m_forge_version.isEmpty()) | ||||||
|         components->setComponentVersion("net.minecraftforge", m_forge_version); |         components->setComponentVersion("net.minecraftforge", m_forge_version); | ||||||
|  |     if (!m_neoForge_version.isEmpty()) | ||||||
|  |         components->setComponentVersion("net.neoforged", m_neoForge_version); | ||||||
|  |  | ||||||
|     if (m_instIcon != "default") { |     if (m_instIcon != "default") { | ||||||
|         instance.setIconKey(m_instIcon); |         instance.setIconKey(m_instIcon); | ||||||
| @@ -398,6 +400,8 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, | |||||||
|                         m_quilt_version = Json::requireString(*it, "Quilt Loader version"); |                         m_quilt_version = Json::requireString(*it, "Quilt Loader version"); | ||||||
|                     } else if (name == "forge") { |                     } else if (name == "forge") { | ||||||
|                         m_forge_version = Json::requireString(*it, "Forge version"); |                         m_forge_version = Json::requireString(*it, "Forge version"); | ||||||
|  |                     } else if (name == "neoforge") { | ||||||
|  |                         m_neoForge_version = Json::requireString(*it, "NeoForge version"); | ||||||
|                     } else { |                     } else { | ||||||
|                         throw JSONValidationError("Unknown dependency type: " + name); |                         throw JSONValidationError("Unknown dependency type: " + name); | ||||||
|                     } |                     } | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { | |||||||
|    private: |    private: | ||||||
|     QWidget* m_parent = nullptr; |     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; |     QString m_managed_id, m_managed_version_id, m_managed_name; | ||||||
|  |  | ||||||
|     std::vector<Modrinth::File> m_files; |     std::vector<Modrinth::File> m_files; | ||||||
|   | |||||||
| @@ -33,12 +33,14 @@ const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "z | |||||||
| ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, | ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, | ||||||
|                                                const QString& version, |                                                const QString& version, | ||||||
|                                                const QString& summary, |                                                const QString& summary, | ||||||
|  |                                                bool optionalFiles, | ||||||
|                                                InstancePtr instance, |                                                InstancePtr instance, | ||||||
|                                                const QString& output, |                                                const QString& output, | ||||||
|                                                MMCZip::FilterFunction filter) |                                                MMCZip::FilterFunction filter) | ||||||
|     : name(name) |     : name(name) | ||||||
|     , version(version) |     , version(version) | ||||||
|     , summary(summary) |     , summary(summary) | ||||||
|  |     , optionalFiles(optionalFiles) | ||||||
|     , instance(instance) |     , instance(instance) | ||||||
|     , mcInstance(dynamic_cast<MinecraftInstance*>(instance.get())) |     , mcInstance(dynamic_cast<MinecraftInstance*>(instance.get())) | ||||||
|     , gameRoot(instance->gameRoot()) |     , gameRoot(instance->gameRoot()) | ||||||
| @@ -245,6 +247,7 @@ QByteArray ModrinthPackExportTask::generateIndex() | |||||||
|         const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); |         const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); | ||||||
|         const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); |         const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); | ||||||
|         const ComponentPtr forge = profile->getComponent("net.minecraftforge"); |         const ComponentPtr forge = profile->getComponent("net.minecraftforge"); | ||||||
|  |         const ComponentPtr neoForge = profile->getComponent("net.neoforged"); | ||||||
|  |  | ||||||
|         // convert all available components to mrpack dependencies |         // convert all available components to mrpack dependencies | ||||||
|         QJsonObject dependencies; |         QJsonObject dependencies; | ||||||
| @@ -256,6 +259,8 @@ QByteArray ModrinthPackExportTask::generateIndex() | |||||||
|             dependencies["fabric-loader"] = fabric->m_version; |             dependencies["fabric-loader"] = fabric->m_version; | ||||||
|         if (forge != nullptr) |         if (forge != nullptr) | ||||||
|             dependencies["forge"] = forge->m_version; |             dependencies["forge"] = forge->m_version; | ||||||
|  |         if (neoForge != nullptr) | ||||||
|  |             dependencies["neoforge"] = neoForge->m_version; | ||||||
|  |  | ||||||
|         out["dependencies"] = dependencies; |         out["dependencies"] = dependencies; | ||||||
|     } |     } | ||||||
| @@ -267,6 +272,7 @@ QByteArray ModrinthPackExportTask::generateIndex() | |||||||
|         QString path = iterator.key(); |         QString path = iterator.key(); | ||||||
|         const ResolvedFile& value = iterator.value(); |         const ResolvedFile& value = iterator.value(); | ||||||
|  |  | ||||||
|  |         if (optionalFiles) { | ||||||
|             // detect disabled mod |             // detect disabled mod | ||||||
|             const QFileInfo pathInfo(path); |             const QFileInfo pathInfo(path); | ||||||
|             if (pathInfo.suffix() == "disabled") { |             if (pathInfo.suffix() == "disabled") { | ||||||
| @@ -278,6 +284,7 @@ QByteArray ModrinthPackExportTask::generateIndex() | |||||||
|                 env["server"] = "optional"; |                 env["server"] = "optional"; | ||||||
|                 fileOut["env"] = env; |                 fileOut["env"] = env; | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         fileOut["path"] = path; |         fileOut["path"] = path; | ||||||
|         fileOut["downloads"] = QJsonArray{ iterator.value().url }; |         fileOut["downloads"] = QJsonArray{ iterator.value().url }; | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ class ModrinthPackExportTask : public Task { | |||||||
|     ModrinthPackExportTask(const QString& name, |     ModrinthPackExportTask(const QString& name, | ||||||
|                            const QString& version, |                            const QString& version, | ||||||
|                            const QString& summary, |                            const QString& summary, | ||||||
|  |                            bool optionalFiles, | ||||||
|                            InstancePtr instance, |                            InstancePtr instance, | ||||||
|                            const QString& output, |                            const QString& output, | ||||||
|                            MMCZip::FilterFunction filter); |                            MMCZip::FilterFunction filter); | ||||||
| @@ -50,6 +51,7 @@ class ModrinthPackExportTask : public Task { | |||||||
|  |  | ||||||
|     // inputs |     // inputs | ||||||
|     const QString name, version, summary; |     const QString name, version, summary; | ||||||
|  |     const bool optionalFiles; | ||||||
|     const InstancePtr instance; |     const InstancePtr instance; | ||||||
|     MinecraftInstance* mcInstance; |     MinecraftInstance* mcInstance; | ||||||
|     const QDir gameRoot; |     const QDir gameRoot; | ||||||
|   | |||||||
| @@ -93,19 +93,19 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob | |||||||
|     pack.extraDataLoaded = true; |     pack.extraDataLoaded = true; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, | void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst) | ||||||
|                                        QJsonArray& arr, |  | ||||||
|                                        [[maybe_unused]] const shared_qobject_ptr<QNetworkAccessManager>& network, |  | ||||||
|                                        const BaseInstance* inst) |  | ||||||
| { | { | ||||||
|     QVector<ModPlatform::IndexedVersion> unsortedVersions; |     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) { |     for (auto versionIter : arr) { | ||||||
|         auto obj = versionIter.toObject(); |         auto obj = versionIter.toObject(); | ||||||
|         auto file = loadIndexedPackVersion(obj); |         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); |             unsortedVersions.append(file); | ||||||
|     } |     } | ||||||
|     auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { |     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"); |     auto loaders = Json::requireArray(obj, "loaders"); | ||||||
|     for (auto loader : 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 = Json::requireString(obj, "name"); | ||||||
|     file.version_number = Json::requireString(obj, "version_number"); |     file.version_number = Json::requireString(obj, "version_number"); | ||||||
| @@ -218,15 +229,20 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t | |||||||
|     return {}; |     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) { |     for (auto versionIter : arr) { | ||||||
|         auto obj = versionIter.toObject(); |         auto obj = versionIter.toObject(); | ||||||
|         auto file = loadIndexedPackVersion(obj); |         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); |             versions.append(file); | ||||||
|     } |     } | ||||||
|     auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { |     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 loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); | ||||||
| void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj); | void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj); | ||||||
| void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, | void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst); | ||||||
|                              QJsonArray& arr, |  | ||||||
|                              const shared_qobject_ptr<QNetworkAccessManager>& network, |  | ||||||
|                              const BaseInstance* inst); |  | ||||||
| auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion; | 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 | }  // namespace Modrinth | ||||||
|   | |||||||
| @@ -218,9 +218,24 @@ void HttpMetaCache::Load() | |||||||
|     if (!index.open(QIODevice::ReadOnly)) |     if (!index.open(QIODevice::ReadOnly)) | ||||||
|         return; |         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 |     // check file version first | ||||||
|     auto version_val = Json::ensureString(root, "version"); |     auto version_val = Json::ensureString(root, "version"); | ||||||
|   | |||||||
| @@ -36,9 +36,14 @@ | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| #include "NetJob.h" | #include "NetJob.h" | ||||||
|  | #include "Application.h" | ||||||
| #include "tasks/ConcurrentTask.h" | #include "tasks/ConcurrentTask.h" | ||||||
| #include "ui/dialogs/CustomMessageBox.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 | auto NetJob::addNetAction(NetAction::Ptr action) -> bool | ||||||
| { | { | ||||||
|     action->setNetwork(m_network); |     action->setNetwork(m_network); | ||||||
|   | |||||||
| @@ -52,9 +52,7 @@ class NetJob : public ConcurrentTask { | |||||||
|    public: |    public: | ||||||
|     using Ptr = shared_qobject_ptr<NetJob>; |     using Ptr = shared_qobject_ptr<NetJob>; | ||||||
|  |  | ||||||
|     explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network) |     explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network); | ||||||
|         : ConcurrentTask(nullptr, job_name), m_network(network) |  | ||||||
|     {} |  | ||||||
|     ~NetJob() override = default; |     ~NetJob() override = default; | ||||||
|  |  | ||||||
|     void startNext() override; |     void startNext() override; | ||||||
|   | |||||||
| @@ -350,6 +350,7 @@ | |||||||
|  |  | ||||||
|         <file>scalable/instances/quiltmc.svg</file>  <!-- CC0 QuiltMC --> |         <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/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/forge.png</file>  <!-- LGPL3 Forge Development LLC --> | ||||||
|         <file>128x128/instances/liteloader.png</file>  <!-- CC-BY-SA 4.0 LiteLoader --> |         <file>128x128/instances/liteloader.png</file>  <!-- CC-BY-SA 4.0 LiteLoader --> | ||||||
|     </qresource> |     </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); |     explicit ConcurrentTask(QObject* parent = nullptr, QString task_name = "", int max_concurrent = 6); | ||||||
|     ~ConcurrentTask() override; |     ~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; } |     bool canAbort() const override { return true; } | ||||||
|  |  | ||||||
|     inline auto isMultiStep() const -> bool override { return totalSize() > 1; } |     inline auto isMultiStep() const -> bool override { return totalSize() > 1; } | ||||||
|   | |||||||
| @@ -44,8 +44,6 @@ | |||||||
| #include <QPushButton> | #include <QPushButton> | ||||||
| #include <QScrollBar> | #include <QScrollBar> | ||||||
|  |  | ||||||
| #include "ui/dialogs/CustomMessageBox.h" |  | ||||||
| #include "ui/dialogs/ProgressDialog.h" |  | ||||||
| #include "ui/widgets/PageContainer.h" | #include "ui/widgets/PageContainer.h" | ||||||
|  |  | ||||||
| #include "InstancePageProvider.h" | #include "InstancePageProvider.h" | ||||||
| @@ -76,40 +74,44 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget* parent) : QMainWin | |||||||
|  |  | ||||||
|     // Add custom buttons to the page container layout. |     // Add custom buttons to the page container layout. | ||||||
|     { |     { | ||||||
|         auto horizontalLayout = new QHBoxLayout(); |         auto horizontalLayout = new QHBoxLayout(this); | ||||||
|         horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); |         horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); | ||||||
|         horizontalLayout->setContentsMargins(6, -1, 6, -1); |         horizontalLayout->setContentsMargins(6, -1, 6, -1); | ||||||
|  |  | ||||||
|         auto btnHelp = new QPushButton(); |         auto btnHelp = new QPushButton(this); | ||||||
|         btnHelp->setText(tr("Help")); |         btnHelp->setText(tr("Help")); | ||||||
|         horizontalLayout->addWidget(btnHelp); |         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); |         auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); | ||||||
|         horizontalLayout->addSpacerItem(spacer); |         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); |         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(); |         updateButtons(); | ||||||
|         horizontalLayout->addWidget(m_launchOfflineButton); |  | ||||||
|         m_launchOfflineButton->setText(tr("Launch Offline")); |  | ||||||
|  |  | ||||||
|         m_launchDemoButton = new QPushButton(); |         m_closeButton = new QPushButton(this); | ||||||
|         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->setText(tr("Close")); |         m_closeButton->setText(tr("Close")); | ||||||
|         horizontalLayout->addWidget(m_closeButton); |         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); |         m_container->addButtons(horizontalLayout); | ||||||
|  |  | ||||||
|  |         connect(m_instance.get(), &BaseInstance::profilerChanged, this, &InstanceWindow::updateButtons); | ||||||
|  |         connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceWindow::updateButtons); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // restore window state |     // 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_launchButton->setEnabled(m_instance->canLaunch()); | ||||||
|         m_launchOfflineButton->setEnabled(false); |     m_killButton->setEnabled(m_instance->isRunning()); | ||||||
|         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); |  | ||||||
|  |  | ||||||
|         // Disable demo-mode if not available. |     QMenu* launchMenu = m_launchButton->menu(); | ||||||
|         auto instance = dynamic_cast<MinecraftInstance*>(m_instance.get()); |     if (launchMenu) | ||||||
|         if (instance) { |         launchMenu->clear(); | ||||||
|             m_launchDemoButton->setEnabled(instance->supportsDemo()); |     else | ||||||
|         } |         launchMenu = new QMenu(this); | ||||||
|  |     m_instance->populateLaunchMenu(launchMenu); | ||||||
|         m_killButton->setText(tr("Launch")); |     m_launchButton->setMenu(launchMenu); | ||||||
|         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); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc) | 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) | void InstanceWindow::runningStateChanged(bool running) | ||||||
| { | { | ||||||
|     updateLaunchButtons(); |     updateButtons(); | ||||||
|     m_container->refreshContainer(); |     m_container->refreshContainer(); | ||||||
|     if (running) { |     if (running) { | ||||||
|         selectPage("log"); |         selectPage("log"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| void InstanceWindow::on_closeButton_clicked() |  | ||||||
| { |  | ||||||
|     close(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void InstanceWindow::closeEvent(QCloseEvent* event) | void InstanceWindow::closeEvent(QCloseEvent* event) | ||||||
| { | { | ||||||
|     bool proceed = true; |     bool proceed = true; | ||||||
| @@ -233,15 +201,6 @@ bool InstanceWindow::saveAll() | |||||||
|     return m_container->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() | QString InstanceWindow::instanceId() | ||||||
| { | { | ||||||
|     return m_instance->id(); |     return m_instance->id(); | ||||||
| @@ -252,17 +211,15 @@ bool InstanceWindow::selectPage(QString pageId) | |||||||
|     return m_container->selectPage(pageId); |     return m_container->selectPage(pageId); | ||||||
| } | } | ||||||
|  |  | ||||||
| BasePage* InstanceWindow::selectedPage() const |  | ||||||
| { |  | ||||||
|     return m_container->selectedPage(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void InstanceWindow::refreshContainer() | void InstanceWindow::refreshContainer() | ||||||
| { | { | ||||||
|     m_container->refreshContainer(); |     m_container->refreshContainer(); | ||||||
| } | } | ||||||
|  |  | ||||||
| InstanceWindow::~InstanceWindow() {} | BasePage* InstanceWindow::selectedPage() const | ||||||
|  | { | ||||||
|  |     return m_container->selectedPage(); | ||||||
|  | } | ||||||
|  |  | ||||||
| bool InstanceWindow::requestClose() | bool InstanceWindow::requestClose() | ||||||
| { | { | ||||||
|   | |||||||
| @@ -38,6 +38,7 @@ | |||||||
|  |  | ||||||
| #include <QMainWindow> | #include <QMainWindow> | ||||||
| #include <QSystemTrayIcon> | #include <QSystemTrayIcon> | ||||||
|  | #include <QToolButton> | ||||||
|  |  | ||||||
| #include "LaunchController.h" | #include "LaunchController.h" | ||||||
| #include "launch/LaunchTask.h" | #include "launch/LaunchTask.h" | ||||||
| @@ -53,7 +54,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer { | |||||||
|  |  | ||||||
|    public: |    public: | ||||||
|     explicit InstanceWindow(InstancePtr proc, QWidget* parent = 0); |     explicit InstanceWindow(InstancePtr proc, QWidget* parent = 0); | ||||||
|     virtual ~InstanceWindow(); |     virtual ~InstanceWindow() = default; | ||||||
|  |  | ||||||
|     bool selectPage(QString pageId) override; |     bool selectPage(QString pageId) override; | ||||||
|     BasePage* selectedPage() const override; |     BasePage* selectedPage() const override; | ||||||
| @@ -71,11 +72,6 @@ class InstanceWindow : public QMainWindow, public BasePageContainer { | |||||||
|     void isClosing(); |     void isClosing(); | ||||||
|  |  | ||||||
|    private slots: |    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 instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc); | ||||||
|     void runningStateChanged(bool running); |     void runningStateChanged(bool running); | ||||||
|     void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus); |     void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus); | ||||||
| @@ -84,7 +80,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer { | |||||||
|     void closeEvent(QCloseEvent*) override; |     void closeEvent(QCloseEvent*) override; | ||||||
|  |  | ||||||
|    private: |    private: | ||||||
|     void updateLaunchButtons(); |     void updateButtons(); | ||||||
|  |  | ||||||
|    private: |    private: | ||||||
|     shared_qobject_ptr<LaunchTask> m_proc; |     shared_qobject_ptr<LaunchTask> m_proc; | ||||||
| @@ -92,7 +88,6 @@ class InstanceWindow : public QMainWindow, public BasePageContainer { | |||||||
|     bool m_doNotSave = false; |     bool m_doNotSave = false; | ||||||
|     PageContainer* m_container = nullptr; |     PageContainer* m_container = nullptr; | ||||||
|     QPushButton* m_closeButton = nullptr; |     QPushButton* m_closeButton = nullptr; | ||||||
|  |     QToolButton* m_launchButton = nullptr; | ||||||
|     QPushButton* m_killButton = nullptr; |     QPushButton* m_killButton = nullptr; | ||||||
|     QPushButton* m_launchOfflineButton = nullptr; |  | ||||||
|     QPushButton* m_launchDemoButton = nullptr; |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -43,7 +43,6 @@ | |||||||
| #include "FileSystem.h" | #include "FileSystem.h" | ||||||
|  |  | ||||||
| #include "MainWindow.h" | #include "MainWindow.h" | ||||||
| #include "ui/dialogs/ExportToModListDialog.h" |  | ||||||
| #include "ui_MainWindow.h" | #include "ui_MainWindow.h" | ||||||
|  |  | ||||||
| #include <QDir> | #include <QDir> | ||||||
| @@ -90,17 +89,14 @@ | |||||||
| #include <news/NewsChecker.h> | #include <news/NewsChecker.h> | ||||||
| #include <tools/BaseProfiler.h> | #include <tools/BaseProfiler.h> | ||||||
| #include <updater/ExternalUpdater.h> | #include <updater/ExternalUpdater.h> | ||||||
| #include "InstancePageProvider.h" |  | ||||||
| #include "InstanceWindow.h" | #include "InstanceWindow.h" | ||||||
| #include "JavaCommon.h" |  | ||||||
| #include "LaunchController.h" |  | ||||||
|  |  | ||||||
| #include "ui/dialogs/AboutDialog.h" | #include "ui/dialogs/AboutDialog.h" | ||||||
| #include "ui/dialogs/CopyInstanceDialog.h" | #include "ui/dialogs/CopyInstanceDialog.h" | ||||||
| #include "ui/dialogs/CustomMessageBox.h" | #include "ui/dialogs/CustomMessageBox.h" | ||||||
| #include "ui/dialogs/EditAccountDialog.h" |  | ||||||
| #include "ui/dialogs/ExportInstanceDialog.h" | #include "ui/dialogs/ExportInstanceDialog.h" | ||||||
| #include "ui/dialogs/ExportPackDialog.h" | #include "ui/dialogs/ExportPackDialog.h" | ||||||
|  | #include "ui/dialogs/ExportToModListDialog.h" | ||||||
| #include "ui/dialogs/IconPickerDialog.h" | #include "ui/dialogs/IconPickerDialog.h" | ||||||
| #include "ui/dialogs/ImportResourceDialog.h" | #include "ui/dialogs/ImportResourceDialog.h" | ||||||
| #include "ui/dialogs/NewInstanceDialog.h" | #include "ui/dialogs/NewInstanceDialog.h" | ||||||
| @@ -113,17 +109,22 @@ | |||||||
| #include "ui/themes/ThemeManager.h" | #include "ui/themes/ThemeManager.h" | ||||||
| #include "ui/widgets/LabeledToolButton.h" | #include "ui/widgets/LabeledToolButton.h" | ||||||
|  |  | ||||||
|  | #include "minecraft/PackProfile.h" | ||||||
|  | #include "minecraft/VersionFile.h" | ||||||
| #include "minecraft/WorldList.h" | #include "minecraft/WorldList.h" | ||||||
| #include "minecraft/mod/ModFolderModel.h" | #include "minecraft/mod/ModFolderModel.h" | ||||||
|  | #include "minecraft/mod/ResourcePackFolderModel.h" | ||||||
| #include "minecraft/mod/ShaderPackFolderModel.h" | #include "minecraft/mod/ShaderPackFolderModel.h" | ||||||
|  | #include "minecraft/mod/TexturePackFolderModel.h" | ||||||
| #include "minecraft/mod/tasks/LocalResourceParse.h" | #include "minecraft/mod/tasks/LocalResourceParse.h" | ||||||
|  |  | ||||||
|  | #include "modplatform/ModIndex.h" | ||||||
| #include "modplatform/flame/FlameAPI.h" | #include "modplatform/flame/FlameAPI.h" | ||||||
|  | #include "modplatform/flame/FlameModIndex.h" | ||||||
|  |  | ||||||
| #include "KonamiCode.h" | #include "KonamiCode.h" | ||||||
|  |  | ||||||
| #include "InstanceCopyTask.h" | #include "InstanceCopyTask.h" | ||||||
| #include "InstanceImportTask.h" |  | ||||||
|  |  | ||||||
| #include "Json.h" | #include "Json.h" | ||||||
|  |  | ||||||
| @@ -553,71 +554,15 @@ void MainWindow::updateMainToolBar() | |||||||
|     ui->mainToolBar->setVisible(ui->menuBar->isNativeMenuBar() || !APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()); |     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(); |     QMenu* launchMenu = ui->actionLaunchInstance->menu(); | ||||||
|     if (launchMenu) { |     if (launchMenu) | ||||||
|         launchMenu->clear(); |         launchMenu->clear(); | ||||||
|     } else { |     else | ||||||
|         launchMenu = new QMenu(this); |         launchMenu = new QMenu(this); | ||||||
|     } |     if (m_selectedInstance) | ||||||
|     QAction* normalLaunch = launchMenu->addAction(tr("Launch")); |         m_selectedInstance->populateLaunchMenu(launchMenu); | ||||||
|     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); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     ui->actionLaunchInstance->setMenu(launchMenu); |     ui->actionLaunchInstance->setMenu(launchMenu); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -927,7 +872,7 @@ void MainWindow::finalizeInstance(InstancePtr inst) | |||||||
|     } else { |     } else { | ||||||
|         CustomMessageBox::selectable(this, tr("Error"), |         CustomMessageBox::selectable(this, tr("Error"), | ||||||
|                                      tr("The launcher cannot download Minecraft or update instances unless you have at least " |                                      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) |                                      QMessageBox::Warning) | ||||||
|             ->show(); |             ->show(); | ||||||
|     } |     } | ||||||
| @@ -980,6 +925,7 @@ void MainWindow::processURLs(QList<QUrl> urls) | |||||||
|         if (url.scheme().isEmpty()) |         if (url.scheme().isEmpty()) | ||||||
|             url.setScheme("file"); |             url.setScheme("file"); | ||||||
|  |  | ||||||
|  |         ModPlatform::IndexedVersion version; | ||||||
|         QMap<QString, QString> extra_info; |         QMap<QString, QString> extra_info; | ||||||
|         QUrl local_url; |         QUrl local_url; | ||||||
|         if (!url.isLocalFile()) {  // download the remote resource and identify |         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 |                 // format of url curseforge://install?addonId=IDHERE&fileId=IDHERE | ||||||
|                 QUrlQuery query(url); |                 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 addonId = query.allQueryItemValues("addonId")[0]; | ||||||
|                 auto fileId = query.allQueryItemValues("fileId")[0]; |                 auto fileId = query.allQueryItemValues("fileId")[0]; | ||||||
|  |  | ||||||
| @@ -1000,20 +951,19 @@ void MainWindow::processURLs(QList<QUrl> urls) | |||||||
|                 auto api = FlameAPI(); |                 auto api = FlameAPI(); | ||||||
|                 auto job = api.getFile(addonId, fileId, array); |                 auto job = api.getFile(addonId, fileId, array); | ||||||
|  |  | ||||||
|                 QString resource_name; |  | ||||||
|  |  | ||||||
|                 connect(job.get(), &Task::failed, this, |                 connect(job.get(), &Task::failed, this, | ||||||
|                         [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); |                         [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(); |                     qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str(); | ||||||
|                     auto doc = Json::requireDocument(*array); |                     auto doc = Json::requireDocument(*array); | ||||||
|                     auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data"); |                     auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data"); | ||||||
|                     // No way to find out if it's a mod or a modpack before here |                     // 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 |                     // 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 |                     // 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()) { |                     if (!dl_url.isValid()) { | ||||||
|                         CustomMessageBox::selectable( |                         CustomMessageBox::selectable( | ||||||
|                             this, tr("Error"), |                             this, tr("Error"), | ||||||
| @@ -1024,7 +974,6 @@ void MainWindow::processURLs(QList<QUrl> urls) | |||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     QFileInfo dl_file(dl_url.fileName()); |                     QFileInfo dl_file(dl_url.fileName()); | ||||||
|                     resource_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName"); |  | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|                 {  // drop stack |                 {  // drop stack | ||||||
| @@ -1099,7 +1048,7 @@ void MainWindow::processURLs(QList<QUrl> urls) | |||||||
|                 qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName; |                 qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName; | ||||||
|                 break; |                 break; | ||||||
|             case PackedResourceType::Mod: |             case PackedResourceType::Mod: | ||||||
|                 minecraftInst->loaderModList()->installMod(localFileName); |                 minecraftInst->loaderModList()->installMod(localFileName, version); | ||||||
|                 break; |                 break; | ||||||
|             case PackedResourceType::ShaderPack: |             case PackedResourceType::ShaderPack: | ||||||
|                 minecraftInst->shaderPackList()->installResource(localFileName); |                 minecraftInst->shaderPackList()->installResource(localFileName); | ||||||
| @@ -1278,7 +1227,7 @@ void MainWindow::globalSettingsClosed() | |||||||
|     proxymodel->invalidate(); |     proxymodel->invalidate(); | ||||||
|     proxymodel->sort(0); |     proxymodel->sort(0); | ||||||
|     updateMainToolBar(); |     updateMainToolBar(); | ||||||
|     updateToolsMenu(); |     updateLaunchButton(); | ||||||
|     updateThemeMenu(); |     updateThemeMenu(); | ||||||
|     updateStatusCenter(); |     updateStatusCenter(); | ||||||
|     // This needs to be done to prevent UI elements disappearing in the event the config is changed |     // This needs to be done to prevent UI elements disappearing in the event the config is changed | ||||||
| @@ -1408,10 +1357,11 @@ void MainWindow::on_actionDeleteInstance_triggered() | |||||||
|  |  | ||||||
|     if (APPLICATION->instances()->trashInstance(id)) { |     if (APPLICATION->instances()->trashInstance(id)) { | ||||||
|         ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); |         ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); | ||||||
|         return; |     } else { | ||||||
|     } |  | ||||||
|  |  | ||||||
|         APPLICATION->instances()->deleteInstance(id); |         APPLICATION->instances()->deleteInstance(id); | ||||||
|  |     } | ||||||
|  |     APPLICATION->settings()->set("SelectedInstance", QString()); | ||||||
|  |     selectionBad(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::on_actionExportInstanceZip_triggered() | void MainWindow::on_actionExportInstanceZip_triggered() | ||||||
| @@ -1513,20 +1463,6 @@ void MainWindow::activateInstance(InstancePtr instance) | |||||||
|     APPLICATION->launch(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() | void MainWindow::on_actionKillInstance_triggered() | ||||||
| { | { | ||||||
|     if (m_selectedInstance && m_selectedInstance->isRunning()) { |     if (m_selectedInstance && m_selectedInstance->isRunning()) { | ||||||
| @@ -1700,6 +1636,7 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co | |||||||
|     } |     } | ||||||
|     if (m_selectedInstance) { |     if (m_selectedInstance) { | ||||||
|         disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); |         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(); |     QString id = current.data(InstanceList::InstanceIDRole).toString(); | ||||||
|     m_selectedInstance = APPLICATION->instances()->getInstanceById(id); |     m_selectedInstance = APPLICATION->instances()->getInstanceById(id); | ||||||
| @@ -1707,14 +1644,6 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co | |||||||
|         ui->instanceToolBar->setEnabled(true); |         ui->instanceToolBar->setEnabled(true); | ||||||
|         setInstanceActionsEnabled(true); |         setInstanceActionsEnabled(true); | ||||||
|         ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); |         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->actionKillInstance->setEnabled(m_selectedInstance->isRunning()); | ||||||
|         ui->actionExportInstance->setEnabled(m_selectedInstance->canExport()); |         ui->actionExportInstance->setEnabled(m_selectedInstance->canExport()); | ||||||
| @@ -1723,18 +1652,13 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co | |||||||
|         updateStatusCenter(); |         updateStatusCenter(); | ||||||
|         updateInstanceToolIcon(m_selectedInstance->iconKey()); |         updateInstanceToolIcon(m_selectedInstance->iconKey()); | ||||||
|  |  | ||||||
|         updateToolsMenu(); |         updateLaunchButton(); | ||||||
|  |  | ||||||
|         APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id()); |         APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id()); | ||||||
|  |  | ||||||
|         connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); |         connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); | ||||||
|  |         connect(m_selectedInstance.get(), &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance); | ||||||
|     } else { |     } 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()); |         APPLICATION->settings()->set("SelectedInstance", QString()); | ||||||
|         selectionBad(); |         selectionBad(); | ||||||
|         return; |         return; | ||||||
| @@ -1759,11 +1683,12 @@ void MainWindow::selectionBad() | |||||||
| { | { | ||||||
|     // start by reseting everything... |     // start by reseting everything... | ||||||
|     m_selectedInstance = nullptr; |     m_selectedInstance = nullptr; | ||||||
|  |     m_statusLeft->setText(tr("No instance selected")); | ||||||
|  |  | ||||||
|     statusBar()->clearMessage(); |     statusBar()->clearMessage(); | ||||||
|     ui->instanceToolBar->setEnabled(false); |     ui->instanceToolBar->setEnabled(false); | ||||||
|     setInstanceActionsEnabled(false); |     setInstanceActionsEnabled(false); | ||||||
|     updateToolsMenu(); |     updateLaunchButton(); | ||||||
|     renameButton->setText(tr("Rename Instance")); |     renameButton->setText(tr("Rename Instance")); | ||||||
|     updateInstanceToolIcon("grass"); |     updateInstanceToolIcon("grass"); | ||||||
|  |  | ||||||
| @@ -1810,7 +1735,9 @@ void MainWindow::updateStatusCenter() | |||||||
|  |  | ||||||
|     int timePlayed = APPLICATION->instances()->getTotalPlayTime(); |     int timePlayed = APPLICATION->instances()->getTotalPlayTime(); | ||||||
|     if (timePlayed > 0) { |     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) | // "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); |     ui->actionCreateInstanceShortcut->setEnabled(enabled); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::refreshCurrentInstance([[maybe_unused]] bool running) | void MainWindow::refreshCurrentInstance() | ||||||
| { | { | ||||||
|     auto current = view->selectionModel()->currentIndex(); |     auto current = view->selectionModel()->currentIndex(); | ||||||
|     instanceChanged(current, current); |     instanceChanged(current, current); | ||||||
|   | |||||||
| @@ -144,10 +144,6 @@ class MainWindow : public QMainWindow { | |||||||
|  |  | ||||||
|     void on_actionLaunchInstance_triggered(); |     void on_actionLaunchInstance_triggered(); | ||||||
|  |  | ||||||
|     void on_actionLaunchInstanceOffline_triggered(); |  | ||||||
|  |  | ||||||
|     void on_actionLaunchInstanceDemo_triggered(); |  | ||||||
|  |  | ||||||
|     void on_actionKillInstance_triggered(); |     void on_actionKillInstance_triggered(); | ||||||
|  |  | ||||||
|     void on_actionDeleteInstance_triggered(); |     void on_actionDeleteInstance_triggered(); | ||||||
| @@ -155,10 +151,7 @@ class MainWindow : public QMainWindow { | |||||||
|     void deleteGroup(); |     void deleteGroup(); | ||||||
|     void undoTrashInstance(); |     void undoTrashInstance(); | ||||||
|  |  | ||||||
|     inline void on_actionExportInstance_triggered() |     inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); } | ||||||
|     { |  | ||||||
|         on_actionExportInstanceZip_triggered(); |  | ||||||
|     } |  | ||||||
|     void on_actionExportInstanceZip_triggered(); |     void on_actionExportInstanceZip_triggered(); | ||||||
|     void on_actionExportInstanceMrPack_triggered(); |     void on_actionExportInstanceMrPack_triggered(); | ||||||
|     void on_actionExportInstanceFlamePack_triggered(); |     void on_actionExportInstanceFlamePack_triggered(); | ||||||
| @@ -181,7 +174,7 @@ class MainWindow : public QMainWindow { | |||||||
|  |  | ||||||
|     void updateMainToolBar(); |     void updateMainToolBar(); | ||||||
|  |  | ||||||
|     void updateToolsMenu(); |     void updateLaunchButton(); | ||||||
|  |  | ||||||
|     void updateThemeMenu(); |     void updateThemeMenu(); | ||||||
|  |  | ||||||
| @@ -215,7 +208,7 @@ class MainWindow : public QMainWindow { | |||||||
|     void keyReleaseEvent(QKeyEvent* event) override; |     void keyReleaseEvent(QKeyEvent* event) override; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|     void refreshCurrentInstance(bool running); |     void refreshCurrentInstance(); | ||||||
|  |  | ||||||
|    private: |    private: | ||||||
|     void retranslateUi(); |     void retranslateUi(); | ||||||
|   | |||||||
| @@ -440,22 +440,6 @@ | |||||||
|     <string>Ctrl+D</string> |     <string>Ctrl+D</string> | ||||||
|    </property> |    </property> | ||||||
|   </action> |   </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"> |   <action name="actionExportInstance"> | ||||||
|    <property name="icon"> |    <property name="icon"> | ||||||
|     <iconset theme="export"> |     <iconset theme="export"> | ||||||
|   | |||||||
| @@ -44,7 +44,8 @@ | |||||||
| BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods) | BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods) | ||||||
|     : QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(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); |     connect(m_hashing_task.get(), &Task::finished, this, &BlockedModsDialog::hashTaskFinished); | ||||||
|  |  | ||||||
|     ui->setupUi(this); |     ui->setupUi(this); | ||||||
|   | |||||||
| @@ -37,15 +37,21 @@ | |||||||
| ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider) | ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider) | ||||||
|     : QDialog(parent), instance(instance), ui(new Ui::ExportPackDialog), m_provider(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->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) { |     if (m_provider == ModPlatform::ResourceProvider::MODRINTH) { | ||||||
|         ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]); |         setWindowTitle(tr("Export Modrinth Pack")); | ||||||
|         setWindowTitle("Export Modrinth Pack"); |         ui->summary->setText(instance->settings()->get("ExportSummary").toString()); | ||||||
|     } else { |     } else { | ||||||
|         setWindowTitle("Export CurseForge Pack"); |         setWindowTitle(tr("Export CurseForge Pack")); | ||||||
|         ui->version->setText(""); |         ui->summaryLabel->setText(tr("&Author")); | ||||||
|         ui->summaryLabel->setText("Author"); |         ui->summary->setText(instance->settings()->get("ExportAuthor").toString()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // ensure a valid pack is generated |     // ensure a valid pack is generated | ||||||
| @@ -75,20 +81,19 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla | |||||||
|  |  | ||||||
|     MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get()); |     MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get()); | ||||||
|     if (mcInstance) { |     if (mcInstance) { | ||||||
|         mcInstance->loaderModList()->update(); |  | ||||||
|         const QDir index = mcInstance->loaderModList()->indexDir(); |         const QDir index = mcInstance->loaderModList()->indexDir(); | ||||||
|         if (index.exists()) |         if (index.exists()) | ||||||
|             proxy->blockedPaths().insert(root.relativeFilePath(index.absolutePath())); |             proxy->ignoreFilesWithPath().insert(root.relativeFilePath(index.absolutePath())); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     ui->treeView->setModel(proxy); |     ui->files->setModel(proxy); | ||||||
|     ui->treeView->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot()))); |     ui->files->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot()))); | ||||||
|     ui->treeView->sortByColumn(0, Qt::AscendingOrder); |     ui->files->sortByColumn(0, Qt::AscendingOrder); | ||||||
|  |  | ||||||
|     model->setFilter(filter); |     model->setFilter(filter); | ||||||
|     model->setRootPath(instance->gameRoot()); |     model->setRootPath(instance->gameRoot()); | ||||||
|  |  | ||||||
|     QHeaderView* headerView = ui->treeView->header(); |     QHeaderView* headerView = ui->files->header(); | ||||||
|     headerView->setSectionResizeMode(QHeaderView::ResizeToContents); |     headerView->setSectionResizeMode(QHeaderView::ResizeToContents); | ||||||
|     headerView->setSectionResizeMode(0, QHeaderView::Stretch); |     headerView->setSectionResizeMode(0, QHeaderView::Stretch); | ||||||
| } | } | ||||||
| @@ -100,26 +105,41 @@ ExportPackDialog::~ExportPackDialog() | |||||||
|  |  | ||||||
| void ExportPackDialog::done(int result) | void ExportPackDialog::done(int result) | ||||||
| { | { | ||||||
|     if (result == Accepted) { |     auto settings = instance->settings(); | ||||||
|         const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text()); |     settings->set("ExportName", ui->name->text()); | ||||||
|         QString output; |     settings->set("ExportVersion", ui->version->text()); | ||||||
|         if (m_provider == ModPlatform::ResourceProvider::MODRINTH) |     settings->set(m_provider == ModPlatform::ResourceProvider::FLAME ? "ExportAuthor" : "ExportSummary", ui->summary->text()); | ||||||
|             output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), |     settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked()); | ||||||
|                                                   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); |  | ||||||
|  |  | ||||||
|  |     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()) |             if (output.isEmpty()) | ||||||
|                 return; |                 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; |         Task* task; | ||||||
|         if (m_provider == ModPlatform::ResourceProvider::MODRINTH) |         if (m_provider == ModPlatform::ResourceProvider::MODRINTH) { | ||||||
|             task = new ModrinthPackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, |             task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance, | ||||||
|                                               std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); |                                               output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); | ||||||
|         else |         } else { | ||||||
|             task = new FlamePackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, |             task = new FlamePackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance, output, | ||||||
|                                            std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); |                                            std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         connect(task, &Task::failed, |         connect(task, &Task::failed, | ||||||
|                 [this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); |                 [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() | void ExportPackDialog::validate() | ||||||
| { | { | ||||||
|     const bool invalid = |     ui->buttonBox->button(QDialogButtonBox::Ok) | ||||||
|         ui->name->text().isEmpty() || ((m_provider == ModPlatform::ResourceProvider::MODRINTH) && ui->version->text().isEmpty()); |         ->setDisabled(m_provider == ModPlatform::ResourceProvider::MODRINTH && ui->version->text().isEmpty()); | ||||||
|     ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(invalid); |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,12 +7,9 @@ | |||||||
|     <x>0</x> |     <x>0</x> | ||||||
|     <y>0</y> |     <y>0</y> | ||||||
|     <width>650</width> |     <width>650</width> | ||||||
|     <height>413</height> |     <height>510</height> | ||||||
|    </rect> |    </rect> | ||||||
|   </property> |   </property> | ||||||
|   <property name="windowTitle"> |  | ||||||
|    <string>Export Pack</string> |  | ||||||
|   </property> |  | ||||||
|   <property name="sizeGripEnabled"> |   <property name="sizeGripEnabled"> | ||||||
|    <bool>true</bool> |    <bool>true</bool> | ||||||
|   </property> |   </property> | ||||||
| @@ -20,13 +17,16 @@ | |||||||
|    <item> |    <item> | ||||||
|     <widget class="QGroupBox" name="information"> |     <widget class="QGroupBox" name="information"> | ||||||
|      <property name="title"> |      <property name="title"> | ||||||
|       <string>Information</string> |       <string>&Description</string> | ||||||
|      </property> |      </property> | ||||||
|      <layout class="QGridLayout" name="gridLayout"> |      <layout class="QGridLayout" name="gridLayout"> | ||||||
|       <item row="3" column="0"> |       <item row="3" column="0"> | ||||||
|        <widget class="QLabel" name="summaryLabel"> |        <widget class="QLabel" name="summaryLabel"> | ||||||
|         <property name="text"> |         <property name="text"> | ||||||
|          <string>Summary</string> |          <string>&Summary</string> | ||||||
|  |         </property> | ||||||
|  |         <property name="buddy"> | ||||||
|  |          <cstring>summary</cstring> | ||||||
|         </property> |         </property> | ||||||
|        </widget> |        </widget> | ||||||
|       </item> |       </item> | ||||||
| @@ -36,14 +36,20 @@ | |||||||
|       <item row="0" column="0"> |       <item row="0" column="0"> | ||||||
|        <widget class="QLabel" name="nameLabel"> |        <widget class="QLabel" name="nameLabel"> | ||||||
|         <property name="text"> |         <property name="text"> | ||||||
|          <string>Name</string> |          <string>&Name</string> | ||||||
|  |         </property> | ||||||
|  |         <property name="buddy"> | ||||||
|  |          <cstring>name</cstring> | ||||||
|         </property> |         </property> | ||||||
|        </widget> |        </widget> | ||||||
|       </item> |       </item> | ||||||
|       <item row="1" column="0"> |       <item row="1" column="0"> | ||||||
|        <widget class="QLabel" name="versionLabel"> |        <widget class="QLabel" name="versionLabel"> | ||||||
|         <property name="text"> |         <property name="text"> | ||||||
|          <string>Version</string> |          <string>&Version</string> | ||||||
|  |         </property> | ||||||
|  |         <property name="buddy"> | ||||||
|  |          <cstring>version</cstring> | ||||||
|         </property> |         </property> | ||||||
|        </widget> |        </widget> | ||||||
|       </item> |       </item> | ||||||
| @@ -57,19 +63,27 @@ | |||||||
|         </property> |         </property> | ||||||
|        </widget> |        </widget> | ||||||
|       </item> |       </item> | ||||||
|       |  | ||||||
|      </layout> |      </layout> | ||||||
|     </widget> |     </widget> | ||||||
|    </item> |    </item> | ||||||
|  |    <item> | ||||||
|  |     <widget class="QGroupBox" name="options"> | ||||||
|  |      <property name="title"> | ||||||
|  |       <string>&Options</string> | ||||||
|  |      </property> | ||||||
|  |      <layout class="QVBoxLayout" name="verticalLayout"> | ||||||
|       <item> |       <item> | ||||||
|        <widget class="QLabel" name="filesLabel"> |        <widget class="QLabel" name="filesLabel"> | ||||||
|         <property name="text"> |         <property name="text"> | ||||||
|       <string>Files</string> |          <string>&Files</string> | ||||||
|  |         </property> | ||||||
|  |         <property name="buddy"> | ||||||
|  |          <cstring>files</cstring> | ||||||
|         </property> |         </property> | ||||||
|        </widget> |        </widget> | ||||||
|       </item> |       </item> | ||||||
|       <item> |       <item> | ||||||
|     <widget class="QTreeView" name="treeView"> |        <widget class="QTreeView" name="files"> | ||||||
|         <property name="alternatingRowColors"> |         <property name="alternatingRowColors"> | ||||||
|          <bool>true</bool> |          <bool>true</bool> | ||||||
|         </property> |         </property> | ||||||
| @@ -84,6 +98,19 @@ | |||||||
|         </attribute> |         </attribute> | ||||||
|        </widget> |        </widget> | ||||||
|       </item> |       </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> |    <item> | ||||||
|     <widget class="QDialogButtonBox" name="buttonBox"> |     <widget class="QDialogButtonBox" name="buttonBox"> | ||||||
|      <property name="standardButtons"> |      <property name="standardButtons"> | ||||||
| @@ -97,7 +124,8 @@ | |||||||
|   <tabstop>name</tabstop> |   <tabstop>name</tabstop> | ||||||
|   <tabstop>version</tabstop> |   <tabstop>version</tabstop> | ||||||
|   <tabstop>summary</tabstop> |   <tabstop>summary</tabstop> | ||||||
|   <tabstop>treeView</tabstop> |   <tabstop>files</tabstop> | ||||||
|  |   <tabstop>optionalFiles</tabstop> | ||||||
|  </tabstops> |  </tabstops> | ||||||
|  <resources/> |  <resources/> | ||||||
|  <connections> |  <connections> | ||||||
|   | |||||||
| @@ -129,7 +129,9 @@ InstallLoaderDialog::InstallLoaderDialog(std::shared_ptr<PackProfile> profile, c | |||||||
|  |  | ||||||
| QList<BasePage*> InstallLoaderDialog::getPages() | 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), |              new InstallLoaderPage("net.minecraftforge", "forge", tr("Forge"), {}, profile), | ||||||
|              // Fabric |              // Fabric | ||||||
|              new InstallLoaderPage("net.fabricmc.fabric-loader", "fabricmc", tr("Fabric"), Version("1.14"), profile), |              new InstallLoaderPage("net.fabricmc.fabric-loader", "fabricmc", tr("Fabric"), Version("1.14"), profile), | ||||||
|   | |||||||
| @@ -3,10 +3,11 @@ | |||||||
| #include "CustomMessageBox.h" | #include "CustomMessageBox.h" | ||||||
| #include "ProgressDialog.h" | #include "ProgressDialog.h" | ||||||
| #include "ScrollMessageBox.h" | #include "ScrollMessageBox.h" | ||||||
|  | #include "minecraft/mod/tasks/GetModDependenciesTask.h" | ||||||
|  | #include "modplatform/ModIndex.h" | ||||||
|  | #include "modplatform/flame/FlameAPI.h" | ||||||
| #include "ui_ReviewMessageBox.h" | #include "ui_ReviewMessageBox.h" | ||||||
|  |  | ||||||
| #include "FileSystem.h" |  | ||||||
| #include "Json.h" |  | ||||||
| #include "Markdown.h" | #include "Markdown.h" | ||||||
|  |  | ||||||
| #include "tasks/ConcurrentTask.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() }; |     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, | ModUpdateDialog::ModUpdateDialog(QWidget* parent, | ||||||
| @@ -43,7 +44,8 @@ ModUpdateDialog::ModUpdateDialog(QWidget* parent, | |||||||
|     , m_parent(parent) |     , m_parent(parent) | ||||||
|     , m_mod_model(mods) |     , m_mod_model(mods) | ||||||
|     , m_candidates(search_for) |     , 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) |     , m_instance(instance) | ||||||
| { | { | ||||||
|     ReviewMessageBox::setGeometry(0, 0, 800, 600); |     ReviewMessageBox::setGeometry(0, 0, 800, 600); | ||||||
| @@ -126,6 +128,8 @@ void ModUpdateDialog::checkCandidates() | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> selectedVers; | ||||||
|  |  | ||||||
|     // Add found updates for Modrinth |     // Add found updates for Modrinth | ||||||
|     if (m_modrinth_check_task) { |     if (m_modrinth_check_task) { | ||||||
|         auto modrinth_updates = m_modrinth_check_task->getUpdatable(); |         auto modrinth_updates = m_modrinth_check_task->getUpdatable(); | ||||||
| @@ -135,6 +139,7 @@ void ModUpdateDialog::checkCandidates() | |||||||
|             appendMod(updatable); |             appendMod(updatable); | ||||||
|             m_tasks.insert(updatable.name, updatable.download); |             m_tasks.insert(updatable.name, updatable.download); | ||||||
|         } |         } | ||||||
|  |         selectedVers.append(m_modrinth_check_task->getDependencies()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Add found updated for Flame |     // Add found updated for Flame | ||||||
| @@ -146,6 +151,7 @@ void ModUpdateDialog::checkCandidates() | |||||||
|             appendMod(updatable); |             appendMod(updatable); | ||||||
|             m_tasks.insert(updatable.name, updatable.download); |             m_tasks.insert(updatable.name, updatable.download); | ||||||
|         } |         } | ||||||
|  |         selectedVers.append(m_flame_check_task->getDependencies()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Report failed update checking |     // 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 there's no mod to be updated | ||||||
|     if (ui->modTreeWidget->topLevelItemCount() == 0) { |     if (ui->modTreeWidget->topLevelItemCount() == 0) { | ||||||
|         m_no_updates = true; |         m_no_updates = true; | ||||||
| @@ -238,6 +285,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool | |||||||
|         if (skip_rest) |         if (skip_rest) | ||||||
|             continue; |             continue; | ||||||
|  |  | ||||||
|  |         if (candidate->type() == ResourceType::FOLDER) { | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (confirm_rest) { |         if (confirm_rest) { | ||||||
|             addToTmp(candidate, provider_rest); |             addToTmp(candidate, provider_rest); | ||||||
|             should_try_others.insert(candidate->internal_id(), try_others_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); |     auto item_top = new QTreeWidgetItem(ui->modTreeWidget); | ||||||
|     item_top->setCheckState(0, Qt::CheckState::Checked); |     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); |     auto new_version_item = new QTreeWidgetItem(item_top); | ||||||
|     new_version_item->setText(0, tr("New version: %1").arg(info.new_version)); |     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); |     auto changelog_item = new QTreeWidgetItem(item_top); | ||||||
|     changelog_item->setText(0, tr("Changelog of the latest version")); |     changelog_item->setText(0, tr("Changelog of the latest version")); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ class ModUpdateDialog final : public ReviewMessageBox { | |||||||
|  |  | ||||||
|     void checkCandidates(); |     void checkCandidates(); | ||||||
|  |  | ||||||
|     void appendMod(const CheckUpdateTask::UpdatableMod& info); |     void appendMod(const CheckUpdateTask::UpdatableMod& info, QStringList requiredBy = {}); | ||||||
|  |  | ||||||
|     const QList<ResourceDownloadTask::Ptr> getTasks(); |     const QList<ResourceDownloadTask::Ptr> getTasks(); | ||||||
|     auto indexDir() const -> QDir { return m_mod_model->indexDir(); } |     auto indexDir() const -> QDir { return m_mod_model->indexDir(); } | ||||||
|   | |||||||
| @@ -127,35 +127,12 @@ void ResourceDownloadDialog::connectButtons() | |||||||
|  |  | ||||||
| static ModPlatform::ProviderCapabilities ProviderCaps; | 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() | void ResourceDownloadDialog::confirm() | ||||||
| { | { | ||||||
|     auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); |     auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); | ||||||
|     confirm_dialog->retranslateUi(resourcesString()); |     confirm_dialog->retranslateUi(resourcesString()); | ||||||
|  |  | ||||||
|  |     QHash<QString, QStringList> getRequiredBy; | ||||||
|     if (auto task = getModDependenciesTask(); task) { |     if (auto task = getModDependenciesTask(); task) { | ||||||
|         connect(task.get(), &Task::failed, this, |         connect(task.get(), &Task::failed, this, | ||||||
|                 [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); |                 [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); | ||||||
| @@ -180,6 +157,7 @@ void ResourceDownloadDialog::confirm() | |||||||
|         } else { |         } else { | ||||||
|             for (auto dep : task->getDependecies()) |             for (auto dep : task->getDependecies()) | ||||||
|                 addResource(dep->pack, dep->version); |                 addResource(dep->pack, dep->version); | ||||||
|  |             getRequiredBy = task->getRequiredBy(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -189,7 +167,8 @@ void ResourceDownloadDialog::confirm() | |||||||
|     }); |     }); | ||||||
|     for (auto& task : selected) { |     for (auto& task : selected) { | ||||||
|         confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(), |         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()) { |     if (confirm_dialog->exec()) { | ||||||
| @@ -279,7 +258,7 @@ QList<BasePage*> ModDownloadDialog::getPages() | |||||||
| { | { | ||||||
|     QList<BasePage*> pages; |     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)) |     if (ModrinthAPI::validateModLoaders(loaders)) | ||||||
|         pages.append(ModrinthModPage::create(this, *m_instance)); |         pages.append(ModrinthModPage::create(this, *m_instance)); | ||||||
| @@ -370,6 +349,8 @@ QList<BasePage*> ShaderPackDownloadDialog::getPages() | |||||||
| { | { | ||||||
|     QList<BasePage*> pages; |     QList<BasePage*> pages; | ||||||
|     pages.append(ModrinthShaderPackPage::create(this, *m_instance)); |     pages.append(ModrinthShaderPackPage::create(this, *m_instance)); | ||||||
|  |     if (APPLICATION->capabilities() & Application::SupportsFlame) | ||||||
|  |         pages.append(FlameShaderPackPage::create(this, *m_instance)); | ||||||
|     return pages; |     return pages; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -35,12 +35,6 @@ | |||||||
|           <verstretch>0</verstretch> |           <verstretch>0</verstretch> | ||||||
|          </sizepolicy> |          </sizepolicy> | ||||||
|         </property> |         </property> | ||||||
|         <property name="maximumSize"> |  | ||||||
|          <size> |  | ||||||
|           <width>28</width> |  | ||||||
|           <height>16777215</height> |  | ||||||
|          </size> |  | ||||||
|         </property> |  | ||||||
|         <property name="text"> |         <property name="text"> | ||||||
|          <string>Browse</string> |          <string>Browse</string> | ||||||
|         </property> |         </property> | ||||||
|   | |||||||
| @@ -64,7 +64,8 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new | |||||||
|     ui->setupUi(this); |     ui->setupUi(this); | ||||||
|     ui->listView->setEmptyString( |     ui->listView->setEmptyString( | ||||||
|         tr("Welcome!\n" |         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->setEmptyMode(VersionListView::String); | ||||||
|     ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); |     ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); | ||||||
|  |  | ||||||
| @@ -85,6 +86,8 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new | |||||||
|     connect(selectionModel, &QItemSelectionModel::selectionChanged, |     connect(selectionModel, &QItemSelectionModel::selectionChanged, | ||||||
|             [this]([[maybe_unused]] const QItemSelection& sel, [[maybe_unused]] const QItemSelection& dsel) { updateButtonStates(); }); |             [this]([[maybe_unused]] const QItemSelection& sel, [[maybe_unused]] const QItemSelection& dsel) { updateButtonStates(); }); | ||||||
|     connect(ui->listView, &VersionListView::customContextMenuRequested, this, &AccountListPage::ShowContextMenu); |     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::listChanged, this, &AccountListPage::listChanged); | ||||||
|     connect(m_accounts.get(), &AccountList::listActivityChanged, 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; |     auto sysMiB = Sys::getSystemRam() / Sys::mebibyte; | ||||||
|     unsigned int maxMem = ui->maxMemSpinBox->value(); |     unsigned int maxMem = ui->maxMemSpinBox->value(); | ||||||
|  |     unsigned int minMem = ui->minMemSpinBox->value(); | ||||||
|  |  | ||||||
|     QString iconName; |     QString iconName; | ||||||
|  |  | ||||||
| @@ -194,6 +195,9 @@ void JavaPage::updateThresholds() | |||||||
|     } else if (maxMem > (sysMiB * 0.9)) { |     } else if (maxMem > (sysMiB * 0.9)) { | ||||||
|         iconName = "status-yellow"; |         iconName = "status-yellow"; | ||||||
|         ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity.")); |         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 { |     } else { | ||||||
|         iconName = "status-good"; |         iconName = "status-good"; | ||||||
|         ui->labelMaxMemIcon->setToolTip(""); |         ui->labelMaxMemIcon->setToolTip(""); | ||||||
|   | |||||||
| @@ -189,6 +189,9 @@ void LauncherPage::applySettings() | |||||||
|  |  | ||||||
|     s->set("MenuBarInsteadOfToolBar", ui->preferMenuBarCheckBox->isChecked()); |     s->set("MenuBarInsteadOfToolBar", ui->preferMenuBarCheckBox->isChecked()); | ||||||
|  |  | ||||||
|  |     s->set("NumberOfConcurrentTasks", ui->numberOfConcurrentTasksSpinBox->value()); | ||||||
|  |     s->set("NumberOfConcurrentDownloads", ui->numberOfConcurrentDownloadsSpinBox->value()); | ||||||
|  |  | ||||||
|     // Console settings |     // Console settings | ||||||
|     s->set("ShowConsole", ui->showConsoleCheck->isChecked()); |     s->set("ShowConsole", ui->showConsoleCheck->isChecked()); | ||||||
|     s->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked()); |     s->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked()); | ||||||
| @@ -236,6 +239,9 @@ void LauncherPage::loadSettings() | |||||||
| #endif | #endif | ||||||
|     ui->preferMenuBarCheckBox->setChecked(s->get("MenuBarInsteadOfToolBar").toBool()); |     ui->preferMenuBarCheckBox->setChecked(s->get("MenuBarInsteadOfToolBar").toBool()); | ||||||
|  |  | ||||||
|  |     ui->numberOfConcurrentTasksSpinBox->setValue(s->get("NumberOfConcurrentTasks").toInt()); | ||||||
|  |     ui->numberOfConcurrentDownloadsSpinBox->setValue(s->get("NumberOfConcurrentDownloads").toInt()); | ||||||
|  |  | ||||||
|     // Console settings |     // Console settings | ||||||
|     ui->showConsoleCheck->setChecked(s->get("ShowConsole").toBool()); |     ui->showConsoleCheck->setChecked(s->get("ShowConsole").toBool()); | ||||||
|     ui->autoCloseConsoleCheck->setChecked(s->get("AutoCloseConsole").toBool()); |     ui->autoCloseConsoleCheck->setChecked(s->get("AutoCloseConsole").toBool()); | ||||||
|   | |||||||
| @@ -189,6 +189,43 @@ | |||||||
|          </layout> |          </layout> | ||||||
|         </widget> |         </widget> | ||||||
|        </item> |        </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> |        <item> | ||||||
|         <spacer name="verticalSpacer_2"> |         <spacer name="verticalSpacer_2"> | ||||||
|          <property name="orientation"> |          <property name="orientation"> | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ | |||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -35,6 +34,7 @@ | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| #include "MinecraftPage.h" | #include "MinecraftPage.h" | ||||||
|  | #include "BuildConfig.h" | ||||||
| #include "ui_MinecraftPage.h" | #include "ui_MinecraftPage.h" | ||||||
|  |  | ||||||
| #include <QDir> | #include <QDir> | ||||||
| @@ -44,9 +44,15 @@ | |||||||
| #include "Application.h" | #include "Application.h" | ||||||
| #include "settings/SettingsObject.h" | #include "settings/SettingsObject.h" | ||||||
|  |  | ||||||
|  | #ifdef Q_OS_LINUX | ||||||
|  | #include "MangoHud.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| MinecraftPage::MinecraftPage(QWidget* parent) : QWidget(parent), ui(new Ui::MinecraftPage) | MinecraftPage::MinecraftPage(QWidget* parent) : QWidget(parent), ui(new Ui::MinecraftPage) | ||||||
| { | { | ||||||
|     ui->setupUi(this); |     ui->setupUi(this); | ||||||
|  |     connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &MinecraftPage::onUseNativeGLFWChanged); | ||||||
|  |     connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &MinecraftPage::onUseNativeOpenALChanged); | ||||||
|     loadSettings(); |     loadSettings(); | ||||||
|     updateCheckboxStuff(); |     updateCheckboxStuff(); | ||||||
| } | } | ||||||
| @@ -74,6 +80,16 @@ void MinecraftPage::on_maximizedCheckBox_clicked(bool checked) | |||||||
|     updateCheckboxStuff(); |     updateCheckboxStuff(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void MinecraftPage::onUseNativeGLFWChanged(bool checked) | ||||||
|  | { | ||||||
|  |     ui->lineEditGLFWPath->setEnabled(checked); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MinecraftPage::onUseNativeOpenALChanged(bool checked) | ||||||
|  | { | ||||||
|  |     ui->lineEditOpenALPath->setEnabled(checked); | ||||||
|  | } | ||||||
|  |  | ||||||
| void MinecraftPage::applySettings() | void MinecraftPage::applySettings() | ||||||
| { | { | ||||||
|     auto s = APPLICATION->settings(); |     auto s = APPLICATION->settings(); | ||||||
| @@ -84,8 +100,10 @@ void MinecraftPage::applySettings() | |||||||
|     s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); |     s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); | ||||||
|  |  | ||||||
|     // Native library workarounds |     // Native library workarounds | ||||||
|     s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked()); |  | ||||||
|     s->set("UseNativeGLFW", ui->useNativeGLFWCheck->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 |     // Peformance related options | ||||||
|     s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked()); |     s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked()); | ||||||
| @@ -96,13 +114,11 @@ void MinecraftPage::applySettings() | |||||||
|     s->set("ShowGameTime", ui->showGameTime->isChecked()); |     s->set("ShowGameTime", ui->showGameTime->isChecked()); | ||||||
|     s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked()); |     s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked()); | ||||||
|     s->set("RecordGameTime", ui->recordGameTime->isChecked()); |     s->set("RecordGameTime", ui->recordGameTime->isChecked()); | ||||||
|  |     s->set("ShowGameTimeWithoutDays", ui->showGameTimeWithoutDays->isChecked()); | ||||||
|  |  | ||||||
|     // Miscellaneous |     // Miscellaneous | ||||||
|     s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked()); |     s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked()); | ||||||
|     s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked()); |     s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked()); | ||||||
|  |  | ||||||
|     // Mod loader settings |  | ||||||
|     s->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked()); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void MinecraftPage::loadSettings() | void MinecraftPage::loadSettings() | ||||||
| @@ -114,8 +130,20 @@ void MinecraftPage::loadSettings() | |||||||
|     ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt()); |     ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt()); | ||||||
|     ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt()); |     ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt()); | ||||||
|  |  | ||||||
|     ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool()); |  | ||||||
|     ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").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->enableFeralGamemodeCheck->setChecked(s->get("EnableFeralGamemode").toBool()); | ||||||
|     ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool()); |     ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool()); | ||||||
| @@ -138,11 +166,10 @@ void MinecraftPage::loadSettings() | |||||||
|     ui->showGameTime->setChecked(s->get("ShowGameTime").toBool()); |     ui->showGameTime->setChecked(s->get("ShowGameTime").toBool()); | ||||||
|     ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool()); |     ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool()); | ||||||
|     ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool()); |     ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool()); | ||||||
|  |     ui->showGameTimeWithoutDays->setChecked(s->get("ShowGameTimeWithoutDays").toBool()); | ||||||
|  |  | ||||||
|     ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool()); |     ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool()); | ||||||
|     ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool()); |     ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool()); | ||||||
|  |  | ||||||
|     ui->disableQuiltBeaconCheckBox->setChecked(s->get("DisableQuiltBeacon").toBool()); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void MinecraftPage::retranslate() | void MinecraftPage::retranslate() | ||||||
|   | |||||||
| @@ -70,6 +70,9 @@ class MinecraftPage : public QWidget, public BasePage { | |||||||
|    private slots: |    private slots: | ||||||
|     void on_maximizedCheckBox_clicked(bool checked); |     void on_maximizedCheckBox_clicked(bool checked); | ||||||
|  |  | ||||||
|  |     void onUseNativeGLFWChanged(bool checked); | ||||||
|  |     void onUseNativeOpenALChanged(bool checked); | ||||||
|  |  | ||||||
|    private: |    private: | ||||||
|     Ui::MinecraftPage* ui; |     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