Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into refactor/NetActions
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
		
							
								
								
									
										8
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | # EditorConfig specs and documentation: https://EditorConfig.org | ||||||
|  |  | ||||||
|  | # top-most EditorConfig file | ||||||
|  | root = true | ||||||
|  |  | ||||||
|  | # C++ Code Style settings | ||||||
|  | [*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] | ||||||
|  | cpp_generate_documentation_comments = doxygen_slash_star | ||||||
							
								
								
									
										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}. | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -264,23 +264,23 @@ jobs: | |||||||
|       - name: Configure CMake (macOS) |       - name: Configure CMake (macOS) | ||||||
|         if: runner.os == 'macOS' && matrix.qt_ver == 6 |         if: runner.os == 'macOS' && matrix.qt_ver == 6 | ||||||
|         run: | |         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) |       - name: Configure CMake (macOS-Legacy) | ||||||
|         if: runner.os == 'macOS' && matrix.qt_ver == 5 |         if: runner.os == 'macOS' && matrix.qt_ver == 5 | ||||||
|         run: | |         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) |       - name: Configure CMake (Windows MinGW-w64) | ||||||
|         if: runner.os == 'Windows' && matrix.msystem != '' |         if: runner.os == 'Windows' && matrix.msystem != '' | ||||||
|         shell: msys2 {0} |         shell: msys2 {0} | ||||||
|         run: | |         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) |       - name: Configure CMake (Windows MSVC) | ||||||
|         if: runner.os == 'Windows' && matrix.msystem == '' |         if: runner.os == 'Windows' && matrix.msystem == '' | ||||||
|         run: | |         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) |           # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) | ||||||
|           if ("${{ env.CCACHE_VAR }}") |           if ("${{ env.CCACHE_VAR }}") | ||||||
|           { |           { | ||||||
| @@ -295,7 +295,7 @@ jobs: | |||||||
|       - name: Configure CMake (Linux) |       - name: Configure CMake (Linux) | ||||||
|         if: runner.os == 'Linux' |         if: runner.os == 'Linux' | ||||||
|         run: | |         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 |       # BUILD | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -19,3 +19,6 @@ | |||||||
| [submodule "libraries/cmark"] | [submodule "libraries/cmark"] | ||||||
| 	path = libraries/cmark | 	path = libraries/cmark | ||||||
| 	url = https://github.com/commonmark/cmark.git | 	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") | set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},0,0") | ||||||
|  |  | ||||||
| # Build platform. | # 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 | # Channel list URL | ||||||
| set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.") | set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.") | ||||||
|   | |||||||
| @@ -61,3 +61,10 @@ 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-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 | [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. | ||||||
|   | |||||||
| @@ -65,7 +65,7 @@ Config::Config() | |||||||
|     MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@"; |     MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@"; | ||||||
|     MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@"; |     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; |         UPDATER_ENABLED = true; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -68,7 +68,7 @@ class Config { | |||||||
|  |  | ||||||
|     bool UPDATER_ENABLED = false; |     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; |     QString BUILD_PLATFORM; | ||||||
|  |  | ||||||
|     /// A string containing the build timestamp |     /// A string containing the build timestamp | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										18
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							| @@ -21,11 +21,11 @@ | |||||||
|         "nixpkgs-lib": "nixpkgs-lib" |         "nixpkgs-lib": "nixpkgs-lib" | ||||||
|       }, |       }, | ||||||
|       "locked": { |       "locked": { | ||||||
|         "lastModified": 1688254665, |         "lastModified": 1688466019, | ||||||
|         "narHash": "sha256-8FHEgBrr7gYNiS/NzCxIO3m4hvtLRW9YY1nYo1ivm3o=", |         "narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=", | ||||||
|         "owner": "hercules-ci", |         "owner": "hercules-ci", | ||||||
|         "repo": "flake-parts", |         "repo": "flake-parts", | ||||||
|         "rev": "267149c58a14d15f7f81b4d737308421de9d7152", |         "rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec", | ||||||
|         "type": "github" |         "type": "github" | ||||||
|       }, |       }, | ||||||
|       "original": { |       "original": { | ||||||
| @@ -91,11 +91,11 @@ | |||||||
|     }, |     }, | ||||||
|     "nixpkgs": { |     "nixpkgs": { | ||||||
|       "locked": { |       "locked": { | ||||||
|         "lastModified": 1688221086, |         "lastModified": 1689413807, | ||||||
|         "narHash": "sha256-cdW6qUL71cNWhHCpMPOJjlw0wzSRP0pVlRn2vqX/VVg=", |         "narHash": "sha256-exuzOvOhGAEKWQKwDuZAL4N8a1I837hH5eocaTcIbLc=", | ||||||
|         "owner": "nixos", |         "owner": "nixos", | ||||||
|         "repo": "nixpkgs", |         "repo": "nixpkgs", | ||||||
|         "rev": "cd99c2b3c9f160cd004318e0697f90bbd5960825", |         "rev": "46ed466081b9cad1125b11f11a2af5cc40b942c7", | ||||||
|         "type": "github" |         "type": "github" | ||||||
|       }, |       }, | ||||||
|       "original": { |       "original": { | ||||||
| @@ -138,11 +138,11 @@ | |||||||
|         ] |         ] | ||||||
|       }, |       }, | ||||||
|       "locked": { |       "locked": { | ||||||
|         "lastModified": 1688386108, |         "lastModified": 1689328505, | ||||||
|         "narHash": "sha256-Vffto9QaVonzYAcPlAzd0soqWYpPpKk60dfNLSIXcFA=", |         "narHash": "sha256-9B3+OeUn1a/CvzE3GW6nWNwS5J7PDHTyHGlpL3wV5oA=", | ||||||
|         "owner": "cachix", |         "owner": "cachix", | ||||||
|         "repo": "pre-commit-hooks.nix", |         "repo": "pre-commit-hooks.nix", | ||||||
|         "rev": "42587d3414d1747999a5f71e92a83cf6547b62da", |         "rev": "5e28316db471d1ac234beb70031b635437421dd6", | ||||||
|         "type": "github" |         "type": "github" | ||||||
|       }, |       }, | ||||||
|       "original": { |       "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: | sdk-extensions: | ||||||
|   - org.freedesktop.Sdk.Extension.openjdk17 |   - org.freedesktop.Sdk.Extension.openjdk17 | ||||||
|   - org.freedesktop.Sdk.Extension.openjdk8 |   - 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 | command: prismlauncher | ||||||
| finish-args: | finish-args: | ||||||
| @@ -26,21 +19,31 @@ finish-args: | |||||||
|     # Mod drag&drop |     # Mod drag&drop | ||||||
|   - --filesystem=xdg-download:ro |   - --filesystem=xdg-download:ro | ||||||
|  |  | ||||||
|  | cleanup: | ||||||
|  |   - /lib/libGLU* | ||||||
|  |  | ||||||
| modules: | 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 |   - name: prismlauncher | ||||||
|     buildsystem: cmake-ninja |     buildsystem: cmake-ninja | ||||||
|  |     builddir: true | ||||||
|     config-opts: |     config-opts: | ||||||
|       - -DLauncher_BUILD_PLATFORM=flatpak |       - -DLauncher_BUILD_PLATFORM=flatpak | ||||||
|       - -DCMAKE_BUILD_TYPE=Debug |       - -DCMAKE_BUILD_TYPE=RelWithDebInfo | ||||||
|       - -DLauncher_QT_VERSION_MAJOR=5 |       - -DLauncher_QT_VERSION_MAJOR=5 | ||||||
|     build-options: |     build-options: | ||||||
|       env: |       env: | ||||||
|         JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17 |         JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17 | ||||||
|         JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac |         JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac | ||||||
|     sources: |     sources: | ||||||
|         - type: dir |       - type: dir | ||||||
|           path: ../ |         path: ../ | ||||||
|     builddir: true |  | ||||||
|   - name: openjdk |   - name: openjdk | ||||||
|     buildsystem: simple |     buildsystem: simple | ||||||
|     build-commands: |     build-commands: | ||||||
| @@ -49,14 +52,45 @@ modules: | |||||||
|       - mv /app/jre /app/jdk/17 |       - mv /app/jre /app/jdk/17 | ||||||
|       - /usr/lib/sdk/openjdk8/install.sh |       - /usr/lib/sdk/openjdk8/install.sh | ||||||
|       - mv /app/jre /app/jdk/8 |       - 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 |   - name: xrandr | ||||||
|     buildsystem: autotools |     buildsystem: autotools | ||||||
|     sources: |     sources: | ||||||
|       - type: archive |       - type: archive | ||||||
|         url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.1.tar.xz |         url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.2.tar.xz | ||||||
|         sha256: 7bc76daf9d72f8aff885efad04ce06b90488a1a169d118dea8a2b661832e8762 |         sha256: c8bee4790d9058bacc4b6246456c58021db58a87ddda1a9d0139bf5f18f1f240 | ||||||
|     cleanup: [/share/man, /bin/xkeystone] |         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 |   - name: gamemode | ||||||
|     buildsystem: meson |     buildsystem: meson | ||||||
|     config-opts: |     config-opts: | ||||||
| @@ -67,19 +101,56 @@ modules: | |||||||
|       # post-install is running inside the build dir, we need it from the source though |       # post-install is running inside the build dir, we need it from the source though | ||||||
|       - install -Dm755 ../data/gamemoderun -t /app/bin |       - install -Dm755 ../data/gamemoderun -t /app/bin | ||||||
|     sources: |     sources: | ||||||
|       - type: git |       - type: archive | ||||||
|         url: https://github.com/FeralInteractive/gamemode |         archive-type: tar-gzip | ||||||
|         tag: "1.7" |         url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.7 | ||||||
|         commit: 4dc99dff76218718763a6b07fc1900fa6d1dafd9 |         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 |   - name: enhance | ||||||
|     buildsystem: simple |     buildsystem: simple | ||||||
|     build-commands: |     build-commands: | ||||||
|       - mkdir -p /app/utils/gamescope |  | ||||||
|       - install -Dm755 prime-run /app/bin/prime-run |       - install -Dm755 prime-run /app/bin/prime-run | ||||||
|       - mv /app/bin/prismlauncher /app/bin/prismrun |       - mv /app/bin/prismlauncher /app/bin/prismrun | ||||||
|       - install -Dm755 prismlauncher /app/bin/prismlauncher |       - install -Dm755 prismlauncher /app/bin/prismlauncher | ||||||
|     sources: |     sources: | ||||||
|       - type: file |       - type: file | ||||||
|         path: ../flatpak/prime-run |         path: prime-run | ||||||
|       - type: file |       - 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"; |     test -S "$XDG_RUNTIME_DIR"/discord-ipc-"$i" || ln -sf {app/com.discordapp.Discord,"$XDG_RUNTIME_DIR"}/discord-ipc-"$i"; | ||||||
| done | done | ||||||
|  |  | ||||||
| export PATH="${PATH}${PATH:+:}/app/utils/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin" | export PATH="${PATH}${PATH:+:}/usr/lib/extensions/vulkan/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 VK_LAYER_PATH="/usr/lib/extensions/vulkan/share/vulkan/implicit_layer.d/" | ||||||
|  |  | ||||||
| exec /app/bin/prismrun "$@" | exec /app/bin/prismrun "$@" | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								flatpak/shared-modules
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								flatpak/shared-modules
									
									
									
									
									
										Submodule
									
								
							 Submodule flatpak/shared-modules added at 45094ca570
									
								
							| @@ -6,9 +6,10 @@ | |||||||
|  *  Prism Launcher - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> |  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||||
|  *  Copyright (C) 2022 Lenny McLennington <lenny@sneed.church> |  *  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 TheKodeToad <TheKodeToad@proton.me> | ||||||
|  *  Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> |  *  Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> | ||||||
|  |  *  Copyright (C) 2023 seth <getchoo at tuta dot io> | ||||||
|  * |  * | ||||||
|  *  This program is free software: you can redistribute it and/or modify |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -475,6 +476,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) | |||||||
|  |  | ||||||
|         qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT; |         qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT; | ||||||
|         qDebug() << "Version                    : " << BuildConfig.printableVersionString(); |         qDebug() << "Version                    : " << BuildConfig.printableVersionString(); | ||||||
|  |         qDebug() << "Platform                   : " << BuildConfig.BUILD_PLATFORM; | ||||||
|         qDebug() << "Git commit                 : " << BuildConfig.GIT_COMMIT; |         qDebug() << "Git commit                 : " << BuildConfig.GIT_COMMIT; | ||||||
|         qDebug() << "Git refspec                : " << BuildConfig.GIT_REFSPEC; |         qDebug() << "Git refspec                : " << BuildConfig.GIT_REFSPEC; | ||||||
|         if (adjustedBy.size()) |         if (adjustedBy.size()) | ||||||
| @@ -609,6 +611,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) | |||||||
|         m_settings->registerSetting("IgnoreJavaCompatibility", false); |         m_settings->registerSetting("IgnoreJavaCompatibility", false); | ||||||
|         m_settings->registerSetting("IgnoreJavaWizard", false); |         m_settings->registerSetting("IgnoreJavaWizard", false); | ||||||
|  |  | ||||||
|  |         // Mod loader settings | ||||||
|  |         m_settings->registerSetting("DisableQuiltBeacon", false); | ||||||
|  |  | ||||||
|         // Native library workarounds |         // Native library workarounds | ||||||
|         m_settings->registerSetting("UseNativeOpenAL", false); |         m_settings->registerSetting("UseNativeOpenAL", false); | ||||||
|         m_settings->registerSetting("UseNativeGLFW", false); |         m_settings->registerSetting("UseNativeGLFW", false); | ||||||
| @@ -1181,7 +1186,17 @@ QIcon Application::getThemedIcon(const QString& name) | |||||||
|     return QIcon::fromTheme(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); |     const QString file = QDir::current().absoluteFilePath(filename); | ||||||
|     if (m_settings->get("JsonEditor").toString().isEmpty()) |     if (m_settings->get("JsonEditor").toString().isEmpty()) | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> |  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||||
|  *  Copyright (C) 2022 Tayou <tayou@gmx.net> |  *  Copyright (C) 2022 Tayou <git@tayou.org> | ||||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> |  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> | ||||||
|  * |  * | ||||||
|  *  This program is free software: you can redistribute it and/or modify |  *  This program is free software: you can redistribute it and/or modify | ||||||
| @@ -48,6 +48,7 @@ | |||||||
| #include <BaseInstance.h> | #include <BaseInstance.h> | ||||||
|  |  | ||||||
| #include "minecraft/launch/MinecraftServerTarget.h" | #include "minecraft/launch/MinecraftServerTarget.h" | ||||||
|  | #include "ui/themes/CatPack.h" | ||||||
|  |  | ||||||
| class LaunchController; | class LaunchController; | ||||||
| class LocalPeer; | class LocalPeer; | ||||||
| @@ -126,9 +127,11 @@ public: | |||||||
|  |  | ||||||
|     void setApplicationTheme(const QString& name); |     void setApplicationTheme(const QString& name); | ||||||
|  |  | ||||||
|     shared_qobject_ptr<ExternalUpdater> updater() { |     QList<CatPack*> getValidCatPacks(); | ||||||
|         return m_updater; |  | ||||||
|     } |     QString getCatPack(QString catName = ""); | ||||||
|  |  | ||||||
|  |     shared_qobject_ptr<ExternalUpdater> updater() { return m_updater; } | ||||||
|  |  | ||||||
|     void triggerUpdateCheck(); |     void triggerUpdateCheck(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,16 +15,15 @@ | |||||||
|  |  | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include <memory> |  | ||||||
| #include <QString> |  | ||||||
| #include <QMetaType> | #include <QMetaType> | ||||||
|  | #include <QString> | ||||||
|  | #include <memory> | ||||||
|  |  | ||||||
| /*! | /*! | ||||||
|  * An abstract base class for versions. |  * An abstract base class for versions. | ||||||
|  */ |  */ | ||||||
| class BaseVersion | class BaseVersion { | ||||||
| { |    public: | ||||||
| public: |  | ||||||
|     using Ptr = std::shared_ptr<BaseVersion>; |     using Ptr = std::shared_ptr<BaseVersion>; | ||||||
|     virtual ~BaseVersion() {} |     virtual ~BaseVersion() {} | ||||||
|     /*! |     /*! | ||||||
| @@ -45,14 +44,8 @@ public: | |||||||
|      */ |      */ | ||||||
|     virtual QString typeString() const = 0; |     virtual QString typeString() const = 0; | ||||||
|  |  | ||||||
|     virtual bool operator<(BaseVersion &a) |     virtual bool operator<(BaseVersion& a) { return name() < a.name(); }; | ||||||
|     { |     virtual bool operator>(BaseVersion& a) { return name() > a.name(); }; | ||||||
|         return name() < a.name(); |  | ||||||
|     }; |  | ||||||
|     virtual bool operator>(BaseVersion &a) |  | ||||||
|     { |  | ||||||
|         return name() > a.name(); |  | ||||||
|     }; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Q_DECLARE_METATYPE(BaseVersion::Ptr) | Q_DECLARE_METATYPE(BaseVersion::Ptr) | ||||||
|   | |||||||
| @@ -497,6 +497,9 @@ set(API_SOURCES | |||||||
|     modplatform/helpers/HashUtils.cpp |     modplatform/helpers/HashUtils.cpp | ||||||
|     modplatform/helpers/OverrideUtils.h |     modplatform/helpers/OverrideUtils.h | ||||||
|     modplatform/helpers/OverrideUtils.cpp |     modplatform/helpers/OverrideUtils.cpp | ||||||
|  |  | ||||||
|  |     modplatform/helpers/ExportToModList.h | ||||||
|  |     modplatform/helpers/ExportToModList.cpp | ||||||
| ) | ) | ||||||
|  |  | ||||||
| set(FTB_SOURCES | set(FTB_SOURCES | ||||||
| @@ -768,6 +771,8 @@ SET(LAUNCHER_SOURCES | |||||||
|     ui/themes/SystemTheme.h |     ui/themes/SystemTheme.h | ||||||
|     ui/themes/ThemeManager.cpp |     ui/themes/ThemeManager.cpp | ||||||
|     ui/themes/ThemeManager.h |     ui/themes/ThemeManager.h | ||||||
|  |     ui/themes/CatPack.cpp | ||||||
|  |     ui/themes/CatPack.h | ||||||
|  |  | ||||||
|     # Processes |     # Processes | ||||||
|     LaunchController.h |     LaunchController.h | ||||||
| @@ -921,6 +926,8 @@ SET(LAUNCHER_SOURCES | |||||||
|     ui/dialogs/ExportInstanceDialog.h |     ui/dialogs/ExportInstanceDialog.h | ||||||
|     ui/dialogs/ExportPackDialog.cpp |     ui/dialogs/ExportPackDialog.cpp | ||||||
|     ui/dialogs/ExportPackDialog.h |     ui/dialogs/ExportPackDialog.h | ||||||
|  |     ui/dialogs/ExportToModListDialog.cpp | ||||||
|  |     ui/dialogs/ExportToModListDialog.h | ||||||
|     ui/dialogs/IconPickerDialog.cpp |     ui/dialogs/IconPickerDialog.cpp | ||||||
|     ui/dialogs/IconPickerDialog.h |     ui/dialogs/IconPickerDialog.h | ||||||
|     ui/dialogs/ImportResourceDialog.cpp |     ui/dialogs/ImportResourceDialog.cpp | ||||||
| @@ -1068,6 +1075,7 @@ qt_wrap_ui(LAUNCHER_UI | |||||||
|     ui/dialogs/SkinUploadDialog.ui |     ui/dialogs/SkinUploadDialog.ui | ||||||
|     ui/dialogs/ExportInstanceDialog.ui |     ui/dialogs/ExportInstanceDialog.ui | ||||||
|     ui/dialogs/ExportPackDialog.ui |     ui/dialogs/ExportPackDialog.ui | ||||||
|  |     ui/dialogs/ExportToModListDialog.ui | ||||||
|     ui/dialogs/IconPickerDialog.ui |     ui/dialogs/IconPickerDialog.ui | ||||||
|     ui/dialogs/ImportResourceDialog.ui |     ui/dialogs/ImportResourceDialog.ui | ||||||
|     ui/dialogs/MSALoginDialog.ui |     ui/dialogs/MSALoginDialog.ui | ||||||
|   | |||||||
| @@ -390,7 +390,10 @@ void LaunchController::launchInstance() | |||||||
|     m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher)); |     m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher)); | ||||||
|  |  | ||||||
|     // Prepend Version |     // 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(); |     m_launcher->start(); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| // SPDX-License-Identifier: GPL-3.0-only | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  PolyMC - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> |  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||||
|  |  *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> | ||||||
|  * |  * | ||||||
|  *  This program is free software: you can redistribute it and/or modify |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -33,56 +34,50 @@ | |||||||
|  *      limitations under the License. |  *      limitations under the License. | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
|  | #include "MMCZip.h" | ||||||
| #include <quazip/quazip.h> | #include <quazip/quazip.h> | ||||||
| #include <quazip/quazipdir.h> | #include <quazip/quazipdir.h> | ||||||
| #include <quazip/quazipfile.h> | #include <quazip/quazipfile.h> | ||||||
| #include "MMCZip.h" |  | ||||||
| #include "FileSystem.h" | #include "FileSystem.h" | ||||||
|  |  | ||||||
| #include <QCoreApplication> | #include <QCoreApplication> | ||||||
| #include <QDebug> | #include <QDebug> | ||||||
|  | #include <QtConcurrentRun> | ||||||
|  |  | ||||||
|  | namespace MMCZip { | ||||||
| // ours | // 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()); |     QuaZip modZip(from.filePath()); | ||||||
|     modZip.open(QuaZip::mdUnzip); |     modZip.open(QuaZip::mdUnzip); | ||||||
|  |  | ||||||
|     QuaZipFile fileInsideMod(&modZip); |     QuaZipFile fileInsideMod(&modZip); | ||||||
|     QuaZipFile zipOutFile(into); |     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(); |         QString filename = modZip.getCurrentFileName(); | ||||||
|         if (filter && !filter(filename)) |         if (filter && !filter(filename)) { | ||||||
|         { |             qDebug() << "Skipping file " << filename << " from " << from.fileName() << " - filtered"; | ||||||
|             qDebug() << "Skipping file " << filename << " from " |  | ||||||
|                         << from.fileName() << " - filtered"; |  | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
|         if (contained.contains(filename)) |         if (contained.contains(filename)) { | ||||||
|         { |             qDebug() << "Skipping already contained file " << filename << " from " << from.fileName(); | ||||||
|             qDebug() << "Skipping already contained file " << filename << " from " |  | ||||||
|                         << from.fileName(); |  | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
|         contained.insert(filename); |         contained.insert(filename); | ||||||
|  |  | ||||||
|         if (!fileInsideMod.open(QIODevice::ReadOnly)) |         if (!fileInsideMod.open(QIODevice::ReadOnly)) { | ||||||
|         { |  | ||||||
|             qCritical() << "Failed to open " << filename << " from " << from.fileName(); |             qCritical() << "Failed to open " << filename << " from " << from.fileName(); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         QuaZipNewInfo info_out(fileInsideMod.getActualFileName()); |         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"; |             qCritical() << "Failed to open " << filename << " in the jar"; | ||||||
|             fileInsideMod.close(); |             fileInsideMod.close(); | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         if (!JlCompress::copyData(fileInsideMod, zipOutFile)) |         if (!JlCompress::copyData(fileInsideMod, zipOutFile)) { | ||||||
|         { |  | ||||||
|             zipOutFile.close(); |             zipOutFile.close(); | ||||||
|             fileInsideMod.close(); |             fileInsideMod.close(); | ||||||
|             qCritical() << "Failed to copy data of " << filename << " into the jar"; |             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; |     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); |     QDir directory(dir); | ||||||
|     if (!directory.exists()) return false; |     if (!directory.exists()) | ||||||
|  |         return false; | ||||||
|  |  | ||||||
|     for (auto e : files) { |     for (auto e : files) { | ||||||
|         auto filePath = directory.relativeFilePath(e.absoluteFilePath()); |         auto filePath = directory.relativeFilePath(e.absoluteFilePath()); | ||||||
| @@ -109,17 +105,18 @@ bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, boo | |||||||
|                 srcPath = e.canonicalFilePath(); |                 srcPath = e.canonicalFilePath(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if( !JlCompress::compressFile(zip, srcPath, filePath)) return false; |         if (!JlCompress::compressFile(zip, srcPath, filePath)) | ||||||
|  |             return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return true; |     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); |     QuaZip zip(fileCompressed); | ||||||
|     QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); |     QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); | ||||||
|     if(!zip.open(QuaZip::mdCreate)) { |     if (!zip.open(QuaZip::mdCreate)) { | ||||||
|         QFile::remove(fileCompressed); |         QFile::remove(fileCompressed); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| @@ -127,7 +124,7 @@ bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList | |||||||
|     auto result = compressDirFiles(&zip, dir, files, followSymlinks); |     auto result = compressDirFiles(&zip, dir, files, followSymlinks); | ||||||
|  |  | ||||||
|     zip.close(); |     zip.close(); | ||||||
|     if(zip.getZipError()!=0) { |     if (zip.getZipError() != 0) { | ||||||
|         QFile::remove(fileCompressed); |         QFile::remove(fileCompressed); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| @@ -136,11 +133,10 @@ bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList | |||||||
| } | } | ||||||
|  |  | ||||||
| // ours | // 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); |     QuaZip zipOut(targetJarPath); | ||||||
|     if (!zipOut.open(QuaZip::mdCreate)) |     if (!zipOut.open(QuaZip::mdCreate)) { | ||||||
|     { |  | ||||||
|         QFile::remove(targetJarPath); |         QFile::remove(targetJarPath); | ||||||
|         qCritical() << "Failed to open the minecraft.jar for modding"; |         qCritical() << "Failed to open the minecraft.jar for modding"; | ||||||
|         return false; |         return false; | ||||||
| @@ -151,37 +147,29 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const | |||||||
|  |  | ||||||
|     // Modify the jar |     // Modify the jar | ||||||
|     // This needs to be done in reverse-order to ensure we respect the loading order of components |     // 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; |         const auto* mod = *i; | ||||||
|         // do not merge disabled mods. |         // do not merge disabled mods. | ||||||
|         if (!mod->enabled()) |         if (!mod->enabled()) | ||||||
|             continue; |             continue; | ||||||
|         if (mod->type() == ResourceType::ZIPFILE) |         if (mod->type() == ResourceType::ZIPFILE) { | ||||||
|         { |             if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) { | ||||||
|             if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) |  | ||||||
|             { |  | ||||||
|                 zipOut.close(); |                 zipOut.close(); | ||||||
|                 QFile::remove(targetJarPath); |                 QFile::remove(targetJarPath); | ||||||
|                 qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; |                 qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|         } |         } else if (mod->type() == ResourceType::SINGLEFILE) { | ||||||
|         else if (mod->type() == ResourceType::SINGLEFILE) |  | ||||||
|         { |  | ||||||
|             // FIXME: buggy - does not work with addedFiles |             // FIXME: buggy - does not work with addedFiles | ||||||
|             auto filename = mod->fileinfo(); |             auto filename = mod->fileinfo(); | ||||||
|             if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) |             if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) { | ||||||
|             { |  | ||||||
|                 zipOut.close(); |                 zipOut.close(); | ||||||
|                 QFile::remove(targetJarPath); |                 QFile::remove(targetJarPath); | ||||||
|                 qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; |                 qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|             addedFiles.insert(filename.fileName()); |             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 |             // untested, but seems to be unused / not possible to reach | ||||||
|             // FIXME: buggy - does not work with addedFiles |             // FIXME: buggy - does not work with addedFiles | ||||||
|             auto filename = mod->fileinfo(); |             auto filename = mod->fileinfo(); | ||||||
| @@ -190,25 +178,21 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const | |||||||
|             dir.cdUp(); |             dir.cdUp(); | ||||||
|             QString parent_dir = dir.absolutePath(); |             QString parent_dir = dir.absolutePath(); | ||||||
|             auto files = QFileInfoList(); |             auto files = QFileInfoList(); | ||||||
|             MMCZip::collectFileListRecursively(what_to_zip, nullptr, &files, nullptr); |             collectFileListRecursively(what_to_zip, nullptr, &files, nullptr); | ||||||
|  |  | ||||||
|             for (auto e : files) { |             for (auto e : files) { | ||||||
|                 if (addedFiles.contains(e.filePath())) |                 if (addedFiles.contains(e.filePath())) | ||||||
|                     files.removeAll(e); |                     files.removeAll(e); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (!MMCZip::compressDirFiles(&zipOut, parent_dir, files)) |             if (!compressDirFiles(&zipOut, parent_dir, files)) { | ||||||
|             { |  | ||||||
|                 zipOut.close(); |                 zipOut.close(); | ||||||
|                 QFile::remove(targetJarPath); |                 QFile::remove(targetJarPath); | ||||||
|                 qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; |                 qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|             qDebug() << "Adding folder " << filename.fileName() << " from " |             qDebug() << "Adding folder " << filename.fileName() << " from " << filename.absoluteFilePath(); | ||||||
|                      << filename.absoluteFilePath(); |         } else { | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             // Make sure we do not continue launching when something is missing or undefined... |             // Make sure we do not continue launching when something is missing or undefined... | ||||||
|             zipOut.close(); |             zipOut.close(); | ||||||
|             QFile::remove(targetJarPath); |             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(); |         zipOut.close(); | ||||||
|         QFile::remove(targetJarPath); |         QFile::remove(targetJarPath); | ||||||
|         qCritical() << "Failed to insert minecraft.jar contents."; |         qCritical() << "Failed to insert minecraft.jar contents."; | ||||||
| @@ -227,8 +210,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const | |||||||
|  |  | ||||||
|     // Recompress the jar |     // Recompress the jar | ||||||
|     zipOut.close(); |     zipOut.close(); | ||||||
|     if (zipOut.getZipError() != 0) |     if (zipOut.getZipError() != 0) { | ||||||
|     { |  | ||||||
|         QFile::remove(targetJarPath); |         QFile::remove(targetJarPath); | ||||||
|         qCritical() << "Failed to finalize minecraft.jar!"; |         qCritical() << "Failed to finalize minecraft.jar!"; | ||||||
|         return false; |         return false; | ||||||
| @@ -237,7 +219,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const | |||||||
| } | } | ||||||
|  |  | ||||||
| // ours | // 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); |     QuaZipDir rootDir(zip, root); | ||||||
|     for (auto&& fileName : rootDir.entryList(QDir::Files)) { |     for (auto&& fileName : rootDir.entryList(QDir::Files)) { | ||||||
| @@ -261,27 +243,23 @@ QString MMCZip::findFolderOfFileInZip(QuaZip* zip, const QString& what, const QS | |||||||
| } | } | ||||||
|  |  | ||||||
| // ours | // 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); |     QuaZipDir rootDir(zip, root); | ||||||
|     for(auto fileName: rootDir.entryList(QDir::Files)) |     for (auto fileName : rootDir.entryList(QDir::Files)) { | ||||||
|     { |         if (fileName == what) { | ||||||
|         if(fileName == what) |  | ||||||
|         { |  | ||||||
|             result.append(root); |             result.append(root); | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     for(auto fileName: rootDir.entryList(QDir::Dirs)) |     for (auto fileName : rootDir.entryList(QDir::Dirs)) { | ||||||
|     { |  | ||||||
|         findFilesInZip(zip, what, result, root + fileName); |         findFilesInZip(zip, what, result, root + fileName); | ||||||
|     } |     } | ||||||
|     return !result.isEmpty(); |     return !result.isEmpty(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| // ours | // 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); |     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; |     qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target; | ||||||
|     auto numEntries = zip->getEntriesCount(); |     auto numEntries = zip->getEntriesCount(); | ||||||
|     if(numEntries < 0) { |     if (numEntries < 0) { | ||||||
|         qWarning() << "Failed to enumerate files in archive"; |         qWarning() << "Failed to enumerate files in archive"; | ||||||
|         return std::nullopt; |         return std::nullopt; | ||||||
|     } |     } else if (numEntries == 0) { | ||||||
|     else if(numEntries == 0) { |  | ||||||
|         qDebug() << "Extracting empty archives seems odd..."; |         qDebug() << "Extracting empty archives seems odd..."; | ||||||
|         return extracted; |         return extracted; | ||||||
|     } |     } else if (!zip->goToFirstFile()) { | ||||||
|     else if (!zip->goToFirstFile()) |  | ||||||
|     { |  | ||||||
|         qWarning() << "Failed to seek to first file in zip"; |         qWarning() << "Failed to seek to first file in zip"; | ||||||
|         return std::nullopt; |         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))) { |         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; |             return std::nullopt; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -345,7 +321,8 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         extracted.append(target_file_path); |         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; |         qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; | ||||||
|     } while (zip->goToNextFile()); |     } while (zip->goToNextFile()); | ||||||
| @@ -354,66 +331,66 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su | |||||||
| } | } | ||||||
|  |  | ||||||
| // ours | // 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); |     return JlCompress::extractFile(zip, file, target); | ||||||
| } | } | ||||||
|  |  | ||||||
| // ours | // ours | ||||||
| std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir) | std::optional<QStringList> extractDir(QString fileCompressed, QString dir) | ||||||
| { | { | ||||||
|     QuaZip zip(fileCompressed); |     QuaZip zip(fileCompressed); | ||||||
|     if (!zip.open(QuaZip::mdUnzip)) |     if (!zip.open(QuaZip::mdUnzip)) { | ||||||
|     { |  | ||||||
|         // check if this is a minimum size empty zip file... |         // check if this is a minimum size empty zip file... | ||||||
|         QFileInfo fileInfo(fileCompressed); |         QFileInfo fileInfo(fileCompressed); | ||||||
|         if(fileInfo.size() == 22) { |         if (fileInfo.size() == 22) { | ||||||
|             return QStringList(); |             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 std::nullopt; | ||||||
|     } |     } | ||||||
|     return MMCZip::extractSubDir(&zip, "", dir); |     return extractSubDir(&zip, "", dir); | ||||||
| } | } | ||||||
|  |  | ||||||
| // ours | // 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); |     QuaZip zip(fileCompressed); | ||||||
|     if (!zip.open(QuaZip::mdUnzip)) |     if (!zip.open(QuaZip::mdUnzip)) { | ||||||
|     { |  | ||||||
|         // check if this is a minimum size empty zip file... |         // check if this is a minimum size empty zip file... | ||||||
|         QFileInfo fileInfo(fileCompressed); |         QFileInfo fileInfo(fileCompressed); | ||||||
|         if(fileInfo.size() == 22) { |         if (fileInfo.size() == 22) { | ||||||
|             return QStringList(); |             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 std::nullopt; | ||||||
|     } |     } | ||||||
|     return MMCZip::extractSubDir(&zip, subdir, dir); |     return extractSubDir(&zip, subdir, dir); | ||||||
| } | } | ||||||
|  |  | ||||||
| // ours | // ours | ||||||
| bool MMCZip::extractFile(QString fileCompressed, QString file, QString target) | bool extractFile(QString fileCompressed, QString file, QString target) | ||||||
| { | { | ||||||
|     QuaZip zip(fileCompressed); |     QuaZip zip(fileCompressed); | ||||||
|     if (!zip.open(QuaZip::mdUnzip)) |     if (!zip.open(QuaZip::mdUnzip)) { | ||||||
|     { |  | ||||||
|         // check if this is a minimum size empty zip file... |         // check if this is a minimum size empty zip file... | ||||||
|         QFileInfo fileInfo(fileCompressed); |         QFileInfo fileInfo(fileCompressed); | ||||||
|         if(fileInfo.size() == 22) { |         if (fileInfo.size() == 22) { | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|         qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); |         qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|     return MMCZip::extractRelFile(&zip, file, target); |     return extractRelFile(&zip, file, target); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList *files, | bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter) | ||||||
|                                         MMCZip::FilterFunction excludeFilter) { | { | ||||||
|     QDir rootDirectory(rootDir); |     QDir rootDirectory(rootDir); | ||||||
|     if (!rootDirectory.exists()) return false; |     if (!rootDirectory.exists()) | ||||||
|  |         return false; | ||||||
|  |  | ||||||
|     QDir directory; |     QDir directory; | ||||||
|     if (subDir == nullptr) |     if (subDir == nullptr) | ||||||
| @@ -421,25 +398,107 @@ bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& s | |||||||
|     else |     else | ||||||
|         directory = QDir(subDir); |         directory = QDir(subDir); | ||||||
|  |  | ||||||
|     if (!directory.exists()) return false;  // shouldn't ever happen |     if (!directory.exists()) | ||||||
|  |         return false;  // shouldn't ever happen | ||||||
|  |  | ||||||
|     // recurse directories |     // recurse directories | ||||||
|     QFileInfoList entries = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden); |     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)) |         if (!collectFileListRecursively(rootDir, e.filePath(), files, excludeFilter)) | ||||||
|             return false; |             return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // collect files |     // collect files | ||||||
|     entries = directory.entryInfoList(QDir::Files); |     entries = directory.entryInfoList(QDir::Files); | ||||||
|     for (const auto& e: entries) { |     for (const auto& e : entries) { | ||||||
|         QString relativeFilePath = rootDirectory.relativeFilePath(e.absoluteFilePath()); |         QString relativeFilePath = rootDirectory.relativeFilePath(e.absoluteFilePath()); | ||||||
|         if (excludeFilter && excludeFilter(relativeFilePath)) { |         if (excludeFilter && excludeFilter(relativeFilePath)) { | ||||||
|             qDebug() << "Skipping file " << relativeFilePath; |             qDebug() << "Skipping file " << relativeFilePath; | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         files->append(e);  // we want the original paths for MMCZip::compressDirFiles |         files->append(e);  // we want the original paths for compressDirFiles | ||||||
|     } |     } | ||||||
|     return true; |     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 | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  PolyMC - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> |  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||||
|  |  *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> | ||||||
|  * |  * | ||||||
|  *  This program is free software: you can redistribute it and/or modify |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -35,110 +36,157 @@ | |||||||
|  |  | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include <QString> | #include <quazip.h> | ||||||
| #include <QFileInfo> |  | ||||||
| #include <QSet> |  | ||||||
| #include "minecraft/mod/Mod.h" |  | ||||||
| #include <functional> |  | ||||||
|  |  | ||||||
| #include <quazip/JlCompress.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 <optional> | ||||||
|  | #include "minecraft/mod/Mod.h" | ||||||
|  | #include "tasks/Task.h" | ||||||
|  |  | ||||||
| namespace MMCZip | namespace MMCZip { | ||||||
| { | using FilterFunction = std::function<bool(const QString&)>; | ||||||
|     using FilterFunction = std::function<bool(const QString &)>; |  | ||||||
|  |  | ||||||
|     /** | /** | ||||||
|      * Merge two zip files, using a filter function |  * Merge two zip files, using a filter function | ||||||
|      */ |  */ | ||||||
|     bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, | bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction filter = nullptr); | ||||||
|                                             const FilterFunction filter = nullptr); |  | ||||||
|  |  | ||||||
|     /** | /** | ||||||
|      * Compress directory, by providing a list of files to compress |  * Compress directory, by providing a list of files to compress | ||||||
|      * \param zip target archive |  * \param zip target archive | ||||||
|      * \param dir directory that will be compressed (to compress with relative paths) |  * \param dir directory that will be compressed (to compress with relative paths) | ||||||
|      * \param files list of files to compress |  * \param files list of files to compress | ||||||
|      * \param followSymlinks should follow symlinks when compressing file data |  * \param followSymlinks should follow symlinks when compressing file data | ||||||
|      * \return true for success or false for failure |  * \return true for success or false for failure | ||||||
|      */ |  */ | ||||||
|     bool compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, bool followSymlinks = false); | bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool followSymlinks = false); | ||||||
|  |  | ||||||
|     /** | /** | ||||||
|      * Compress directory, by providing a list of files to compress |  * Compress directory, by providing a list of files to compress | ||||||
|      * \param fileCompressed target archive file |  * \param fileCompressed target archive file | ||||||
|      * \param dir directory that will be compressed (to compress with relative paths) |  * \param dir directory that will be compressed (to compress with relative paths) | ||||||
|      * \param files list of files to compress |  * \param files list of files to compress | ||||||
|      * \param followSymlinks should follow symlinks when compressing file data |  * \param followSymlinks should follow symlinks when compressing file data | ||||||
|      * \return true for success or false for failure |  * \return true for success or false for failure | ||||||
|      */ |  */ | ||||||
|     bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false); | bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false); | ||||||
|  |  | ||||||
|     /** | /** | ||||||
|      * take a source jar, add mods to it, resulting in target jar |  * take a source jar, add mods to it, resulting in target jar | ||||||
|      */ |  */ | ||||||
|     bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods); | bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods); | ||||||
|  |  | ||||||
|     /** | /** | ||||||
|      * Find a single file in archive by file name (not path) |  * Find a single file in archive by file name (not path) | ||||||
|      * |  * | ||||||
|      * \param ignore_paths paths to skip when recursing the search |  * \param ignore_paths paths to skip when recursing the search | ||||||
|      * |  * | ||||||
|      * \return the path prefix where the file is |  * \return the path prefix where the file is | ||||||
|      */ |  */ | ||||||
|     QString findFolderOfFileInZip(QuaZip * zip, const QString & what, const QStringList& ignore_paths = {}, const QString &root = QString("")); | 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 |  * 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 |  * If a file is found in a path, no deeper paths are searched | ||||||
|      * |  * | ||||||
|      * \return true if anything was found |  * \return true if anything was found | ||||||
|      */ |  */ | ||||||
|     bool findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString()); | bool findFilesInZip(QuaZip* zip, const QString& what, QStringList& result, const QString& root = QString()); | ||||||
|  |  | ||||||
|     /** | /** | ||||||
|      * Extract a subdirectory from an archive |  * Extract a subdirectory from an archive | ||||||
|      */ |  */ | ||||||
|     std::optional<QStringList> extractSubDir(QuaZip *zip, const QString & subdir, const QString &target); | 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. |  * Extract a whole archive. | ||||||
|      * |  * | ||||||
|      * \param fileCompressed The name of the archive. |  * \param fileCompressed The name of the archive. | ||||||
|      * \param dir The directory to extract to, the current directory if left empty. |  * \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. |  * \return The list of the full paths of the files extracted, empty on failure. | ||||||
|      */ |  */ | ||||||
|     std::optional<QStringList> extractDir(QString fileCompressed, QString dir); | std::optional<QStringList> extractDir(QString fileCompressed, QString dir); | ||||||
|  |  | ||||||
|     /** | /** | ||||||
|      * Extract a subdirectory from an archive |  * Extract a subdirectory from an archive | ||||||
|      * |  * | ||||||
|      * \param fileCompressed The name of the archive. |  * \param fileCompressed The name of the archive. | ||||||
|      * \param subdir The directory within the archive to extract |  * \param subdir The directory within the archive to extract | ||||||
|      * \param dir The directory to extract to, the current directory if left empty. |  * \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. |  * \return The list of the full paths of the files extracted, empty on failure. | ||||||
|      */ |  */ | ||||||
|     std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir); | std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir); | ||||||
|  |  | ||||||
|     /** | /** | ||||||
|      * Extract a single file from an archive into a directory |  * Extract a single file from an archive into a directory | ||||||
|      * |  * | ||||||
|      * \param fileCompressed The name of the archive. |  * \param fileCompressed The name of the archive. | ||||||
|      * \param file The file within the archive to extract |  * \param file The file within the archive to extract | ||||||
|      * \param dir The directory to extract to, the current directory if left empty. |  * \param dir The directory to extract to, the current directory if left empty. | ||||||
|      * \return true for success or false for failure |  * \return true for success or false for failure | ||||||
|      */ |  */ | ||||||
|     bool extractFile(QString fileCompressed, QString file, QString dir); | 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. |  * Populate a QFileInfoList with a directory tree recursively, while allowing to excludeFilter what shouldn't be included. | ||||||
|      * \param rootDir directory to start off |  * \param rootDir directory to start off | ||||||
|      * \param subDir subdirectory, should be nullptr for first invocation |  * \param subDir subdirectory, should be nullptr for first invocation | ||||||
|      * \param files resulting list of QFileInfo |  * \param files resulting list of QFileInfo | ||||||
|      * \param excludeFilter function to excludeFilter which files shouldn't be included (returning true means to excude) |  * \param excludeFilter function to excludeFilter which files shouldn't be included (returning true means to excude) | ||||||
|      * \return true for success or false for failure |  * \return true for success or false for failure | ||||||
|      */ |  */ | ||||||
|     bool collectFileListRecursively(const QString &rootDir, const QString &subDir, QFileInfoList *files, FilterFunction excludeFilter); | 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 | ||||||
|   | |||||||
| @@ -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 "JavaInstall.h" | ||||||
|  |  | ||||||
|  | #include "BaseVersion.h" | ||||||
| #include "StringUtils.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); |     auto archCompare = StringUtils::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive); | ||||||
|     if(archCompare != 0) |     if (archCompare != 0) | ||||||
|         return archCompare < 0; |         return archCompare < 0; | ||||||
|     if(id < rhs.id) |     if (id < rhs.id) { | ||||||
|     { |  | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|     if(id > rhs.id) |     if (id > rhs.id) { | ||||||
|     { |  | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|     return StringUtils::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0; |     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; |     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)); |     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 | #pragma once | ||||||
|  |  | ||||||
| #include "BaseVersion.h" | #include "BaseVersion.h" | ||||||
| #include "JavaVersion.h" | #include "JavaVersion.h" | ||||||
|  |  | ||||||
| struct JavaInstall : public BaseVersion | struct JavaInstall : public BaseVersion { | ||||||
| { |     JavaInstall() {} | ||||||
|     JavaInstall(){} |     JavaInstall(QString id, QString arch, QString path) : id(id), arch(arch), path(path) {} | ||||||
|     JavaInstall(QString id, QString arch, QString path) |     virtual QString descriptor() { return id.toString(); } | ||||||
|     : id(id), arch(arch), path(path) |  | ||||||
|     { |  | ||||||
|     } |  | ||||||
|     virtual QString descriptor() |  | ||||||
|     { |  | ||||||
|         return id.toString(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     virtual QString name() |     virtual QString name() { return id.toString(); } | ||||||
|     { |  | ||||||
|         return id.toString(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     virtual QString typeString() const |     virtual QString typeString() const { return arch; } | ||||||
|     { |  | ||||||
|         return arch; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool operator<(const JavaInstall & rhs); |     virtual bool operator<(BaseVersion& a) override; | ||||||
|     bool operator==(const JavaInstall & rhs); |     virtual bool operator>(BaseVersion& a) override; | ||||||
|     bool operator>(const JavaInstall & rhs); |     bool operator<(const JavaInstall& rhs); | ||||||
|  |     bool operator==(const JavaInstall& rhs); | ||||||
|  |     bool operator>(const JavaInstall& rhs); | ||||||
|  |  | ||||||
|     JavaVersion id; |     JavaVersion id; | ||||||
|     QString arch; |     QString arch; | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> |  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||||
|  *  Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org> |  *  Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org> | ||||||
|  *  Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> |  *  Copyright (C) 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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -186,6 +187,10 @@ void MinecraftInstance::loadSpecificSettings() | |||||||
|         m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride); |         m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride); | ||||||
|         m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride); |         m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride); | ||||||
|  |  | ||||||
|  |         // Mod loader specific options | ||||||
|  |         auto modLoaderSettings = m_settings->registerSetting("OverrideModLoaderSettings", false); | ||||||
|  |         m_settings->registerOverride(global_settings->getSetting("DisableQuiltBeacon"), modLoaderSettings); | ||||||
|  |  | ||||||
|         m_settings->set("InstanceType", "OneSix"); |         m_settings->set("InstanceType", "OneSix"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -391,6 +396,12 @@ QStringList MinecraftInstance::extraArguments() | |||||||
|         agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath()); |         agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath()); | ||||||
|         list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument())); |         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; |     return list; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ bool AuthSession::MakeOffline(QString offline_playername) | |||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|     session = "-"; |     session = "-"; | ||||||
|  |     access_token = "0"; | ||||||
|     player_name = offline_playername; |     player_name = offline_playername; | ||||||
|     status = PlayableOffline; |     status = PlayableOffline; | ||||||
|     return true; |     return true; | ||||||
|   | |||||||
| @@ -37,6 +37,7 @@ | |||||||
|  |  | ||||||
| #include "MinecraftAccount.h" | #include "MinecraftAccount.h" | ||||||
|  |  | ||||||
|  | #include <QCryptographicHash> | ||||||
| #include <QUuid> | #include <QUuid> | ||||||
| #include <QJsonObject> | #include <QJsonObject> | ||||||
| #include <QJsonArray> | #include <QJsonArray> | ||||||
| @@ -100,7 +101,7 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username) | |||||||
|     account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); |     account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); | ||||||
|     account->data.minecraftEntitlement.ownsMinecraft = true; |     account->data.minecraftEntitlement.ownsMinecraft = true; | ||||||
|     account->data.minecraftEntitlement.canPlayMinecraft = 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.name = username; | ||||||
|     account->data.minecraftProfile.validity = Katabasis::Validity::Certain; |     account->data.minecraftProfile.validity = Katabasis::Validity::Certain; | ||||||
|     return account; |     return account; | ||||||
| @@ -334,3 +335,32 @@ void MinecraftAccount::incrementUses() | |||||||
|         qWarning() << "Profile" << data.profileId() << "is now in use."; |         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 loadFromJsonV2(const QJsonObject &json); | ||||||
|     static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json); |     static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json); | ||||||
|  |  | ||||||
|  |     static QUuid uuidFromUsername(QString username); | ||||||
|  |  | ||||||
|     //! Saves a MinecraftAccount to a JSON object and returns it. |     //! Saves a MinecraftAccount to a JSON object and returns it. | ||||||
|     QJsonObject saveToJson() const; |     QJsonObject saveToJson() const; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,6 +23,10 @@ bool Flame::FileResolvingTask::abort() | |||||||
|  |  | ||||||
| void Flame::FileResolvingTask::executeTask() | void Flame::FileResolvingTask::executeTask() | ||||||
| { | { | ||||||
|  |     if (m_toProcess.files.isEmpty()) {  // no file to resolve so leave it empty and emit success immediately | ||||||
|  |         emitSucceeded(); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|     setStatus(tr("Resolving mod IDs...")); |     setStatus(tr("Resolving mod IDs...")); | ||||||
|     setProgress(0, 3); |     setProgress(0, 3); | ||||||
|     m_dljob.reset(new NetJob("Mod id resolver", m_network)); |     m_dljob.reset(new NetJob("Mod id resolver", m_network)); | ||||||
| @@ -130,12 +134,13 @@ void Flame::FileResolvingTask::netJobFinished() | |||||||
|     m_checkJob->start(); |     m_checkJob->start(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Flame::FileResolvingTask::modrinthCheckFinished() { | void Flame::FileResolvingTask::modrinthCheckFinished() | ||||||
|  | { | ||||||
|     setProgress(2, 3); |     setProgress(2, 3); | ||||||
|     qDebug() << "Finished with blocked mods : " << blockedProjects.size(); |     qDebug() << "Finished with blocked mods : " << blockedProjects.size(); | ||||||
|  |  | ||||||
|     for (auto it = blockedProjects.keyBegin(); it != blockedProjects.keyEnd(); it++) { |     for (auto it = blockedProjects.keyBegin(); it != blockedProjects.keyEnd(); it++) { | ||||||
|         auto &out = *it; |         auto& out = *it; | ||||||
|         auto bytes = blockedProjects[out]; |         auto bytes = blockedProjects[out]; | ||||||
|         if (!out->resolved) { |         if (!out->resolved) { | ||||||
|             continue; |             continue; | ||||||
| @@ -155,15 +160,13 @@ void Flame::FileResolvingTask::modrinthCheckFinished() { | |||||||
|             out->resolved = false; |             out->resolved = false; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     //copy to an output list and filter out projects found on modrinth |     // copy to an output list and filter out projects found on modrinth | ||||||
|     auto block = std::make_shared<QList<File*>>(); |     auto block = std::make_shared<QList<File*>>(); | ||||||
|     auto it = blockedProjects.keys(); |     auto it = blockedProjects.keys(); | ||||||
|     std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) { |     std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File* f) { return !f->resolved; }); | ||||||
|         return !f->resolved; |     // Display not found mods early | ||||||
|     }); |  | ||||||
|     //Display not found mods early |  | ||||||
|     if (!block->empty()) { |     if (!block->empty()) { | ||||||
|         //blocked mods found, we need the slug for displaying.... we need another job :D ! |         // blocked mods found, we need the slug for displaying.... we need another job :D ! | ||||||
|         m_slugJob.reset(new NetJob("Slug Job", m_network)); |         m_slugJob.reset(new NetJob("Slug Job", m_network)); | ||||||
|         int index = 0; |         int index = 0; | ||||||
|         for (auto mod : *block) { |         for (auto mod : *block) { | ||||||
| @@ -175,8 +178,8 @@ void Flame::FileResolvingTask::modrinthCheckFinished() { | |||||||
|             QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() { |             QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() { | ||||||
|                 auto mod = block->at(index);  // use the shared_ptr so it is captured and only freed when we are done |                 auto mod = block->at(index);  // use the shared_ptr so it is captured and only freed when we are done | ||||||
|                 auto json = QJsonDocument::fromJson(*output); |                 auto json = QJsonDocument::fromJson(*output); | ||||||
|                 auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"), |                 auto base = | ||||||
|                         "websiteUrl"); |                     Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json), "data"), "links"), "websiteUrl"); | ||||||
|                 auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId)); |                 auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId)); | ||||||
|                 mod->websiteUrl = link; |                 mod->websiteUrl = link; | ||||||
|             }); |             }); | ||||||
|   | |||||||
| @@ -57,17 +57,13 @@ | |||||||
| #include <QDebug> | #include <QDebug> | ||||||
| #include <QFileInfo> | #include <QFileInfo> | ||||||
|  |  | ||||||
|  | #include "meta/Index.h" | ||||||
|  | #include "meta/VersionList.h" | ||||||
| #include "minecraft/World.h" | #include "minecraft/World.h" | ||||||
| #include "minecraft/mod/tasks/LocalResourceParse.h" | #include "minecraft/mod/tasks/LocalResourceParse.h" | ||||||
|  |  | ||||||
| #include "net/ApiDownload.h" | #include "net/ApiDownload.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; | static const FlameAPI api; | ||||||
|  |  | ||||||
| bool FlameCreationTask::abort() | bool FlameCreationTask::abort() | ||||||
| @@ -261,6 +257,56 @@ bool FlameCreationTask::updateInstance() | |||||||
|     return false; |     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() | bool FlameCreationTask::createInstance() | ||||||
| { | { | ||||||
|     QEventLoop loop; |     QEventLoop loop; | ||||||
| @@ -299,22 +345,29 @@ bool FlameCreationTask::createInstance() | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     QString forgeVersion; |     QString loaderType; | ||||||
|     QString fabricVersion; |     QString loaderUid; | ||||||
|     // TODO: is Quilt relevant here? |     QString loaderVersion; | ||||||
|  |  | ||||||
|     for (auto& loader : m_pack.minecraft.modLoaders) { |     for (auto& loader : m_pack.minecraft.modLoaders) { | ||||||
|         auto id = loader.id; |         auto id = loader.id; | ||||||
|         if (id.startsWith("forge-")) { |         if (id.startsWith("forge-")) { | ||||||
|             id.remove("forge-"); |             id.remove("forge-"); | ||||||
|             forgeVersion = id; |             loaderType = "forge"; | ||||||
|             continue; |             loaderUid = "net.minecraftforge"; | ||||||
|         } |         } else if (loaderType == "fabric") { | ||||||
|         if (id.startsWith("fabric-")) { |  | ||||||
|             id.remove("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; |             continue; | ||||||
|         } |         } | ||||||
|         logWarning(tr("Unknown mod loader in manifest: %1").arg(id)); |         loaderVersion = id; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); |     QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); | ||||||
| @@ -331,19 +384,12 @@ bool FlameCreationTask::createInstance() | |||||||
|     auto components = instance.getPackProfile(); |     auto components = instance.getPackProfile(); | ||||||
|     components->buildingFromScratch(); |     components->buildingFromScratch(); | ||||||
|     components->setComponentVersion("net.minecraft", mcVersion, true); |     components->setComponentVersion("net.minecraft", mcVersion, true); | ||||||
|     if (!forgeVersion.isEmpty()) { |     if (!loaderType.isEmpty()) { | ||||||
|         // FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata. |         auto version = getVersionForLoader(loaderUid, loaderType, loaderVersion, mcVersion); | ||||||
|         if (forgeVersion == "recommended") { |         if (version.isEmpty()) | ||||||
|             if (forgemap.contains(mcVersion)) { |             return false; | ||||||
|                 forgeVersion = forgemap[mcVersion]; |         components->setComponentVersion(loaderUid, version); | ||||||
|             } else { |  | ||||||
|                 logWarning(tr("Could not map recommended Forge version for Minecraft %1").arg(mcVersion)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         components->setComponentVersion("net.minecraftforge", forgeVersion); |  | ||||||
|     } |     } | ||||||
|     if (!fabricVersion.isEmpty()) |  | ||||||
|         components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); |  | ||||||
|  |  | ||||||
|     if (m_instIcon != "default") { |     if (m_instIcon != "default") { | ||||||
|         instance.setIconKey(m_instIcon); |         instance.setIconKey(m_instIcon); | ||||||
| @@ -504,7 +550,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) | |||||||
|         m_files_job.reset(); |         m_files_job.reset(); | ||||||
|         setError(reason); |         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)); |         setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); | ||||||
|         setProgress(current, total); |         setProgress(current, total); | ||||||
|     }); |     }); | ||||||
| @@ -547,7 +593,6 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods) | |||||||
|     setAbortable(true); |     setAbortable(true); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| void FlameCreationTask::validateZIPResouces() | void FlameCreationTask::validateZIPResouces() | ||||||
| { | { | ||||||
|     qDebug() << "Validating whether resources stored as .zip are in the right place"; |     qDebug() << "Validating whether resources stored as .zip are in the right place"; | ||||||
| @@ -571,7 +616,7 @@ void FlameCreationTask::validateZIPResouces() | |||||||
|             return localPath; |             return localPath; | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         auto installWorld = [this](QString worldPath){ |         auto installWorld = [this](QString worldPath) { | ||||||
|             qDebug() << "Installing World from" << worldPath; |             qDebug() << "Installing World from" << worldPath; | ||||||
|             QFileInfo worldFileInfo(worldPath); |             QFileInfo worldFileInfo(worldPath); | ||||||
|             World w(worldFileInfo); |             World w(worldFileInfo); | ||||||
| @@ -588,29 +633,29 @@ void FlameCreationTask::validateZIPResouces() | |||||||
|         QString worldPath; |         QString worldPath; | ||||||
|  |  | ||||||
|         switch (type) { |         switch (type) { | ||||||
|             case PackedResourceType::Mod : |             case PackedResourceType::Mod: | ||||||
|                 validatePath(fileName, targetFolder, "mods"); |                 validatePath(fileName, targetFolder, "mods"); | ||||||
|                 break; |                 break; | ||||||
|             case PackedResourceType::ResourcePack : |             case PackedResourceType::ResourcePack: | ||||||
|                 validatePath(fileName, targetFolder, "resourcepacks"); |                 validatePath(fileName, targetFolder, "resourcepacks"); | ||||||
|                 break; |                 break; | ||||||
|             case PackedResourceType::TexturePack : |             case PackedResourceType::TexturePack: | ||||||
|                 validatePath(fileName, targetFolder, "texturepacks"); |                 validatePath(fileName, targetFolder, "texturepacks"); | ||||||
|                 break; |                 break; | ||||||
|             case PackedResourceType::DataPack : |             case PackedResourceType::DataPack: | ||||||
|                 validatePath(fileName, targetFolder, "datapacks"); |                 validatePath(fileName, targetFolder, "datapacks"); | ||||||
|                 break; |                 break; | ||||||
|             case PackedResourceType::ShaderPack : |             case PackedResourceType::ShaderPack: | ||||||
|                 // in theroy flame API can't do this but who knows, that *may* change ? |                 // 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 |                 // better to handle it if it *does* occure in the future | ||||||
|                 validatePath(fileName, targetFolder, "shaderpacks"); |                 validatePath(fileName, targetFolder, "shaderpacks"); | ||||||
|                 break; |                 break; | ||||||
|             case PackedResourceType::WorldSave : |             case PackedResourceType::WorldSave: | ||||||
|                 worldPath = validatePath(fileName, targetFolder, "saves"); |                 worldPath = validatePath(fileName, targetFolder, "saves"); | ||||||
|                 installWorld(worldPath); |                 installWorld(worldPath); | ||||||
|                 break; |                 break; | ||||||
|             case PackedResourceType::UNKNOWN : |             case PackedResourceType::UNKNOWN: | ||||||
|             default : |             default: | ||||||
|                 qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is."; |                 qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is."; | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -57,10 +57,7 @@ class FlameCreationTask final : public InstanceCreationTask { | |||||||
|                       QString id, |                       QString id, | ||||||
|                       QString version_id, |                       QString version_id, | ||||||
|                       QString original_instance_id = {}) |                       QString original_instance_id = {}) | ||||||
|         : InstanceCreationTask() |         : InstanceCreationTask(), m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(version_id)) | ||||||
|         , m_parent(parent) |  | ||||||
|         , m_managed_id(std::move(id)) |  | ||||||
|         , m_managed_version_id(std::move(version_id)) |  | ||||||
|     { |     { | ||||||
|         setStagingPath(staging_path); |         setStagingPath(staging_path); | ||||||
|         setParentSettings(global_settings); |         setParentSettings(global_settings); | ||||||
| @@ -78,6 +75,7 @@ class FlameCreationTask final : public InstanceCreationTask { | |||||||
|     void setupDownloadJob(QEventLoop&); |     void setupDownloadJob(QEventLoop&); | ||||||
|     void copyBlockedMods(QList<BlockedMod> const& blocked_mods); |     void copyBlockedMods(QList<BlockedMod> const& blocked_mods); | ||||||
|     void validateZIPResouces(); |     void validateZIPResouces(); | ||||||
|  |     QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion); | ||||||
|  |  | ||||||
|    private: |    private: | ||||||
|     QWidget* m_parent = nullptr; |     QWidget* m_parent = nullptr; | ||||||
|   | |||||||
							
								
								
									
										200
									
								
								launcher/modplatform/helpers/ExportToModList.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								launcher/modplatform/helpers/ExportToModList.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,200 @@ | |||||||
|  | // 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 "ExportToModList.h" | ||||||
|  | #include <QJsonArray> | ||||||
|  | #include <QJsonDocument> | ||||||
|  | #include <QJsonObject> | ||||||
|  |  | ||||||
|  | namespace ExportToModList { | ||||||
|  | QString toHTML(QList<Mod*> mods, OptionalData extraData) | ||||||
|  | { | ||||||
|  |     QStringList lines; | ||||||
|  |     for (auto mod : mods) { | ||||||
|  |         auto meta = mod->metadata(); | ||||||
|  |         auto modName = mod->name().toHtmlEscaped(); | ||||||
|  |         if (extraData & Url) { | ||||||
|  |             auto url = mod->metaurl().toHtmlEscaped(); | ||||||
|  |             if (!url.isEmpty()) | ||||||
|  |                 modName = QString("<a href=\"%1\">%2</a>").arg(url, modName); | ||||||
|  |         } | ||||||
|  |         auto line = modName; | ||||||
|  |         if (extraData & Version) { | ||||||
|  |             auto ver = mod->version(); | ||||||
|  |             if (ver.isEmpty() && meta != nullptr) | ||||||
|  |                 ver = meta->version().toString(); | ||||||
|  |             if (!ver.isEmpty()) | ||||||
|  |                 line += QString(" [%1]").arg(ver.toHtmlEscaped()); | ||||||
|  |         } | ||||||
|  |         if (extraData & Authors && !mod->authors().isEmpty()) | ||||||
|  |             line += " by " + mod->authors().join(", ").toHtmlEscaped(); | ||||||
|  |         lines.append(QString("<li>%1</li>").arg(line)); | ||||||
|  |     } | ||||||
|  |     return QString("<html><body><ul>\n\t%1\n</ul></body></html>").arg(lines.join("\n\t")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QString toMarkdown(QList<Mod*> mods, OptionalData extraData) | ||||||
|  | { | ||||||
|  |     QStringList lines; | ||||||
|  |     for (auto mod : mods) { | ||||||
|  |         auto meta = mod->metadata(); | ||||||
|  |         auto modName = mod->name(); | ||||||
|  |         if (extraData & Url) { | ||||||
|  |             auto url = mod->metaurl(); | ||||||
|  |             if (!url.isEmpty()) | ||||||
|  |                 modName = QString("[%1](%2)").arg(modName, url); | ||||||
|  |         } | ||||||
|  |         auto line = modName; | ||||||
|  |         if (extraData & Version) { | ||||||
|  |             auto ver = mod->version(); | ||||||
|  |             if (ver.isEmpty() && meta != nullptr) | ||||||
|  |                 ver = meta->version().toString(); | ||||||
|  |             if (!ver.isEmpty()) | ||||||
|  |                 line += QString(" [%1]").arg(ver); | ||||||
|  |         } | ||||||
|  |         if (extraData & Authors && !mod->authors().isEmpty()) | ||||||
|  |             line += " by " + mod->authors().join(", "); | ||||||
|  |         lines << "- " + line; | ||||||
|  |     } | ||||||
|  |     return lines.join("\n"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QString toPlainTXT(QList<Mod*> mods, OptionalData extraData) | ||||||
|  | { | ||||||
|  |     QStringList lines; | ||||||
|  |     for (auto mod : mods) { | ||||||
|  |         auto meta = mod->metadata(); | ||||||
|  |         auto modName = mod->name(); | ||||||
|  |  | ||||||
|  |         auto line = modName; | ||||||
|  |         if (extraData & Url) { | ||||||
|  |             auto url = mod->metaurl(); | ||||||
|  |             if (!url.isEmpty()) | ||||||
|  |                 line += QString(" (%1)").arg(url); | ||||||
|  |         } | ||||||
|  |         if (extraData & Version) { | ||||||
|  |             auto ver = mod->version(); | ||||||
|  |             if (ver.isEmpty() && meta != nullptr) | ||||||
|  |                 ver = meta->version().toString(); | ||||||
|  |             if (!ver.isEmpty()) | ||||||
|  |                 line += QString(" [%1]").arg(ver); | ||||||
|  |         } | ||||||
|  |         if (extraData & Authors && !mod->authors().isEmpty()) | ||||||
|  |             line += " by " + mod->authors().join(", "); | ||||||
|  |         lines << line; | ||||||
|  |     } | ||||||
|  |     return lines.join("\n"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QString toJSON(QList<Mod*> mods, OptionalData extraData) | ||||||
|  | { | ||||||
|  |     QJsonArray lines; | ||||||
|  |     for (auto mod : mods) { | ||||||
|  |         auto meta = mod->metadata(); | ||||||
|  |         auto modName = mod->name(); | ||||||
|  |         QJsonObject line; | ||||||
|  |         line["name"] = modName; | ||||||
|  |         if (extraData & Url) { | ||||||
|  |             auto url = mod->metaurl(); | ||||||
|  |             if (!url.isEmpty()) | ||||||
|  |                 line["url"] = url; | ||||||
|  |         } | ||||||
|  |         if (extraData & Version) { | ||||||
|  |             auto ver = mod->version(); | ||||||
|  |             if (ver.isEmpty() && meta != nullptr) | ||||||
|  |                 ver = meta->version().toString(); | ||||||
|  |             if (!ver.isEmpty()) | ||||||
|  |                 line["version"] = ver; | ||||||
|  |         } | ||||||
|  |         if (extraData & Authors && !mod->authors().isEmpty()) | ||||||
|  |             line["authors"] = QJsonArray::fromStringList(mod->authors()); | ||||||
|  |         lines << line; | ||||||
|  |     } | ||||||
|  |     QJsonDocument doc; | ||||||
|  |     doc.setArray(lines); | ||||||
|  |     return doc.toJson(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QString toCSV(QList<Mod*> mods, OptionalData extraData) | ||||||
|  | { | ||||||
|  |     QStringList lines; | ||||||
|  |     for (auto mod : mods) { | ||||||
|  |         QStringList data; | ||||||
|  |         auto meta = mod->metadata(); | ||||||
|  |         auto modName = mod->name(); | ||||||
|  |  | ||||||
|  |         data << modName; | ||||||
|  |         if (extraData & Url) | ||||||
|  |             data << mod->metaurl(); | ||||||
|  |         if (extraData & Version) { | ||||||
|  |             auto ver = mod->version(); | ||||||
|  |             if (ver.isEmpty() && meta != nullptr) | ||||||
|  |                 ver = meta->version().toString(); | ||||||
|  |             data << ver; | ||||||
|  |         } | ||||||
|  |         if (extraData & Authors) { | ||||||
|  |             QString authors; | ||||||
|  |             if (mod->authors().length() == 1) | ||||||
|  |                 authors = mod->authors().back(); | ||||||
|  |             else if (mod->authors().length() > 1) | ||||||
|  |                 authors = QString("\"%1\"").arg(mod->authors().join(",")); | ||||||
|  |             data << authors; | ||||||
|  |         } | ||||||
|  |         lines << data.join(","); | ||||||
|  |     } | ||||||
|  |     return lines.join("\n"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData) | ||||||
|  | { | ||||||
|  |     switch (format) { | ||||||
|  |         case HTML: | ||||||
|  |             return toHTML(mods, extraData); | ||||||
|  |         case MARKDOWN: | ||||||
|  |             return toMarkdown(mods, extraData); | ||||||
|  |         case PLAINTXT: | ||||||
|  |             return toPlainTXT(mods, extraData); | ||||||
|  |         case JSON: | ||||||
|  |             return toJSON(mods, extraData); | ||||||
|  |         case CSV: | ||||||
|  |             return toCSV(mods, extraData); | ||||||
|  |         default: { | ||||||
|  |             return QString("unknown format:%1").arg(format); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QString exportToModList(QList<Mod*> mods, QString lineTemplate) | ||||||
|  | { | ||||||
|  |     QStringList lines; | ||||||
|  |     for (auto mod : mods) { | ||||||
|  |         auto meta = mod->metadata(); | ||||||
|  |         auto modName = mod->name(); | ||||||
|  |         auto url = mod->metaurl(); | ||||||
|  |         auto ver = mod->version(); | ||||||
|  |         if (ver.isEmpty() && meta != nullptr) | ||||||
|  |             ver = meta->version().toString(); | ||||||
|  |         auto authors = mod->authors().join(", "); | ||||||
|  |         lines << QString(lineTemplate) | ||||||
|  |                      .replace("{name}", modName) | ||||||
|  |                      .replace("{url}", url) | ||||||
|  |                      .replace("{version}", ver) | ||||||
|  |                      .replace("{authors}", authors); | ||||||
|  |     } | ||||||
|  |     return lines.join("\n"); | ||||||
|  | } | ||||||
|  | }  // namespace ExportToModList | ||||||
							
								
								
									
										33
									
								
								launcher/modplatform/helpers/ExportToModList.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								launcher/modplatform/helpers/ExportToModList.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | // 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 <QList> | ||||||
|  | #include <QString> | ||||||
|  | #include "minecraft/mod/Mod.h" | ||||||
|  |  | ||||||
|  | namespace ExportToModList { | ||||||
|  |  | ||||||
|  | enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM }; | ||||||
|  | enum OptionalData { | ||||||
|  |     Authors = 1 << 0, | ||||||
|  |     Url = 1 << 1, | ||||||
|  |     Version = 1 << 2, | ||||||
|  | }; | ||||||
|  | QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData); | ||||||
|  | QString exportToModList(QList<Mod*> mods, QString lineTemplate); | ||||||
|  | }  // namespace ExportToModList | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -158,6 +158,7 @@ private slots: | |||||||
|     void on_actionExportInstanceZip_triggered(); |     void on_actionExportInstanceZip_triggered(); | ||||||
|     void on_actionExportInstanceMrPack_triggered(); |     void on_actionExportInstanceMrPack_triggered(); | ||||||
|     void on_actionExportInstanceFlamePack_triggered(); |     void on_actionExportInstanceFlamePack_triggered(); | ||||||
|  |     void on_actionExportInstanceToModList_triggered(); | ||||||
|  |  | ||||||
|     void on_actionRenameInstance_triggered(); |     void on_actionRenameInstance_triggered(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -487,6 +487,14 @@ | |||||||
|     <string>CurseForge (zip)</string>   |     <string>CurseForge (zip)</string>   | ||||||
|    </property> |    </property> | ||||||
|   </action> |   </action> | ||||||
|  |   <action name="actionExportInstanceToModList"> | ||||||
|  |    <property name="icon"> | ||||||
|  |     <iconset theme="new"/> | ||||||
|  |    </property> | ||||||
|  |    <property name="text"> | ||||||
|  |     <string>Mod List</string> | ||||||
|  |    </property> | ||||||
|  |   </action> | ||||||
|   <action name="actionCreateInstanceShortcut"> |   <action name="actionCreateInstanceShortcut"> | ||||||
|    <property name="icon"> |    <property name="icon"> | ||||||
|     <iconset theme="shortcut"> |     <iconset theme="shortcut"> | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|  *  Prism Launcher - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> |  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -41,6 +42,9 @@ | |||||||
| #include <QFileSystemModel> | #include <QFileSystemModel> | ||||||
| #include <QMessageBox> | #include <QMessageBox> | ||||||
| #include "FileIgnoreProxy.h" | #include "FileIgnoreProxy.h" | ||||||
|  | #include "QObjectPtr.h" | ||||||
|  | #include "ui/dialogs/CustomMessageBox.h" | ||||||
|  | #include "ui/dialogs/ProgressDialog.h" | ||||||
| #include "ui_ExportInstanceDialog.h" | #include "ui_ExportInstanceDialog.h" | ||||||
|  |  | ||||||
| #include <FileSystem.h> | #include <FileSystem.h> | ||||||
| @@ -72,7 +76,7 @@ ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget* parent | |||||||
|     ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root))); |     ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root))); | ||||||
|     ui->treeView->sortByColumn(0, Qt::AscendingOrder); |     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->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); | ||||||
|     model->setRootPath(root); |     model->setRootPath(root); | ||||||
| @@ -92,32 +96,26 @@ void SaveIcon(InstancePtr m_instance) | |||||||
|     auto iconKey = m_instance->iconKey(); |     auto iconKey = m_instance->iconKey(); | ||||||
|     auto iconList = APPLICATION->icons(); |     auto iconList = APPLICATION->icons(); | ||||||
|     auto mmcIcon = iconList->icon(iconKey); |     auto mmcIcon = iconList->icon(iconKey); | ||||||
|     if(!mmcIcon || mmcIcon->isBuiltIn()) { |     if (!mmcIcon || mmcIcon->isBuiltIn()) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     auto path = mmcIcon->getFilePath(); |     auto path = mmcIcon->getFilePath(); | ||||||
|     if(!path.isNull()) { |     if (!path.isNull()) { | ||||||
|         QFileInfo inInfo (path); |         QFileInfo inInfo(path); | ||||||
|         FS::copy(path, FS::PathCombine(m_instance->instanceRoot(), inInfo.fileName())) (); |         FS::copy(path, FS::PathCombine(m_instance->instanceRoot(), inInfo.fileName()))(); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     auto & image = mmcIcon->m_images[mmcIcon->type()]; |     auto& image = mmcIcon->m_images[mmcIcon->type()]; | ||||||
|     auto & icon = image.icon; |     auto& icon = image.icon; | ||||||
|     auto sizes = icon.availableSizes(); |     auto sizes = icon.availableSizes(); | ||||||
|     if(sizes.size() == 0) |     if (sizes.size() == 0) { | ||||||
|     { |  | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     auto areaOf = [](QSize size) |     auto areaOf = [](QSize size) { return size.width() * size.height(); }; | ||||||
|     { |  | ||||||
|         return size.width() * size.height(); |  | ||||||
|     }; |  | ||||||
|     QSize largest = sizes[0]; |     QSize largest = sizes[0]; | ||||||
|     // find variant with largest area |     // find variant with largest area | ||||||
|     for(auto size: sizes) |     for (auto size : sizes) { | ||||||
|     { |         if (areaOf(largest) < areaOf(size)) { | ||||||
|         if(areaOf(largest) < areaOf(size)) |  | ||||||
|         { |  | ||||||
|             largest = size; |             largest = size; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -125,16 +123,15 @@ void SaveIcon(InstancePtr m_instance) | |||||||
|     pixmap.save(FS::PathCombine(m_instance->instanceRoot(), iconKey + ".png")); |     pixmap.save(FS::PathCombine(m_instance->instanceRoot(), iconKey + ".png")); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool ExportInstanceDialog::doExport() | void ExportInstanceDialog::doExport() | ||||||
| { | { | ||||||
|     auto name = FS::RemoveInvalidFilenameChars(m_instance->name()); |     auto name = FS::RemoveInvalidFilenameChars(m_instance->name()); | ||||||
|  |  | ||||||
|     const QString output = QFileDialog::getSaveFileName( |     const QString output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(m_instance->name()), | ||||||
|         this, tr("Export %1").arg(m_instance->name()), |                                                         FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr); | ||||||
|         FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr); |     if (output.isEmpty()) { | ||||||
|     if (output.isEmpty()) |         QDialog::done(QDialog::Rejected); | ||||||
|     { |         return; | ||||||
|         return false; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     SaveIcon(m_instance); |     SaveIcon(m_instance); | ||||||
| @@ -143,46 +140,40 @@ bool ExportInstanceDialog::doExport() | |||||||
|     if (!MMCZip::collectFileListRecursively(m_instance->instanceRoot(), nullptr, &files, |     if (!MMCZip::collectFileListRecursively(m_instance->instanceRoot(), nullptr, &files, | ||||||
|                                             std::bind(&FileIgnoreProxy::filterFile, proxyModel, std::placeholders::_1))) { |                                             std::bind(&FileIgnoreProxy::filterFile, proxyModel, std::placeholders::_1))) { | ||||||
|         QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); |         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)) |     auto task = makeShared<MMCZip::ExportToZipTask>(output, m_instance->instanceRoot(), files, "", true); | ||||||
|     { |  | ||||||
|         QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); |     connect(task.get(), &Task::failed, this, | ||||||
|         return false; |             [this, output](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); | ||||||
|     } |     connect(task.get(), &Task::finished, this, [task] { task->deleteLater(); }); | ||||||
|     return true; |  | ||||||
|  |     ProgressDialog progress(this); | ||||||
|  |     progress.setSkipButton(true, tr("Abort")); | ||||||
|  |     auto result = progress.execWithTask(task.get()); | ||||||
|  |     QDialog::done(result); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ExportInstanceDialog::done(int result) | void ExportInstanceDialog::done(int result) | ||||||
| { | { | ||||||
|     savePackIgnore(); |     savePackIgnore(); | ||||||
|     if (result == QDialog::Accepted) |     if (result == QDialog::Accepted) { | ||||||
|     { |         doExport(); | ||||||
|         if (doExport()) |         return; | ||||||
|         { |  | ||||||
|             QDialog::done(QDialog::Accepted); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|     QDialog::done(result); |     QDialog::done(result); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ExportInstanceDialog::rowsInserted(QModelIndex parent, int top, int bottom) | void ExportInstanceDialog::rowsInserted(QModelIndex parent, int top, int bottom) | ||||||
| { | { | ||||||
|     //WARNING: possible off-by-one? |     // WARNING: possible off-by-one? | ||||||
|     for(int i = top; i < bottom; i++) |     for (int i = top; i < bottom; i++) { | ||||||
|     { |  | ||||||
|         auto node = proxyModel->index(i, 0, parent); |         auto node = proxyModel->index(i, 0, parent); | ||||||
|         if(proxyModel->shouldExpand(node)) |         if (proxyModel->shouldExpand(node)) { | ||||||
|         { |  | ||||||
|             auto expNode = node.parent(); |             auto expNode = node.parent(); | ||||||
|             if(!expNode.isValid()) |             if (!expNode.isValid()) { | ||||||
|             { |  | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|             ui->treeView->expand(node); |             ui->treeView->expand(node); | ||||||
| @@ -199,8 +190,7 @@ void ExportInstanceDialog::loadPackIgnore() | |||||||
| { | { | ||||||
|     auto filename = ignoreFileName(); |     auto filename = ignoreFileName(); | ||||||
|     QFile ignoreFile(filename); |     QFile ignoreFile(filename); | ||||||
|     if(!ignoreFile.open(QIODevice::ReadOnly)) |     if (!ignoreFile.open(QIODevice::ReadOnly)) { | ||||||
|     { |  | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     auto data = ignoreFile.readAll(); |     auto data = ignoreFile.readAll(); | ||||||
| @@ -216,12 +206,9 @@ void ExportInstanceDialog::savePackIgnore() | |||||||
| { | { | ||||||
|     auto data = proxyModel->blockedPaths().toStringList().join('\n').toUtf8(); |     auto data = proxyModel->blockedPaths().toStringList().join('\n').toUtf8(); | ||||||
|     auto filename = ignoreFileName(); |     auto filename = ignoreFileName(); | ||||||
|     try |     try { | ||||||
|     { |  | ||||||
|         FS::write(filename, data); |         FS::write(filename, data); | ||||||
|     } |     } catch (const Exception& e) { | ||||||
|     catch (const Exception &e) |  | ||||||
|     { |  | ||||||
|         qWarning() << e.cause(); |         qWarning() << e.cause(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -38,39 +39,37 @@ | |||||||
| #include <QDialog> | #include <QDialog> | ||||||
| #include <QModelIndex> | #include <QModelIndex> | ||||||
| #include <memory> | #include <memory> | ||||||
| #include "FileIgnoreProxy.h" |  | ||||||
| #include "FastFileIconProvider.h" | #include "FastFileIconProvider.h" | ||||||
|  | #include "FileIgnoreProxy.h" | ||||||
|  |  | ||||||
| class BaseInstance; | class BaseInstance; | ||||||
| typedef std::shared_ptr<BaseInstance> InstancePtr; | typedef std::shared_ptr<BaseInstance> InstancePtr; | ||||||
|  |  | ||||||
| namespace Ui | namespace Ui { | ||||||
| { |  | ||||||
| class ExportInstanceDialog; | class ExportInstanceDialog; | ||||||
| } | } | ||||||
|  |  | ||||||
| class ExportInstanceDialog : public QDialog | class ExportInstanceDialog : public QDialog { | ||||||
| { |  | ||||||
|     Q_OBJECT |     Q_OBJECT | ||||||
|  |  | ||||||
| public: |    public: | ||||||
|     explicit ExportInstanceDialog(InstancePtr instance, QWidget *parent = 0); |     explicit ExportInstanceDialog(InstancePtr instance, QWidget* parent = 0); | ||||||
|     ~ExportInstanceDialog(); |     ~ExportInstanceDialog(); | ||||||
|  |  | ||||||
|     virtual void done(int result); |     virtual void done(int result); | ||||||
|  |  | ||||||
| private: |    private: | ||||||
|     bool doExport(); |     void doExport(); | ||||||
|     void loadPackIgnore(); |     void loadPackIgnore(); | ||||||
|     void savePackIgnore(); |     void savePackIgnore(); | ||||||
|     QString ignoreFileName(); |     QString ignoreFileName(); | ||||||
|  |  | ||||||
| private: |    private: | ||||||
|     Ui::ExportInstanceDialog *ui; |     Ui::ExportInstanceDialog* ui; | ||||||
|     InstancePtr m_instance; |     InstancePtr m_instance; | ||||||
|     FileIgnoreProxy * proxyModel; |     FileIgnoreProxy* proxyModel; | ||||||
|     FastFileIconProvider icons; |     FastFileIconProvider icons; | ||||||
|  |  | ||||||
| private slots: |    private slots: | ||||||
|     void rowsInserted(QModelIndex parent, int top, int bottom); |     void rowsInserted(QModelIndex parent, int top, int bottom); | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										223
									
								
								launcher/ui/dialogs/ExportToModListDialog.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								launcher/ui/dialogs/ExportToModListDialog.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,223 @@ | |||||||
|  | // 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 "ExportToModListDialog.h" | ||||||
|  | #include <QCheckBox> | ||||||
|  | #include <QComboBox> | ||||||
|  | #include <QTextEdit> | ||||||
|  | #include "FileSystem.h" | ||||||
|  | #include "Markdown.h" | ||||||
|  | #include "minecraft/MinecraftInstance.h" | ||||||
|  | #include "minecraft/mod/ModFolderModel.h" | ||||||
|  | #include "modplatform/helpers/ExportToModList.h" | ||||||
|  | #include "ui_ExportToModListDialog.h" | ||||||
|  |  | ||||||
|  | #include <QFileDialog> | ||||||
|  | #include <QFileSystemModel> | ||||||
|  | #include <QJsonDocument> | ||||||
|  | #include <QMessageBox> | ||||||
|  | #include <QPushButton> | ||||||
|  |  | ||||||
|  | const QHash<ExportToModList::Formats, QString> ExportToModListDialog::exampleLines = { | ||||||
|  |     { ExportToModList::HTML, "<li><a href=\"{url}\">{name}</a> [{version}] by {authors}</li>" }, | ||||||
|  |     { ExportToModList::MARKDOWN, "[{name}]({url}) [{version}] by {authors}" }, | ||||||
|  |     { ExportToModList::PLAINTXT, "{name} ({url}) [{version}] by {authors}" }, | ||||||
|  |     { ExportToModList::JSON, "{\"name\":\"{name}\",\"url\":\"{url}\",\"version\":\"{version}\",\"authors\":\"{authors}\"}," }, | ||||||
|  |     { ExportToModList::CSV, "{name},{url},{version},\"{authors}\"" }, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | ExportToModListDialog::ExportToModListDialog(InstancePtr instance, QWidget* parent) | ||||||
|  |     : QDialog(parent), m_template_changed(false), name(instance->name()), ui(new Ui::ExportToModListDialog) | ||||||
|  | { | ||||||
|  |     ui->setupUi(this); | ||||||
|  |     enableCustom(false); | ||||||
|  |  | ||||||
|  |     MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get()); | ||||||
|  |     if (mcInstance) { | ||||||
|  |         mcInstance->loaderModList()->update(); | ||||||
|  |         connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, [this, mcInstance]() { | ||||||
|  |             m_allMods = mcInstance->loaderModList()->allMods(); | ||||||
|  |             triggerImp(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     connect(ui->formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ExportToModListDialog::formatChanged); | ||||||
|  |     connect(ui->authorsCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); | ||||||
|  |     connect(ui->versionCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); | ||||||
|  |     connect(ui->urlCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); | ||||||
|  |     connect(ui->authorsButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Authors); }); | ||||||
|  |     connect(ui->versionButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Version); }); | ||||||
|  |     connect(ui->urlButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Url); }); | ||||||
|  |     connect(ui->templateText, &QTextEdit::textChanged, this, [this] { | ||||||
|  |         if (ui->templateText->toPlainText() != exampleLines[format]) | ||||||
|  |             ui->formatComboBox->setCurrentIndex(5); | ||||||
|  |         else | ||||||
|  |             triggerImp(); | ||||||
|  |     }); | ||||||
|  |     connect(ui->copyButton, &QPushButton::clicked, this, [this](bool) { | ||||||
|  |         this->ui->finalText->selectAll(); | ||||||
|  |         this->ui->finalText->copy(); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ExportToModListDialog::~ExportToModListDialog() | ||||||
|  | { | ||||||
|  |     delete ui; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ExportToModListDialog::formatChanged(int index) | ||||||
|  | { | ||||||
|  |     switch (index) { | ||||||
|  |         case 0: { | ||||||
|  |             enableCustom(false); | ||||||
|  |             ui->resultText->show(); | ||||||
|  |             format = ExportToModList::HTML; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         case 1: { | ||||||
|  |             enableCustom(false); | ||||||
|  |             ui->resultText->show(); | ||||||
|  |             format = ExportToModList::MARKDOWN; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         case 2: { | ||||||
|  |             enableCustom(false); | ||||||
|  |             ui->resultText->hide(); | ||||||
|  |             format = ExportToModList::PLAINTXT; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         case 3: { | ||||||
|  |             enableCustom(false); | ||||||
|  |             ui->resultText->hide(); | ||||||
|  |             format = ExportToModList::JSON; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         case 4: { | ||||||
|  |             enableCustom(false); | ||||||
|  |             ui->resultText->hide(); | ||||||
|  |             format = ExportToModList::CSV; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         case 5: { | ||||||
|  |             m_template_changed = true; | ||||||
|  |             enableCustom(true); | ||||||
|  |             ui->resultText->hide(); | ||||||
|  |             format = ExportToModList::CUSTOM; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     triggerImp(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ExportToModListDialog::triggerImp() | ||||||
|  | { | ||||||
|  |     if (format == ExportToModList::CUSTOM) { | ||||||
|  |         ui->finalText->setPlainText(ExportToModList::exportToModList(m_allMods, ui->templateText->toPlainText())); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     auto opt = 0; | ||||||
|  |     if (ui->authorsCheckBox->isChecked()) | ||||||
|  |         opt |= ExportToModList::Authors; | ||||||
|  |     if (ui->versionCheckBox->isChecked()) | ||||||
|  |         opt |= ExportToModList::Version; | ||||||
|  |     if (ui->urlCheckBox->isChecked()) | ||||||
|  |         opt |= ExportToModList::Url; | ||||||
|  |     auto txt = ExportToModList::exportToModList(m_allMods, format, static_cast<ExportToModList::OptionalData>(opt)); | ||||||
|  |     ui->finalText->setPlainText(txt); | ||||||
|  |     switch (format) { | ||||||
|  |         case ExportToModList::CUSTOM: | ||||||
|  |             return; | ||||||
|  |         case ExportToModList::HTML: | ||||||
|  |             ui->resultText->setHtml(txt); | ||||||
|  |             break; | ||||||
|  |         case ExportToModList::MARKDOWN: | ||||||
|  |             ui->resultText->setHtml(markdownToHTML(txt)); | ||||||
|  |             break; | ||||||
|  |         case ExportToModList::PLAINTXT: | ||||||
|  |             break; | ||||||
|  |         case ExportToModList::JSON: | ||||||
|  |             break; | ||||||
|  |         case ExportToModList::CSV: | ||||||
|  |             break; | ||||||
|  |     } | ||||||
|  |     auto exampleLine = exampleLines[format]; | ||||||
|  |     if (!m_template_changed && ui->templateText->toPlainText() != exampleLine) | ||||||
|  |         ui->templateText->setPlainText(exampleLine); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ExportToModListDialog::done(int result) | ||||||
|  | { | ||||||
|  |     if (result == Accepted) { | ||||||
|  |         const QString filename = FS::RemoveInvalidFilenameChars(name); | ||||||
|  |         const QString output = | ||||||
|  |             QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + extension()), | ||||||
|  |                                          "File (*.txt *.html *.md *.json *.csv)", nullptr); | ||||||
|  |  | ||||||
|  |         if (output.isEmpty()) | ||||||
|  |             return; | ||||||
|  |         FS::write(output, ui->finalText->toPlainText().toUtf8()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     QDialog::done(result); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QString ExportToModListDialog::extension() | ||||||
|  | { | ||||||
|  |     switch (format) { | ||||||
|  |         case ExportToModList::HTML: | ||||||
|  |             return ".html"; | ||||||
|  |         case ExportToModList::MARKDOWN: | ||||||
|  |             return ".md"; | ||||||
|  |         case ExportToModList::PLAINTXT: | ||||||
|  |             return ".txt"; | ||||||
|  |         case ExportToModList::CUSTOM: | ||||||
|  |             return ".txt"; | ||||||
|  |         case ExportToModList::JSON: | ||||||
|  |             return ".json"; | ||||||
|  |         case ExportToModList::CSV: | ||||||
|  |             return ".csv"; | ||||||
|  |     } | ||||||
|  |     return ".txt"; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ExportToModListDialog::addExtra(ExportToModList::OptionalData option) | ||||||
|  | { | ||||||
|  |     if (format != ExportToModList::CUSTOM) | ||||||
|  |         return; | ||||||
|  |     switch (option) { | ||||||
|  |         case ExportToModList::Authors: | ||||||
|  |             ui->templateText->insertPlainText("{authors}"); | ||||||
|  |             break; | ||||||
|  |         case ExportToModList::Url: | ||||||
|  |             ui->templateText->insertPlainText("{url}"); | ||||||
|  |             break; | ||||||
|  |         case ExportToModList::Version: | ||||||
|  |             ui->templateText->insertPlainText("{version}"); | ||||||
|  |             break; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | void ExportToModListDialog::enableCustom(bool enabled) | ||||||
|  | { | ||||||
|  |     ui->authorsCheckBox->setHidden(enabled); | ||||||
|  |     ui->versionCheckBox->setHidden(enabled); | ||||||
|  |     ui->urlCheckBox->setHidden(enabled); | ||||||
|  |  | ||||||
|  |     ui->authorsButton->setHidden(!enabled); | ||||||
|  |     ui->versionButton->setHidden(!enabled); | ||||||
|  |     ui->urlButton->setHidden(!enabled); | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								launcher/ui/dialogs/ExportToModListDialog.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								launcher/ui/dialogs/ExportToModListDialog.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | // 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 <QDialog> | ||||||
|  | #include <QList> | ||||||
|  | #include "BaseInstance.h" | ||||||
|  | #include "minecraft/mod/Mod.h" | ||||||
|  | #include "modplatform/helpers/ExportToModList.h" | ||||||
|  |  | ||||||
|  | namespace Ui { | ||||||
|  | class ExportToModListDialog; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ExportToModListDialog : public QDialog { | ||||||
|  |     Q_OBJECT | ||||||
|  |  | ||||||
|  |    public: | ||||||
|  |     explicit ExportToModListDialog(InstancePtr instance, QWidget* parent = nullptr); | ||||||
|  |     ~ExportToModListDialog(); | ||||||
|  |  | ||||||
|  |     void done(int result) override; | ||||||
|  |  | ||||||
|  |    protected slots: | ||||||
|  |     void formatChanged(int index); | ||||||
|  |     void triggerImp(); | ||||||
|  |     void trigger(int) { triggerImp(); }; | ||||||
|  |     void addExtra(ExportToModList::OptionalData option); | ||||||
|  |  | ||||||
|  |    private: | ||||||
|  |     QString extension(); | ||||||
|  |     void enableCustom(bool enabled); | ||||||
|  |     QList<Mod*> m_allMods; | ||||||
|  |     bool m_template_changed; | ||||||
|  |     QString name; | ||||||
|  |     ExportToModList::Formats format = ExportToModList::Formats::HTML; | ||||||
|  |     Ui::ExportToModListDialog* ui; | ||||||
|  |     static const QHash<ExportToModList::Formats, QString> exampleLines; | ||||||
|  | }; | ||||||
							
								
								
									
										240
									
								
								launcher/ui/dialogs/ExportToModListDialog.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								launcher/ui/dialogs/ExportToModListDialog.ui
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,240 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <ui version="4.0"> | ||||||
|  |  <class>ExportToModListDialog</class> | ||||||
|  |  <widget class="QDialog" name="ExportToModListDialog"> | ||||||
|  |   <property name="geometry"> | ||||||
|  |    <rect> | ||||||
|  |     <x>0</x> | ||||||
|  |     <y>0</y> | ||||||
|  |     <width>650</width> | ||||||
|  |     <height>446</height> | ||||||
|  |    </rect> | ||||||
|  |   </property> | ||||||
|  |   <property name="windowTitle"> | ||||||
|  |    <string>Export Pack to ModList</string> | ||||||
|  |   </property> | ||||||
|  |   <property name="sizeGripEnabled"> | ||||||
|  |    <bool>true</bool> | ||||||
|  |   </property> | ||||||
|  |   <layout class="QVBoxLayout" name="verticalLayout_2"> | ||||||
|  |    <item> | ||||||
|  |     <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0"> | ||||||
|  |      <item> | ||||||
|  |       <widget class="QGroupBox" name="groupBox_3"> | ||||||
|  |        <property name="title"> | ||||||
|  |         <string>Settings</string> | ||||||
|  |        </property> | ||||||
|  |        <layout class="QGridLayout" name="gridLayout"> | ||||||
|  |         <item row="0" column="1"> | ||||||
|  |          <widget class="QComboBox" name="formatComboBox"> | ||||||
|  |           <item> | ||||||
|  |            <property name="text"> | ||||||
|  |             <string>HTML</string> | ||||||
|  |            </property> | ||||||
|  |           </item> | ||||||
|  |           <item> | ||||||
|  |            <property name="text"> | ||||||
|  |             <string>Markdown</string> | ||||||
|  |            </property> | ||||||
|  |           </item> | ||||||
|  |           <item> | ||||||
|  |            <property name="text"> | ||||||
|  |             <string>Plaintext</string> | ||||||
|  |            </property> | ||||||
|  |           </item> | ||||||
|  |           <item> | ||||||
|  |            <property name="text"> | ||||||
|  |             <string>JSON</string> | ||||||
|  |            </property> | ||||||
|  |           </item> | ||||||
|  |           <item> | ||||||
|  |            <property name="text"> | ||||||
|  |             <string>CSV</string> | ||||||
|  |            </property> | ||||||
|  |           </item> | ||||||
|  |           <item> | ||||||
|  |            <property name="text"> | ||||||
|  |             <string>Custom</string> | ||||||
|  |            </property> | ||||||
|  |           </item> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |         <item row="1" column="0"> | ||||||
|  |          <widget class="QGroupBox" name="templateGroup"> | ||||||
|  |           <property name="title"> | ||||||
|  |            <string>Template</string> | ||||||
|  |           </property> | ||||||
|  |           <layout class="QVBoxLayout" name="verticalLayout_4"> | ||||||
|  |            <item> | ||||||
|  |             <widget class="QTextEdit" name="templateText"/> | ||||||
|  |            </item> | ||||||
|  |           </layout> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |         <item row="1" column="1"> | ||||||
|  |          <widget class="QGroupBox" name="optionsGroup"> | ||||||
|  |           <property name="title"> | ||||||
|  |            <string>Optional Info</string> | ||||||
|  |           </property> | ||||||
|  |           <layout class="QVBoxLayout" name="verticalLayout_5"> | ||||||
|  |            <item> | ||||||
|  |             <widget class="QCheckBox" name="versionCheckBox"> | ||||||
|  |              <property name="text"> | ||||||
|  |               <string>Version</string> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |            <item> | ||||||
|  |             <widget class="QCheckBox" name="authorsCheckBox"> | ||||||
|  |              <property name="text"> | ||||||
|  |               <string>Authors</string> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |            <item> | ||||||
|  |             <widget class="QCheckBox" name="urlCheckBox"> | ||||||
|  |              <property name="text"> | ||||||
|  |               <string>URL</string> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |            <item> | ||||||
|  |             <widget class="QPushButton" name="versionButton"> | ||||||
|  |              <property name="text"> | ||||||
|  |               <string>Version</string> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |            <item> | ||||||
|  |             <widget class="QPushButton" name="authorsButton"> | ||||||
|  |              <property name="text"> | ||||||
|  |               <string>Authors</string> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |            <item> | ||||||
|  |             <widget class="QPushButton" name="urlButton"> | ||||||
|  |              <property name="text"> | ||||||
|  |               <string>URL</string> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |           </layout> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |         <item row="0" column="0"> | ||||||
|  |          <widget class="QLabel" name="label"> | ||||||
|  |           <property name="frameShape"> | ||||||
|  |            <enum>QFrame::NoFrame</enum> | ||||||
|  |           </property> | ||||||
|  |           <property name="frameShadow"> | ||||||
|  |            <enum>QFrame::Plain</enum> | ||||||
|  |           </property> | ||||||
|  |           <property name="lineWidth"> | ||||||
|  |            <number>1</number> | ||||||
|  |           </property> | ||||||
|  |           <property name="text"> | ||||||
|  |            <string>Format</string> | ||||||
|  |           </property> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |        </layout> | ||||||
|  |       </widget> | ||||||
|  |      </item> | ||||||
|  |      <item> | ||||||
|  |       <widget class="QGroupBox" name="groupBox_4"> | ||||||
|  |        <property name="title"> | ||||||
|  |         <string>Result</string> | ||||||
|  |        </property> | ||||||
|  |        <layout class="QHBoxLayout" name="horizontalLayout"> | ||||||
|  |         <item> | ||||||
|  |          <widget class="QPlainTextEdit" name="finalText"> | ||||||
|  |           <property name="minimumSize"> | ||||||
|  |            <size> | ||||||
|  |             <width>0</width> | ||||||
|  |             <height>143</height> | ||||||
|  |            </size> | ||||||
|  |           </property> | ||||||
|  |           <property name="readOnly"> | ||||||
|  |            <bool>true</bool> | ||||||
|  |           </property> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |         <item> | ||||||
|  |          <widget class="QTextBrowser" name="resultText"> | ||||||
|  |           <property name="openExternalLinks"> | ||||||
|  |            <bool>true</bool> | ||||||
|  |           </property> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |        </layout> | ||||||
|  |       </widget> | ||||||
|  |      </item> | ||||||
|  |      <item> | ||||||
|  |       <widget class="QLabel" name="warningLabel"> | ||||||
|  |        <property name="text"> | ||||||
|  |         <string>This depends on the mods' metadata. To ensure it is available, run an update on the instance. Installing the updates isn't necessary.</string> | ||||||
|  |        </property> | ||||||
|  |        <property name="wordWrap"> | ||||||
|  |         <bool>true</bool> | ||||||
|  |        </property> | ||||||
|  |       </widget> | ||||||
|  |      </item> | ||||||
|  |     </layout> | ||||||
|  |    </item> | ||||||
|  |    <item> | ||||||
|  |     <layout class="QHBoxLayout" name="horizontalLayout_2"> | ||||||
|  |      <item> | ||||||
|  |       <widget class="QPushButton" name="copyButton"> | ||||||
|  |        <property name="text"> | ||||||
|  |         <string>Copy</string> | ||||||
|  |        </property> | ||||||
|  |       </widget> | ||||||
|  |      </item> | ||||||
|  |      <item> | ||||||
|  |       <widget class="QDialogButtonBox" name="buttonBox"> | ||||||
|  |        <property name="standardButtons"> | ||||||
|  |         <set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set> | ||||||
|  |        </property> | ||||||
|  |       </widget> | ||||||
|  |      </item> | ||||||
|  |     </layout> | ||||||
|  |    </item> | ||||||
|  |   </layout> | ||||||
|  |  </widget> | ||||||
|  |  <resources/> | ||||||
|  |  <connections> | ||||||
|  |   <connection> | ||||||
|  |    <sender>buttonBox</sender> | ||||||
|  |    <signal>accepted()</signal> | ||||||
|  |    <receiver>ExportToModListDialog</receiver> | ||||||
|  |    <slot>accept()</slot> | ||||||
|  |    <hints> | ||||||
|  |     <hint type="sourcelabel"> | ||||||
|  |      <x>334</x> | ||||||
|  |      <y>435</y> | ||||||
|  |     </hint> | ||||||
|  |     <hint type="destinationlabel"> | ||||||
|  |      <x>324</x> | ||||||
|  |      <y>206</y> | ||||||
|  |     </hint> | ||||||
|  |    </hints> | ||||||
|  |   </connection> | ||||||
|  |   <connection> | ||||||
|  |    <sender>buttonBox</sender> | ||||||
|  |    <signal>rejected()</signal> | ||||||
|  |    <receiver>ExportToModListDialog</receiver> | ||||||
|  |    <slot>reject()</slot> | ||||||
|  |    <hints> | ||||||
|  |     <hint type="sourcelabel"> | ||||||
|  |      <x>324</x> | ||||||
|  |      <y>390</y> | ||||||
|  |     </hint> | ||||||
|  |     <hint type="destinationlabel"> | ||||||
|  |      <x>324</x> | ||||||
|  |      <y>206</y> | ||||||
|  |     </hint> | ||||||
|  |    </hints> | ||||||
|  |   </connection> | ||||||
|  |  </connections> | ||||||
|  | </ui> | ||||||
| @@ -48,7 +48,6 @@ | |||||||
| #include <QAccessible> | #include <QAccessible> | ||||||
|  |  | ||||||
| #include "VisualGroup.h" | #include "VisualGroup.h" | ||||||
| #include "ui/themes/ThemeManager.h" |  | ||||||
| #include <QDebug> | #include <QDebug> | ||||||
|  |  | ||||||
| #include <Application.h> | #include <Application.h> | ||||||
| @@ -504,7 +503,7 @@ void InstanceView::setPaintCat(bool visible) | |||||||
| { | { | ||||||
|     m_catVisible = visible; |     m_catVisible = visible; | ||||||
|     if (visible) |     if (visible) | ||||||
|         m_catPixmap.load(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage())); |         m_catPixmap.load(APPLICATION->getCatPack()); | ||||||
|     else |     else | ||||||
|         m_catPixmap = QPixmap(); |         m_catPixmap = QPixmap(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| // SPDX-License-Identifier: GPL-3.0-only | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  PolyMC - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> |  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> | ||||||
|  |  *  Copyright (C) 2023 Tayou <git@tayou.org> | ||||||
|  * |  * | ||||||
|  *  This program is free software: you can redistribute it and/or modify |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -35,22 +36,18 @@ | |||||||
|  |  | ||||||
| #include "VisualGroup.h" | #include "VisualGroup.h" | ||||||
|  |  | ||||||
|  | #include <QApplication> | ||||||
|  | #include <QDebug> | ||||||
| #include <QModelIndex> | #include <QModelIndex> | ||||||
| #include <QPainter> | #include <QPainter> | ||||||
| #include <QtMath> | #include <QtMath> | ||||||
| #include <QApplication> | #include <utility> | ||||||
| #include <QDebug> |  | ||||||
|  |  | ||||||
| #include "InstanceView.h" | #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) | VisualGroup::VisualGroup(const VisualGroup* other) : view(other->view), text(other->text), collapsed(other->collapsed) {} | ||||||
|     : view(other->view), text(other->text), collapsed(other->collapsed) |  | ||||||
| { |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void VisualGroup::update() | void VisualGroup::update() | ||||||
| { | { | ||||||
| @@ -64,13 +61,11 @@ void VisualGroup::update() | |||||||
|     int positionInRow = 0; |     int positionInRow = 0; | ||||||
|     int currentRow = 0; |     int currentRow = 0; | ||||||
|     int offsetFromTop = 0; |     int offsetFromTop = 0; | ||||||
|     for (auto item: temp_items) |     for (auto item : temp_items) { | ||||||
|     { |         if (positionInRow == itemsPerRow) { | ||||||
|         if(positionInRow == itemsPerRow) |  | ||||||
|         { |  | ||||||
|             rows[currentRow].height = maxRowHeight; |             rows[currentRow].height = maxRowHeight; | ||||||
|             rows[currentRow].top = offsetFromTop; |             rows[currentRow].top = offsetFromTop; | ||||||
|             currentRow ++; |             currentRow++; | ||||||
|             offsetFromTop += maxRowHeight + 5; |             offsetFromTop += maxRowHeight + 5; | ||||||
|             positionInRow = 0; |             positionInRow = 0; | ||||||
|             maxRowHeight = 0; |             maxRowHeight = 0; | ||||||
| @@ -83,8 +78,7 @@ void VisualGroup::update() | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
|         auto itemHeight = view->itemDelegate()->sizeHint(viewItemOption, item).height(); |         auto itemHeight = view->itemDelegate()->sizeHint(viewItemOption, item).height(); | ||||||
|         if(itemHeight > maxRowHeight) |         if (itemHeight > maxRowHeight) { | ||||||
|         { |  | ||||||
|             maxRowHeight = itemHeight; |             maxRowHeight = itemHeight; | ||||||
|         } |         } | ||||||
|         rows[currentRow].items.append(item); |         rows[currentRow].items.append(item); | ||||||
| @@ -94,16 +88,13 @@ void VisualGroup::update() | |||||||
|     rows[currentRow].top = offsetFromTop; |     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; |     int y = 0; | ||||||
|     for (auto & row: rows) |     for (auto& row : rows) { | ||||||
|     { |         for (auto x = 0; x < row.items.size(); x++) { | ||||||
|         for(auto x = 0; x < row.items.size(); x++) |             if (row.items[x] == index) { | ||||||
|         { |                 return qMakePair(x, y); | ||||||
|             if(row.items[x] == index) |  | ||||||
|             { |  | ||||||
|                 return qMakePair(x,y); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         y++; |         y++; | ||||||
| @@ -112,193 +103,109 @@ QPair<int, int> VisualGroup::positionOf(const QModelIndex &index) const | |||||||
|     return qMakePair(0, 0); |     return qMakePair(0, 0); | ||||||
| } | } | ||||||
|  |  | ||||||
| int VisualGroup::rowTopOf(const QModelIndex &index) const | int VisualGroup::rowTopOf(const QModelIndex& index) const | ||||||
| { | { | ||||||
|     auto position = positionOf(index); |     auto position = positionOf(index); | ||||||
|     return rows[position.second].top; |     return rows[position.second].top; | ||||||
| } | } | ||||||
|  |  | ||||||
| int VisualGroup::rowHeightOf(const QModelIndex &index) const | int VisualGroup::rowHeightOf(const QModelIndex& index) const | ||||||
| { | { | ||||||
|     auto position = positionOf(index); |     auto position = positionOf(index); | ||||||
|     return rows[position.second].height; |     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; |     VisualGroup::HitResults results = VisualGroup::NoHit; | ||||||
|     int y_start = verticalPosition(); |     int y_start = verticalPosition(); | ||||||
|     int body_start = y_start + headerHeight(); |     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 y = pos.y(); | ||||||
|     // int x = pos.x(); |     // int x = pos.x(); | ||||||
|     if (y < y_start) |     if (y < y_start) { | ||||||
|     { |  | ||||||
|         results = VisualGroup::NoHit; |         results = VisualGroup::NoHit; | ||||||
|     } |     } else if (y < body_start) { | ||||||
|     else if (y < body_start) |  | ||||||
|     { |  | ||||||
|         results = VisualGroup::HeaderHit; |         results = VisualGroup::HeaderHit; | ||||||
|         int collapseSize = headerHeight() - 4; |         int collapseSize = headerHeight() - 4; | ||||||
|  |  | ||||||
|         // the icon |         // the icon | ||||||
|         QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize); |         QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, view->width() - 4, collapseSize); | ||||||
|         if (iconRect.contains(pos)) |         if (iconRect.contains(pos)) { | ||||||
|         { |  | ||||||
|             results |= VisualGroup::CheckboxHit; |             results |= VisualGroup::CheckboxHit; | ||||||
|         } |         } | ||||||
|     } |     } else if (y < body_end) { | ||||||
|     else if (y < body_end) |  | ||||||
|     { |  | ||||||
|         results |= VisualGroup::BodyHit; |         results |= VisualGroup::BodyHit; | ||||||
|     } |     } | ||||||
|     return results; |     return results; | ||||||
| } | } | ||||||
|  |  | ||||||
| void VisualGroup::drawHeader(QPainter *painter, const QStyleOptionViewItem &option) | void VisualGroup::drawHeader(QPainter* painter, const QStyleOptionViewItem& option) const | ||||||
| { | { | ||||||
|     painter->setRenderHint(QPainter::Antialiasing); |     QRect optRect = option.rect; | ||||||
|  |     optRect.setTop(optRect.top() + 7); | ||||||
|     const QRect optRect = option.rect; |  | ||||||
|     QFont font(QApplication::font()); |     QFont font(QApplication::font()); | ||||||
|     font.setBold(true); |     font.setBold(true); | ||||||
|     const QFontMetrics fontMetrics = QFontMetrics(font); |     const QFontMetrics fontMetrics = QFontMetrics(font); | ||||||
|  |     painter->setFont(font); | ||||||
|  |  | ||||||
|     QColor outlineColor = option.palette.text().color(); |     QPen pen; | ||||||
|     outlineColor.setAlphaF(0.35); |     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(); |         QPolygon arrowPolygon; | ||||||
|         painter->setPen(outlineColor); |         if (collapsed) { | ||||||
|         const QPointF topLeft(optRect.topLeft()); |             arrowPolygon << QPoint(arrowOffsetLeft - arrowSize / 2, centerHeight - arrowSize) | ||||||
|         QRectF arc(topLeft, QSizeF(4, 4)); |                          << QPoint(arrowOffsetLeft + arrowSize / 2, centerHeight) | ||||||
|         arc.translate(0.5, 0.5); |                          << QPoint(arrowOffsetLeft - arrowSize / 2, centerHeight + arrowSize); | ||||||
|         painter->drawArc(arc, 1440, 1440); |             painter->drawPolyline(arrowPolygon); | ||||||
|         painter->restore(); |         } else { | ||||||
|     } |             arrowPolygon << QPoint(arrowOffsetLeft - arrowSize, centerHeight - arrowSize / 2) | ||||||
|     //END: top left corner |                          << QPoint(arrowOffsetLeft, centerHeight + arrowSize / 2) | ||||||
|  |                          << QPoint(arrowOffsetLeft + arrowSize, centerHeight - arrowSize / 2); | ||||||
|     //BEGIN: left vertical line |             painter->drawPolyline(arrowPolygon); | ||||||
|     { |  | ||||||
|         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); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         painter->restore(); |  | ||||||
|     } |     } | ||||||
|     //END: checkboxy thing |     // END: arrow | ||||||
|  |  | ||||||
|     //BEGIN: text |     // BEGIN: text | ||||||
|     { |     { | ||||||
|         QRect textRect(option.rect); |         QRect textRect(optRect); | ||||||
|         textRect.setTop(textRect.top() + 7); |         textRect.setTop(textRect.top()); | ||||||
|         textRect.setLeft(textRect.left() + 7 + fontMetrics.height() + 7); |         textRect.setLeft(textOffsetLeft); | ||||||
|         textRect.setHeight(fontMetrics.height()); |         textRect.setHeight(fontMetrics.height()); | ||||||
|         textRect.setRight(textRect.right() - 7); |         textRect.setRight(textRect.right() - 7); | ||||||
|  |  | ||||||
|         painter->save(); |         painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, !text.isEmpty() ? text : QObject::tr("Ungrouped")); | ||||||
|         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(); |  | ||||||
|     } |     } | ||||||
|     //END: text |     // END: text | ||||||
| } | } | ||||||
|  |  | ||||||
| int VisualGroup::totalHeight() const | 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()); |     QFont font(QApplication::font()); | ||||||
|     font.setBold(true); |     font.setBold(true); | ||||||
|     QFontMetrics fontMetrics(font); |     QFontMetrics fontMetrics(font); | ||||||
|  |  | ||||||
|     const int height = fontMetrics.height() + 1 /* 1 pixel-width gradient */ |     const int height = fontMetrics.height() + 1 /* 1 pixel-width gradient */ | ||||||
|                                             + 11 /* top and bottom separation */; |                        + 11 /* top and bottom separation */; | ||||||
|     return height; |     return height; | ||||||
|     /* |     /* | ||||||
|     int raw = view->viewport()->fontMetrics().height() + 4; |     int raw = view->viewport()->fontMetrics().height() + 4; | ||||||
| @@ -311,8 +218,7 @@ int VisualGroup::headerHeight() const | |||||||
|  |  | ||||||
| int VisualGroup::contentHeight() const | int VisualGroup::contentHeight() const | ||||||
| { | { | ||||||
|     if (collapsed) |     if (collapsed) { | ||||||
|     { |  | ||||||
|         return 0; |         return 0; | ||||||
|     } |     } | ||||||
|     auto last = rows[numRows() - 1]; |     auto last = rows[numRows() - 1]; | ||||||
| @@ -321,7 +227,7 @@ int VisualGroup::contentHeight() const | |||||||
|  |  | ||||||
| int VisualGroup::numRows() const | int VisualGroup::numRows() const | ||||||
| { | { | ||||||
|     return rows.size(); |     return (int)rows.size(); | ||||||
| } | } | ||||||
|  |  | ||||||
| int VisualGroup::verticalPosition() const | int VisualGroup::verticalPosition() const | ||||||
| @@ -332,11 +238,9 @@ int VisualGroup::verticalPosition() const | |||||||
| QList<QModelIndex> VisualGroup::items() const | QList<QModelIndex> VisualGroup::items() const | ||||||
| { | { | ||||||
|     QList<QModelIndex> indices; |     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); |         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); |             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"); |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  * you may not use this file except in compliance with the License. |  *  it under the terms of the GNU General Public License as published by | ||||||
|  * You may obtain a copy of the License at |  *  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 |  *  You should have received a copy of the GNU General Public License | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  *  along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  * | ||||||
|  * See the License for the specific language governing permissions and |  * This file incorporates work covered by the following copyright and | ||||||
|  * limitations under the License. |  * 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 | #pragma once | ||||||
| @@ -42,8 +62,8 @@ struct VisualRow | |||||||
| struct VisualGroup | struct VisualGroup | ||||||
| { | { | ||||||
| /* constructors */ | /* constructors */ | ||||||
|     VisualGroup(const QString &text, InstanceView *view); |     VisualGroup(QString text, InstanceView *view); | ||||||
|     VisualGroup(const VisualGroup *other); |     explicit VisualGroup(const VisualGroup *other); | ||||||
|  |  | ||||||
| /* data */ | /* data */ | ||||||
|     InstanceView *view = nullptr; |     InstanceView *view = nullptr; | ||||||
| @@ -58,13 +78,13 @@ struct VisualGroup | |||||||
|     void update(); |     void update(); | ||||||
|  |  | ||||||
|     /// draw the header at y-position. |     /// 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. |     /// height of the group, in total. includes a small bit of padding. | ||||||
|     int totalHeight() const; |     int totalHeight() const; | ||||||
|  |  | ||||||
|     /// height of the group header, in pixels |     /// height of the group header, in pixels | ||||||
|     int headerHeight() const; |     static int headerHeight() ; | ||||||
|  |  | ||||||
|     /// height of the group content, in pixels |     /// height of the group content, in pixels | ||||||
|     int contentHeight() const; |     int contentHeight() const; | ||||||
|   | |||||||
| @@ -159,19 +159,6 @@ void AccountListPage::on_actionAddMojang_triggered() | |||||||
|  |  | ||||||
| void AccountListPage::on_actionAddMicrosoft_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( |     MinecraftAccountPtr account = MSALoginDialog::newAccount( | ||||||
|         this, |         this, | ||||||
|         tr("Please enter your Mojang account email and password to add your account.") |         tr("Please enter your Mojang account email and password to add your account.") | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
|  *  Prism Launcher - Minecraft Launcher |  *  Prism Launcher - Minecraft Launcher | ||||||
|  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> |  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> | ||||||
|  *  Copyright (c) 2022 dada513 <dada513@protonmail.com> |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| /* | /* | ||||||
|  *  PolyMC - Minecraft Launcher |  *  PolyMC - Minecraft Launcher | ||||||
|  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> |  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> | ||||||
|  |  *  Copyright (C) 2023 seth <getchoo at tuta dot io> | ||||||
|  * |  * | ||||||
|  *  This program is free software: you can redistribute it and/or modify |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -99,6 +100,9 @@ void MinecraftPage::applySettings() | |||||||
|     // Miscellaneous |     // Miscellaneous | ||||||
|     s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked()); |     s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked()); | ||||||
|     s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked()); |     s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked()); | ||||||
|  |  | ||||||
|  |     // Mod loader settings | ||||||
|  |     s->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked()); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MinecraftPage::loadSettings() | void MinecraftPage::loadSettings() | ||||||
| @@ -137,6 +141,8 @@ void MinecraftPage::loadSettings() | |||||||
|  |  | ||||||
|     ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool()); |     ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool()); | ||||||
|     ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool()); |     ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool()); | ||||||
|  |  | ||||||
|  |     ui->disableQuiltBeaconCheckBox->setChecked(s->get("DisableQuiltBeacon").toBool()); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MinecraftPage::retranslate() | void MinecraftPage::retranslate() | ||||||
|   | |||||||
| @@ -190,6 +190,25 @@ | |||||||
|        <string>Tweaks</string> |        <string>Tweaks</string> | ||||||
|       </attribute> |       </attribute> | ||||||
|       <layout class="QVBoxLayout" name="verticalLayout_12"> |       <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> |        <item> | ||||||
|         <widget class="QGroupBox" name="nativeLibWorkaroundGroupBox"> |         <widget class="QGroupBox" name="nativeLibWorkaroundGroupBox"> | ||||||
|          <property name="title"> |          <property name="title"> | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|  *  PolyMC - Minecraft Launcher |  *  PolyMC - Minecraft Launcher | ||||||
|  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> |  *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> | ||||||
|  *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -50,9 +51,9 @@ | |||||||
| #include "Application.h" | #include "Application.h" | ||||||
| #include "minecraft/auth/AccountList.h" | #include "minecraft/auth/AccountList.h" | ||||||
|  |  | ||||||
|  | #include "FileSystem.h" | ||||||
| #include "java/JavaInstallList.h" | #include "java/JavaInstallList.h" | ||||||
| #include "java/JavaUtils.h" | #include "java/JavaUtils.h" | ||||||
| #include "FileSystem.h" |  | ||||||
|  |  | ||||||
| InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent) | InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent) | ||||||
|     : QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst) |     : QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst) | ||||||
| @@ -280,6 +281,14 @@ void InstanceSettingsPage::applySettings() | |||||||
|         m_settings->reset("InstanceAccountId"); |         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 |     // FIXME: This should probably be called by a signal instead | ||||||
|     m_instance->updateRuntimeContext(); |     m_instance->updateRuntimeContext(); | ||||||
| } | } | ||||||
| @@ -380,6 +389,10 @@ void InstanceSettingsPage::loadSettings() | |||||||
|  |  | ||||||
|     ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool()); |     ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool()); | ||||||
|     updateAccountsMenu(); |     updateAccountsMenu(); | ||||||
|  |  | ||||||
|  |     // Mod loader specific settings | ||||||
|  |     ui->modLoaderSettingsGroupBox->setChecked(m_settings->get("OverrideModLoaderSettings").toBool()); | ||||||
|  |     ui->disableQuiltBeaconCheckBox->setChecked(m_settings->get("DisableQuiltBeacon").toBool()); | ||||||
| } | } | ||||||
|  |  | ||||||
| void InstanceSettingsPage::on_javaDetectBtn_clicked() | void InstanceSettingsPage::on_javaDetectBtn_clicked() | ||||||
|   | |||||||
| @@ -541,6 +541,31 @@ | |||||||
|        <string>Miscellaneous</string> |        <string>Miscellaneous</string> | ||||||
|       </attribute> |       </attribute> | ||||||
|       <layout class="QVBoxLayout" name="verticalLayout_9"> |       <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> |        <item> | ||||||
|         <widget class="QGroupBox" name="gameTimeGroupBox"> |         <widget class="QGroupBox" name="gameTimeGroupBox"> | ||||||
|          <property name="enabled"> |          <property name="enabled"> | ||||||
|   | |||||||
| @@ -104,6 +104,7 @@ void ResourcePage::openedImpl() | |||||||
|  |  | ||||||
|     updateSelectionButton(); |     updateSelectionButton(); | ||||||
|     triggerSearch(); |     triggerSearch(); | ||||||
|  |     m_ui->searchEdit->setFocus(); | ||||||
| } | } | ||||||
|  |  | ||||||
| auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool | auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| // SPDX-License-Identifier: GPL-3.0-only | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -61,7 +61,7 @@ void ThemeWizardPage::updateIcons() | |||||||
| void ThemeWizardPage::updateCat() | void ThemeWizardPage::updateCat() | ||||||
| { | { | ||||||
|     qDebug() << "Setting Cat"; |     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() | void ThemeWizardPage::retranslate() | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| // SPDX-License-Identifier: GPL-3.0-only | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
|   | |||||||
							
								
								
									
										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 | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| // SPDX-License-Identifier: GPL-3.0-only | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| // SPDX-License-Identifier: GPL-3.0-only | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| // SPDX-License-Identifier: GPL-3.0-only | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| // SPDX-License-Identifier: GPL-3.0-only | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| // SPDX-License-Identifier: GPL-3.0-only | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| // SPDX-License-Identifier: GPL-3.0-only | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -21,7 +21,10 @@ | |||||||
| #include <QDir> | #include <QDir> | ||||||
| #include <QDirIterator> | #include <QDirIterator> | ||||||
| #include <QIcon> | #include <QIcon> | ||||||
|  | #include <QImageReader> | ||||||
|  | #include "Exception.h" | ||||||
| #include "ui/themes/BrightTheme.h" | #include "ui/themes/BrightTheme.h" | ||||||
|  | #include "ui/themes/CatPack.h" | ||||||
| #include "ui/themes/CustomTheme.h" | #include "ui/themes/CustomTheme.h" | ||||||
| #include "ui/themes/DarkTheme.h" | #include "ui/themes/DarkTheme.h" | ||||||
| #include "ui/themes/SystemTheme.h" | #include "ui/themes/SystemTheme.h" | ||||||
| @@ -32,6 +35,7 @@ ThemeManager::ThemeManager(MainWindow* mainWindow) | |||||||
| { | { | ||||||
|     m_mainWindow = mainWindow; |     m_mainWindow = mainWindow; | ||||||
|     initializeThemes(); |     initializeThemes(); | ||||||
|  |     initializeCatPacks(); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// @brief Adds the Theme to the list of themes | /// @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 ThemeManager::addTheme(std::unique_ptr<ITheme> theme) | ||||||
| { | { | ||||||
|     QString id = theme->id(); |     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; |     return id; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -77,7 +84,7 @@ void ThemeManager::initializeThemes() | |||||||
|         QString themeFolder = QDir("./themes/").absoluteFilePath(""); |         QString themeFolder = QDir("./themes/").absoluteFilePath(""); | ||||||
|         themeDebugLog() << "Theme Folder Path: " << themeFolder; |         themeDebugLog() << "Theme Folder Path: " << themeFolder; | ||||||
|  |  | ||||||
|         QDirIterator directoryIterator(themeFolder, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); |         QDirIterator directoryIterator(themeFolder, QDir::Dirs | QDir::NoDotAndDotDot); | ||||||
|         while (directoryIterator.hasNext()) { |         while (directoryIterator.hasNext()) { | ||||||
|             QDir dir(directoryIterator.next()); |             QDir dir(directoryIterator.next()); | ||||||
|             QFileInfo themeJson(dir.absoluteFilePath("theme.json")); |             QFileInfo themeJson(dir.absoluteFilePath("theme.json")); | ||||||
| @@ -111,6 +118,16 @@ QList<ITheme*> ThemeManager::getValidApplicationThemes() | |||||||
|     return ret; |     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) | void ThemeManager::setIconTheme(const QString& name) | ||||||
| { | { | ||||||
|     QIcon::setThemeName(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(); |     auto catIter = m_catPacks.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString()); | ||||||
|     QDateTime birthday(QDate(now.date().year(), 11, 30), QTime(0, 0)); |     if (catIter != m_catPacks.end()) { | ||||||
|     QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0)); |         auto& catPack = catIter->second; | ||||||
|     QDateTime halloween(QDate(now.date().year(), 10, 31), QTime(0, 0)); |         themeDebugLog() << "applying catpack" << catPack->id(); | ||||||
|     QString cat = !catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString(); |         return catPack->path(); | ||||||
|     if (std::abs(now.daysTo(xmas)) <= 4) { |     } else { | ||||||
|         cat += "-xmas"; |         themeWarningLog() << "Tried to get invalid catPack:" << catName; | ||||||
|     } else if (std::abs(now.daysTo(halloween)) <= 4) { |     } | ||||||
|         cat += "-spooky"; |  | ||||||
|     } else if (std::abs(now.daysTo(birthday)) <= 12) { |     return m_catPacks.begin()->second->path(); | ||||||
|         cat += "-bday"; | } | ||||||
|  |  | ||||||
|  | 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 | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -20,6 +20,7 @@ | |||||||
| #include <QString> | #include <QString> | ||||||
|  |  | ||||||
| #include "ui/MainWindow.h" | #include "ui/MainWindow.h" | ||||||
|  | #include "ui/themes/CatPack.h" | ||||||
| #include "ui/themes/ITheme.h" | #include "ui/themes/ITheme.h" | ||||||
|  |  | ||||||
| inline auto themeDebugLog() | inline auto themeDebugLog() | ||||||
| @@ -40,18 +41,20 @@ class ThemeManager { | |||||||
|     void applyCurrentlySelectedTheme(bool initial = false); |     void applyCurrentlySelectedTheme(bool initial = false); | ||||||
|     void setApplicationTheme(const QString& name, bool initial = false); |     void setApplicationTheme(const QString& name, bool initial = false); | ||||||
|  |  | ||||||
|     /// <summary> |     /// @brief Returns the background based on selected and with events (Birthday, XMas, etc.) | ||||||
|     /// Returns the cat based on selected cat and with events (Birthday, XMas, etc.) |     /// @param catName Optional, if you need a specific background. | ||||||
|     /// </summary> |     /// @return | ||||||
|     /// <param name="catName">Optional, if you need a specific cat.</param> |     QString getCatPack(QString catName = ""); | ||||||
|     /// <returns></returns> |     QList<CatPack*> getValidCatPacks(); | ||||||
|     static QString getCatImage(QString catName = ""); |  | ||||||
|  |  | ||||||
|    private: |    private: | ||||||
|     std::map<QString, std::unique_ptr<ITheme>> m_themes; |     std::map<QString, std::unique_ptr<ITheme>> m_themes; | ||||||
|  |     std::map<QString, std::unique_ptr<CatPack>> m_catPacks; | ||||||
|     MainWindow* m_mainWindow; |     MainWindow* m_mainWindow; | ||||||
|  |  | ||||||
|     void initializeThemes(); |     void initializeThemes(); | ||||||
|  |     void initializeCatPacks(); | ||||||
|     QString addTheme(std::unique_ptr<ITheme> theme); |     QString addTheme(std::unique_ptr<ITheme> theme); | ||||||
|     ITheme* getTheme(QString themeId); |     ITheme* getTheme(QString themeId); | ||||||
|  |     QString addCatPack(std::unique_ptr<CatPack> catPack); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| // SPDX-License-Identifier: GPL-3.0-only | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -95,9 +95,14 @@ void ThemeCustomizationWidget::applyWidgetTheme(int index) { | |||||||
|     emit currentWidgetThemeChanged(index); |     emit currentWidgetThemeChanged(index); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ThemeCustomizationWidget::applyCatTheme(int index) { | void ThemeCustomizationWidget::applyCatTheme(int index) | ||||||
|  | { | ||||||
|     auto settings = APPLICATION->settings(); |     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); |     emit currentCatChanged(index); | ||||||
| } | } | ||||||
| @@ -135,10 +140,10 @@ void ThemeCustomizationWidget::loadSettings() | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     auto cat = settings->get("BackgroundCat").toString(); |     auto cat = settings->get("BackgroundCat").toString(); | ||||||
|     for (auto& catFromList : m_catOptions) { |     for (auto& catFromList : APPLICATION->getValidCatPacks()) { | ||||||
|         QIcon catIcon = QIcon(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage(catFromList.first))); |         QIcon catIcon = QIcon(QString("%1").arg(catFromList->path())); | ||||||
|         ui->backgroundCatComboBox->addItem(catIcon, catFromList.second); |         ui->backgroundCatComboBox->addItem(catIcon, catFromList->name(), catFromList->id()); | ||||||
|         if (cat == catFromList.first) { |         if (cat == catFromList->id()) { | ||||||
|             ui->backgroundCatComboBox->setCurrentIndex(ui->backgroundCatComboBox->count() - 1); |             ui->backgroundCatComboBox->setCurrentIndex(ui->backgroundCatComboBox->count() - 1); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| // SPDX-License-Identifier: GPL-3.0-only | // SPDX-License-Identifier: GPL-3.0-only | ||||||
| /* | /* | ||||||
|  *  Prism Launcher - Minecraft Launcher |  *  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 |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  *  it under the terms of the GNU General Public License as published by |  *  it under the terms of the GNU General Public License as published by | ||||||
| @@ -53,25 +53,17 @@ class ThemeCustomizationWidget : public QWidget { | |||||||
|    private: |    private: | ||||||
|     Ui::ThemeCustomizationWidget* ui; |     Ui::ThemeCustomizationWidget* ui; | ||||||
|  |  | ||||||
|     //TODO finish implementing |     // TODO finish implementing | ||||||
|     QList<std::pair<QString, QString>> m_iconThemeOptions{  |     QList<std::pair<QString, QString>> m_iconThemeOptions{ { "pe_colored", QObject::tr("Simple (Colored Icons)") }, | ||||||
|         { "pe_colored",     QObject::tr("Simple (Colored Icons)") },  |                                                            { "pe_light", QObject::tr("Simple (Light Icons)") }, | ||||||
|         { "pe_light",       QObject::tr("Simple (Light Icons)") },      |                                                            { "pe_dark", QObject::tr("Simple (Dark Icons)") }, | ||||||
|         { "pe_dark",        QObject::tr("Simple (Dark Icons)") }, |                                                            { "pe_blue", QObject::tr("Simple (Blue Icons)") }, | ||||||
|         { "pe_blue",        QObject::tr("Simple (Blue Icons)") },     |                                                            { "breeze_light", QObject::tr("Breeze Light") }, | ||||||
|         { "breeze_light",   QObject::tr("Breeze Light") },  |                                                            { "breeze_dark", QObject::tr("Breeze Dark") }, | ||||||
|         { "breeze_dark",    QObject::tr("Breeze Dark") }, |                                                            { "OSX", QObject::tr("OSX") }, | ||||||
|         { "OSX",            QObject::tr("OSX") },         |                                                            { "iOS", QObject::tr("iOS") }, | ||||||
|         { "iOS",            QObject::tr("iOS") },           |                                                            { "flat", QObject::tr("Flat") }, | ||||||
|         { "flat",           QObject::tr("Flat") }, |                                                            { "flat_white", QObject::tr("Flat (White)") }, | ||||||
|         { "flat_white",     QObject::tr("Flat (White)") },  |                                                            { "multimc", QObject::tr("Legacy") }, | ||||||
|         { "multimc",        QObject::tr("Legacy") },       |                                                            { "custom", QObject::tr("Custom") } }; | ||||||
|         { "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)") } |  | ||||||
|     }; |  | ||||||
| }; | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Trial97
					Trial97