Merge branch 'develop' into feature/java-downloader
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
		
							
								
								
									
										32
									
								
								.github/workflows/backport.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								.github/workflows/backport.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| name: Backport | ||||
| on: | ||||
|   pull_request_target: | ||||
|     types: [closed, labeled] | ||||
|  | ||||
| # WARNING: | ||||
| # When extending this action, be aware that $GITHUB_TOKEN allows write access to | ||||
| # the GitHub repository. This means that it should not evaluate user input in a | ||||
| # way that allows code injection. | ||||
|  | ||||
| permissions: | ||||
|   contents: read | ||||
|  | ||||
| jobs: | ||||
|   backport: | ||||
|     permissions: | ||||
|       contents: write # for korthout/backport-action to create branch | ||||
|       pull-requests: write # for korthout/backport-action to create PR to backport | ||||
|     name: Backport Pull Request | ||||
|     if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name)) | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|         with: | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
|       - name: Create backport PRs | ||||
|         uses: korthout/backport-action@v1.3.1 | ||||
|         with: | ||||
|           # Config README: https://github.com/korthout/backport-action#backport-action | ||||
|           pull_description: |- | ||||
|             Bot-based backport to `${target_branch}`, triggered by a label in #${pull_number}. | ||||
|  | ||||
							
								
								
									
										40
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -264,23 +264,23 @@ jobs: | ||||
|       - name: Configure CMake (macOS) | ||||
|         if: runner.os == 'macOS' && matrix.qt_ver == 6 | ||||
|         run: | | ||||
|           cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja | ||||
|           cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja | ||||
|  | ||||
|       - name: Configure CMake (macOS-Legacy) | ||||
|         if: runner.os == 'macOS' && matrix.qt_ver == 5 | ||||
|         run: | | ||||
|           cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja | ||||
|           cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja | ||||
|  | ||||
|       - name: Configure CMake (Windows MinGW-w64) | ||||
|         if: runner.os == 'Windows' && matrix.msystem != '' | ||||
|         shell: msys2 {0} | ||||
|         run: | | ||||
|           cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -G Ninja | ||||
|           cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -G Ninja | ||||
|  | ||||
|       - name: Configure CMake (Windows MSVC) | ||||
|         if: runner.os == 'Windows' && matrix.msystem == '' | ||||
|         run: | | ||||
|           cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON | ||||
|           cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON | ||||
|           # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) | ||||
|           if ("${{ env.CCACHE_VAR }}") | ||||
|           { | ||||
| @@ -295,7 +295,7 @@ jobs: | ||||
|       - name: Configure CMake (Linux) | ||||
|         if: runner.os == 'Linux' | ||||
|         run: | | ||||
|           cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=Linux -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja | ||||
|           cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja | ||||
|  | ||||
|       ## | ||||
|       # BUILD | ||||
| @@ -586,33 +586,3 @@ jobs: | ||||
|         with: | ||||
|           bundle: "Prism Launcher.flatpak" | ||||
|           manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml  | ||||
|  | ||||
|   nix: | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       matrix: | ||||
|         package: | ||||
|           - prismlauncher | ||||
|           - prismlauncher-qt5 | ||||
|     steps: | ||||
|       - name: Clone repository | ||||
|         if: inputs.build_type == 'Debug' | ||||
|         uses: actions/checkout@v3 | ||||
|         with: | ||||
|           submodules: 'true' | ||||
|       - name: Install nix | ||||
|         if: inputs.build_type == 'Debug' | ||||
|         uses: cachix/install-nix-action@v22 | ||||
|         with: | ||||
|           install_url: https://nixos.org/nix/install | ||||
|           extra_nix_config: | | ||||
|             auto-optimise-store = true | ||||
|             experimental-features = nix-command flakes | ||||
|       - uses: cachix/cachix-action@v12 | ||||
|         if: inputs.build_type == 'Debug' | ||||
|         with: | ||||
|           name: prismlauncher | ||||
|           authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' | ||||
|       - name: Build | ||||
|         if: inputs.build_type == 'Debug' | ||||
|         run: nix build .#${{ matrix.package }} --print-build-logs | ||||
|   | ||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -19,3 +19,6 @@ | ||||
| [submodule "libraries/cmark"] | ||||
| 	path = libraries/cmark | ||||
| 	url = https://github.com/commonmark/cmark.git | ||||
| [submodule "flatpak/shared-modules"] | ||||
| 	path = flatpak/shared-modules | ||||
| 	url = https://github.com/flathub/shared-modules.git | ||||
|   | ||||
| @@ -178,7 +178,7 @@ set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}. | ||||
| set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},0,0") | ||||
|  | ||||
| # Build platform. | ||||
| set(Launcher_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.") | ||||
| set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.") | ||||
|  | ||||
| # Channel list URL | ||||
| set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.") | ||||
|   | ||||
| @@ -19,7 +19,7 @@ In an effort to ensure that the code you contribute is actually compatible with | ||||
|  | ||||
| This can be done by appending `-s` to your `git commit` call, or by manually appending the following text to your commit message: | ||||
|  | ||||
| ``` | ||||
| ```text | ||||
| <commit message> | ||||
|  | ||||
| Signed-off-by: Author name <Author email> | ||||
| @@ -27,7 +27,7 @@ Signed-off-by: Author name <Author email> | ||||
|  | ||||
| By signing off your work, you agree to the terms below: | ||||
|  | ||||
| ``` | ||||
| ```text | ||||
| Developer's Certificate of Origin 1.1 | ||||
|  | ||||
| By making a contribution to this project, I certify that: | ||||
| @@ -61,3 +61,9 @@ As a bonus, you can also [cryptographically sign your commits][gh-signing-commit | ||||
|  | ||||
| [gh-signing-commits]: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits | ||||
| [gh-vigilant-mode]: https://docs.github.com/en/authentication/managing-commit-signature-verification/displaying-verification-statuses-for-all-of-your-commits | ||||
|  | ||||
| ## Backporting to Release Branches | ||||
|  | ||||
| We use [automated backports](https://github.com/PrismLauncher/PrismLauncher/blob/develop/.github/workflows/backport.yml) to merge specific contributions from develop into `release` branches. | ||||
|  | ||||
| This is done when pull requests are merged and have labels such as `backport release-7.x` - which should be added along with the milestone for the release. | ||||
|   | ||||
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							| @@ -42,7 +42,7 @@ Feel free to create a GitHub issue if you find a bug or want to suggest a new fe | ||||
|  | ||||
| - **Our Matrix space:** | ||||
|  | ||||
| [](https://prismlauncher.org/matrix) | ||||
| [](https://prismlauncher.org/matrix) | ||||
|  | ||||
| - **Our Subreddit:** | ||||
|  | ||||
| @@ -50,7 +50,7 @@ Feel free to create a GitHub issue if you find a bug or want to suggest a new fe | ||||
|  | ||||
| ## Translations | ||||
|  | ||||
| The translation effort for PrismLauncher is hosted on [Weblate](https://hosted.weblate.org/projects/prismlauncher/launcher/) and information about translating Prism Launcher is available at <https://github.com/PrismLauncher/Translations> | ||||
| The translation effort for Prism Launcher is hosted on [Weblate](https://hosted.weblate.org/projects/prismlauncher/launcher/) and information about translating Prism Launcher is available at <https://github.com/PrismLauncher/Translations> | ||||
|  | ||||
| ## Building | ||||
|  | ||||
| @@ -82,14 +82,16 @@ Thanks to the awesome people over at [MacStadium](https://www.macstadium.com/), | ||||
|  | ||||
| ## Forking/Redistributing/Custom builds policy | ||||
|  | ||||
| We don't care what you do with your fork/custom build as long as you follow the terms of the [license](LICENSE) (this is a legal responsibility), and if you made code changes rather than just packaging a custom build, please do the following as a basic courtesy: | ||||
| You are free to fork, redistribute and provide custom builds as long as you follow the terms of the [license](LICENSE) (this is a legal responsibility), and if you made code changes rather than just packaging a custom build, please do the following as a basic courtesy: | ||||
|  | ||||
| - Make it clear that your fork is not PrismLauncher and is not endorsed by or affiliated with the PrismLauncher project (<https://prismlauncher.org>). | ||||
| - Go through [CMakeLists.txt](CMakeLists.txt) and change PrismLauncher's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled). | ||||
| - Make it clear that your fork is not Prism Launcher and is not endorsed by or affiliated with the Prism Launcher project (<https://prismlauncher.org>). | ||||
| - Go through [CMakeLists.txt](CMakeLists.txt) and change Prism Launcher's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled). | ||||
|  | ||||
| If you have any questions or want any clarification on the above conditions please make an issue and ask us. | ||||
|  | ||||
| Be aware that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions: | ||||
| If you are just building Prism Launcher for your distribution, please make sure to set the `Launcher_BUILD_PLATFORM` to a slug representing your distribution. Examples are `archlinux`, `fedora` and `nixpkgs`. | ||||
|  | ||||
| Note that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions: | ||||
|  | ||||
| - [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use) | ||||
| - [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions) | ||||
|   | ||||
| @@ -65,7 +65,7 @@ Config::Config() | ||||
|     MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@"; | ||||
|     MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@"; | ||||
|  | ||||
|     if (BUILD_PLATFORM == "macOS" && !MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty()) | ||||
|     if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty()) | ||||
|     { | ||||
|         UPDATER_ENABLED = true; | ||||
|     } | ||||
|   | ||||
| @@ -68,7 +68,7 @@ class Config { | ||||
|  | ||||
|     bool UPDATER_ENABLED = false; | ||||
|  | ||||
|     /// A short string identifying this build's platform. For example, "lin64" or "win32". | ||||
|     /// A short string identifying this build's platform or distribution. | ||||
|     QString BUILD_PLATFORM; | ||||
|  | ||||
|     /// A string containing the build timestamp | ||||
|   | ||||
							
								
								
									
										24
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										24
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							| @@ -21,11 +21,11 @@ | ||||
|         "nixpkgs-lib": "nixpkgs-lib" | ||||
|       }, | ||||
|       "locked": { | ||||
|         "lastModified": 1688254665, | ||||
|         "narHash": "sha256-8FHEgBrr7gYNiS/NzCxIO3m4hvtLRW9YY1nYo1ivm3o=", | ||||
|         "lastModified": 1688466019, | ||||
|         "narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=", | ||||
|         "owner": "hercules-ci", | ||||
|         "repo": "flake-parts", | ||||
|         "rev": "267149c58a14d15f7f81b4d737308421de9d7152", | ||||
|         "rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
| @@ -76,11 +76,11 @@ | ||||
|     "libnbtplusplus": { | ||||
|       "flake": false, | ||||
|       "locked": { | ||||
|         "lastModified": 1650031308, | ||||
|         "narHash": "sha256-TvVOjkUobYJD9itQYueELJX3wmecvEdCbJ0FinW2mL4=", | ||||
|         "lastModified": 1690036783, | ||||
|         "narHash": "sha256-A5kTgICnx+Qdq3Fir/bKTfdTt/T1NQP2SC+nhN1ENug=", | ||||
|         "owner": "PrismLauncher", | ||||
|         "repo": "libnbtplusplus", | ||||
|         "rev": "2203af7eeb48c45398139b583615134efd8d407f", | ||||
|         "rev": "a5e8fd52b8bf4ab5d5bcc042b2a247867589985f", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
| @@ -91,11 +91,11 @@ | ||||
|     }, | ||||
|     "nixpkgs": { | ||||
|       "locked": { | ||||
|         "lastModified": 1688221086, | ||||
|         "narHash": "sha256-cdW6qUL71cNWhHCpMPOJjlw0wzSRP0pVlRn2vqX/VVg=", | ||||
|         "lastModified": 1690026219, | ||||
|         "narHash": "sha256-oOduRk/kzQxOBknZXTLSEYd7tk+GoKvr8wV6Ab+t4AU=", | ||||
|         "owner": "nixos", | ||||
|         "repo": "nixpkgs", | ||||
|         "rev": "cd99c2b3c9f160cd004318e0697f90bbd5960825", | ||||
|         "rev": "f465da166263bc0d4b39dfd4ca28b777c92d4b73", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
| @@ -138,11 +138,11 @@ | ||||
|         ] | ||||
|       }, | ||||
|       "locked": { | ||||
|         "lastModified": 1688386108, | ||||
|         "narHash": "sha256-Vffto9QaVonzYAcPlAzd0soqWYpPpKk60dfNLSIXcFA=", | ||||
|         "lastModified": 1689668210, | ||||
|         "narHash": "sha256-XAATwDkaUxH958yXLs1lcEOmU6pSEIkatY3qjqk8X0E=", | ||||
|         "owner": "cachix", | ||||
|         "repo": "pre-commit-hooks.nix", | ||||
|         "rev": "42587d3414d1747999a5f71e92a83cf6547b62da", | ||||
|         "rev": "eb433bff05b285258be76513add6f6c57b441775", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
|   | ||||
							
								
								
									
										22
									
								
								flatpak/libdecor.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								flatpak/libdecor.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| { | ||||
|   "name": "libdecor", | ||||
|   "buildsystem": "meson", | ||||
|   "config-opts": [ | ||||
|     "-Ddemo=false" | ||||
|   ], | ||||
|   "sources": [ | ||||
|     { | ||||
|       "type": "git", | ||||
|       "url": "https://gitlab.freedesktop.org/libdecor/libdecor.git", | ||||
|       "commit": "73260393a97291c887e1074ab7f318e031be0ac6" | ||||
|     }, | ||||
|     { | ||||
|       "type": "patch", | ||||
|       "path": "patches/weird_libdecor.patch" | ||||
|     } | ||||
|   ], | ||||
|   "cleanup": [ | ||||
|     "/include", | ||||
|     "/lib/pkgconfig" | ||||
|   ] | ||||
| } | ||||
| @@ -5,13 +5,6 @@ sdk: org.kde.Sdk | ||||
| sdk-extensions: | ||||
|   - org.freedesktop.Sdk.Extension.openjdk17 | ||||
|   - org.freedesktop.Sdk.Extension.openjdk8 | ||||
| add-extensions: | ||||
|   com.valvesoftware.Steam.Utility.gamescope: | ||||
|     version: stable | ||||
|     add-ld-path: lib | ||||
|     no-autodownload: true | ||||
|     autodelete: false | ||||
|     directory: utils/gamescope | ||||
|  | ||||
| command: prismlauncher | ||||
| finish-args: | ||||
| @@ -26,21 +19,31 @@ finish-args: | ||||
|     # Mod drag&drop | ||||
|   - --filesystem=xdg-download:ro | ||||
|  | ||||
| cleanup: | ||||
|   - /lib/libGLU* | ||||
|  | ||||
| modules: | ||||
|   # Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31) | ||||
|   - shared-modules/libusb/libusb.json | ||||
|  | ||||
|   # Needed for proper Wayland support | ||||
|   - libdecor.json | ||||
|  | ||||
|   - name: prismlauncher | ||||
|     buildsystem: cmake-ninja | ||||
|     builddir: true | ||||
|     config-opts: | ||||
|       - -DLauncher_BUILD_PLATFORM=flatpak | ||||
|       - -DCMAKE_BUILD_TYPE=Debug | ||||
|       - -DCMAKE_BUILD_TYPE=RelWithDebInfo | ||||
|       - -DLauncher_QT_VERSION_MAJOR=5 | ||||
|     build-options: | ||||
|       env: | ||||
|         JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17 | ||||
|         JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac | ||||
|     sources: | ||||
|         - type: dir | ||||
|           path: ../ | ||||
|     builddir: true | ||||
|       - type: dir | ||||
|         path: ../ | ||||
|  | ||||
|   - name: openjdk | ||||
|     buildsystem: simple | ||||
|     build-commands: | ||||
| @@ -49,14 +52,45 @@ modules: | ||||
|       - mv /app/jre /app/jdk/17 | ||||
|       - /usr/lib/sdk/openjdk8/install.sh | ||||
|       - mv /app/jre /app/jdk/8 | ||||
|     cleanup: [/jre] | ||||
|     cleanup: | ||||
|       - /jre | ||||
|  | ||||
|   - name: glfw | ||||
|     buildsystem: cmake-ninja | ||||
|     config-opts: | ||||
|       - -DCMAKE_BUILD_TYPE=RelWithDebInfo | ||||
|       - -DBUILD_SHARED_LIBS:BOOL=ON | ||||
|       - -DGLFW_USE_WAYLAND=ON | ||||
|     sources: | ||||
|       - type: git | ||||
|         url: https://github.com/glfw/glfw.git | ||||
|         commit: 3fa2360720eeba1964df3c0ecf4b5df8648a8e52 | ||||
|       - type: patch | ||||
|         path: patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch | ||||
|       - type: patch | ||||
|         path: patches/0005-Add-warning-about-being-an-unofficial-patch.patch | ||||
|       - type: patch | ||||
|         path: patches/0007-Platform-Prefer-Wayland-over-X11.patch | ||||
|     cleanup: | ||||
|       - /include | ||||
|       - /lib/cmake | ||||
|       - /lib/pkgconfig | ||||
|  | ||||
|   - name: xrandr | ||||
|     buildsystem: autotools | ||||
|     sources: | ||||
|       - type: archive | ||||
|         url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.1.tar.xz | ||||
|         sha256: 7bc76daf9d72f8aff885efad04ce06b90488a1a169d118dea8a2b661832e8762 | ||||
|     cleanup: [/share/man, /bin/xkeystone] | ||||
|         url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.2.tar.xz | ||||
|         sha256: c8bee4790d9058bacc4b6246456c58021db58a87ddda1a9d0139bf5f18f1f240 | ||||
|         x-checker-data: | ||||
|           type: anitya | ||||
|           project-id: 14957 | ||||
|           stable-only: true | ||||
|           url-template: https://xorg.freedesktop.org/archive/individual/app/xrandr-$version.tar.xz | ||||
|     cleanup: | ||||
|       - /share/man | ||||
|       - /bin/xkeystone | ||||
|  | ||||
|   - name: gamemode | ||||
|     buildsystem: meson | ||||
|     config-opts: | ||||
| @@ -67,19 +101,56 @@ modules: | ||||
|       # post-install is running inside the build dir, we need it from the source though | ||||
|       - install -Dm755 ../data/gamemoderun -t /app/bin | ||||
|     sources: | ||||
|       - type: git | ||||
|         url: https://github.com/FeralInteractive/gamemode | ||||
|         tag: "1.7" | ||||
|         commit: 4dc99dff76218718763a6b07fc1900fa6d1dafd9 | ||||
|       - type: archive | ||||
|         archive-type: tar-gzip | ||||
|         url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.7 | ||||
|         sha256: 57ce73ba605d1cf12f8d13725006a895182308d93eba0f69f285648449641803 | ||||
|         x-checker-data: | ||||
|           type: json | ||||
|           url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest | ||||
|           version-query: .tag_name | ||||
|           url-query: .tarball_url | ||||
|           timestamp-query: .published_at | ||||
|     cleanup: | ||||
|       - /include | ||||
|       - /lib/pkgconfig | ||||
|       - /lib/libgamemodeauto.a | ||||
|  | ||||
|   - name: glxinfo | ||||
|     buildsystem: meson | ||||
|     config-opts: | ||||
|       - --bindir=/app/mesa-demos | ||||
|       - -Degl=disabled | ||||
|       - -Dglut=disabled | ||||
|       - -Dosmesa=disabled | ||||
|       - -Dvulkan=disabled | ||||
|       - -Dwayland=disabled | ||||
|     post-install: | ||||
|       - mv -v /app/mesa-demos/glxinfo /app/bin | ||||
|     sources: | ||||
|       - type: archive | ||||
|         url: https://archive.mesa3d.org/demos/mesa-demos-9.0.0.tar.xz | ||||
|         sha256: 3046a3d26a7b051af7ebdd257a5f23bfeb160cad6ed952329cdff1e9f1ed496b | ||||
|         x-checker-data: | ||||
|           type: anitya | ||||
|           project-id: 16781 | ||||
|           stable-only: true | ||||
|           url-template: https://archive.mesa3d.org/demos/mesa-demos-$version.tar.xz | ||||
|     cleanup: | ||||
|       - /include | ||||
|       - /mesa-demos | ||||
|       - /share | ||||
|     modules: | ||||
|       - shared-modules/glu/glu-9.json | ||||
|  | ||||
|   - name: enhance | ||||
|     buildsystem: simple | ||||
|     build-commands: | ||||
|       - mkdir -p /app/utils/gamescope | ||||
|       - install -Dm755 prime-run /app/bin/prime-run | ||||
|       - mv /app/bin/prismlauncher /app/bin/prismrun | ||||
|       - install -Dm755 prismlauncher /app/bin/prismlauncher | ||||
|     sources: | ||||
|       - type: file | ||||
|         path: ../flatpak/prime-run | ||||
|         path: prime-run | ||||
|       - type: file | ||||
|         path: ../flatpak/prismlauncher | ||||
|         path: prismlauncher | ||||
|   | ||||
| @@ -0,0 +1,24 @@ | ||||
| diff --git a/src/wl_window.c b/src/wl_window.c | ||||
| index 52d3b9eb..4ac4eb5d 100644 | ||||
| --- a/src/wl_window.c | ||||
| +++ b/src/wl_window.c | ||||
| @@ -2117,8 +2117,7 @@ void _glfwSetWindowTitleWayland(_GLFWwindow* window, const char* title) | ||||
|  void _glfwSetWindowIconWayland(_GLFWwindow* window, | ||||
|                                 int count, const GLFWimage* images) | ||||
|  { | ||||
| -    _glfwInputError(GLFW_FEATURE_UNAVAILABLE, | ||||
| -                    "Wayland: The platform does not support setting the window icon"); | ||||
| +    fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the window icon\n"); | ||||
|  } | ||||
|   | ||||
|  void _glfwGetWindowPosWayland(_GLFWwindow* window, int* xpos, int* ypos) | ||||
| @@ -2361,8 +2360,7 @@ void _glfwRequestWindowAttentionWayland(_GLFWwindow* window) | ||||
|   | ||||
|  void _glfwFocusWindowWayland(_GLFWwindow* window) | ||||
|  { | ||||
| -    _glfwInputError(GLFW_FEATURE_UNAVAILABLE, | ||||
| -                    "Wayland: The platform does not support setting the input focus"); | ||||
| +    fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the input focus\n"); | ||||
|  } | ||||
|   | ||||
|  void _glfwSetWindowMonitorWayland(_GLFWwindow* window, | ||||
| @@ -0,0 +1,17 @@ | ||||
| diff --git a/src/init.c b/src/init.c | ||||
| index 06dbb3f2..a7c6da86 100644 | ||||
| --- a/src/init.c | ||||
| +++ b/src/init.c | ||||
| @@ -449,6 +449,12 @@ GLFWAPI int glfwInit(void) | ||||
|      _glfw.initialized = GLFW_TRUE; | ||||
|   | ||||
|      glfwDefaultWindowHints(); | ||||
| + | ||||
| +    fprintf(stderr, "!!! Patched GLFW from https://github.com/Admicos/minecraft-wayland\n" | ||||
| +         "!!! If any issues with the window, or some issues with rendering, occur, " | ||||
| +         "first try with the built-in GLFW, and if that solves the issue, report there first.\n" | ||||
| +         "!!! Use outside Minecraft is untested, and things might break.\n"); | ||||
| + | ||||
|      return GLFW_TRUE; | ||||
|  } | ||||
|   | ||||
							
								
								
									
										20
									
								
								flatpak/patches/0007-Platform-Prefer-Wayland-over-X11.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								flatpak/patches/0007-Platform-Prefer-Wayland-over-X11.patch
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| diff --git a/src/platform.c b/src/platform.c | ||||
| index c5966ae7..3e7442f9 100644 | ||||
| --- a/src/platform.c | ||||
| +++ b/src/platform.c | ||||
| @@ -49,12 +49,12 @@ static const struct | ||||
|  #if defined(_GLFW_COCOA) | ||||
|      { GLFW_PLATFORM_COCOA, _glfwConnectCocoa }, | ||||
|  #endif | ||||
| -#if defined(_GLFW_X11) | ||||
| -    { GLFW_PLATFORM_X11, _glfwConnectX11 }, | ||||
| -#endif | ||||
|  #if defined(_GLFW_WAYLAND) | ||||
|      { GLFW_PLATFORM_WAYLAND, _glfwConnectWayland }, | ||||
|  #endif | ||||
| +#if defined(_GLFW_X11) | ||||
| +    { GLFW_PLATFORM_X11, _glfwConnectX11 }, | ||||
| +#endif | ||||
|  }; | ||||
|   | ||||
|  GLFWbool _glfwSelectPlatform(int desiredID, _GLFWplatform* platform) | ||||
							
								
								
									
										40
									
								
								flatpak/patches/weird_libdecor.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								flatpak/patches/weird_libdecor.patch
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| diff --git a/src/libdecor.c b/src/libdecor.c | ||||
| index a9c1106..1aa38b3 100644 | ||||
| --- a/src/libdecor.c | ||||
| +++ b/src/libdecor.c | ||||
| @@ -1391,22 +1391,32 @@ calculate_priority(const struct libdecor_plugin_description *plugin_description) | ||||
|  static bool | ||||
|  check_symbol_conflicts(const struct libdecor_plugin_description *plugin_description) | ||||
|  { | ||||
| +	bool ret = true; | ||||
|  	char * const *symbol; | ||||
| +	void* main_prog = dlopen(NULL, RTLD_LAZY); | ||||
| +	if (!main_prog) { | ||||
| +		fprintf(stderr, "Plugin \"%s\" couldn't check conflicting symbols: \"%s\".\n", | ||||
| +					plugin_description->description, dlerror()); | ||||
| +		return false; | ||||
| +	} | ||||
| + | ||||
|   | ||||
|  	symbol = plugin_description->conflicting_symbols; | ||||
|  	while (*symbol) { | ||||
|  		dlerror(); | ||||
| -		dlsym (RTLD_DEFAULT, *symbol); | ||||
| +		dlsym (main_prog, *symbol); | ||||
|  		if (!dlerror()) { | ||||
|  			fprintf(stderr, "Plugin \"%s\" uses conflicting symbol \"%s\".\n", | ||||
|  					plugin_description->description, *symbol); | ||||
| -			return false; | ||||
| +			ret = false; | ||||
| +			break; | ||||
|  		} | ||||
|   | ||||
|  		symbol++; | ||||
|  	} | ||||
|   | ||||
| -	return true; | ||||
| +	dlclose(main_prog); | ||||
| +	return ret; | ||||
|  } | ||||
|   | ||||
|  static struct plugin_loader * | ||||
| @@ -5,7 +5,7 @@ for i in {0..9}; do | ||||
|     test -S "$XDG_RUNTIME_DIR"/discord-ipc-"$i" || ln -sf {app/com.discordapp.Discord,"$XDG_RUNTIME_DIR"}/discord-ipc-"$i"; | ||||
| done | ||||
|  | ||||
| export PATH="${PATH}${PATH:+:}/app/utils/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin" | ||||
| export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}${LD_LIBRARY_PATH:+:}/usr/lib/extensions/vulkan/MangoHud/\$LIB/" | ||||
| export PATH="${PATH}${PATH:+:}/usr/lib/extensions/vulkan/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin" | ||||
| export VK_LAYER_PATH="/usr/lib/extensions/vulkan/share/vulkan/implicit_layer.d/" | ||||
|  | ||||
| exec /app/bin/prismrun "$@" | ||||
|   | ||||
							
								
								
									
										1
									
								
								flatpak/shared-modules
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								flatpak/shared-modules
									
									
									
									
									
										Submodule
									
								
							 Submodule flatpak/shared-modules added at 45094ca570
									
								
							
							
								
								
									
										6
									
								
								garnix.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								garnix.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| builds: | ||||
|   exclude: [] | ||||
|   include: | ||||
|     - "checks.x86_64-linux.*" | ||||
|     - "devShells.*.*" | ||||
|     - "packages.*.*" | ||||
| @@ -6,9 +6,10 @@ | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||
|  *  Copyright (C) 2022 Lenny McLennington <lenny@sneed.church> | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> | ||||
|  *  Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> | ||||
|  *  Copyright (C) 2023 seth <getchoo at tuta dot io> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -475,6 +476,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) | ||||
|  | ||||
|         qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT; | ||||
|         qDebug() << "Version                    : " << BuildConfig.printableVersionString(); | ||||
|         qDebug() << "Platform                   : " << BuildConfig.BUILD_PLATFORM; | ||||
|         qDebug() << "Git commit                 : " << BuildConfig.GIT_COMMIT; | ||||
|         qDebug() << "Git refspec                : " << BuildConfig.GIT_REFSPEC; | ||||
|         if (adjustedBy.size()) | ||||
| @@ -609,6 +611,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) | ||||
|         m_settings->registerSetting("IgnoreJavaCompatibility", false); | ||||
|         m_settings->registerSetting("IgnoreJavaWizard", false); | ||||
|  | ||||
|         // Mod loader settings | ||||
|         m_settings->registerSetting("DisableQuiltBeacon", false); | ||||
|  | ||||
|         // Native library workarounds | ||||
|         m_settings->registerSetting("UseNativeOpenAL", false); | ||||
|         m_settings->registerSetting("UseNativeGLFW", false); | ||||
| @@ -1181,7 +1186,17 @@ QIcon Application::getThemedIcon(const QString& name) | ||||
|     return QIcon::fromTheme(name); | ||||
| } | ||||
|  | ||||
| bool Application::openJsonEditor(const QString &filename) | ||||
| QList<CatPack*> Application::getValidCatPacks() | ||||
| { | ||||
|     return m_themeManager->getValidCatPacks(); | ||||
| } | ||||
|  | ||||
| QString Application::getCatPack(QString catName) | ||||
| { | ||||
|     return m_themeManager->getCatPack(catName); | ||||
| } | ||||
|  | ||||
| bool Application::openJsonEditor(const QString& filename) | ||||
| { | ||||
|     const QString file = QDir::current().absoluteFilePath(filename); | ||||
|     if (m_settings->get("JsonEditor").toString().isEmpty()) | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
| @@ -48,6 +48,7 @@ | ||||
| #include <BaseInstance.h> | ||||
|  | ||||
| #include "minecraft/launch/MinecraftServerTarget.h" | ||||
| #include "ui/themes/CatPack.h" | ||||
|  | ||||
| class LaunchController; | ||||
| class LocalPeer; | ||||
| @@ -126,9 +127,11 @@ public: | ||||
|  | ||||
|     void setApplicationTheme(const QString& name); | ||||
|  | ||||
|     shared_qobject_ptr<ExternalUpdater> updater() { | ||||
|         return m_updater; | ||||
|     } | ||||
|     QList<CatPack*> getValidCatPacks(); | ||||
|  | ||||
|     QString getCatPack(QString catName = ""); | ||||
|  | ||||
|     shared_qobject_ptr<ExternalUpdater> updater() { return m_updater; } | ||||
|  | ||||
|     void triggerUpdateCheck(); | ||||
|  | ||||
|   | ||||
| @@ -15,16 +15,15 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include <QString> | ||||
| #include <QMetaType> | ||||
| #include <QString> | ||||
| #include <memory> | ||||
|  | ||||
| /*! | ||||
|  * An abstract base class for versions. | ||||
|  */ | ||||
| class BaseVersion | ||||
| { | ||||
| public: | ||||
| class BaseVersion { | ||||
|    public: | ||||
|     using Ptr = std::shared_ptr<BaseVersion>; | ||||
|     virtual ~BaseVersion() {} | ||||
|     /*! | ||||
| @@ -45,14 +44,8 @@ public: | ||||
|      */ | ||||
|     virtual QString typeString() const = 0; | ||||
|  | ||||
|     virtual bool operator<(BaseVersion &a) | ||||
|     { | ||||
|         return name() < a.name(); | ||||
|     }; | ||||
|     virtual bool operator>(BaseVersion &a) | ||||
|     { | ||||
|         return name() > a.name(); | ||||
|     }; | ||||
|     virtual bool operator<(BaseVersion& a) { return name() < a.name(); }; | ||||
|     virtual bool operator>(BaseVersion& a) { return name() > a.name(); }; | ||||
| }; | ||||
|  | ||||
| Q_DECLARE_METATYPE(BaseVersion::Ptr) | ||||
|   | ||||
| @@ -763,6 +763,8 @@ SET(LAUNCHER_SOURCES | ||||
|     ui/themes/SystemTheme.h | ||||
|     ui/themes/ThemeManager.cpp | ||||
|     ui/themes/ThemeManager.h | ||||
|     ui/themes/CatPack.cpp | ||||
|     ui/themes/CatPack.h | ||||
|  | ||||
|     # Processes | ||||
|     LaunchController.h | ||||
|   | ||||
| @@ -99,7 +99,7 @@ void InstanceImportTask::executeTask() | ||||
|  | ||||
|         connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); | ||||
|         connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); | ||||
|         connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propogateStepProgress); | ||||
|         connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress); | ||||
|         connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed); | ||||
|         connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted); | ||||
|  | ||||
| @@ -293,7 +293,7 @@ void InstanceImportTask::processFlame() | ||||
|     }); | ||||
|     connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed); | ||||
|     connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress); | ||||
|     connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress); | ||||
|     connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress); | ||||
|     connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus); | ||||
|     connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails); | ||||
|  | ||||
| @@ -385,7 +385,7 @@ void InstanceImportTask::processModrinth() | ||||
|     }); | ||||
|     connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); | ||||
|     connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); | ||||
|     connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress); | ||||
|     connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress); | ||||
|     connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); | ||||
|     connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails); | ||||
|     connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); | ||||
|   | ||||
| @@ -799,7 +799,7 @@ class InstanceStaging : public Task { | ||||
|         connect(child, &Task::status, this, &InstanceStaging::setStatus); | ||||
|         connect(child, &Task::details, this, &InstanceStaging::setDetails); | ||||
|         connect(child, &Task::progress, this, &InstanceStaging::setProgress); | ||||
|         connect(child, &Task::stepProgress, this, &InstanceStaging::propogateStepProgress); | ||||
|         connect(child, &Task::stepProgress, this, &InstanceStaging::propagateStepProgress); | ||||
|         connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -390,7 +390,10 @@ void LaunchController::launchInstance() | ||||
|     m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher)); | ||||
|  | ||||
|     // Prepend Version | ||||
|     m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), BuildConfig.LAUNCHER_DISPLAYNAME + " version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::Launcher)); | ||||
|     { | ||||
|         auto versionString = QString("%1 version: %2 (%3)").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString(), BuildConfig.BUILD_PLATFORM); | ||||
|         m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), versionString + "\n\n", MessageLevel::Launcher)); | ||||
|     } | ||||
|     m_launcher->start(); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  PolyMC - Minecraft Launcher | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||
|  *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -33,56 +34,50 @@ | ||||
|  *      limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #include "MMCZip.h" | ||||
| #include <quazip/quazip.h> | ||||
| #include <quazip/quazipdir.h> | ||||
| #include <quazip/quazipfile.h> | ||||
| #include "MMCZip.h" | ||||
| #include "FileSystem.h" | ||||
|  | ||||
| #include <QCoreApplication> | ||||
| #include <QDebug> | ||||
| #include <QtConcurrentRun> | ||||
|  | ||||
| namespace MMCZip { | ||||
| // ours | ||||
| bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, const FilterFunction filter) | ||||
| bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction filter) | ||||
| { | ||||
|     QuaZip modZip(from.filePath()); | ||||
|     modZip.open(QuaZip::mdUnzip); | ||||
|  | ||||
|     QuaZipFile fileInsideMod(&modZip); | ||||
|     QuaZipFile zipOutFile(into); | ||||
|     for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) | ||||
|     { | ||||
|     for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) { | ||||
|         QString filename = modZip.getCurrentFileName(); | ||||
|         if (filter && !filter(filename)) | ||||
|         { | ||||
|             qDebug() << "Skipping file " << filename << " from " | ||||
|                         << from.fileName() << " - filtered"; | ||||
|         if (filter && !filter(filename)) { | ||||
|             qDebug() << "Skipping file " << filename << " from " << from.fileName() << " - filtered"; | ||||
|             continue; | ||||
|         } | ||||
|         if (contained.contains(filename)) | ||||
|         { | ||||
|             qDebug() << "Skipping already contained file " << filename << " from " | ||||
|                         << from.fileName(); | ||||
|         if (contained.contains(filename)) { | ||||
|             qDebug() << "Skipping already contained file " << filename << " from " << from.fileName(); | ||||
|             continue; | ||||
|         } | ||||
|         contained.insert(filename); | ||||
|  | ||||
|         if (!fileInsideMod.open(QIODevice::ReadOnly)) | ||||
|         { | ||||
|         if (!fileInsideMod.open(QIODevice::ReadOnly)) { | ||||
|             qCritical() << "Failed to open " << filename << " from " << from.fileName(); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         QuaZipNewInfo info_out(fileInsideMod.getActualFileName()); | ||||
|  | ||||
|         if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) | ||||
|         { | ||||
|         if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) { | ||||
|             qCritical() << "Failed to open " << filename << " in the jar"; | ||||
|             fileInsideMod.close(); | ||||
|             return false; | ||||
|         } | ||||
|         if (!JlCompress::copyData(fileInsideMod, zipOutFile)) | ||||
|         { | ||||
|         if (!JlCompress::copyData(fileInsideMod, zipOutFile)) { | ||||
|             zipOutFile.close(); | ||||
|             fileInsideMod.close(); | ||||
|             qCritical() << "Failed to copy data of " << filename << " into the jar"; | ||||
| @@ -94,10 +89,11 @@ bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &containe | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, bool followSymlinks) | ||||
| bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool followSymlinks) | ||||
| { | ||||
|     QDir directory(dir); | ||||
|     if (!directory.exists()) return false; | ||||
|     if (!directory.exists()) | ||||
|         return false; | ||||
|  | ||||
|     for (auto e : files) { | ||||
|         auto filePath = directory.relativeFilePath(e.absoluteFilePath()); | ||||
| @@ -109,17 +105,18 @@ bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, boo | ||||
|                 srcPath = e.canonicalFilePath(); | ||||
|             } | ||||
|         } | ||||
|         if( !JlCompress::compressFile(zip, srcPath, filePath)) return false; | ||||
|         if (!JlCompress::compressFile(zip, srcPath, filePath)) | ||||
|             return false; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks) | ||||
| bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks) | ||||
| { | ||||
|     QuaZip zip(fileCompressed); | ||||
|     QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); | ||||
|     if(!zip.open(QuaZip::mdCreate)) { | ||||
|     if (!zip.open(QuaZip::mdCreate)) { | ||||
|         QFile::remove(fileCompressed); | ||||
|         return false; | ||||
|     } | ||||
| @@ -127,7 +124,7 @@ bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList | ||||
|     auto result = compressDirFiles(&zip, dir, files, followSymlinks); | ||||
|  | ||||
|     zip.close(); | ||||
|     if(zip.getZipError()!=0) { | ||||
|     if (zip.getZipError() != 0) { | ||||
|         QFile::remove(fileCompressed); | ||||
|         return false; | ||||
|     } | ||||
| @@ -136,11 +133,10 @@ bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList | ||||
| } | ||||
|  | ||||
| // ours | ||||
| bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods) | ||||
| bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods) | ||||
| { | ||||
|     QuaZip zipOut(targetJarPath); | ||||
|     if (!zipOut.open(QuaZip::mdCreate)) | ||||
|     { | ||||
|     if (!zipOut.open(QuaZip::mdCreate)) { | ||||
|         QFile::remove(targetJarPath); | ||||
|         qCritical() << "Failed to open the minecraft.jar for modding"; | ||||
|         return false; | ||||
| @@ -151,37 +147,29 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const | ||||
|  | ||||
|     // Modify the jar | ||||
|     // This needs to be done in reverse-order to ensure we respect the loading order of components | ||||
|     for (auto i = mods.crbegin(); i != mods.crend(); i++) | ||||
|     { | ||||
|     for (auto i = mods.crbegin(); i != mods.crend(); i++) { | ||||
|         const auto* mod = *i; | ||||
|         // do not merge disabled mods. | ||||
|         if (!mod->enabled()) | ||||
|             continue; | ||||
|         if (mod->type() == ResourceType::ZIPFILE) | ||||
|         { | ||||
|             if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) | ||||
|             { | ||||
|         if (mod->type() == ResourceType::ZIPFILE) { | ||||
|             if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) { | ||||
|                 zipOut.close(); | ||||
|                 QFile::remove(targetJarPath); | ||||
|                 qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         else if (mod->type() == ResourceType::SINGLEFILE) | ||||
|         { | ||||
|         } else if (mod->type() == ResourceType::SINGLEFILE) { | ||||
|             // FIXME: buggy - does not work with addedFiles | ||||
|             auto filename = mod->fileinfo(); | ||||
|             if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) | ||||
|             { | ||||
|             if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) { | ||||
|                 zipOut.close(); | ||||
|                 QFile::remove(targetJarPath); | ||||
|                 qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; | ||||
|                 return false; | ||||
|             } | ||||
|             addedFiles.insert(filename.fileName()); | ||||
|         } | ||||
|         else if (mod->type() == ResourceType::FOLDER) | ||||
|         { | ||||
|         } else if (mod->type() == ResourceType::FOLDER) { | ||||
|             // untested, but seems to be unused / not possible to reach | ||||
|             // FIXME: buggy - does not work with addedFiles | ||||
|             auto filename = mod->fileinfo(); | ||||
| @@ -190,25 +178,21 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const | ||||
|             dir.cdUp(); | ||||
|             QString parent_dir = dir.absolutePath(); | ||||
|             auto files = QFileInfoList(); | ||||
|             MMCZip::collectFileListRecursively(what_to_zip, nullptr, &files, nullptr); | ||||
|             collectFileListRecursively(what_to_zip, nullptr, &files, nullptr); | ||||
|  | ||||
|             for (auto e : files) { | ||||
|                 if (addedFiles.contains(e.filePath())) | ||||
|                     files.removeAll(e); | ||||
|             } | ||||
|  | ||||
|             if (!MMCZip::compressDirFiles(&zipOut, parent_dir, files)) | ||||
|             { | ||||
|             if (!compressDirFiles(&zipOut, parent_dir, files)) { | ||||
|                 zipOut.close(); | ||||
|                 QFile::remove(targetJarPath); | ||||
|                 qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; | ||||
|                 return false; | ||||
|             } | ||||
|             qDebug() << "Adding folder " << filename.fileName() << " from " | ||||
|                      << filename.absoluteFilePath(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             qDebug() << "Adding folder " << filename.fileName() << " from " << filename.absoluteFilePath(); | ||||
|         } else { | ||||
|             // Make sure we do not continue launching when something is missing or undefined... | ||||
|             zipOut.close(); | ||||
|             QFile::remove(targetJarPath); | ||||
| @@ -217,8 +201,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key){return !key.contains("META-INF");})) | ||||
|     { | ||||
|     if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key) { return !key.contains("META-INF"); })) { | ||||
|         zipOut.close(); | ||||
|         QFile::remove(targetJarPath); | ||||
|         qCritical() << "Failed to insert minecraft.jar contents."; | ||||
| @@ -227,8 +210,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const | ||||
|  | ||||
|     // Recompress the jar | ||||
|     zipOut.close(); | ||||
|     if (zipOut.getZipError() != 0) | ||||
|     { | ||||
|     if (zipOut.getZipError() != 0) { | ||||
|         QFile::remove(targetJarPath); | ||||
|         qCritical() << "Failed to finalize minecraft.jar!"; | ||||
|         return false; | ||||
| @@ -237,7 +219,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const | ||||
| } | ||||
|  | ||||
| // ours | ||||
| QString MMCZip::findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root) | ||||
| QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root) | ||||
| { | ||||
|     QuaZipDir rootDir(zip, root); | ||||
|     for (auto&& fileName : rootDir.entryList(QDir::Files)) { | ||||
| @@ -261,27 +243,23 @@ QString MMCZip::findFolderOfFileInZip(QuaZip* zip, const QString& what, const QS | ||||
| } | ||||
|  | ||||
| // ours | ||||
| bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root) | ||||
| bool findFilesInZip(QuaZip* zip, const QString& what, QStringList& result, const QString& root) | ||||
| { | ||||
|     QuaZipDir rootDir(zip, root); | ||||
|     for(auto fileName: rootDir.entryList(QDir::Files)) | ||||
|     { | ||||
|         if(fileName == what) | ||||
|         { | ||||
|     for (auto fileName : rootDir.entryList(QDir::Files)) { | ||||
|         if (fileName == what) { | ||||
|             result.append(root); | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|     for(auto fileName: rootDir.entryList(QDir::Dirs)) | ||||
|     { | ||||
|     for (auto fileName : rootDir.entryList(QDir::Dirs)) { | ||||
|         findFilesInZip(zip, what, result, root + fileName); | ||||
|     } | ||||
|     return !result.isEmpty(); | ||||
| } | ||||
|  | ||||
|  | ||||
| // ours | ||||
| std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target) | ||||
| std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, const QString& target) | ||||
| { | ||||
|     auto target_top_dir = QUrl::fromLocalFile(target); | ||||
|  | ||||
| @@ -289,16 +267,13 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su | ||||
|  | ||||
|     qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target; | ||||
|     auto numEntries = zip->getEntriesCount(); | ||||
|     if(numEntries < 0) { | ||||
|     if (numEntries < 0) { | ||||
|         qWarning() << "Failed to enumerate files in archive"; | ||||
|         return std::nullopt; | ||||
|     } | ||||
|     else if(numEntries == 0) { | ||||
|     } else if (numEntries == 0) { | ||||
|         qDebug() << "Extracting empty archives seems odd..."; | ||||
|         return extracted; | ||||
|     } | ||||
|     else if (!zip->goToFirstFile()) | ||||
|     { | ||||
|     } else if (!zip->goToFirstFile()) { | ||||
|         qWarning() << "Failed to seek to first file in zip"; | ||||
|         return std::nullopt; | ||||
|     } | ||||
| @@ -334,7 +309,8 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su | ||||
|         } | ||||
|  | ||||
|         if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) { | ||||
|             qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path" << target; | ||||
|             qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path" | ||||
|                        << target; | ||||
|             return std::nullopt; | ||||
|         } | ||||
|  | ||||
| @@ -345,7 +321,8 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su | ||||
|         } | ||||
|  | ||||
|         extracted.append(target_file_path); | ||||
|         QFile::setPermissions(target_file_path, QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser); | ||||
|         QFile::setPermissions(target_file_path, | ||||
|                               QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser); | ||||
|  | ||||
|         qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; | ||||
|     } while (zip->goToNextFile()); | ||||
| @@ -354,66 +331,66 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su | ||||
| } | ||||
|  | ||||
| // ours | ||||
| bool MMCZip::extractRelFile(QuaZip *zip, const QString &file, const QString &target) | ||||
| bool extractRelFile(QuaZip* zip, const QString& file, const QString& target) | ||||
| { | ||||
|     return JlCompress::extractFile(zip, file, target); | ||||
| } | ||||
|  | ||||
| // ours | ||||
| std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir) | ||||
| std::optional<QStringList> extractDir(QString fileCompressed, QString dir) | ||||
| { | ||||
|     QuaZip zip(fileCompressed); | ||||
|     if (!zip.open(QuaZip::mdUnzip)) | ||||
|     { | ||||
|     if (!zip.open(QuaZip::mdUnzip)) { | ||||
|         // check if this is a minimum size empty zip file... | ||||
|         QFileInfo fileInfo(fileCompressed); | ||||
|         if(fileInfo.size() == 22) { | ||||
|         if (fileInfo.size() == 22) { | ||||
|             return QStringList(); | ||||
|         } | ||||
|         qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();; | ||||
|         qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); | ||||
|         ; | ||||
|         return std::nullopt; | ||||
|     } | ||||
|     return MMCZip::extractSubDir(&zip, "", dir); | ||||
|     return extractSubDir(&zip, "", dir); | ||||
| } | ||||
|  | ||||
| // ours | ||||
| std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir) | ||||
| std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir) | ||||
| { | ||||
|     QuaZip zip(fileCompressed); | ||||
|     if (!zip.open(QuaZip::mdUnzip)) | ||||
|     { | ||||
|     if (!zip.open(QuaZip::mdUnzip)) { | ||||
|         // check if this is a minimum size empty zip file... | ||||
|         QFileInfo fileInfo(fileCompressed); | ||||
|         if(fileInfo.size() == 22) { | ||||
|         if (fileInfo.size() == 22) { | ||||
|             return QStringList(); | ||||
|         } | ||||
|         qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();; | ||||
|         qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); | ||||
|         ; | ||||
|         return std::nullopt; | ||||
|     } | ||||
|     return MMCZip::extractSubDir(&zip, subdir, dir); | ||||
|     return extractSubDir(&zip, subdir, dir); | ||||
| } | ||||
|  | ||||
| // ours | ||||
| bool MMCZip::extractFile(QString fileCompressed, QString file, QString target) | ||||
| bool extractFile(QString fileCompressed, QString file, QString target) | ||||
| { | ||||
|     QuaZip zip(fileCompressed); | ||||
|     if (!zip.open(QuaZip::mdUnzip)) | ||||
|     { | ||||
|     if (!zip.open(QuaZip::mdUnzip)) { | ||||
|         // check if this is a minimum size empty zip file... | ||||
|         QFileInfo fileInfo(fileCompressed); | ||||
|         if(fileInfo.size() == 22) { | ||||
|         if (fileInfo.size() == 22) { | ||||
|             return true; | ||||
|         } | ||||
|         qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); | ||||
|         return false; | ||||
|     } | ||||
|     return MMCZip::extractRelFile(&zip, file, target); | ||||
|     return extractRelFile(&zip, file, target); | ||||
| } | ||||
|  | ||||
| bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList *files, | ||||
|                                         MMCZip::FilterFunction excludeFilter) { | ||||
| bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter) | ||||
| { | ||||
|     QDir rootDirectory(rootDir); | ||||
|     if (!rootDirectory.exists()) return false; | ||||
|     if (!rootDirectory.exists()) | ||||
|         return false; | ||||
|  | ||||
|     QDir directory; | ||||
|     if (subDir == nullptr) | ||||
| @@ -421,25 +398,107 @@ bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& s | ||||
|     else | ||||
|         directory = QDir(subDir); | ||||
|  | ||||
|     if (!directory.exists()) return false;  // shouldn't ever happen | ||||
|     if (!directory.exists()) | ||||
|         return false;  // shouldn't ever happen | ||||
|  | ||||
|     // recurse directories | ||||
|     QFileInfoList entries = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden); | ||||
|     for (const auto& e: entries) { | ||||
|     for (const auto& e : entries) { | ||||
|         if (!collectFileListRecursively(rootDir, e.filePath(), files, excludeFilter)) | ||||
|             return false; | ||||
|     } | ||||
|  | ||||
|     // collect files | ||||
|     entries = directory.entryInfoList(QDir::Files); | ||||
|     for (const auto& e: entries) { | ||||
|     for (const auto& e : entries) { | ||||
|         QString relativeFilePath = rootDirectory.relativeFilePath(e.absoluteFilePath()); | ||||
|         if (excludeFilter && excludeFilter(relativeFilePath)) { | ||||
|             qDebug() << "Skipping file " << relativeFilePath; | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         files->append(e);  // we want the original paths for MMCZip::compressDirFiles | ||||
|         files->append(e);  // we want the original paths for compressDirFiles | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| void ExportToZipTask::executeTask() | ||||
| { | ||||
|     setStatus("Adding files..."); | ||||
|     setProgress(0, m_files.length()); | ||||
|     m_build_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return exportZip(); }); | ||||
|     connect(&m_build_zip_watcher, &QFutureWatcher<ZipResult>::finished, this, &ExportToZipTask::finish); | ||||
|     m_build_zip_watcher.setFuture(m_build_zip_future); | ||||
| } | ||||
|  | ||||
| auto ExportToZipTask::exportZip() -> ZipResult | ||||
| { | ||||
|     if (!m_dir.exists()) { | ||||
|         return ZipResult(tr("Folder doesn't exist")); | ||||
|     } | ||||
|     if (!m_output.isOpen() && !m_output.open(QuaZip::mdCreate)) { | ||||
|         return ZipResult(tr("Could not create file")); | ||||
|     } | ||||
|  | ||||
|     for (auto fileName : m_extra_files.keys()) { | ||||
|         if (m_build_zip_future.isCanceled()) | ||||
|             return ZipResult(); | ||||
|         QuaZipFile indexFile(&m_output); | ||||
|         if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileName))) { | ||||
|             return ZipResult(tr("Could not create:") + fileName); | ||||
|         } | ||||
|         indexFile.write(m_extra_files[fileName]); | ||||
|     } | ||||
|  | ||||
|     for (const QFileInfo& file : m_files) { | ||||
|         if (m_build_zip_future.isCanceled()) | ||||
|             return ZipResult(); | ||||
|  | ||||
|         auto absolute = file.absoluteFilePath(); | ||||
|         auto relative = m_dir.relativeFilePath(absolute); | ||||
|         setStatus("Compresing: " + relative); | ||||
|         setProgress(m_progress + 1, m_progressTotal); | ||||
|         if (m_follow_symlinks) { | ||||
|             if (file.isSymLink()) | ||||
|                 absolute = file.symLinkTarget(); | ||||
|             else | ||||
|                 absolute = file.canonicalFilePath(); | ||||
|         } | ||||
|  | ||||
|         if (!m_exclude_files.contains(relative) && !JlCompress::compressFile(&m_output, absolute, m_destination_prefix + relative)) { | ||||
|             return ZipResult(tr("Could not read and compress %1").arg(relative)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     m_output.close(); | ||||
|     if (m_output.getZipError() != 0) { | ||||
|         return ZipResult(tr("A zip error occurred")); | ||||
|     } | ||||
|     return ZipResult(); | ||||
| } | ||||
|  | ||||
| void ExportToZipTask::finish() | ||||
| { | ||||
|     if (m_build_zip_future.isCanceled()) { | ||||
|         QFile::remove(m_output_path); | ||||
|         emitAborted(); | ||||
|     } else if (auto result = m_build_zip_future.result(); result.has_value()) { | ||||
|         QFile::remove(m_output_path); | ||||
|         emitFailed(result.value()); | ||||
|     } else { | ||||
|         emitSucceeded(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool ExportToZipTask::abort() | ||||
| { | ||||
|     if (m_build_zip_future.isRunning()) { | ||||
|         m_build_zip_future.cancel(); | ||||
|         // NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur | ||||
|         // immediately. | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| }  // namespace MMCZip | ||||
| @@ -1,7 +1,8 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  PolyMC - Minecraft Launcher | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||
|  *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -35,110 +36,157 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <QString> | ||||
| #include <QFileInfo> | ||||
| #include <QSet> | ||||
| #include "minecraft/mod/Mod.h" | ||||
| #include <functional> | ||||
|  | ||||
| #include <quazip.h> | ||||
| #include <quazip/JlCompress.h> | ||||
| #include <QDir> | ||||
| #include <QFileInfo> | ||||
| #include <QFuture> | ||||
| #include <QFutureWatcher> | ||||
| #include <QHash> | ||||
| #include <QSet> | ||||
| #include <QString> | ||||
| #include <functional> | ||||
| #include <memory> | ||||
| #include <optional> | ||||
| #include "minecraft/mod/Mod.h" | ||||
| #include "tasks/Task.h" | ||||
|  | ||||
| namespace MMCZip | ||||
| { | ||||
|     using FilterFunction = std::function<bool(const QString &)>; | ||||
| namespace MMCZip { | ||||
| using FilterFunction = std::function<bool(const QString&)>; | ||||
|  | ||||
|     /** | ||||
|      * Merge two zip files, using a filter function | ||||
|      */ | ||||
|     bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, | ||||
|                                             const FilterFunction filter = nullptr); | ||||
| /** | ||||
|  * Merge two zip files, using a filter function | ||||
|  */ | ||||
| bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction filter = nullptr); | ||||
|  | ||||
|     /** | ||||
|      * Compress directory, by providing a list of files to compress | ||||
|      * \param zip target archive | ||||
|      * \param dir directory that will be compressed (to compress with relative paths) | ||||
|      * \param files list of files to compress | ||||
|      * \param followSymlinks should follow symlinks when compressing file data | ||||
|      * \return true for success or false for failure | ||||
|      */ | ||||
|     bool compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, bool followSymlinks = false); | ||||
| /** | ||||
|  * Compress directory, by providing a list of files to compress | ||||
|  * \param zip target archive | ||||
|  * \param dir directory that will be compressed (to compress with relative paths) | ||||
|  * \param files list of files to compress | ||||
|  * \param followSymlinks should follow symlinks when compressing file data | ||||
|  * \return true for success or false for failure | ||||
|  */ | ||||
| bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool followSymlinks = false); | ||||
|  | ||||
|     /** | ||||
|      * Compress directory, by providing a list of files to compress | ||||
|      * \param fileCompressed target archive file | ||||
|      * \param dir directory that will be compressed (to compress with relative paths) | ||||
|      * \param files list of files to compress | ||||
|      * \param followSymlinks should follow symlinks when compressing file data | ||||
|      * \return true for success or false for failure | ||||
|      */ | ||||
|     bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false); | ||||
| /** | ||||
|  * Compress directory, by providing a list of files to compress | ||||
|  * \param fileCompressed target archive file | ||||
|  * \param dir directory that will be compressed (to compress with relative paths) | ||||
|  * \param files list of files to compress | ||||
|  * \param followSymlinks should follow symlinks when compressing file data | ||||
|  * \return true for success or false for failure | ||||
|  */ | ||||
| bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false); | ||||
|  | ||||
|     /** | ||||
|      * take a source jar, add mods to it, resulting in target jar | ||||
|      */ | ||||
|     bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods); | ||||
| /** | ||||
|  * take a source jar, add mods to it, resulting in target jar | ||||
|  */ | ||||
| bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods); | ||||
|  | ||||
|     /** | ||||
|      * Find a single file in archive by file name (not path) | ||||
|      * | ||||
|      * \param ignore_paths paths to skip when recursing the search | ||||
|      * | ||||
|      * \return the path prefix where the file is | ||||
|      */ | ||||
|     QString findFolderOfFileInZip(QuaZip * zip, const QString & what, const QStringList& ignore_paths = {}, const QString &root = QString("")); | ||||
| /** | ||||
|  * Find a single file in archive by file name (not path) | ||||
|  * | ||||
|  * \param ignore_paths paths to skip when recursing the search | ||||
|  * | ||||
|  * \return the path prefix where the file is | ||||
|  */ | ||||
| QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths = {}, const QString& root = QString("")); | ||||
|  | ||||
|     /** | ||||
|      * Find a multiple files of the same name in archive by file name | ||||
|      * If a file is found in a path, no deeper paths are searched | ||||
|      * | ||||
|      * \return true if anything was found | ||||
|      */ | ||||
|     bool findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString()); | ||||
| /** | ||||
|  * Find a multiple files of the same name in archive by file name | ||||
|  * If a file is found in a path, no deeper paths are searched | ||||
|  * | ||||
|  * \return true if anything was found | ||||
|  */ | ||||
| bool findFilesInZip(QuaZip* zip, const QString& what, QStringList& result, const QString& root = QString()); | ||||
|  | ||||
|     /** | ||||
|      * Extract a subdirectory from an archive | ||||
|      */ | ||||
|     std::optional<QStringList> extractSubDir(QuaZip *zip, const QString & subdir, const QString &target); | ||||
| /** | ||||
|  * Extract a subdirectory from an archive | ||||
|  */ | ||||
| std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, const QString& target); | ||||
|  | ||||
|     bool extractRelFile(QuaZip *zip, const QString & file, const QString &target); | ||||
| bool extractRelFile(QuaZip* zip, const QString& file, const QString& target); | ||||
|  | ||||
|     /** | ||||
|      * Extract a whole archive. | ||||
|      * | ||||
|      * \param fileCompressed The name of the archive. | ||||
|      * \param dir The directory to extract to, the current directory if left empty. | ||||
|      * \return The list of the full paths of the files extracted, empty on failure. | ||||
|      */ | ||||
|     std::optional<QStringList> extractDir(QString fileCompressed, QString dir); | ||||
| /** | ||||
|  * Extract a whole archive. | ||||
|  * | ||||
|  * \param fileCompressed The name of the archive. | ||||
|  * \param dir The directory to extract to, the current directory if left empty. | ||||
|  * \return The list of the full paths of the files extracted, empty on failure. | ||||
|  */ | ||||
| std::optional<QStringList> extractDir(QString fileCompressed, QString dir); | ||||
|  | ||||
|     /** | ||||
|      * Extract a subdirectory from an archive | ||||
|      * | ||||
|      * \param fileCompressed The name of the archive. | ||||
|      * \param subdir The directory within the archive to extract | ||||
|      * \param dir The directory to extract to, the current directory if left empty. | ||||
|      * \return The list of the full paths of the files extracted, empty on failure. | ||||
|      */ | ||||
|     std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir); | ||||
| /** | ||||
|  * Extract a subdirectory from an archive | ||||
|  * | ||||
|  * \param fileCompressed The name of the archive. | ||||
|  * \param subdir The directory within the archive to extract | ||||
|  * \param dir The directory to extract to, the current directory if left empty. | ||||
|  * \return The list of the full paths of the files extracted, empty on failure. | ||||
|  */ | ||||
| std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir); | ||||
|  | ||||
|     /** | ||||
|      * Extract a single file from an archive into a directory | ||||
|      * | ||||
|      * \param fileCompressed The name of the archive. | ||||
|      * \param file The file within the archive to extract | ||||
|      * \param dir The directory to extract to, the current directory if left empty. | ||||
|      * \return true for success or false for failure | ||||
|      */ | ||||
|     bool extractFile(QString fileCompressed, QString file, QString dir); | ||||
| /** | ||||
|  * Extract a single file from an archive into a directory | ||||
|  * | ||||
|  * \param fileCompressed The name of the archive. | ||||
|  * \param file The file within the archive to extract | ||||
|  * \param dir The directory to extract to, the current directory if left empty. | ||||
|  * \return true for success or false for failure | ||||
|  */ | ||||
| bool extractFile(QString fileCompressed, QString file, QString dir); | ||||
|  | ||||
|     /** | ||||
|      * Populate a QFileInfoList with a directory tree recursively, while allowing to excludeFilter what shouldn't be included. | ||||
|      * \param rootDir directory to start off | ||||
|      * \param subDir subdirectory, should be nullptr for first invocation | ||||
|      * \param files resulting list of QFileInfo | ||||
|      * \param excludeFilter function to excludeFilter which files shouldn't be included (returning true means to excude) | ||||
|      * \return true for success or false for failure | ||||
|      */ | ||||
|     bool collectFileListRecursively(const QString &rootDir, const QString &subDir, QFileInfoList *files, FilterFunction excludeFilter); | ||||
| } | ||||
| /** | ||||
|  * Populate a QFileInfoList with a directory tree recursively, while allowing to excludeFilter what shouldn't be included. | ||||
|  * \param rootDir directory to start off | ||||
|  * \param subDir subdirectory, should be nullptr for first invocation | ||||
|  * \param files resulting list of QFileInfo | ||||
|  * \param excludeFilter function to excludeFilter which files shouldn't be included (returning true means to excude) | ||||
|  * \return true for success or false for failure | ||||
|  */ | ||||
| bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter); | ||||
|  | ||||
| class ExportToZipTask : public Task { | ||||
|    public: | ||||
|     ExportToZipTask(QString outputPath, QDir dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false) | ||||
|         : m_output_path(outputPath) | ||||
|         , m_output(outputPath) | ||||
|         , m_dir(dir) | ||||
|         , m_files(files) | ||||
|         , m_destination_prefix(destinationPrefix) | ||||
|         , m_follow_symlinks(followSymlinks) | ||||
|     { | ||||
|         setAbortable(true); | ||||
|     }; | ||||
|     ExportToZipTask(QString outputPath, QString dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false) | ||||
|         : ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks){}; | ||||
|  | ||||
|     virtual ~ExportToZipTask() = default; | ||||
|  | ||||
|     void setExcludeFiles(QStringList excludeFiles) { m_exclude_files = excludeFiles; } | ||||
|     void addExtraFile(QString fileName, QByteArray data) { m_extra_files.insert(fileName, data); } | ||||
|  | ||||
|     typedef std::optional<QString> ZipResult; | ||||
|  | ||||
|    protected: | ||||
|     virtual void executeTask() override; | ||||
|     bool abort() override; | ||||
|  | ||||
|     ZipResult exportZip(); | ||||
|     void finish(); | ||||
|  | ||||
|    private: | ||||
|     QString m_output_path; | ||||
|     QuaZip m_output; | ||||
|     QDir m_dir; | ||||
|     QFileInfoList m_files; | ||||
|     QString m_destination_prefix; | ||||
|     bool m_follow_symlinks; | ||||
|     QStringList m_exclude_files; | ||||
|     QHash<QString, QByteArray> m_extra_files; | ||||
|  | ||||
|     QFuture<ZipResult> m_build_zip_future; | ||||
|     QFutureWatcher<ZipResult> m_build_zip_watcher; | ||||
| }; | ||||
| }  // namespace MMCZip | ||||
|   | ||||
| @@ -54,7 +54,7 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, | ||||
|     m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename()))); | ||||
|     connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded); | ||||
|     connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged); | ||||
|     connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propogateStepProgress); | ||||
|     connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propagateStepProgress); | ||||
|     connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed); | ||||
|  | ||||
|     addTask(m_filesNetJob); | ||||
|   | ||||
| @@ -1,29 +1,64 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|  *  the Free Software Foundation, version 3. | ||||
|  * | ||||
|  *  This program is distributed in the hope that it will be useful, | ||||
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  *  GNU General Public License for more details. | ||||
|  * | ||||
|  *  You should have received a copy of the GNU General Public License | ||||
|  *  along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| #include "JavaInstall.h" | ||||
|  | ||||
| #include "BaseVersion.h" | ||||
| #include "StringUtils.h" | ||||
|  | ||||
| bool JavaInstall::operator<(const JavaInstall &rhs) | ||||
| bool JavaInstall::operator<(const JavaInstall& rhs) | ||||
| { | ||||
|     auto archCompare = StringUtils::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive); | ||||
|     if(archCompare != 0) | ||||
|     if (archCompare != 0) | ||||
|         return archCompare < 0; | ||||
|     if(id < rhs.id) | ||||
|     { | ||||
|     if (id < rhs.id) { | ||||
|         return true; | ||||
|     } | ||||
|     if(id > rhs.id) | ||||
|     { | ||||
|     if (id > rhs.id) { | ||||
|         return false; | ||||
|     } | ||||
|     return StringUtils::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0; | ||||
| } | ||||
|  | ||||
| bool JavaInstall::operator==(const JavaInstall &rhs) | ||||
| bool JavaInstall::operator==(const JavaInstall& rhs) | ||||
| { | ||||
|     return arch == rhs.arch && id == rhs.id && path == rhs.path; | ||||
| } | ||||
|  | ||||
| bool JavaInstall::operator>(const JavaInstall &rhs) | ||||
| bool JavaInstall::operator>(const JavaInstall& rhs) | ||||
| { | ||||
|     return (!operator<(rhs)) && (!operator==(rhs)); | ||||
| } | ||||
|  | ||||
| bool JavaInstall::operator<(BaseVersion& a) | ||||
| { | ||||
|     try { | ||||
|         return operator<(dynamic_cast<JavaInstall&>(a)); | ||||
|     } catch (const std::bad_cast& e) { | ||||
|         return BaseVersion::operator<(a); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool JavaInstall::operator>(BaseVersion& a) | ||||
| { | ||||
|     try { | ||||
|         return operator>(dynamic_cast<JavaInstall&>(a)); | ||||
|     } catch (const std::bad_cast& e) { | ||||
|         return BaseVersion::operator>(a); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,33 +1,40 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|  *  the Free Software Foundation, version 3. | ||||
|  * | ||||
|  *  This program is distributed in the hope that it will be useful, | ||||
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  *  GNU General Public License for more details. | ||||
|  * | ||||
|  *  You should have received a copy of the GNU General Public License | ||||
|  *  along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "BaseVersion.h" | ||||
| #include "JavaVersion.h" | ||||
|  | ||||
| struct JavaInstall : public BaseVersion | ||||
| { | ||||
|     JavaInstall(){} | ||||
|     JavaInstall(QString id, QString arch, QString path) | ||||
|     : id(id), arch(arch), path(path) | ||||
|     { | ||||
|     } | ||||
|     virtual QString descriptor() | ||||
|     { | ||||
|         return id.toString(); | ||||
|     } | ||||
| struct JavaInstall : public BaseVersion { | ||||
|     JavaInstall() {} | ||||
|     JavaInstall(QString id, QString arch, QString path) : id(id), arch(arch), path(path) {} | ||||
|     virtual QString descriptor() { return id.toString(); } | ||||
|  | ||||
|     virtual QString name() | ||||
|     { | ||||
|         return id.toString(); | ||||
|     } | ||||
|     virtual QString name() { return id.toString(); } | ||||
|  | ||||
|     virtual QString typeString() const | ||||
|     { | ||||
|         return arch; | ||||
|     } | ||||
|     virtual QString typeString() const { return arch; } | ||||
|  | ||||
|     bool operator<(const JavaInstall & rhs); | ||||
|     bool operator==(const JavaInstall & rhs); | ||||
|     bool operator>(const JavaInstall & rhs); | ||||
|     virtual bool operator<(BaseVersion& a) override; | ||||
|     virtual bool operator>(BaseVersion& a) override; | ||||
|     bool operator<(const JavaInstall& rhs); | ||||
|     bool operator==(const JavaInstall& rhs); | ||||
|     bool operator>(const JavaInstall& rhs); | ||||
|  | ||||
|     JavaVersion id; | ||||
|     QString arch; | ||||
|   | ||||
| @@ -28,7 +28,7 @@ void Update::executeTask() | ||||
|     { | ||||
|         connect(m_updateTask.get(), &Task::finished, this, &Update::updateFinished); | ||||
|         connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress); | ||||
|         connect(m_updateTask.get(), &Task::stepProgress, this, &Update::propogateStepProgress); | ||||
|         connect(m_updateTask.get(), &Task::stepProgress, this, &Update::propagateStepProgress); | ||||
|         connect(m_updateTask.get(), &Task::status, this, &Update::setStatus); | ||||
|         connect(m_updateTask.get(), &Task::details, this, &Update::setDetails); | ||||
|         emit progressReportingRequest(); | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||
|  *  Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org> | ||||
|  *  Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> | ||||
|  *  Copyright (c) 2023 seth <getchoo at tuta dot io> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -186,6 +187,10 @@ void MinecraftInstance::loadSpecificSettings() | ||||
|         m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride); | ||||
|         m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride); | ||||
|  | ||||
|         // Mod loader specific options | ||||
|         auto modLoaderSettings = m_settings->registerSetting("OverrideModLoaderSettings", false); | ||||
|         m_settings->registerOverride(global_settings->getSetting("DisableQuiltBeacon"), modLoaderSettings); | ||||
|  | ||||
|         m_settings->set("InstanceType", "OneSix"); | ||||
|     } | ||||
|  | ||||
| @@ -391,6 +396,12 @@ QStringList MinecraftInstance::extraArguments() | ||||
|         agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath()); | ||||
|         list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument())); | ||||
|     } | ||||
|  | ||||
|     { | ||||
|         const auto loaders = version->getModLoaders(); | ||||
|         if (loaders.has_value() && loaders.value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool()) | ||||
|             list.append("-Dloader.disable_beacon=true"); | ||||
|     } | ||||
|     return list; | ||||
| } | ||||
|  | ||||
| @@ -832,7 +843,7 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess | ||||
|     { | ||||
|         addToFilter(sessionRef.session, tr("<SESSION ID>")); | ||||
|     } | ||||
|     if (sessionRef.access_token != "offline") { | ||||
|     if (sessionRef.access_token != "0") { | ||||
|         addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>")); | ||||
|     } | ||||
|     if(sessionRef.client_token.size()) { | ||||
|   | ||||
| @@ -22,7 +22,7 @@ void MinecraftLoadAndCheck::executeTask() | ||||
|     connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed); | ||||
|     connect(m_task.get(), &Task::aborted, this, [this]{ subtaskFailed(tr("Aborted")); }); | ||||
|     connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress); | ||||
|     connect(m_task.get(), &Task::stepProgress, this, &MinecraftLoadAndCheck::propogateStepProgress); | ||||
|     connect(m_task.get(), &Task::stepProgress, this, &MinecraftLoadAndCheck::propagateStepProgress); | ||||
|     connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -100,7 +100,7 @@ void MinecraftUpdate::next() | ||||
|         disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); | ||||
|         disconnect(task.get(), &Task::aborted, this, &Task::abort); | ||||
|         disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); | ||||
|         disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress); | ||||
|         disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propagateStepProgress); | ||||
|         disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); | ||||
|         disconnect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails); | ||||
|     } | ||||
| @@ -120,7 +120,7 @@ void MinecraftUpdate::next() | ||||
|     connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); | ||||
|     connect(task.get(), &Task::aborted, this, &Task::abort); | ||||
|     connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); | ||||
|     connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress); | ||||
|     connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propagateStepProgress); | ||||
|     connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); | ||||
|     connect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails); | ||||
|     // if the task is already running, do not start it again | ||||
|   | ||||
| @@ -374,6 +374,10 @@ bool AccountData::resumeStateFromV3(QJsonObject data) { | ||||
|     } | ||||
|  | ||||
|     yggdrasilToken = tokenFromJSONV3(data, "ygg"); | ||||
|     // versions before 7.2 used "offline" as the offline token | ||||
|     if (yggdrasilToken.token == "offline") | ||||
|         yggdrasilToken.token = "0"; | ||||
|  | ||||
|     minecraftProfile = profileFromJSONV3(data, "profile"); | ||||
|     if(!entitlementFromJSONV3(data, minecraftEntitlement)) { | ||||
|         if(minecraftProfile.validity != Katabasis::Validity::None) { | ||||
|   | ||||
| @@ -26,6 +26,7 @@ bool AuthSession::MakeOffline(QString offline_playername) | ||||
|         return false; | ||||
|     } | ||||
|     session = "-"; | ||||
|     access_token = "0"; | ||||
|     player_name = offline_playername; | ||||
|     status = PlayableOffline; | ||||
|     return true; | ||||
|   | ||||
| @@ -37,6 +37,7 @@ | ||||
|  | ||||
| #include "MinecraftAccount.h" | ||||
|  | ||||
| #include <QCryptographicHash> | ||||
| #include <QUuid> | ||||
| #include <QJsonObject> | ||||
| #include <QJsonArray> | ||||
| @@ -93,14 +94,14 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username) | ||||
| { | ||||
|     auto account = makeShared<MinecraftAccount>(); | ||||
|     account->data.type = AccountType::Offline; | ||||
|     account->data.yggdrasilToken.token = "offline"; | ||||
|     account->data.yggdrasilToken.token = "0"; | ||||
|     account->data.yggdrasilToken.validity = Katabasis::Validity::Certain; | ||||
|     account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); | ||||
|     account->data.yggdrasilToken.extra["userName"] = username; | ||||
|     account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); | ||||
|     account->data.minecraftEntitlement.ownsMinecraft = true; | ||||
|     account->data.minecraftEntitlement.canPlayMinecraft = true; | ||||
|     account->data.minecraftProfile.id = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); | ||||
|     account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(QRegularExpression("[{}-]")); | ||||
|     account->data.minecraftProfile.name = username; | ||||
|     account->data.minecraftProfile.validity = Katabasis::Validity::Certain; | ||||
|     return account; | ||||
| @@ -334,3 +335,32 @@ void MinecraftAccount::incrementUses() | ||||
|         qWarning() << "Profile" << data.profileId() << "is now in use."; | ||||
|     } | ||||
| } | ||||
|  | ||||
| QUuid MinecraftAccount::uuidFromUsername(QString username) { | ||||
|     auto input = QString("OfflinePlayer:%1").arg(username).toUtf8(); | ||||
|  | ||||
|     // basically a reimplementation of Java's UUID#nameUUIDFromBytes | ||||
|     QByteArray digest = QCryptographicHash::hash(input, QCryptographicHash::Md5); | ||||
|  | ||||
| #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) | ||||
|     auto bOr = [](QByteArray& array, int index, char value) { | ||||
|         array[index] = array.at(index) | value; | ||||
|     }; | ||||
|     auto bAnd = [](QByteArray& array, int index, char value) { | ||||
|         array[index] = array.at(index) & value; | ||||
|     }; | ||||
| #else | ||||
|     auto bOr = [](QByteArray& array, qsizetype index, char value) { | ||||
|         array[index] |= value; | ||||
|     }; | ||||
|     auto bAnd = [](QByteArray& array, qsizetype index, char value) { | ||||
|         array[index] &= value; | ||||
|     }; | ||||
| #endif | ||||
|     bAnd(digest, 6, (char) 0x0f); // clear version | ||||
|     bOr(digest, 6, (char) 0x30); // set to version 3 | ||||
|     bAnd(digest, 8, (char) 0x3f); // clear variant | ||||
|     bOr(digest, 8, (char) 0x80); // set to IETF variant | ||||
|  | ||||
|     return QUuid::fromRfc4122(digest); | ||||
| } | ||||
|   | ||||
| @@ -98,6 +98,8 @@ public: /* construction */ | ||||
|     static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json); | ||||
|     static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json); | ||||
|  | ||||
|     static QUuid uuidFromUsername(QString username); | ||||
|  | ||||
|     //! Saves a MinecraftAccount to a JSON object and returns it. | ||||
|     QJsonObject saveToJson() const; | ||||
|  | ||||
|   | ||||
| @@ -45,7 +45,7 @@ void AssetUpdateTask::executeTask() | ||||
|     connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed); | ||||
|     connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); | ||||
|     connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); | ||||
|     connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propogateStepProgress); | ||||
|     connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propagateStepProgress); | ||||
|  | ||||
|     qDebug() << m_inst->name() << ": Starting asset index download"; | ||||
|     downloadJob->start(); | ||||
| @@ -84,7 +84,7 @@ void AssetUpdateTask::assetIndexFinished() | ||||
|         connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed); | ||||
|         connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); | ||||
|         connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); | ||||
|         connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propogateStepProgress); | ||||
|         connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propagateStepProgress); | ||||
|         downloadJob->start(); | ||||
|         return; | ||||
|     } | ||||
|   | ||||
| @@ -75,7 +75,7 @@ void FMLLibrariesTask::executeTask() | ||||
|     connect(dljob.get(), &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); | ||||
|     connect(dljob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); | ||||
|     connect(dljob.get(), &NetJob::progress, this, &FMLLibrariesTask::progress); | ||||
|     connect(dljob.get(), &NetJob::stepProgress, this, &FMLLibrariesTask::propogateStepProgress); | ||||
|     connect(dljob.get(), &NetJob::stepProgress, this, &FMLLibrariesTask::propagateStepProgress); | ||||
|     downloadJob.reset(dljob); | ||||
|     downloadJob->start(); | ||||
| } | ||||
|   | ||||
| @@ -70,7 +70,7 @@ void LibrariesTask::executeTask() | ||||
|     connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed); | ||||
|     connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); | ||||
|     connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress); | ||||
|     connect(downloadJob.get(), &NetJob::stepProgress, this, &LibrariesTask::propogateStepProgress); | ||||
|     connect(downloadJob.get(), &NetJob::stepProgress, this, &LibrariesTask::propagateStepProgress); | ||||
|  | ||||
|     downloadJob->start(); | ||||
| } | ||||
|   | ||||
| @@ -684,7 +684,7 @@ void PackInstallTask::installConfigs() | ||||
|         abortable = true; | ||||
|         setProgress(current, total); | ||||
|     }); | ||||
|     connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); | ||||
|     connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress); | ||||
|     connect(jobPtr.get(), &NetJob::aborted, [&]{ | ||||
|         abortable = false; | ||||
|         jobPtr.reset(); | ||||
| @@ -852,7 +852,7 @@ void PackInstallTask::downloadMods() | ||||
|         abortable = true; | ||||
|         setProgress(current, total); | ||||
|     }); | ||||
|     connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); | ||||
|     connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress); | ||||
|     connect(jobPtr.get(), &NetJob::aborted, [&] | ||||
|     { | ||||
|         abortable = false; | ||||
|   | ||||
| @@ -52,7 +52,7 @@ void Flame::FileResolvingTask::executeTask() | ||||
|         stepProgress(*step_progress); | ||||
|         emitFailed(reason); | ||||
|     }); | ||||
|     connect(m_dljob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress); | ||||
|     connect(m_dljob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress); | ||||
|     connect(m_dljob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) { | ||||
|         qDebug() << "Resolve slug progress" << current << total; | ||||
|         step_progress->update(current, total); | ||||
| @@ -118,7 +118,7 @@ void Flame::FileResolvingTask::netJobFinished() | ||||
|         stepProgress(*step_progress); | ||||
|         emitFailed(reason); | ||||
|     }); | ||||
|     connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress); | ||||
|     connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress); | ||||
|     connect(m_checkJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) { | ||||
|         qDebug() << "Resolve slug progress" << current << total; | ||||
|         step_progress->update(current, total); | ||||
| @@ -195,7 +195,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished() | ||||
|             stepProgress(*step_progress); | ||||
|             emitFailed(reason); | ||||
|         }); | ||||
|         connect(m_slugJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress); | ||||
|         connect(m_slugJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress); | ||||
|         connect(m_slugJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) { | ||||
|             qDebug() << "Resolve slug progress" << current << total; | ||||
|             step_progress->update(current, total); | ||||
|   | ||||
| @@ -57,15 +57,11 @@ | ||||
| #include <QDebug> | ||||
| #include <QFileInfo> | ||||
|  | ||||
| #include "meta/Index.h" | ||||
| #include "meta/VersionList.h" | ||||
| #include "minecraft/World.h" | ||||
| #include "minecraft/mod/tasks/LocalResourceParse.h" | ||||
|  | ||||
|  | ||||
| const static QMap<QString, QString> forgemap = { { "1.2.5", "3.4.9.171" }, | ||||
|                                                  { "1.4.2", "6.0.1.355" }, | ||||
|                                                  { "1.4.7", "6.6.2.534" }, | ||||
|                                                  { "1.5.2", "7.8.1.737" } }; | ||||
|  | ||||
| static const FlameAPI api; | ||||
|  | ||||
| bool FlameCreationTask::abort() | ||||
| @@ -259,6 +255,56 @@ bool FlameCreationTask::updateInstance() | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType, QString loaderVersion, QString mcVersion) | ||||
| { | ||||
|     if (loaderVersion == "recommended") { | ||||
|         auto vlist = APPLICATION->metadataIndex()->get(uid); | ||||
|         if (!vlist) { | ||||
|             setError(tr("Failed to get local metadata index for %1").arg(uid)); | ||||
|             return {}; | ||||
|         } | ||||
|  | ||||
|         if (!vlist->isLoaded()) { | ||||
|             QEventLoop loadVersionLoop; | ||||
|             auto task = vlist->getLoadTask(); | ||||
|             connect(task.get(), &Task::finished, &loadVersionLoop, &QEventLoop::quit); | ||||
|             if (!task->isRunning()) | ||||
|                 task->start(); | ||||
|  | ||||
|             loadVersionLoop.exec(); | ||||
|         } | ||||
|  | ||||
|         for (auto version : vlist->versions()) { | ||||
|             // first recommended build we find, we use. | ||||
|             if (!version->isRecommended()) | ||||
|                 continue; | ||||
|             auto reqs = version->requiredSet(); | ||||
|  | ||||
|             // filter by minecraft version, if the loader depends on a certain version. | ||||
|             // not all mod loaders depend on a given Minecraft version, so we won't do this | ||||
|             // filtering for those loaders. | ||||
|             if (loaderType == "forge") { | ||||
|                 auto iter = std::find_if(reqs.begin(), reqs.end(), [mcVersion](const Meta::Require& req) { | ||||
|                     return req.uid == "net.minecraft" && req.equalsVersion == mcVersion; | ||||
|                 }); | ||||
|                 if (iter == reqs.end()) | ||||
|                     continue; | ||||
|             } | ||||
|             return version->descriptor(); | ||||
|         } | ||||
|  | ||||
|         setError(tr("Failed to find version for %1 loader").arg(loaderType)); | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     if (loaderVersion.isEmpty()) { | ||||
|         emitFailed(tr("No loader version set for modpack!")); | ||||
|         return {}; | ||||
|     } | ||||
|  | ||||
|     return loaderVersion; | ||||
| } | ||||
|  | ||||
| bool FlameCreationTask::createInstance() | ||||
| { | ||||
|     QEventLoop loop; | ||||
| @@ -297,22 +343,29 @@ bool FlameCreationTask::createInstance() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     QString forgeVersion; | ||||
|     QString fabricVersion; | ||||
|     // TODO: is Quilt relevant here? | ||||
|     QString loaderType; | ||||
|     QString loaderUid; | ||||
|     QString loaderVersion; | ||||
|  | ||||
|     for (auto& loader : m_pack.minecraft.modLoaders) { | ||||
|         auto id = loader.id; | ||||
|         if (id.startsWith("forge-")) { | ||||
|             id.remove("forge-"); | ||||
|             forgeVersion = id; | ||||
|             continue; | ||||
|         } | ||||
|         if (id.startsWith("fabric-")) { | ||||
|             loaderType = "forge"; | ||||
|             loaderUid = "net.minecraftforge"; | ||||
|         } else if (loaderType == "fabric") { | ||||
|             id.remove("fabric-"); | ||||
|             fabricVersion = id; | ||||
|             loaderType = "fabric"; | ||||
|             loaderUid = "net.fabricmc.fabric-loader"; | ||||
|         } else if (loaderType == "quilt") { | ||||
|             id.remove("quilt-"); | ||||
|             loaderType = "quilt"; | ||||
|             loaderUid = "org.quiltmc.quilt-loader"; | ||||
|         } else { | ||||
|             logWarning(tr("Unknown mod loader in manifest: %1").arg(id)); | ||||
|             continue; | ||||
|         } | ||||
|         logWarning(tr("Unknown mod loader in manifest: %1").arg(id)); | ||||
|         loaderVersion = id; | ||||
|     } | ||||
|  | ||||
|     QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); | ||||
| @@ -329,19 +382,12 @@ bool FlameCreationTask::createInstance() | ||||
|     auto components = instance.getPackProfile(); | ||||
|     components->buildingFromScratch(); | ||||
|     components->setComponentVersion("net.minecraft", mcVersion, true); | ||||
|     if (!forgeVersion.isEmpty()) { | ||||
|         // FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata. | ||||
|         if (forgeVersion == "recommended") { | ||||
|             if (forgemap.contains(mcVersion)) { | ||||
|                 forgeVersion = forgemap[mcVersion]; | ||||
|             } else { | ||||
|                 logWarning(tr("Could not map recommended Forge version for Minecraft %1").arg(mcVersion)); | ||||
|             } | ||||
|         } | ||||
|         components->setComponentVersion("net.minecraftforge", forgeVersion); | ||||
|     if (!loaderType.isEmpty()) { | ||||
|         auto version = getVersionForLoader(loaderUid, loaderType, loaderVersion, mcVersion); | ||||
|         if (version.isEmpty()) | ||||
|             return false; | ||||
|         components->setComponentVersion(loaderUid, version); | ||||
|     } | ||||
|     if (!fabricVersion.isEmpty()) | ||||
|         components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); | ||||
|  | ||||
|     if (m_instIcon != "default") { | ||||
|         instance.setIconKey(m_instIcon); | ||||
| @@ -386,7 +432,7 @@ bool FlameCreationTask::createInstance() | ||||
|     }); | ||||
|     connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress); | ||||
|     connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus); | ||||
|     connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propogateStepProgress); | ||||
|     connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propagateStepProgress); | ||||
|     connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails); | ||||
|     m_mod_id_resolver->start(); | ||||
|  | ||||
| @@ -502,11 +548,11 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) | ||||
|         m_files_job.reset(); | ||||
|         setError(reason); | ||||
|     }); | ||||
|     connect(m_files_job.get(), &NetJob::progress, this, [this](qint64 current, qint64 total){ | ||||
|     connect(m_files_job.get(), &NetJob::progress, this, [this](qint64 current, qint64 total) { | ||||
|         setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); | ||||
|         setProgress(current, total); | ||||
|     }); | ||||
|     connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propogateStepProgress); | ||||
|     connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propagateStepProgress); | ||||
|     connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); | ||||
|  | ||||
|     setStatus(tr("Downloading mods...")); | ||||
| @@ -545,7 +591,6 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods) | ||||
|     setAbortable(true); | ||||
| } | ||||
|  | ||||
|  | ||||
| void FlameCreationTask::validateZIPResouces() | ||||
| { | ||||
|     qDebug() << "Validating whether resources stored as .zip are in the right place"; | ||||
| @@ -569,7 +614,7 @@ void FlameCreationTask::validateZIPResouces() | ||||
|             return localPath; | ||||
|         }; | ||||
|  | ||||
|         auto installWorld = [this](QString worldPath){ | ||||
|         auto installWorld = [this](QString worldPath) { | ||||
|             qDebug() << "Installing World from" << worldPath; | ||||
|             QFileInfo worldFileInfo(worldPath); | ||||
|             World w(worldFileInfo); | ||||
| @@ -586,29 +631,29 @@ void FlameCreationTask::validateZIPResouces() | ||||
|         QString worldPath; | ||||
|  | ||||
|         switch (type) { | ||||
|             case PackedResourceType::Mod : | ||||
|             case PackedResourceType::Mod: | ||||
|                 validatePath(fileName, targetFolder, "mods"); | ||||
|                 break; | ||||
|             case PackedResourceType::ResourcePack : | ||||
|             case PackedResourceType::ResourcePack: | ||||
|                 validatePath(fileName, targetFolder, "resourcepacks"); | ||||
|                 break; | ||||
|             case PackedResourceType::TexturePack : | ||||
|             case PackedResourceType::TexturePack: | ||||
|                 validatePath(fileName, targetFolder, "texturepacks"); | ||||
|                 break; | ||||
|             case PackedResourceType::DataPack : | ||||
|             case PackedResourceType::DataPack: | ||||
|                 validatePath(fileName, targetFolder, "datapacks"); | ||||
|                 break; | ||||
|             case PackedResourceType::ShaderPack : | ||||
|             case PackedResourceType::ShaderPack: | ||||
|                 // in theroy flame API can't do this but who knows, that *may* change ? | ||||
|                 // better to handle it if it *does* occure in the future | ||||
|                 validatePath(fileName, targetFolder, "shaderpacks"); | ||||
|                 break; | ||||
|             case PackedResourceType::WorldSave : | ||||
|             case PackedResourceType::WorldSave: | ||||
|                 worldPath = validatePath(fileName, targetFolder, "saves"); | ||||
|                 installWorld(worldPath); | ||||
|                 break; | ||||
|             case PackedResourceType::UNKNOWN : | ||||
|             default : | ||||
|             case PackedResourceType::UNKNOWN: | ||||
|             default: | ||||
|                 qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is."; | ||||
|                 break; | ||||
|         } | ||||
|   | ||||
| @@ -57,10 +57,7 @@ class FlameCreationTask final : public InstanceCreationTask { | ||||
|                       QString id, | ||||
|                       QString version_id, | ||||
|                       QString original_instance_id = {}) | ||||
|         : InstanceCreationTask() | ||||
|         , m_parent(parent) | ||||
|         , m_managed_id(std::move(id)) | ||||
|         , m_managed_version_id(std::move(version_id)) | ||||
|         : InstanceCreationTask(), m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(version_id)) | ||||
|     { | ||||
|         setStagingPath(staging_path); | ||||
|         setParentSettings(global_settings); | ||||
| @@ -78,6 +75,7 @@ class FlameCreationTask final : public InstanceCreationTask { | ||||
|     void setupDownloadJob(QEventLoop&); | ||||
|     void copyBlockedMods(QList<BlockedMod> const& blocked_mods); | ||||
|     void validateZIPResouces(); | ||||
|     QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion); | ||||
|  | ||||
|    private: | ||||
|     QWidget* m_parent = nullptr; | ||||
|   | ||||
| @@ -166,7 +166,7 @@ void FlamePackExportTask::collectHashes() | ||||
|         stepProgress(*progressStep); | ||||
|         emitFailed(reason); | ||||
|     }); | ||||
|     connect(hashingTask.get(), &Task::stepProgress, this, &FlamePackExportTask::propogateStepProgress); | ||||
|     connect(hashingTask.get(), &Task::stepProgress, this, &FlamePackExportTask::propagateStepProgress); | ||||
|  | ||||
|     connect(hashingTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { | ||||
|         progressStep->update(current, total); | ||||
|   | ||||
| @@ -81,7 +81,7 @@ void PackInstallTask::downloadPack() | ||||
|  | ||||
|     connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip); | ||||
|     connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed); | ||||
|     connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); | ||||
|     connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress); | ||||
|     connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::emitAborted); | ||||
|  | ||||
|     netJobContainer->start(); | ||||
|   | ||||
| @@ -267,7 +267,7 @@ bool ModrinthCreationTask::createInstance() | ||||
|         setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); | ||||
|         setProgress(current, total);  | ||||
|     }); | ||||
|     connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propogateStepProgress); | ||||
|     connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propagateStepProgress); | ||||
|  | ||||
|     setStatus(tr("Downloading mods...")); | ||||
|     m_files_job->start(); | ||||
|   | ||||
| @@ -50,7 +50,7 @@ void Technic::SingleZipPackInstallTask::executeTask() | ||||
|     auto job = m_filesNetJob.get(); | ||||
|     connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded); | ||||
|     connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged); | ||||
|     connect(job, &NetJob::stepProgress, this, &Technic::SingleZipPackInstallTask::propogateStepProgress); | ||||
|     connect(job, &NetJob::stepProgress, this, &Technic::SingleZipPackInstallTask::propagateStepProgress); | ||||
|     connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed); | ||||
|     m_filesNetJob->start(); | ||||
| } | ||||
|   | ||||
| @@ -126,7 +126,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded() | ||||
|  | ||||
|     connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded); | ||||
|     connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged); | ||||
|     connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &Technic::SolderPackInstallTask::propogateStepProgress); | ||||
|     connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &Technic::SolderPackInstallTask::propagateStepProgress); | ||||
|     connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); | ||||
|     connect(m_filesNetJob.get(), &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted); | ||||
|     m_filesNetJob->start(); | ||||
|   | ||||
| @@ -161,7 +161,7 @@ void Task::emitSucceeded() | ||||
|     emit finished(); | ||||
| } | ||||
|  | ||||
| void Task::propogateStepProgress(TaskStepProgress const& task_progress) | ||||
| void Task::propagateStepProgress(TaskStepProgress const& task_progress) | ||||
| { | ||||
|     emit stepProgress(task_progress); | ||||
| } | ||||
|   | ||||
| @@ -167,7 +167,7 @@ class Task : public QObject, public QRunnable { | ||||
|     virtual void emitAborted(); | ||||
|     virtual void emitFailed(QString reason = ""); | ||||
|  | ||||
|     virtual void propogateStepProgress(TaskStepProgress const& task_progress); | ||||
|     virtual void propagateStepProgress(TaskStepProgress const& task_progress); | ||||
|  | ||||
|    public slots: | ||||
|     void setStatus(const QString& status); | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> | ||||
|  *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -41,6 +42,9 @@ | ||||
| #include <QFileSystemModel> | ||||
| #include <QMessageBox> | ||||
| #include "FileIgnoreProxy.h" | ||||
| #include "QObjectPtr.h" | ||||
| #include "ui/dialogs/CustomMessageBox.h" | ||||
| #include "ui/dialogs/ProgressDialog.h" | ||||
| #include "ui_ExportInstanceDialog.h" | ||||
|  | ||||
| #include <FileSystem.h> | ||||
| @@ -66,13 +70,15 @@ ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget* parent | ||||
|     auto prefix = QDir(instance->instanceRoot()).relativeFilePath(instance->gameRoot()); | ||||
|     proxyModel->ignoreFilesWithPath().insert({ FS::PathCombine(prefix, "logs"), FS::PathCombine(prefix, "crash-reports") }); | ||||
|     proxyModel->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" }); | ||||
|     proxyModel->ignoreFilesWithPath().insert( | ||||
|         { FS::PathCombine(prefix, ".cache"), FS::PathCombine(prefix, ".fabric"), FS::PathCombine(prefix, ".quilt") }); | ||||
|     loadPackIgnore(); | ||||
|  | ||||
|     ui->treeView->setModel(proxyModel); | ||||
|     ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root))); | ||||
|     ui->treeView->sortByColumn(0, Qt::AscendingOrder); | ||||
|  | ||||
|     connect(proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int))); | ||||
|     connect(proxyModel, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(rowsInserted(QModelIndex, int, int))); | ||||
|  | ||||
|     model->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); | ||||
|     model->setRootPath(root); | ||||
| @@ -92,32 +98,26 @@ void SaveIcon(InstancePtr m_instance) | ||||
|     auto iconKey = m_instance->iconKey(); | ||||
|     auto iconList = APPLICATION->icons(); | ||||
|     auto mmcIcon = iconList->icon(iconKey); | ||||
|     if(!mmcIcon || mmcIcon->isBuiltIn()) { | ||||
|     if (!mmcIcon || mmcIcon->isBuiltIn()) { | ||||
|         return; | ||||
|     } | ||||
|     auto path = mmcIcon->getFilePath(); | ||||
|     if(!path.isNull()) { | ||||
|         QFileInfo inInfo (path); | ||||
|         FS::copy(path, FS::PathCombine(m_instance->instanceRoot(), inInfo.fileName())) (); | ||||
|     if (!path.isNull()) { | ||||
|         QFileInfo inInfo(path); | ||||
|         FS::copy(path, FS::PathCombine(m_instance->instanceRoot(), inInfo.fileName()))(); | ||||
|         return; | ||||
|     } | ||||
|     auto & image = mmcIcon->m_images[mmcIcon->type()]; | ||||
|     auto & icon = image.icon; | ||||
|     auto& image = mmcIcon->m_images[mmcIcon->type()]; | ||||
|     auto& icon = image.icon; | ||||
|     auto sizes = icon.availableSizes(); | ||||
|     if(sizes.size() == 0) | ||||
|     { | ||||
|     if (sizes.size() == 0) { | ||||
|         return; | ||||
|     } | ||||
|     auto areaOf = [](QSize size) | ||||
|     { | ||||
|         return size.width() * size.height(); | ||||
|     }; | ||||
|     auto areaOf = [](QSize size) { return size.width() * size.height(); }; | ||||
|     QSize largest = sizes[0]; | ||||
|     // find variant with largest area | ||||
|     for(auto size: sizes) | ||||
|     { | ||||
|         if(areaOf(largest) < areaOf(size)) | ||||
|         { | ||||
|     for (auto size : sizes) { | ||||
|         if (areaOf(largest) < areaOf(size)) { | ||||
|             largest = size; | ||||
|         } | ||||
|     } | ||||
| @@ -125,16 +125,15 @@ void SaveIcon(InstancePtr m_instance) | ||||
|     pixmap.save(FS::PathCombine(m_instance->instanceRoot(), iconKey + ".png")); | ||||
| } | ||||
|  | ||||
| bool ExportInstanceDialog::doExport() | ||||
| void ExportInstanceDialog::doExport() | ||||
| { | ||||
|     auto name = FS::RemoveInvalidFilenameChars(m_instance->name()); | ||||
|  | ||||
|     const QString output = QFileDialog::getSaveFileName( | ||||
|         this, tr("Export %1").arg(m_instance->name()), | ||||
|         FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr); | ||||
|     if (output.isEmpty()) | ||||
|     { | ||||
|         return false; | ||||
|     const QString output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(m_instance->name()), | ||||
|                                                         FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr); | ||||
|     if (output.isEmpty()) { | ||||
|         QDialog::done(QDialog::Rejected); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     SaveIcon(m_instance); | ||||
| @@ -143,46 +142,40 @@ bool ExportInstanceDialog::doExport() | ||||
|     if (!MMCZip::collectFileListRecursively(m_instance->instanceRoot(), nullptr, &files, | ||||
|                                             std::bind(&FileIgnoreProxy::filterFile, proxyModel, std::placeholders::_1))) { | ||||
|         QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); | ||||
|         return false; | ||||
|         QDialog::done(QDialog::Rejected); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (!MMCZip::compressDirFiles(output, m_instance->instanceRoot(), files, true)) | ||||
|     { | ||||
|         QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); | ||||
|         return false; | ||||
|     } | ||||
|     return true; | ||||
|     auto task = makeShared<MMCZip::ExportToZipTask>(output, m_instance->instanceRoot(), files, "", true); | ||||
|  | ||||
|     connect(task.get(), &Task::failed, this, | ||||
|             [this, output](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); | ||||
|     connect(task.get(), &Task::finished, this, [task] { task->deleteLater(); }); | ||||
|  | ||||
|     ProgressDialog progress(this); | ||||
|     progress.setSkipButton(true, tr("Abort")); | ||||
|     auto result = progress.execWithTask(task.get()); | ||||
|     QDialog::done(result); | ||||
| } | ||||
|  | ||||
| void ExportInstanceDialog::done(int result) | ||||
| { | ||||
|     savePackIgnore(); | ||||
|     if (result == QDialog::Accepted) | ||||
|     { | ||||
|         if (doExport()) | ||||
|         { | ||||
|             QDialog::done(QDialog::Accepted); | ||||
|             return; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|     if (result == QDialog::Accepted) { | ||||
|         doExport(); | ||||
|         return; | ||||
|     } | ||||
|     QDialog::done(result); | ||||
| } | ||||
|  | ||||
| void ExportInstanceDialog::rowsInserted(QModelIndex parent, int top, int bottom) | ||||
| { | ||||
|     //WARNING: possible off-by-one? | ||||
|     for(int i = top; i < bottom; i++) | ||||
|     { | ||||
|     // WARNING: possible off-by-one? | ||||
|     for (int i = top; i < bottom; i++) { | ||||
|         auto node = proxyModel->index(i, 0, parent); | ||||
|         if(proxyModel->shouldExpand(node)) | ||||
|         { | ||||
|         if (proxyModel->shouldExpand(node)) { | ||||
|             auto expNode = node.parent(); | ||||
|             if(!expNode.isValid()) | ||||
|             { | ||||
|             if (!expNode.isValid()) { | ||||
|                 continue; | ||||
|             } | ||||
|             ui->treeView->expand(node); | ||||
| @@ -199,8 +192,7 @@ void ExportInstanceDialog::loadPackIgnore() | ||||
| { | ||||
|     auto filename = ignoreFileName(); | ||||
|     QFile ignoreFile(filename); | ||||
|     if(!ignoreFile.open(QIODevice::ReadOnly)) | ||||
|     { | ||||
|     if (!ignoreFile.open(QIODevice::ReadOnly)) { | ||||
|         return; | ||||
|     } | ||||
|     auto data = ignoreFile.readAll(); | ||||
| @@ -216,12 +208,9 @@ void ExportInstanceDialog::savePackIgnore() | ||||
| { | ||||
|     auto data = proxyModel->blockedPaths().toStringList().join('\n').toUtf8(); | ||||
|     auto filename = ignoreFileName(); | ||||
|     try | ||||
|     { | ||||
|     try { | ||||
|         FS::write(filename, data); | ||||
|     } | ||||
|     catch (const Exception &e) | ||||
|     { | ||||
|     } catch (const Exception& e) { | ||||
|         qWarning() << e.cause(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> | ||||
|  *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -38,39 +39,37 @@ | ||||
| #include <QDialog> | ||||
| #include <QModelIndex> | ||||
| #include <memory> | ||||
| #include "FileIgnoreProxy.h" | ||||
| #include "FastFileIconProvider.h" | ||||
| #include "FileIgnoreProxy.h" | ||||
|  | ||||
| class BaseInstance; | ||||
| typedef std::shared_ptr<BaseInstance> InstancePtr; | ||||
|  | ||||
| namespace Ui | ||||
| { | ||||
| namespace Ui { | ||||
| class ExportInstanceDialog; | ||||
| } | ||||
|  | ||||
| class ExportInstanceDialog : public QDialog | ||||
| { | ||||
| class ExportInstanceDialog : public QDialog { | ||||
|     Q_OBJECT | ||||
|  | ||||
| public: | ||||
|     explicit ExportInstanceDialog(InstancePtr instance, QWidget *parent = 0); | ||||
|    public: | ||||
|     explicit ExportInstanceDialog(InstancePtr instance, QWidget* parent = 0); | ||||
|     ~ExportInstanceDialog(); | ||||
|  | ||||
|     virtual void done(int result); | ||||
|  | ||||
| private: | ||||
|     bool doExport(); | ||||
|    private: | ||||
|     void doExport(); | ||||
|     void loadPackIgnore(); | ||||
|     void savePackIgnore(); | ||||
|     QString ignoreFileName(); | ||||
|  | ||||
| private: | ||||
|     Ui::ExportInstanceDialog *ui; | ||||
|    private: | ||||
|     Ui::ExportInstanceDialog* ui; | ||||
|     InstancePtr m_instance; | ||||
|     FileIgnoreProxy * proxyModel; | ||||
|     FileIgnoreProxy* proxyModel; | ||||
|     FastFileIconProvider icons; | ||||
|  | ||||
| private slots: | ||||
|    private slots: | ||||
|     void rowsInserted(QModelIndex parent, int top, int bottom); | ||||
| }; | ||||
|   | ||||
| @@ -61,7 +61,7 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla | ||||
|     // use the game root - everything outside cannot be exported | ||||
|     const QDir root(instance->gameRoot()); | ||||
|     proxy = new FileIgnoreProxy(instance->gameRoot(), this); | ||||
|     proxy->ignoreFilesWithPath().insert({ "logs", "crash-reports" }); | ||||
|     proxy->ignoreFilesWithPath().insert({ "logs", "crash-reports", ".cache", ".fabric", ".quilt" }); | ||||
|     proxy->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" }); | ||||
|     proxy->setSourceModel(model); | ||||
|  | ||||
|   | ||||
| @@ -48,7 +48,6 @@ | ||||
| #include <QAccessible> | ||||
|  | ||||
| #include "VisualGroup.h" | ||||
| #include "ui/themes/ThemeManager.h" | ||||
| #include <QDebug> | ||||
|  | ||||
| #include <Application.h> | ||||
| @@ -504,7 +503,7 @@ void InstanceView::setPaintCat(bool visible) | ||||
| { | ||||
|     m_catVisible = visible; | ||||
|     if (visible) | ||||
|         m_catPixmap.load(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage())); | ||||
|         m_catPixmap.load(APPLICATION->getCatPack()); | ||||
|     else | ||||
|         m_catPixmap = QPixmap(); | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  PolyMC - Minecraft Launcher | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||
|  *  Copyright (C) 2023 Tayou <git@tayou.org> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -35,22 +36,18 @@ | ||||
|  | ||||
| #include "VisualGroup.h" | ||||
|  | ||||
| #include <QApplication> | ||||
| #include <QDebug> | ||||
| #include <QModelIndex> | ||||
| #include <QPainter> | ||||
| #include <QtMath> | ||||
| #include <QApplication> | ||||
| #include <QDebug> | ||||
| #include <utility> | ||||
|  | ||||
| #include "InstanceView.h" | ||||
|  | ||||
| VisualGroup::VisualGroup(const QString &text, InstanceView *view) : view(view), text(text), collapsed(false) | ||||
| { | ||||
| } | ||||
| VisualGroup::VisualGroup(QString text, InstanceView* view) : view(view), text(std::move(text)), collapsed(false) {} | ||||
|  | ||||
| VisualGroup::VisualGroup(const VisualGroup *other) | ||||
|     : view(other->view), text(other->text), collapsed(other->collapsed) | ||||
| { | ||||
| } | ||||
| VisualGroup::VisualGroup(const VisualGroup* other) : view(other->view), text(other->text), collapsed(other->collapsed) {} | ||||
|  | ||||
| void VisualGroup::update() | ||||
| { | ||||
| @@ -64,13 +61,11 @@ void VisualGroup::update() | ||||
|     int positionInRow = 0; | ||||
|     int currentRow = 0; | ||||
|     int offsetFromTop = 0; | ||||
|     for (auto item: temp_items) | ||||
|     { | ||||
|         if(positionInRow == itemsPerRow) | ||||
|         { | ||||
|     for (auto item : temp_items) { | ||||
|         if (positionInRow == itemsPerRow) { | ||||
|             rows[currentRow].height = maxRowHeight; | ||||
|             rows[currentRow].top = offsetFromTop; | ||||
|             currentRow ++; | ||||
|             currentRow++; | ||||
|             offsetFromTop += maxRowHeight + 5; | ||||
|             positionInRow = 0; | ||||
|             maxRowHeight = 0; | ||||
| @@ -83,8 +78,7 @@ void VisualGroup::update() | ||||
| #endif | ||||
|  | ||||
|         auto itemHeight = view->itemDelegate()->sizeHint(viewItemOption, item).height(); | ||||
|         if(itemHeight > maxRowHeight) | ||||
|         { | ||||
|         if (itemHeight > maxRowHeight) { | ||||
|             maxRowHeight = itemHeight; | ||||
|         } | ||||
|         rows[currentRow].items.append(item); | ||||
| @@ -94,16 +88,13 @@ void VisualGroup::update() | ||||
|     rows[currentRow].top = offsetFromTop; | ||||
| } | ||||
|  | ||||
| QPair<int, int> VisualGroup::positionOf(const QModelIndex &index) const | ||||
| QPair<int, int> VisualGroup::positionOf(const QModelIndex& index) const | ||||
| { | ||||
|     int y = 0; | ||||
|     for (auto & row: rows) | ||||
|     { | ||||
|         for(auto x = 0; x < row.items.size(); x++) | ||||
|         { | ||||
|             if(row.items[x] == index) | ||||
|             { | ||||
|                 return qMakePair(x,y); | ||||
|     for (auto& row : rows) { | ||||
|         for (auto x = 0; x < row.items.size(); x++) { | ||||
|             if (row.items[x] == index) { | ||||
|                 return qMakePair(x, y); | ||||
|             } | ||||
|         } | ||||
|         y++; | ||||
| @@ -112,193 +103,109 @@ QPair<int, int> VisualGroup::positionOf(const QModelIndex &index) const | ||||
|     return qMakePair(0, 0); | ||||
| } | ||||
|  | ||||
| int VisualGroup::rowTopOf(const QModelIndex &index) const | ||||
| int VisualGroup::rowTopOf(const QModelIndex& index) const | ||||
| { | ||||
|     auto position = positionOf(index); | ||||
|     return rows[position.second].top; | ||||
| } | ||||
|  | ||||
| int VisualGroup::rowHeightOf(const QModelIndex &index) const | ||||
| int VisualGroup::rowHeightOf(const QModelIndex& index) const | ||||
| { | ||||
|     auto position = positionOf(index); | ||||
|     return rows[position.second].height; | ||||
| } | ||||
|  | ||||
| VisualGroup::HitResults VisualGroup::hitScan(const QPoint &pos) const | ||||
| VisualGroup::HitResults VisualGroup::hitScan(const QPoint& pos) const | ||||
| { | ||||
|     VisualGroup::HitResults results = VisualGroup::NoHit; | ||||
|     int y_start = verticalPosition(); | ||||
|     int body_start = y_start + headerHeight(); | ||||
|     int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5? | ||||
|     int body_end = body_start + contentHeight(); | ||||
|     int y = pos.y(); | ||||
|     // int x = pos.x(); | ||||
|     if (y < y_start) | ||||
|     { | ||||
|     if (y < y_start) { | ||||
|         results = VisualGroup::NoHit; | ||||
|     } | ||||
|     else if (y < body_start) | ||||
|     { | ||||
|     } else if (y < body_start) { | ||||
|         results = VisualGroup::HeaderHit; | ||||
|         int collapseSize = headerHeight() - 4; | ||||
|  | ||||
|         // the icon | ||||
|         QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize); | ||||
|         if (iconRect.contains(pos)) | ||||
|         { | ||||
|         QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, view->width() - 4, collapseSize); | ||||
|         if (iconRect.contains(pos)) { | ||||
|             results |= VisualGroup::CheckboxHit; | ||||
|         } | ||||
|     } | ||||
|     else if (y < body_end) | ||||
|     { | ||||
|     } else if (y < body_end) { | ||||
|         results |= VisualGroup::BodyHit; | ||||
|     } | ||||
|     return results; | ||||
| } | ||||
|  | ||||
| void VisualGroup::drawHeader(QPainter *painter, const QStyleOptionViewItem &option) | ||||
| void VisualGroup::drawHeader(QPainter* painter, const QStyleOptionViewItem& option) const | ||||
| { | ||||
|     painter->setRenderHint(QPainter::Antialiasing); | ||||
|  | ||||
|     const QRect optRect = option.rect; | ||||
|     QRect optRect = option.rect; | ||||
|     optRect.setTop(optRect.top() + 7); | ||||
|     QFont font(QApplication::font()); | ||||
|     font.setBold(true); | ||||
|     const QFontMetrics fontMetrics = QFontMetrics(font); | ||||
|     painter->setFont(font); | ||||
|  | ||||
|     QColor outlineColor = option.palette.text().color(); | ||||
|     outlineColor.setAlphaF(0.35); | ||||
|     QPen pen; | ||||
|     pen.setWidth(2); | ||||
|     QColor penColor = option.palette.text().color(); | ||||
|     penColor.setAlphaF(0.6); | ||||
|     pen.setColor(penColor); | ||||
|     painter->setPen(pen); | ||||
|     painter->setRenderHint(QPainter::Antialiasing); | ||||
|  | ||||
|     //BEGIN: top left corner | ||||
|     // sizes and offsets, to keep things consistent below | ||||
|     int arrowOffsetLeft = fontMetrics.height() / 2 + 7; | ||||
|     int textOffsetLeft = arrowOffsetLeft * 2; | ||||
|     int arrowSize = 6; | ||||
|     int centerHeight = optRect.top() + fontMetrics.height() / 2; | ||||
|  | ||||
|     // BEGIN: arrow | ||||
|     { | ||||
|         painter->save(); | ||||
|         painter->setPen(outlineColor); | ||||
|         const QPointF topLeft(optRect.topLeft()); | ||||
|         QRectF arc(topLeft, QSizeF(4, 4)); | ||||
|         arc.translate(0.5, 0.5); | ||||
|         painter->drawArc(arc, 1440, 1440); | ||||
|         painter->restore(); | ||||
|     } | ||||
|     //END: top left corner | ||||
|  | ||||
|     //BEGIN: left vertical line | ||||
|     { | ||||
|         QPoint start(optRect.topLeft()); | ||||
|         start.ry() += 3; | ||||
|         QPoint verticalGradBottom(optRect.topLeft()); | ||||
|         verticalGradBottom.ry() += fontMetrics.height() + 5; | ||||
|         QLinearGradient gradient(start, verticalGradBottom); | ||||
|         gradient.setColorAt(0, outlineColor); | ||||
|         gradient.setColorAt(1, Qt::transparent); | ||||
|         painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); | ||||
|     } | ||||
|     //END: left vertical line | ||||
|  | ||||
|     //BEGIN: horizontal line | ||||
|     { | ||||
|         QPoint start(optRect.topLeft()); | ||||
|         start.rx() += 3; | ||||
|         QPoint horizontalGradTop(optRect.topLeft()); | ||||
|         horizontalGradTop.rx() += optRect.width() - 6; | ||||
|         painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); | ||||
|     } | ||||
|     //END: horizontal line | ||||
|  | ||||
|     //BEGIN: top right corner | ||||
|     { | ||||
|         painter->save(); | ||||
|         painter->setPen(outlineColor); | ||||
|         QPointF topRight(optRect.topRight()); | ||||
|         topRight.rx() -= 4; | ||||
|         QRectF arc(topRight, QSizeF(4, 4)); | ||||
|         arc.translate(0.5, 0.5); | ||||
|         painter->drawArc(arc, 0, 1440); | ||||
|         painter->restore(); | ||||
|     } | ||||
|     //END: top right corner | ||||
|  | ||||
|     //BEGIN: right vertical line | ||||
|     { | ||||
|         QPoint start(optRect.topRight()); | ||||
|         start.ry() += 3; | ||||
|         QPoint verticalGradBottom(optRect.topRight()); | ||||
|         verticalGradBottom.ry() += fontMetrics.height() + 5; | ||||
|         QLinearGradient gradient(start, verticalGradBottom); | ||||
|         gradient.setColorAt(0, outlineColor); | ||||
|         gradient.setColorAt(1, Qt::transparent); | ||||
|         painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); | ||||
|     } | ||||
|     //END: right vertical line | ||||
|  | ||||
|     //BEGIN: checkboxy thing | ||||
|     { | ||||
|         painter->save(); | ||||
|         painter->setRenderHint(QPainter::Antialiasing, false); | ||||
|         painter->setFont(font); | ||||
|         QColor penColor(option.palette.text().color()); | ||||
|         penColor.setAlphaF(0.6); | ||||
|         painter->setPen(penColor); | ||||
|         QRect iconSubRect(option.rect); | ||||
|         iconSubRect.setTop(iconSubRect.top() + 7); | ||||
|         iconSubRect.setLeft(iconSubRect.left() + 7); | ||||
|  | ||||
|         int sizing = fontMetrics.height(); | ||||
|         int even = ( (sizing - 1) % 2 ); | ||||
|  | ||||
|         iconSubRect.setHeight(sizing - even); | ||||
|         iconSubRect.setWidth(sizing - even); | ||||
|         painter->drawRect(iconSubRect); | ||||
|  | ||||
|  | ||||
|         /* | ||||
|         if(collapsed) | ||||
|             painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "+"); | ||||
|         else | ||||
|             painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "-"); | ||||
|         */ | ||||
|         painter->setBrush(option.palette.text()); | ||||
|         painter->fillRect(iconSubRect.x(), iconSubRect.y() + iconSubRect.height() / 2, | ||||
|                           iconSubRect.width(), 2, penColor); | ||||
|         if (collapsed) | ||||
|         { | ||||
|             painter->fillRect(iconSubRect.x() + iconSubRect.width() / 2, iconSubRect.y(), 2, | ||||
|                               iconSubRect.height(), penColor); | ||||
|         QPolygon arrowPolygon; | ||||
|         if (collapsed) { | ||||
|             arrowPolygon << QPoint(arrowOffsetLeft - arrowSize / 2, centerHeight - arrowSize) | ||||
|                          << QPoint(arrowOffsetLeft + arrowSize / 2, centerHeight) | ||||
|                          << QPoint(arrowOffsetLeft - arrowSize / 2, centerHeight + arrowSize); | ||||
|             painter->drawPolyline(arrowPolygon); | ||||
|         } else { | ||||
|             arrowPolygon << QPoint(arrowOffsetLeft - arrowSize, centerHeight - arrowSize / 2) | ||||
|                          << QPoint(arrowOffsetLeft, centerHeight + arrowSize / 2) | ||||
|                          << QPoint(arrowOffsetLeft + arrowSize, centerHeight - arrowSize / 2); | ||||
|             painter->drawPolyline(arrowPolygon); | ||||
|         } | ||||
|  | ||||
|         painter->restore(); | ||||
|     } | ||||
|     //END: checkboxy thing | ||||
|     // END: arrow | ||||
|  | ||||
|     //BEGIN: text | ||||
|     // BEGIN: text | ||||
|     { | ||||
|         QRect textRect(option.rect); | ||||
|         textRect.setTop(textRect.top() + 7); | ||||
|         textRect.setLeft(textRect.left() + 7 + fontMetrics.height() + 7); | ||||
|         QRect textRect(optRect); | ||||
|         textRect.setTop(textRect.top()); | ||||
|         textRect.setLeft(textOffsetLeft); | ||||
|         textRect.setHeight(fontMetrics.height()); | ||||
|         textRect.setRight(textRect.right() - 7); | ||||
|  | ||||
|         painter->save(); | ||||
|         painter->setFont(font); | ||||
|         QColor penColor(option.palette.text().color()); | ||||
|         penColor.setAlphaF(0.6); | ||||
|         painter->setPen(penColor); | ||||
|         painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); | ||||
|         painter->restore(); | ||||
|         painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, !text.isEmpty() ? text : QObject::tr("Ungrouped")); | ||||
|     } | ||||
|     //END: text | ||||
|     // END: text | ||||
| } | ||||
|  | ||||
| int VisualGroup::totalHeight() const | ||||
| { | ||||
|     return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'? | ||||
|     return headerHeight() + contentHeight(); | ||||
| } | ||||
|  | ||||
| int VisualGroup::headerHeight() const | ||||
| int VisualGroup::headerHeight() | ||||
| { | ||||
|     QFont font(QApplication::font()); | ||||
|     font.setBold(true); | ||||
|     QFontMetrics fontMetrics(font); | ||||
|  | ||||
|     const int height = fontMetrics.height() + 1 /* 1 pixel-width gradient */ | ||||
|                                             + 11 /* top and bottom separation */; | ||||
|                        + 11 /* top and bottom separation */; | ||||
|     return height; | ||||
|     /* | ||||
|     int raw = view->viewport()->fontMetrics().height() + 4; | ||||
| @@ -311,8 +218,7 @@ int VisualGroup::headerHeight() const | ||||
|  | ||||
| int VisualGroup::contentHeight() const | ||||
| { | ||||
|     if (collapsed) | ||||
|     { | ||||
|     if (collapsed) { | ||||
|         return 0; | ||||
|     } | ||||
|     auto last = rows[numRows() - 1]; | ||||
| @@ -321,7 +227,7 @@ int VisualGroup::contentHeight() const | ||||
|  | ||||
| int VisualGroup::numRows() const | ||||
| { | ||||
|     return rows.size(); | ||||
|     return (int)rows.size(); | ||||
| } | ||||
|  | ||||
| int VisualGroup::verticalPosition() const | ||||
| @@ -332,11 +238,9 @@ int VisualGroup::verticalPosition() const | ||||
| QList<QModelIndex> VisualGroup::items() const | ||||
| { | ||||
|     QList<QModelIndex> indices; | ||||
|     for (int i = 0; i < view->model()->rowCount(); ++i) | ||||
|     { | ||||
|     for (int i = 0; i < view->model()->rowCount(); ++i) { | ||||
|         const QModelIndex index = view->model()->index(i, 0); | ||||
|         if (index.data(InstanceViewRoles::GroupRole).toString() == text) | ||||
|         { | ||||
|         if (index.data(InstanceViewRoles::GroupRole).toString() == text) { | ||||
|             indices.append(index); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -1,16 +1,36 @@ | ||||
| /* Copyright 2013-2021 MultiMC Contributors | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2023 Tayou <git@tayou.org> | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|  *  the Free Software Foundation, version 3. | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  *  This program is distributed in the hope that it will be useful, | ||||
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  *  GNU General Public License for more details. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  *  You should have received a copy of the GNU General Public License | ||||
|  *  along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  * This file incorporates work covered by the following copyright and | ||||
|  * permission notice: | ||||
|  * | ||||
|  *      Copyright 2013-2021 MultiMC Contributors | ||||
|  * | ||||
|  *      Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  *      you may not use this file except in compliance with the License. | ||||
|  *      You may obtain a copy of the License at | ||||
|  * | ||||
|  *          http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  *      Unless required by applicable law or agreed to in writing, software | ||||
|  *      distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  *      See the License for the specific language governing permissions and | ||||
|  *      limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
| @@ -42,8 +62,8 @@ struct VisualRow | ||||
| struct VisualGroup | ||||
| { | ||||
| /* constructors */ | ||||
|     VisualGroup(const QString &text, InstanceView *view); | ||||
|     VisualGroup(const VisualGroup *other); | ||||
|     VisualGroup(QString text, InstanceView *view); | ||||
|     explicit VisualGroup(const VisualGroup *other); | ||||
|  | ||||
| /* data */ | ||||
|     InstanceView *view = nullptr; | ||||
| @@ -58,13 +78,13 @@ struct VisualGroup | ||||
|     void update(); | ||||
|  | ||||
|     /// draw the header at y-position. | ||||
|     void drawHeader(QPainter *painter, const QStyleOptionViewItem &option); | ||||
|     void drawHeader(QPainter *painter, const QStyleOptionViewItem &option) const; | ||||
|  | ||||
|     /// height of the group, in total. includes a small bit of padding. | ||||
|     int totalHeight() const; | ||||
|  | ||||
|     /// height of the group header, in pixels | ||||
|     int headerHeight() const; | ||||
|     static int headerHeight() ; | ||||
|  | ||||
|     /// height of the group content, in pixels | ||||
|     int contentHeight() const; | ||||
|   | ||||
| @@ -30,7 +30,7 @@ | ||||
|      </property> | ||||
|      <widget class="QWidget" name="tab"> | ||||
|       <attribute name="title"> | ||||
|        <string notr="true">Services</string> | ||||
|        <string>Services</string> | ||||
|       </attribute> | ||||
|       <layout class="QVBoxLayout" name="verticalLayout_2"> | ||||
|        <item> | ||||
|   | ||||
| @@ -159,19 +159,6 @@ void AccountListPage::on_actionAddMojang_triggered() | ||||
|  | ||||
| void AccountListPage::on_actionAddMicrosoft_triggered() | ||||
| { | ||||
|     if(BuildConfig.BUILD_PLATFORM == "osx64") { | ||||
|         CustomMessageBox::selectable( | ||||
|             this, | ||||
|             tr("Microsoft Accounts not available"), | ||||
|             //: %1 refers to the launcher itself | ||||
|             tr( | ||||
|                 "Microsoft accounts are only usable on macOS 10.13 or newer, with fully updated %1.\n\n" | ||||
|                 "Please update both your operating system and %1." | ||||
|             ).arg(BuildConfig.LAUNCHER_DISPLAYNAME), | ||||
|             QMessageBox::Warning | ||||
|         )->exec(); | ||||
|         return; | ||||
|     } | ||||
|     MinecraftAccountPtr account = MSALoginDialog::newAccount( | ||||
|         this, | ||||
|         tr("Please enter your Mojang account email and password to add your account.") | ||||
|   | ||||
| @@ -58,7 +58,7 @@ | ||||
|           <item row="2" column="0"> | ||||
|            <widget class="QLabel" name="labelPermGen"> | ||||
|             <property name="text"> | ||||
|              <string notr="true">&PermGen:</string> | ||||
|              <string>&PermGen:</string> | ||||
|             </property> | ||||
|             <property name="buddy"> | ||||
|              <cstring>permGenSpinBox</cstring> | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> | ||||
|  *  Copyright (c) 2022 dada513 <dada513@protonmail.com> | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|   | ||||
| @@ -62,7 +62,7 @@ public: | ||||
|  | ||||
|     QString displayName() const override | ||||
|     { | ||||
|         return "Launcher"; | ||||
|         return tr("Launcher"); | ||||
|     } | ||||
|     QIcon icon() const override | ||||
|     { | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| /* | ||||
|  *  PolyMC - Minecraft Launcher | ||||
|  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> | ||||
|  *  Copyright (C) 2023 seth <getchoo at tuta dot io> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -99,6 +100,9 @@ void MinecraftPage::applySettings() | ||||
|     // Miscellaneous | ||||
|     s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked()); | ||||
|     s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked()); | ||||
|  | ||||
|     // Mod loader settings | ||||
|     s->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked()); | ||||
| } | ||||
|  | ||||
| void MinecraftPage::loadSettings() | ||||
| @@ -137,6 +141,8 @@ void MinecraftPage::loadSettings() | ||||
|  | ||||
|     ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool()); | ||||
|     ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool()); | ||||
|  | ||||
|     ui->disableQuiltBeaconCheckBox->setChecked(s->get("DisableQuiltBeacon").toBool()); | ||||
| } | ||||
|  | ||||
| void MinecraftPage::retranslate() | ||||
|   | ||||
| @@ -39,7 +39,7 @@ | ||||
|      </property> | ||||
|      <widget class="QWidget" name="minecraftTab"> | ||||
|       <attribute name="title"> | ||||
|        <string notr="true">General</string> | ||||
|        <string>General</string> | ||||
|       </attribute> | ||||
|       <layout class="QVBoxLayout" name="verticalLayout_3"> | ||||
|        <item> | ||||
| @@ -190,6 +190,25 @@ | ||||
|        <string>Tweaks</string> | ||||
|       </attribute> | ||||
|       <layout class="QVBoxLayout" name="verticalLayout_12"> | ||||
|        <item> | ||||
|         <widget class="QGroupBox" name="modLoaderSettingsGroupBox"> | ||||
|          <property name="title"> | ||||
|           <string>Mod loader settings</string> | ||||
|          </property> | ||||
|          <layout class="QVBoxLayout" name="verticalLayout_13"> | ||||
|           <item> | ||||
|            <widget class="QCheckBox" name="disableQuiltBeaconCheckBox"> | ||||
|             <property name="text"> | ||||
|              <string>Disable Quilt Loader Beacon</string> | ||||
|             </property> | ||||
|             <property name="toolTip"> | ||||
|              <string>Disable Quilt loader's beacon for counting monthly active users</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|          </layout> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item> | ||||
|         <widget class="QGroupBox" name="nativeLibWorkaroundGroupBox"> | ||||
|          <property name="title"> | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
|  *  PolyMC - Minecraft Launcher | ||||
|  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> | ||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||
|  *  Copyright (C) 2023 seth <getchoo at tuta dot io> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -51,9 +52,9 @@ | ||||
| #include "minecraft/auth/AccountList.h" | ||||
|  | ||||
| #include "JavaDownloader.h" | ||||
| #include "FileSystem.h" | ||||
| #include "java/JavaInstallList.h" | ||||
| #include "java/JavaUtils.h" | ||||
| #include "FileSystem.h" | ||||
|  | ||||
| InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent) | ||||
|     : QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst) | ||||
| @@ -281,6 +282,14 @@ void InstanceSettingsPage::applySettings() | ||||
|         m_settings->reset("InstanceAccountId"); | ||||
|     } | ||||
|  | ||||
|     bool overrideModLoaderSettings = ui->modLoaderSettingsGroupBox->isChecked(); | ||||
|     m_settings->set("OverrideModLoaderSettings", overrideModLoaderSettings); | ||||
|     if (overrideModLoaderSettings) { | ||||
|         m_settings->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked()); | ||||
|     } else { | ||||
|         m_settings->reset("DisableQuiltBeacon"); | ||||
|     } | ||||
|  | ||||
|     // FIXME: This should probably be called by a signal instead | ||||
|     m_instance->updateRuntimeContext(); | ||||
| } | ||||
| @@ -381,6 +390,10 @@ void InstanceSettingsPage::loadSettings() | ||||
|  | ||||
|     ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool()); | ||||
|     updateAccountsMenu(); | ||||
|  | ||||
|     // Mod loader specific settings | ||||
|     ui->modLoaderSettingsGroupBox->setChecked(m_settings->get("OverrideModLoaderSettings").toBool()); | ||||
|     ui->disableQuiltBeaconCheckBox->setChecked(m_settings->get("DisableQuiltBeacon").toBool()); | ||||
| } | ||||
|  | ||||
| void InstanceSettingsPage::on_javaDownloadBtn_clicked() | ||||
|   | ||||
| @@ -123,7 +123,7 @@ | ||||
|           <item row="2" column="0"> | ||||
|            <widget class="QLabel" name="labelPermGen"> | ||||
|             <property name="text"> | ||||
|              <string notr="true">PermGen:</string> | ||||
|              <string>PermGen:</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
| @@ -548,6 +548,31 @@ | ||||
|        <string>Miscellaneous</string> | ||||
|       </attribute> | ||||
|       <layout class="QVBoxLayout" name="verticalLayout_9"> | ||||
|        <item> | ||||
|         <widget class="QGroupBox" name="modLoaderSettingsGroupBox"> | ||||
|          <property name="checkable"> | ||||
|           <bool>true</bool> | ||||
|          </property> | ||||
|          <property name="checked"> | ||||
|           <bool>false</bool> | ||||
|          </property> | ||||
|          <property name="title"> | ||||
|           <string>Mod loader settings</string> | ||||
|          </property> | ||||
|          <layout class="QVBoxLayout" name="VerticalLayout_16"> | ||||
|           <item> | ||||
|            <widget class="QCheckBox" name="disableQuiltBeaconCheckBox"> | ||||
|             <property name="text"> | ||||
|              <string>Disable Quilt Loader Beacon</string> | ||||
|             </property> | ||||
|             <property name="toolTip"> | ||||
|              <string>Disable Quilt loader's beacon for counting monthly active users</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|          </layout> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item> | ||||
|         <widget class="QGroupBox" name="gameTimeGroupBox"> | ||||
|          <property name="enabled"> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -61,7 +61,7 @@ void ThemeWizardPage::updateIcons() | ||||
| void ThemeWizardPage::updateCat() | ||||
| { | ||||
|     qDebug() << "Setting Cat"; | ||||
|     ui->catImagePreviewButton->setIcon(QIcon(QString(R"(:/backgrounds/%1)").arg(ThemeManager::getCatImage()))); | ||||
|     ui->catImagePreviewButton->setIcon(QIcon(QString(R"(%1)").arg(APPLICATION->getCatPack()))); | ||||
| } | ||||
|  | ||||
| void ThemeWizardPage::retranslate() | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|   | ||||
							
								
								
									
										117
									
								
								launcher/ui/themes/CatPack.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								launcher/ui/themes/CatPack.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|  *  the Free Software Foundation, version 3. | ||||
|  * | ||||
|  *  This program is distributed in the hope that it will be useful, | ||||
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  *  GNU General Public License for more details. | ||||
|  * | ||||
|  *  You should have received a copy of the GNU General Public License | ||||
|  *  along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  * This file incorporates work covered by the following copyright and | ||||
|  * permission notice: | ||||
|  * | ||||
|  *      Copyright 2013-2021 MultiMC Contributors | ||||
|  * | ||||
|  *      Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  *      you may not use this file except in compliance with the License. | ||||
|  *      You may obtain a copy of the License at | ||||
|  * | ||||
|  *          http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  *      Unless required by applicable law or agreed to in writing, software | ||||
|  *      distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  *      See the License for the specific language governing permissions and | ||||
|  *      limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #include "ui/themes/CatPack.h" | ||||
| #include <QDate> | ||||
| #include <QDir> | ||||
| #include <QFileInfo> | ||||
| #include "FileSystem.h" | ||||
| #include "Json.h" | ||||
|  | ||||
| QString BasicCatPack::path() | ||||
| { | ||||
|     const auto now = QDate::currentDate(); | ||||
|     const auto birthday = QDate(now.year(), 11, 30); | ||||
|     const auto xmas = QDate(now.year(), 12, 25); | ||||
|     const auto halloween = QDate(now.year(), 10, 31); | ||||
|  | ||||
|     QString cat = QString(":/backgrounds/%1").arg(m_id); | ||||
|     if (std::abs(now.daysTo(xmas)) <= 4) { | ||||
|         cat += "-xmas"; | ||||
|     } else if (std::abs(now.daysTo(halloween)) <= 4) { | ||||
|         cat += "-spooky"; | ||||
|     } else if (std::abs(now.daysTo(birthday)) <= 12) { | ||||
|         cat += "-bday"; | ||||
|     } | ||||
|     return cat; | ||||
| } | ||||
|  | ||||
| JsonCatPack::PartialDate partialDate(QJsonObject date) | ||||
| { | ||||
|     auto month = Json::ensureInteger(date, "month", 1); | ||||
|     if (month > 12) | ||||
|         month = 12; | ||||
|     else if (month <= 0) | ||||
|         month = 1; | ||||
|     auto day = Json::ensureInteger(date, "day", 1); | ||||
|     if (day > 31) | ||||
|         day = 31; | ||||
|     else if (day <= 0) | ||||
|         day = 1; | ||||
|     return { month, day }; | ||||
| }; | ||||
|  | ||||
| JsonCatPack::JsonCatPack(QFileInfo& manifestInfo) : BasicCatPack(manifestInfo.dir().dirName()) | ||||
| { | ||||
|     QString path = manifestInfo.path(); | ||||
|     auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "CatPack JSON file"); | ||||
|     const auto root = doc.object(); | ||||
|     m_name = Json::requireString(root, "name", "Catpack name"); | ||||
|     m_defaultPath = FS::PathCombine(path, Json::requireString(root, "default", "Default Cat")); | ||||
|     auto variants = Json::ensureArray(root, "variants", QJsonArray(), "Catpack Variants"); | ||||
|     for (auto v : variants) { | ||||
|         auto variant = Json::ensureObject(v, QJsonObject(), "Cat variant"); | ||||
|         m_variants << Variant{ FS::PathCombine(path, Json::requireString(variant, "path", "Variant path")), | ||||
|                                partialDate(Json::requireObject(variant, "startTime", "Variant startTime")), | ||||
|                                partialDate(Json::requireObject(variant, "endTime", "Variant endTime")) }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| QDate ensureDay(int year, int month, int day) | ||||
| { | ||||
|     QDate date(year, month, 1); | ||||
|     if (day > date.daysInMonth()) | ||||
|         day = date.daysInMonth(); | ||||
|     return QDate(year, month, day); | ||||
| } | ||||
|  | ||||
| QString JsonCatPack::path() | ||||
| { | ||||
|     const QDate now = QDate::currentDate(); | ||||
|     for (auto var : m_variants) { | ||||
|         QDate startDate = ensureDay(now.year(), var.startTime.month, var.startTime.day); | ||||
|         QDate endDate = ensureDay(now.year(), var.endTime.month, var.endTime.day); | ||||
|         if (startDate > endDate) {  // it's spans over multiple years | ||||
|             if (endDate <= now)     // end date is in the past so jump one year into the future for endDate | ||||
|                 endDate = endDate.addYears(1); | ||||
|             else  // end date is in the future so jump one year into the past for startDate | ||||
|                 startDate = startDate.addYears(-1); | ||||
|         } | ||||
|  | ||||
|         if (startDate >= now && now >= endDate) | ||||
|             return var.path; | ||||
|     } | ||||
|     return m_defaultPath; | ||||
| } | ||||
							
								
								
									
										91
									
								
								launcher/ui/themes/CatPack.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								launcher/ui/themes/CatPack.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|  *  the Free Software Foundation, version 3. | ||||
|  * | ||||
|  *  This program is distributed in the hope that it will be useful, | ||||
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  *  GNU General Public License for more details. | ||||
|  * | ||||
|  *  You should have received a copy of the GNU General Public License | ||||
|  *  along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  * This file incorporates work covered by the following copyright and | ||||
|  * permission notice: | ||||
|  * | ||||
|  *      Copyright 2013-2021 MultiMC Contributors | ||||
|  * | ||||
|  *      Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  *      you may not use this file except in compliance with the License. | ||||
|  *      You may obtain a copy of the License at | ||||
|  * | ||||
|  *          http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  *      Unless required by applicable law or agreed to in writing, software | ||||
|  *      distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  *      See the License for the specific language governing permissions and | ||||
|  *      limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <QDate> | ||||
| #include <QFileInfo> | ||||
| #include <QList> | ||||
| #include <QString> | ||||
|  | ||||
| class CatPack { | ||||
|    public: | ||||
|     virtual ~CatPack() {} | ||||
|     virtual QString id() = 0; | ||||
|     virtual QString name() = 0; | ||||
|     virtual QString path() = 0; | ||||
| }; | ||||
|  | ||||
| class BasicCatPack : public CatPack { | ||||
|    public: | ||||
|     BasicCatPack(QString id, QString name) : m_id(id), m_name(name) {} | ||||
|     BasicCatPack(QString id) : BasicCatPack(id, id) {} | ||||
|     virtual QString id() { return m_id; }; | ||||
|     virtual QString name() { return m_name; }; | ||||
|     virtual QString path(); | ||||
|  | ||||
|    protected: | ||||
|     QString m_id; | ||||
|     QString m_name; | ||||
| }; | ||||
|  | ||||
| class FileCatPack : public BasicCatPack { | ||||
|    public: | ||||
|     FileCatPack(QString id, QFileInfo& fileInfo) : BasicCatPack(id), m_path(fileInfo.absoluteFilePath()) {} | ||||
|     FileCatPack(QFileInfo& fileInfo) : FileCatPack(fileInfo.baseName(), fileInfo) {} | ||||
|     virtual QString path() { return m_path; } | ||||
|  | ||||
|    private: | ||||
|     QString m_path; | ||||
| }; | ||||
|  | ||||
| class JsonCatPack : public BasicCatPack { | ||||
|    public: | ||||
|     struct PartialDate { | ||||
|         int month; | ||||
|         int day; | ||||
|     }; | ||||
|     struct Variant { | ||||
|         QString path; | ||||
|         PartialDate startTime; | ||||
|         PartialDate endTime; | ||||
|     }; | ||||
|     JsonCatPack(QFileInfo& manifestInfo); | ||||
|     virtual QString path(); | ||||
|  | ||||
|    private: | ||||
|     QString m_defaultPath; | ||||
|     QList<Variant> m_variants; | ||||
| }; | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -21,7 +21,10 @@ | ||||
| #include <QDir> | ||||
| #include <QDirIterator> | ||||
| #include <QIcon> | ||||
| #include <QImageReader> | ||||
| #include "Exception.h" | ||||
| #include "ui/themes/BrightTheme.h" | ||||
| #include "ui/themes/CatPack.h" | ||||
| #include "ui/themes/CustomTheme.h" | ||||
| #include "ui/themes/DarkTheme.h" | ||||
| #include "ui/themes/SystemTheme.h" | ||||
| @@ -32,6 +35,7 @@ ThemeManager::ThemeManager(MainWindow* mainWindow) | ||||
| { | ||||
|     m_mainWindow = mainWindow; | ||||
|     initializeThemes(); | ||||
|     initializeCatPacks(); | ||||
| } | ||||
|  | ||||
| /// @brief Adds the Theme to the list of themes | ||||
| @@ -40,7 +44,10 @@ ThemeManager::ThemeManager(MainWindow* mainWindow) | ||||
| QString ThemeManager::addTheme(std::unique_ptr<ITheme> theme) | ||||
| { | ||||
|     QString id = theme->id(); | ||||
|     m_themes.emplace(id, std::move(theme)); | ||||
|     if (m_themes.find(id) == m_themes.end()) | ||||
|         m_themes.emplace(id, std::move(theme)); | ||||
|     else | ||||
|         themeWarningLog() << "Theme(" << id << ") not added to prevent id duplication"; | ||||
|     return id; | ||||
| } | ||||
|  | ||||
| @@ -77,7 +84,7 @@ void ThemeManager::initializeThemes() | ||||
|         QString themeFolder = QDir("./themes/").absoluteFilePath(""); | ||||
|         themeDebugLog() << "Theme Folder Path: " << themeFolder; | ||||
|  | ||||
|         QDirIterator directoryIterator(themeFolder, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); | ||||
|         QDirIterator directoryIterator(themeFolder, QDir::Dirs | QDir::NoDotAndDotDot); | ||||
|         while (directoryIterator.hasNext()) { | ||||
|             QDir dir(directoryIterator.next()); | ||||
|             QFileInfo themeJson(dir.absoluteFilePath("theme.json")); | ||||
| @@ -111,6 +118,16 @@ QList<ITheme*> ThemeManager::getValidApplicationThemes() | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| QList<CatPack*> ThemeManager::getValidCatPacks() | ||||
| { | ||||
|     QList<CatPack*> ret; | ||||
|     ret.reserve(m_catPacks.size()); | ||||
|     for (auto&& [id, theme] : m_catPacks) { | ||||
|         ret.append(theme.get()); | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| void ThemeManager::setIconTheme(const QString& name) | ||||
| { | ||||
|     QIcon::setThemeName(name); | ||||
| @@ -137,19 +154,74 @@ void ThemeManager::setApplicationTheme(const QString& name, bool initial) | ||||
|     } | ||||
| } | ||||
|  | ||||
| QString ThemeManager::getCatImage(QString catName) | ||||
| QString ThemeManager::getCatPack(QString catName) | ||||
| { | ||||
|     QDateTime now = QDateTime::currentDateTime(); | ||||
|     QDateTime birthday(QDate(now.date().year(), 11, 30), QTime(0, 0)); | ||||
|     QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0)); | ||||
|     QDateTime halloween(QDate(now.date().year(), 10, 31), QTime(0, 0)); | ||||
|     QString cat = !catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString(); | ||||
|     if (std::abs(now.daysTo(xmas)) <= 4) { | ||||
|         cat += "-xmas"; | ||||
|     } else if (std::abs(now.daysTo(halloween)) <= 4) { | ||||
|         cat += "-spooky"; | ||||
|     } else if (std::abs(now.daysTo(birthday)) <= 12) { | ||||
|         cat += "-bday"; | ||||
|     auto catIter = m_catPacks.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString()); | ||||
|     if (catIter != m_catPacks.end()) { | ||||
|         auto& catPack = catIter->second; | ||||
|         themeDebugLog() << "applying catpack" << catPack->id(); | ||||
|         return catPack->path(); | ||||
|     } else { | ||||
|         themeWarningLog() << "Tried to get invalid catPack:" << catName; | ||||
|     } | ||||
|  | ||||
|     return m_catPacks.begin()->second->path(); | ||||
| } | ||||
|  | ||||
| QString ThemeManager::addCatPack(std::unique_ptr<CatPack> catPack) | ||||
| { | ||||
|     QString id = catPack->id(); | ||||
|     if (m_catPacks.find(id) == m_catPacks.end()) | ||||
|         m_catPacks.emplace(id, std::move(catPack)); | ||||
|     else | ||||
|         themeWarningLog() << "CatPack(" << id << ") not added to prevent id duplication"; | ||||
|     return id; | ||||
| } | ||||
|  | ||||
| void ThemeManager::initializeCatPacks() | ||||
| { | ||||
|     QList<std::pair<QString, QString>> defaultCats{ { "kitteh", QObject::tr("Background Cat (from MultiMC)") }, | ||||
|                                                     { "rory", QObject::tr("Rory ID 11 (drawn by Ashtaka)") }, | ||||
|                                                     { "rory-flat", QObject::tr("Rory ID 11 (flat edition, drawn by Ashtaka)") }, | ||||
|                                                     { "teawie", QObject::tr("Teawie (drawn by SympathyTea)") } }; | ||||
|     for (auto [id, name] : defaultCats) { | ||||
|         addCatPack(std::unique_ptr<CatPack>(new BasicCatPack(id, name))); | ||||
|     } | ||||
|     QDir catpacksDir("catpacks"); | ||||
|     QString catpacksFolder = catpacksDir.absoluteFilePath(""); | ||||
|     themeDebugLog() << "CatPacks Folder Path:" << catpacksFolder; | ||||
|  | ||||
|     QStringList supportedImageFormats; | ||||
|     for (auto format : QImageReader::supportedImageFormats()) { | ||||
|         supportedImageFormats.append("*." + format); | ||||
|     } | ||||
|     auto loadFiles = [this, supportedImageFormats](QDir dir) { | ||||
|         // Load image files directly | ||||
|         QDirIterator ImageFileIterator(dir.absoluteFilePath(""), supportedImageFormats, QDir::Files); | ||||
|         while (ImageFileIterator.hasNext()) { | ||||
|             QFile customCatFile(ImageFileIterator.next()); | ||||
|             QFileInfo customCatFileInfo(customCatFile); | ||||
|             themeDebugLog() << "Loading CatPack from:" << customCatFileInfo.absoluteFilePath(); | ||||
|             addCatPack(std::unique_ptr<CatPack>(new FileCatPack(customCatFileInfo))); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     loadFiles(catpacksDir); | ||||
|  | ||||
|     QDirIterator directoryIterator(catpacksFolder, QDir::Dirs | QDir::NoDotAndDotDot); | ||||
|     while (directoryIterator.hasNext()) { | ||||
|         QDir dir(directoryIterator.next()); | ||||
|         QFileInfo manifest(dir.absoluteFilePath("catpack.json")); | ||||
|         if (manifest.isFile()) { | ||||
|             try { | ||||
|                 // Load background manifest | ||||
|                 themeDebugLog() << "Loading background manifest from:" << manifest.absoluteFilePath(); | ||||
|                 addCatPack(std::unique_ptr<CatPack>(new JsonCatPack(manifest))); | ||||
|             } catch (const Exception& e) { | ||||
|                 themeWarningLog() << "Couldn't load catpack json:" << e.cause(); | ||||
|             } | ||||
|         } else { | ||||
|             loadFiles(dir); | ||||
|         } | ||||
|     } | ||||
|     return cat; | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -20,6 +20,7 @@ | ||||
| #include <QString> | ||||
|  | ||||
| #include "ui/MainWindow.h" | ||||
| #include "ui/themes/CatPack.h" | ||||
| #include "ui/themes/ITheme.h" | ||||
|  | ||||
| inline auto themeDebugLog() | ||||
| @@ -40,18 +41,20 @@ class ThemeManager { | ||||
|     void applyCurrentlySelectedTheme(bool initial = false); | ||||
|     void setApplicationTheme(const QString& name, bool initial = false); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Returns the cat based on selected cat and with events (Birthday, XMas, etc.) | ||||
|     /// </summary> | ||||
|     /// <param name="catName">Optional, if you need a specific cat.</param> | ||||
|     /// <returns></returns> | ||||
|     static QString getCatImage(QString catName = ""); | ||||
|     /// @brief Returns the background based on selected and with events (Birthday, XMas, etc.) | ||||
|     /// @param catName Optional, if you need a specific background. | ||||
|     /// @return | ||||
|     QString getCatPack(QString catName = ""); | ||||
|     QList<CatPack*> getValidCatPacks(); | ||||
|  | ||||
|    private: | ||||
|     std::map<QString, std::unique_ptr<ITheme>> m_themes; | ||||
|     std::map<QString, std::unique_ptr<CatPack>> m_catPacks; | ||||
|     MainWindow* m_mainWindow; | ||||
|  | ||||
|     void initializeThemes(); | ||||
|     void initializeCatPacks(); | ||||
|     QString addTheme(std::unique_ptr<ITheme> theme); | ||||
|     ITheme* getTheme(QString themeId); | ||||
|     QString addCatPack(std::unique_ptr<CatPack> catPack); | ||||
| }; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -95,9 +95,14 @@ void ThemeCustomizationWidget::applyWidgetTheme(int index) { | ||||
|     emit currentWidgetThemeChanged(index); | ||||
| } | ||||
|  | ||||
| void ThemeCustomizationWidget::applyCatTheme(int index) { | ||||
| void ThemeCustomizationWidget::applyCatTheme(int index) | ||||
| { | ||||
|     auto settings = APPLICATION->settings(); | ||||
|     settings->set("BackgroundCat", m_catOptions[index].first); | ||||
|     auto originalCat = settings->get("BackgroundCat").toString(); | ||||
|     auto newCat = ui->backgroundCatComboBox->currentData().toString(); | ||||
|     if (originalCat != newCat) { | ||||
|         settings->set("BackgroundCat", newCat); | ||||
|     } | ||||
|  | ||||
|     emit currentCatChanged(index); | ||||
| } | ||||
| @@ -135,10 +140,10 @@ void ThemeCustomizationWidget::loadSettings() | ||||
|     } | ||||
|  | ||||
|     auto cat = settings->get("BackgroundCat").toString(); | ||||
|     for (auto& catFromList : m_catOptions) { | ||||
|         QIcon catIcon = QIcon(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage(catFromList.first))); | ||||
|         ui->backgroundCatComboBox->addItem(catIcon, catFromList.second); | ||||
|         if (cat == catFromList.first) { | ||||
|     for (auto& catFromList : APPLICATION->getValidCatPacks()) { | ||||
|         QIcon catIcon = QIcon(QString("%1").arg(catFromList->path())); | ||||
|         ui->backgroundCatComboBox->addItem(catIcon, catFromList->name(), catFromList->id()); | ||||
|         if (cat == catFromList->id()) { | ||||
|             ui->backgroundCatComboBox->setCurrentIndex(ui->backgroundCatComboBox->count() - 1); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  Prism Launcher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> | ||||
|  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -53,25 +53,17 @@ class ThemeCustomizationWidget : public QWidget { | ||||
|    private: | ||||
|     Ui::ThemeCustomizationWidget* ui; | ||||
|  | ||||
|     //TODO finish implementing | ||||
|     QList<std::pair<QString, QString>> m_iconThemeOptions{  | ||||
|         { "pe_colored",     QObject::tr("Simple (Colored Icons)") },  | ||||
|         { "pe_light",       QObject::tr("Simple (Light Icons)") },      | ||||
|         { "pe_dark",        QObject::tr("Simple (Dark Icons)") }, | ||||
|         { "pe_blue",        QObject::tr("Simple (Blue Icons)") },     | ||||
|         { "breeze_light",   QObject::tr("Breeze Light") },  | ||||
|         { "breeze_dark",    QObject::tr("Breeze Dark") }, | ||||
|         { "OSX",            QObject::tr("OSX") },         | ||||
|         { "iOS",            QObject::tr("iOS") },           | ||||
|         { "flat",           QObject::tr("Flat") }, | ||||
|         { "flat_white",     QObject::tr("Flat (White)") },  | ||||
|         { "multimc",        QObject::tr("Legacy") },       | ||||
|         { "custom",         QObject::tr("Custom") }  | ||||
|     }; | ||||
|     QList<std::pair<QString, QString>> m_catOptions{  | ||||
|         { "kitteh",     QObject::tr("Background Cat (from MultiMC)") },  | ||||
|         { "rory",       QObject::tr("Rory ID 11 (drawn by Ashtaka)") },  | ||||
|         { "rory-flat",  QObject::tr("Rory ID 11 (flat edition, drawn by Ashtaka)") }, | ||||
|         { "teawie",     QObject::tr("Teawie (drawn by SympathyTea)") } | ||||
|     }; | ||||
|     // TODO finish implementing | ||||
|     QList<std::pair<QString, QString>> m_iconThemeOptions{ { "pe_colored", QObject::tr("Simple (Colored Icons)") }, | ||||
|                                                            { "pe_light", QObject::tr("Simple (Light Icons)") }, | ||||
|                                                            { "pe_dark", QObject::tr("Simple (Dark Icons)") }, | ||||
|                                                            { "pe_blue", QObject::tr("Simple (Blue Icons)") }, | ||||
|                                                            { "breeze_light", QObject::tr("Breeze Light") }, | ||||
|                                                            { "breeze_dark", QObject::tr("Breeze Dark") }, | ||||
|                                                            { "OSX", QObject::tr("OSX") }, | ||||
|                                                            { "iOS", QObject::tr("iOS") }, | ||||
|                                                            { "flat", QObject::tr("Flat") }, | ||||
|                                                            { "flat_white", QObject::tr("Flat (White)") }, | ||||
|                                                            { "multimc", QObject::tr("Legacy") }, | ||||
|                                                            { "custom", QObject::tr("Custom") } }; | ||||
| }; | ||||
|   | ||||
| @@ -61,7 +61,7 @@ The `standard` and `legacy` launchers are available. | ||||
|  | ||||
| Example (some parts have been censored): | ||||
|  | ||||
| ``` | ||||
| ```text | ||||
| mod legacyjavafixer-1.0 | ||||
| mainClass net.minecraft.launchwrapper.Launch | ||||
| param --username | ||||
|   | ||||
 Submodule libraries/libnbtplusplus updated: 2203af7eeb...a5e8fd52b8
									
								
							
							
								
								
									
										10
									
								
								nix/NIX.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								nix/NIX.md
									
									
									
									
									
								
							| @@ -53,7 +53,8 @@ home.packages = [ pkgs.prismlauncher ]; | ||||
|  | ||||
| ### Without flakes-enabled nix | ||||
|  | ||||
| #### Using channels | ||||
| <details> | ||||
| <summary>Using channels</summary> | ||||
|  | ||||
| ```sh | ||||
| nix-channel --add https://github.com/PrismLauncher/PrismLauncher/archive/master.tar.gz prismlauncher | ||||
| @@ -61,7 +62,10 @@ nix-channel --update prismlauncher | ||||
| nix-env -iA prismlauncher | ||||
| ``` | ||||
|  | ||||
| #### Using the overlay | ||||
| </details> | ||||
|  | ||||
| <details> | ||||
| <summary>Using the overlay</summary> | ||||
|  | ||||
| ```nix | ||||
| # In your configuration.nix: | ||||
| @@ -74,6 +78,8 @@ nix-env -iA prismlauncher | ||||
| } | ||||
| ``` | ||||
|  | ||||
| </details> | ||||
|  | ||||
| ## Running ad-hoc | ||||
|  | ||||
| If you're on a flakes-enabled nix you can run the launcher in one-line | ||||
|   | ||||
| @@ -24,9 +24,9 @@ | ||||
|   # Supported systems. | ||||
|   systems = [ | ||||
|     "x86_64-linux" | ||||
|     "x86_64-darwin" | ||||
|     "aarch64-linux" | ||||
|     # Disabled due to qtbase being currently broken for "aarch64-darwin." | ||||
|     # Disabled due to our packages not supporting darwin yet. | ||||
|     # "x86_64-darwin" | ||||
|     # "aarch64-darwin" | ||||
|   ]; | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 TheKodeToad
					TheKodeToad