Compare commits

..

181 commits

Author SHA1 Message Date
Alexandru Ionut Tripon
07010f226e
[Backport release-9.x] Recognize riscv64 as a 64-bit platform (#3428) 2025-02-17 20:35:08 +02:00
thonkdifferent
d75b23c092 Recognize riscv64 as a 64-bit platform
Currently PrismLauncher complains about the installed JDK being a 32-bit version, despite it being compiled for 64-bit RISC-V `riscv64`.

This PR fixes this issue.

Signed-off-by: thonkdifferent <41342923+thonkdifferent@users.noreply.github.com>
(cherry picked from commit 8f1750df51)
2025-02-17 18:33:29 +00:00
Alexandru Ionut Tripon
748b92571c
[Backport release-9.x] make sure if user changes java path also disable java management (#3426) 2025-02-17 20:32:54 +02:00
Trial97
97180324ee
make sure if user changes java path also disable java management
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-17 16:14:15 +02:00
Rachel Powers
ec7aaaadf3
[Backport release-9.x] add libMangoHud_shim.so to mangohub preloadlist (#3418) 2025-02-13 14:42:35 -07:00
Rachel Powers
7d786987ad
[Backport release-9.x] make sure if user changes java path also disable java management (#3417) 2025-02-13 14:42:26 -07:00
Trial97
1ab6ce42b5 add libMangoHud_shim.so to mangohub preloadlist
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 9e954548dd)
2025-02-13 21:41:14 +00:00
Trial97
05bf9c3689 make sure if user changes java path also disable java management
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 174cddcf42)
2025-02-13 21:40:54 +00:00
Alexandru Ionut Tripon
f760f08df6
[Backport release-9.x] Fix some undefined behaviour (#3408) 2025-02-11 21:11:17 +02:00
Alexandru Ionut Tripon
9456140155 Update launcher/ui/widgets/CheckComboBox.cpp
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
(cherry picked from commit 22b59e760c)
2025-02-11 19:10:21 +00:00
Alexandru Ionut Tripon
159771f283 Apply suggestions from code review
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
(cherry picked from commit 1d1a4f1b30)
2025-02-11 19:10:21 +00:00
Trial97
9616d898d4 Fix some undefined behaviour
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit bf1084d7fa)
2025-02-11 19:10:21 +00:00
Alexandru Ionut Tripon
e2526c80c8
Fixed when game crashes, it doesn't log any time played (#3392) (#3395) 2025-02-06 09:41:41 +02:00
Alexandru Ionut Tripon
a37cac6f9b
Fixed when game crashes, it doesn't log any time played (#3392)
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-06 09:41:19 +02:00
Alexandru Ionut Tripon
2f889429da
Merge pull request #3376 from PrismLauncher/backport-3373-to-release-9.x
[Backport release-9.x] Show warning when adding new resources with no instances
2025-01-31 23:04:21 +02:00
Trial97
c7831fd697 fix add resource with no instance
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit c5efe081b4)
2025-01-31 21:04:07 +00:00
Alexandru Ionut Tripon
b4c3123957
Merge pull request #3375 from PrismLauncher/backport-3260-to-release-9.x
[Backport release-9.x] Map Minecraft snapshots to Modrinth
2025-01-31 23:03:25 +02:00
Trial97
ae3e1a262e ensure that the snapshot mapping is on all apis
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 8e5a7c6e33)
2025-01-31 21:03:11 +00:00
Trial97
f8dc58665b map modrinth snapshots
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 1d8bf1d5a7)
2025-01-31 21:03:11 +00:00
Alexandru Ionut Tripon
19247bad14
Merge pull request #3367 from PrismLauncher/backport-3359-to-release-9.x
[Backport release-9.x] Publish on FlakeHub
2025-01-30 18:31:44 +02:00
Seth Flynn
4371933a84 feat: publish on flakehub
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 86cc6d3c5e)
2025-01-30 16:31:27 +00:00
Seth Flynn
dd90049b30 ci: use generic workflow for publishing
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 61d7f08883)
2025-01-30 16:31:27 +00:00
Alexandru Ionut Tripon
18170cc900
Merge pull request #3364 from PrismLauncher/backport-3314-to-release-9.x
[Backport release-9.x] update submodules
2025-01-29 23:41:13 +02:00
Trial97
71eced3e61 update submodules
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit dc00c47f2e)
2025-01-29 21:40:21 +00:00
Alexandru Ionut Tripon
cb49b27513
Merge pull request #3363 from PrismLauncher/backport-3362-to-release-9.x
[Backport release-9.x] ci: use flakehub cache
2025-01-29 23:34:39 +02:00
Seth Flynn
7b502fe8c9 ci: run nix workflow on pull_request_target
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 169f5728b1)
2025-01-29 21:34:15 +00:00
Seth Flynn
55578bf949 ci: use flakehub cache
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 8816be1668)
2025-01-29 21:34:15 +00:00
Alexandru Ionut Tripon
ee33bf50c2
Merge pull request #3361 from getchoo-contrib/backport-3360-to-release-9.x
[Backport release-9.x] Split Flatpak and Nix CI jobs from main build workflow
2025-01-29 23:12:18 +02:00
Seth Flynn
702db71f61
ci: separate flatpak job
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 49d734f314)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 16:06:27 -05:00
Seth Flynn
8e8efc304f
ci: separate nix job
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 417688089d)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 16:06:27 -05:00
Alexandru Ionut Tripon
2b204c8169
Merge pull request #3333 from PrismLauncher/backport-3330-to-release-9.x
[Backport release-9.x] Use better compile flags for MINGW
2025-01-29 22:52:29 +02:00
Alexandru Ionut Tripon
ed2eddc258
Merge pull request #3335 from PrismLauncher/backport-3294-to-release-9.x
[Backport release-9.x] Fix updating Curseforge mods with missing mod loader
2025-01-21 23:45:40 +02:00
Trial97
855e49bda0 fix curseforge with empty loader as newest version
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit e4ad4051c8)
2025-01-21 21:44:27 +00:00
Seth Flynn
bbde47842b feat: use better compile flags for mingw
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit e66f447ce5)
2025-01-21 13:18:57 +00:00
Alexandru Ionut Tripon
0508bbd05f
Merge pull request #3331 from getchoo-contrib/backport-ubuntu-22.04
[Backport release-9.x] Use Ubuntu 22.04 for Linux builds
2025-01-21 09:08:46 +02:00
Alexandru Ionut Tripon
808978080e
Merge pull request #3329 from getchoo-contrib/backport-qt-6.8
[Backport release-9.x] chore: update to qt 6.8.x
2025-01-21 09:07:58 +02:00
Alexandru Ionut Tripon
ea0d710aaf
Update .github/workflows/build.yml
Co-authored-by: Seth Flynn <getchoo@tuta.io>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
(cherry picked from commit db766574a4)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 23:47:00 -05:00
Alexandru Ionut Tripon
02e1988779
Update .github/workflows/build.yml
Co-authored-by: seth <getchoo@tuta.io>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
(cherry picked from commit 1b5d3c2bf9)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 23:46:59 -05:00
Trial97
fdab044c04
remove specific step for qt6
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit be3eca8c97)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 23:46:59 -05:00
Trial97
e77ac36c3f
fix appimage
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit bca517b8d3)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 23:46:59 -05:00
Edgars Cirulis
0d06fae7ae
chore: update Qt to 6.8.1
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
(cherry picked from commit eb8c375ec5)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 19:09:02 -05:00
Edgars Cirulis
a0c4cc59ac
ci: uprev qt install action to v4
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
(cherry picked from commit 0beaa94311)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 19:09:02 -05:00
Edgars Cirulis
bd7371dbf1
ci: fix qt-6.8 workflow
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
(cherry picked from commit b39098dbc5)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 19:09:02 -05:00
Edgars Cirulis
d7756d29dc
chore: update Qt to 6.8.0
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
(cherry picked from commit 5b6d551650)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 19:09:01 -05:00
Alexandru Ionut Tripon
0a7325ac12
Merge pull request #3324 from getchoo-contrib/backport-arm-stuff
[Backport release-9.x] Build Nix packages and Flatpaks for ARM
2025-01-20 09:21:29 +02:00
seth
ed2b5d4b0e
ci: build flatpaks for arm
Signed-off-by: seth <getchoo@tuta.io>
(cherry picked from commit 66f0397087)
Signed-off-by: seth <getchoo@tuta.io>
2025-01-19 19:47:44 -05:00
seth
9f199a470c
ci: build nix packages for aarch64-linux
Signed-off-by: seth <getchoo@tuta.io>
(cherry picked from commit 8e8538b506)
Signed-off-by: seth <getchoo@tuta.io>
2025-01-19 19:47:39 -05:00
Alexandru Ionut Tripon
1a98ef8bb7
Merge pull request #3309 from Trial97/bump9.3
bump to 9.3
2025-01-18 13:47:11 +02:00
Alexandru Ionut Tripon
d25d0d7ee6
Merge pull request #3312 from PrismLauncher/backport-1689-to-release-9.x
[Backport release-9.x] Added mouse interactions for labels in ProgressDialog
2025-01-17 21:10:03 +02:00
Trial97
beac093d62 Added mouse interactions for labels in ProgressDialog
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 08f5148a9a)
2025-01-17 19:00:06 +00:00
Trial97
81318d4953
bump to 9.3
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-17 09:17:49 +02:00
Alexandru Ionut Tripon
8c6602147b
Merge pull request #3308 from PrismLauncher/backport-3254-to-release-9.x
[Backport release-9.x] remove EditAccountDialog
2025-01-17 09:02:31 +02:00
Alexandru Ionut Tripon
9d8c3f251f
Merge pull request #3307 from PrismLauncher/backport-2737-to-release-9.x
[Backport release-9.x] Truncate logs for mclo.gs to fit 25k line limit
2025-01-17 09:02:01 +02:00
Alexandru Ionut Tripon
c787e65ed4
Merge pull request #3306 from PrismLauncher/backport-1555-to-release-9.x
[Backport release-9.x] Correctly expand environment variables for pre/post launch and wrapper commands
2025-01-17 09:01:21 +02:00
Alexandru Ionut Tripon
ae84a78470
Merge pull request #3303 from getchoo-contrib/backport-3179-to-release-9.x
[Backport release-9.x] Fix $INST_JAVA not being set for auto download java
2025-01-17 08:56:39 +02:00
Trial97
820b9e3fd0 remove EditAccountDialog
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit e01df73deb)
2025-01-17 06:56:28 +00:00
maskers
c8c3370d36 fix off by one error
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
(cherry picked from commit 6a12c43c78)
2025-01-17 06:50:00 +00:00
TheKodeToad
cffa9a2da0 Confirm Truncate -> Truncation
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 43fc9ba932)
2025-01-17 06:50:00 +00:00
maskers
01397896eb don't mention prism in the middle of the log
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
(cherry picked from commit 762d24bd02)
2025-01-17 06:50:00 +00:00
maskers
3048e43900 apply suggestion from code review
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: maskers <97827489+maskersss@users.noreply.github.com>
(cherry picked from commit a910337e9d)
2025-01-17 06:50:00 +00:00
maskers
417bb2fce6 fix formatting
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
(cherry picked from commit 6d017b5f0b)
2025-01-17 06:50:00 +00:00
maskers
e234c66818 add a Cancel option
Co-authored-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
Signed-off-by: maskers <97827489+maskersss@users.noreply.github.com>
(cherry picked from commit 65f8526152)
2025-01-17 06:50:00 +00:00
maskers
589ee73bfd Apply suggestions from code review
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: maskers <97827489+maskersss@users.noreply.github.com>
(cherry picked from commit 99bd4a8937)
2025-01-17 06:50:00 +00:00
maskers
0cb5ac7a06 add a warning about the log being too large
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
(cherry picked from commit cf914526bf)
2025-01-17 06:50:00 +00:00
maskers
db35f47105 fix formatting
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
(cherry picked from commit 858f6aa9b8)
2025-01-17 06:50:00 +00:00
maskers
cf8a82e78a fix formatting
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
(cherry picked from commit e6f30c0ebe)
2025-01-17 06:50:00 +00:00
maskers
15887c2d92 truncate logs for mclo.gs upload to fit 25k line limit
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
(cherry picked from commit 014fc14291)
2025-01-17 06:50:00 +00:00
Trial97
b7b5630588 expand env from wrapped cmd
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 09a118e85e)
2025-01-17 06:47:08 +00:00
Trial97
43834e2148 correctly expand env vars
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit c12beb43a0)
2025-01-17 06:47:08 +00:00
sshcrack
feb275a580
load meta first
Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com>
(cherry picked from commit cf2dcbd431)
Signed-off-by: seth <getchoo@tuta.io>
2025-01-16 21:46:13 -05:00
sshcrack
759db0c90f
change order of steps
Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com>
(cherry picked from commit dedb7a2343)
Signed-off-by: seth <getchoo@tuta.io>
2025-01-16 21:03:52 -05:00
seth
b2c75dde95
Merge pull request #3302 from PrismLauncher/backport-3292-to-release-9.x
[Backport release-9.x] Update Modrinth API authentication link
2025-01-16 20:55:38 -05:00
Trial97
62ed3f8406 fix modrinth link
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 3ee73916ca)
2025-01-17 01:42:14 +00:00
Alexandru Ionut Tripon
5e227ceaae
Merge pull request #3281 from PrismLauncher/backport-3255-to-release-9.x
[Backport release-9.x] propagate load component error
2025-01-13 08:18:00 +02:00
Trial97
a739db8bdb propagate load component error
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 6ebfcb91cf)
2025-01-08 22:22:36 +00:00
Alexandru Ionut Tripon
fdfdf2eef1
Merge pull request #3224 from Trial97/backport_fix_auto_mod_provider
[backport]Fix automatically choose mod provider option
2024-12-19 10:34:01 +02:00
Trial97
bbfa5a5ed1
Fix automatically choose mod provider option
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-19 09:23:51 +02:00
Alexandru Ionut Tripon
43dc330e55
Merge pull request #3223 from Trial97/backport_fix_flame_loader_match
[backport]fix the flame loaders match
2024-12-18 23:30:10 +02:00
Trial97
0c962dff59
fix the flame loaders match
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-18 22:53:18 +02:00
timoreo
ca0dd583ba
Merge pull request #3222 from PrismLauncher/backport-3197-to-release-9.x
[Backport release-9.x] Do not fail curseforge import if modrinth file check fails
2024-12-18 19:53:32 +01:00
Trial97
7bb686c85b Do not fail curseforge import if modrinth file check fails
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit a1c1c0b769)
2024-12-18 18:53:13 +00:00
timoreo
f8d3d8399d
Merge pull request #3221 from PrismLauncher/backport-3214-to-release-9.x
[Backport release-9.x] Correct symbolic link destination on manifest (Mojang) Java download
2024-12-18 19:42:58 +01:00
Kenneth Chew
9cb2a3ab2d Correct symbolic link destination on manifest Java download
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
(cherry picked from commit 8d53242952)
2024-12-18 18:42:39 +00:00
Alexandru Ionut Tripon
072c495ec6
Merge pull request #3206 from PrismLauncher/backport-3202-to-release-9.x
[Backport release-9.x] [macOS] Update Sparkle to v2.6.4
2024-12-14 18:39:04 +02:00
Kenneth Chew
335941fb46 Update Sparkle to v2.6.4
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
(cherry picked from commit b6cd46ad27)
2024-12-14 16:36:04 +00:00
Alexandru Ionut Tripon
ebeff36da5
Merge pull request #3205 from PrismLauncher/backport-3199-to-release-9.x
[Backport release-9.x] Fix tab order in launcher settings
2024-12-14 18:34:38 +02:00
Kenneth Chew
1d29f63bec Fix tab order in launcher settings
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
(cherry picked from commit c3712ba648)
2024-12-14 16:32:21 +00:00
Alexandru Ionut Tripon
87dc1bb6a6
Merge pull request #3204 from PrismLauncher/backport-3198-to-release-9.x
[Backport release-9.x] [macOS] Add local network usage description
2024-12-14 18:19:20 +02:00
Alexandru Ionut Tripon
a85720b8d6
Merge pull request #3203 from PrismLauncher/backport-3181-to-release-9.x
[Backport release-9.x] fix crash with invalid mrpack format
2024-12-14 18:19:09 +02:00
Kenneth Chew
4c28a94de1 Add local network usage description
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
(cherry picked from commit c3e44554ab)
2024-12-14 16:16:49 +00:00
Trial97
344ae87f25 fix crash with invalid mrpack format
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 1dd0728a58)
2024-12-14 16:16:06 +00:00
Alexandru Ionut Tripon
577999bd35
Merge pull request #3183 from PrismLauncher/backport-3170-to-release-9.x
[Backport release-9.x] [Linux] Don't use fallback icon search paths
2024-12-09 09:15:53 +02:00
leia uwu
887db29f35 fix icon theme search paths
using fallback search paths breaks qadwaitadecorations

Signed-off-by: leia uwu <leia@tutamail.com>
(cherry picked from commit 614574f15c)
2024-12-09 07:14:38 +00:00
Alexandru Ionut Tripon
7865b8128e
Merge pull request #3169 from PrismLauncher/backport-3166-to-release-9.x
[Backport release-9.x] chore(deps): update actions/cache action to v4.2.0
2024-12-06 08:47:51 +02:00
renovate[bot]
a58f125535 chore(deps): update actions/cache action to v4.2.0
(cherry picked from commit 94c893bd86)
2024-12-06 06:47:20 +00:00
Alexandru Ionut Tripon
d1a05d312d
Merge pull request #3158 from PrismLauncher/backport-3119-to-release-9.x
[Backport release-9.x] Sync Flatpak manifest with Flathub's
2024-12-02 17:30:57 +02:00
guihkx
bbf21e5824 ci(flatpak): update build artifact name
Just aligning the name of the Flatpak package with other build artifacts.

Signed-off-by: guihkx <626206+guihkx@users.noreply.github.com>
(cherry picked from commit e0faee7f26)
2024-12-02 15:30:33 +00:00
guihkx
73b4223b61 flatpak: update KDE runtime to 6.8
This also switches to the Docker image provided and maintained by
Flathub collaborators through the 'flathub-infra' organization on
GitHub, because it looks better maintained at the moment.

Signed-off-by: guihkx <626206+guihkx@users.noreply.github.com>
(cherry picked from commit f6770a847a)
2024-12-02 15:30:33 +00:00
guihkx
686e0b7b18 flatpak: update xrandr to 1.5.3
Signed-off-by: guihkx <626206+guihkx@users.noreply.github.com>
(cherry picked from commit 4a50e94967)
2024-12-02 15:30:33 +00:00
Alexandru Ionut Tripon
189878e7e3
Merge pull request #3157 from PrismLauncher/backport-3135-to-release-9.x
[Backport release-9.x] Make FTB Import note italic to match others
2024-12-02 16:25:06 +02:00
Trial97
af74d5f410 Make FTB Import note italic to match others
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit a09af619ce)
2024-12-02 14:17:44 +00:00
Tayou
cdfd0b1002
Merge pull request #3156 from PrismLauncher/backport-3152-to-release-9.x
[Backport release-9.x] Fix system icons on Linux
2024-12-02 15:02:21 +01:00
leia uwu
9791c306dc fix: icon search paths memory leak
Signed-off-by: leia uwu <leia@tutamail.com>
(cherry picked from commit 3f67ef968b)
2024-12-02 14:00:59 +00:00
leia uwu
e7e9265c40 fix: fix system icons
This sets the fallback icon theme to the current(system default) icon theme before
launcher specific themes are applied

And removes `Inherits` line of multimc/legacy icon theme because it can end up making it
inherit a default theme set from /usr/share/icons/default/index.theme
instead of the user configured theme (probably a qt bug?)

Signed-off-by: leia uwu <leia@tutamail.com>
(cherry picked from commit fd9c80db62)
2024-12-02 14:00:59 +00:00
Alexandru Ionut Tripon
e0c323a190
Merge pull request #3138 from PrismLauncher/backport-3130-to-release-9.x
[Backport release-9.x] Improve MANIFEST.MF parsing
2024-11-25 12:09:01 +02:00
Kationor
a914747416 Improve MANIFEST.MF parsing
Previously, we would only properly parse LF-encoded manifests, and even
those only if they used the recommended casing.

This commit allows the parser to recognise CR and CRLF newlines, and
also makes the name comparison case insensitive to align with the
specification. (Though not completely: we still don't support multiline
values)

Signed-off-by: Kationor <n96211028@gmail.com>
(cherry picked from commit b40a1973bf)
2024-11-25 10:05:19 +00:00
Alexandru Ionut Tripon
d453240a94
Merge pull request #3105 from Trial97/backport_3019
[Backport release-9.x] fixed double deletion for tasks
2024-11-15 10:35:25 +02:00
Trial97
4c4017d7ca remove task parent from constuctor
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-15 09:35:36 +02:00
Alexandru Ionut Tripon
2a4c4ed8ea
Merge pull request #3103 from PrismLauncher/backport-3024-to-release-9.x
[Backport release-9.x] fix leak on resource search
2024-11-15 08:55:46 +02:00
Alexandru Ionut Tripon
947fbf7d64
Merge pull request #3102 from PrismLauncher/backport-3070-to-release-9.x
[Backport release-9.x] Add scrollbar to Settings -> Launcher -> Features
2024-11-15 08:55:17 +02:00
Trial97
1c94a3e3e5 fix leak on resource search
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 8e7d84d05b)
2024-11-15 05:48:32 +00:00
TheKodeToad
eefeb5799d Add scrollbar to Settings -> Launcher -> Features
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 71e55f8829)
2024-11-15 05:40:31 +00:00
timoreo
6c6a4625ab
Merge pull request #3101 from PrismLauncher/backport-3096-to-release-9.x
[Backport release-9.x] Fix file remaingin open after program closure
2024-11-15 06:15:22 +01:00
timoreo
6d2a96f6a7
Merge pull request #3098 from Trial97/resource_backport
Fix crash caused by invalid resource pointer
2024-11-15 06:15:06 +01:00
Trial97
ea9029d7b6 Fix file remaingin open after program closure
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit ce61ed2f86)
2024-11-15 05:14:42 +00:00
Trial97
76602391f4 Fix crash caused by invalid resource pointer
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-14 16:26:21 +02:00
Alexandru Ionut Tripon
509f8c7307
Merge pull request #3079 from PrismLauncher/backport-3074-to-release-9.x
[Backport release-9.x] Close the window using the invokeMethod to not block
2024-11-07 23:48:40 +02:00
Trial97
984daa450b Close the window using the invokeMethod to not block
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 9601fbf2d5)
2024-11-07 21:48:30 +00:00
Tayou
04e1a97d59
Merge pull request #3077 from PrismLauncher/backport-3036-to-release-9.x
[Backport release-9.x] Fix installed typo
2024-11-07 17:31:15 +01:00
Trial97
1dce1360bd Fix installed typo
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 670c932f32)
2024-11-07 16:31:03 +00:00
timoreo
87d5b96760
Merge pull request #3061 from PrismLauncher/backport-3060-to-release-9.x
[Backport release-9.x] ci: bump linux qt version
2024-11-05 11:26:18 +01:00
Rachel Powers
7f074ca7b9 ci: libxcb-curcsor_dev ?
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
(cherry picked from commit ba6743d134)
2024-11-05 10:25:53 +00:00
Rachel Powers
c0394c52b7 ci: bump linux qt version
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
(cherry picked from commit d63a336411)
2024-11-05 10:25:53 +00:00
Alexandru Ionut Tripon
53bc20f13f
Merge pull request #3052 from PrismLauncher/backport-3042-to-release-9.x
[Backport release-9.x] use isPortable to determine if the MSAStep should check for url handler
2024-11-03 11:56:20 +02:00
Trial97
40af3cf3d2 use isPortable to determine if the MSAStep should check for url handler
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 99cfb62370)
2024-11-03 09:56:08 +00:00
Alexandru Ionut Tripon
3977918c5b
Merge pull request #3051 from PrismLauncher/backport-3045-to-release-9.x
[Backport release-9.x] disable retry for modrinth currentVersions API
2024-11-03 11:56:07 +02:00
Trial97
484d90899f disable retry for modrinth currentVersions API
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 6f2f3c2d3b)
2024-11-03 09:55:50 +00:00
Tayou
0f5eb03839
Merge pull request #3034 from PrismLauncher/backport-3010-to-release-9.x 2024-10-30 21:57:18 +01:00
Trial97
d21cda1880 lock m_instanceExtras
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 11d4d948aa)
2024-10-30 20:56:30 +00:00
Alexandru Ionut Tripon
52a321b93b
Merge pull request #3033 from PrismLauncher/backport-3030-to-release-9.x
[Backport release-9.x] Fix system detection for Intel Macs
2024-10-30 21:21:49 +02:00
Trial97
dd8dec8be8 fix macos system detection
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 14c95b9d37)
2024-10-30 19:16:53 +00:00
Tayou
23d6f8aaed
Merge pull request #3028 from PrismLauncher/backport-3027-to-release-9.x 2024-10-29 23:15:22 +01:00
Ben Westover
d105d76f2a Fix some typos
Signed-off-by: Ben Westover <me@benthetechguy.net>
(cherry picked from commit f6511c601e)
2024-10-29 22:06:15 +00:00
Ben Westover
698a838b56 Shorten metainfo.xml app summary
Signed-off-by: Ben Westover <me@benthetechguy.net>
(cherry picked from commit fd109c4740)
2024-10-29 22:06:15 +00:00
Alexandru Ionut Tripon
1129b4160c
Merge pull request #3022 from PrismLauncher/backport-2989-to-release-9.x
[Backport release-9.x] add extra protection against empty download link
2024-10-28 23:00:19 +02:00
Trial97
63a6f823fd add qassert
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 94f65f8727)
2024-10-28 20:59:25 +00:00
Trial97
db32b2ad99 remove message box
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 9cdda4377b)
2024-10-28 20:59:25 +00:00
Trial97
c3cf5d31da replace currentData with itemData on QComboBox::currentIndexChanged slots
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 0cafac84ac)
2024-10-28 20:59:25 +00:00
Trial97
1e696328bb fix #3001
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit b593ff09e9)
2024-10-28 20:59:25 +00:00
Trial97
1d9c97803a rename snake_case to camelCase
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 44894a29b1)
2024-10-28 20:59:25 +00:00
Trial97
3d55236fdf add extra protection against empty download link
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 8daa1219a5)
2024-10-28 20:59:25 +00:00
Alexandru Ionut Tripon
42190819ae
Merge pull request #3020 from PrismLauncher/backport-2837-to-release-9.x
[Backport release-9.x] translate standard buttons
2024-10-28 22:36:29 +02:00
Trial97
130714a9ab translate standard buttons
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit bd82737efb)
2024-10-28 20:34:51 +00:00
Tayou
bf57d77075
Merge pull request #3017 from PrismLauncher/backport-3002-to-release-9.x 2024-10-28 14:24:25 +01:00
Trial97
98fe035442 replace unzipping with unpacking
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 9eb1ce3ad7)
2024-10-28 13:23:42 +00:00
Trial97
8e4994590b fix unzipping typo
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 685d3c79ac)
2024-10-28 13:23:42 +00:00
Tayou
0722dba820
Merge pull request #3016 from PrismLauncher/backport-3009-to-release-9.x 2024-10-28 14:04:52 +01:00
Trial97
9560447c92 swap search with filter button for modpacks
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 61448a61ea)
2024-10-28 13:04:39 +00:00
Alexandru Ionut Tripon
4d89512ca1
Merge pull request #3013 from PrismLauncher/backport-3008-to-release-9.x
[Backport release-9.x] change contributors name
2024-10-27 22:30:02 +02:00
Trial97
42a6bd1151 change ZekeZ name
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit c4cd7cf6c1)
2024-10-27 19:31:56 +00:00
Trial97
f06505b138 change contributors name
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit f373a5cea0)
2024-10-27 19:31:56 +00:00
seth
d854087089
Merge pull request #2997 from PrismLauncher/backport-2992-to-release-9.x
[Backport release-9.x] Sync Flake with Nixpkgs
2024-10-25 21:20:31 -04:00
seth
31031ec923 chore(nix): use self for version
Signed-off-by: seth <getchoo@tuta.io>
(cherry picked from commit 63b10738b2)
2024-10-26 00:57:00 +00:00
seth
5f06517b62 chore(nix): sync with nixpkgs
Signed-off-by: seth <getchoo@tuta.io>
(cherry picked from commit a5c554cf6e)
2024-10-26 00:57:00 +00:00
Tayou
dde5fc47d3
Merge pull request #2987 from Trial97/bump_9.2 2024-10-25 09:53:49 +02:00
Alexandru Ionut Tripon
76d5f632dc
Merge pull request #2993 from PrismLauncher/backport-2982-to-release-9.x
[Backport release-9.x] Use Launcher log level in AutoInstallJava
2024-10-25 09:46:30 +03:00
TheKodeToad
27c6596bcb Use Launcher log level in AutoInstallJava
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit d8702e1357)
2024-10-25 06:42:32 +00:00
Alexandru Ionut Tripon
e2f5fb27a6
Merge pull request #2991 from PrismLauncher/backport-2963-to-release-9.x
[Backport release-9.x] chore(deps): update actions/cache action to v4.1.2
2024-10-25 09:33:52 +03:00
renovate[bot]
98819a0d02 chore(deps): update actions/cache action to v4.1.2
(cherry picked from commit 210d0d8aa2)
2024-10-25 06:30:00 +00:00
seth
2dde2c4bec
Merge pull request #2990 from PrismLauncher/backport-2980-to-release-9.x
[Backport release-9.x] Sync with Flathub manifest
2024-10-25 02:20:10 -04:00
Arcitec
008d69e5e5 fix: bring back Flathub manifest improvements to the repo
- Brings back all manifest improvements, such as the incredibly important Mojang Java Downloader (without it, the Flatpak doesn't work), and important fixes for Wayland.

Signed-off-by: Arcitec <38923130+Arcitec@users.noreply.github.com>
(cherry picked from commit 4ce025c0a2)
2024-10-25 06:19:25 +00:00
Trial97
a839258c07
bump develop to 9.2
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-25 00:19:06 +03:00
Alexandru Ionut Tripon
89041531e1
Merge pull request #2981 from PrismLauncher/backport-2975-to-release-9.x
[Backport release-9.x] fix: don't hang the ui for a full version load
2024-10-24 15:09:19 +03:00
Rachel Powers
729cec5f45 fix don't hang the ui for a full version load
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
(cherry picked from commit 7bd04ae928)
2024-10-24 10:24:53 +00:00
TheKodeToad
80d675d2e6
Merge pull request #2974 from PrismLauncher/backport-2972-to-release-9.x
[Backport release-9.x] Fix launching Minecraft in portable Linux
2024-10-23 22:39:58 +01:00
TheKodeToad
fb7d19941f Fix CleanEnviroment()'s usage of stripVariableEntries
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit e7cd8fe078)
2024-10-23 21:30:57 +00:00
Alexandru Ionut Tripon
877ab62c2f
Merge pull request #2964 from PrismLauncher/backport-2958-to-release-9.x
[Backport release-9.x] skip parsing open QSaveFile temprary files as resources
2024-10-22 17:41:45 +03:00
Trial97
b983ae0fb0 fix small leak
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 836aebc0d9)
2024-10-22 14:41:34 +00:00
Trial97
85422427b9 Replaced QSet with QHash
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 73d33f93b3)
2024-10-22 14:41:34 +00:00
Trial97
51a71d0471 skip QSaveFile temprary files
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 562c301326)
2024-10-22 14:41:34 +00:00
Alexandru Ionut Tripon
25eaa4eba6
Merge pull request #2959 from PrismLauncher/backport-2954-to-release-9.x
[Backport release-9.x] do not try to import skin if path is empty
2024-10-22 00:15:10 +03:00
Alexandru Ionut Tripon
6da14d66bb Update launcher/ui/dialogs/skins/SkinManageDialog.cpp
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
(cherry picked from commit 69028969f1)
2024-10-21 21:14:30 +00:00
Trial97
199c5497d3 do not try to import skin if path is empty
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 9a5b773e69)
2024-10-21 21:14:30 +00:00
Alexandru Ionut Tripon
740db2db02
Merge pull request #2956 from PrismLauncher/backport-2953-to-release-9.x
[Backport release-9.x] Fix removing portable.txt on Linux portable build
2024-10-21 22:46:49 +03:00
TheKodeToad
577f8074e1 Fix removing portable.txt on Linux portable build
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit f3f4c44620)
2024-10-21 19:45:10 +00:00
Alexandru Ionut Tripon
c5e7bb90c5
Merge pull request #2952 from PrismLauncher/backport-2947-to-release-9.x
[Backport release-9.x] do not require java if auto-download is enabled
2024-10-21 18:04:53 +03:00
Trial97
1cf91fa5d9 do not require java if auto-download is enabled
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit fcadbbb739)
2024-10-21 15:02:54 +00:00
Tayou
3bf4fbf8f4
Merge pull request #2951 from PrismLauncher/backport-2948-to-release-9.x 2024-10-21 16:55:28 +02:00
TheKodeToad
ff97affa72 Fix /norestart
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 78e24962f9)
2024-10-21 14:54:35 +00:00
Alexandru Ionut Tripon
e8c9722a53
Merge pull request #2939 from PrismLauncher/backport-2938-to-release-9.x
[Backport release-9.x] fix sparkle signature
2024-10-21 00:08:40 +03:00
Trial97
9a810dfa6e fix sparkle signature
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 0586d38e03)
2024-10-20 21:07:55 +00:00
363 changed files with 5436 additions and 5312 deletions

View file

@ -1,23 +1,5 @@
Checks:
- modernize-use-using
- readability-avoid-const-params-in-decls
- misc-unused-parameters,
- readability-identifier-naming
# ^ Without unused-parameters the readability-identifier-naming check doesn't cause any warnings.
CheckOptions:
- { key: readability-identifier-naming.ClassCase, value: PascalCase }
- { key: readability-identifier-naming.EnumCase, value: PascalCase }
- { key: readability-identifier-naming.FunctionCase, value: camelCase }
- { key: readability-identifier-naming.GlobalVariableCase, value: camelCase }
- { key: readability-identifier-naming.GlobalFunctionCase, value: camelCase }
- { key: readability-identifier-naming.GlobalConstantCase, value: SCREAMING_SNAKE_CASE }
- { key: readability-identifier-naming.MacroDefinitionCase, value: SCREAMING_SNAKE_CASE }
- { key: readability-identifier-naming.ClassMemberCase, value: camelCase }
- { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ }
- { key: readability-identifier-naming.ProtectedMemberPrefix, value: m_ }
- { key: readability-identifier-naming.PrivateStaticMemberPrefix, value: s_ }
- { key: readability-identifier-naming.ProtectedStaticMemberPrefix, value: s_ }
- { key: readability-identifier-naming.PublicStaticConstantCase, value: SCREAMING_SNAKE_CASE }
- { key: readability-identifier-naming.EnumConstantCase, value: SCREAMING_SNAKE_CASE }
SystemHeaders: false

View file

@ -164,13 +164,13 @@ jobs:
- name: Setup ccache
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
uses: hendrikmuhs/ccache-action@v1.2.17
uses: hendrikmuhs/ccache-action@v1.2.14
with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
- name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
uses: actions/cache@v4.2.1
uses: actions/cache@v4.2.0
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
@ -377,13 +377,11 @@ jobs:
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}'
ENTITLEMENTS_FILE='../program_info/App.entitlements'
else
APPLE_CODESIGN_ID='-'
ENTITLEMENTS_FILE='../program_info/AdhocSignedApp.entitlements'
fi
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "$ENTITLEMENTS_FILE" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
mv "PrismLauncher.app" "Prism Launcher.app"
- name: Notarize (macOS)

View file

@ -8,6 +8,8 @@ on:
- ".github/ISSUE_TEMPLATE/**"
- ".markdownlint**"
- "flatpak/**"
tags:
- "*"
pull_request_target:
paths-ignore:
- "**.md"
@ -78,13 +80,9 @@ jobs:
- name: Build debug package
if: ${{ env.DEBUG }}
run: |
nix build \
--no-link --print-build-logs --print-out-paths \
.#prismlauncher-debug >> "$GITHUB_STEP_SUMMARY"
nix build --print-build-logs .#prismlauncher-debug
- name: Build release package
if: ${{ !env.DEBUG }}
run: |
nix build \
--no-link --print-build-logs --print-out-paths \
.#prismlauncher >> "$GITHUB_STEP_SUMMARY"
nix build --print-build-logs .#prismlauncher

View file

@ -1,29 +0,0 @@
name: Stale
on:
schedule:
# run weekly on sunday
- cron: "0 0 * * 0"
workflow_dispatch:
jobs:
label:
name: Label issues and PRs
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
days-before-stale: 60
days-before-close: -1 # Don't close anything
exempt-issue-labels: rfc,nostale,help wanted
exempt-all-milestones: true
exempt-all-assignees: true
operations-per-run: 1000
stale-issue-label: inactive
stale-pr-label: inactive

View file

@ -187,8 +187,8 @@ set(Launcher_LOGIN_CALLBACK_URL "https://prismlauncher.org/successful-login" CAC
set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for FML Libraries.")
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 10)
set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_MAJOR 9)
set(Launcher_VERSION_MINOR 3)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0")
@ -306,8 +306,6 @@ include(QtVersionlessBackport)
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
set(QT_VERSION_MAJOR 5)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth)
find_package(Qt5 COMPONENTS DBus)
list(APPEND Launcher_QT_DBUS Qt5::DBus)
if(NOT Launcher_FORCE_BUNDLED_LIBS)
find_package(QuaZip-Qt5 1.3 QUIET)
@ -322,8 +320,6 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
set(QT_VERSION_MAJOR 6)
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth)
find_package(Qt6 COMPONENTS DBus)
list(APPEND Launcher_QT_DBUS Qt6::DBus)
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
if(NOT Launcher_FORCE_BUNDLED_LIBS)

View file

@ -2,59 +2,16 @@
## Code formatting
All files are formatted with `clang-format` using the configuration in `.clang-format`. Ensure it is run on changed files before committing!
Try to follow the existing formatting.
If there is no existing formatting, you may use `clang-format` with our included `.clang-format` configuration.
Please also follow the project's conventions for C++:
In general, in order of importance:
- Class and type names should be formatted as `PascalCase`: `MyClass`.
- Private or protected class data members should be formatted as `camelCase` prefixed with `m_`: `m_myCounter`.
- Private or protected `static` class data members should be formatted as `camelCase` prefixed with `s_`: `s_instance`.
- Public class data members should be formatted as `camelCase` without the prefix: `dateOfBirth`.
- Public, private or protected `static const` class data members should be formatted as `SCREAMING_SNAKE_CASE`: `MAX_VALUE`.
- Class function members should be formatted as `camelCase` without a prefix: `incrementCounter`.
- Global functions and non-`const` global variables should be formatted as `camelCase` without a prefix: `globalData`.
- `const` global variables, macros, and enum constants should be formatted as `SCREAMING_SNAKE_CASE`: `LIGHT_GRAY`.
- Avoid inventing acronyms or abbreviations especially for a name of multiple words - like `tp` for `texturePack`.
Most of these rules are included in the `.clang-tidy` file, so you can run `clang-tidy` to check for any violations.
Here is what these conventions with the formatting configuration look like:
```c++
#define AWESOMENESS 10
constexpr double PI = 3.14159;
enum class PizzaToppings { HAM_AND_PINEAPPLE, OREO_AND_KETCHUP };
struct Person {
QString name;
QDateTime dateOfBirth;
long daysOld() const { return dateOfBirth.daysTo(QDateTime::currentDateTime()); }
};
class ImportantClass {
public:
void incrementCounter()
{
if (m_counter + 1 > MAX_COUNTER_VALUE)
throw std::runtime_error("Counter has reached limit!");
++m_counter;
}
int counter() const { return m_counter; }
private:
static constexpr int MAX_COUNTER_VALUE = 100;
int m_counter;
};
ImportantClass importantClassInstance;
```
If you see any names which do not follow these conventions, it is preferred that you leave them be - renames increase the number of changes therefore make reviewing harder and make your PR more prone to conflicts. However, if you're refactoring a whole class anyway, it's fine.
- Make sure your IDE is not messing up line endings or whitespace and avoid using linters.
- Prefer readability over dogma.
- Keep to the existing formatting.
- Indent with 4 space unless it's in a submodule.
- Keep lists (of arguments, parameters, initializers...) as lists, not paragraphs. It should either read from top to bottom, or left to right. Not both.
## Signing your work

View file

@ -1,5 +1,3 @@
<p align="center"><b><i>This is a fork of PrismLauncher, unlocked to be able to use offline accounts without owning the game.</b></i></p>
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg">

18
flake.lock generated
View file

@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
@ -34,11 +34,11 @@
},
"nix-filter": {
"locked": {
"lastModified": 1731533336,
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
"lastModified": 1710156097,
"narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
"rev": "3342559a24e85fc164b295c3444e8a139924675b",
"type": "github"
},
"original": {
@ -49,11 +49,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1739446958,
"narHash": "sha256-+/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc=",
"lastModified": 1729256560,
"narHash": "sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c+cHUJwA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2ff53fe64443980e139eaa286017f53f88336dd0",
"rev": "4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0",
"type": "github"
},
"original": {

View file

@ -59,6 +59,8 @@
#include "ui/pages/BasePageProvider.h"
#include "ui/pages/global/APIPage.h"
#include "ui/pages/global/AccountListPage.h"
#include "ui/pages/global/CustomCommandsPage.h"
#include "ui/pages/global/EnvironmentVariablesPage.h"
#include "ui/pages/global/ExternalToolsPage.h"
#include "ui/pages/global/JavaPage.h"
#include "ui/pages/global/LanguagePage.h"
@ -241,7 +243,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
{ { "w", "world" }, "Join the specified world on launch (only valid in combination with --launch)", "world" },
{ { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
{ { "o", "offline" }, "Launch offline, with given player name (only valid in combination with --launch)", "offline" },
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
{ { "I", "import" }, "Import instance or resource from specified local path or URL", "url" },
{ "show", "Opens the window for the specified instance (by instance ID)", "show" } });
@ -257,10 +258,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_serverToJoin = parser.value("server");
m_worldToJoin = parser.value("world");
m_profileToUse = parser.value("profile");
if (parser.isSet("offline")) {
m_offline = true;
m_offlineName = parser.value("offline");
}
m_liveCheck = parser.isSet("alive");
m_instanceIdToShowWindowOf = parser.value("show");
@ -275,9 +272,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
// error if --launch is missing with --server or --profile
if ((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty() || !m_profileToUse.isEmpty() || m_offline) &&
m_instanceIdToLaunch.isEmpty()) {
std::cerr << "--server, --profile and --offline can only be used in combination with --launch!" << std::endl;
if (((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty()) || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) {
std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl;
m_status = Application::Failed;
return;
}
@ -402,10 +398,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
if (!m_profileToUse.isEmpty()) {
launch.args["profile"] = m_profileToUse;
}
if (m_offline) {
launch.args["offline_enabled"] = "true";
launch.args["offline_name"] = m_offlineName;
}
m_peerInstance->sendMessage(launch.serialize(), timeout);
}
m_status = Application::Succeeded;
@ -614,7 +606,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("IconsDir", "icons");
m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
m_settings->registerSetting("DownloadsDirWatchRecursive", false);
m_settings->registerSetting("MoveModsFromDownloadsDir", false);
m_settings->registerSetting("SkinsDir", "skins");
m_settings->registerSetting("JavaDir", "java");
@ -800,6 +791,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_globalSettingsProvider->addPage<MinecraftPage>();
m_globalSettingsProvider->addPage<JavaPage>();
m_globalSettingsProvider->addPage<LanguagePage>();
m_globalSettingsProvider->addPage<CustomCommandsPage>();
m_globalSettingsProvider->addPage<EnvironmentVariablesPage>();
m_globalSettingsProvider->addPage<ProxyPage>();
m_globalSettingsProvider->addPage<ExternalToolsPage>();
m_globalSettingsProvider->addPage<AccountListPage>();
@ -843,7 +836,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
":/icons/multimc/128x128/instances/", ":/icons/multimc/scalable/instances/" };
m_icons.reset(new IconList(instFolders, setting->get().toString()));
connect(setting.get(), &Setting::SettingChanged,
[this](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
[&](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
qDebug() << "<> Instance icons initialized.";
}
@ -1078,11 +1071,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
bool Application::createSetupWizard()
{
bool javaRequired = [this]() {
if (BuildConfig.JAVA_DOWNLOADER_ENABLED && settings()->get("AutomaticJavaDownload").toBool()) {
bool javaRequired = [&]() {
if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_settings->get("AutomaticJavaDownload").toBool()) {
return false;
}
bool ignoreJavaWizard = settings()->get("IgnoreJavaWizard").toBool();
bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool();
if (ignoreJavaWizard) {
return false;
}
@ -1096,8 +1089,8 @@ bool Application::createSetupWizard()
QString actualPath = FS::ResolveExecutable(currentJavaPath);
return actualPath.isNull();
}();
bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !settings()->get("AutomaticJavaDownload").toBool() &&
!settings()->get("AutomaticJavaSwitch").toBool() && !settings()->get("UserAskedAboutAutomaticJavaDownload").toBool();
bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() &&
!m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool();
bool languageRequired = settings()->get("Language").toString().isEmpty();
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
bool validWidgets = m_themeManager->isValidApplicationTheme(settings()->get("ApplicationTheme").toString());
@ -1217,7 +1210,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse;
}
launch(inst, !m_offline, false, targetToJoin, accountToUse, m_offlineName);
launch(inst, true, false, targetToJoin, accountToUse);
return;
}
}
@ -1316,8 +1309,6 @@ void Application::messageReceived(const QByteArray& message)
QString server = received.args["server"];
QString world = received.args["world"];
QString profile = received.args["profile"];
bool offline = received.args["offline_enabled"] == "true";
QString offlineName = received.args["offline_name"];
InstancePtr instance;
if (!id.isEmpty()) {
@ -1347,7 +1338,7 @@ void Application::messageReceived(const QByteArray& message)
}
}
launch(instance, !offline, false, serverObject, accountObject, offlineName);
launch(instance, true, false, serverObject, accountObject);
} else {
qWarning() << "Received invalid message" << message;
}
@ -1385,12 +1376,7 @@ bool Application::openJsonEditor(const QString& filename)
}
}
bool Application::launch(InstancePtr instance,
bool online,
bool demo,
MinecraftTarget::Ptr targetToJoin,
MinecraftAccountPtr accountToUse,
const QString& offlineName)
bool Application::launch(InstancePtr instance, bool online, bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse)
{
if (m_updateRunning) {
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
@ -1411,7 +1397,6 @@ bool Application::launch(InstancePtr instance,
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
controller->setTargetToJoin(targetToJoin);
controller->setAccountToUse(accountToUse);
controller->setOfflineName(offlineName);
if (window) {
controller->setParentWidget(window);
} else if (m_mainWindow) {

View file

@ -211,8 +211,7 @@ class Application : public QApplication {
bool online = true,
bool demo = false,
MinecraftTarget::Ptr targetToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr,
const QString& offlineName = QString());
MinecraftAccountPtr accountToUse = nullptr);
bool kill(InstancePtr instance);
void closeCurrentWindow();
@ -302,8 +301,6 @@ class Application : public QApplication {
QString m_serverToJoin;
QString m_worldToJoin;
QString m_profileToUse;
bool m_offline = false;
QString m_offlineName;
bool m_liveCheck = false;
QList<QUrl> m_urlsToImport;
QString m_instanceIdToShowWindowOf;

View file

@ -411,8 +411,3 @@ void BaseInstance::updateRuntimeContext()
{
// NOOP
}
bool BaseInstance::isLegacy()
{
return traits().contains("legacyLaunch") || traits().contains("alphaLaunch");
}

View file

@ -269,8 +269,6 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
bool removeLinkedInstanceId(const QString& id);
bool isLinkedToInstanceId(const QString& id) const;
bool isLegacy();
protected:
void changeStatus(Status newStatus);

View file

@ -345,12 +345,13 @@ set(MINECRAFT_SOURCES
minecraft/mod/TexturePackFolderModel.h
minecraft/mod/TexturePackFolderModel.cpp
minecraft/mod/ShaderPackFolderModel.h
minecraft/mod/tasks/ResourceFolderLoadTask.h
minecraft/mod/tasks/ResourceFolderLoadTask.cpp
minecraft/mod/tasks/BasicFolderLoadTask.h
minecraft/mod/tasks/ModFolderLoadTask.h
minecraft/mod/tasks/ModFolderLoadTask.cpp
minecraft/mod/tasks/LocalModParseTask.h
minecraft/mod/tasks/LocalModParseTask.cpp
minecraft/mod/tasks/LocalResourceUpdateTask.h
minecraft/mod/tasks/LocalResourceUpdateTask.cpp
minecraft/mod/tasks/LocalModUpdateTask.h
minecraft/mod/tasks/LocalModUpdateTask.cpp
minecraft/mod/tasks/LocalDataPackParseTask.h
minecraft/mod/tasks/LocalDataPackParseTask.cpp
minecraft/mod/tasks/LocalResourcePackParseTask.h
@ -908,6 +909,7 @@ SET(LAUNCHER_SOURCES
ui/pages/instance/NotesPage.h
ui/pages/instance/LogPage.cpp
ui/pages/instance/LogPage.h
ui/pages/instance/InstanceSettingsPage.cpp
ui/pages/instance/InstanceSettingsPage.h
ui/pages/instance/ScreenshotsPage.cpp
ui/pages/instance/ScreenshotsPage.h
@ -921,12 +923,17 @@ SET(LAUNCHER_SOURCES
# GUI - global settings pages
ui/pages/global/AccountListPage.cpp
ui/pages/global/AccountListPage.h
ui/pages/global/CustomCommandsPage.cpp
ui/pages/global/CustomCommandsPage.h
ui/pages/global/EnvironmentVariablesPage.cpp
ui/pages/global/EnvironmentVariablesPage.h
ui/pages/global/ExternalToolsPage.cpp
ui/pages/global/ExternalToolsPage.h
ui/pages/global/JavaPage.cpp
ui/pages/global/JavaPage.h
ui/pages/global/LanguagePage.cpp
ui/pages/global/LanguagePage.h
ui/pages/global/MinecraftPage.cpp
ui/pages/global/MinecraftPage.h
ui/pages/global/LauncherPage.cpp
ui/pages/global/LauncherPage.h
@ -1060,8 +1067,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/BlockedModsDialog.h
ui/dialogs/ChooseProviderDialog.h
ui/dialogs/ChooseProviderDialog.cpp
ui/dialogs/ResourceUpdateDialog.cpp
ui/dialogs/ResourceUpdateDialog.h
ui/dialogs/ModUpdateDialog.cpp
ui/dialogs/ModUpdateDialog.h
ui/dialogs/InstallLoaderDialog.cpp
ui/dialogs/InstallLoaderDialog.h
@ -1077,12 +1084,14 @@ SET(LAUNCHER_SOURCES
ui/widgets/CustomCommands.h
ui/widgets/EnvironmentVariables.cpp
ui/widgets/EnvironmentVariables.h
ui/widgets/DropLabel.cpp
ui/widgets/DropLabel.h
ui/widgets/FocusLineEdit.cpp
ui/widgets/FocusLineEdit.h
ui/widgets/IconLabel.cpp
ui/widgets/IconLabel.h
ui/widgets/JavaWizardWidget.cpp
ui/widgets/JavaWizardWidget.h
ui/widgets/JavaSettingsWidget.cpp
ui/widgets/JavaSettingsWidget.h
ui/widgets/LabeledToolButton.cpp
ui/widgets/LabeledToolButton.h
ui/widgets/LanguageSelectionWidget.cpp
@ -1118,10 +1127,6 @@ SET(LAUNCHER_SOURCES
ui/widgets/WideBar.cpp
ui/widgets/ThemeCustomizationWidget.h
ui/widgets/ThemeCustomizationWidget.cpp
ui/widgets/MinecraftSettingsWidget.h
ui/widgets/MinecraftSettingsWidget.cpp
ui/widgets/JavaSettingsWidget.h
ui/widgets/JavaSettingsWidget.cpp
# GUI - instance group view
ui/instanceview/InstanceProxyModel.cpp
@ -1165,6 +1170,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/global/LauncherPage.ui
ui/pages/global/APIPage.ui
ui/pages/global/ProxyPage.ui
ui/pages/global/MinecraftPage.ui
ui/pages/global/ExternalToolsPage.ui
ui/pages/instance/ExternalResourcesPage.ui
ui/pages/instance/NotesPage.ui
@ -1172,6 +1178,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/instance/ServersPage.ui
ui/pages/instance/GameOptionsPage.ui
ui/pages/instance/OtherLogsPage.ui
ui/pages/instance/InstanceSettingsPage.ui
ui/pages/instance/VersionPage.ui
ui/pages/instance/ManagedPackPage.ui
ui/pages/instance/WorldListPage.ui
@ -1194,8 +1201,6 @@ qt_wrap_ui(LAUNCHER_UI
ui/widgets/ModFilterWidget.ui
ui/widgets/SubTaskProgressBar.ui
ui/widgets/ThemeCustomizationWidget.ui
ui/widgets/MinecraftSettingsWidget.ui
ui/widgets/JavaSettingsWidget.ui
ui/dialogs/CopyInstanceDialog.ui
ui/dialogs/ProfileSetupDialog.ui
ui/dialogs/ProgressDialog.ui
@ -1215,6 +1220,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/ScrollMessageBox.ui
ui/dialogs/BlockedModsDialog.ui
ui/dialogs/ChooseProviderDialog.ui
ui/dialogs/skins/SkinManageDialog.ui
)
@ -1289,7 +1295,6 @@ target_link_libraries(Launcher_logic
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::NetworkAuth
${Launcher_QT_DBUS}
${Launcher_QT_LIBS}
)
target_link_libraries(Launcher_logic
@ -1298,10 +1303,6 @@ target_link_libraries(Launcher_logic
LocalPeer
Launcher_rainbow
)
if (TARGET ${Launcher_QT_DBUS})
add_compile_definitions(WITH_QTDBUS)
endif()
if(APPLE)
set(CMAKE_MACOSX_RPATH 1)
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/")

View file

@ -24,7 +24,7 @@ void DataMigrationTask::executeTask()
// 1. Scan
// Check how many files we gotta copy
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
return m_copy(true); // dry run to collect amount of files
});
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
@ -57,7 +57,7 @@ void DataMigrationTask::dryRunFinished()
setProgress(m_copy.totalCopied(), m_toCopy);
setStatus(tr("Copying %1…").arg(shortenedName));
});
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
return m_copy(false); // actually copy now
});
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);

View file

@ -341,7 +341,7 @@ bool copy::operator()(const QString& offset, bool dryRun)
opt |= copy_opts::overwrite_existing;
// Function that'll do the actual copying
auto copy_file = [this, dryRun, src, dst, opt, &err](QString src_path, QString relative_dst_path) {
auto copy_file = [&](QString src_path, QString relative_dst_path) {
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
return;
@ -428,7 +428,7 @@ void create_link::make_link_list(const QString& offset)
m_recursive = true;
// Function that'll do the actual linking
auto link_file = [this, dst](QString src_path, QString relative_dst_path) {
auto link_file = [&](QString src_path, QString relative_dst_path) {
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) {
qDebug() << "path" << relative_dst_path << "in black list or not in whitelist";
return;
@ -523,7 +523,7 @@ void create_link::runPrivileged(const QString& offset)
QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric();
connect(&m_linkServer, &QLocalServer::newConnection, this, [this, &gotResults]() {
connect(&m_linkServer, &QLocalServer::newConnection, this, [&]() {
qDebug() << "Client connected, sending out pairs";
// construct block of data to send
QByteArray block;
@ -605,7 +605,7 @@ void create_link::runPrivileged(const QString& offset)
}
ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this);
connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [this, gotResults]() { emit finishedPrivileged(gotResults); });
connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivileged(gotResults); });
connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater);
linkFileProcess->start();
@ -1295,7 +1295,7 @@ bool clone::operator()(const QString& offset, bool dryRun)
std::error_code err;
// Function that'll do the actual cloneing
auto cloneFile = [this, dryRun, dst, &err](QString src_path, QString relative_dst_path) {
auto cloneFile = [&](QString src_path, QString relative_dst_path) {
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
return;

View file

@ -91,7 +91,7 @@ void InstanceCopyTask::executeTask()
QEventLoop loop;
bool got_priv_results = false;
connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&got_priv_results, &loop](bool gotResults) {
connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults) {
if (!gotResults) {
qDebug() << "Privileged run exited without results!";
}

View file

@ -487,7 +487,7 @@ InstanceList::InstListError InstanceList::loadList()
int front_bookmark = -1;
int back_bookmark = -1;
int currentItem = -1;
auto removeNow = [this, &front_bookmark, &back_bookmark, &currentItem]() {
auto removeNow = [&]() {
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
endRemoveRows();

View file

@ -43,7 +43,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
values.append(new ServersPage(onesix));
// values.append(new GameOptionsPage(onesix.get()));
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
values.append(new InstanceSettingsPage(onesix));
values.append(new InstanceSettingsPage(onesix.get()));
auto logMatcher = inst->getLogFileMatcher();
if (logMatcher) {
values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher));

View file

@ -43,7 +43,6 @@
#include "ui/InstanceWindow.h"
#include "ui/MainWindow.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/MSALoginDialog.h"
#include "ui/dialogs/ProfileSelectDialog.h"
#include "ui/dialogs/ProfileSetupDialog.h"
#include "ui/dialogs/ProgressDialog.h"
@ -235,15 +234,10 @@ void LaunchController::login()
if (!m_session->wants_online) {
// we ask the user for a player name
bool ok = false;
QString name;
if (m_offlineName.isEmpty()) {
name = askOfflineName(m_session->player_name, m_session->demo, ok);
if (!ok) {
tryagain = false;
break;
}
} else {
name = m_offlineName;
auto name = askOfflineName(m_session->player_name, m_session->demo, ok);
if (!ok) {
tryagain = false;
break;
}
m_session->MakeOffline(name);
// offline flavored game from here :3
@ -293,8 +287,10 @@ void LaunchController::login()
continue;
}
case AccountState::Expired: {
if (reauthenticateCurrentAccount())
continue;
auto errorString = tr("The account has expired and needs to be logged into manually again.");
QMessageBox::warning(m_parentWidget, tr("Account refresh failed"), errorString, QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok);
emitFailed(errorString);
return;
}
case AccountState::Disabled: {
@ -318,33 +314,6 @@ void LaunchController::login()
emitFailed(tr("Failed to launch."));
}
bool LaunchController::reauthenticateCurrentAccount()
{
auto button =
QMessageBox::warning(m_parentWidget, tr("Account refresh failed"),
tr("The account has expired and needs to be reauthenticated. Do you want to reauthenticate this account?"),
QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::StandardButton::Yes);
if (button == QMessageBox::StandardButton::Yes) {
auto accounts = APPLICATION->accounts();
bool isDefault = accounts->defaultAccount() == m_accountToUse;
accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(m_accountToUse->profileId())));
if (m_accountToUse->accountType() == AccountType::MSA) {
auto newAccount = MSALoginDialog::newAccount(m_parentWidget);
accounts->addAccount(newAccount);
if (isDefault) {
accounts->setDefaultAccount(newAccount);
}
m_accountToUse = nullptr;
decideAccount();
return true;
}
emitFailed(tr("Account expired and re-login attempt failed"));
} else {
emitFailed(tr("The account has expired and needs to be reauthenticated"));
}
return false;
}
void LaunchController::launchInstance()
{
Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL");

View file

@ -56,8 +56,6 @@ class LaunchController : public Task {
void setOnline(bool online) { m_online = online; }
void setOfflineName(const QString& offlineName) { m_offlineName = offlineName; }
void setDemo(bool demo) { m_demo = demo; }
void setProfiler(BaseProfilerFactory* profiler) { m_profiler = profiler; }
@ -78,7 +76,6 @@ class LaunchController : public Task {
void decideAccount();
bool askPlayDemo();
QString askOfflineName(QString playerName, bool demo, bool& ok);
bool reauthenticateCurrentAccount();
private slots:
void readyForLaunch();
@ -90,7 +87,6 @@ class LaunchController : public Task {
private:
BaseProfilerFactory* m_profiler = nullptr;
bool m_online = true;
QString m_offlineName;
bool m_demo = false;
InstancePtr m_instance;
QWidget* m_parentWidget = nullptr;

View file

@ -33,7 +33,7 @@ class shared_qobject_ptr : public QSharedPointer<T> {
{}
void reset() { QSharedPointer<T>::reset(); }
void reset(T* other)
void reset(T*&& other)
{
shared_qobject_ptr<T> t(other);
this->swap(t);

View file

@ -35,9 +35,9 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
QString custom_target_folder)
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs), m_custom_target_folder(custom_target_folder)
{
if (is_indexed) {
m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version));
connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource);
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model && is_indexed) {
m_update_task.reset(new LocalModUpdateTask(model->indexDir(), *m_pack, m_pack_version));
connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource);
addTask(m_update_task);
}
@ -91,8 +91,12 @@ void ResourceDownloadTask::downloadSucceeded()
m_filesNetJob.reset();
auto name = std::get<0>(to_delete);
auto filename = std::get<1>(to_delete);
if (!name.isEmpty() && filename != m_pack_version.fileName)
m_pack_model->uninstallResource(filename, true);
if (!name.isEmpty() && filename != m_pack_version.fileName) {
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model)
model->uninstallMod(filename, true);
else
m_pack_model->uninstallResource(filename);
}
}
void ResourceDownloadTask::downloadFailed(QString reason)

View file

@ -22,7 +22,7 @@
#include "net/NetJob.h"
#include "tasks/SequentialTask.h"
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "modplatform/ModIndex.h"
class ResourceFolderModel;
@ -50,7 +50,7 @@ class ResourceDownloadTask : public SequentialTask {
QString m_custom_target_folder;
NetJob::Ptr m_filesNetJob;
LocalResourceUpdateTask::Ptr m_update_task;
LocalModUpdateTask::Ptr m_update_task;
void downloadProgressChanged(qint64 current, qint64 total);
void downloadFailed(QString reason);

View file

@ -79,7 +79,7 @@ void Version::parse()
if (m_string.isEmpty())
return;
auto classChange = [&currentSection](QChar lastChar, QChar currentChar) {
auto classChange = [&](QChar lastChar, QChar currentChar) {
if (lastChar.isNull())
return false;
if (lastChar.isDigit() != currentChar.isDigit())

View file

@ -104,11 +104,11 @@ void FileLinkApp::joinServer(QString server)
in.setDevice(&socket);
connect(&socket, &QLocalSocket::connected, this, []() { qDebug() << "connected to server"; });
connect(&socket, &QLocalSocket::connected, this, [&]() { qDebug() << "connected to server"; });
connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
connect(&socket, &QLocalSocket::errorOccurred, this, [this](QLocalSocket::LocalSocketError socketError) {
connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) {
m_status = Failed;
switch (socketError) {
case QLocalSocket::ServerNotFoundError:
@ -132,7 +132,7 @@ void FileLinkApp::joinServer(QString server)
}
});
connect(&socket, &QLocalSocket::disconnected, this, [this]() {
connect(&socket, &QLocalSocket::disconnected, this, [&]() {
qDebug() << "disconnected from server, should exit";
m_status = Succeeded;
exit();

View file

@ -47,24 +47,24 @@
#define MAX_SIZE 1024
IconList::IconList(const QStringList& builtinPaths, const QString& path, QObject* parent) : QAbstractListModel(parent)
IconList::IconList(const QStringList& builtinPaths, QString path, QObject* parent) : QAbstractListModel(parent)
{
QSet<QString> builtinNames;
// add builtin icons
for (const auto& builtinPath : builtinPaths) {
QDir instanceIcons(builtinPath);
auto fileInfoList = instanceIcons.entryInfoList(QDir::Files, QDir::Name);
for (const auto& fileInfo : fileInfoList) {
builtinNames.insert(fileInfo.baseName());
for (auto& builtinPath : builtinPaths) {
QDir instance_icons(builtinPath);
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
for (auto file_info : file_info_list) {
builtinNames.insert(file_info.completeBaseName());
}
}
for (const auto& builtinName : builtinNames) {
for (auto& builtinName : builtinNames) {
addThemeIcon(builtinName);
}
m_watcher.reset(new QFileSystemWatcher());
m_isWatching = false;
is_watching = false;
connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &IconList::directoryChanged);
connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &IconList::fileChanged);
@ -77,131 +77,91 @@ IconList::IconList(const QStringList& builtinPaths, const QString& path, QObject
void IconList::sortIconList()
{
qDebug() << "Sorting icon list...";
std::sort(m_icons.begin(), m_icons.end(), [](const MMCIcon& a, const MMCIcon& b) {
bool aIsSubdir = a.m_key.contains(QDir::separator());
bool bIsSubdir = b.m_key.contains(QDir::separator());
if (aIsSubdir != bIsSubdir) {
return !aIsSubdir; // root-level icons come first
}
return a.m_key.localeAwareCompare(b.m_key) < 0;
});
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { return a.m_key.localeAwareCompare(b.m_key) < 0; });
reindex();
}
// Helper function to add directories recursively
bool IconList::addPathRecursively(const QString& path)
{
QDir dir(path);
if (!dir.exists())
return false;
// Add the directory itself
bool watching = m_watcher->addPath(path);
// Add all subdirectories
QFileInfoList entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const QFileInfo& entry : entries) {
if (addPathRecursively(entry.absoluteFilePath())) {
watching = true;
}
}
return watching;
}
QStringList IconList::getIconFilePaths() const
{
QStringList iconFiles{};
QStringList directories{ m_dir.absolutePath() };
while (!directories.isEmpty()) {
QString first = directories.takeFirst();
QDir dir(first);
for (QFileInfo& fileInfo : dir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot, QDir::Name)) {
if (fileInfo.isDir())
directories.push_back(fileInfo.absoluteFilePath());
else
iconFiles.push_back(fileInfo.absoluteFilePath());
}
}
return iconFiles;
}
QString formatName(const QDir& iconsDir, const QFileInfo& iconFile)
{
if (iconFile.dir() == iconsDir)
return iconFile.baseName();
constexpr auto delimiter = " » ";
QString relativePathWithoutExtension = iconsDir.relativeFilePath(iconFile.dir().path()) + QDir::separator() + iconFile.baseName();
return relativePathWithoutExtension.replace(QDir::separator(), delimiter);
}
/// Split into a separate function because the preprocessing impedes readability
QSet<QString> toStringSet(const QList<QString>& list)
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QSet<QString> set(list.begin(), list.end());
#else
QSet<QString> set = list.toSet();
#endif
return set;
}
void IconList::directoryChanged(const QString& path)
{
QDir newDir(path);
if (m_dir.absolutePath() != newDir.absolutePath()) {
if (!path.startsWith(m_dir.absolutePath()))
m_dir.setPath(path);
QDir new_dir(path);
if (m_dir.absolutePath() != new_dir.absolutePath()) {
m_dir.setPath(path);
m_dir.refresh();
if (m_isWatching)
if (is_watching)
stopWatching();
startWatching();
}
if (!m_dir.exists() && !FS::ensureFolderPathExists(m_dir.absolutePath()))
return;
if (!m_dir.exists())
if (!FS::ensureFolderPathExists(m_dir.absolutePath()))
return;
m_dir.refresh();
const QStringList newFileNamesList = getIconFilePaths();
const QSet<QString> newSet = toStringSet(newFileNamesList);
QSet<QString> currentSet;
for (const MMCIcon& it : m_icons) {
auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
for (auto it = new_list.begin(); it != new_list.end(); it++) {
QString& foo = (*it);
foo = m_dir.filePath(foo);
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QSet<QString> new_set(new_list.begin(), new_list.end());
#else
auto new_set = new_list.toSet();
#endif
QList<QString> current_list;
for (auto& it : icons) {
if (!it.has(IconType::FileBased))
continue;
currentSet.insert(it.m_images[IconType::FileBased].filename);
current_list.push_back(it.m_images[IconType::FileBased].filename);
}
QSet<QString> toRemove = currentSet - newSet;
QSet<QString> toAdd = newSet - currentSet;
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QSet<QString> current_set(current_list.begin(), current_list.end());
#else
QSet<QString> current_set = current_list.toSet();
#endif
for (const QString& removedPath : toRemove) {
qDebug() << "Removing icon " << removedPath;
QFileInfo removedFile(removedPath);
QString key = m_dir.relativeFilePath(removedFile.absoluteFilePath());
QSet<QString> to_remove = current_set;
to_remove -= new_set;
QSet<QString> to_add = new_set;
to_add -= current_set;
for (auto remove : to_remove) {
qDebug() << "Removing " << remove;
QFileInfo rmfile(remove);
QString key = rmfile.completeBaseName();
QString suffix = rmfile.suffix();
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
if (!IconUtils::isIconSuffix(suffix))
key = rmfile.fileName();
int idx = getIconIndex(key);
if (idx == -1)
continue;
m_icons[idx].remove(FileBased);
if (m_icons[idx].type() == ToBeDeleted) {
icons[idx].remove(IconType::FileBased);
if (icons[idx].type() == IconType::ToBeDeleted) {
beginRemoveRows(QModelIndex(), idx, idx);
m_icons.remove(idx);
icons.remove(idx);
reindex();
endRemoveRows();
} else {
dataChanged(index(idx), index(idx));
}
m_watcher->removePath(removedPath);
m_watcher->removePath(remove);
emit iconUpdated(key);
}
for (const QString& addedPath : toAdd) {
qDebug() << "Adding icon " << addedPath;
for (auto add : to_add) {
qDebug() << "Adding " << add;
QFileInfo addfile(addedPath);
QString relativePath = m_dir.relativeFilePath(addfile.absoluteFilePath());
QString key = QFileInfo(relativePath).completeBaseName();
QString name = formatName(m_dir, addfile);
QFileInfo addfile(add);
QString key = addfile.completeBaseName();
if (addIcon(key, name, addfile.filePath(), IconType::FileBased)) {
m_watcher->addPath(addedPath);
QString suffix = addfile.suffix();
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
if (!IconUtils::isIconSuffix(suffix))
key = addfile.fileName();
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) {
m_watcher->addPath(add);
emit iconUpdated(key);
}
}
@ -211,24 +171,24 @@ void IconList::directoryChanged(const QString& path)
void IconList::fileChanged(const QString& path)
{
qDebug() << "Checking icon " << path;
qDebug() << "Checking " << path;
QFileInfo checkfile(path);
if (!checkfile.exists())
return;
QString key = m_dir.relativeFilePath(checkfile.absoluteFilePath());
QString key = checkfile.completeBaseName();
int idx = getIconIndex(key);
if (idx == -1)
return;
QIcon icon(path);
if (icon.availableSizes().empty())
if (!icon.availableSizes().size())
return;
m_icons[idx].m_images[IconType::FileBased].icon = icon;
icons[idx].m_images[IconType::FileBased].icon = icon;
dataChanged(index(idx), index(idx));
emit iconUpdated(key);
}
void IconList::SettingChanged(const Setting& setting, const QVariant& value)
void IconList::SettingChanged(const Setting& setting, QVariant value)
{
if (setting.id() != "IconsDir")
return;
@ -240,8 +200,8 @@ void IconList::startWatching()
{
auto abs_path = m_dir.absolutePath();
FS::ensureFolderPathExists(abs_path);
m_isWatching = addPathRecursively(abs_path);
if (m_isWatching) {
is_watching = m_watcher->addPath(abs_path);
if (is_watching) {
qDebug() << "Started watching " << abs_path;
} else {
qDebug() << "Failed to start watching " << abs_path;
@ -252,7 +212,7 @@ void IconList::stopWatching()
{
m_watcher->removePaths(m_watcher->files());
m_watcher->removePaths(m_watcher->directories());
m_isWatching = false;
is_watching = false;
}
QStringList IconList::mimeTypes() const
@ -282,7 +242,7 @@ bool IconList::dropMimeData(const QMimeData* data,
if (data->hasUrls()) {
auto urls = data->urls();
QStringList iconFiles;
for (const auto& url : urls) {
for (auto url : urls) {
// only local files may be dropped...
if (!url.isLocalFile())
continue;
@ -303,33 +263,33 @@ Qt::ItemFlags IconList::flags(const QModelIndex& index) const
QVariant IconList::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return {};
return QVariant();
int row = index.row();
if (row < 0 || row >= m_icons.size())
return {};
if (row < 0 || row >= icons.size())
return QVariant();
switch (role) {
case Qt::DecorationRole:
return m_icons[row].icon();
return icons[row].icon();
case Qt::DisplayRole:
return m_icons[row].name();
return icons[row].name();
case Qt::UserRole:
return m_icons[row].m_key;
return icons[row].m_key;
default:
return {};
return QVariant();
}
}
int IconList::rowCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : m_icons.size();
return parent.isValid() ? 0 : icons.size();
}
void IconList::installIcons(const QStringList& iconFiles)
{
for (const QString& file : iconFiles)
for (QString file : iconFiles)
installIcon(file, {});
}
@ -352,13 +312,12 @@ bool IconList::iconFileExists(const QString& key) const
return iconEntry && iconEntry->has(IconType::FileBased);
}
/// Returns the icon with the given key or nullptr if it doesn't exist.
const MMCIcon* IconList::icon(const QString& key) const
{
int iconIdx = getIconIndex(key);
if (iconIdx == -1)
return nullptr;
return &m_icons[iconIdx];
return &icons[iconIdx];
}
bool IconList::deleteIcon(const QString& key)
@ -373,22 +332,22 @@ bool IconList::trashIcon(const QString& key)
bool IconList::addThemeIcon(const QString& key)
{
auto iter = m_nameIndex.find(key);
if (iter != m_nameIndex.end()) {
auto& oldOne = m_icons[*iter];
auto iter = name_index.find(key);
if (iter != name_index.end()) {
auto& oldOne = icons[*iter];
oldOne.replace(Builtin, key);
dataChanged(index(*iter), index(*iter));
return true;
}
// add a new icon
beginInsertRows(QModelIndex(), m_icons.size(), m_icons.size());
beginInsertRows(QModelIndex(), icons.size(), icons.size());
{
MMCIcon mmc_icon;
mmc_icon.m_name = key;
mmc_icon.m_key = key;
mmc_icon.replace(Builtin, key);
m_icons.push_back(mmc_icon);
m_nameIndex[key] = m_icons.size() - 1;
icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1;
}
endInsertRows();
return true;
@ -400,22 +359,22 @@ bool IconList::addIcon(const QString& key, const QString& name, const QString& p
QIcon icon(path);
if (icon.isNull())
return false;
auto iter = m_nameIndex.find(key);
if (iter != m_nameIndex.end()) {
auto& oldOne = m_icons[*iter];
auto iter = name_index.find(key);
if (iter != name_index.end()) {
auto& oldOne = icons[*iter];
oldOne.replace(type, icon, path);
dataChanged(index(*iter), index(*iter));
return true;
}
// add a new icon
beginInsertRows(QModelIndex(), m_icons.size(), m_icons.size());
beginInsertRows(QModelIndex(), icons.size(), icons.size());
{
MMCIcon mmc_icon;
mmc_icon.m_name = name;
mmc_icon.m_key = key;
mmc_icon.replace(type, icon, path);
m_icons.push_back(mmc_icon);
m_nameIndex[key] = m_icons.size() - 1;
icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1;
}
endInsertRows();
return true;
@ -430,32 +389,33 @@ void IconList::saveIcon(const QString& key, const QString& path, const char* for
void IconList::reindex()
{
m_nameIndex.clear();
for (int i = 0; i < m_icons.size(); i++) {
m_nameIndex[m_icons[i].m_key] = i;
emit iconUpdated(m_icons[i].m_key); // prevents incorrect indices with proxy model
name_index.clear();
int i = 0;
for (auto& iter : icons) {
name_index[iter.m_key] = i;
i++;
}
}
QIcon IconList::getIcon(const QString& key) const
{
int iconIndex = getIconIndex(key);
int icon_index = getIconIndex(key);
if (iconIndex != -1)
return m_icons[iconIndex].icon();
if (icon_index != -1)
return icons[icon_index].icon();
// Fallback for icons that don't exist.b
iconIndex = getIconIndex("grass");
// Fallback for icons that don't exist.
icon_index = getIconIndex("grass");
if (iconIndex != -1)
return m_icons[iconIndex].icon();
return {};
if (icon_index != -1)
return icons[icon_index].icon();
return QIcon();
}
int IconList::getIconIndex(const QString& key) const
{
auto iter = m_nameIndex.find(key == "default" ? "grass" : key);
if (iter != m_nameIndex.end())
auto iter = name_index.find(key == "default" ? "grass" : key);
if (iter != name_index.end())
return *iter;
return -1;
@ -465,15 +425,3 @@ QString IconList::getDirectory() const
{
return m_dir.absolutePath();
}
/// Returns the directory of the icon with the given key or the default directory if it's a builtin icon.
QString IconList::iconDirectory(const QString& key) const
{
for (const auto& mmcIcon : m_icons) {
if (mmcIcon.m_key == key && mmcIcon.has(IconType::FileBased)) {
QFileInfo iconFile(mmcIcon.getFilePath());
return iconFile.dir().path();
}
}
return getDirectory();
}

View file

@ -51,7 +51,7 @@ class QFileSystemWatcher;
class IconList : public QAbstractListModel {
Q_OBJECT
public:
explicit IconList(const QStringList& builtinPaths, const QString& path, QObject* parent = 0);
explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0);
virtual ~IconList() {};
QIcon getIcon(const QString& key) const;
@ -72,7 +72,6 @@ class IconList : public QAbstractListModel {
bool deleteIcon(const QString& key);
bool trashIcon(const QString& key);
bool iconFileExists(const QString& key) const;
QString iconDirectory(const QString& key) const;
void installIcons(const QStringList& iconFiles);
void installIcon(const QString& file, const QString& name);
@ -92,20 +91,18 @@ class IconList : public QAbstractListModel {
IconList& operator=(const IconList&) = delete;
void reindex();
void sortIconList();
bool addPathRecursively(const QString& path);
QStringList getIconFilePaths() const;
public slots:
void directoryChanged(const QString& path);
protected slots:
void fileChanged(const QString& path);
void SettingChanged(const Setting& setting, const QVariant& value);
void SettingChanged(const Setting& setting, QVariant value);
private:
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
bool m_isWatching;
QMap<QString, int> m_nameIndex;
QVector<MMCIcon> m_icons;
bool is_watching;
QMap<QString, int> name_index;
QVector<MMCIcon> icons;
QDir m_dir;
};

View file

@ -405,7 +405,7 @@ QList<QString> JavaUtils::FindJavaPaths()
{
QList<QString> javas;
javas.append(this->GetDefaultJava()->path);
auto scanJavaDir = [&javas](
auto scanJavaDir = [&](
const QString& dirPath,
const std::function<bool(const QFileInfo&)>& filter = [](const QFileInfo&) { return true; }) {
QDir dir(dirPath);
@ -424,7 +424,7 @@ QList<QString> JavaUtils::FindJavaPaths()
};
// java installed in a snap is installed in the standard directory, but underneath $SNAP
auto snap = qEnvironmentVariable("SNAP");
auto scanJavaDirs = [scanJavaDir, snap](const QString& dirPath) {
auto scanJavaDirs = [&](const QString& dirPath) {
scanJavaDir(dirPath);
if (!snap.isNull()) {
scanJavaDir(snap + dirPath);
@ -442,15 +442,9 @@ QList<QString> JavaUtils::FindJavaPaths()
QString fileName = info.fileName();
return fileName.startsWith("openjdk-") || fileName.startsWith("openj9-");
};
// AOSC OS's locations for openjdk
auto aoscFilter = [](const QFileInfo& info) {
QString fileName = info.fileName();
return fileName == "java" || fileName.startsWith("java-");
};
scanJavaDir("/usr/lib64", gentooFilter);
scanJavaDir("/usr/lib", gentooFilter);
scanJavaDir("/opt", gentooFilter);
scanJavaDir("/usr/lib", aoscFilter);
// javas stored in Prism Launcher's folder
scanJavaDirs("java");
// manually installed JDKs in /opt
@ -552,12 +546,12 @@ QStringList getPrismJavaBundle()
{
QList<QString> javas;
auto scanDir = [&javas](QString prefix) {
auto scanDir = [&](QString prefix) {
javas.append(FS::PathCombine(prefix, "jre", "bin", JavaUtils::javaExecutable));
javas.append(FS::PathCombine(prefix, "bin", JavaUtils::javaExecutable));
javas.append(FS::PathCombine(prefix, JavaUtils::javaExecutable));
};
auto scanJavaDir = [scanDir](const QString& dirPath) {
auto scanJavaDir = [&](const QString& dirPath) {
QDir dir(dirPath);
if (!dir.exists())
return;

View file

@ -61,7 +61,7 @@ void PostLaunchCommand::executeTask()
void PostLaunchCommand::on_state(LoggedProcess::State state)
{
auto getError = [this]() { return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
auto getError = [&]() { return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
switch (state) {
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:

View file

@ -60,7 +60,7 @@ void PreLaunchCommand::executeTask()
void PreLaunchCommand::on_state(LoggedProcess::State state)
{
auto getError = [this]() { return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
auto getError = [&]() { return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
switch (state) {
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:

View file

@ -158,8 +158,8 @@ Version::Ptr VersionList::getVersion(const QString& version)
bool VersionList::hasVersion(QString version) const
{
auto ver = std::find_if(m_versions.constBegin(), m_versions.constEnd(),
[version](Meta::Version::Ptr const& a) { return a->version() == version; });
auto ver =
std::find_if(m_versions.constBegin(), m_versions.constEnd(), [&](Meta::Version::Ptr const& a) { return a->version() == version; });
return (ver != m_versions.constEnd());
}

View file

@ -65,7 +65,7 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
{
bool local = isLocal();
// Lambda function to get the absolute file path
auto actualPath = [this, local, overridePath](QString relPath) {
auto actualPath = [&](QString relPath) {
relPath = FS::RemoveInvalidPathChars(relPath);
QFileInfo out(FS::PathCombine(storagePrefix(), relPath));
if (local && !overridePath.isEmpty()) {
@ -115,7 +115,7 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
bool local = isLocal();
// Lambda function to check if a local file exists
auto check_local_file = [overridePath, &failedLocalFiles](QString storage) {
auto check_local_file = [&](QString storage) {
QFileInfo fileinfo(storage);
QString fileName = fileinfo.fileName();
auto fullPath = FS::PathCombine(overridePath, fileName);
@ -128,7 +128,7 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
};
// Lambda function to add a download request
auto add_download = [this, local, check_local_file, cache, stale, &out](QString storage, QString url, QString sha1) {
auto add_download = [&](QString storage, QString url, QString sha1) {
if (local) {
return check_local_file(storage);
}
@ -198,7 +198,7 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
}
}
} else {
auto raw_dl = [this, raw_storage]() {
auto raw_dl = [&]() {
if (!m_absoluteURL.isEmpty()) {
return m_absoluteURL;
}

View file

@ -91,56 +91,13 @@
#include "tools/BaseProfiler.h"
#include <QActionGroup>
#include <QMainWindow>
#include <QScreen>
#include <QWindow>
#ifdef Q_OS_LINUX
#include "MangoHud.h"
#endif
#ifdef WITH_QTDBUS
#include <QtDBus/QtDBus>
#endif
#define IBUS "@im=ibus"
static bool switcherooSetupGPU(QProcessEnvironment& env)
{
#ifdef WITH_QTDBUS
if (!QDBusConnection::systemBus().isConnected())
return false;
QDBusInterface switcheroo("net.hadess.SwitcherooControl", "/net/hadess/SwitcherooControl", "org.freedesktop.DBus.Properties",
QDBusConnection::systemBus());
if (!switcheroo.isValid())
return false;
QDBusReply<QDBusVariant> reply =
switcheroo.call(QStringLiteral("Get"), QStringLiteral("net.hadess.SwitcherooControl"), QStringLiteral("GPUs"));
if (!reply.isValid())
return false;
QDBusArgument arg = qvariant_cast<QDBusArgument>(reply.value().variant());
QList<QVariantMap> gpus;
arg >> gpus;
for (const auto& gpu : gpus) {
QString name = qvariant_cast<QString>(gpu[QStringLiteral("Name")]);
bool defaultGpu = qvariant_cast<bool>(gpu[QStringLiteral("Default")]);
if (!defaultGpu) {
QStringList envList = qvariant_cast<QStringList>(gpu[QStringLiteral("Environment")]);
for (int i = 0; i + 1 < envList.size(); i += 2) {
env.insert(envList[i], envList[i + 1]);
}
return true;
}
}
#endif
return false;
}
// all of this because keeping things compatible with deprecated old settings
// if either of the settings {a, b} is true, this also resolves to true
class OrSetting : public Setting {
@ -573,7 +530,7 @@ QStringList MinecraftInstance::javaArguments()
QString MinecraftInstance::getLauncher()
{
// use legacy launcher if the traits are set
if (isLegacy())
if (traits().contains("legacyLaunch") || traits().contains("alphaLaunch"))
return "legacy";
return "standard";
@ -594,13 +551,6 @@ QMap<QString, QString> MinecraftInstance::getVariables()
out.insert("INST_JAVA", settings()->get("JavaPath").toString());
out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
out.insert("NO_COLOR", "1");
#ifdef Q_OS_MACOS
// get library for Steam overlay support
QString steamDyldInsertLibraries = qEnvironmentVariable("STEAM_DYLD_INSERT_LIBRARIES");
if (!steamDyldInsertLibraries.isEmpty()) {
out.insert("DYLD_INSERT_LIBRARIES", steamDyldInsertLibraries);
}
#endif
return out;
}
@ -665,14 +615,12 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
}
if (settings()->get("UseDiscreteGpu").toBool()) {
if (!switcherooSetupGPU(env)) {
// Open Source Drivers
env.insert("DRI_PRIME", "1");
// Proprietary Nvidia Drivers
env.insert("__NV_PRIME_RENDER_OFFLOAD", "1");
env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only");
env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
}
// Open Source Drivers
env.insert("DRI_PRIME", "1");
// Proprietary Nvidia Drivers
env.insert("__NV_PRIME_RENDER_OFFLOAD", "1");
env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only");
env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
}
if (settings()->get("UseZink").toBool()) {
@ -805,34 +753,11 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftT
// window size, title and state, legacy
{
QString windowParams;
if (settings()->get("LaunchMaximized").toBool()) {
// FIXME doesn't support maximisation
if (!isLegacy()) {
auto screen = QGuiApplication::primaryScreen();
auto screenGeometry = screen->availableSize();
// small hack to get the widow decorations
for (auto w : QApplication::topLevelWidgets()) {
auto mainWindow = qobject_cast<QMainWindow*>(w);
if (mainWindow) {
auto m = mainWindow->windowHandle()->frameMargins();
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
screenGeometry = screenGeometry.shrunkBy(m);
#else
screenGeometry = { screenGeometry.width() - m.left() - m.right(), screenGeometry.height() - m.top() - m.bottom() };
#endif
break;
}
}
windowParams = QString("%1x%2").arg(screenGeometry.width()).arg(screenGeometry.height());
} else {
windowParams = "maximized";
}
} else {
if (settings()->get("LaunchMaximized").toBool())
windowParams = "maximized";
else
windowParams =
QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt());
}
launchScript += "windowTitle " + windowTitle() + "\n";
launchScript += "windowParams " + windowParams + "\n";
}
@ -904,7 +829,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << "Libraries:";
QStringList jars, nativeJars;
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
auto printLibFile = [&out](const QString& path) {
auto printLibFile = [&](const QString& path) {
QFileInfo info(path);
if (info.exists()) {
out << " " + path;
@ -924,7 +849,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
}
// mods and core mods
auto printModList = [&out](const QString& label, ModFolderModel& model) {
auto printModList = [&](const QString& label, ModFolderModel& model) {
if (model.size()) {
out << QString("%1:").arg(label);
auto modList = model.allMods();
@ -1253,7 +1178,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList()
{
if (!m_loader_mod_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed, true));
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed));
}
return m_loader_mod_list;
}
@ -1262,7 +1187,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList()
{
if (!m_core_mod_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed, true));
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed));
}
return m_core_mod_list;
}
@ -1279,8 +1204,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::nilModList()
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
{
if (!m_resource_pack_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this, is_indexed, true));
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this));
}
return m_resource_pack_list;
}
@ -1288,8 +1212,7 @@ std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
{
if (!m_texture_pack_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this, is_indexed, true));
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this));
}
return m_texture_pack_list;
}
@ -1297,17 +1220,11 @@ std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList()
{
if (!m_shader_pack_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this, is_indexed, true));
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this));
}
return m_shader_pack_list;
}
QList<std::shared_ptr<ResourceFolderModel>> MinecraftInstance::resourceLists()
{
return { loaderModList(), coreModList(), nilModList(), resourcePackList(), texturePackList(), shaderPackList() };
}
std::shared_ptr<WorldList> MinecraftInstance::worldList()
{
if (!m_world_list) {

View file

@ -116,7 +116,6 @@ class MinecraftInstance : public BaseInstance {
std::shared_ptr<ResourcePackFolderModel> resourcePackList();
std::shared_ptr<TexturePackFolderModel> texturePackList();
std::shared_ptr<ShaderPackFolderModel> shaderPackList();
QList<std::shared_ptr<ResourceFolderModel>> resourceLists();
std::shared_ptr<WorldList> worldList();
std::shared_ptr<GameOptions> gameOptionsModel();

View file

@ -176,7 +176,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc
}
}
auto readLibs = [&root, &out, &filename](const char* which, QList<LibraryPtr>& outList) {
auto readLibs = [&](const char* which, QList<LibraryPtr>& outList) {
for (auto libVal : requireArray(root.value(which))) {
QJsonObject libObj = requireObject(libVal);
// parse the library

View file

@ -752,7 +752,7 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch)
}
// FIXME: we need a generic way of removing local resources, not just jar mods...
auto preRemoveJarMod = [this](LibraryPtr jarMod) -> bool {
auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool {
if (!jarMod->isLocal()) {
return true;
}

View file

@ -116,7 +116,7 @@ class MinecraftAccount : public QObject, public Usable {
[[nodiscard]] AccountType accountType() const noexcept { return data.type; }
bool ownsMinecraft() const { return true; }
bool ownsMinecraft() const { return data.type != AccountType::Offline && data.minecraftEntitlement.ownsMinecraft; }
bool hasProfile() const { return data.profileId().size() != 0; }

View file

@ -54,8 +54,8 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent)
{
if (parent->instance()->settings()->get("CloseAfterLaunch").toBool()) {
std::shared_ptr<QMetaObject::Connection> connection{ new QMetaObject::Connection };
*connection = connect(
&m_process, &LoggedProcess::log, this, [connection](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) {
*connection =
connect(&m_process, &LoggedProcess::log, this, [=](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) {
qDebug() << lines;
if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) {
APPLICATION->closeAllWindows();

View file

@ -26,48 +26,33 @@
// launcher/minecraft/mod/Mod.h
class Mod;
namespace Metadata {
using ModStruct = Packwiz::V1::Mod;
using ModSide = Packwiz::V1::Side;
/* Abstraction file for easily changing the way metadata is stored / handled
* Needs to be a class because of -Wunused-function and no C++17 [[maybe_unused]]
* */
class Metadata {
public:
using ModStruct = Packwiz::V1::Mod;
using ModSide = Packwiz::V1::Side;
inline auto create(const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct
{
return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version);
}
static auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct
{
return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version);
}
inline auto create(const QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct
{
return Packwiz::V1::createModFormat(index_dir, internal_mod, std::move(mod_slug));
}
static auto create(QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct
{
return Packwiz::V1::createModFormat(index_dir, internal_mod, mod_slug);
}
inline void update(const QDir& index_dir, ModStruct& mod)
{
Packwiz::V1::updateModIndex(index_dir, mod);
}
static void update(QDir& index_dir, ModStruct& mod) { Packwiz::V1::updateModIndex(index_dir, mod); }
inline void remove(const QDir& index_dir, QString mod_slug)
{
Packwiz::V1::deleteModIndex(index_dir, mod_slug);
}
static void remove(QDir& index_dir, QString mod_slug) { Packwiz::V1::deleteModIndex(index_dir, mod_slug); }
inline void remove(const QDir& index_dir, QVariant& mod_id)
{
Packwiz::V1::deleteModIndex(index_dir, mod_id);
}
static void remove(QDir& index_dir, QVariant& mod_id) { Packwiz::V1::deleteModIndex(index_dir, mod_id); }
inline auto get(const QDir& index_dir, QString mod_slug) -> ModStruct
{
return Packwiz::V1::getIndexForMod(index_dir, std::move(mod_slug));
}
static auto get(QDir& index_dir, QString mod_slug) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_slug); }
inline auto get(const QDir& index_dir, QVariant& mod_id) -> ModStruct
{
return Packwiz::V1::getIndexForMod(index_dir, mod_id);
}
static auto get(QDir& index_dir, QVariant& mod_id) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_id); }
inline auto modSideToString(ModSide side) -> QString
{
return Packwiz::V1::sideToString(side);
}
}; // namespace Metadata
static auto modSideToString(ModSide side) -> QString { return Packwiz::V1::sideToString(side); }
};

View file

@ -38,15 +38,16 @@
#include "Mod.h"
#include <qpixmap.h>
#include <QDebug>
#include <QDir>
#include <QRegularExpression>
#include <QString>
#include "MTPixmapCache.h"
#include "MetadataHandler.h"
#include "Resource.h"
#include "Version.h"
#include "minecraft/mod/ModDetails.h"
#include "minecraft/mod/Resource.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "modplatform/ModIndex.h"
@ -55,6 +56,24 @@ Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details()
m_enabled = (file.suffix() != "disabled");
}
Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) : Mod(mods_dir.absoluteFilePath(metadata.filename))
{
m_name = metadata.name;
m_local_details.metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
}
void Mod::setStatus(ModStatus status)
{
m_local_details.status = status;
}
void Mod::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
{
if (status() == ModStatus::NoMetadata)
setStatus(ModStatus::Installed);
m_local_details.metadata = metadata;
}
void Mod::setDetails(const ModDetails& details)
{
m_local_details = details;
@ -82,28 +101,33 @@ int Mod::compare(const Resource& other, SortType type) const
return -1;
break;
}
case SortType::SIDE: {
auto compare_result = QString::compare(side(), cast_other->side(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
break;
case SortType::PROVIDER: {
return QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive);
}
case SortType::MC_VERSIONS: {
auto compare_result = QString::compare(mcVersions(), cast_other->mcVersions(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
case SortType::SIDE: {
if (side() > cast_other->side())
return 1;
else if (side() < cast_other->side())
return -1;
break;
}
case SortType::LOADERS: {
auto compare_result = QString::compare(loaders(), cast_other->loaders(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
if (loaders() > cast_other->loaders())
return 1;
else if (loaders() < cast_other->loaders())
return -1;
break;
}
case SortType::MC_VERSIONS: {
auto thisVersion = mcVersions().join(",");
auto otherVersion = cast_other->mcVersions().join(",");
return QString::compare(thisVersion, otherVersion, Qt::CaseInsensitive);
}
case SortType::RELEASE_TYPE: {
auto compare_result = QString::compare(releaseType(), cast_other->releaseType(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
if (releaseType() > cast_other->releaseType())
return 1;
else if (releaseType() < cast_other->releaseType())
return -1;
break;
}
}
@ -124,6 +148,28 @@ bool Mod::applyFilter(QRegularExpression filter) const
return Resource::applyFilter(filter);
}
auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
{
if (!preserve_metadata) {
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
destroyMetadata(index_dir);
}
return Resource::destroy(attempt_trash);
}
void Mod::destroyMetadata(QDir& index_dir)
{
if (metadata()) {
Metadata::remove(index_dir, metadata()->slug);
} else {
auto n = name();
Metadata::remove(index_dir, n);
}
m_local_details.metadata = nullptr;
}
auto Mod::details() const -> const ModDetails&
{
return m_local_details;
@ -135,7 +181,10 @@ auto Mod::name() const -> QString
if (!d_name.isEmpty())
return d_name;
return Resource::name();
if (metadata())
return metadata()->name;
return m_name;
}
auto Mod::version() const -> QString
@ -143,52 +192,16 @@ auto Mod::version() const -> QString
return details().version;
}
auto Mod::homepage() const -> QString
auto Mod::homeurl() const -> QString
{
QString metaUrl = Resource::homepage();
if (metaUrl.isEmpty())
return details().homeurl;
else
return metaUrl;
return details().homeurl;
}
auto Mod::loaders() const -> QString
auto Mod::metaurl() const -> QString
{
if (metadata()) {
QStringList loaders;
auto modLoaders = metadata()->loaders;
for (auto loader : ModPlatform::modLoaderTypesToList(modLoaders)) {
loaders << getModLoaderAsString(loader);
}
return loaders.join(", ");
}
return {};
}
auto Mod::side() const -> QString
{
if (metadata())
return Metadata::modSideToString(metadata()->side);
return Metadata::modSideToString(Metadata::ModSide::UniversalSide);
}
auto Mod::mcVersions() const -> QString
{
if (metadata())
return metadata()->mcVersions.join(", ");
return {};
}
auto Mod::releaseType() const -> QString
{
if (metadata())
return metadata()->releaseType.toString();
return ModPlatform::IndexedVersionType().toString();
if (metadata() == nullptr)
return homeurl();
return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
}
auto Mod::description() const -> QString
@ -201,17 +214,73 @@ auto Mod::authors() const -> QStringList
return details().authors;
}
auto Mod::status() const -> ModStatus
{
return details().status;
}
auto Mod::metadata() -> std::shared_ptr<Metadata::ModStruct>
{
return m_local_details.metadata;
}
auto Mod::metadata() const -> const std::shared_ptr<Metadata::ModStruct>
{
return m_local_details.metadata;
}
void Mod::finishResolvingWithDetails(ModDetails&& details)
{
m_is_resolving = false;
m_is_resolved = true;
std::shared_ptr<Metadata::ModStruct> metadata = details.metadata;
if (details.status == ModStatus::Unknown)
details.status = m_local_details.status;
m_local_details = std::move(details);
if (metadata)
setMetadata(std::move(metadata));
if (!iconPath().isEmpty()) {
m_packImageCacheKey.wasReadAttempt = false;
}
}
auto Mod::provider() const -> std::optional<QString>
{
if (metadata())
return ModPlatform::ProviderCapabilities::readableName(metadata()->provider);
return {};
}
auto Mod::side() const -> Metadata::ModSide
{
if (metadata())
return metadata()->side;
return Metadata::ModSide::UniversalSide;
}
auto Mod::releaseType() const -> ModPlatform::IndexedVersionType
{
if (metadata())
return metadata()->releaseType;
return ModPlatform::IndexedVersionType::VersionType::Unknown;
}
auto Mod::loaders() const -> ModPlatform::ModLoaderTypes
{
if (metadata())
return metadata()->loaders;
return {};
}
auto Mod::mcVersions() const -> QStringList
{
if (metadata())
return metadata()->mcVersions;
return {};
}
auto Mod::licenses() const -> const QList<ModLicense>&
{
return details().licenses;

View file

@ -48,6 +48,7 @@
#include "ModDetails.h"
#include "Resource.h"
#include "modplatform/ModIndex.h"
class Mod : public Resource {
Q_OBJECT
@ -57,20 +58,24 @@ class Mod : public Resource {
Mod() = default;
Mod(const QFileInfo& file);
Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata);
Mod(QString file_path) : Mod(QFileInfo(file_path)) {}
auto details() const -> const ModDetails&;
auto name() const -> QString override;
auto version() const -> QString;
auto homepage() const -> QString override;
auto homeurl() const -> QString;
auto description() const -> QString;
auto authors() const -> QStringList;
auto status() const -> ModStatus;
auto provider() const -> std::optional<QString>;
auto licenses() const -> const QList<ModLicense>&;
auto issueTracker() const -> QString;
auto side() const -> QString;
auto loaders() const -> QString;
auto mcVersions() const -> QString;
auto releaseType() const -> QString;
auto metaurl() const -> QString;
auto side() const -> Metadata::ModSide;
auto loaders() const -> ModPlatform::ModLoaderTypes;
auto mcVersions() const -> QStringList;
auto releaseType() const -> ModPlatform::IndexedVersionType;
/** Get the intneral path to the mod's icon file*/
QString iconPath() const { return m_local_details.icon_file; }
@ -79,11 +84,17 @@ class Mod : public Resource {
/** Thread-safe. */
QPixmap setIcon(QImage new_image) const;
auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
void setStatus(ModStatus status);
void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
void setDetails(const ModDetails& details);
bool valid() const override;
[[nodiscard]] int compare(const Resource & other, SortType type) const override;
[[nodiscard]] int compare(Resource const& other, SortType type) const override;
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
// Delete all the files of this mod

View file

@ -43,6 +43,13 @@
#include "minecraft/mod/MetadataHandler.h"
enum class ModStatus {
Installed, // Both JAR and Metadata are present
NotInstalled, // Only the Metadata is present
NoMetadata, // Only the JAR is present
Unknown, // Default status
};
struct ModLicense {
QString name = {};
QString id = {};
@ -142,6 +149,12 @@ struct ModDetails {
/* Path of mod logo */
QString icon_file = {};
/* Installation status of the mod */
ModStatus status = ModStatus::Unknown;
/* Metadata information, if any */
std::shared_ptr<Metadata::ModStruct> metadata = nullptr;
ModDetails() = default;
/** Metadata should be handled manually to properly set the mod status. */
@ -156,9 +169,40 @@ struct ModDetails {
, issue_tracker(other.issue_tracker)
, licenses(other.licenses)
, icon_file(other.icon_file)
, status(other.status)
{}
ModDetails& operator=(const ModDetails& other) = default;
ModDetails& operator=(const ModDetails& other)
{
this->mod_id = other.mod_id;
this->name = other.name;
this->version = other.version;
this->mcversion = other.mcversion;
this->homeurl = other.homeurl;
this->description = other.description;
this->authors = other.authors;
this->issue_tracker = other.issue_tracker;
this->licenses = other.licenses;
this->icon_file = other.icon_file;
this->status = other.status;
ModDetails& operator=(ModDetails&& other) = default;
return *this;
}
ModDetails& operator=(const ModDetails&& other)
{
this->mod_id = other.mod_id;
this->name = other.name;
this->version = other.version;
this->mcversion = other.mcversion;
this->homeurl = other.homeurl;
this->description = other.description;
this->authors = other.authors;
this->issue_tracker = other.issue_tracker;
this->licenses = other.licenses;
this->icon_file = other.icon_file;
this->status = other.status;
return *this;
}
};

View file

@ -51,10 +51,18 @@
#include "Application.h"
#include "Json.h"
#include "minecraft/mod/MetadataHandler.h"
#include "minecraft/mod/Resource.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
{
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders",
"Minecraft Versions", "Release Type" });
@ -84,7 +92,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
case NameColumn:
return m_resources[row]->name();
case VersionColumn: {
switch (at(row).type()) {
switch (m_resources[row]->type()) {
case ResourceType::FOLDER:
return tr("Folder");
case ResourceType::SINGLEFILE:
@ -92,50 +100,64 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
default:
break;
}
return at(row).version();
return at(row)->version();
}
case DateColumn:
return at(row).dateTimeChanged();
return m_resources[row]->dateTimeChanged();
case ProviderColumn: {
return at(row).provider();
auto provider = at(row)->provider();
if (!provider.has_value()) {
//: Unknown mod provider (i.e. not Modrinth, CurseForge, etc...)
return tr("Unknown");
}
return provider.value();
}
case SideColumn: {
return at(row).side();
return Metadata::modSideToString(at(row)->side());
}
case LoadersColumn: {
return at(row).loaders();
QStringList loaders;
auto modLoaders = at(row)->loaders();
for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Cauldron, ModPlatform::LiteLoader,
ModPlatform::Fabric, ModPlatform::Quilt }) {
if (modLoaders & loader) {
loaders << getModLoaderAsString(loader);
}
}
return loaders.join(", ");
}
case McVersionsColumn: {
return at(row).mcVersions();
return at(row)->mcVersions().join(", ");
}
case ReleaseTypeColumn: {
return at(row).releaseType();
return at(row)->releaseType().toString();
}
case SizeColumn:
return at(row).sizeStr();
return m_resources[row]->sizeStr();
default:
return QVariant();
}
case Qt::ToolTipRole:
if (column == NameColumn) {
if (at(row).isSymLinkUnder(instDirPath())) {
if (at(row)->isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
"\nCanonical Path: %1")
.arg(at(row).fileinfo().canonicalFilePath());
.arg(at(row)->fileinfo().canonicalFilePath());
}
if (at(row).isMoreThanOneHardLink()) {
if (at(row)->isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
}
}
return m_resources[row]->internal_id();
case Qt::DecorationRole: {
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row).icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
return at(row)->icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {};
}
@ -147,7 +169,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
case Qt::CheckStateRole:
switch (column) {
case ActiveColumn:
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
default:
return QVariant();
}
@ -188,7 +210,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
case DateColumn:
return tr("The date and time this mod was last changed (or added).");
case ProviderColumn:
return tr("The source provider of the mod.");
return tr("Where the mod was downloaded from.");
case SideColumn:
return tr("On what environment the mod is running.");
case LoadersColumn:
@ -213,16 +235,133 @@ int ModFolderModel::columnCount(const QModelIndex& parent) const
return parent.isValid() ? 0 : NUM_COLUMNS;
}
Task* ModFolderModel::createUpdateTask()
{
auto index_dir = indexDir();
auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load);
m_first_folder_load = false;
return task;
}
Task* ModFolderModel::createParseTask(Resource& resource)
{
return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo());
}
bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
{
for (auto mod : allMods()) {
if (mod->getOriginalFileName() == filename) {
auto index_dir = indexDir();
mod->destroy(index_dir, preserve_metadata, false);
update();
return true;
}
}
return false;
}
bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
{
if (indexes.isEmpty())
return true;
for (auto i : indexes) {
if (i.column() != 0) {
continue;
}
auto m = at(i.row());
auto index_dir = indexDir();
m->destroy(index_dir);
}
update();
return true;
}
bool ModFolderModel::deleteModsMetadata(const QModelIndexList& indexes)
{
if (indexes.isEmpty())
return true;
for (auto i : indexes) {
if (i.column() != 0) {
continue;
}
auto m = at(i.row());
auto index_dir = indexDir();
m->destroyMetadata(index_dir);
}
update();
return true;
}
bool ModFolderModel::isValid()
{
return m_dir.exists() && m_dir.isReadable();
}
bool ModFolderModel::startWatching()
{
// Remove orphaned metadata next time
m_first_folder_load = true;
return ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
}
bool ModFolderModel::stopWatching()
{
return ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
}
auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList<Mod*>
{
QList<Mod*> selected_resources;
for (auto i : indexes) {
if (i.column() != 0)
continue;
selected_resources.push_back(at(i.row()));
}
return selected_resources;
}
auto ModFolderModel::allMods() -> QList<Mod*>
{
QList<Mod*> mods;
for (auto& res : qAsConst(m_resources)) {
mods.append(static_cast<Mod*>(res.get()));
}
return mods;
}
void ModFolderModel::onUpdateSucceeded()
{
auto update_results = static_cast<ModFolderLoadTask*>(m_current_update_task.get())->result();
auto& new_mods = update_results->mods;
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
auto current_list = m_resources_index.keys();
QSet<QString> current_set(current_list.begin(), current_list.end());
auto new_list = new_mods.keys();
QSet<QString> new_set(new_list.begin(), new_list.end());
#else
QSet<QString> current_set(m_resources_index.keys().toSet());
QSet<QString> new_set(new_mods.keys().toSet());
#endif
applyUpdates(current_set, new_set, new_mods);
}
void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
{
auto iter = m_active_parse_tasks.constFind(ticket);
@ -240,7 +379,51 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
auto result = cast_task->result();
if (result && resource)
static_cast<Mod*>(resource.get())->finishResolvingWithDetails(std::move(result->details));
resource->finishResolvingWithDetails(std::move(result->details));
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
}
static const FlameAPI flameAPI;
bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers)
{
if (vers.addonId.isValid()) {
ModPlatform::IndexedPack pack{
vers.addonId,
ModPlatform::ResourceProvider::FLAME,
};
QEventLoop loop;
auto response = std::make_shared<QByteArray>();
auto job = flameAPI.getProject(vers.addonId.toString(), response);
QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qDebug() << *response;
return;
}
try {
auto obj = Json::requireObject(Json::requireObject(doc), "data");
FlameMod::loadIndexedPack(pack, obj);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading mod info: " << e.cause();
}
LocalModUpdateTask update_metadata(indexDir(), pack, vers);
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
update_metadata.start();
});
job->start();
loop.exec();
}
return ResourceFolderModel::installResource(file_path);
}

View file

@ -47,6 +47,11 @@
#include "Mod.h"
#include "ResourceFolderModel.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
class LegacyInstance;
class BaseInstance;
class QFileSystemWatcher;
@ -71,7 +76,8 @@ class ModFolderModel : public ResourceFolderModel {
ReleaseTypeColumn,
NUM_COLUMNS
};
ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
enum ModStatusAction { Disable, Enable, Toggle };
ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true);
virtual QString id() const override { return "mods"; }
@ -80,13 +86,34 @@ class ModFolderModel : public ResourceFolderModel {
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
int columnCount(const QModelIndex& parent) const override;
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new Mod(file); }
[[nodiscard]] Task* createUpdateTask() override;
[[nodiscard]] Task* createParseTask(Resource&) override;
bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
bool installMod(QString file_path, ModPlatform::IndexedVersion& vers);
bool uninstallMod(const QString& filename, bool preserve_metadata = false);
/// Deletes all the selected mods
bool deleteMods(const QModelIndexList& indexes);
bool deleteModsMetadata(const QModelIndexList& indexes);
bool isValid();
bool startWatching() override;
bool stopWatching() override;
QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; }
auto selectedMods(QModelIndexList& indexes) -> QList<Mod*>;
auto allMods() -> QList<Mod*>;
RESOURCE_HELPERS(Mod)
private slots:
void onUpdateSucceeded() override;
void onParseSucceeded(int ticket, QString resource_id) override;
protected:
bool m_is_indexed;
bool m_first_folder_load = true;
};

View file

@ -21,7 +21,7 @@ void Resource::setFile(QFileInfo file_info)
parseFile();
}
static std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
{
if (file.isDir()) {
auto dir = QDir(file.absoluteFilePath());
@ -72,14 +72,6 @@ void Resource::parseFile()
m_changed_date_time = m_file_info.lastModified();
}
auto Resource::name() const -> QString
{
if (metadata())
return metadata()->name;
return m_name;
}
static void removeThePrefix(QString& string)
{
QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption);
@ -87,30 +79,6 @@ static void removeThePrefix(QString& string)
string = string.trimmed();
}
auto Resource::provider() const -> QString
{
if (metadata())
return ModPlatform::ProviderCapabilities::readableName(metadata()->provider);
return tr("Unknown");
}
auto Resource::homepage() const -> QString
{
if (metadata())
return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
return {};
}
void Resource::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
{
if (status() == ResourceStatus::NO_METADATA)
setStatus(ResourceStatus::INSTALLED);
m_metadata = metadata;
}
int Resource::compare(const Resource& other, SortType type) const
{
switch (type) {
@ -125,7 +93,6 @@ int Resource::compare(const Resource& other, SortType type) const
QString this_name{ name() };
QString other_name{ other.name() };
// TODO do we need this? it could result in 0 being returned
removeThePrefix(this_name);
removeThePrefix(other_name);
@ -151,12 +118,6 @@ int Resource::compare(const Resource& other, SortType type) const
return -1;
break;
}
case SortType::PROVIDER: {
auto compare_result = QString::compare(provider(), other.provider(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
break;
}
}
return 0;
@ -213,27 +174,10 @@ bool Resource::enable(EnableAction action)
return true;
}
auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
bool Resource::destroy(bool attemptTrash)
{
m_type = ResourceType::UNKNOWN;
if (!preserve_metadata) {
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
destroyMetadata(index_dir);
}
return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
}
auto Resource::destroyMetadata(const QDir& index_dir) -> void
{
if (metadata()) {
Metadata::remove(index_dir, metadata()->slug);
} else {
auto n = name();
Metadata::remove(index_dir, n);
}
m_metadata = nullptr;
return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
}
bool Resource::isSymLinkUnder(const QString& instPath) const

View file

@ -40,7 +40,6 @@
#include <QObject>
#include <QPointer>
#include "MetadataHandler.h"
#include "QObjectPtr.h"
enum class ResourceType {
@ -51,14 +50,7 @@ enum class ResourceType {
LITEMOD, //!< The resource is a litemod
};
enum class ResourceStatus {
INSTALLED, // Both JAR and Metadata are present
NOT_INSTALLED, // Only the Metadata is present
NO_METADATA, // Only the JAR is present
UNKNOWN, // Default status
};
enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, MC_VERSIONS, LOADERS, RELEASE_TYPE };
enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, LOADERS, MC_VERSIONS, RELEASE_TYPE };
enum class EnableAction { ENABLE, DISABLE, TOGGLE };
@ -92,19 +84,9 @@ class Resource : public QObject {
[[nodiscard]] QString sizeStr() const { return m_size_str; }
[[nodiscard]] qint64 sizeInfo() const { return m_size_info; }
[[nodiscard]] virtual auto name() const -> QString;
[[nodiscard]] virtual auto name() const -> QString { return m_name; }
[[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; }
[[nodiscard]] auto status() const -> ResourceStatus { return m_status; };
[[nodiscard]] auto metadata() -> std::shared_ptr<Metadata::ModStruct> { return m_metadata; }
[[nodiscard]] auto metadata() const -> std::shared_ptr<const Metadata::ModStruct> { return m_metadata; }
[[nodiscard]] auto provider() const -> QString;
[[nodiscard]] virtual auto homepage() const -> QString;
void setStatus(ResourceStatus status) { m_status = status; }
void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
/** Compares two Resources, for sorting purposes, considering a ascending order, returning:
* > 0: 'this' comes after 'other'
* = 0: 'this' is equal to 'other'
@ -135,9 +117,7 @@ class Resource : public QObject {
}
// Delete all files of this resource.
auto destroy(const QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
// Delete the metadata only.
auto destroyMetadata(const QDir& index_dir) -> void;
bool destroy(bool attemptTrash = true);
[[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); }
@ -166,11 +146,6 @@ class Resource : public QObject {
/* The type of file we're dealing with. */
ResourceType m_type = ResourceType::UNKNOWN;
/* Installation status of the resource. */
ResourceStatus m_status = ResourceStatus::UNKNOWN;
std::shared_ptr<Metadata::ModStruct> m_metadata = nullptr;
/* Whether the resource is enabled (e.g. shows up in the game) or not. */
bool m_enabled = true;

View file

@ -11,23 +11,20 @@
#include <QStyle>
#include <QThreadPool>
#include <QUrl>
#include <utility>
#include "Application.h"
#include "FileSystem.h"
#include "minecraft/mod/tasks/ResourceFolderLoadTask.h"
#include "QVariantUtils.h"
#include "StringUtils.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "Json.h"
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
#include "settings/Setting.h"
#include "tasks/Task.h"
#include "ui/dialogs/CustomMessageBox.h"
ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this), m_is_indexed(is_indexed)
ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir)
: QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this)
{
if (create_dir) {
FS::ensureFolderPathExists(m_dir.absolutePath());
@ -51,9 +48,6 @@ ResourceFolderModel::~ResourceFolderModel()
bool ResourceFolderModel::startWatching(const QStringList& paths)
{
// Remove orphaned metadata next time
m_first_folder_load = true;
if (m_is_watching)
return false;
@ -164,55 +158,11 @@ bool ResourceFolderModel::installResource(QString original_path)
return false;
}
bool ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers)
{
if (vers.addonId.isValid()) {
ModPlatform::IndexedPack pack{
vers.addonId,
ModPlatform::ResourceProvider::FLAME,
};
QEventLoop loop;
auto response = std::make_shared<QByteArray>();
auto job = FlameAPI().getProject(vers.addonId.toString(), response);
QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qDebug() << *response;
return;
}
try {
auto obj = Json::requireObject(Json::requireObject(doc), "data");
FlameMod::loadIndexedPack(pack, obj);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading mod info: " << e.cause();
}
LocalResourceUpdateTask update_metadata(indexDir(), pack, vers);
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
update_metadata.start();
});
job->start();
loop.exec();
}
return installResource(std::move(path));
}
bool ResourceFolderModel::uninstallResource(QString file_name, bool preserve_metadata)
bool ResourceFolderModel::uninstallResource(QString file_name)
{
for (auto& resource : m_resources) {
if (resource->fileinfo().fileName() == file_name) {
auto res = resource->destroy(indexDir(), preserve_metadata, false);
auto res = resource->destroy(false);
update();
@ -228,11 +178,13 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
return true;
for (auto i : indexes) {
if (i.column() != 0)
if (i.column() != 0) {
continue;
}
auto& resource = m_resources.at(i.row());
resource->destroy(indexDir());
resource->destroy();
}
update();
@ -240,22 +192,6 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
return true;
}
void ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes)
{
if (indexes.isEmpty())
return;
for (auto i : indexes) {
if (i.column() != 0)
continue;
auto& resource = m_resources.at(i.row());
resource->destroyMetadata(indexDir());
}
update();
}
bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action)
{
if (indexes.isEmpty())
@ -309,7 +245,7 @@ bool ResourceFolderModel::update()
connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection);
connect(
m_current_update_task.get(), &Task::finished, this,
[this] {
[=] {
m_current_update_task.reset();
if (m_scheduled_update) {
m_scheduled_update = false;
@ -363,7 +299,7 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res)
void ResourceFolderModel::onUpdateSucceeded()
{
auto update_results = static_cast<ResourceFolderLoadTask*>(m_current_update_task.get())->result();
auto update_results = static_cast<BasicFolderLoadTask*>(m_current_update_task.get())->result();
auto& new_resources = update_results->resources;
@ -393,11 +329,7 @@ void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
Task* ResourceFolderModel::createUpdateTask()
{
auto index_dir = indexDir();
auto task = new ResourceFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load,
[this](const QFileInfo& file) { return createResource(file); });
m_first_folder_load = false;
return task;
return new BasicFolderLoadTask(m_dir);
}
bool ResourceFolderModel::hasPendingParseTasks() const
@ -487,8 +419,6 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
return m_resources[row]->name();
case DateColumn:
return m_resources[row]->dateTimeChanged();
case ProviderColumn:
return m_resources[row]->provider();
case SizeColumn:
return m_resources[row]->sizeStr();
default:
@ -560,23 +490,22 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien
case ActiveColumn:
case NameColumn:
case DateColumn:
case ProviderColumn:
case SizeColumn:
return columnNames().at(section);
default:
return {};
}
case Qt::ToolTipRole: {
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
switch (section) {
case ActiveColumn:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("Is the resource enabled?");
case NameColumn:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("The name of the resource.");
case DateColumn:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("The date and time this resource was last changed (or added).");
case ProviderColumn:
return tr("The source provider of the resource.");
case SizeColumn:
return tr("The size of the resource.");
default:
@ -722,126 +651,3 @@ void ResourceFolderModel::onParseFailed(int ticket, QString resource_id)
}
endRemoveRows();
}
void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, Resource::Ptr>& new_resources)
{
// see if the kept resources changed in some way
{
QSet<QString> kept_set = current_set;
kept_set.intersect(new_set);
for (auto const& kept : kept_set) {
auto row_it = m_resources_index.constFind(kept);
Q_ASSERT(row_it != m_resources_index.constEnd());
auto row = row_it.value();
auto& new_resource = new_resources[kept];
auto const& current_resource = m_resources.at(row);
if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
// no significant change, ignore...
continue;
}
// If the resource is resolving, but something about it changed, we don't want to
// continue the resolving.
if (current_resource->isResolving()) {
auto ticket = current_resource->resolutionTicket();
if (m_active_parse_tasks.contains(ticket)) {
auto task = (*m_active_parse_tasks.find(ticket)).get();
task->abort();
}
}
m_resources[row].reset(new_resource);
resolveResource(m_resources.at(row));
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
}
}
// remove resources no longer present
{
QSet<QString> removed_set = current_set;
removed_set.subtract(new_set);
QList<int> removed_rows;
for (auto& removed : removed_set)
removed_rows.append(m_resources_index[removed]);
std::sort(removed_rows.begin(), removed_rows.end(), std::greater<int>());
for (auto& removed_index : removed_rows) {
auto removed_it = m_resources.begin() + removed_index;
Q_ASSERT(removed_it != m_resources.end());
if ((*removed_it)->isResolving()) {
auto ticket = (*removed_it)->resolutionTicket();
if (m_active_parse_tasks.contains(ticket)) {
auto task = (*m_active_parse_tasks.find(ticket)).get();
task->abort();
}
}
beginRemoveRows(QModelIndex(), removed_index, removed_index);
m_resources.erase(removed_it);
endRemoveRows();
}
}
// add new resources to the end
{
QSet<QString> added_set = new_set;
added_set.subtract(current_set);
// When you have a Qt build with assertions turned on, proceeding here will abort the application
if (added_set.size() > 0) {
beginInsertRows(QModelIndex(), static_cast<int>(m_resources.size()),
static_cast<int>(m_resources.size() + added_set.size() - 1));
for (auto& added : added_set) {
auto res = new_resources[added];
m_resources.append(res);
resolveResource(m_resources.last());
}
endInsertRows();
}
}
// update index
{
m_resources_index.clear();
int idx = 0;
for (auto const& mod : qAsConst(m_resources)) {
m_resources_index[mod->internal_id()] = idx;
idx++;
}
}
}
Resource::Ptr ResourceFolderModel::find(QString id)
{
auto iter =
std::find_if(m_resources.constBegin(), m_resources.constEnd(), [&](Resource::Ptr const& r) { return r->internal_id() == id; });
if (iter == m_resources.constEnd())
return nullptr;
return *iter;
}
QList<Resource*> ResourceFolderModel::allResources()
{
QList<Resource*> result;
result.reserve(m_resources.size());
for (const Resource ::Ptr& resource : m_resources)
result.append((resource.get()));
return result;
}
QList<Resource*> ResourceFolderModel::selectedResources(const QModelIndexList& indexes)
{
QList<Resource*> result;
for (const QModelIndex& index : indexes) {
if (index.column() != 0)
continue;
result.append(&at(index.row()));
}
return result;
}

View file

@ -19,38 +19,6 @@
class QSortFilterProxyModel;
/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
#define RESOURCE_HELPERS(T) \
[[nodiscard]] T& at(int index) \
{ \
return *static_cast<T*>(m_resources[index].get()); \
} \
[[nodiscard]] const T& at(int index) const \
{ \
return *static_cast<const T*>(m_resources.at(index).get()); \
} \
QList<T*> selected##T##s(const QModelIndexList& indexes) \
{ \
QList<T*> result; \
for (const QModelIndex& index : indexes) { \
if (index.column() != 0) \
continue; \
\
result.append(&at(index.row())); \
} \
return result; \
} \
QList<T*> all##T##s() \
{ \
QList<T*> result; \
result.reserve(m_resources.size()); \
\
for (const Resource::Ptr& resource : m_resources) \
result.append(static_cast<T*>(resource.get())); \
\
return result; \
}
/** A basic model for external resources.
*
* This model manages a list of resources. As such, external users of such resources do not own them,
@ -61,7 +29,7 @@ class QSortFilterProxyModel;
class ResourceFolderModel : public QAbstractListModel {
Q_OBJECT
public:
ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true);
~ResourceFolderModel() override;
virtual QString id() const { return "resource"; }
@ -81,10 +49,8 @@ class ResourceFolderModel : public QAbstractListModel {
bool stopWatching(const QStringList& paths);
/* Helper methods for subclasses, using a predetermined list of paths. */
virtual bool startWatching() { return startWatching({ indexDir().absolutePath(), m_dir.absolutePath() }); }
virtual bool stopWatching() { return stopWatching({ indexDir().absolutePath(), m_dir.absolutePath() }); }
QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; }
virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); }
virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); }
/** Given a path in the system, install that resource, moving it to its place in the
* instance file hierarchy.
@ -93,15 +59,12 @@ class ResourceFolderModel : public QAbstractListModel {
*/
virtual bool installResource(QString path);
virtual bool installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers);
/** Uninstall (i.e. remove all data about it) a resource, given its file name.
*
* Returns whether the removal was successful.
*/
virtual bool uninstallResource(QString file_name, bool preserve_metadata = false);
virtual bool uninstallResource(QString file_name);
virtual bool deleteResources(const QModelIndexList&);
virtual void deleteMetadata(const QModelIndexList&);
/** Applies the given 'action' to the resources in 'indexes'.
*
@ -117,13 +80,9 @@ class ResourceFolderModel : public QAbstractListModel {
[[nodiscard]] qsizetype size() const { return m_resources.size(); }
[[nodiscard]] bool empty() const { return size() == 0; }
[[nodiscard]] Resource& at(int index) { return *m_resources[index].get(); }
[[nodiscard]] const Resource& at(int index) const { return *m_resources.at(index).get(); }
QList<Resource*> selectedResources(const QModelIndexList& indexes);
QList<Resource*> allResources();
[[nodiscard]] Resource::Ptr find(QString id);
[[nodiscard]] Resource& at(int index) { return *m_resources.at(index); }
[[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); }
[[nodiscard]] QList<Resource::Ptr> const& all() const { return m_resources; }
[[nodiscard]] QDir const& dir() const { return m_dir; }
@ -137,8 +96,7 @@ class ResourceFolderModel : public QAbstractListModel {
/* Qt behavior */
/* Basic columns */
enum Columns { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
enum Columns { ActiveColumn = 0, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS };
QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; }
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
@ -195,9 +153,7 @@ class ResourceFolderModel : public QAbstractListModel {
* This Task is normally executed when opening a page, so it shouldn't contain much heavy work.
* If such work is needed, try using it in the Task create by createParseTask() instead!
*/
[[nodiscard]] Task* createUpdateTask();
[[nodiscard]] virtual Resource* createResource(const QFileInfo& info) { return new Resource(info); }
[[nodiscard]] virtual Task* createUpdateTask();
/** This creates a new parse task to be executed by onUpdateSucceeded().
*
@ -211,8 +167,10 @@ class ResourceFolderModel : public QAbstractListModel {
* It uses set operations to find differences between the current state and the updated state,
* to act only on those disparities.
*
* The implementation is at the end of this header.
*/
void applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, Resource::Ptr>& new_resources);
template <typename T>
void applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources);
protected slots:
void directoryChanged(QString);
@ -237,22 +195,19 @@ class ResourceFolderModel : public QAbstractListModel {
protected:
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
// As such, the order in with they appear is very important!
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE };
QStringList m_column_names = { "Enable", "Name", "Last Modified", "Provider", "Size" };
QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") };
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::SIZE };
QStringList m_column_names = { "Enable", "Name", "Last Modified", "Size" };
QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Size") };
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
QHeaderView::Interactive, QHeaderView::Interactive };
QList<bool> m_columnsHideable = { false, false, true, true, true };
QList<bool> m_columnsHiddenByDefault = { false, false, false, false, true };
QHeaderView::Interactive };
QList<bool> m_columnsHideable = { false, false, true, true };
QList<bool> m_columnsHiddenByDefault = { false, false, false, false };
QDir m_dir;
BaseInstance* m_instance;
QFileSystemWatcher m_watcher;
bool m_is_watching = false;
bool m_is_indexed;
bool m_first_folder_load = true;
Task::Ptr m_current_update_task = nullptr;
bool m_scheduled_update = false;
@ -265,3 +220,133 @@ class ResourceFolderModel : public QAbstractListModel {
QMap<int, Task::Ptr> m_active_parse_tasks;
std::atomic<int> m_next_resolution_ticket = 0;
};
/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
#define RESOURCE_HELPERS(T) \
[[nodiscard]] T* operator[](int index) \
{ \
return static_cast<T*>(m_resources[index].get()); \
} \
[[nodiscard]] T* at(int index) \
{ \
return static_cast<T*>(m_resources[index].get()); \
} \
[[nodiscard]] const T* at(int index) const \
{ \
return static_cast<const T*>(m_resources.at(index).get()); \
} \
[[nodiscard]] T* first() \
{ \
return static_cast<T*>(m_resources.first().get()); \
} \
[[nodiscard]] T* last() \
{ \
return static_cast<T*>(m_resources.last().get()); \
} \
[[nodiscard]] T* find(QString id) \
{ \
auto iter = std::find_if(m_resources.constBegin(), m_resources.constEnd(), \
[&](Resource::Ptr const& r) { return r->internal_id() == id; }); \
if (iter == m_resources.constEnd()) \
return nullptr; \
return static_cast<T*>((*iter).get()); \
}
/* Template definition to avoid some code duplication */
template <typename T>
void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources)
{
// see if the kept resources changed in some way
{
QSet<QString> kept_set = current_set;
kept_set.intersect(new_set);
for (auto const& kept : kept_set) {
auto row_it = m_resources_index.constFind(kept);
Q_ASSERT(row_it != m_resources_index.constEnd());
auto row = row_it.value();
auto& new_resource = new_resources[kept];
auto const& current_resource = m_resources.at(row);
if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
// no significant change, ignore...
continue;
}
// If the resource is resolving, but something about it changed, we don't want to
// continue the resolving.
if (current_resource->isResolving()) {
auto ticket = current_resource->resolutionTicket();
if (m_active_parse_tasks.contains(ticket)) {
auto task = (*m_active_parse_tasks.find(ticket)).get();
task->abort();
}
}
m_resources[row].reset(new_resource);
resolveResource(m_resources.at(row));
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
}
}
// remove resources no longer present
{
QSet<QString> removed_set = current_set;
removed_set.subtract(new_set);
QList<int> removed_rows;
for (auto& removed : removed_set)
removed_rows.append(m_resources_index[removed]);
std::sort(removed_rows.begin(), removed_rows.end(), std::greater<int>());
for (auto& removed_index : removed_rows) {
auto removed_it = m_resources.begin() + removed_index;
Q_ASSERT(removed_it != m_resources.end());
if ((*removed_it)->isResolving()) {
auto ticket = (*removed_it)->resolutionTicket();
if (m_active_parse_tasks.contains(ticket)) {
auto task = (*m_active_parse_tasks.find(ticket)).get();
task->abort();
}
}
beginRemoveRows(QModelIndex(), removed_index, removed_index);
m_resources.erase(removed_it);
endRemoveRows();
}
}
// add new resources to the end
{
QSet<QString> added_set = new_set;
added_set.subtract(current_set);
// When you have a Qt build with assertions turned on, proceeding here will abort the application
if (added_set.size() > 0) {
beginInsertRows(QModelIndex(), static_cast<int>(m_resources.size()),
static_cast<int>(m_resources.size() + added_set.size() - 1));
for (auto& added : added_set) {
auto res = new_resources[added];
m_resources.append(res);
resolveResource(m_resources.last());
}
endInsertRows();
}
}
// update index
{
m_resources_index.clear();
int idx = 0;
for (auto const& mod : qAsConst(m_resources)) {
m_resources_index[mod->internal_id()] = idx;
idx++;
}
}
}

View file

@ -44,19 +44,19 @@
#include "Application.h"
#include "Version.h"
#include "minecraft/mod/Resource.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(dir, instance, is_indexed, create_dir, parent)
ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance)
{
m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size" });
m_column_names_translated =
QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider"), tr("Size") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT,
SortType::DATE, SortType::PROVIDER, SortType::SIZE };
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Size" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Size") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE, SortType::SIZE };
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch,
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true, true, true };
m_columnsHideable = { false, true, false, true, true, true };
m_columnsHiddenByDefault = { false, false, false, false, false, false };
}
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
@ -73,12 +73,12 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
case NameColumn:
return m_resources[row]->name();
case PackFormatColumn: {
auto& resource = at(row);
auto pack_format = resource.packFormat();
auto resource = at(row);
auto pack_format = resource->packFormat();
if (pack_format == 0)
return tr("Unrecognized");
auto version_bounds = resource.compatibleVersions();
auto version_bounds = resource->compatibleVersions();
if (version_bounds.first.toString().isEmpty())
return QString::number(pack_format);
@ -87,18 +87,17 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
}
case DateColumn:
return m_resources[row]->dateTimeChanged();
case ProviderColumn:
return m_resources[row]->provider();
case SizeColumn:
return m_resources[row]->sizeStr();
default:
return {};
}
case Qt::DecorationRole: {
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {};
}
@ -108,14 +107,14 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
}
if (column == NameColumn) {
if (at(row).isSymLinkUnder(instDirPath())) {
if (at(row)->isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
"\nCanonical Path: %1")
.arg(at(row).fileinfo().canonicalFilePath());
.arg(at(row)->fileinfo().canonicalFilePath());
;
}
if (at(row).isMoreThanOneHardLink()) {
if (at(row)->isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
}
@ -130,7 +129,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
case Qt::CheckStateRole:
switch (column) {
case ActiveColumn:
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
default:
return {};
}
@ -149,7 +148,6 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
case PackFormatColumn:
case DateColumn:
case ImageColumn:
case ProviderColumn:
case SizeColumn:
return columnNames().at(section);
default:
@ -159,7 +157,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
case Qt::ToolTipRole:
switch (section) {
case ActiveColumn:
return tr("Is the resource pack enabled?");
return tr("Is the resource pack enabled? (Only valid for ZIPs)");
case NameColumn:
return tr("The name of the resource pack.");
case PackFormatColumn:
@ -167,8 +165,6 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
case DateColumn:
return tr("The date and time this resource pack was last changed (or added).");
case ProviderColumn:
return tr("The source provider of the resource pack.");
case SizeColumn:
return tr("The size of the resource pack.");
default:
@ -189,6 +185,11 @@ int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
return parent.isValid() ? 0 : NUM_COLUMNS;
}
Task* ResourcePackFolderModel::createUpdateTask()
{
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<ResourcePack>(entry); });
}
Task* ResourcePackFolderModel::createParseTask(Resource& resource)
{
return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast<ResourcePack&>(resource));

View file

@ -7,18 +7,18 @@
class ResourcePackFolderModel : public ResourceFolderModel {
Q_OBJECT
public:
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, SizeColumn, NUM_COLUMNS };
explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
explicit ResourcePackFolderModel(const QString& dir, BaseInstance* instance);
QString id() const override { return "resourcepacks"; }
virtual QString id() const override { return "resourcepacks"; }
[[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
[[nodiscard]] int columnCount(const QModelIndex& parent) const override;
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new ResourcePack(file); }
[[nodiscard]] Task* createUpdateTask() override;
[[nodiscard]] Task* createParseTask(Resource&) override;
RESOURCE_HELPERS(ResourcePack)

View file

@ -2,24 +2,24 @@
#include "ResourceFolderModel.h"
#include "minecraft/mod/ShaderPack.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalShaderPackParseTask.h"
class ShaderPackFolderModel : public ResourceFolderModel {
Q_OBJECT
public:
explicit ShaderPackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr)
: ResourceFolderModel(dir, instance, is_indexed, create_dir, parent)
{}
explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) {}
virtual QString id() const override { return "shaderpacks"; }
[[nodiscard]] Resource* createResource(const QFileInfo& info) override { return new ShaderPack(info); }
[[nodiscard]] Task* createUpdateTask() override
{
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<ShaderPack>(entry); });
}
[[nodiscard]] Task* createParseTask(Resource& resource) override
{
return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast<ShaderPack&>(resource));
}
RESOURCE_HELPERS(ShaderPack);
};

View file

@ -39,18 +39,22 @@
#include "TexturePackFolderModel.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
#include "minecraft/mod/tasks/ResourceFolderLoadTask.h"
TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance)
{
m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE };
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true, true };
m_columnsHiddenByDefault = { false, false, false, false, false, true };
m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Size" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Size") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::SIZE };
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true };
}
Task* TexturePackFolderModel::createUpdateTask()
{
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<TexturePack>(entry); });
}
Task* TexturePackFolderModel::createParseTask(Resource& resource)
@ -73,8 +77,6 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
return m_resources[row]->name();
case DateColumn:
return m_resources[row]->dateTimeChanged();
case ProviderColumn:
return m_resources[row]->provider();
case SizeColumn:
return m_resources[row]->sizeStr();
default:
@ -82,14 +84,14 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
}
case Qt::ToolTipRole:
if (column == NameColumn) {
if (at(row).isSymLinkUnder(instDirPath())) {
if (at(row)->isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
"\nCanonical Path: %1")
.arg(at(row).fileinfo().canonicalFilePath());
.arg(at(row)->fileinfo().canonicalFilePath());
;
}
if (at(row).isMoreThanOneHardLink()) {
if (at(row)->isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
}
@ -97,10 +99,10 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
return m_resources[row]->internal_id();
case Qt::DecorationRole: {
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {};
}
@ -128,7 +130,6 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
case NameColumn:
case DateColumn:
case ImageColumn:
case ProviderColumn:
case SizeColumn:
return columnNames().at(section);
default:
@ -137,13 +138,14 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
case Qt::ToolTipRole: {
switch (section) {
case ActiveColumn:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("Is the texture pack enabled?");
case NameColumn:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("The name of the texture pack.");
case DateColumn:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("The date and time this texture pack was last changed (or added).");
case ProviderColumn:
return tr("The source provider of the texture pack.");
case SizeColumn:
return tr("The size of the texture pack.");
default:

View file

@ -44,9 +44,9 @@ class TexturePackFolderModel : public ResourceFolderModel {
Q_OBJECT
public:
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS };
explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
explicit TexturePackFolderModel(const QString& dir, std::shared_ptr<const BaseInstance> instance);
virtual QString id() const override { return "texturepacks"; }
@ -55,7 +55,8 @@ class TexturePackFolderModel : public ResourceFolderModel {
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
[[nodiscard]] int columnCount(const QModelIndex& parent) const override;
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new TexturePack(file); }
explicit TexturePackFolderModel(const QString& dir, BaseInstance* instance);
[[nodiscard]] Task* createUpdateTask() override;
[[nodiscard]] Task* createParseTask(Resource&) override;
RESOURCE_HELPERS(TexturePack)

View file

@ -0,0 +1,81 @@
#pragma once
#include <QDir>
#include <QMap>
#include <QObject>
#include <QThread>
#include <memory>
#include "Application.h"
#include "FileSystem.h"
#include "minecraft/mod/Resource.h"
#include "tasks/Task.h"
/** Very simple task that just loads a folder's contents directly.
*/
class BasicFolderLoadTask : public Task {
Q_OBJECT
public:
struct Result {
QMap<QString, Resource::Ptr> resources;
};
using ResultPtr = std::shared_ptr<Result>;
[[nodiscard]] ResultPtr result() const { return m_result; }
public:
BasicFolderLoadTask(QDir dir) : Task(false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread())
{
m_create_func = [](QFileInfo const& entry) -> Resource::Ptr { return makeShared<Resource>(entry); };
}
BasicFolderLoadTask(QDir dir, std::function<Resource::Ptr(QFileInfo const&)> create_function)
: Task(false), m_dir(dir), m_result(new Result), m_create_func(std::move(create_function)), m_thread_to_spawn_into(thread())
{}
[[nodiscard]] bool canAbort() const override { return true; }
bool abort() override
{
m_aborted.store(true);
return true;
}
void executeTask() override
{
if (thread() != m_thread_to_spawn_into)
connect(this, &Task::finished, this->thread(), &QThread::quit);
m_dir.refresh();
for (auto entry : m_dir.entryInfoList()) {
auto filePath = entry.absoluteFilePath();
if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) {
continue;
}
auto newFilePath = FS::getUniqueResourceName(filePath);
if (newFilePath != filePath) {
FS::move(filePath, newFilePath);
entry = QFileInfo(newFilePath);
}
auto resource = m_create_func(entry);
resource->moveToThread(m_thread_to_spawn_into);
m_result->resources.insert(resource->internal_id(), resource);
}
if (m_aborted)
emit finished();
else
emitSucceeded();
}
private:
QDir m_dir;
ResultPtr m_result;
std::atomic<bool> m_aborted = false;
std::function<Resource::Ptr(QFileInfo const&)> m_create_func;
/** This is the thread in which we should put new mod objects */
QThread* m_thread_to_spawn_into;
};

View file

@ -27,7 +27,7 @@ namespace ModUtils {
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
ModDetails ReadMCModInfo(QByteArray contents)
{
auto getInfoFromArray = [](QJsonArray arr) -> ModDetails {
auto getInfoFromArray = [&](QJsonArray arr) -> ModDetails {
if (!arr.at(0).isObject()) {
return {};
}

View file

@ -47,6 +47,11 @@ class LocalModParseTask : public Task {
[[nodiscard]] int token() const { return m_token; }
private:
void processAsZip();
void processAsFolder();
void processAsLitemod();
private:
int m_token;
ResourceType m_type;

View file

@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "LocalResourceUpdateTask.h"
#include "LocalModUpdateTask.h"
#include "FileSystem.h"
#include "minecraft/mod/MetadataHandler.h"
@ -26,12 +26,12 @@
#include <windows.h>
#endif
LocalResourceUpdateTask::LocalResourceUpdateTask(QDir index_dir, ModPlatform::IndexedPack& project, ModPlatform::IndexedVersion& version)
: m_index_dir(index_dir), m_project(project), m_version(version)
LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version)
: m_index_dir(index_dir), m_mod(mod), m_mod_version(mod_version)
{
// Ensure a '.index' folder exists in the mods folder, and create it if it does not
if (!FS::ensureFolderPathExists(index_dir.path())) {
emitFailed(QString("Unable to create index directory at %1!").arg(index_dir.absolutePath()));
emitFailed(QString("Unable to create index for mod %1!").arg(m_mod.name));
}
#ifdef Q_OS_WIN32
@ -39,28 +39,28 @@ LocalResourceUpdateTask::LocalResourceUpdateTask(QDir index_dir, ModPlatform::In
#endif
}
void LocalResourceUpdateTask::executeTask()
void LocalModUpdateTask::executeTask()
{
setStatus(tr("Updating index for resource:\n%1").arg(m_project.name));
setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name));
auto old_metadata = Metadata::get(m_index_dir, m_project.addonId);
auto old_metadata = Metadata::get(m_index_dir, m_mod.addonId);
if (old_metadata.isValid()) {
emit hasOldResource(old_metadata.name, old_metadata.filename);
if (m_project.slug.isEmpty())
m_project.slug = old_metadata.slug;
emit hasOldMod(old_metadata.name, old_metadata.filename);
if (m_mod.slug.isEmpty())
m_mod.slug = old_metadata.slug;
}
auto pw_mod = Metadata::create(m_index_dir, m_project, m_version);
auto pw_mod = Metadata::create(m_index_dir, m_mod, m_mod_version);
if (pw_mod.isValid()) {
Metadata::update(m_index_dir, pw_mod);
emitSucceeded();
} else {
qCritical() << "Tried to update an invalid resource!";
qCritical() << "Tried to update an invalid mod!";
emitFailed(tr("Invalid metadata"));
}
}
auto LocalResourceUpdateTask::abort() -> bool
auto LocalModUpdateTask::abort() -> bool
{
emitAborted();
return true;

View file

@ -23,12 +23,12 @@
#include "modplatform/ModIndex.h"
#include "tasks/Task.h"
class LocalResourceUpdateTask : public Task {
class LocalModUpdateTask : public Task {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<LocalResourceUpdateTask>;
using Ptr = shared_qobject_ptr<LocalModUpdateTask>;
explicit LocalResourceUpdateTask(QDir index_dir, ModPlatform::IndexedPack& project, ModPlatform::IndexedVersion& version);
explicit LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version);
auto canAbort() const -> bool override { return true; }
auto abort() -> bool override;
@ -38,10 +38,10 @@ class LocalResourceUpdateTask : public Task {
void executeTask() override;
signals:
void hasOldResource(QString name, QString filename);
void hasOldMod(QString name, QString filename);
private:
QDir m_index_dir;
ModPlatform::IndexedPack m_project;
ModPlatform::IndexedVersion m_version;
ModPlatform::IndexedPack m_mod;
ModPlatform::IndexedVersion m_mod_version;
};

View file

@ -34,7 +34,7 @@
* limitations under the License.
*/
#include "ResourceFolderLoadTask.h"
#include "ModFolderLoadTask.h"
#include "Application.h"
#include "FileSystem.h"
@ -42,22 +42,17 @@
#include <QThread>
ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resource_dir,
const QDir& index_dir,
bool is_indexed,
bool clean_orphan,
std::function<Resource*(const QFileInfo&)> create_function)
ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan)
: Task(false)
, m_resource_dir(resource_dir)
, m_mods_dir(mods_dir)
, m_index_dir(index_dir)
, m_is_indexed(is_indexed)
, m_clean_orphan(clean_orphan)
, m_create_func(create_function)
, m_result(new Result())
, m_thread_to_spawn_into(thread())
{}
void ResourceFolderLoadTask::executeTask()
void ModFolderLoadTask::executeTask()
{
if (thread() != m_thread_to_spawn_into)
connect(this, &Task::finished, this->thread(), &QThread::quit);
@ -68,8 +63,8 @@ void ResourceFolderLoadTask::executeTask()
}
// Read JAR files that don't have metadata
m_resource_dir.refresh();
for (auto entry : m_resource_dir.entryInfoList()) {
m_mods_dir.refresh();
for (auto entry : m_mods_dir.entryInfoList()) {
auto filePath = entry.absoluteFilePath();
if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) {
continue;
@ -79,33 +74,32 @@ void ResourceFolderLoadTask::executeTask()
FS::move(filePath, newFilePath);
entry = QFileInfo(newFilePath);
}
Mod* mod(new Mod(entry));
Resource* resource = m_create_func(entry);
if (resource->enabled()) {
if (m_result->resources.contains(resource->internal_id())) {
m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED);
if (mod->enabled()) {
if (m_result->mods.contains(mod->internal_id())) {
m_result->mods[mod->internal_id()]->setStatus(ModStatus::Installed);
// Delete the object we just created, since a valid one is already in the mods list.
delete resource;
delete mod;
} else {
m_result->resources[resource->internal_id()].reset(resource);
m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA);
m_result->mods[mod->internal_id()].reset(std::move(mod));
m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata);
}
} else {
QString chopped_id = resource->internal_id().chopped(9);
if (m_result->resources.contains(chopped_id)) {
m_result->resources[resource->internal_id()].reset(resource);
QString chopped_id = mod->internal_id().chopped(9);
if (m_result->mods.contains(chopped_id)) {
m_result->mods[mod->internal_id()].reset(std::move(mod));
auto metadata = m_result->resources[chopped_id]->metadata();
auto metadata = m_result->mods[chopped_id]->metadata();
if (metadata) {
resource->setMetadata(*metadata);
mod->setMetadata(*metadata);
m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED);
m_result->resources.remove(chopped_id);
m_result->mods[mod->internal_id()]->setStatus(ModStatus::Installed);
m_result->mods.remove(chopped_id);
}
} else {
m_result->resources[resource->internal_id()].reset(resource);
m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA);
m_result->mods[mod->internal_id()].reset(std::move(mod));
m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata);
}
}
}
@ -113,17 +107,17 @@ void ResourceFolderLoadTask::executeTask()
// Remove orphan metadata to prevent issues
// See https://github.com/PolyMC/PolyMC/issues/996
if (m_clean_orphan) {
QMutableMapIterator iter(m_result->resources);
QMutableMapIterator iter(m_result->mods);
while (iter.hasNext()) {
auto resource = iter.next().value();
if (resource->status() == ResourceStatus::NOT_INSTALLED) {
resource->destroy(m_index_dir, false, false);
auto mod = iter.next().value();
if (mod->status() == ModStatus::NotInstalled) {
mod->destroy(m_index_dir, false, false);
iter.remove();
}
}
}
for (auto mod : m_result->resources)
for (auto mod : m_result->mods)
mod->moveToThread(m_thread_to_spawn_into);
if (m_aborted)
@ -132,18 +126,18 @@ void ResourceFolderLoadTask::executeTask()
emitSucceeded();
}
void ResourceFolderLoadTask::getFromMetadata()
void ModFolderLoadTask::getFromMetadata()
{
m_index_dir.refresh();
for (auto entry : m_index_dir.entryList(QDir::Files)) {
auto metadata = Metadata::get(m_index_dir, entry);
if (!metadata.isValid())
if (!metadata.isValid()) {
continue;
}
auto* resource = m_create_func(QFileInfo(m_resource_dir.filePath(metadata.filename)));
resource->setMetadata(metadata);
resource->setStatus(ResourceStatus::NOT_INSTALLED);
m_result->resources[resource->internal_id()].reset(resource);
auto* mod = new Mod(m_mods_dir, metadata);
mod->setStatus(ModStatus::NotInstalled);
m_result->mods[mod->internal_id()].reset(std::move(mod));
}
}

View file

@ -44,21 +44,17 @@
#include "minecraft/mod/Mod.h"
#include "tasks/Task.h"
class ResourceFolderLoadTask : public Task {
class ModFolderLoadTask : public Task {
Q_OBJECT
public:
struct Result {
QMap<QString, Resource::Ptr> resources;
QMap<QString, Mod::Ptr> mods;
};
using ResultPtr = std::shared_ptr<Result>;
ResultPtr result() const { return m_result; }
public:
ResourceFolderLoadTask(const QDir& resource_dir,
const QDir& index_dir,
bool is_indexed,
bool clean_orphan,
std::function<Resource*(const QFileInfo&)> create_function);
ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan = false);
[[nodiscard]] bool canAbort() const override { return true; }
bool abort() override
@ -73,10 +69,9 @@ class ResourceFolderLoadTask : public Task {
void getFromMetadata();
private:
QDir m_resource_dir, m_index_dir;
QDir m_mods_dir, m_index_dir;
bool m_is_indexed;
bool m_clean_orphan;
std::function<Resource*(QFileInfo const&)> m_create_func;
ResultPtr m_result;
std::atomic<bool> m_aborted = false;

View file

@ -25,7 +25,7 @@ void LibrariesTask::executeTask()
auto metacache = APPLICATION->metacache();
auto processArtifactPool = [this, inst, metacache](const QList<LibraryPtr>& pool, QStringList& errors, const QString& localPath) {
auto processArtifactPool = [&](const QList<LibraryPtr>& pool, QStringList& errors, const QString& localPath) {
for (auto lib : pool) {
if (!lib) {
emitFailed(tr("Null jar is specified in the metadata, aborting."));

View file

@ -3,7 +3,6 @@
#include "minecraft/mod/Mod.h"
#include "minecraft/mod/tasks/GetModDependenciesTask.h"
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "tasks/Task.h"
class ResourceDownloadTask;
@ -13,18 +12,13 @@ class CheckUpdateTask : public Task {
Q_OBJECT
public:
CheckUpdateTask(QList<Resource*>& resources,
CheckUpdateTask(QList<Mod*>& mods,
std::list<Version>& mcVersions,
QList<ModPlatform::ModLoaderType> loadersList,
std::shared_ptr<ResourceFolderModel> resourceModel)
: Task()
, m_resources(resources)
, m_game_versions(mcVersions)
, m_loaders_list(std::move(loadersList))
, m_resource_model(std::move(resourceModel))
{}
std::shared_ptr<ModFolderModel> mods_folder)
: Task(), m_mods(mods), m_game_versions(mcVersions), m_loaders_list(loadersList), m_mods_folder(mods_folder) {};
struct Update {
struct UpdatableMod {
QString name;
QString old_hash;
QString old_version;
@ -36,28 +30,28 @@ class CheckUpdateTask : public Task {
bool enabled = true;
public:
Update(QString name,
QString old_h,
QString old_v,
QString new_v,
std::optional<ModPlatform::IndexedVersionType> new_v_type,
QString changelog,
ModPlatform::ResourceProvider p,
shared_qobject_ptr<ResourceDownloadTask> t,
bool enabled = true)
: name(std::move(name))
, old_hash(std::move(old_h))
, old_version(std::move(old_v))
, new_version(std::move(new_v))
, new_version_type(std::move(new_v_type))
, changelog(std::move(changelog))
UpdatableMod(QString name,
QString old_h,
QString old_v,
QString new_v,
std::optional<ModPlatform::IndexedVersionType> new_v_type,
QString changelog,
ModPlatform::ResourceProvider p,
shared_qobject_ptr<ResourceDownloadTask> t,
bool enabled = true)
: name(name)
, old_hash(old_h)
, old_version(old_v)
, new_version(new_v)
, new_version_type(new_v_type)
, changelog(changelog)
, provider(p)
, download(std::move(t))
, download(t)
, enabled(enabled)
{}
};
auto getUpdates() -> std::vector<Update>&& { return std::move(m_updates); }
auto getUpdatable() -> std::vector<UpdatableMod>&& { return std::move(m_updatable); }
auto getDependencies() -> QList<std::shared_ptr<GetModDependenciesTask::PackDependency>>&& { return std::move(m_deps); }
public slots:
@ -67,14 +61,14 @@ class CheckUpdateTask : public Task {
void executeTask() override = 0;
signals:
void checkFailed(Resource* failed, QString reason, QUrl recover_url = {});
void checkFailed(Mod* failed, QString reason, QUrl recover_url = {});
protected:
QList<Resource*>& m_resources;
QList<Mod*>& m_mods;
std::list<Version>& m_game_versions;
QList<ModPlatform::ModLoaderType> m_loaders_list;
std::shared_ptr<ResourceFolderModel> m_resource_model;
std::shared_ptr<ModFolderModel> m_mods_folder;
std::vector<Update> m_updates;
std::vector<UpdatableMod> m_updatable;
QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> m_deps;
};

View file

@ -7,7 +7,7 @@
#include "Json.h"
#include "minecraft/mod/Mod.h"
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
@ -18,57 +18,56 @@
static ModrinthAPI modrinth_api;
static FlameAPI flame_api;
EnsureMetadataTask::EnsureMetadataTask(Resource* resource, QDir dir, ModPlatform::ResourceProvider prov)
EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::ResourceProvider prov)
: Task(), m_index_dir(dir), m_provider(prov), m_hashingTask(nullptr), m_current_task(nullptr)
{
auto hashTask = createNewHash(resource);
auto hashTask = createNewHash(mod);
if (!hashTask)
return;
connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_resources.insert(hash, resource); });
connect(hashTask.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); });
connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); });
connect(hashTask.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); });
m_hashingTask = hashTask;
}
EnsureMetadataTask::EnsureMetadataTask(QList<Resource*>& resources, QDir dir, ModPlatform::ResourceProvider prov)
EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::ResourceProvider prov)
: Task(), m_index_dir(dir), m_provider(prov), m_current_task(nullptr)
{
auto hashTask = makeShared<ConcurrentTask>("MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
m_hashingTask = hashTask;
for (auto* resource : resources) {
auto hash_task = createNewHash(resource);
for (auto* mod : mods) {
auto hash_task = createNewHash(mod);
if (!hash_task)
continue;
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_resources.insert(hash, resource); });
connect(hash_task.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); });
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); });
connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); });
hashTask->addTask(hash_task);
}
}
EnsureMetadataTask::EnsureMetadataTask(QHash<QString, Resource*>& resources, QDir dir, ModPlatform::ResourceProvider prov)
: Task(), m_resources(resources), m_index_dir(dir), m_provider(prov), m_current_task(nullptr)
EnsureMetadataTask::EnsureMetadataTask(QHash<QString, Mod*>& mods, QDir dir, ModPlatform::ResourceProvider prov)
: Task(), m_mods(mods), m_index_dir(dir), m_provider(prov), m_current_task(nullptr)
{}
Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Resource* resource)
Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Mod* mod)
{
if (!resource || !resource->valid() || resource->type() == ResourceType::FOLDER)
if (!mod || !mod->valid() || mod->type() == ResourceType::FOLDER)
return nullptr;
return Hashing::createHasher(resource->fileinfo().absoluteFilePath(), m_provider);
return Hashing::createHasher(mod->fileinfo().absoluteFilePath(), m_provider);
}
QString EnsureMetadataTask::getExistingHash(Resource* resource)
QString EnsureMetadataTask::getExistingHash(Mod* mod)
{
// Check for already computed hashes
// (linear on the number of mods vs. linear on the size of the mod's JAR)
auto it = m_resources.keyValueBegin();
while (it != m_resources.keyValueEnd()) {
if ((*it).second == resource)
auto it = m_mods.keyValueBegin();
while (it != m_mods.keyValueEnd()) {
if ((*it).second == mod)
break;
it++;
}
// We already have the hash computed
if (it != m_resources.keyValueEnd()) {
if (it != m_mods.keyValueEnd()) {
return (*it).first;
}
@ -88,25 +87,25 @@ bool EnsureMetadataTask::abort()
void EnsureMetadataTask::executeTask()
{
setStatus(tr("Checking if resources have metadata..."));
setStatus(tr("Checking if mods have metadata..."));
for (auto* resource : m_resources) {
if (!resource->valid()) {
qDebug() << "Resource" << resource->name() << "is invalid!";
emitFail(resource);
for (auto* mod : m_mods) {
if (!mod->valid()) {
qDebug() << "Mod" << mod->name() << "is invalid!";
emitFail(mod);
continue;
}
// They already have the right metadata :o
if (resource->status() != ResourceStatus::NO_METADATA && resource->metadata() && resource->metadata()->provider == m_provider) {
qDebug() << "Resource" << resource->name() << "already has metadata!";
emitReady(resource);
if (mod->status() != ModStatus::NoMetadata && mod->metadata() && mod->metadata()->provider == m_provider) {
qDebug() << "Mod" << mod->name() << "already has metadata!";
emitReady(mod);
continue;
}
// Folders don't have metadata
if (resource->type() == ResourceType::FOLDER) {
emitReady(resource);
if (mod->type() == ResourceType::FOLDER) {
emitReady(mod);
}
}
@ -122,9 +121,9 @@ void EnsureMetadataTask::executeTask()
}
auto invalidade_leftover = [this] {
for (auto resource = m_resources.constBegin(); resource != m_resources.constEnd(); resource++)
emitFail(resource.value(), resource.key(), RemoveFromList::No);
m_resources.clear();
for (auto mod = m_mods.constBegin(); mod != m_mods.constEnd(); mod++)
emitFail(mod.value(), mod.key(), RemoveFromList::No);
m_mods.clear();
emitSucceeded();
};
@ -146,7 +145,7 @@ void EnsureMetadataTask::executeTask()
return;
}
connect(project_task.get(), &Task::finished, this, [this, invalidade_leftover, project_task] {
connect(project_task.get(), &Task::finished, this, [=] {
invalidade_leftover();
project_task->deleteLater();
if (m_current_task)
@ -158,59 +157,59 @@ void EnsureMetadataTask::executeTask()
project_task->start();
});
connect(version_task.get(), &Task::finished, [this, version_task] {
connect(version_task.get(), &Task::finished, [=] {
version_task->deleteLater();
if (m_current_task)
m_current_task.reset();
});
if (m_resources.size() > 1)
if (m_mods.size() > 1)
setStatus(tr("Requesting metadata information from %1...").arg(ModPlatform::ProviderCapabilities::readableName(m_provider)));
else if (!m_resources.empty())
else if (!m_mods.empty())
setStatus(tr("Requesting metadata information from %1 for '%2'...")
.arg(ModPlatform::ProviderCapabilities::readableName(m_provider), m_resources.begin().value()->name()));
.arg(ModPlatform::ProviderCapabilities::readableName(m_provider), m_mods.begin().value()->name()));
m_current_task = version_task;
version_task->start();
}
void EnsureMetadataTask::emitReady(Resource* resource, QString key, RemoveFromList remove)
void EnsureMetadataTask::emitReady(Mod* m, QString key, RemoveFromList remove)
{
if (!resource) {
qCritical() << "Tried to mark a null resource as ready.";
if (!m) {
qCritical() << "Tried to mark a null mod as ready.";
if (!key.isEmpty())
m_resources.remove(key);
m_mods.remove(key);
return;
}
qDebug() << QString("Generated metadata for %1").arg(resource->name());
emit metadataReady(resource);
qDebug() << QString("Generated metadata for %1").arg(m->name());
emit metadataReady(m);
if (remove == RemoveFromList::Yes) {
if (key.isEmpty())
key = getExistingHash(resource);
m_resources.remove(key);
key = getExistingHash(m);
m_mods.remove(key);
}
}
void EnsureMetadataTask::emitFail(Resource* resource, QString key, RemoveFromList remove)
void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove)
{
if (!resource) {
qCritical() << "Tried to mark a null resource as failed.";
if (!m) {
qCritical() << "Tried to mark a null mod as failed.";
if (!key.isEmpty())
m_resources.remove(key);
m_mods.remove(key);
return;
}
qDebug() << QString("Failed to generate metadata for %1").arg(resource->name());
emit metadataFailed(resource);
qDebug() << QString("Failed to generate metadata for %1").arg(m->name());
emit metadataFailed(m);
if (remove == RemoveFromList::Yes) {
if (key.isEmpty())
key = getExistingHash(resource);
m_resources.remove(key);
key = getExistingHash(m);
m_mods.remove(key);
}
}
@ -221,7 +220,7 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
auto hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first();
auto response = std::make_shared<QByteArray>();
auto ver_task = modrinth_api.currentVersions(m_resources.keys(), hash_type, response);
auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response);
// Prevents unfortunate timings when aborting the task
if (!ver_task)
@ -241,20 +240,20 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
try {
auto entries = Json::requireObject(doc);
for (auto& hash : m_resources.keys()) {
auto resource = m_resources.find(hash).value();
for (auto& hash : m_mods.keys()) {
auto mod = m_mods.find(hash).value();
try {
auto entry = Json::requireObject(entries, hash);
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(resource->name()));
qDebug() << "Getting version for" << resource->name() << "from Modrinth";
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name()));
qDebug() << "Getting version for" << mod->name() << "from Modrinth";
m_temp_versions.insert(hash, Modrinth::loadIndexedPackVersion(entry));
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << entries;
emitFail(resource);
emitFail(mod);
}
}
} catch (Json::JsonException& e) {
@ -326,23 +325,23 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
auto hash = addonIds.find(pack.addonId.toString()).value();
auto resource_iter = m_resources.find(hash);
if (resource_iter == m_resources.end()) {
auto mod_iter = m_mods.find(hash);
if (mod_iter == m_mods.end()) {
qWarning() << "Invalid project id from the API response.";
continue;
}
auto* resource = resource_iter.value();
auto* mod = mod_iter.value();
try {
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(resource->name()));
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name()));
modrinthCallback(pack, m_temp_versions.find(hash).value(), resource);
modrinthCallback(pack, m_temp_versions.find(hash).value(), mod);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << entries;
emitFail(resource);
emitFail(mod);
}
}
});
@ -356,7 +355,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
auto response = std::make_shared<QByteArray>();
QList<uint> fingerprints;
for (auto& murmur : m_resources.keys()) {
for (auto& murmur : m_mods.keys()) {
fingerprints.push_back(murmur.toUInt());
}
@ -396,13 +395,13 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
}
auto fingerprint = QString::number(Json::ensureVariant(file_obj, "fileFingerprint").toUInt());
auto resource = m_resources.find(fingerprint);
if (resource == m_resources.end()) {
auto mod = m_mods.find(fingerprint);
if (mod == m_mods.end()) {
qWarning() << "Invalid fingerprint from the API response.";
continue;
}
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg((*resource)->name()));
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg((*mod)->name()));
m_temp_versions.insert(fingerprint, FlameMod::loadIndexedPackVersion(file_obj));
}
@ -419,7 +418,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
Task::Ptr EnsureMetadataTask::flameProjectsTask()
{
QHash<QString, QString> addonIds;
for (auto const& hash : m_resources.keys()) {
for (auto const& hash : m_mods.keys()) {
if (m_temp_versions.contains(hash)) {
auto data = m_temp_versions.find(hash).value();
@ -466,20 +465,20 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
auto id = QString::number(Json::requireInteger(entry_obj, "id"));
auto hash = addonIds.find(id).value();
auto resource = m_resources.find(hash).value();
auto mod = m_mods.find(hash).value();
try {
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name()));
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(mod->name()));
ModPlatform::IndexedPack pack;
FlameMod::loadIndexedPack(pack, entry_obj);
flameCallback(pack, m_temp_versions.find(hash).value(), resource);
flameCallback(pack, m_temp_versions.find(hash).value(), mod);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << entries;
emitFail(resource);
emitFail(mod);
}
}
} catch (Json::JsonException& e) {
@ -491,17 +490,17 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
return proj_task;
}
void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource* resource)
void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod* mod)
{
// Prevent file name mismatch
ver.fileName = resource->fileinfo().fileName();
ver.fileName = mod->fileinfo().fileName();
if (ver.fileName.endsWith(".disabled"))
ver.fileName.chop(9);
QDir tmp_index_dir(m_index_dir);
{
LocalResourceUpdateTask update_metadata(m_index_dir, pack, ver);
LocalModUpdateTask update_metadata(m_index_dir, pack, ver);
QEventLoop loop;
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
@ -515,27 +514,27 @@ void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPla
auto metadata = Metadata::get(tmp_index_dir, pack.slug);
if (!metadata.isValid()) {
qCritical() << "Failed to generate metadata at last step!";
emitFail(resource);
emitFail(mod);
return;
}
resource->setMetadata(metadata);
mod->setMetadata(metadata);
emitReady(resource);
emitReady(mod);
}
void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource* resource)
void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod* mod)
{
try {
// Prevent file name mismatch
ver.fileName = resource->fileinfo().fileName();
ver.fileName = mod->fileinfo().fileName();
if (ver.fileName.endsWith(".disabled"))
ver.fileName.chop(9);
QDir tmp_index_dir(m_index_dir);
{
LocalResourceUpdateTask update_metadata(m_index_dir, pack, ver);
LocalModUpdateTask update_metadata(m_index_dir, pack, ver);
QEventLoop loop;
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
@ -549,16 +548,16 @@ void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatfo
auto metadata = Metadata::get(tmp_index_dir, pack.slug);
if (!metadata.isValid()) {
qCritical() << "Failed to generate metadata at last step!";
emitFail(resource);
emitFail(mod);
return;
}
resource->setMetadata(metadata);
mod->setMetadata(metadata);
emitReady(resource);
emitReady(mod);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
emitFail(resource);
emitFail(mod);
}
}

View file

@ -1,23 +1,22 @@
#pragma once
#include "ModIndex.h"
#include "net/NetJob.h"
#include "modplatform/helpers/HashUtils.h"
#include "minecraft/mod/Resource.h"
#include "tasks/ConcurrentTask.h"
#include <QDir>
class Mod;
class QDir;
class EnsureMetadataTask : public Task {
Q_OBJECT
public:
EnsureMetadataTask(Resource*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
EnsureMetadataTask(QList<Resource*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
EnsureMetadataTask(QHash<QString, Resource*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
EnsureMetadataTask(Mod*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
EnsureMetadataTask(QList<Mod*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
EnsureMetadataTask(QHash<QString, Mod*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
~EnsureMetadataTask() = default;
@ -38,23 +37,23 @@ class EnsureMetadataTask : public Task {
// Helpers
enum class RemoveFromList { Yes, No };
void emitReady(Resource*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
void emitFail(Resource*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
void emitReady(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
void emitFail(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
// Hashes and stuff
auto createNewHash(Resource*) -> Hashing::Hasher::Ptr;
auto getExistingHash(Resource*) -> QString;
auto createNewHash(Mod*) -> Hashing::Hasher::Ptr;
auto getExistingHash(Mod*) -> QString;
private slots:
void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource*);
void flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource*);
void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*);
void flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*);
signals:
void metadataReady(Resource*);
void metadataFailed(Resource*);
void metadataReady(Mod*);
void metadataFailed(Mod*);
private:
QHash<QString, Resource*> m_resources;
QHash<QString, Mod*> m_mods;
QDir m_index_dir;
ModPlatform::ResourceProvider m_provider;

View file

@ -76,7 +76,6 @@ class ResourceAPI {
std::optional<std::list<Version>> versions;
std::optional<QString> side;
std::optional<QStringList> categoryIds;
bool openSource;
};
struct SearchCallbacks {
std::function<void(QJsonDocument&)> on_succeed;
@ -87,7 +86,7 @@ class ResourceAPI {
struct VersionSearchArgs {
ModPlatform::IndexedPack pack;
std::optional<std::list<Version>> mcVersions;
std::optional<std::list<Version> > mcVersions;
std::optional<ModPlatform::ModLoaderTypes> loaders;
VersionSearchArgs(VersionSearchArgs const&) = default;

View file

@ -641,22 +641,22 @@ void PackInstallTask::installConfigs()
jobPtr->addNetAction(dl);
archivePath = entry->getFullPath();
connect(jobPtr.get(), &NetJob::succeeded, this, [this]() {
connect(jobPtr.get(), &NetJob::succeeded, this, [&]() {
abortable = false;
jobPtr.reset();
extractConfigs();
});
connect(jobPtr.get(), &NetJob::failed, [this](QString reason) {
connect(jobPtr.get(), &NetJob::failed, [&](QString reason) {
abortable = false;
jobPtr.reset();
emitFailed(reason);
});
connect(jobPtr.get(), &NetJob::progress, [this](qint64 current, qint64 total) {
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
abortable = true;
setProgress(current, total);
});
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
connect(jobPtr.get(), &NetJob::aborted, [this] {
connect(jobPtr.get(), &NetJob::aborted, [&] {
abortable = false;
jobPtr.reset();
emitAborted();
@ -685,8 +685,8 @@ void PackInstallTask::extractConfigs()
m_extractFuture =
QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/minecraft");
#endif
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, [this]() { downloadMods(); });
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [this]() { emitAborted(); });
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, [&]() { downloadMods(); });
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]() { emitAborted(); });
m_extractFutureWatcher.setFuture(m_extractFuture);
}

View file

@ -87,30 +87,6 @@ void Flame::FileResolvingTask::executeTask()
m_task->start();
}
PackedResourceType getResourceType(int classId)
{
switch (classId) {
case 17: // Worlds
return PackedResourceType::WorldSave;
case 6: // Mods
return PackedResourceType::Mod;
case 12: // Resource Packs
// return PackedResourceType::ResourcePack; // not really a resourcepack
/* fallthrough */
case 4546: // Customization
// return PackedResourceType::ShaderPack; // not really a shaderPack
/* fallthrough */
case 4471: // Modpacks
/* fallthrough */
case 5: // Bukkit Plugins
/* fallthrough */
case 4559: // Addons
/* fallthrough */
default:
return PackedResourceType::UNKNOWN;
}
}
void Flame::FileResolvingTask::netJobFinished()
{
setProgress(1, 3);
@ -256,10 +232,6 @@ void Flame::FileResolvingTask::getFlameProjects()
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(file->version.fileName));
FlameMod::loadIndexedPack(file->pack, entry_obj);
file->resourceType = getResourceType(Json::requireInteger(entry_obj, "classId", "modClassId"));
if (file->resourceType == PackedResourceType::WorldSave) {
file->targetFolder = "saves";
}
}
} catch (Json::JsonException& e) {
qDebug() << e.cause();

View file

@ -121,65 +121,59 @@ ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileI
* */
void FlameCheckUpdate::executeTask()
{
setStatus(tr("Preparing resources for CurseForge..."));
setStatus(tr("Preparing mods for CurseForge..."));
int i = 0;
for (auto* resource : m_resources) {
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(resource->name()));
setProgress(i++, m_resources.size());
for (auto* mod : m_mods) {
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name()));
setProgress(i++, m_mods.size());
auto latest_vers = api.getLatestVersions({ { resource->metadata()->project_id.toString() }, m_game_versions });
auto latest_vers = api.getLatestVersions({ { mod->metadata()->project_id.toString() }, m_game_versions });
// Check if we were aborted while getting the latest version
if (m_was_aborted) {
aborted();
return;
}
auto latest_ver = api.getLatestVersion(latest_vers, m_loaders_list, resource->metadata()->loaders);
auto latest_ver = api.getLatestVersion(latest_vers, m_loaders_list, mod->loaders());
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name()));
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(mod->name()));
if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) {
QString reason;
if (dynamic_cast<Mod*>(resource) != nullptr)
reason =
tr("No valid version found for this resource. It's probably unavailable for the current game "
"version / mod loader.");
else
reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
emit checkFailed(resource, reason);
emit checkFailed(mod, tr("No valid version found for this mod. It's probably unavailable for the current game "
"version / mod loader."));
continue;
}
if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) {
if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != mod->metadata()->file_id) {
auto pack = getProjectInfo(latest_ver.value());
auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver->fileId.toString());
emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."), recover_url);
emit checkFailed(mod, tr("Mod has a new update available, but is not downloadable using CurseForge."), recover_url);
continue;
}
// Fake pack with the necessary info to pass to the download task :)
auto pack = std::make_shared<ModPlatform::IndexedPack>();
pack->name = resource->name();
pack->slug = resource->metadata()->slug;
pack->addonId = resource->metadata()->project_id;
pack->name = mod->name();
pack->slug = mod->metadata()->slug;
pack->addonId = mod->metadata()->project_id;
pack->websiteUrl = mod->homeurl();
for (auto& author : mod->authors())
pack->authors.append({ author });
pack->description = mod->description();
pack->provider = ModPlatform::ResourceProvider::FLAME;
if (!latest_ver->hash.isEmpty() &&
(resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) {
auto old_version = resource->metadata()->version_number;
if (old_version.isEmpty()) {
if (resource->status() == ResourceStatus::NOT_INSTALLED)
old_version = tr("Not installed");
else
old_version = tr("Unknown");
if (!latest_ver->hash.isEmpty() && (mod->metadata()->hash != latest_ver->hash || mod->status() == ModStatus::NotInstalled)) {
auto old_version = mod->version();
if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) {
auto current_ver = getFileInfo(latest_ver->addonId.toInt(), mod->metadata()->file_id.toInt());
old_version = current_ver.version;
}
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver.value(), m_resource_model);
m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type,
api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()),
ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled());
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver.value(), m_mods_folder);
m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type,
api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()),
ModPlatform::ResourceProvider::FLAME, download_task, mod->enabled());
}
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver.value()));
}

View file

@ -7,11 +7,11 @@ class FlameCheckUpdate : public CheckUpdateTask {
Q_OBJECT
public:
FlameCheckUpdate(QList<Resource*>& resources,
FlameCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions,
QList<ModPlatform::ModLoaderType> loadersList,
std::shared_ptr<ResourceFolderModel> resourceModel)
: CheckUpdateTask(resources, mcVersions, std::move(loadersList), std::move(resourceModel))
std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loadersList, mods_folder)
{}
public slots:

View file

@ -36,7 +36,7 @@
#include "FlameInstanceCreationTask.h"
#include "QObjectPtr.h"
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "modplatform/flame/FileResolvingTask.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
@ -75,12 +75,12 @@ bool FlameCreationTask::abort()
return false;
m_abort = true;
if (m_processUpdateFileInfoJob)
m_processUpdateFileInfoJob->abort();
if (m_filesJob)
m_filesJob->abort();
if (m_modIdResolver)
m_modIdResolver->abort();
if (m_process_update_file_info_job)
m_process_update_file_info_job->abort();
if (m_files_job)
m_files_job->abort();
if (m_mod_id_resolver)
m_mod_id_resolver->abort();
return Task::abort();
}
@ -232,12 +232,12 @@ bool FlameCreationTask::updateInstance()
connect(job.get(), &Task::failed, this, [](QString reason) { qCritical() << "Failed to get files: " << reason; });
connect(job.get(), &Task::finished, &loop, &QEventLoop::quit);
m_processUpdateFileInfoJob = job;
m_process_update_file_info_job = job;
job->start();
loop.exec();
m_processUpdateFileInfoJob = nullptr;
m_process_update_file_info_job = nullptr;
} else {
// We don't have an old index file, so we may duplicate stuff!
auto dialog = CustomMessageBox::selectable(m_parent, tr("No index file."),
@ -430,26 +430,26 @@ bool FlameCreationTask::createInstance()
}
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managedId.isEmpty())
instance.setManagedPack("flame", m_managedId, m_pack.name, m_managedVersionId, m_pack.version);
if (!m_managed_id.isEmpty())
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
else
instance.setManagedPack("flame", "", name(), "", "");
instance.setName(name());
m_modIdResolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack));
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); });
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [this, &loop](QString reason) {
m_modIdResolver.reset();
m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack));
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); });
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) {
m_mod_id_resolver.reset();
setError(tr("Unable to resolve mod IDs:\n") + reason);
loop.quit();
});
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::aborted, &loop, &QEventLoop::quit);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propagateStepProgress);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails);
m_modIdResolver->start();
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::aborted, &loop, &QEventLoop::quit);
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress);
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus);
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propagateStepProgress);
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails);
m_mod_id_resolver->start();
loop.exec();
@ -468,14 +468,14 @@ bool FlameCreationTask::createInstance()
void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
{
auto results = m_modIdResolver->getResults();
auto results = m_mod_id_resolver->getResults();
// first check for blocked mods
QList<BlockedMod> blocked_mods;
auto anyBlocked = false;
for (const auto& result : results.files.values()) {
if (result.resourceType != PackedResourceType::Mod) {
m_otherResources.append(std::make_pair(result.version.fileName, result.targetFolder));
if (result.version.fileName.endsWith(".zip")) {
m_ZIP_resources.append(std::make_pair(result.version.fileName, result.targetFolder));
}
if (result.version.downloadUrl.isEmpty()) {
@ -507,7 +507,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
copyBlockedMods(blocked_mods);
setupDownloadJob(loop);
} else {
m_modIdResolver.reset();
m_mod_id_resolver.reset();
setError("Canceled");
loop.quit();
}
@ -518,8 +518,8 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
{
m_filesJob.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
auto results = m_modIdResolver->getResults().files;
m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
auto results = m_mod_id_resolver->getResults().files;
QStringList optionalFiles;
for (auto& result : results) {
@ -554,26 +554,26 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
if (!result.version.downloadUrl.isEmpty()) {
qDebug() << "Will download" << result.version.downloadUrl << "to" << path;
auto dl = Net::ApiDownload::makeFile(result.version.downloadUrl, path);
m_filesJob->addNetAction(dl);
m_files_job->addNetAction(dl);
}
}
connect(m_filesJob.get(), &NetJob::finished, this, [this, &loop]() {
m_filesJob.reset();
validateOtherResources(loop);
connect(m_files_job.get(), &NetJob::finished, this, [this, &loop]() {
m_files_job.reset();
validateZIPResources(loop);
});
connect(m_filesJob.get(), &NetJob::failed, [this](QString reason) {
m_filesJob.reset();
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
m_files_job.reset();
setError(reason);
});
connect(m_filesJob.get(), &NetJob::progress, this, [this](qint64 current, qint64 total) {
connect(m_files_job.get(), &NetJob::progress, this, [this](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
setProgress(current, total);
});
connect(m_filesJob.get(), &NetJob::stepProgress, this, &FlameCreationTask::propagateStepProgress);
connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propagateStepProgress);
setStatus(tr("Downloading mods..."));
m_filesJob->start();
m_files_job->start();
}
/// @brief copy the matched blocked mods to the instance staging area
@ -597,14 +597,8 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
qDebug() << "Will try to copy" << mod.localPath << "to" << destPath;
if (mod.move) {
if (!FS::move(mod.localPath, destPath)) {
qDebug() << "Move of" << mod.localPath << "to" << destPath << "Failed";
}
} else {
if (!FS::copy(mod.localPath, destPath)()) {
qDebug() << "Copy of" << mod.localPath << "to" << destPath << "Failed";
}
if (!FS::copy(mod.localPath, destPath)()) {
qDebug() << "Copy of" << mod.localPath << "to" << destPath << "Failed";
}
i++;
@ -614,11 +608,11 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
setAbortable(true);
}
void FlameCreationTask::validateOtherResources(QEventLoop& loop)
void FlameCreationTask::validateZIPResources(QEventLoop& loop)
{
qDebug() << "Validating whether other resources are in the right place";
qDebug() << "Validating whether resources stored as .zip are in the right place";
QStringList zipMods;
for (auto [fileName, targetFolder] : m_otherResources) {
for (auto [fileName, targetFolder] : m_ZIP_resources) {
qDebug() << "Checking" << fileName << "...";
auto localPath = FS::PathCombine(m_stagingPath, "minecraft", targetFolder, fileName);
@ -678,23 +672,21 @@ void FlameCreationTask::validateOtherResources(QEventLoop& loop)
installWorld(worldPath);
break;
case PackedResourceType::UNKNOWN:
/* fallthrough */
default:
qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is.";
break;
}
}
// TODO make this work with other sorts of resource
auto task = makeShared<ConcurrentTask>("CreateModMetadata", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
auto results = m_modIdResolver->getResults().files;
auto results = m_mod_id_resolver->getResults().files;
auto folder = FS::PathCombine(m_stagingPath, "minecraft", "mods", ".index");
for (auto file : results) {
if (file.targetFolder != "mods" || (file.version.fileName.endsWith(".zip") && !zipMods.contains(file.version.fileName))) {
continue;
}
task->addTask(makeShared<LocalResourceUpdateTask>(folder, file.pack, file.version));
task->addTask(makeShared<LocalModUpdateTask>(folder, file.pack, file.version));
}
connect(task.get(), &Task::finished, &loop, &QEventLoop::quit);
m_processUpdateFileInfoJob = task;
m_process_update_file_info_job = task;
task->start();
}

View file

@ -57,7 +57,7 @@ class FlameCreationTask final : public InstanceCreationTask {
QString id,
QString version_id,
QString original_instance_id = {})
: InstanceCreationTask(), m_parent(parent), m_managedId(std::move(id)), m_managedVersionId(std::move(version_id))
: InstanceCreationTask(), m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(version_id))
{
setStagingPath(staging_path);
setParentSettings(global_settings);
@ -74,22 +74,22 @@ class FlameCreationTask final : public InstanceCreationTask {
void idResolverSucceeded(QEventLoop&);
void setupDownloadJob(QEventLoop&);
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
void validateOtherResources(QEventLoop& loop);
void validateZIPResources(QEventLoop& loop);
QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion);
private:
QWidget* m_parent = nullptr;
shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver;
shared_qobject_ptr<Flame::FileResolvingTask> m_mod_id_resolver;
Flame::Manifest m_pack;
// Handle to allow aborting
Task::Ptr m_processUpdateFileInfoJob = nullptr;
NetJob::Ptr m_filesJob = nullptr;
Task::Ptr m_process_update_file_info_job = nullptr;
NetJob::Ptr m_files_job = nullptr;
QString m_managedId, m_managedVersionId;
QString m_managed_id, m_managed_version_id;
QList<std::pair<QString, QString>> m_otherResources;
QList<std::pair<QString, QString>> m_ZIP_resources;
std::optional<InstancePtr> m_instance;
};

View file

@ -40,7 +40,6 @@
#include <QString>
#include <QUrl>
#include <QVector>
#include "minecraft/mod/tasks/LocalResourceParse.h"
#include "modplatform/ModIndex.h"
namespace Flame {
@ -55,7 +54,6 @@ struct File {
// our
QString targetFolder = QStringLiteral("mods");
PackedResourceType resourceType;
};
struct Modloader {

View file

@ -28,7 +28,7 @@ QString toHTML(QList<Mod*> mods, OptionalData extraData)
auto meta = mod->metadata();
auto modName = mod->name().toHtmlEscaped();
if (extraData & Url) {
auto url = mod->homepage().toHtmlEscaped();
auto url = mod->metaurl().toHtmlEscaped();
if (!url.isEmpty())
modName = QString("<a href=\"%1\">%2</a>").arg(url, modName);
}
@ -65,7 +65,7 @@ QString toMarkdown(QList<Mod*> mods, OptionalData extraData)
auto meta = mod->metadata();
auto modName = toMarkdownEscaped(mod->name());
if (extraData & Url) {
auto url = mod->homepage();
auto url = mod->metaurl();
if (!url.isEmpty())
modName = QString("[%1](%2)").arg(modName, url);
}
@ -95,7 +95,7 @@ QString toPlainTXT(QList<Mod*> mods, OptionalData extraData)
auto line = modName;
if (extraData & Url) {
auto url = mod->homepage();
auto url = mod->metaurl();
if (!url.isEmpty())
line += QString(" (%1)").arg(url);
}
@ -124,7 +124,7 @@ QString toJSON(QList<Mod*> mods, OptionalData extraData)
QJsonObject line;
line["name"] = modName;
if (extraData & Url) {
auto url = mod->homepage();
auto url = mod->metaurl();
if (!url.isEmpty())
line["url"] = url;
}
@ -156,7 +156,7 @@ QString toCSV(QList<Mod*> mods, OptionalData extraData)
data << modName;
if (extraData & Url)
data << mod->homepage();
data << mod->metaurl();
if (extraData & Version) {
auto ver = mod->version();
if (ver.isEmpty() && meta != nullptr)
@ -203,7 +203,7 @@ QString exportToModList(QList<Mod*> mods, QString lineTemplate)
for (auto mod : mods) {
auto meta = mod->metadata();
auto modName = mod->name();
auto url = mod->homepage();
auto url = mod->metaurl();
auto ver = mod->version();
if (ver.isEmpty() && meta != nullptr)
ver = meta->version().toString();

View file

@ -152,7 +152,7 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] {
QObject::connect(netJob.get(), &NetJob::succeeded, [=] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {

View file

@ -71,15 +71,13 @@ class ModrinthAPI : public NetworkResourceAPI {
static auto getSideFilters(QString side) -> const QString
{
if (side.isEmpty()) {
if (side.isEmpty() || side == "both") {
return {};
}
if (side == "both")
return QString("\"client_side:required\"],[\"server_side:required\"");
if (side == "client")
return QString("\"client_side:required\",\"client_side:optional\"],[\"server_side:optional\",\"server_side:unsupported\"");
return QString("\"client_side:required\",\"client_side:optional\"");
if (side == "server")
return QString("\"server_side:required\",\"server_side:optional\"],[\"client_side:optional\",\"client_side:unsupported\"");
return QString("\"server_side:required\",\"server_side:optional\"");
return {};
}
@ -133,8 +131,6 @@ class ModrinthAPI : public NetworkResourceAPI {
}
if (args.categoryIds.has_value() && !args.categoryIds->empty())
facets_list.append(QString("[%1]").arg(getCategoriesFilters(args.categoryIds.value())));
if (args.openSource)
facets_list.append("[\"open_source:true\"]");
facets_list.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type)));

View file

@ -15,6 +15,14 @@
static ModrinthAPI api;
ModrinthCheckUpdate::ModrinthCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions,
QList<ModPlatform::ModLoaderType> loadersList,
std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loadersList, mods_folder)
, m_hash_type(ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first())
{}
bool ModrinthCheckUpdate::abort()
{
if (m_job)
@ -29,24 +37,24 @@ bool ModrinthCheckUpdate::abort()
* */
void ModrinthCheckUpdate::executeTask()
{
setStatus(tr("Preparing resources for Modrinth..."));
setProgress(0, (m_loaders_list.isEmpty() ? 1 : m_loaders_list.length()) * 2 + 1);
setStatus(tr("Preparing mods for Modrinth..."));
setProgress(0, 9);
auto hashing_task =
makeShared<ConcurrentTask>("MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
for (auto* resource : m_resources) {
auto hash = resource->metadata()->hash;
for (auto* mod : m_mods) {
auto hash = mod->metadata()->hash;
// Sadly the API can only handle one hash type per call, se we
// need to generate a new hash if the current one is innadequate
// (though it will rarely happen, if at all)
if (resource->metadata()->hash_format != m_hash_type) {
auto hash_task = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH);
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_mappings.insert(hash, resource); });
if (mod->metadata()->hash_format != m_hash_type) {
auto hash_task = Hashing::createHasher(mod->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH);
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mappings.insert(hash, mod); });
connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); });
hashing_task->addTask(hash_task);
} else {
m_mappings.insert(hash, resource);
m_mappings.insert(hash, mod);
}
}
@ -55,28 +63,10 @@ void ModrinthCheckUpdate::executeTask()
hashing_task->start();
}
void ModrinthCheckUpdate::getUpdateModsForLoader(std::optional<ModPlatform::ModLoaderTypes> loader)
void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> response,
ModPlatform::ModLoaderTypes loader,
bool forceModLoaderCheck)
{
setStatus(tr("Waiting for the API response from Modrinth..."));
setProgress(m_progress + 1, m_progressTotal);
auto response = std::make_shared<QByteArray>();
QStringList hashes = m_mappings.keys();
auto job = api.latestVersions(hashes, m_hash_type, m_game_versions, loader, response);
connect(job.get(), &Task::succeeded, this, [this, response, loader] { checkVersionsResponse(response, loader); });
connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::checkNextLoader);
m_job = job;
job->start();
}
void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> response, std::optional<ModPlatform::ModLoaderTypes> loader)
{
setStatus(tr("Parsing the API response from Modrinth..."));
setProgress(m_progress + 1, m_progressTotal);
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
@ -88,31 +78,29 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> resp
return;
}
setStatus(tr("Parsing the API response from Modrinth..."));
setProgress(m_next_loader_idx * 2, 9);
try {
auto iter = m_mappings.begin();
while (iter != m_mappings.end()) {
const QString hash = iter.key();
Resource* resource = iter.value();
for (auto hash : m_mappings.keys()) {
if (forceModLoaderCheck && !(m_mappings[hash]->loaders() & loader)) {
continue;
}
auto project_obj = doc[hash].toObject();
// If the returned project is empty, but we have Modrinth metadata,
// it means this specific version is not available
if (project_obj.isEmpty()) {
qDebug() << "Mod " << m_mappings.find(hash).value()->name() << " got an empty response." << "Hash: " << hash;
++iter;
continue;
}
// Sometimes a version may have multiple files, one with "forge" and one with "fabric",
// so we may want to filter it
QString loader_filter;
if (loader.has_value()) {
for (auto flag : ModPlatform::modLoaderTypesToList(*loader)) {
loader_filter = ModPlatform::getModLoaderAsString(flag);
break;
}
for (auto flag : ModPlatform::modLoaderTypesToList(loader)) {
loader_filter = ModPlatform::getModLoaderAsString(flag);
break;
}
// Currently, we rely on a couple heuristics to determine whether an update is actually available or not:
@ -124,72 +112,106 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> resp
auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, m_hash_type, loader_filter);
if (project_ver.downloadUrl.isEmpty()) {
qCritical() << "Modrinth mod without download url!" << project_ver.fileName;
++iter;
continue;
}
auto mod_iter = m_mappings.find(hash);
if (mod_iter == m_mappings.end()) {
qCritical() << "Failed to remap mod from Modrinth!";
continue;
}
auto mod = *mod_iter;
m_mappings.remove(hash);
auto key = project_ver.hash;
// Fake pack with the necessary info to pass to the download task :)
auto pack = std::make_shared<ModPlatform::IndexedPack>();
pack->name = resource->name();
pack->slug = resource->metadata()->slug;
pack->addonId = resource->metadata()->project_id;
pack->name = mod->name();
pack->slug = mod->metadata()->slug;
pack->addonId = mod->metadata()->project_id;
pack->websiteUrl = mod->homeurl();
for (auto& author : mod->authors())
pack->authors.append({ author });
pack->description = mod->description();
pack->provider = ModPlatform::ResourceProvider::MODRINTH;
if ((project_ver.hash != hash && project_ver.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) {
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_resource_model);
if ((key != hash && project_ver.is_preferred) || (mod->status() == ModStatus::NotInstalled)) {
if (mod->version() == project_ver.version_number)
continue;
QString old_version = resource->metadata()->version_number;
if (old_version.isEmpty()) {
if (resource->status() == ResourceStatus::NOT_INSTALLED)
old_version = tr("Not installed");
else
old_version = tr("Unknown");
}
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder);
m_updates.emplace_back(pack->name, hash, old_version, project_ver.version_number, project_ver.version_type,
project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, resource->enabled());
m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.version_type,
project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, mod->enabled());
}
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
iter = m_mappings.erase(iter);
}
} catch (Json::JsonException& e) {
emitFailed(e.cause() + ": " + e.what());
emitFailed(e.cause() + " : " + e.what());
return;
}
checkNextLoader();
}
void ModrinthCheckUpdate::getUpdateModsForLoader(ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck)
{
auto response = std::make_shared<QByteArray>();
QStringList hashes;
if (forceModLoaderCheck) {
for (auto hash : m_mappings.keys()) {
if (m_mappings[hash]->loaders() & loader) {
hashes.append(hash);
}
}
} else {
hashes = m_mappings.keys();
}
auto job = api.latestVersions(hashes, m_hash_type, m_game_versions, loader, response);
connect(job.get(), &Task::succeeded, this,
[this, response, loader, forceModLoaderCheck] { checkVersionsResponse(response, loader, forceModLoaderCheck); });
connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::checkNextLoader);
setStatus(tr("Waiting for the API response from Modrinth..."));
setProgress(m_next_loader_idx * 2 - 1, 9);
m_job = job;
job->start();
}
void ModrinthCheckUpdate::checkNextLoader()
{
if (m_mappings.isEmpty()) {
emitSucceeded();
return;
}
if (m_loaders_list.isEmpty() && m_loader_idx == 0) {
getUpdateModsForLoader({});
m_loader_idx++;
if (m_next_loader_idx < m_loaders_list.size()) {
getUpdateModsForLoader(m_loaders_list.at(m_next_loader_idx));
m_next_loader_idx++;
return;
}
if (m_loader_idx < m_loaders_list.size()) {
getUpdateModsForLoader(m_loaders_list.at(m_loader_idx));
m_loader_idx++;
return;
static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge, ModPlatform::ModLoaderType::Quilt,
ModPlatform::ModLoaderType::Fabric };
for (auto flag : flags) {
if (!m_loaders_list.contains(flag)) {
m_loaders_list.append(flag);
m_next_loader_idx++;
setProgress(m_next_loader_idx * 2 - 1, 9);
for (auto m : m_mappings) {
if (m->loaders() & flag) {
getUpdateModsForLoader(flag, true);
return;
}
}
setProgress(m_next_loader_idx * 2, 9);
}
}
for (auto resource : m_mappings) {
QString reason;
if (dynamic_cast<Mod*>(resource) != nullptr)
reason =
tr("No valid version found for this resource. It's probably unavailable for the current game "
"version / mod loader.");
else
reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
emit checkFailed(resource, reason);
for (auto m : m_mappings) {
emit checkFailed(m,
tr("No valid version found for this mod. It's probably unavailable for the current game version / mod loader."));
}
emitSucceeded();
return;
}

View file

@ -6,26 +6,23 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
Q_OBJECT
public:
ModrinthCheckUpdate(QList<Resource*>& resources,
ModrinthCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions,
QList<ModPlatform::ModLoaderType> loadersList,
std::shared_ptr<ResourceFolderModel> resourceModel)
: CheckUpdateTask(resources, mcVersions, std::move(loadersList), std::move(resourceModel))
, m_hash_type(ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first())
{}
std::shared_ptr<ModFolderModel> mods_folder);
public slots:
bool abort() override;
protected slots:
void executeTask() override;
void getUpdateModsForLoader(std::optional<ModPlatform::ModLoaderTypes> loader);
void checkVersionsResponse(std::shared_ptr<QByteArray> response, std::optional<ModPlatform::ModLoaderTypes> loader);
void getUpdateModsForLoader(ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck = false);
void checkVersionsResponse(std::shared_ptr<QByteArray> response, ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck = false);
void checkNextLoader();
private:
Task::Ptr m_job = nullptr;
QHash<QString, Resource*> m_mappings;
QHash<QString, Mod*> m_mappings;
QString m_hash_type;
int m_loader_idx = 0;
int m_next_loader_idx = 0;
};

View file

@ -243,8 +243,7 @@ bool ModrinthCreationTask::createInstance()
auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path);
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
// TODO make this work with other sorts of resource
QHash<QString, Resource*> resources;
QHash<QString, Mod*> mods;
for (auto file : m_files) {
auto fileName = file.path;
fileName = FS::RemoveInvalidPathChars(fileName);
@ -260,7 +259,7 @@ bool ModrinthCreationTask::createInstance()
ModDetails d;
d.mod_id = file_path;
mod->setDetails(d);
resources[file.hash.toHex()] = mod;
mods[file.hash.toHex()] = mod;
}
if (file.downloads.empty()) {
setError(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName));
@ -286,13 +285,13 @@ bool ModrinthCreationTask::createInstance()
bool ended_well = false;
connect(downloadMods.get(), &NetJob::succeeded, this, [&ended_well]() { ended_well = true; });
connect(downloadMods.get(), &NetJob::failed, [this, &ended_well](const QString& reason) {
connect(downloadMods.get(), &NetJob::succeeded, this, [&]() { ended_well = true; });
connect(downloadMods.get(), &NetJob::failed, [&](const QString& reason) {
ended_well = false;
setError(reason);
});
connect(downloadMods.get(), &NetJob::finished, &loop, &QEventLoop::quit);
connect(downloadMods.get(), &NetJob::progress, [this](qint64 current, qint64 total) {
connect(downloadMods.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
setProgress(current, total);
});
@ -305,18 +304,18 @@ bool ModrinthCreationTask::createInstance()
loop.exec();
if (!ended_well) {
for (auto resource : resources) {
delete resource;
for (auto m : mods) {
delete m;
}
return ended_well;
}
QEventLoop ensureMetaLoop;
QDir folder = FS::PathCombine(instance.modsRoot(), ".index");
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(resources, folder, ModPlatform::ResourceProvider::MODRINTH);
connect(ensureMetadataTask.get(), &Task::succeeded, this, [&ended_well]() { ended_well = true; });
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(mods, folder, ModPlatform::ResourceProvider::MODRINTH);
connect(ensureMetadataTask.get(), &Task::succeeded, this, [&]() { ended_well = true; });
connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit);
connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) {
connect(ensureMetadataTask.get(), &Task::progress, [&](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
setProgress(current, total);
});
@ -326,10 +325,10 @@ bool ModrinthCreationTask::createInstance()
m_task = ensureMetadataTask;
ensureMetaLoop.exec();
for (auto resource : resources) {
delete resource;
for (auto m : mods) {
delete m;
}
resources.clear();
mods.clear();
// Update information of the already installed instance, if any.
if (m_instance && ended_well) {

View file

@ -123,7 +123,7 @@ void ModrinthPackExportTask::collectHashes()
modIter != allMods.end()) {
const Mod* mod = *modIter;
if (mod->metadata() != nullptr) {
const QUrl& url = mod->metadata()->url;
QUrl& url = mod->metadata()->url;
// ensure the url is permitted on modrinth.com
if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) {
qDebug() << "Resolving" << relative << "from index";

View file

@ -35,7 +35,7 @@
namespace Packwiz {
auto getRealIndexName(const QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString
auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString
{
QFile index_file(index_dir.absoluteFilePath(normalized_fname));
@ -90,7 +90,7 @@ auto intEntry(toml::table table, QString entry_name) -> int
return node.value_or(0);
}
auto V1::createModFormat([[maybe_unused]] const QDir& index_dir,
auto V1::createModFormat([[maybe_unused]] QDir& index_dir,
ModPlatform::IndexedPack& mod_pack,
ModPlatform::IndexedVersion& mod_version) -> Mod
{
@ -119,14 +119,10 @@ auto V1::createModFormat([[maybe_unused]] const QDir& index_dir,
mod.mcVersions.sort();
mod.releaseType = mod_version.version_type;
mod.version_number = mod_version.version_number;
if (mod.version_number.isNull()) // on CurseForge, there is only a version name - not a version number
mod.version_number = mod_version.version;
return mod;
}
auto V1::createModFormat(const QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, QString slug) -> Mod
auto V1::createModFormat(QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, QString slug) -> Mod
{
// Try getting metadata if it exists
Mod mod{ getIndexForMod(index_dir, slug) };
@ -138,7 +134,7 @@ auto V1::createModFormat(const QDir& index_dir, [[maybe_unused]] ::Mod& internal
return {};
}
void V1::updateModIndex(const QDir& index_dir, Mod& mod)
void V1::updateModIndex(QDir& index_dir, Mod& mod)
{
if (!mod.isValid()) {
qCritical() << QString("Tried to update metadata of an invalid mod!");
@ -212,7 +208,6 @@ void V1::updateModIndex(const QDir& index_dir, Mod& mod)
{ "x-prismlauncher-loaders", loaders },
{ "x-prismlauncher-mc-versions", mcVersions },
{ "x-prismlauncher-release-type", mod.releaseType.toString().toStdString() },
{ "x-prismlauncher-version-number", mod.version_number.toStdString() },
{ "download",
toml::table{
{ "mode", mod.mode.toStdString() },
@ -230,7 +225,7 @@ void V1::updateModIndex(const QDir& index_dir, Mod& mod)
index_file.close();
}
void V1::deleteModIndex(const QDir& index_dir, QString& mod_slug)
void V1::deleteModIndex(QDir& index_dir, QString& mod_slug)
{
auto normalized_fname = indexFileName(mod_slug);
auto real_fname = getRealIndexName(index_dir, normalized_fname);
@ -249,7 +244,7 @@ void V1::deleteModIndex(const QDir& index_dir, QString& mod_slug)
}
}
void V1::deleteModIndex(const QDir& index_dir, QVariant& mod_id)
void V1::deleteModIndex(QDir& index_dir, QVariant& mod_id)
{
for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) {
auto mod = getIndexForMod(index_dir, file_name);
@ -261,7 +256,7 @@ void V1::deleteModIndex(const QDir& index_dir, QVariant& mod_id)
}
}
auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod
auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
{
Mod mod;
@ -297,7 +292,7 @@ auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod
mod.name = stringEntry(table, "name");
mod.filename = stringEntry(table, "filename");
mod.side = stringToSide(stringEntry(table, "side"));
mod.releaseType = ModPlatform::IndexedVersionType(table["x-prismlauncher-release-type"].value_or(""));
mod.releaseType = ModPlatform::IndexedVersionType(stringEntry(table, "x-prismlauncher-release-type"));
if (auto loaders = table["x-prismlauncher-loaders"]; loaders && loaders.is_array()) {
for (auto&& loader : *loaders.as_array()) {
if (loader.is_string()) {
@ -317,7 +312,6 @@ auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod
mod.mcVersions.sort();
}
}
mod.version_number = table["x-prismlauncher-version-number"].value_or("");
{ // [download] info
auto download_table = table["download"].as_table();
@ -359,7 +353,7 @@ auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod
return mod;
}
auto V1::getIndexForMod(const QDir& index_dir, QVariant& mod_id) -> Mod
auto V1::getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod
{
for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) {
auto mod = getIndexForMod(index_dir, file_name);

View file

@ -32,13 +32,11 @@ class Mod;
namespace Packwiz {
auto getRealIndexName(const QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString;
auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString;
class V1 {
public:
enum class Side { ClientSide = 1 << 0, ServerSide = 1 << 1, UniversalSide = ClientSide | ServerSide };
// can also represent other resources beside loader mods - but this is what packwiz calls it
struct Mod {
QString slug{};
QString name{};
@ -58,7 +56,6 @@ class V1 {
ModPlatform::ResourceProvider provider{};
QVariant file_id{};
QVariant project_id{};
QString version_number{};
public:
// This is a totally heuristic, but should work for now.
@ -73,33 +70,33 @@ class V1 {
/* Generates the object representing the information in a mod.pw.toml file via
* its common representation in the launcher, when downloading mods.
* */
static auto createModFormat(const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod;
static auto createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod;
/* Generates the object representing the information in a mod.pw.toml file via
* its common representation in the launcher, plus a necessary slug.
* */
static auto createModFormat(const QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod;
static auto createModFormat(QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod;
/* Updates the mod index for the provided mod.
* This creates a new index if one does not exist already
* TODO: Ask the user if they want to override, and delete the old mod's files, or keep the old one.
* */
static void updateModIndex(const QDir& index_dir, Mod& mod);
static void updateModIndex(QDir& index_dir, Mod& mod);
/* Deletes the metadata for the mod with the given slug. If the metadata doesn't exist, it does nothing. */
static void deleteModIndex(const QDir& index_dir, QString& mod_slug);
static void deleteModIndex(QDir& index_dir, QString& mod_slug);
/* Deletes the metadata for the mod with the given id. If the metadata doesn't exist, it does nothing. */
static void deleteModIndex(const QDir& index_dir, QVariant& mod_id);
static void deleteModIndex(QDir& index_dir, QVariant& mod_id);
/* Gets the metadata for a mod with a particular file name.
* If the mod doesn't have a metadata, it simply returns an empty Mod object.
* */
static auto getIndexForMod(const QDir& index_dir, QString slug) -> Mod;
static auto getIndexForMod(QDir& index_dir, QString slug) -> Mod;
/* Gets the metadata for a mod with a particular id.
* If the mod doesn't have a metadata, it simply returns an empty Mod object.
* */
static auto getIndexForMod(const QDir& index_dir, QVariant& mod_id) -> Mod;
static auto getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod;
static auto sideToString(Side side) -> QString;
static auto stringToSide(QString side) -> Side;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 186 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 200 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 195 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 184 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Before After
Before After

Some files were not shown because too many files have changed in this diff Show more