Compare commits

..

384 commits

Author SHA1 Message Date
HerozDotExe
6799b772de docs: explain the point of this fork
Some checks failed
CodeQL Code Scanning / CodeQL (push) Has been cancelled
Nix / Build (x86_64-darwin) (push) Has been cancelled
Nix / Build (aarch64-darwin) (push) Has been cancelled
Nix / Build (x86_64-linux) (push) Has been cancelled
Nix / Build (aarch64-linux) (push) Has been cancelled
Build Application / Build Debug (push) Has been cancelled
2025-02-23 10:52:10 +01:00
HerozDotExe
3b87452699 feat: allow offline accounts 2025-02-23 10:50:49 +01:00
Seth Flynn
0af021fef2
ci: always run nix workflow on push (#3433) 2025-02-19 02:17:41 -05:00
Alexandru Ionut Tripon
d24066c965
chore(deps): update actions/cache action to v4.2.1 (#3432) 2025-02-19 08:20:04 +02:00
Seth Flynn
46385e671d
ci: always run nix workflow on push
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-02-18 22:55:51 -05:00
renovate[bot]
73bb740bbb
chore(deps): update actions/cache action to v4.2.1 2025-02-18 18:14:21 +00:00
Alexandru Ionut Tripon
50ab1b2c40
Recognize riscv64 as a 64-bit platform (#3425) 2025-02-17 20:33:17 +02:00
Alexandru Ionut Tripon
d472b966dc
chore(nix): update lockfile (#3401) 2025-02-17 18:04:45 +02:00
Alexandru Ionut Tripon
beee93e199
Update hendrikmuhs/ccache-action action to v1.2.17 (#3388) 2025-02-17 18:04:33 +02:00
thonkdifferent
8f1750df51
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>
2025-02-16 19:30:31 +02:00
github-actions[bot]
bdf87fa02f chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/3a228057f5b619feb3186e986dbe76278d707b6e?narHash=sha256-xvTo0Aw0%2Bveek7hvEVLzErmJyQkEcRk6PSR4zsRQFEc%3D' (2025-02-01)
  → 'github:NixOS/nixpkgs/2ff53fe64443980e139eaa286017f53f88336dd0?narHash=sha256-%2B/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc%3D' (2025-02-13)
2025-02-16 00:25:49 +00:00
Rachel Powers
3295b0717a
add libMangoHud_shim.so to mangohub preloadlist (#3413) 2025-02-13 14:41:01 -07:00
Rachel Powers
7b97863685
make sure if user changes java path also disable java management (#3414) 2025-02-13 14:40:40 -07:00
Alexandru Ionut Tripon
3e4fc2ff3b
Increase the stale operations (#3393) 2025-02-13 13:44:01 +02:00
Alexandru Ionut Tripon
d22a20ffa6
Update .github/workflows/stale.yml
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-02-13 08:14:20 +02:00
TheKodeToad
de86e63611
Update .github/workflows/stale.yml
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-02-13 00:05:14 +00:00
TheKodeToad
e41a60e57e
remove some unused files (#3412) 2025-02-13 00:04:45 +00:00
Trial97
174cddcf42
make sure if user changes java path also disable java management
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-12 20:40:09 +02:00
Trial97
9e954548dd
add libMangoHud_shim.so to mangohub preloadlist
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-12 20:21:45 +02:00
Trial97
14b653e08d
remove some unused files
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-12 20:16:00 +02:00
TheKodeToad
79180db663
Fix some undefined behaviour (#3407) 2025-02-11 19:04:56 +00:00
Alexandru Ionut Tripon
22b59e760c
Update launcher/ui/widgets/CheckComboBox.cpp
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-02-11 21:03:04 +02:00
Alexandru Ionut Tripon
1d1a4f1b30
Apply suggestions from code review
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-02-11 21:02:27 +02:00
Trial97
bf1084d7fa
Fix some undefined behaviour
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-11 10:59:10 +02:00
Trial97
ef825a7bc5
update stale action
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-06 09:58:41 +02:00
Alexandru Ionut Tripon
8d143c7b95
Fixed when game crashes, it doesn't log any time played (#3392) 2025-02-06 09:35:23 +02:00
Riley Richard
9ea33e0651 DCO Remediation Commit for Riley Richard <bubbalou112@gmail.com>
I, Riley Richard <bubbalou112@gmail.com>, hereby add my Signed-off-by to this commit: cb83f8e52d

Signed-off-by: Riley Richard <bubbalou112@gmail.com>
2025-02-05 18:10:31 -05:00
Trial97
f1209c1279
Increase the stale operations
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-05 23:43:38 +02:00
Riley Richard
17e95c0d07 forgot to sign
Signed-off-by: Riley Richard <bubbalou112@gmail.com>
2025-02-05 16:33:15 -05:00
Riley Richard
cb83f8e52d fixed thing 2025-02-05 16:13:22 -05:00
Alexandru Ionut Tripon
7d202e348d
Use the same UI for instance and global settings (#3341) 2025-02-05 23:04:54 +02:00
Alexandru Ionut Tripon
364b5be856
Add stale workflow (#3389) 2025-02-05 23:04:38 +02:00
TheKodeToad
e1896d5b6b
Update launcher/ui/pages/global/MinecraftPage.h
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-02-05 13:45:49 +00:00
TheKodeToad
6e3dd04a1f
Update launcher/ui/pages/global/MinecraftPage.h
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-02-05 12:09:32 +00:00
TheKodeToad
f7a401ab22
Update launcher/ui/pages/instance/InstanceSettingsPage.h
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-02-05 12:09:26 +00:00
TheKodeToad
9ed6801b15
Update launcher/ui/pages/global/JavaPage.h
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-02-05 12:09:12 +00:00
TheKodeToad
06b50dfd33
Fix more header mistakes
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-02-05 11:11:45 +00:00
TheKodeToad
5a76d9b7e1
Make requested changes
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-02-05 10:58:06 +00:00
Alexandru Ionut Tripon
34438824c1
Update .github/workflows/stale.yml
Co-authored-by: Seth Flynn <getchoo@tuta.io>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-02-04 23:15:18 +02:00
Alexandru Ionut Tripon
ac598740b3
Update .github/workflows/stale.yml
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-02-04 22:08:46 +02:00
Alexandru Ionut Tripon
85c2710e33
Apply suggestions from code review
Co-authored-by: Seth Flynn <getchoo@tuta.io>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-02-04 22:08:16 +02:00
Alexandru Ionut Tripon
afe6d4836d
Update .github/workflows/stale.yml
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-02-04 17:41:36 +02:00
Trial97
9c26ba5932
add stale workflow
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-04 16:13:55 +02:00
renovate[bot]
2a987b5a4a
chore(deps): update hendrikmuhs/ccache-action action to v1.2.17 2025-02-03 22:11:56 +00:00
Alexandru Ionut Tripon
1a375dc243
Merge pull request #3382 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2025-02-03 00:28:59 +02:00
Alexandru Ionut Tripon
ab3792b080
Merge pull request #3381 from razzeee/patch-1
Add PackageManager category
2025-02-03 00:28:42 +02:00
Seth Flynn
f9dcc31843
Merge pull request #3374 from getchoo-contrib/ci/fix-nix-out-paths
Fix Nix workflow
2025-02-02 17:18:03 -05:00
Seth Flynn
989e6c6cb6
ci: directly print nix out paths to step summary
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-02-01 22:55:39 -05:00
Seth Flynn
138684a84f
Revert "ci: add nix out paths to job summary"
This reverts commit 588e0d6305.

Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-02-01 22:54:26 -05:00
github-actions[bot]
faf7e6b06e chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/825479c345a7f806485b7f00dbe3abb50641b083?narHash=sha256-nU6AezEX4EuahTO1YopzueAXfjFfmCHylYEFCagduHU%3D' (2025-01-24)
  → 'github:NixOS/nixpkgs/3a228057f5b619feb3186e986dbe76278d707b6e?narHash=sha256-xvTo0Aw0%2Bveek7hvEVLzErmJyQkEcRk6PSR4zsRQFEc%3D' (2025-02-01)
2025-02-02 00:24:39 +00:00
Kolja
f1671e3ded
Add PackageManager category
Signed-off-by: Kolja <razzeee@gmail.com>
2025-02-01 20:25:01 +01:00
Alexandru Ionut Tripon
6404d81ada
Merge pull request #3373 from Trial97/fix_crash2
Show warning when adding new resources with no instances
2025-01-31 23:03:55 +02:00
Alexandru Ionut Tripon
d6eb96df5c
Merge pull request #3260 from Trial97/fix_modrinth_shapshot
Map Minecraft snapshots to Modrinth
2025-01-31 23:02:55 +02:00
Trial97
c5efe081b4
fix add resource with no instance
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-31 22:03:57 +02:00
Seth Flynn
a639bc019e
Merge pull request #3368 from getchoo-contrib/ci/print-nix-out-paths 2025-01-31 07:00:37 -05:00
Seth Flynn
588e0d6305
ci: add nix out paths to job summary
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-30 13:34:34 -05:00
Sefa Eyeoglu
d7eddedf41
Merge pull request #3359 from PrismLauncher/ci/flakehub 2025-01-30 16:06:58 +01:00
Alexandru Ionut Tripon
6ae9fa4df4
Merge pull request #3314 from Trial97/update_modules
update submodules
2025-01-29 23:35:08 +02:00
Seth Flynn
86cc6d3c5e
feat: publish on flakehub
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 16:34:31 -05:00
Seth Flynn
61d7f08883
ci: use generic workflow for publishing
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 16:34:27 -05:00
Seth Flynn
0deb5e20ae
Merge pull request #3362 from getchoo-contrib/ci/flakehub-cache 2025-01-29 16:34:02 -05:00
Alexandru Ionut Tripon
fcb3dd5e9d
Merge pull request #3346 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2025-01-29 23:33:01 +02:00
Seth Flynn
169f5728b1
ci: run nix workflow on pull_request_target
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 16:22:44 -05:00
Seth Flynn
8816be1668
ci: use flakehub cache
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 16:17:01 -05:00
Alexandru Ionut Tripon
bd752c025c
Merge pull request #3360 from getchoo-contrib/ci/split
Split Flatpak and Nix CI jobs from main build workflow
2025-01-29 22:51:07 +02:00
Seth Flynn
49d734f314
ci: separate flatpak job
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 08:32:49 -05:00
Seth Flynn
417688089d
ci: separate nix job
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 08:31:45 -05:00
Seth Flynn
de21b77ae1
Merge pull request #3334 from PrismLauncher/renovate/determinatesystems-magic-nix-cache-action-9.x
chore(deps): update determinatesystems/magic-nix-cache-action action to v9
2025-01-29 07:44:06 -05:00
TheKodeToad
68bf409a98
Fix formatting issues
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 19:14:57 +00:00
TheKodeToad
5d179671bf
Fix non-Linux build
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 18:55:24 +00:00
TheKodeToad
dc6f536843
Fix Qt 5 compilation
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 18:04:41 +00:00
TheKodeToad
cc504f4a6c
Merge remote-tracking branch 'upstream/develop' into unify-mc-settings
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 17:02:50 +00:00
TheKodeToad
a92c68adbd
Remove duplicate console section
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 16:54:45 +00:00
TheKodeToad
7aaa9fa93c
Finish off MinecraftSettingsWidget implementation
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 16:46:26 +00:00
TheKodeToad
81769b9047
Use MinecraftInstancePtr instance of InstancePtr
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 14:34:07 +00:00
TheKodeToad
147b315c14
Update thresholds when min. memory value is changed too
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 14:15:07 +00:00
TheKodeToad
9d0918a064
Remove outdated JavaWizardWidget comment
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 13:19:10 +00:00
TheKodeToad
98ab686348
Merge remote-tracking branch 'upstream/develop' into unify-mc-settings
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-27 13:18:19 +00:00
TheKodeToad
39ae2fbd4a
Fix tab inconsistencies
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-26 22:44:43 +00:00
TheKodeToad
36a35d7925
Fix tab order
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-26 22:36:48 +00:00
TheKodeToad
1b4b36df33
Unify Java page
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2025-01-26 22:25:26 +00:00
github-actions[bot]
3d9e073721 chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/5df43628fdf08d642be8ba5b3625a6c70731c19c?narHash=sha256-Tbk1MZbtV2s5aG%2BiM99U8FqwxU/YNArMcWAv6clcsBc%3D' (2025-01-16)
  → 'github:NixOS/nixpkgs/825479c345a7f806485b7f00dbe3abb50641b083?narHash=sha256-nU6AezEX4EuahTO1YopzueAXfjFfmCHylYEFCagduHU%3D' (2025-01-24)
2025-01-26 00:23:21 +00:00
Alexandru Ionut Tripon
fdb4159f2a
Merge pull request #3332 from eff3ry/modrinth-filter-tweak
Update Modrinth Client/Server Filters to match Modrinth website & launcher.
2025-01-21 23:42:37 +02:00
Alexandru Ionut Tripon
98be91c9d5
Merge pull request #3294 from Trial97/fix_flame_update
Fix updating Curseforge mods with missing mod loader
2025-01-21 23:40:21 +02:00
renovate[bot]
705375c0df
chore(deps): update determinatesystems/magic-nix-cache-action action to v9 2025-01-21 21:35:18 +00:00
TheKodeToad
8d3684c484
Merge pull request #1413 from Trial97/fix_curse_import
feat: updated flame mod resolution
2025-01-21 13:53:00 +00:00
Alexandru Ionut Tripon
0d4eeb9965
Merge pull request #3330 from getchoo-contrib/mingw-cfg
Use better compile flags for MINGW
2025-01-21 15:18:43 +02:00
Jeffery Tolmie
caccaefb39
Change "none" to ""
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: Jeffery Tolmie <jeffery.tolmie@gmail.com>
2025-01-22 02:06:49 +13:00
eff3ry
532fec7748 DCO Remediation Commit
DCO Remediation Commit for eff3ry <jeffery.tolmie@gmail.com>

I, eff3ry <jeffery.tolmie@gmail.com>, hereby add my Signed-off-by to this commit: 2a403b3bfe
I, eff3ry <jeffery.tolmie@gmail.com>, hereby add my Signed-off-by to this commit: 4ba3fd559d

Signed-off-by: eff3ry <jeffery.tolmie@gmail.com>
2025-01-22 01:48:04 +13:00
eff3ry
4ba3fd559d Update Filter Checks
Updates the filter checks so that options both and none can be differentiated.

Signed-off-by: Jeffery Tolmie Jeffery.tolmie@gmail.com
2025-01-22 01:40:59 +13:00
eff3ry
2a403b3bfe Edit Modrinth search faucets
Edit Modrinth search faucets to alter the client and server filters to be more in-line with the Modrinth website and Launcher, making it easier to find client-side only mods etc.

Signed-off-by: Jeffery Tolmie Jeffery.tolmie@gmail.com
2025-01-22 01:39:28 +13:00
Seth Flynn
f8a504e254
Merge pull request #3299 from Trial97/appimage2
Use Ubuntu 22.04 for Linux builds
2025-01-20 23:43:45 -05:00
Seth Flynn
e66f447ce5
feat: use better compile flags for mingw
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 20:36:48 -05:00
Seth Flynn
67b218b1f3
Merge pull request #2895 from Edgars-Cirulis/qt-6.8
chore: update to qt 6.8.x
2025-01-20 18:53:05 -05:00
Alexandru Ionut Tripon
db766574a4
Update .github/workflows/build.yml
Co-authored-by: Seth Flynn <getchoo@tuta.io>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-01-21 01:38:13 +02:00
timoreo
18da01c9a6
Merge pull request #3320 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2025-01-20 09:48:11 +01:00
Alexandru Ionut Tripon
c79ad73311
Merge pull request #3319 from getchoo-contrib/flatpak-arm
Build Flatpaks for ARM
2025-01-19 10:16:41 +02:00
Sefa Eyeoglu
fb3926a85d
Merge pull request #3318 from getchoo-contrib/nix-arm
Build Nix packages for aarch64-linux
2025-01-19 09:08:17 +01:00
github-actions[bot]
8d4f279539 chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/bffc22eb12172e6db3c5dde9e3e5628f8e3e7912?narHash=sha256-8YVQ9ZbSfuUk2bUf2KRj60NRraLPKPS0Q4QFTbc%2Bc2c%3D' (2025-01-08)
  → 'github:NixOS/nixpkgs/5df43628fdf08d642be8ba5b3625a6c70731c19c?narHash=sha256-Tbk1MZbtV2s5aG%2BiM99U8FqwxU/YNArMcWAv6clcsBc%3D' (2025-01-16)
2025-01-19 00:24:56 +00:00
Alexandru Ionut Tripon
1b5d3c2bf9
Update .github/workflows/build.yml
Co-authored-by: seth <getchoo@tuta.io>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2025-01-19 02:03:21 +02:00
Trial97
be3eca8c97
remove specific step for qt6
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-19 01:05:06 +02:00
seth
66f0397087
ci: build flatpaks for arm
Signed-off-by: seth <getchoo@tuta.io>
2025-01-18 17:16:02 -05:00
seth
8e8538b506
ci: build nix packages for aarch64-linux
Signed-off-by: seth <getchoo@tuta.io>
2025-01-18 16:55:38 -05:00
Trial97
dc00c47f2e
update submodules
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-18 13:27:18 +02:00
Trial97
7d3238d234
rename variables
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-17 21:09:44 +02:00
Alexandru Ionut Tripon
f17599c8b8
Merge pull request #1689 from Trial97/progrss_bar_interaction
Added mouse interactions for labels in ProgressDialog
2025-01-17 20:59:52 +02:00
Trial97
8e5a7c6e33
ensure that the snapshot mapping is on all apis
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-17 10:35:13 +02:00
Trial97
1d8bf1d5a7
map modrinth snapshots
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-17 09:19:39 +02:00
Alexandru Ionut Tripon
c2f4389e5f
Merge pull request #3254 from Trial97/remove_multimc
remove EditAccountDialog
2025-01-17 08:51:44 +02:00
Alexandru Ionut Tripon
b7d117cc77
Merge pull request #2737 from maskersss/log-upload
Truncate logs for mclo.gs to fit 25k line limit
2025-01-17 08:49:49 +02:00
seth
1cce582d56
Merge pull request #3292 from Trial97/fix_modrinth_link
fix modrinth link
2025-01-16 20:42:00 -05:00
seth
867aaf881e
Merge pull request #3286 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2025-01-16 20:40:44 -05:00
seth
14b348ffe7
Merge pull request #3285 from PrismLauncher/renovate/hendrikmuhs-ccache-action-1.x
chore(deps): update hendrikmuhs/ccache-action action to v1.2.16
2025-01-16 20:40:17 -05:00
Edgars Cīrulis
181610a8c1
Merge branch 'PrismLauncher:develop' into qt-6.8 2025-01-17 03:04:43 +02:00
seth
b7f2175718
Merge pull request #3179 from sshcrack/fix-inst-java
Fix $INST_JAVA not being set for auto download java
2025-01-16 20:03:26 -05:00
seth
7a1dc0e9c9
Merge pull request #1555 from Trial97/env_vars
correctly expand env vars for pre/post launch commands
2025-01-16 19:45:31 -05:00
Trial97
9317a675ed
removed unused type
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-16 17:07:44 +02:00
Trial97
7cb8deeb90
feat: updated flame mod resolution
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-16 17:05:36 +02:00
Trial97
bca517b8d3
fix appimage
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-16 12:10:19 +02:00
Trial97
e4ad4051c8
fix curseforge with empty loader as newest version
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-13 13:56:55 +02:00
Trial97
3ee73916ca
fix modrinth link
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-13 08:16:12 +02:00
github-actions[bot]
03834e1455 chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/6df24922a1400241dae323af55f30e4318a6ca65?narHash=sha256-dklw3AXr3OGO4/XT1Tu3Xz9n/we8GctZZ75ZWVqAVhk%3D' (2025-01-02)
  → 'github:NixOS/nixpkgs/bffc22eb12172e6db3c5dde9e3e5628f8e3e7912?narHash=sha256-8YVQ9ZbSfuUk2bUf2KRj60NRraLPKPS0Q4QFTbc%2Bc2c%3D' (2025-01-08)
2025-01-12 00:26:01 +00:00
renovate[bot]
54da41c625
chore(deps): update hendrikmuhs/ccache-action action to v1.2.16 2025-01-11 22:11:18 +00:00
Alexandru Ionut Tripon
f4f1d5f0d7
Merge pull request #3228 from Trial97/delete_block
Add settings to control the if prism should move the downloaded mods
2025-01-09 00:12:12 +02:00
Alexandru Ionut Tripon
fbd886e9db
Merge pull request #3255 from Trial97/propagate_load
propagate load component error
2025-01-09 00:11:48 +02:00
Alexandru Ionut Tripon
0fb99d837f
Merge pull request #3230 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2025-01-09 00:11:24 +02:00
Alexandru Ionut Tripon
cb8320ca41
Merge pull request #3278 from Trial97/remove_drop
remove drop label
2025-01-09 00:10:50 +02:00
Alexandru Ionut Tripon
33981b9870
Merge pull request #3215 from Trial97/fix_custom_instance_icons
Fix curseforge/modrinth instance icons
2025-01-09 00:10:39 +02:00
Alexandru Ionut Tripon
07a0a15457
Merge pull request #3277 from Trial97/drop_file
fix drop file on resource page
2025-01-08 10:46:55 +02:00
Trial97
0f9ea5c949
remove drop label
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-08 01:55:45 +02:00
Trial97
d40c8b704c
fix drop file on resource page
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-08 01:52:11 +02:00
github-actions[bot]
82a500f4e8 chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/5d67ea6b4b63378b9c13be21e2ec9d1afc921713?narHash=sha256-Pj39hSoUA86ZePPF/UXiYHHM7hMIkios8TYG29kQT4g%3D' (2024-12-11)
  → 'github:NixOS/nixpkgs/6df24922a1400241dae323af55f30e4318a6ca65?narHash=sha256-dklw3AXr3OGO4/XT1Tu3Xz9n/we8GctZZ75ZWVqAVhk%3D' (2025-01-02)
2025-01-05 00:25:34 +00:00
Trial97
6ebfcb91cf
propagate load component error
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-31 13:44:22 +02:00
Trial97
e01df73deb
remove EditAccountDialog
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-31 11:13:01 +02:00
Trial97
7dde35fef2
Add settings to control the if prism should move the downloaded mods
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-20 19:57:09 +02:00
Alexandru Ionut Tripon
0b6ce5525d
Merge pull request #3216 from Trial97/fix_auto_mod_provider
Fix automatically choose mod provider option
2024-12-19 09:16:59 +02:00
timoreo
f17faadf8a
Merge pull request #3174 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2024-12-18 19:54:44 +01:00
timoreo
c7b6977692
Merge pull request #3043 from Trial97/fix_flame_loader_match
fix the flame loaders match
2024-12-18 19:53:22 +01:00
timoreo
423d72a6e8
Merge pull request #3197 from Trial97/fix_curseforge_import
Do not fail curseforge import if modrinth file check fails
2024-12-18 19:48:05 +01:00
timoreo
2a2ed9f8bd
Merge pull request #3214 from kthchew/fix/manifest-symlink
Correct symbolic link destination on manifest (Mojang) Java download
2024-12-18 19:42:25 +01:00
Trial97
0412ed3f78
Fix automatically choose mod provider option
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-16 14:51:59 +02:00
Trial97
f4d93a08ba
Fix curseforge/modrinth instance icons
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-16 13:27:44 +02:00
Kenneth Chew
8d53242952
Correct symbolic link destination on manifest Java download
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
2024-12-16 03:30:09 -05:00
github-actions[bot]
706e263050 chore(nix): update lockfile
Flake lock file updates:

• Updated input 'flake-compat':
    'github:edolstra/flake-compat/9ed2ac151eada2306ca8c418ebd97807bb08f6ac?narHash=sha256-HRJ/18p%2BWoXpWJkcdsk9St5ZiukCqSDgbOGFa8Okehg%3D' (2024-11-27)
  → 'github:edolstra/flake-compat/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec?narHash=sha256-NeCCThCEP3eCl2l/%2B27kNNK7QrwZB1IJCrXfrbv5oqU%3D' (2024-12-04)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/970e93b9f82e2a0f3675757eb0bfc73297cc6370?narHash=sha256-jNRNr49UiuIwaarqijgdTR2qLPifxsVhlJrKzQ8XUIE%3D' (2024-11-28)
  → 'github:NixOS/nixpkgs/5d67ea6b4b63378b9c13be21e2ec9d1afc921713?narHash=sha256-Pj39hSoUA86ZePPF/UXiYHHM7hMIkios8TYG29kQT4g%3D' (2024-12-11)
2024-12-15 00:27:47 +00:00
Alexandru Ionut Tripon
507faf9f00
Merge pull request #3150 from kthchew/fix/unneeded-hr-exceptions
Remove unnecessary hardened runtime exceptions on macOS
2024-12-14 19:30:11 +02:00
Alexandru Ionut Tripon
c0803365ef
Merge pull request #3202 from kthchew/chore/bump-sparkle
[macOS] Update Sparkle to v2.6.4
2024-12-14 18:18:04 +02:00
Alexandru Ionut Tripon
02e527c15b
Merge pull request #3199 from kthchew/fix/launcher-settings-tab
Fix tab order in launcher settings
2024-12-14 18:17:08 +02:00
Alexandru Ionut Tripon
5659dad23d
Merge pull request #3198 from kthchew/fix/local-net-use-desc
[macOS] Add local network usage description
2024-12-14 18:16:34 +02:00
Alexandru Ionut Tripon
647bb04a0d
Merge pull request #3181 from Trial97/fix_mrpack_crash
fix crash with invalid mrpack format
2024-12-14 18:15:50 +02:00
Kenneth Chew
b6cd46ad27
Update Sparkle to v2.6.4
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
2024-12-14 00:42:08 -05:00
Kenneth Chew
c3712ba648
Fix tab order in launcher settings
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
2024-12-12 14:53:39 -05:00
Kenneth Chew
c3e44554ab
Add local network usage description
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
2024-12-12 14:43:28 -05:00
Trial97
a1c1c0b769
Do not fail curseforge import if modrinth file check fails
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-12 17:29:46 +02:00
Alexandru Ionut Tripon
15e58589d5
Merge pull request #3170 from leia-uwu/fix-icon-theme-path
[Linux] Don't use fallback icon search paths
2024-12-09 09:14:25 +02:00
Edgars Cirulis
eb8c375ec5
chore: update Qt to 6.8.1
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
2024-12-09 00:40:05 +02:00
Trial97
1dd0728a58
fix crash with invalid mrpack format
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-08 22:04:16 +02:00
sshcrack
cf2dcbd431
load meta first
Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com>
2024-12-08 18:27:57 +01:00
sshcrack
dedb7a2343
change order of steps
Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com>
2024-12-08 14:39:06 +01:00
leia uwu
614574f15c
fix icon theme search paths
using fallback search paths breaks qadwaitadecorations

Signed-off-by: leia uwu <leia@tutamail.com>
2024-12-06 23:26:46 -03:00
Alexandru Ionut Tripon
28a471777a
Merge pull request #2800 from QazCetelic/advanced-icon-dialog
Add search and support for subdirectories to icon picker
2024-12-06 12:37:45 +02:00
Alexandru Ionut Tripon
93526637cb
Merge pull request #3166 from PrismLauncher/renovate/actions-cache-4.x
chore(deps): update actions/cache action to v4.2.0
2024-12-06 08:47:04 +02:00
renovate[bot]
94c893bd86
chore(deps): update actions/cache action to v4.2.0 2024-12-05 20:29:11 +00:00
Alexandru Ionut Tripon
c99876f3d7
Merge pull request #3154 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2024-12-03 16:17:50 +02:00
Alexandru Ionut Tripon
a3b2f1a931
Merge pull request #3119 from guihkx/flathub-manifest-sync
Sync Flatpak manifest with Flathub's
2024-12-02 17:30:19 +02:00
TheKodeToad
488dc7815c
Merge pull request #3135 from Trial97/italic
Make FTB Import note italic to match others
2024-12-02 14:07:42 +00:00
Tayou
dbee4bee14
Merge pull request #3152 from leia-uwu/fix-system-icons
Fix system icons on Linux
2024-12-02 15:00:43 +01:00
github-actions[bot]
2cfd116947 chore(nix): update lockfile
Flake lock file updates:

• Updated input 'flake-compat':
    'github:edolstra/flake-compat/0f9255e01c2351cc7d116c072cb317785dd33b33?narHash=sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U%3D' (2023-10-04)
  → 'github:edolstra/flake-compat/9ed2ac151eada2306ca8c418ebd97807bb08f6ac?narHash=sha256-HRJ/18p%2BWoXpWJkcdsk9St5ZiukCqSDgbOGFa8Okehg%3D' (2024-11-27)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/23e89b7da85c3640bbc2173fe04f4bd114342367?narHash=sha256-y/MEyuJ5oBWrWAic/14LaIr/u5E0wRVzyYsouYY3W6w%3D' (2024-11-19)
  → 'github:NixOS/nixpkgs/970e93b9f82e2a0f3675757eb0bfc73297cc6370?narHash=sha256-jNRNr49UiuIwaarqijgdTR2qLPifxsVhlJrKzQ8XUIE%3D' (2024-11-28)
2024-12-01 00:30:03 +00:00
leia uwu
3f67ef968b
fix: icon search paths memory leak
Signed-off-by: leia uwu <leia@tutamail.com>
2024-11-30 12:51:39 -03:00
leia uwu
fd9c80db62
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>
2024-11-30 11:35:41 -03:00
Kenneth Chew
7e76638f2c
Only disable library validation if using ad-hoc signature
While not needed for regular releases, it is needed for CI when an Apple Developer account isn't available and you can only perform an ad-hoc signature.

Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
2024-11-29 21:18:07 -05:00
Kenneth Chew
884f15da1e
Remove unnecessary hardened runtime exceptions
These are cruft from an older time when the launcher was ad-hoc signed, and the OS couldn't tell the libraries and launcher were signed by the same party.

Additionally, add a workaround to send the necessary library injection to the game if Steam overlay is used despite library validation being on for the launcher.

Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
2024-11-29 21:18:06 -05:00
TheKodeToad
616aa002e1
Merge pull request #2875 from Jan200101/PR/switcheroo
[Linux] Integrate switcheroo-control for discrete GPU support
2024-11-28 20:02:58 +00:00
guihkx
e0faee7f26
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>
2024-11-27 14:35:15 -03:00
guihkx
f6770a847a
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>
2024-11-26 13:51:09 -03:00
guihkx
4a50e94967
flatpak: update xrandr to 1.5.3
Signed-off-by: guihkx <626206+guihkx@users.noreply.github.com>
2024-11-26 13:51:09 -03:00
Jan200101
514ec99a03
Integrate switcheroo-control for discrete GPU support
Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
2024-11-25 22:29:39 +01:00
TheKodeToad
6c062c48b4
Merge pull request #3130 from Kationor/trimversion
Improve MANIFEST.MF parsing
2024-11-25 10:05:02 +00:00
Kationor
b40a1973bf
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>
2024-11-24 19:30:59 +01:00
Trial97
a09af619ce
Make FTB Import note italic to match others
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-24 20:29:07 +02:00
TheKodeToad
835944387b
Merge pull request #3128 from QazCetelic/naming-clang-tidy
Add naming check in `.clang-tidy` file
2024-11-24 16:10:41 +00:00
QazCetelic
d927c539b5 Update CONTRIBUTING.md conventions section
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-11-23 19:55:40 +01:00
QazCetelic
14454faac8 Use old list style
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-11-23 18:21:03 +01:00
QazCetelic
2351531e84 Add naming checks to clang-tidy
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-11-23 18:15:07 +01:00
QazCetelic
33ff3b4f36 Fix naming
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-11-23 18:10:33 +01:00
seth
11e7b218d4
Merge pull request #3113 from PrismLauncher/update_flake_lock_action
chore(nix): adopt new darwin SDK pattern
2024-11-22 18:25:21 -05:00
seth
fbbd64ddb7
chore(nix): sync with upstream nixpkgs
Signed-off-by: seth <getchoo@tuta.io>
2024-11-22 17:33:16 -05:00
github-actions[bot]
41db87c8cf chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nix-filter':
    'github:numtide/nix-filter/776e68c1d014c3adde193a18db9d738458cd2ba4?narHash=sha256-SCHiL%2B1f7q9TAnxpasriP6fMarWE5H43t25F5/9e28I%3D' (2024-10-29)
  → 'github:numtide/nix-filter/f7653272fd234696ae94229839a99b73c9ab7de0?narHash=sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms%3D' (2024-11-13)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/4aa36568d413aca0ea84a1684d2d46f55dbabad7?narHash=sha256-Zwl8YgTVJTEum%2BL%2B0zVAWvXAGbWAuXHax3KzuejaDyo%3D' (2024-11-05)
  → 'github:NixOS/nixpkgs/23e89b7da85c3640bbc2173fe04f4bd114342367?narHash=sha256-y/MEyuJ5oBWrWAic/14LaIr/u5E0wRVzyYsouYY3W6w%3D' (2024-11-19)
2024-11-22 22:26:01 +00:00
Alexandru Ionut Tripon
79dd912126
Merge pull request #3091 from Awqre/develop
Lossless image asset compression
2024-11-20 10:12:34 +02:00
Awqre
00fa65c18b
Merge branch 'PrismLauncher:develop' into develop 2024-11-20 02:08:31 +00:00
seth
59db80cb0e
Merge pull request #3068 from Scrumplex/chore/update-nixos-readme
[Linux] Update Nix README
2024-11-19 10:09:11 -05:00
Alexandru Ionut Tripon
e5a4dfdf16
Merge pull request #2962 from AOSC-Tracking/upstream/detect-aosc-os-jdks
javautils: add a filter for detecting Java paths on AOSC OS
2024-11-19 13:44:57 +02:00
QazCetelic
6ca18c6266 Refactoring
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-11-18 19:19:02 +01:00
QazCetelic
e74592fa09 Code style conventions (camelCase, m_ prefix)
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-11-17 20:43:09 +01:00
QazCetelic
abbebff400 Removed bug when renaming icon file in nested folder while application is running
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-11-17 20:35:24 +01:00
Awqre
3c88eccf17
Merge branch 'PrismLauncher:develop' into develop 2024-11-17 08:19:35 +00:00
TheKodeToad
48235f5b71
Merge pull request #3083 from Trial97/fix_resource_search
Fix resource search
2024-11-15 23:09:28 +00:00
timoreo
736ecf266d
Merge pull request #3019 from Trial97/task_parent
fixed double deletion for tasks
2024-11-15 06:19:40 +01:00
timoreo
b2a37452be
Merge pull request #3024 from Trial97/leak
fix leak on resource search
2024-11-15 06:18:33 +01:00
timoreo
54f2e75b9d
Merge pull request #3070 from TheKodeToad/general-scrollbar
Add scrollbar to Settings -> Launcher -> Features
2024-11-15 06:16:54 +01:00
timoreo
f7620a43aa
Merge pull request #3094 from Trial97/remove_resource_warning
Fix warnings regarding Resource static cast
2024-11-15 06:15:55 +01:00
timoreo
bcbe5da87f
Merge pull request #3096 from Trial97/dangling_file
Fix file remaingin open after program closure
2024-11-15 06:14:25 +01:00
Alexandru Ionut Tripon
e735ea7e63
Merge pull request #3003 from PrismLauncher/update_flake_lock_action
chore(nix): update lockfile
2024-11-14 22:50:53 +02:00
Trial97
ce61ed2f86 Fix file remaingin open after program closure
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-14 13:27:16 +02:00
Trial97
172e43cd3f Fix warnings regarding Resource static cast
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-13 23:11:33 +02:00
Awqre
9f0b2bafd8 Further additional even better(er) compression
Truly unprecedented levels of optimization we're reaching here. (OxiPNG/ZopfliPNG/ECT/DeflOpt)

Signed-off-by: Awqre <32497511+Awqre@users.noreply.github.com>
2024-11-13 14:31:20 -05:00
txtsd
03b34e39f8
feat: Compression script with compressed images
Also modified genicons.sh

Signed-off-by: txtsd <code@ihavea.quest>
2024-11-13 19:04:31 +05:30
Awqre
885fc66c14 Lossless image compression
Applied lossless zopfli/ect/deflopt compression to PNG files including ones contained in ICO files.

Repacked ICO files with optidash icopack

Signed-off-by: Awqre <32497511+Awqre@users.noreply.github.com>
2024-11-12 20:17:17 -05:00
github-actions[bot]
f41380d91c chore(nix): update lockfile
Flake lock file updates:

• Updated input 'nix-filter':
    'github:numtide/nix-filter/3342559a24e85fc164b295c3444e8a139924675b?narHash=sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj%2BrL8sRQsSM%3D' (2024-03-11)
  → 'github:numtide/nix-filter/776e68c1d014c3adde193a18db9d738458cd2ba4?narHash=sha256-SCHiL%2B1f7q9TAnxpasriP6fMarWE5H43t25F5/9e28I%3D' (2024-10-29)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0?narHash=sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c%2BcHUJwA%3D' (2024-10-18)
  → 'github:NixOS/nixpkgs/4aa36568d413aca0ea84a1684d2d46f55dbabad7?narHash=sha256-Zwl8YgTVJTEum%2BL%2B0zVAWvXAGbWAuXHax3KzuejaDyo%3D' (2024-11-05)
2024-11-10 00:24:54 +00:00
Trial97
66883db645 Fix removed signal handler for filter resource
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-09 23:30:04 +02:00
Tayou
df45e5518f
Merge pull request #3081 from Trial97/fix_markdown
fix CONTRIBUTING.md formating
2024-11-09 22:19:41 +01:00
Trial97
4c8c6e543e fix contrributing
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-08 00:18:12 +02:00
Alexandru Ionut Tripon
1d9508c268
Merge pull request #3074 from Trial97/lock
Close the window using the invokeMethod to not block
2024-11-07 23:45:44 +02:00
Alexandru Ionut Tripon
7d03cb114d
Merge pull request #3031 from Trial97/lambda
do not capture by default all values in lambdas
2024-11-07 23:45:31 +02:00
Tayou
2fcf03ead3
Merge pull request #3036 from Trial97/typo4 2024-11-07 17:30:47 +01:00
TheKodeToad
012bbca197
JavaSettingsWidget -> JavaWizardWidget
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-07 14:51:43 +00:00
TheKodeToad
8132828ad7
Phrasing fix
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-07 13:36:33 +00:00
Trial97
9601fbf2d5
Close the window using the invokeMethod to not block
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-07 15:07:17 +02:00
TheKodeToad
6e55239cec
Merge pull request #3073 from TheKodeToad/fix-formatting-guidelines-oversight
Fix formatting oversight
2024-11-07 13:01:15 +00:00
TheKodeToad
900235642c
Fix formatting oversight
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-07 12:16:22 +00:00
TheKodeToad
f667f56fb7
Merge pull request #3067 from TheKodeToad/updated-formatting-guidelines
Updated formatting guidelines
2024-11-07 11:43:12 +00:00
TheKodeToad
e06b3e7a7a
Implement open global settings
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-07 00:47:32 +00:00
TheKodeToad
223b6b3b2b
Fix non-Linux build
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-07 00:22:22 +00:00
TheKodeToad
71e55f8829
Add scrollbar to Settings -> Launcher -> Features
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-07 00:11:27 +00:00
TheKodeToad
e6d4e5d78d
Unify instance and global settings pt.1
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-06 23:46:30 +00:00
TheKodeToad
8ecc21c093
Make Ryex's suggested change
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-06 22:12:22 +00:00
Sefa Eyeoglu
0a80da28fa
chore: update Nix readme
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2024-11-06 19:29:57 +01:00
TheKodeToad
b67d400f60
Update formatting guidelines
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-11-06 16:27:03 +00:00
timoreo
1ab10bdd84
Merge pull request #3060 from Ryex/ci/bump_linux_qt
ci: bump linux qt version
2024-11-05 11:25:27 +01:00
Rachel Powers
ba6743d134
ci: libxcb-curcsor_dev ?
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2024-11-05 01:30:55 -07:00
Rachel Powers
d63a336411
ci: bump linux qt version
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2024-11-05 01:10:38 -07:00
Alexandru Ionut Tripon
4a92573235
Merge pull request #3042 from Trial97/isportable
use isPortable to determine if the MSAStep should check for url handler
2024-11-03 11:55:55 +02:00
Alexandru Ionut Tripon
db3d796275
Merge pull request #3045 from Trial97/ask_retry3
disable retry for modrinth currentVersions API
2024-11-03 11:55:37 +02:00
Trial97
6f2f3c2d3b
disable retry for modrinth currentVersions API
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-01 17:34:29 +02:00
Trial97
f77f0207f7
fix the flame loaders match
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-01 14:30:47 +02:00
Trial97
99cfb62370
use isPortable to determine if the MSAStep should check for url handler
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-01 11:03:47 +02:00
Trial97
2b590f24ed
remove task parent from constuctor
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-11-01 00:11:45 +02:00
Trial97
670c932f32 Fix installed typo
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-31 14:51:18 +02:00
TheKodeToad
ba07a136dc
Merge pull request #3035 from PrismLauncher/remove-BasicFolderLoadTask
Remove BasicFolderLoadTask
2024-10-31 09:17:27 +00:00
TheKodeToad
9ae16921bc
Remove BasicFolderLoadTask
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-30 21:10:43 +00:00
Tayou
887e4a964a
Merge pull request #3010 from Trial97/lock3 2024-10-30 21:56:17 +01:00
seth
8faff9ac6a
Merge pull request #3030 from Trial97/sys 2024-10-30 15:16:38 -04:00
Trial97
0e80aae1b8
Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into lambda 2024-10-30 19:35:25 +02:00
TheKodeToad
c142d38f08
Merge pull request #1588 from TheKodeToad/resource-meta
Implement tracking and updates for files other than mods
2024-10-30 17:03:54 +00:00
Trial97
82bffabb69
do not capture by default all values in lambdas
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-30 18:16:43 +02:00
Trial97
14c95b9d37
fix macos system detection
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-30 17:15:42 +02:00
Alexandru Ionut Tripon
b7598c6a41
Merge pull request #2331 from michaeltyson/develop
Support for launching from command line in offline mode
2024-10-30 09:49:33 +02:00
Michael Tyson
ced2f9d1a0 Applied clang formatting
Signed-off-by: Michael Tyson <michael@atastypixel.com>
2024-10-30 12:51:34 +11:00
Michael Tyson
5661444414 Removed “--name” flag in favour of specifying offline player name via “--offline” flag
Signed-off-by: Michael Tyson <michael@atastypixel.com>
2024-10-30 12:37:14 +11:00
Michael Tyson
afa1e0599a Improved formatting
Signed-off-by: Michael Tyson <michael@atastypixel.com>
2024-10-30 12:34:33 +11:00
Michael Tyson
43e4481b70 Support for launching from command line in offline mode
This allows launching an offline instance with --offline --name=OfflineName. This is useful for playing split screen by creating two side-by-side instances, which is impossible online, unless one is using two separate paid accounts.

With this PR, it makes it possible to launch from a script - otherwise, one has to launch manually, which is a pain, or create offline profiles for each instance, which interferes with some functionality like skins (my autistic son takes great issue with his skin not being visible, when using offline profiles!).

Implementation is based on MultiMC, which supports this feature.

See also https://github.com/PrismLauncher/PrismLauncher/issues/1059 for discussion.

Signed-off-by: Michael Tyson <michael@atastypixel.com>
2024-10-30 12:30:20 +11:00
Tayou
65b0fdf08d
Merge pull request #3027 from benthetechguy/metadata 2024-10-29 23:06:01 +01:00
Ben Westover
f6511c601e
Fix some typos
Signed-off-by: Ben Westover <me@benthetechguy.net>
2024-10-29 20:45:10 +00:00
Ben Westover
fd109c4740
Shorten metainfo.xml app summary
Signed-off-by: Ben Westover <me@benthetechguy.net>
2024-10-29 20:45:06 +00:00
TheKodeToad
2368521a9a
Add missing semicolon
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-29 17:54:03 +00:00
TheKodeToad
d49e6187cd
Make requested changes
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-29 17:17:51 +00:00
TheKodeToad
c1c198b84b
Combine if statements
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-29 15:45:30 +00:00
TheKodeToad
e7ff3146fe
Merge pull request #1953 from Trial97/account2
Auto reauthenticate account if expired
2024-10-29 14:08:50 +00:00
Trial97
8e7d84d05b
fix leak on resource search
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-29 00:40:13 +02:00
TheKodeToad
d157159106
Fix license headers and formatting
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 22:29:43 +00:00
TheKodeToad
bd8b30c606
Implement link for InfoFrame::updateWithResource
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 22:17:16 +00:00
TheKodeToad
f56a8feade
Merge pull request #2986 from Trial97/bump_10.x
bump develop to 10
2024-10-28 21:12:53 +00:00
TheKodeToad
f3cae427f1
Fix tests (make them green)
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 21:09:45 +00:00
Alexandru Ionut Tripon
51b7abec24
Merge pull request #3005 from Trial97/installed
add installed text to version select
2024-10-28 22:55:40 +02:00
Alexandru Ionut Tripon
d90ed89a30
Merge pull request #2989 from Trial97/what
add extra protection against empty download link
2024-10-28 22:55:21 +02:00
TheKodeToad
ad1d5ddcdf
Revert unwise refactor
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 20:42:59 +00:00
Alexandru Ionut Tripon
2dee936246
Merge pull request #2837 from Trial97/translate_standeard_butons
translate standard buttons
2024-10-28 22:31:47 +02:00
Alexandru Ionut Tripon
a0a74612e1
Merge pull request #1894 from Trial97/maximize
Fixed the maximized option on the newer Minecraft versions
2024-10-28 22:30:09 +02:00
Alexandru Ionut Tripon
fbd3aa36c4
Merge pull request #3006 from Trial97/relase_type
add release_type filer for mod/modpacks
2024-10-28 22:29:38 +02:00
Trial97
94f65f8727
add qassert
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 22:27:40 +02:00
TheKodeToad
2b22a1aebe
Reimplement View Homepage
I removed it for some reason, but now it's back for all pages!

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 20:14:56 +00:00
TheKodeToad
354a0ed46e
Fix column sizes
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 18:01:01 +00:00
Trial97
9cdda4377b
remove message box
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 19:19:06 +02:00
TheKodeToad
49044e23de
Fix Export List placement
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 16:21:00 +00:00
TheKodeToad
f57ca1e79e
Add Change Version action to all pages
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 16:07:41 +00:00
Trial97
0f42f3cc5c
Added warning regarding maximized option
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 17:19:18 +02:00
Trial97
81c5866fa9
Made sure that the qt version check is in place
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 17:19:18 +02:00
Trial97
1be29076ea
When java disappoints try C++
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 17:19:18 +02:00
Trial97
3c17f606ed
Patched the maximized option on the newer Minecraft versions
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 17:19:18 +02:00
Trial97
09a118e85e
expand env from wrapped cmd
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 16:23:33 +02:00
Trial97
c12beb43a0
correctly expand env vars
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 16:23:33 +02:00
Trial97
08f5148a9a
Added mouse interactions for labels in ProgressDialog
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 16:22:52 +02:00
Trial97
14ad720c58
fix conflict
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 16:21:40 +02:00
Trial97
0ee5665c95
Revert "Removed auto reauthenticate"
This reverts commit 400b518bc2.

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 16:21:39 +02:00
Trial97
bd82737efb
translate standard buttons
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 15:46:47 +02:00
Trial97
f75e049675
add release_type filer for mod/modpacks
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 15:36:47 +02:00
Tayou
1f8ce9eb92
Merge pull request #3002 from Trial97/typo2 2024-10-28 14:19:53 +01:00
Alexandru Ionut Tripon
d4bf61da50
Merge pull request #3007 from Trial97/open_source
add open source filter
2024-10-28 15:04:33 +02:00
Tayou
33754db9b9
Merge pull request #3009 from Trial97/order 2024-10-28 14:04:20 +01:00
TheKodeToad
1809858fc7
Ignore .index in ExportPackDialog
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 11:01:30 +00:00
Trial97
0cafac84ac
replace currentData with itemData on QComboBox::currentIndexChanged slots
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 12:54:21 +02:00
Trial97
9eb1ce3ad7
replace unzipping with unpacking
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-28 12:39:26 +02:00
TheKodeToad
6a6fe60a5b
Revert "Use non-mod metadata in ModrinthPackExportTask"
Out-of-scope

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-28 10:29:49 +00:00
Alexandru Ionut Tripon
6338e2b362
Update launcher/ui/widgets/ModFilterWidget.ui
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2024-10-28 00:34:41 +02:00
txtsd
b1113f4293
Merge pull request #3008 from Trial97/about
change contributors name
2024-10-28 01:01:43 +05:30
Trial97
c4cd7cf6c1
change ZekeZ name
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 13:17:58 +02:00
Trial97
11d4d948aa
lock m_instanceExtras
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 12:36:14 +02:00
Trial97
61448a61ea
swap search with filter button for modpacks
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 12:06:45 +02:00
Trial97
f373a5cea0
change contributors name
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 11:58:34 +02:00
Trial97
935b2e8fb2
add open source filter
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 11:56:48 +02:00
Trial97
b1c8ab9350
add installed text to version select
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 10:56:14 +02:00
Trial97
b593ff09e9
fix #3001
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 03:05:56 +02:00
Trial97
44894a29b1
rename snake_case to camelCase
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 03:05:30 +02:00
Trial97
685d3c79ac
fix unzipping typo
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-27 03:02:13 +03:00
TheKodeToad
b66d6b2812
Unhardcode PREFIXES
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-26 13:24:03 +01:00
seth
5b8ea89092
Merge pull request #2992 from getchoo-contrib/nix/sync 2024-10-25 20:56:44 -04:00
TheKodeToad
13e13ea8fc
Use non-mod metadata in ModrinthPackExportTask
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-25 19:13:41 +01:00
seth
63b10738b2
chore(nix): use self for version
Signed-off-by: seth <getchoo@tuta.io>
2024-10-25 02:35:53 -04:00
seth
a5c554cf6e
chore(nix): sync with nixpkgs
Signed-off-by: seth <getchoo@tuta.io>
2024-10-25 02:35:53 -04:00
seth
6a2ed4a631
Merge pull request #2982 from TheKodeToad/launcher-level-autoinstalljava
Use Launcher log level in AutoInstallJava
2024-10-25 02:23:02 -04:00
seth
b7152e7010
Merge pull request #2963 from PrismLauncher/renovate/actions-cache-4.x
chore(deps): update actions/cache action to v4.1.2
2024-10-25 02:22:12 -04:00
seth
d766de5fbf
Merge pull request #2980 from Arcitec/flatpak-enhancements
fix: bring back Flathub manifest improvements to the repo
2024-10-25 02:19:09 -04:00
Trial97
8daa1219a5
add extra protection against empty download link
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-25 01:13:05 +03:00
Trial97
3bc40120cd
bump develop to 10
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-25 00:17:34 +03:00
Arcitec
4ce025c0a2 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>
2024-10-24 17:36:45 +02:00
TheKodeToad
d8702e1357
Use Launcher log level in AutoInstallJava
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-24 12:46:01 +01:00
TheKodeToad
9fe81ff564
Merge pull request #2975 from Ryex/fix/version-page-load-speed
fix: don't hang the ui for a full version load
2024-10-24 11:24:35 +01:00
TheKodeToad
5eff9b0934
Fix column crashes
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-24 01:23:11 +01:00
TheKodeToad
8e586c883c
Fix Modrinth update crashing
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-24 01:16:06 +01:00
Rachel Powers
7bd04ae928
fix don't hang the ui for a full version load
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2024-10-23 16:23:10 -07:00
TheKodeToad
4a7d624e9b
Merge pull request #2972 from TheKodeToad/fix-clean-env
Fix launching Minecraft in portable Linux
2024-10-23 22:30:42 +01:00
TheKodeToad
92a906e0d4
Move everything in CheckUpdateTask::Update
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-23 14:58:52 +01:00
TheKodeToad
24cecf7b63
Fix compile
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-23 14:42:54 +01:00
TheKodeToad
89a327d363
Merge remote-tracking branch 'upstream/develop' into resource-meta
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-23 14:19:07 +01:00
TheKodeToad
e7cd8fe078
Fix CleanEnviroment()'s usage of stripVariableEntries
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-23 13:44:39 +01:00
Kaiyang Wu
3d82bc37af
javautils: add a filter for detecting Java paths on AOSC OS
Signed-off-by: Kaiyang Wu <origincode@aosc.io>
2024-10-22 11:58:00 -07:00
TheKodeToad
f13e875f0d
Try to fix ModrinthCheckUpdate (not working)
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-22 18:46:10 +01:00
Alexandru Ionut Tripon
79439a0217
Merge pull request #2958 from Trial97/crash_QsaveFile
skip parsing open QSaveFile temprary files as resources
2024-10-22 17:38:32 +03:00
renovate[bot]
210d0d8aa2
chore(deps): update actions/cache action to v4.1.2 2024-10-22 13:48:29 +00:00
Trial97
836aebc0d9
fix small leak
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-22 13:43:16 +03:00
Trial97
73d33f93b3
Replaced QSet with QHash
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-22 09:41:00 +03:00
Trial97
562c301326
skip QSaveFile temprary files
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-22 01:22:31 +03:00
Alexandru Ionut Tripon
a43ebefc97
Merge pull request #2954 from Trial97/skin_warn
do not try to import skin if path is empty
2024-10-22 00:14:16 +03:00
Alexandru Ionut Tripon
69028969f1
Update launcher/ui/dialogs/skins/SkinManageDialog.cpp
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
2024-10-21 22:59:51 +03:00
Alexandru Ionut Tripon
671d5270eb
Merge pull request #2953 from TheKodeToad/portable-fix
Fix removing portable.txt on Linux portable build
2024-10-21 22:44:44 +03:00
Trial97
9a5b773e69
do not try to import skin if path is empty
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-21 19:28:34 +03:00
TheKodeToad
f3f4c44620
Fix removing portable.txt on Linux portable build
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-21 16:30:08 +01:00
TheKodeToad
b049e92697
Merge pull request #2947 from Trial97/fix_initial_setup
do not require java if auto-download is enabled
2024-10-21 15:57:47 +01:00
Tayou
ea703ba26e
Merge pull request #2948 from TheKodeToad/vc-redist-norestart 2024-10-21 16:54:18 +02:00
TheKodeToad
78e24962f9
Fix /norestart
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-21 14:41:48 +01:00
Trial97
fcadbbb739
do not require java if auto-download is enabled
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-21 16:17:48 +03:00
Alexandru Ionut Tripon
a8c3a49cb9
Merge pull request #2938 from Trial97/fix_sparkle
fix sparkle signature
2024-10-21 00:05:57 +03:00
Trial97
0586d38e03
fix sparkle signature
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-10-20 23:53:23 +03:00
Edgars Cirulis
0beaa94311
ci: uprev qt install action to v4
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
2024-10-10 15:26:36 +03:00
Edgars Cirulis
b39098dbc5
ci: fix qt-6.8 workflow
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
2024-10-10 15:26:19 +03:00
Edgars Cirulis
5b6d551650
chore: update Qt to 6.8.0
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
2024-10-10 03:45:35 +03:00
TheKodeToad
4b66320f63
Move x-prism-launcher to unnamed table for consistency
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-08 22:51:03 +01:00
TheKodeToad
a76f37760c
Fix log spam when opening files from older versions
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-08 22:30:50 +01:00
TheKodeToad
538815e033
Merge remote-tracking branch 'upstream/develop' into resource-meta
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-08 17:16:10 +01:00
TheKodeToad
3fae659b30
Merge remote-tracking branch 'upstream/develop' into resource-meta
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-10-08 17:15:42 +01:00
QazCetelic
f641f3acda Refactoring
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-24 12:32:49 +02:00
QazCetelic
b675406b1a Split set creation into a separate function for readability
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-24 10:16:26 +02:00
QazCetelic
a58e81d744 snake_case to camelCase
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-24 09:58:47 +02:00
QazCetelic
e35faa5522 Feedback I
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-15 09:04:23 +02:00
QazCetelic
7b49fd6508 fixes for ci errors IV
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-11 10:07:05 +02:00
QazCetelic
f7f7c4b43e fixes for ci errors III
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-11 09:55:27 +02:00
QazCetelic
506ec64273 fixes for ci errors II
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-11 09:43:45 +02:00
QazCetelic
0a576a0f67 fixes for ci errors
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-11 09:31:16 +02:00
QazCetelic
40c3866f63 clang-tidy formatting
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-11 09:22:28 +02:00
QazCetelic
6b0007291b Added search and support for subdirectories to icon picker
Signed-off-by: QazCetelic <qaz.cetelic@protonmail.com>
2024-09-10 22:30:29 +02:00
maskers
6a12c43c78 fix off by one error
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
2024-08-25 16:02:32 +03:00
TheKodeToad
43fc9ba932
Confirm Truncate -> Truncation
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-08-25 13:05:08 +01:00
maskers
762d24bd02 don't mention prism in the middle of the log
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
2024-08-22 21:44:53 +03:00
maskers
a910337e9d
apply suggestion from code review
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: maskers <97827489+maskersss@users.noreply.github.com>
2024-08-22 21:42:05 +03:00
maskers
6d017b5f0b fix formatting
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
2024-08-21 22:39:27 +03:00
maskers
65f8526152
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>
2024-08-21 22:14:11 +03:00
maskers
99bd4a8937
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>
2024-08-21 19:42:35 +03:00
maskers
cf914526bf add a warning about the log being too large
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
2024-08-21 15:59:51 +03:00
maskers
858f6aa9b8 fix formatting
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
2024-08-20 22:16:40 +03:00
maskers
e6f30c0ebe fix formatting
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
2024-08-20 22:14:40 +03:00
maskers
014fc14291 truncate logs for mclo.gs upload to fit 25k line limit
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
2024-08-20 21:53:05 +03:00
TheKodeToad
60bd8104c4
Merge remote-tracking branch 'upstream/develop' into resource-meta
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-04-26 01:53:51 +01:00
TheKodeToad
9ce10231ed
Remove redundant event handler
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-04-26 01:44:43 +01:00
TheKodeToad
803e26a401
Make actions more consistent
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-04-26 01:40:18 +01:00
TheKodeToad
27780cc7ae
Add update UI for all resource types
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-03-06 14:56:04 +00:00
TheKodeToad
82d0f204e2
De-OOP ProviderCapabilities
There was no reason for it to be a class, and imo it created quite a code-smell needing to initialise it everywhere.

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-01-26 03:06:05 +00:00
TheKodeToad
97ee0a19b5
Refactor updating mechanisms to work with all resources
Summary:
- It compiles
- I need to go to bed

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-01-26 02:53:57 +00:00
TheKodeToad
2c18d0f1a5
Store current version in packwiz metadata (temporarily using x-prismlauncher-version-number)
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-01-25 15:41:34 +00:00
TheKodeToad
35b5c8097c
Merge branch 'develop' into resource-meta
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2024-01-23 23:20:40 +00:00
TheKodeToad
f4940e26c6
Merge remote-tracking branch 'upstream/develop' into resource-meta
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-11-12 19:02:14 +00:00
TheKodeToad
179abfa03e
Resource provider column
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-09-01 22:05:44 +01:00
TheKodeToad
ad16d61208
Resource metadata writing
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-09-01 17:42:05 +01:00
TheKodeToad
6aecbfc38f
Fix crashes
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-09-01 12:50:20 +01:00
TheKodeToad
ee48766996
More generalistaion for ResourceFolderModels
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-09-01 00:27:05 +01:00
TheKodeToad
93876e27f8
Generalise resource metadata
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-08-31 17:54:22 +01:00
363 changed files with 5346 additions and 5470 deletions

View file

@ -1,5 +1,23 @@
Checks:
- modernize-use-using
- readability-avoid-const-params-in-decls
- misc-unused-parameters,
- readability-identifier-naming
SystemHeaders: false
# ^ 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 }

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.14
uses: hendrikmuhs/ccache-action@v1.2.17
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.0
uses: actions/cache@v4.2.1
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
@ -377,11 +377,13 @@ 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 "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "$ENTITLEMENTS_FILE" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
mv "PrismLauncher.app" "Prism Launcher.app"
- name: Notarize (macOS)

View file

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

29
.github/workflows/stale.yml vendored Normal file
View file

@ -0,0 +1,29 @@
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 9)
set(Launcher_VERSION_MINOR 3)
set(Launcher_VERSION_MAJOR 10)
set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0")
@ -306,6 +306,8 @@ 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)
@ -320,6 +322,8 @@ 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,16 +2,59 @@
## Code formatting
Try to follow the existing formatting.
If there is no existing formatting, you may use `clang-format` with our included `.clang-format` configuration.
All files are formatted with `clang-format` using the configuration in `.clang-format`. Ensure it is run on changed files before committing!
In general, in order of importance:
Please also follow the project's conventions for C++:
- 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.
- 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.
## Signing your work

View file

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

View file

@ -59,8 +59,6 @@
#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"
@ -243,6 +241,7 @@ 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" } });
@ -258,6 +257,10 @@ 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");
@ -272,8 +275,9 @@ 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_instanceIdToLaunch.isEmpty()) {
std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl;
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;
m_status = Application::Failed;
return;
}
@ -398,6 +402,10 @@ 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;
@ -606,6 +614,7 @@ 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");
@ -791,8 +800,6 @@ 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>();
@ -836,7 +843,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,
[&](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
[this](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
qDebug() << "<> Instance icons initialized.";
}
@ -1071,11 +1078,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
bool Application::createSetupWizard()
{
bool javaRequired = [&]() {
if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_settings->get("AutomaticJavaDownload").toBool()) {
bool javaRequired = [this]() {
if (BuildConfig.JAVA_DOWNLOADER_ENABLED && settings()->get("AutomaticJavaDownload").toBool()) {
return false;
}
bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool();
bool ignoreJavaWizard = settings()->get("IgnoreJavaWizard").toBool();
if (ignoreJavaWizard) {
return false;
}
@ -1089,8 +1096,8 @@ bool Application::createSetupWizard()
QString actualPath = FS::ResolveExecutable(currentJavaPath);
return actualPath.isNull();
}();
bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() &&
!m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool();
bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !settings()->get("AutomaticJavaDownload").toBool() &&
!settings()->get("AutomaticJavaSwitch").toBool() && !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());
@ -1210,7 +1217,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse;
}
launch(inst, true, false, targetToJoin, accountToUse);
launch(inst, !m_offline, false, targetToJoin, accountToUse, m_offlineName);
return;
}
}
@ -1309,6 +1316,8 @@ 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()) {
@ -1338,7 +1347,7 @@ void Application::messageReceived(const QByteArray& message)
}
}
launch(instance, true, false, serverObject, accountObject);
launch(instance, !offline, false, serverObject, accountObject, offlineName);
} else {
qWarning() << "Received invalid message" << message;
}
@ -1376,7 +1385,12 @@ bool Application::openJsonEditor(const QString& filename)
}
}
bool Application::launch(InstancePtr instance, bool online, bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse)
bool Application::launch(InstancePtr instance,
bool online,
bool demo,
MinecraftTarget::Ptr targetToJoin,
MinecraftAccountPtr accountToUse,
const QString& offlineName)
{
if (m_updateRunning) {
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
@ -1397,6 +1411,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
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,7 +211,8 @@ class Application : public QApplication {
bool online = true,
bool demo = false,
MinecraftTarget::Ptr targetToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr);
MinecraftAccountPtr accountToUse = nullptr,
const QString& offlineName = QString());
bool kill(InstancePtr instance);
void closeCurrentWindow();
@ -301,6 +302,8 @@ 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,3 +411,8 @@ void BaseInstance::updateRuntimeContext()
{
// NOOP
}
bool BaseInstance::isLegacy()
{
return traits().contains("legacyLaunch") || traits().contains("alphaLaunch");
}

View file

@ -269,6 +269,8 @@ 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,13 +345,12 @@ set(MINECRAFT_SOURCES
minecraft/mod/TexturePackFolderModel.h
minecraft/mod/TexturePackFolderModel.cpp
minecraft/mod/ShaderPackFolderModel.h
minecraft/mod/tasks/BasicFolderLoadTask.h
minecraft/mod/tasks/ModFolderLoadTask.h
minecraft/mod/tasks/ModFolderLoadTask.cpp
minecraft/mod/tasks/ResourceFolderLoadTask.h
minecraft/mod/tasks/ResourceFolderLoadTask.cpp
minecraft/mod/tasks/LocalModParseTask.h
minecraft/mod/tasks/LocalModParseTask.cpp
minecraft/mod/tasks/LocalModUpdateTask.h
minecraft/mod/tasks/LocalModUpdateTask.cpp
minecraft/mod/tasks/LocalResourceUpdateTask.h
minecraft/mod/tasks/LocalResourceUpdateTask.cpp
minecraft/mod/tasks/LocalDataPackParseTask.h
minecraft/mod/tasks/LocalDataPackParseTask.cpp
minecraft/mod/tasks/LocalResourcePackParseTask.h
@ -909,7 +908,6 @@ 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
@ -923,17 +921,12 @@ 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
@ -1067,8 +1060,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/BlockedModsDialog.h
ui/dialogs/ChooseProviderDialog.h
ui/dialogs/ChooseProviderDialog.cpp
ui/dialogs/ModUpdateDialog.cpp
ui/dialogs/ModUpdateDialog.h
ui/dialogs/ResourceUpdateDialog.cpp
ui/dialogs/ResourceUpdateDialog.h
ui/dialogs/InstallLoaderDialog.cpp
ui/dialogs/InstallLoaderDialog.h
@ -1084,14 +1077,12 @@ 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/JavaSettingsWidget.cpp
ui/widgets/JavaSettingsWidget.h
ui/widgets/JavaWizardWidget.cpp
ui/widgets/JavaWizardWidget.h
ui/widgets/LabeledToolButton.cpp
ui/widgets/LabeledToolButton.h
ui/widgets/LanguageSelectionWidget.cpp
@ -1127,6 +1118,10 @@ 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
@ -1170,7 +1165,6 @@ 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
@ -1178,7 +1172,6 @@ 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
@ -1201,6 +1194,8 @@ 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
@ -1220,7 +1215,6 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/ScrollMessageBox.ui
ui/dialogs/BlockedModsDialog.ui
ui/dialogs/ChooseProviderDialog.ui
ui/dialogs/skins/SkinManageDialog.ui
)
@ -1295,6 +1289,7 @@ 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
@ -1303,6 +1298,10 @@ 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(), [&] {
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
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(), [&] {
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
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 = [&](QString src_path, QString relative_dst_path) {
auto copy_file = [this, dryRun, src, dst, opt, &err](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 = [&](QString src_path, QString relative_dst_path) {
auto link_file = [this, dst](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, [&]() {
connect(&m_linkServer, &QLocalServer::newConnection, this, [this, &gotResults]() {
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, [&]() { emit finishedPrivileged(gotResults); });
connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [this, gotResults]() { 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 = [&](QString src_path, QString relative_dst_path) {
auto cloneFile = [this, dryRun, dst, &err](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, [&](bool gotResults) {
connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&got_priv_results, &loop](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 = [&]() {
auto removeNow = [this, &front_bookmark, &back_bookmark, &currentItem]() {
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.get()));
values.append(new InstanceSettingsPage(onesix));
auto logMatcher = inst->getLogFileMatcher();
if (logMatcher) {
values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher));

View file

@ -43,6 +43,7 @@
#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"
@ -234,10 +235,15 @@ void LaunchController::login()
if (!m_session->wants_online) {
// we ask the user for a player name
bool ok = false;
auto name = askOfflineName(m_session->player_name, m_session->demo, ok);
if (!ok) {
tryagain = false;
break;
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;
}
m_session->MakeOffline(name);
// offline flavored game from here :3
@ -287,10 +293,8 @@ void LaunchController::login()
continue;
}
case AccountState::Expired: {
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);
if (reauthenticateCurrentAccount())
continue;
return;
}
case AccountState::Disabled: {
@ -314,6 +318,33 @@ 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,6 +56,8 @@ 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; }
@ -76,6 +78,7 @@ class LaunchController : public Task {
void decideAccount();
bool askPlayDemo();
QString askOfflineName(QString playerName, bool demo, bool& ok);
bool reauthenticateCurrentAccount();
private slots:
void readyForLaunch();
@ -87,6 +90,7 @@ 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 (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);
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);
addTask(m_update_task);
}
@ -91,12 +91,8 @@ 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) {
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model)
model->uninstallMod(filename, true);
else
m_pack_model->uninstallResource(filename);
}
if (!name.isEmpty() && filename != m_pack_version.fileName)
m_pack_model->uninstallResource(filename, true);
}
void ResourceDownloadTask::downloadFailed(QString reason)

View file

@ -22,7 +22,7 @@
#include "net/NetJob.h"
#include "tasks/SequentialTask.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "modplatform/ModIndex.h"
class ResourceFolderModel;
@ -50,7 +50,7 @@ class ResourceDownloadTask : public SequentialTask {
QString m_custom_target_folder;
NetJob::Ptr m_filesNetJob;
LocalModUpdateTask::Ptr m_update_task;
LocalResourceUpdateTask::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 = [&](QChar lastChar, QChar currentChar) {
auto classChange = [&currentSection](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, [&](QLocalSocket::LocalSocketError socketError) {
connect(&socket, &QLocalSocket::errorOccurred, this, [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, [&]() {
connect(&socket, &QLocalSocket::disconnected, this, [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, QString path, QObject* parent) : QAbstractListModel(parent)
IconList::IconList(const QStringList& builtinPaths, const QString& path, QObject* parent) : QAbstractListModel(parent)
{
QSet<QString> builtinNames;
// add builtin icons
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& builtinPath : builtinPaths) {
QDir instanceIcons(builtinPath);
auto fileInfoList = instanceIcons.entryInfoList(QDir::Files, QDir::Name);
for (const auto& fileInfo : fileInfoList) {
builtinNames.insert(fileInfo.baseName());
}
}
for (auto& builtinName : builtinNames) {
for (const auto& builtinName : builtinNames) {
addThemeIcon(builtinName);
}
m_watcher.reset(new QFileSystemWatcher());
is_watching = false;
m_isWatching = false;
connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &IconList::directoryChanged);
connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &IconList::fileChanged);
@ -77,91 +77,131 @@ IconList::IconList(const QStringList& builtinPaths, QString path, QObject* paren
void IconList::sortIconList()
{
qDebug() << "Sorting icon list...";
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { return a.m_key.localeAwareCompare(b.m_key) < 0; });
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;
});
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 new_dir(path);
if (m_dir.absolutePath() != new_dir.absolutePath()) {
m_dir.setPath(path);
QDir newDir(path);
if (m_dir.absolutePath() != newDir.absolutePath()) {
if (!path.startsWith(m_dir.absolutePath()))
m_dir.setPath(path);
m_dir.refresh();
if (is_watching)
if (m_isWatching)
stopWatching();
startWatching();
}
if (!m_dir.exists())
if (!FS::ensureFolderPathExists(m_dir.absolutePath()))
return;
if (!m_dir.exists() && !FS::ensureFolderPathExists(m_dir.absolutePath()))
return;
m_dir.refresh();
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) {
const QStringList newFileNamesList = getIconFilePaths();
const QSet<QString> newSet = toStringSet(newFileNamesList);
QSet<QString> currentSet;
for (const MMCIcon& it : m_icons) {
if (!it.has(IconType::FileBased))
continue;
current_list.push_back(it.m_images[IconType::FileBased].filename);
currentSet.insert(it.m_images[IconType::FileBased].filename);
}
#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
QSet<QString> toRemove = currentSet - newSet;
QSet<QString> toAdd = newSet - currentSet;
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();
for (const QString& removedPath : toRemove) {
qDebug() << "Removing icon " << removedPath;
QFileInfo removedFile(removedPath);
QString key = m_dir.relativeFilePath(removedFile.absoluteFilePath());
int idx = getIconIndex(key);
if (idx == -1)
continue;
icons[idx].remove(IconType::FileBased);
if (icons[idx].type() == IconType::ToBeDeleted) {
m_icons[idx].remove(FileBased);
if (m_icons[idx].type() == ToBeDeleted) {
beginRemoveRows(QModelIndex(), idx, idx);
icons.remove(idx);
m_icons.remove(idx);
reindex();
endRemoveRows();
} else {
dataChanged(index(idx), index(idx));
}
m_watcher->removePath(remove);
m_watcher->removePath(removedPath);
emit iconUpdated(key);
}
for (auto add : to_add) {
qDebug() << "Adding " << add;
for (const QString& addedPath : toAdd) {
qDebug() << "Adding icon " << addedPath;
QFileInfo addfile(add);
QString key = addfile.completeBaseName();
QFileInfo addfile(addedPath);
QString relativePath = m_dir.relativeFilePath(addfile.absoluteFilePath());
QString key = QFileInfo(relativePath).completeBaseName();
QString name = formatName(m_dir, addfile);
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);
if (addIcon(key, name, addfile.filePath(), IconType::FileBased)) {
m_watcher->addPath(addedPath);
emit iconUpdated(key);
}
}
@ -171,24 +211,24 @@ void IconList::directoryChanged(const QString& path)
void IconList::fileChanged(const QString& path)
{
qDebug() << "Checking " << path;
qDebug() << "Checking icon " << path;
QFileInfo checkfile(path);
if (!checkfile.exists())
return;
QString key = checkfile.completeBaseName();
QString key = m_dir.relativeFilePath(checkfile.absoluteFilePath());
int idx = getIconIndex(key);
if (idx == -1)
return;
QIcon icon(path);
if (!icon.availableSizes().size())
if (icon.availableSizes().empty())
return;
icons[idx].m_images[IconType::FileBased].icon = icon;
m_icons[idx].m_images[IconType::FileBased].icon = icon;
dataChanged(index(idx), index(idx));
emit iconUpdated(key);
}
void IconList::SettingChanged(const Setting& setting, QVariant value)
void IconList::SettingChanged(const Setting& setting, const QVariant& value)
{
if (setting.id() != "IconsDir")
return;
@ -200,8 +240,8 @@ void IconList::startWatching()
{
auto abs_path = m_dir.absolutePath();
FS::ensureFolderPathExists(abs_path);
is_watching = m_watcher->addPath(abs_path);
if (is_watching) {
m_isWatching = addPathRecursively(abs_path);
if (m_isWatching) {
qDebug() << "Started watching " << abs_path;
} else {
qDebug() << "Failed to start watching " << abs_path;
@ -212,7 +252,7 @@ void IconList::stopWatching()
{
m_watcher->removePaths(m_watcher->files());
m_watcher->removePaths(m_watcher->directories());
is_watching = false;
m_isWatching = false;
}
QStringList IconList::mimeTypes() const
@ -242,7 +282,7 @@ bool IconList::dropMimeData(const QMimeData* data,
if (data->hasUrls()) {
auto urls = data->urls();
QStringList iconFiles;
for (auto url : urls) {
for (const auto& url : urls) {
// only local files may be dropped...
if (!url.isLocalFile())
continue;
@ -263,33 +303,33 @@ Qt::ItemFlags IconList::flags(const QModelIndex& index) const
QVariant IconList::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
return {};
int row = index.row();
if (row < 0 || row >= icons.size())
return QVariant();
if (row < 0 || row >= m_icons.size())
return {};
switch (role) {
case Qt::DecorationRole:
return icons[row].icon();
return m_icons[row].icon();
case Qt::DisplayRole:
return icons[row].name();
return m_icons[row].name();
case Qt::UserRole:
return icons[row].m_key;
return m_icons[row].m_key;
default:
return QVariant();
return {};
}
}
int IconList::rowCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : icons.size();
return parent.isValid() ? 0 : m_icons.size();
}
void IconList::installIcons(const QStringList& iconFiles)
{
for (QString file : iconFiles)
for (const QString& file : iconFiles)
installIcon(file, {});
}
@ -312,12 +352,13 @@ 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 &icons[iconIdx];
return &m_icons[iconIdx];
}
bool IconList::deleteIcon(const QString& key)
@ -332,22 +373,22 @@ bool IconList::trashIcon(const QString& key)
bool IconList::addThemeIcon(const QString& key)
{
auto iter = name_index.find(key);
if (iter != name_index.end()) {
auto& oldOne = icons[*iter];
auto iter = m_nameIndex.find(key);
if (iter != m_nameIndex.end()) {
auto& oldOne = m_icons[*iter];
oldOne.replace(Builtin, key);
dataChanged(index(*iter), index(*iter));
return true;
}
// add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size());
beginInsertRows(QModelIndex(), m_icons.size(), m_icons.size());
{
MMCIcon mmc_icon;
mmc_icon.m_name = key;
mmc_icon.m_key = key;
mmc_icon.replace(Builtin, key);
icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1;
m_icons.push_back(mmc_icon);
m_nameIndex[key] = m_icons.size() - 1;
}
endInsertRows();
return true;
@ -359,22 +400,22 @@ bool IconList::addIcon(const QString& key, const QString& name, const QString& p
QIcon icon(path);
if (icon.isNull())
return false;
auto iter = name_index.find(key);
if (iter != name_index.end()) {
auto& oldOne = icons[*iter];
auto iter = m_nameIndex.find(key);
if (iter != m_nameIndex.end()) {
auto& oldOne = m_icons[*iter];
oldOne.replace(type, icon, path);
dataChanged(index(*iter), index(*iter));
return true;
}
// add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size());
beginInsertRows(QModelIndex(), m_icons.size(), m_icons.size());
{
MMCIcon mmc_icon;
mmc_icon.m_name = name;
mmc_icon.m_key = key;
mmc_icon.replace(type, icon, path);
icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1;
m_icons.push_back(mmc_icon);
m_nameIndex[key] = m_icons.size() - 1;
}
endInsertRows();
return true;
@ -389,33 +430,32 @@ void IconList::saveIcon(const QString& key, const QString& path, const char* for
void IconList::reindex()
{
name_index.clear();
int i = 0;
for (auto& iter : icons) {
name_index[iter.m_key] = i;
i++;
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
}
}
QIcon IconList::getIcon(const QString& key) const
{
int icon_index = getIconIndex(key);
int iconIndex = getIconIndex(key);
if (icon_index != -1)
return icons[icon_index].icon();
if (iconIndex != -1)
return m_icons[iconIndex].icon();
// Fallback for icons that don't exist.
icon_index = getIconIndex("grass");
// Fallback for icons that don't exist.b
iconIndex = getIconIndex("grass");
if (icon_index != -1)
return icons[icon_index].icon();
return QIcon();
if (iconIndex != -1)
return m_icons[iconIndex].icon();
return {};
}
int IconList::getIconIndex(const QString& key) const
{
auto iter = name_index.find(key == "default" ? "grass" : key);
if (iter != name_index.end())
auto iter = m_nameIndex.find(key == "default" ? "grass" : key);
if (iter != m_nameIndex.end())
return *iter;
return -1;
@ -425,3 +465,15 @@ 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, QString path, QObject* parent = 0);
explicit IconList(const QStringList& builtinPaths, const QString& path, QObject* parent = 0);
virtual ~IconList() {};
QIcon getIcon(const QString& key) const;
@ -72,6 +72,7 @@ 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);
@ -91,18 +92,20 @@ 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, QVariant value);
void SettingChanged(const Setting& setting, const QVariant& value);
private:
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
bool is_watching;
QMap<QString, int> name_index;
QVector<MMCIcon> icons;
bool m_isWatching;
QMap<QString, int> m_nameIndex;
QVector<MMCIcon> m_icons;
QDir m_dir;
};

View file

@ -405,7 +405,7 @@ QList<QString> JavaUtils::FindJavaPaths()
{
QList<QString> javas;
javas.append(this->GetDefaultJava()->path);
auto scanJavaDir = [&](
auto scanJavaDir = [&javas](
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 = [&](const QString& dirPath) {
auto scanJavaDirs = [scanJavaDir, snap](const QString& dirPath) {
scanJavaDir(dirPath);
if (!snap.isNull()) {
scanJavaDir(snap + dirPath);
@ -442,9 +442,15 @@ 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
@ -546,12 +552,12 @@ QStringList getPrismJavaBundle()
{
QList<QString> javas;
auto scanDir = [&](QString prefix) {
auto scanDir = [&javas](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 = [&](const QString& dirPath) {
auto scanJavaDir = [scanDir](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 = [&]() { return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
auto getError = [this]() { 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 = [&]() { return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
auto getError = [this]() { 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(), [&](Meta::Version::Ptr const& a) { return a->version() == version; });
auto ver = std::find_if(m_versions.constBegin(), m_versions.constEnd(),
[version](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 = [&](QString relPath) {
auto actualPath = [this, local, overridePath](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 = [&](QString storage) {
auto check_local_file = [overridePath, &failedLocalFiles](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 = [&](QString storage, QString url, QString sha1) {
auto add_download = [this, local, check_local_file, cache, stale, &out](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 = [&]() {
auto raw_dl = [this, raw_storage]() {
if (!m_absoluteURL.isEmpty()) {
return m_absoluteURL;
}

View file

@ -91,13 +91,56 @@
#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 {
@ -530,7 +573,7 @@ QStringList MinecraftInstance::javaArguments()
QString MinecraftInstance::getLauncher()
{
// use legacy launcher if the traits are set
if (traits().contains("legacyLaunch") || traits().contains("alphaLaunch"))
if (isLegacy())
return "legacy";
return "standard";
@ -551,6 +594,13 @@ 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;
}
@ -615,12 +665,14 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
}
if (settings()->get("UseDiscreteGpu").toBool()) {
// 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 (!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");
}
}
if (settings()->get("UseZink").toBool()) {
@ -753,11 +805,34 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftT
// window size, title and state, legacy
{
QString windowParams;
if (settings()->get("LaunchMaximized").toBool())
windowParams = "maximized";
else
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 {
windowParams =
QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt());
}
launchScript += "windowTitle " + windowTitle() + "\n";
launchScript += "windowParams " + windowParams + "\n";
}
@ -829,7 +904,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << "Libraries:";
QStringList jars, nativeJars;
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
auto printLibFile = [&](const QString& path) {
auto printLibFile = [&out](const QString& path) {
QFileInfo info(path);
if (info.exists()) {
out << " " + path;
@ -849,7 +924,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
}
// mods and core mods
auto printModList = [&](const QString& label, ModFolderModel& model) {
auto printModList = [&out](const QString& label, ModFolderModel& model) {
if (model.size()) {
out << QString("%1:").arg(label);
auto modList = model.allMods();
@ -1178,7 +1253,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));
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed, true));
}
return m_loader_mod_list;
}
@ -1187,7 +1262,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));
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed, true));
}
return m_core_mod_list;
}
@ -1204,7 +1279,8 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::nilModList()
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
{
if (!m_resource_pack_list) {
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this));
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this, is_indexed, true));
}
return m_resource_pack_list;
}
@ -1212,7 +1288,8 @@ std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
{
if (!m_texture_pack_list) {
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this));
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this, is_indexed, true));
}
return m_texture_pack_list;
}
@ -1220,11 +1297,17 @@ std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList()
{
if (!m_shader_pack_list) {
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this));
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this, is_indexed, true));
}
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,6 +116,7 @@ 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 = [&](const char* which, QList<LibraryPtr>& outList) {
auto readLibs = [&root, &out, &filename](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 = [&](LibraryPtr jarMod) -> bool {
auto preRemoveJarMod = [this](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 data.type != AccountType::Offline && data.minecraftEntitlement.ownsMinecraft; }
bool ownsMinecraft() const { return true; }
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, [=](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) {
*connection = connect(
&m_process, &LoggedProcess::log, this, [connection](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,33 +26,48 @@
// launcher/minecraft/mod/Mod.h
class Mod;
/* 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;
namespace Metadata {
using ModStruct = Packwiz::V1::Mod;
using ModSide = Packwiz::V1::Side;
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, 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, Mod& internal_mod, QString mod_slug) -> ModStruct
{
return Packwiz::V1::createModFormat(index_dir, internal_mod, mod_slug);
}
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 void update(QDir& index_dir, ModStruct& mod) { Packwiz::V1::updateModIndex(index_dir, mod); }
inline void update(const QDir& index_dir, ModStruct& mod)
{
Packwiz::V1::updateModIndex(index_dir, mod);
}
static void remove(QDir& index_dir, QString mod_slug) { Packwiz::V1::deleteModIndex(index_dir, mod_slug); }
inline void remove(const QDir& index_dir, QString mod_slug)
{
Packwiz::V1::deleteModIndex(index_dir, mod_slug);
}
static void remove(QDir& index_dir, QVariant& mod_id) { Packwiz::V1::deleteModIndex(index_dir, mod_id); }
inline void remove(const QDir& index_dir, QVariant& mod_id)
{
Packwiz::V1::deleteModIndex(index_dir, mod_id);
}
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, QString mod_slug) -> ModStruct
{
return Packwiz::V1::getIndexForMod(index_dir, std::move(mod_slug));
}
static auto get(QDir& index_dir, QVariant& mod_id) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_id); }
inline auto get(const QDir& index_dir, QVariant& mod_id) -> ModStruct
{
return Packwiz::V1::getIndexForMod(index_dir, mod_id);
}
static auto modSideToString(ModSide side) -> QString { return Packwiz::V1::sideToString(side); }
};
inline auto modSideToString(ModSide side) -> QString
{
return Packwiz::V1::sideToString(side);
}
}; // namespace Metadata

View file

@ -38,16 +38,15 @@
#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"
@ -56,24 +55,6 @@ 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;
@ -101,33 +82,28 @@ int Mod::compare(const Resource& other, SortType type) const
return -1;
break;
}
case SortType::PROVIDER: {
return QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive);
}
case SortType::SIDE: {
if (side() > cast_other->side())
return 1;
else if (side() < cast_other->side())
return -1;
break;
}
case SortType::LOADERS: {
if (loaders() > cast_other->loaders())
return 1;
else if (loaders() < cast_other->loaders())
return -1;
auto compare_result = QString::compare(side(), cast_other->side(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
break;
}
case SortType::MC_VERSIONS: {
auto thisVersion = mcVersions().join(",");
auto otherVersion = cast_other->mcVersions().join(",");
return QString::compare(thisVersion, otherVersion, Qt::CaseInsensitive);
auto compare_result = QString::compare(mcVersions(), cast_other->mcVersions(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
break;
}
case SortType::LOADERS: {
auto compare_result = QString::compare(loaders(), cast_other->loaders(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
break;
}
case SortType::RELEASE_TYPE: {
if (releaseType() > cast_other->releaseType())
return 1;
else if (releaseType() < cast_other->releaseType())
return -1;
auto compare_result = QString::compare(releaseType(), cast_other->releaseType(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
break;
}
}
@ -148,28 +124,6 @@ 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;
@ -181,10 +135,7 @@ auto Mod::name() const -> QString
if (!d_name.isEmpty())
return d_name;
if (metadata())
return metadata()->name;
return m_name;
return Resource::name();
}
auto Mod::version() const -> QString
@ -192,16 +143,52 @@ auto Mod::version() const -> QString
return details().version;
}
auto Mod::homeurl() const -> QString
auto Mod::homepage() const -> QString
{
return details().homeurl;
QString metaUrl = Resource::homepage();
if (metaUrl.isEmpty())
return details().homeurl;
else
return metaUrl;
}
auto Mod::metaurl() const -> QString
auto Mod::loaders() const -> QString
{
if (metadata() == nullptr)
return homeurl();
return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
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();
}
auto Mod::description() const -> QString
@ -214,73 +201,17 @@ 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,7 +48,6 @@
#include "ModDetails.h"
#include "Resource.h"
#include "modplatform/ModIndex.h"
class Mod : public Resource {
Q_OBJECT
@ -58,24 +57,20 @@ 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 homeurl() const -> QString;
auto homepage() const -> QString override;
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 metaurl() const -> QString;
auto side() const -> Metadata::ModSide;
auto loaders() const -> ModPlatform::ModLoaderTypes;
auto mcVersions() const -> QStringList;
auto releaseType() const -> ModPlatform::IndexedVersionType;
auto side() const -> QString;
auto loaders() const -> QString;
auto mcVersions() const -> QString;
auto releaseType() const -> QString;
/** Get the intneral path to the mod's icon file*/
QString iconPath() const { return m_local_details.icon_file; }
@ -84,17 +79,11 @@ 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(Resource const& other, SortType type) const override;
[[nodiscard]] int compare(const Resource & other, SortType type) const override;
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
// Delete all the files of this mod

View file

@ -43,13 +43,6 @@
#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 = {};
@ -149,12 +142,6 @@ 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. */
@ -169,40 +156,9 @@ struct ModDetails {
, issue_tracker(other.issue_tracker)
, licenses(other.licenses)
, icon_file(other.icon_file)
, status(other.status)
{}
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=(const 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;
}
ModDetails& operator=(ModDetails&& other) = default;
};

View file

@ -51,18 +51,10 @@
#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 QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
{
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders",
"Minecraft Versions", "Release Type" });
@ -92,7 +84,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
case NameColumn:
return m_resources[row]->name();
case VersionColumn: {
switch (m_resources[row]->type()) {
switch (at(row).type()) {
case ResourceType::FOLDER:
return tr("Folder");
case ResourceType::SINGLEFILE:
@ -100,64 +92,50 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
default:
break;
}
return at(row)->version();
return at(row).version();
}
case DateColumn:
return m_resources[row]->dateTimeChanged();
return at(row).dateTimeChanged();
case ProviderColumn: {
auto provider = at(row)->provider();
if (!provider.has_value()) {
//: Unknown mod provider (i.e. not Modrinth, CurseForge, etc...)
return tr("Unknown");
}
return provider.value();
return at(row).provider();
}
case SideColumn: {
return Metadata::modSideToString(at(row)->side());
return at(row).side();
}
case LoadersColumn: {
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(", ");
return at(row).loaders();
}
case McVersionsColumn: {
return at(row)->mcVersions().join(", ");
return at(row).mcVersions();
}
case ReleaseTypeColumn: {
return at(row)->releaseType().toString();
return at(row).releaseType();
}
case SizeColumn:
return m_resources[row]->sizeStr();
return at(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 {};
}
@ -169,7 +147,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();
}
@ -210,7 +188,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("Where the mod was downloaded from.");
return tr("The source provider of the mod.");
case SideColumn:
return tr("On what environment the mod is running.");
case LoadersColumn:
@ -235,133 +213,16 @@ 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);
@ -379,51 +240,7 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
auto result = cast_task->result();
if (result && resource)
resource->finishResolvingWithDetails(std::move(result->details));
static_cast<Mod*>(resource.get())->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,11 +47,6 @@
#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;
@ -76,8 +71,7 @@ class ModFolderModel : public ResourceFolderModel {
ReleaseTypeColumn,
NUM_COLUMNS
};
enum ModStatusAction { Disable, Enable, Toggle };
ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true);
ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
virtual QString id() const override { return "mods"; }
@ -86,34 +80,13 @@ 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]] Task* createUpdateTask() override;
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new Mod(file); }
[[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();
}
std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
static std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
{
if (file.isDir()) {
auto dir = QDir(file.absoluteFilePath());
@ -72,6 +72,14 @@ 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);
@ -79,6 +87,30 @@ 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) {
@ -93,6 +125,7 @@ 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);
@ -118,6 +151,12 @@ 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;
@ -174,10 +213,27 @@ bool Resource::enable(EnableAction action)
return true;
}
bool Resource::destroy(bool attemptTrash)
auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
{
m_type = ResourceType::UNKNOWN;
return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
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;
}
bool Resource::isSymLinkUnder(const QString& instPath) const

View file

@ -40,6 +40,7 @@
#include <QObject>
#include <QPointer>
#include "MetadataHandler.h"
#include "QObjectPtr.h"
enum class ResourceType {
@ -50,7 +51,14 @@ enum class ResourceType {
LITEMOD, //!< The resource is a litemod
};
enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, LOADERS, MC_VERSIONS, RELEASE_TYPE };
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 EnableAction { ENABLE, DISABLE, TOGGLE };
@ -84,9 +92,19 @@ 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 { return m_name; }
[[nodiscard]] virtual auto name() const -> QString;
[[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'
@ -117,7 +135,9 @@ class Resource : public QObject {
}
// Delete all files of this resource.
bool destroy(bool attemptTrash = true);
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;
[[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); }
@ -146,6 +166,11 @@ 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,20 +11,23 @@
#include <QStyle>
#include <QThreadPool>
#include <QUrl>
#include <utility>
#include "Application.h"
#include "FileSystem.h"
#include "QVariantUtils.h"
#include "StringUtils.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "minecraft/mod/tasks/ResourceFolderLoadTask.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(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir)
: QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this)
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)
{
if (create_dir) {
FS::ensureFolderPathExists(m_dir.absolutePath());
@ -48,6 +51,9 @@ ResourceFolderModel::~ResourceFolderModel()
bool ResourceFolderModel::startWatching(const QStringList& paths)
{
// Remove orphaned metadata next time
m_first_folder_load = true;
if (m_is_watching)
return false;
@ -158,11 +164,55 @@ bool ResourceFolderModel::installResource(QString original_path)
return false;
}
bool ResourceFolderModel::uninstallResource(QString file_name)
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)
{
for (auto& resource : m_resources) {
if (resource->fileinfo().fileName() == file_name) {
auto res = resource->destroy(false);
auto res = resource->destroy(indexDir(), preserve_metadata, false);
update();
@ -178,13 +228,11 @@ 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();
resource->destroy(indexDir());
}
update();
@ -192,6 +240,22 @@ 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())
@ -245,7 +309,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;
@ -299,7 +363,7 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res)
void ResourceFolderModel::onUpdateSucceeded()
{
auto update_results = static_cast<BasicFolderLoadTask*>(m_current_update_task.get())->result();
auto update_results = static_cast<ResourceFolderLoadTask*>(m_current_update_task.get())->result();
auto& new_resources = update_results->resources;
@ -329,7 +393,11 @@ void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
Task* ResourceFolderModel::createUpdateTask()
{
return new BasicFolderLoadTask(m_dir);
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;
}
bool ResourceFolderModel::hasPendingParseTasks() const
@ -419,6 +487,8 @@ 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:
@ -490,22 +560,23 @@ 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:
@ -651,3 +722,126 @@ 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,6 +19,38 @@
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,
@ -29,7 +61,7 @@ class QSortFilterProxyModel;
class ResourceFolderModel : public QAbstractListModel {
Q_OBJECT
public:
ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true);
ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
~ResourceFolderModel() override;
virtual QString id() const { return "resource"; }
@ -49,8 +81,10 @@ class ResourceFolderModel : public QAbstractListModel {
bool stopWatching(const QStringList& paths);
/* Helper methods for subclasses, using a predetermined list of paths. */
virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); }
virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); }
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()) }; }
/** Given a path in the system, install that resource, moving it to its place in the
* instance file hierarchy.
@ -59,12 +93,15 @@ 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);
virtual bool uninstallResource(QString file_name, bool preserve_metadata = false);
virtual bool deleteResources(const QModelIndexList&);
virtual void deleteMetadata(const QModelIndexList&);
/** Applies the given 'action' to the resources in 'indexes'.
*
@ -80,9 +117,13 @@ 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.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]] 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]] QDir const& dir() const { return m_dir; }
@ -96,7 +137,8 @@ class ResourceFolderModel : public QAbstractListModel {
/* Qt behavior */
/* Basic columns */
enum Columns { ActiveColumn = 0, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS };
enum Columns { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, 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()); }
@ -153,7 +195,9 @@ 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]] virtual Task* createUpdateTask();
[[nodiscard]] Task* createUpdateTask();
[[nodiscard]] virtual Resource* createResource(const QFileInfo& info) { return new Resource(info); }
/** This creates a new parse task to be executed by onUpdateSucceeded().
*
@ -167,10 +211,8 @@ 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.
*/
template <typename T>
void applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources);
void applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, Resource::Ptr>& new_resources);
protected slots:
void directoryChanged(QString);
@ -195,19 +237,22 @@ 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::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<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<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
QHeaderView::Interactive };
QList<bool> m_columnsHideable = { false, false, true, true };
QList<bool> m_columnsHiddenByDefault = { false, false, false, false };
QHeaderView::Interactive, QHeaderView::Interactive };
QList<bool> m_columnsHideable = { false, false, true, true, true };
QList<bool> m_columnsHiddenByDefault = { false, false, false, false, true };
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;
@ -220,133 +265,3 @@ 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 QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance)
ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(dir, instance, is_indexed, create_dir, parent)
{
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,
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,
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true, true };
m_columnsHiddenByDefault = { false, false, false, false, false, false };
m_columnsHideable = { false, true, false, true, true, true, true };
}
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,17 +87,18 @@ 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 {};
}
@ -107,14 +108,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.");
}
@ -129,7 +130,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 {};
}
@ -148,6 +149,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
case PackFormatColumn:
case DateColumn:
case ImageColumn:
case ProviderColumn:
case SizeColumn:
return columnNames().at(section);
default:
@ -157,7 +159,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
case Qt::ToolTipRole:
switch (section) {
case ActiveColumn:
return tr("Is the resource pack enabled? (Only valid for ZIPs)");
return tr("Is the resource pack enabled?");
case NameColumn:
return tr("The name of the resource pack.");
case PackFormatColumn:
@ -165,6 +167,8 @@ 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:
@ -185,11 +189,6 @@ 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, SizeColumn, NUM_COLUMNS };
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
explicit ResourcePackFolderModel(const QString& dir, BaseInstance* instance);
explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
virtual QString id() const override { return "resourcepacks"; }
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]] Task* createUpdateTask() override;
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new ResourcePack(file); }
[[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 QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) {}
explicit ShaderPackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr)
: ResourceFolderModel(dir, instance, is_indexed, create_dir, parent)
{}
virtual QString id() const override { return "shaderpacks"; }
[[nodiscard]] Task* createUpdateTask() override
{
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<ShaderPack>(entry); });
}
[[nodiscard]] Resource* createResource(const QFileInfo& info) override { return new ShaderPack(info); }
[[nodiscard]] Task* createParseTask(Resource& resource) override
{
return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast<ShaderPack&>(resource));
}
RESOURCE_HELPERS(ShaderPack);
};

View file

@ -39,22 +39,18 @@
#include "TexturePackFolderModel.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
#include "minecraft/mod/tasks/ResourceFolderLoadTask.h"
TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance)
TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
{
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); });
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 };
}
Task* TexturePackFolderModel::createParseTask(Resource& resource)
@ -77,6 +73,8 @@ 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:
@ -84,14 +82,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.");
}
@ -99,10 +97,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 {};
}
@ -130,6 +128,7 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
case NameColumn:
case DateColumn:
case ImageColumn:
case ProviderColumn:
case SizeColumn:
return columnNames().at(section);
default:
@ -138,14 +137,13 @@ 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, SizeColumn, NUM_COLUMNS };
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
explicit TexturePackFolderModel(const QString& dir, std::shared_ptr<const BaseInstance> instance);
explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
virtual QString id() const override { return "texturepacks"; }
@ -55,8 +55,7 @@ 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;
explicit TexturePackFolderModel(const QString& dir, BaseInstance* instance);
[[nodiscard]] Task* createUpdateTask() override;
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new TexturePack(file); }
[[nodiscard]] Task* createParseTask(Resource&) override;
RESOURCE_HELPERS(TexturePack)

View file

@ -1,81 +0,0 @@
#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,11 +47,6 @@ 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 "LocalModUpdateTask.h"
#include "LocalResourceUpdateTask.h"
#include "FileSystem.h"
#include "minecraft/mod/MetadataHandler.h"
@ -26,12 +26,12 @@
#include <windows.h>
#endif
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)
LocalResourceUpdateTask::LocalResourceUpdateTask(QDir index_dir, ModPlatform::IndexedPack& project, ModPlatform::IndexedVersion& version)
: m_index_dir(index_dir), m_project(project), m_version(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 for mod %1!").arg(m_mod.name));
emitFailed(QString("Unable to create index directory at %1!").arg(index_dir.absolutePath()));
}
#ifdef Q_OS_WIN32
@ -39,28 +39,28 @@ LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack&
#endif
}
void LocalModUpdateTask::executeTask()
void LocalResourceUpdateTask::executeTask()
{
setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name));
setStatus(tr("Updating index for resource:\n%1").arg(m_project.name));
auto old_metadata = Metadata::get(m_index_dir, m_mod.addonId);
auto old_metadata = Metadata::get(m_index_dir, m_project.addonId);
if (old_metadata.isValid()) {
emit hasOldMod(old_metadata.name, old_metadata.filename);
if (m_mod.slug.isEmpty())
m_mod.slug = old_metadata.slug;
emit hasOldResource(old_metadata.name, old_metadata.filename);
if (m_project.slug.isEmpty())
m_project.slug = old_metadata.slug;
}
auto pw_mod = Metadata::create(m_index_dir, m_mod, m_mod_version);
auto pw_mod = Metadata::create(m_index_dir, m_project, m_version);
if (pw_mod.isValid()) {
Metadata::update(m_index_dir, pw_mod);
emitSucceeded();
} else {
qCritical() << "Tried to update an invalid mod!";
qCritical() << "Tried to update an invalid resource!";
emitFailed(tr("Invalid metadata"));
}
}
auto LocalModUpdateTask::abort() -> bool
auto LocalResourceUpdateTask::abort() -> bool
{
emitAborted();
return true;

View file

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

View file

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

View file

@ -44,17 +44,21 @@
#include "minecraft/mod/Mod.h"
#include "tasks/Task.h"
class ModFolderLoadTask : public Task {
class ResourceFolderLoadTask : public Task {
Q_OBJECT
public:
struct Result {
QMap<QString, Mod::Ptr> mods;
QMap<QString, Resource::Ptr> resources;
};
using ResultPtr = std::shared_ptr<Result>;
ResultPtr result() const { return m_result; }
public:
ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan = false);
ResourceFolderLoadTask(const QDir& resource_dir,
const QDir& index_dir,
bool is_indexed,
bool clean_orphan,
std::function<Resource*(const QFileInfo&)> create_function);
[[nodiscard]] bool canAbort() const override { return true; }
bool abort() override
@ -69,9 +73,10 @@ class ModFolderLoadTask : public Task {
void getFromMetadata();
private:
QDir m_mods_dir, m_index_dir;
QDir m_resource_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 = [&](const QList<LibraryPtr>& pool, QStringList& errors, const QString& localPath) {
auto processArtifactPool = [this, inst, metacache](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,6 +3,7 @@
#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;
@ -12,13 +13,18 @@ class CheckUpdateTask : public Task {
Q_OBJECT
public:
CheckUpdateTask(QList<Mod*>& mods,
CheckUpdateTask(QList<Resource*>& resources,
std::list<Version>& mcVersions,
QList<ModPlatform::ModLoaderType> loadersList,
std::shared_ptr<ModFolderModel> mods_folder)
: Task(), m_mods(mods), m_game_versions(mcVersions), m_loaders_list(loadersList), m_mods_folder(mods_folder) {};
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))
{}
struct UpdatableMod {
struct Update {
QString name;
QString old_hash;
QString old_version;
@ -30,28 +36,28 @@ class CheckUpdateTask : public Task {
bool enabled = true;
public:
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)
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))
, provider(p)
, download(t)
, download(std::move(t))
, enabled(enabled)
{}
};
auto getUpdatable() -> std::vector<UpdatableMod>&& { return std::move(m_updatable); }
auto getUpdates() -> std::vector<Update>&& { return std::move(m_updates); }
auto getDependencies() -> QList<std::shared_ptr<GetModDependenciesTask::PackDependency>>&& { return std::move(m_deps); }
public slots:
@ -61,14 +67,14 @@ class CheckUpdateTask : public Task {
void executeTask() override = 0;
signals:
void checkFailed(Mod* failed, QString reason, QUrl recover_url = {});
void checkFailed(Resource* failed, QString reason, QUrl recover_url = {});
protected:
QList<Mod*>& m_mods;
QList<Resource*>& m_resources;
std::list<Version>& m_game_versions;
QList<ModPlatform::ModLoaderType> m_loaders_list;
std::shared_ptr<ModFolderModel> m_mods_folder;
std::shared_ptr<ResourceFolderModel> m_resource_model;
std::vector<UpdatableMod> m_updatable;
std::vector<Update> m_updates;
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/LocalModUpdateTask.h"
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
@ -18,56 +18,57 @@
static ModrinthAPI modrinth_api;
static FlameAPI flame_api;
EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::ResourceProvider prov)
EnsureMetadataTask::EnsureMetadataTask(Resource* resource, QDir dir, ModPlatform::ResourceProvider prov)
: Task(), m_index_dir(dir), m_provider(prov), m_hashingTask(nullptr), m_current_task(nullptr)
{
auto hashTask = createNewHash(mod);
auto hashTask = createNewHash(resource);
if (!hashTask)
return;
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); });
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); });
m_hashingTask = hashTask;
}
EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::ResourceProvider prov)
EnsureMetadataTask::EnsureMetadataTask(QList<Resource*>& resources, 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* mod : mods) {
auto hash_task = createNewHash(mod);
for (auto* resource : resources) {
auto hash_task = createNewHash(resource);
if (!hash_task)
continue;
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); });
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); });
hashTask->addTask(hash_task);
}
}
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)
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)
{}
Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Mod* mod)
Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Resource* resource)
{
if (!mod || !mod->valid() || mod->type() == ResourceType::FOLDER)
if (!resource || !resource->valid() || resource->type() == ResourceType::FOLDER)
return nullptr;
return Hashing::createHasher(mod->fileinfo().absoluteFilePath(), m_provider);
return Hashing::createHasher(resource->fileinfo().absoluteFilePath(), m_provider);
}
QString EnsureMetadataTask::getExistingHash(Mod* mod)
QString EnsureMetadataTask::getExistingHash(Resource* resource)
{
// Check for already computed hashes
// (linear on the number of mods vs. linear on the size of the mod's JAR)
auto it = m_mods.keyValueBegin();
while (it != m_mods.keyValueEnd()) {
if ((*it).second == mod)
auto it = m_resources.keyValueBegin();
while (it != m_resources.keyValueEnd()) {
if ((*it).second == resource)
break;
it++;
}
// We already have the hash computed
if (it != m_mods.keyValueEnd()) {
if (it != m_resources.keyValueEnd()) {
return (*it).first;
}
@ -87,25 +88,25 @@ bool EnsureMetadataTask::abort()
void EnsureMetadataTask::executeTask()
{
setStatus(tr("Checking if mods have metadata..."));
setStatus(tr("Checking if resources have metadata..."));
for (auto* mod : m_mods) {
if (!mod->valid()) {
qDebug() << "Mod" << mod->name() << "is invalid!";
emitFail(mod);
for (auto* resource : m_resources) {
if (!resource->valid()) {
qDebug() << "Resource" << resource->name() << "is invalid!";
emitFail(resource);
continue;
}
// They already have the right metadata :o
if (mod->status() != ModStatus::NoMetadata && mod->metadata() && mod->metadata()->provider == m_provider) {
qDebug() << "Mod" << mod->name() << "already has metadata!";
emitReady(mod);
if (resource->status() != ResourceStatus::NO_METADATA && resource->metadata() && resource->metadata()->provider == m_provider) {
qDebug() << "Resource" << resource->name() << "already has metadata!";
emitReady(resource);
continue;
}
// Folders don't have metadata
if (mod->type() == ResourceType::FOLDER) {
emitReady(mod);
if (resource->type() == ResourceType::FOLDER) {
emitReady(resource);
}
}
@ -121,9 +122,9 @@ void EnsureMetadataTask::executeTask()
}
auto invalidade_leftover = [this] {
for (auto mod = m_mods.constBegin(); mod != m_mods.constEnd(); mod++)
emitFail(mod.value(), mod.key(), RemoveFromList::No);
m_mods.clear();
for (auto resource = m_resources.constBegin(); resource != m_resources.constEnd(); resource++)
emitFail(resource.value(), resource.key(), RemoveFromList::No);
m_resources.clear();
emitSucceeded();
};
@ -145,7 +146,7 @@ void EnsureMetadataTask::executeTask()
return;
}
connect(project_task.get(), &Task::finished, this, [=] {
connect(project_task.get(), &Task::finished, this, [this, invalidade_leftover, project_task] {
invalidade_leftover();
project_task->deleteLater();
if (m_current_task)
@ -157,59 +158,59 @@ void EnsureMetadataTask::executeTask()
project_task->start();
});
connect(version_task.get(), &Task::finished, [=] {
connect(version_task.get(), &Task::finished, [this, version_task] {
version_task->deleteLater();
if (m_current_task)
m_current_task.reset();
});
if (m_mods.size() > 1)
if (m_resources.size() > 1)
setStatus(tr("Requesting metadata information from %1...").arg(ModPlatform::ProviderCapabilities::readableName(m_provider)));
else if (!m_mods.empty())
else if (!m_resources.empty())
setStatus(tr("Requesting metadata information from %1 for '%2'...")
.arg(ModPlatform::ProviderCapabilities::readableName(m_provider), m_mods.begin().value()->name()));
.arg(ModPlatform::ProviderCapabilities::readableName(m_provider), m_resources.begin().value()->name()));
m_current_task = version_task;
version_task->start();
}
void EnsureMetadataTask::emitReady(Mod* m, QString key, RemoveFromList remove)
void EnsureMetadataTask::emitReady(Resource* resource, QString key, RemoveFromList remove)
{
if (!m) {
qCritical() << "Tried to mark a null mod as ready.";
if (!resource) {
qCritical() << "Tried to mark a null resource as ready.";
if (!key.isEmpty())
m_mods.remove(key);
m_resources.remove(key);
return;
}
qDebug() << QString("Generated metadata for %1").arg(m->name());
emit metadataReady(m);
qDebug() << QString("Generated metadata for %1").arg(resource->name());
emit metadataReady(resource);
if (remove == RemoveFromList::Yes) {
if (key.isEmpty())
key = getExistingHash(m);
m_mods.remove(key);
key = getExistingHash(resource);
m_resources.remove(key);
}
}
void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove)
void EnsureMetadataTask::emitFail(Resource* resource, QString key, RemoveFromList remove)
{
if (!m) {
qCritical() << "Tried to mark a null mod as failed.";
if (!resource) {
qCritical() << "Tried to mark a null resource as failed.";
if (!key.isEmpty())
m_mods.remove(key);
m_resources.remove(key);
return;
}
qDebug() << QString("Failed to generate metadata for %1").arg(m->name());
emit metadataFailed(m);
qDebug() << QString("Failed to generate metadata for %1").arg(resource->name());
emit metadataFailed(resource);
if (remove == RemoveFromList::Yes) {
if (key.isEmpty())
key = getExistingHash(m);
m_mods.remove(key);
key = getExistingHash(resource);
m_resources.remove(key);
}
}
@ -220,7 +221,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_mods.keys(), hash_type, response);
auto ver_task = modrinth_api.currentVersions(m_resources.keys(), hash_type, response);
// Prevents unfortunate timings when aborting the task
if (!ver_task)
@ -240,20 +241,20 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
try {
auto entries = Json::requireObject(doc);
for (auto& hash : m_mods.keys()) {
auto mod = m_mods.find(hash).value();
for (auto& hash : m_resources.keys()) {
auto resource = m_resources.find(hash).value();
try {
auto entry = Json::requireObject(entries, hash);
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name()));
qDebug() << "Getting version for" << mod->name() << "from Modrinth";
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(resource->name()));
qDebug() << "Getting version for" << resource->name() << "from Modrinth";
m_temp_versions.insert(hash, Modrinth::loadIndexedPackVersion(entry));
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << entries;
emitFail(mod);
emitFail(resource);
}
}
} catch (Json::JsonException& e) {
@ -325,23 +326,23 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
auto hash = addonIds.find(pack.addonId.toString()).value();
auto mod_iter = m_mods.find(hash);
if (mod_iter == m_mods.end()) {
auto resource_iter = m_resources.find(hash);
if (resource_iter == m_resources.end()) {
qWarning() << "Invalid project id from the API response.";
continue;
}
auto* mod = mod_iter.value();
auto* resource = resource_iter.value();
try {
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name()));
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(resource->name()));
modrinthCallback(pack, m_temp_versions.find(hash).value(), mod);
modrinthCallback(pack, m_temp_versions.find(hash).value(), resource);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << entries;
emitFail(mod);
emitFail(resource);
}
}
});
@ -355,7 +356,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
auto response = std::make_shared<QByteArray>();
QList<uint> fingerprints;
for (auto& murmur : m_mods.keys()) {
for (auto& murmur : m_resources.keys()) {
fingerprints.push_back(murmur.toUInt());
}
@ -395,13 +396,13 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
}
auto fingerprint = QString::number(Json::ensureVariant(file_obj, "fileFingerprint").toUInt());
auto mod = m_mods.find(fingerprint);
if (mod == m_mods.end()) {
auto resource = m_resources.find(fingerprint);
if (resource == m_resources.end()) {
qWarning() << "Invalid fingerprint from the API response.";
continue;
}
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg((*mod)->name()));
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg((*resource)->name()));
m_temp_versions.insert(fingerprint, FlameMod::loadIndexedPackVersion(file_obj));
}
@ -418,7 +419,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
Task::Ptr EnsureMetadataTask::flameProjectsTask()
{
QHash<QString, QString> addonIds;
for (auto const& hash : m_mods.keys()) {
for (auto const& hash : m_resources.keys()) {
if (m_temp_versions.contains(hash)) {
auto data = m_temp_versions.find(hash).value();
@ -465,20 +466,20 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
auto id = QString::number(Json::requireInteger(entry_obj, "id"));
auto hash = addonIds.find(id).value();
auto mod = m_mods.find(hash).value();
auto resource = m_resources.find(hash).value();
try {
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(mod->name()));
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name()));
ModPlatform::IndexedPack pack;
FlameMod::loadIndexedPack(pack, entry_obj);
flameCallback(pack, m_temp_versions.find(hash).value(), mod);
flameCallback(pack, m_temp_versions.find(hash).value(), resource);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << entries;
emitFail(mod);
emitFail(resource);
}
}
} catch (Json::JsonException& e) {
@ -490,17 +491,17 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
return proj_task;
}
void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod* mod)
void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource* resource)
{
// Prevent file name mismatch
ver.fileName = mod->fileinfo().fileName();
ver.fileName = resource->fileinfo().fileName();
if (ver.fileName.endsWith(".disabled"))
ver.fileName.chop(9);
QDir tmp_index_dir(m_index_dir);
{
LocalModUpdateTask update_metadata(m_index_dir, pack, ver);
LocalResourceUpdateTask update_metadata(m_index_dir, pack, ver);
QEventLoop loop;
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
@ -514,27 +515,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(mod);
emitFail(resource);
return;
}
mod->setMetadata(metadata);
resource->setMetadata(metadata);
emitReady(mod);
emitReady(resource);
}
void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod* mod)
void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource* resource)
{
try {
// Prevent file name mismatch
ver.fileName = mod->fileinfo().fileName();
ver.fileName = resource->fileinfo().fileName();
if (ver.fileName.endsWith(".disabled"))
ver.fileName.chop(9);
QDir tmp_index_dir(m_index_dir);
{
LocalModUpdateTask update_metadata(m_index_dir, pack, ver);
LocalResourceUpdateTask update_metadata(m_index_dir, pack, ver);
QEventLoop loop;
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
@ -548,16 +549,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(mod);
emitFail(resource);
return;
}
mod->setMetadata(metadata);
resource->setMetadata(metadata);
emitReady(mod);
emitReady(resource);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
emitFail(mod);
emitFail(resource);
}
}

View file

@ -1,22 +1,23 @@
#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(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(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() = default;
@ -37,23 +38,23 @@ class EnsureMetadataTask : public Task {
// Helpers
enum class RemoveFromList { Yes, No };
void emitReady(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
void emitFail(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
void emitReady(Resource*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
void emitFail(Resource*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
// Hashes and stuff
auto createNewHash(Mod*) -> Hashing::Hasher::Ptr;
auto getExistingHash(Mod*) -> QString;
auto createNewHash(Resource*) -> Hashing::Hasher::Ptr;
auto getExistingHash(Resource*) -> QString;
private slots:
void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*);
void flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*);
void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource*);
void flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource*);
signals:
void metadataReady(Mod*);
void metadataFailed(Mod*);
void metadataReady(Resource*);
void metadataFailed(Resource*);
private:
QHash<QString, Mod*> m_mods;
QHash<QString, Resource*> m_resources;
QDir m_index_dir;
ModPlatform::ResourceProvider m_provider;

View file

@ -76,6 +76,7 @@ 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;
@ -86,7 +87,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, [&]() {
connect(jobPtr.get(), &NetJob::succeeded, this, [this]() {
abortable = false;
jobPtr.reset();
extractConfigs();
});
connect(jobPtr.get(), &NetJob::failed, [&](QString reason) {
connect(jobPtr.get(), &NetJob::failed, [this](QString reason) {
abortable = false;
jobPtr.reset();
emitFailed(reason);
});
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
connect(jobPtr.get(), &NetJob::progress, [this](qint64 current, qint64 total) {
abortable = true;
setProgress(current, total);
});
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
connect(jobPtr.get(), &NetJob::aborted, [&] {
connect(jobPtr.get(), &NetJob::aborted, [this] {
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, [&]() { downloadMods(); });
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]() { emitAborted(); });
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, [this]() { downloadMods(); });
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [this]() { emitAborted(); });
m_extractFutureWatcher.setFuture(m_extractFuture);
}

View file

@ -87,6 +87,30 @@ 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);
@ -232,6 +256,10 @@ 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,59 +121,65 @@ ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileI
* */
void FlameCheckUpdate::executeTask()
{
setStatus(tr("Preparing mods for CurseForge..."));
setStatus(tr("Preparing resources for CurseForge..."));
int i = 0;
for (auto* mod : m_mods) {
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name()));
setProgress(i++, m_mods.size());
for (auto* resource : m_resources) {
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(resource->name()));
setProgress(i++, m_resources.size());
auto latest_vers = api.getLatestVersions({ { mod->metadata()->project_id.toString() }, m_game_versions });
auto latest_vers = api.getLatestVersions({ { resource->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, mod->loaders());
auto latest_ver = api.getLatestVersion(latest_vers, m_loaders_list, resource->metadata()->loaders);
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(mod->name()));
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name()));
if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) {
emit checkFailed(mod, tr("No valid version found for this mod. It's probably unavailable for the current game "
"version / mod loader."));
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);
continue;
}
if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != mod->metadata()->file_id) {
if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->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(mod, tr("Mod has a new update available, but is not downloadable using CurseForge."), recover_url);
emit checkFailed(resource, tr("Resource 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 = 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->name = resource->name();
pack->slug = resource->metadata()->slug;
pack->addonId = resource->metadata()->project_id;
pack->provider = ModPlatform::ResourceProvider::FLAME;
if (!latest_ver->hash.isEmpty() && (mod->metadata()->hash != latest_ver->hash || mod->status() == ModStatus::NotInstalled)) {
auto old_version = mod->version();
if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) {
auto current_ver = getFileInfo(latest_ver->addonId.toInt(), mod->metadata()->file_id.toInt());
old_version = current_ver.version;
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");
}
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());
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());
}
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<Mod*>& mods,
FlameCheckUpdate(QList<Resource*>& resources,
std::list<Version>& mcVersions,
QList<ModPlatform::ModLoaderType> loadersList,
std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loadersList, mods_folder)
std::shared_ptr<ResourceFolderModel> resourceModel)
: CheckUpdateTask(resources, mcVersions, std::move(loadersList), std::move(resourceModel))
{}
public slots:

View file

@ -36,7 +36,7 @@
#include "FlameInstanceCreationTask.h"
#include "QObjectPtr.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "minecraft/mod/tasks/LocalResourceUpdateTask.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_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();
if (m_processUpdateFileInfoJob)
m_processUpdateFileInfoJob->abort();
if (m_filesJob)
m_filesJob->abort();
if (m_modIdResolver)
m_modIdResolver->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_process_update_file_info_job = job;
m_processUpdateFileInfoJob = job;
job->start();
loop.exec();
m_process_update_file_info_job = nullptr;
m_processUpdateFileInfoJob = 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_managed_id.isEmpty())
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
if (!m_managedId.isEmpty())
instance.setManagedPack("flame", m_managedId, m_pack.name, m_managedVersionId, m_pack.version);
else
instance.setManagedPack("flame", "", name(), "", "");
instance.setName(name());
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();
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();
setError(tr("Unable to resolve mod IDs:\n") + reason);
loop.quit();
});
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();
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();
loop.exec();
@ -468,14 +468,14 @@ bool FlameCreationTask::createInstance()
void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
{
auto results = m_mod_id_resolver->getResults();
auto results = m_modIdResolver->getResults();
// first check for blocked mods
QList<BlockedMod> blocked_mods;
auto anyBlocked = false;
for (const auto& result : results.files.values()) {
if (result.version.fileName.endsWith(".zip")) {
m_ZIP_resources.append(std::make_pair(result.version.fileName, result.targetFolder));
if (result.resourceType != PackedResourceType::Mod) {
m_otherResources.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_mod_id_resolver.reset();
m_modIdResolver.reset();
setError("Canceled");
loop.quit();
}
@ -518,8 +518,8 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
{
m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
auto results = m_mod_id_resolver->getResults().files;
m_filesJob.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
auto results = m_modIdResolver->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_files_job->addNetAction(dl);
m_filesJob->addNetAction(dl);
}
}
connect(m_files_job.get(), &NetJob::finished, this, [this, &loop]() {
m_files_job.reset();
validateZIPResources(loop);
connect(m_filesJob.get(), &NetJob::finished, this, [this, &loop]() {
m_filesJob.reset();
validateOtherResources(loop);
});
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
m_files_job.reset();
connect(m_filesJob.get(), &NetJob::failed, [this](QString reason) {
m_filesJob.reset();
setError(reason);
});
connect(m_files_job.get(), &NetJob::progress, this, [this](qint64 current, qint64 total) {
connect(m_filesJob.get(), &NetJob::progress, this, [this](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
setProgress(current, total);
});
connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propagateStepProgress);
connect(m_filesJob.get(), &NetJob::stepProgress, this, &FlameCreationTask::propagateStepProgress);
setStatus(tr("Downloading mods..."));
m_files_job->start();
m_filesJob->start();
}
/// @brief copy the matched blocked mods to the instance staging area
@ -597,8 +597,14 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
qDebug() << "Will try to copy" << mod.localPath << "to" << destPath;
if (!FS::copy(mod.localPath, destPath)()) {
qDebug() << "Copy of" << mod.localPath << "to" << destPath << "Failed";
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";
}
}
i++;
@ -608,11 +614,11 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
setAbortable(true);
}
void FlameCreationTask::validateZIPResources(QEventLoop& loop)
void FlameCreationTask::validateOtherResources(QEventLoop& loop)
{
qDebug() << "Validating whether resources stored as .zip are in the right place";
qDebug() << "Validating whether other resources are in the right place";
QStringList zipMods;
for (auto [fileName, targetFolder] : m_ZIP_resources) {
for (auto [fileName, targetFolder] : m_otherResources) {
qDebug() << "Checking" << fileName << "...";
auto localPath = FS::PathCombine(m_stagingPath, "minecraft", targetFolder, fileName);
@ -672,21 +678,23 @@ void FlameCreationTask::validateZIPResources(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_mod_id_resolver->getResults().files;
auto results = m_modIdResolver->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<LocalModUpdateTask>(folder, file.pack, file.version));
task->addTask(makeShared<LocalResourceUpdateTask>(folder, file.pack, file.version));
}
connect(task.get(), &Task::finished, &loop, &QEventLoop::quit);
m_process_update_file_info_job = task;
m_processUpdateFileInfoJob = 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_managed_id(std::move(id)), m_managed_version_id(std::move(version_id))
: InstanceCreationTask(), m_parent(parent), m_managedId(std::move(id)), m_managedVersionId(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 validateZIPResources(QEventLoop& loop);
void validateOtherResources(QEventLoop& loop);
QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion);
private:
QWidget* m_parent = nullptr;
shared_qobject_ptr<Flame::FileResolvingTask> m_mod_id_resolver;
shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver;
Flame::Manifest m_pack;
// Handle to allow aborting
Task::Ptr m_process_update_file_info_job = nullptr;
NetJob::Ptr m_files_job = nullptr;
Task::Ptr m_processUpdateFileInfoJob = nullptr;
NetJob::Ptr m_filesJob = nullptr;
QString m_managed_id, m_managed_version_id;
QString m_managedId, m_managedVersionId;
QList<std::pair<QString, QString>> m_ZIP_resources;
QList<std::pair<QString, QString>> m_otherResources;
std::optional<InstancePtr> m_instance;
};

View file

@ -40,6 +40,7 @@
#include <QString>
#include <QUrl>
#include <QVector>
#include "minecraft/mod/tasks/LocalResourceParse.h"
#include "modplatform/ModIndex.h"
namespace Flame {
@ -54,6 +55,7 @@ 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->metaurl().toHtmlEscaped();
auto url = mod->homepage().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->metaurl();
auto url = mod->homepage();
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->metaurl();
auto url = mod->homepage();
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->metaurl();
auto url = mod->homepage();
if (!url.isEmpty())
line["url"] = url;
}
@ -156,7 +156,7 @@ QString toCSV(QList<Mod*> mods, OptionalData extraData)
data << modName;
if (extraData & Url)
data << mod->metaurl();
data << mod->homepage();
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->metaurl();
auto url = mod->homepage();
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, [=] {
QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {

View file

@ -71,13 +71,15 @@ class ModrinthAPI : public NetworkResourceAPI {
static auto getSideFilters(QString side) -> const QString
{
if (side.isEmpty() || side == "both") {
if (side.isEmpty()) {
return {};
}
if (side == "both")
return QString("\"client_side:required\"],[\"server_side:required\"");
if (side == "client")
return QString("\"client_side:required\",\"client_side:optional\"");
return QString("\"client_side:required\",\"client_side:optional\"],[\"server_side:optional\",\"server_side:unsupported\"");
if (side == "server")
return QString("\"server_side:required\",\"server_side:optional\"");
return QString("\"server_side:required\",\"server_side:optional\"],[\"client_side:optional\",\"client_side:unsupported\"");
return {};
}
@ -131,6 +133,8 @@ 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,14 +15,6 @@
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)
@ -37,24 +29,24 @@ bool ModrinthCheckUpdate::abort()
* */
void ModrinthCheckUpdate::executeTask()
{
setStatus(tr("Preparing mods for Modrinth..."));
setProgress(0, 9);
setStatus(tr("Preparing resources for Modrinth..."));
setProgress(0, (m_loaders_list.isEmpty() ? 1 : m_loaders_list.length()) * 2 + 1);
auto hashing_task =
makeShared<ConcurrentTask>("MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
for (auto* mod : m_mods) {
auto hash = mod->metadata()->hash;
for (auto* resource : m_resources) {
auto hash = resource->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 (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); });
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); });
connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); });
hashing_task->addTask(hash_task);
} else {
m_mappings.insert(hash, mod);
m_mappings.insert(hash, resource);
}
}
@ -63,10 +55,28 @@ void ModrinthCheckUpdate::executeTask()
hashing_task->start();
}
void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> response,
ModPlatform::ModLoaderTypes loader,
bool forceModLoaderCheck)
void ModrinthCheckUpdate::getUpdateModsForLoader(std::optional<ModPlatform::ModLoaderTypes> loader)
{
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) {
@ -78,29 +88,31 @@ 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 {
for (auto hash : m_mappings.keys()) {
if (forceModLoaderCheck && !(m_mappings[hash]->loaders() & loader)) {
continue;
}
auto iter = m_mappings.begin();
while (iter != m_mappings.end()) {
const QString hash = iter.key();
Resource* resource = iter.value();
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;
for (auto flag : ModPlatform::modLoaderTypesToList(loader)) {
loader_filter = ModPlatform::getModLoaderAsString(flag);
break;
if (loader.has_value()) {
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:
@ -112,106 +124,72 @@ 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 = 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->name = resource->name();
pack->slug = resource->metadata()->slug;
pack->addonId = resource->metadata()->project_id;
pack->provider = ModPlatform::ResourceProvider::MODRINTH;
if ((key != hash && project_ver.is_preferred) || (mod->status() == ModStatus::NotInstalled)) {
if (mod->version() == project_ver.version_number)
continue;
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);
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder);
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");
}
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_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_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_next_loader_idx < m_loaders_list.size()) {
getUpdateModsForLoader(m_loaders_list.at(m_next_loader_idx));
m_next_loader_idx++;
if (m_loaders_list.isEmpty() && m_loader_idx == 0) {
getUpdateModsForLoader({});
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);
}
if (m_loader_idx < m_loaders_list.size()) {
getUpdateModsForLoader(m_loaders_list.at(m_loader_idx));
m_loader_idx++;
return;
}
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."));
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);
}
emitSucceeded();
return;
}

View file

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

View file

@ -243,7 +243,8 @@ bool ModrinthCreationTask::createInstance()
auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path);
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
QHash<QString, Mod*> mods;
// TODO make this work with other sorts of resource
QHash<QString, Resource*> resources;
for (auto file : m_files) {
auto fileName = file.path;
fileName = FS::RemoveInvalidPathChars(fileName);
@ -259,7 +260,7 @@ bool ModrinthCreationTask::createInstance()
ModDetails d;
d.mod_id = file_path;
mod->setDetails(d);
mods[file.hash.toHex()] = mod;
resources[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));
@ -285,13 +286,13 @@ bool ModrinthCreationTask::createInstance()
bool ended_well = false;
connect(downloadMods.get(), &NetJob::succeeded, this, [&]() { ended_well = true; });
connect(downloadMods.get(), &NetJob::failed, [&](const QString& reason) {
connect(downloadMods.get(), &NetJob::succeeded, this, [&ended_well]() { ended_well = true; });
connect(downloadMods.get(), &NetJob::failed, [this, &ended_well](const QString& reason) {
ended_well = false;
setError(reason);
});
connect(downloadMods.get(), &NetJob::finished, &loop, &QEventLoop::quit);
connect(downloadMods.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
connect(downloadMods.get(), &NetJob::progress, [this](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
setProgress(current, total);
});
@ -304,18 +305,18 @@ bool ModrinthCreationTask::createInstance()
loop.exec();
if (!ended_well) {
for (auto m : mods) {
delete m;
for (auto resource : resources) {
delete resource;
}
return ended_well;
}
QEventLoop ensureMetaLoop;
QDir folder = FS::PathCombine(instance.modsRoot(), ".index");
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(mods, folder, ModPlatform::ResourceProvider::MODRINTH);
connect(ensureMetadataTask.get(), &Task::succeeded, this, [&]() { ended_well = true; });
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(resources, folder, ModPlatform::ResourceProvider::MODRINTH);
connect(ensureMetadataTask.get(), &Task::succeeded, this, [&ended_well]() { ended_well = true; });
connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit);
connect(ensureMetadataTask.get(), &Task::progress, [&](qint64 current, qint64 total) {
connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
setProgress(current, total);
});
@ -325,10 +326,10 @@ bool ModrinthCreationTask::createInstance()
m_task = ensureMetadataTask;
ensureMetaLoop.exec();
for (auto m : mods) {
delete m;
for (auto resource : resources) {
delete resource;
}
mods.clear();
resources.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) {
QUrl& url = mod->metadata()->url;
const 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(QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString
auto getRealIndexName(const 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]] QDir& index_dir,
auto V1::createModFormat([[maybe_unused]] const QDir& index_dir,
ModPlatform::IndexedPack& mod_pack,
ModPlatform::IndexedVersion& mod_version) -> Mod
{
@ -119,10 +119,14 @@ auto V1::createModFormat([[maybe_unused]] 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(QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, QString slug) -> Mod
auto V1::createModFormat(const QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, QString slug) -> Mod
{
// Try getting metadata if it exists
Mod mod{ getIndexForMod(index_dir, slug) };
@ -134,7 +138,7 @@ auto V1::createModFormat(QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod,
return {};
}
void V1::updateModIndex(QDir& index_dir, Mod& mod)
void V1::updateModIndex(const QDir& index_dir, Mod& mod)
{
if (!mod.isValid()) {
qCritical() << QString("Tried to update metadata of an invalid mod!");
@ -208,6 +212,7 @@ void V1::updateModIndex(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() },
@ -225,7 +230,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod)
index_file.close();
}
void V1::deleteModIndex(QDir& index_dir, QString& mod_slug)
void V1::deleteModIndex(const QDir& index_dir, QString& mod_slug)
{
auto normalized_fname = indexFileName(mod_slug);
auto real_fname = getRealIndexName(index_dir, normalized_fname);
@ -244,7 +249,7 @@ void V1::deleteModIndex(QDir& index_dir, QString& mod_slug)
}
}
void V1::deleteModIndex(QDir& index_dir, QVariant& mod_id)
void V1::deleteModIndex(const QDir& index_dir, QVariant& mod_id)
{
for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) {
auto mod = getIndexForMod(index_dir, file_name);
@ -256,7 +261,7 @@ void V1::deleteModIndex(QDir& index_dir, QVariant& mod_id)
}
}
auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod
{
Mod mod;
@ -292,7 +297,7 @@ auto V1::getIndexForMod(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(stringEntry(table, "x-prismlauncher-release-type"));
mod.releaseType = ModPlatform::IndexedVersionType(table["x-prismlauncher-release-type"].value_or(""));
if (auto loaders = table["x-prismlauncher-loaders"]; loaders && loaders.is_array()) {
for (auto&& loader : *loaders.as_array()) {
if (loader.is_string()) {
@ -312,6 +317,7 @@ auto V1::getIndexForMod(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();
@ -353,7 +359,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
return mod;
}
auto V1::getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod
auto V1::getIndexForMod(const 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,11 +32,13 @@ class Mod;
namespace Packwiz {
auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString;
auto getRealIndexName(const 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{};
@ -56,6 +58,7 @@ 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.
@ -70,33 +73,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(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod;
static auto createModFormat(const 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(QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod;
static auto createModFormat(const 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(QDir& index_dir, Mod& mod);
static void updateModIndex(const 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(QDir& index_dir, QString& mod_slug);
static void deleteModIndex(const 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(QDir& index_dir, QVariant& mod_id);
static void deleteModIndex(const 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(QDir& index_dir, QString slug) -> Mod;
static auto getIndexForMod(const 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(QDir& index_dir, QVariant& mod_id) -> Mod;
static auto getIndexForMod(const 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: 14 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 157 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Before After
Before After

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