Compare commits

..

230 Commits

Author SHA1 Message Date
55207a58a6 fix(packaging): only exclude *some* folders in win>=2h1h 2023-07-26 00:44:28 -04:00
163c22ce4c TESTING: force an autoupdate 2023-07-16 17:18:44 -04:00
615600ec13 fix(packaging): disable filesystem/reg virtualization
prism won't be able to make %appdata%\PrismLauncher on first launch,
which would be ok...but this in turn breaks opening and folders :(

Signed-off-by: seth <getchoo@tuta.io>
2023-07-15 22:38:18 -04:00
17211c2100 TESTING: always check for updates on launch 2023-07-15 19:55:22 -04:00
6f113ae9d6 fix(packaging): use 2018 schema in appinstaller
Signed-off-by: seth <getchoo@tuta.io>
2023-07-15 19:55:22 -04:00
46ce358bb7 fix(packaging): install correct vc++ package in msix
Signed-off-by: seth <getchoo@tuta.io>
2023-07-15 19:55:15 -04:00
aa693f86f4 fix(packaging): remove unnecessary/incompatible elements in msix
Signed-off-by: seth <getchoo@tuta.io>
2023-07-10 20:31:01 -04:00
d6c708da42 disable some other workflows 2023-07-10 20:19:05 -04:00
271fb8829a fix(actions): fix msixbundle versioning
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 15:27:48 -04:00
15f34451b7 feat(packaging): add update/repair uris in msix
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 14:28:22 -04:00
b3d8de2a7a temporarily disable some workflows 2023-07-07 07:44:55 -04:00
2c83ef1c46 fix(actions): use correct variable for appx version
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:08 -04:00
4562700653 fix(packaging): don't sign appinstaller
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:08 -04:00
eb7739c0ca use temp url for testing 2023-07-07 06:46:08 -04:00
a401a9ceb7 revert(actions): build setup.exes for modern msvc again
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:08 -04:00
7dda497cc6 chore(actions): use msix for modern builds in winget release
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:07 -04:00
5e5faa7239 chore(packaging): use org.prismlauncher in msix app id
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:07 -04:00
55ac7625ff chore(packaging): use correct publisher name for msix
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:07 -04:00
c065d06149 chore(actions): include prism_version in matrix for msix
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:07 -04:00
76cc8a33d4 feat(packaging): register file associations in msix
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:06 -04:00
84498285c0 feat(packaging): bundle vc++2022 with msix
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:06 -04:00
faeadeccca feat(packaging): support older windows versions in msix
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:06 -04:00
a67fb3e5ca chore(actions): add msix(bundle)/appinstaller to trigger_release
this probably works...right?

Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:06 -04:00
32b9adec82 chore(actions): distribute appinstaller
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:05 -04:00
6866e5367e feat(packaging): add support for appinstaller
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:05 -04:00
1c66c4ed82 fix(actions): limit msix version suffix to 3 numbers
good thing i got an unlucky commit hash :p

Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:05 -04:00
0df38008a1 fix(actions): checkout repo during bundle-msix
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:05 -04:00
6daa653ab3 fix(packaging): don't use letters in msix version
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:04 -04:00
a37f1dd69d feat(packaging): start using msixbundles
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:04 -04:00
862acca151 fix(packaging): don't hardcode arch/version in appmanifest
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:04 -04:00
c3770a9a32 feat(packaging): use msix for msvc builds
Signed-off-by: seth <getchoo@tuta.io>
2023-07-07 06:46:04 -04:00
2cb22ad280 Merge pull request #1310 from getchoo/autoupdate-flake
feat(actions): add update-flake-lock
2023-07-05 23:25:12 +00:00
5ba13297c6 Merge pull request #1284 from Ryex/fix/properly-track-failed-copies-and-clones 2023-07-05 19:26:07 +02:00
24b9ed106f feat(actions): add update-flake-lock
Signed-off-by: seth <getchoo@tuta.io>
2023-07-04 18:49:22 -04:00
7c8a010378 Merge pull request #1304 from Scrumplex/chore-flake-update-1
flake.lock: Update
2023-07-04 22:43:00 +00:00
dedc9e4edc Merge pull request #1127 from Trial97/scale_cat 2023-07-03 15:46:56 +02:00
14692eed40 flake.lock: Update
Flake lock file updates:

• Updated input 'flake-parts':
    'github:hercules-ci/flake-parts/006c75898cf814ef9497252b022e91c946ba8e17' (2023-05-08)
  → 'github:hercules-ci/flake-parts/267149c58a14d15f7f81b4d737308421de9d7152' (2023-07-01)
• Updated input 'flake-parts/nixpkgs-lib':
    'github:NixOS/nixpkgs/da45bf6ec7bbcc5d1e14d3795c025199f28e0de0?dir=lib' (2023-04-30)
  → 'github:NixOS/nixpkgs/4bc72cae107788bf3f24f30db2e2f685c9298dc9?dir=lib' (2023-06-29)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/aeb75dba965e790de427b73315d5addf91a54955' (2023-05-25)
  → 'github:nixos/nixpkgs/cd99c2b3c9f160cd004318e0697f90bbd5960825' (2023-07-01)
• Updated input 'pre-commit-hooks':
    'github:cachix/pre-commit-hooks.nix/61e567d6497bc9556f391faebe5e410e6623217f' (2023-05-23)
  → 'github:cachix/pre-commit-hooks.nix/42587d3414d1747999a5f71e92a83cf6547b62da' (2023-07-03)
• Updated input 'pre-commit-hooks/flake-utils':
    'github:numtide/flake-utils/5aed5285a952e0b949eb3ba02c12fa4fcfef535f' (2022-11-02)
  → 'github:numtide/flake-utils/a1720a10a6cfe8234c0e93907ffe81be440f4cef' (2023-05-31)
• Added input 'pre-commit-hooks/flake-utils/systems':
    'github:nix-systems/default/da67096a3b9bf56a91d16901293e51ba5b49a27e' (2023-04-09)

Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2023-07-03 14:27:02 +02:00
43e6f05ed5 Merge pull request #1232 from telans/screenshots-update
ScreenshotsPage fixes
2023-07-02 16:44:54 +01:00
b51f1f1d41 Merge pull request #1298 from TurboWafflz/develop 2023-07-02 09:52:59 +02:00
3fe518ff2b Fix compiling on OpenBSD
Signed-off-by: Finian Wright <turbowafflz@gmail.com>
2023-07-02 00:36:21 -04:00
c523765c19 Merge pull request #1292 from Trial97/export5
Removed logs from instance export
2023-07-01 04:14:35 +00:00
18e628e873 removed extra condition
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-30 12:10:00 +03:00
81207c6502 Made sure the logs are ignored when collecting files
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-30 10:52:10 +03:00
87efa700ab Removed logs from instance export
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-30 00:01:36 +03:00
0008b22d8b Renamed function
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-28 18:41:47 +03:00
8f9bd9617f Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into scale_cat 2023-06-28 18:29:04 +03:00
faec21d572 Merge pull request #1266 from TheKodeToad/smol-tweaks 2023-06-28 08:47:53 +02:00
213f03351f Merge pull request #1280 from Trial97/shortcut
Fixed illegal characters in shortcuts name
2023-06-27 20:01:48 +00:00
00be211169 Update launcher/FileSystem.cpp
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-06-27 13:01:28 -07:00
90fc720190 Merge pull request #1281 from Trial97/screenshot
Added more information to the screenshot upload warning
2023-06-27 20:01:18 +00:00
23b3711f96 fix(filesystem): track failed copies and clones
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-06-27 12:41:36 -07:00
54cb077b40 Added more information to the screenshot upload warning
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-27 19:31:36 +03:00
92847b9774 omit icon remove on macos
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-27 19:15:20 +03:00
6e5716f097 Fixed illegal characters in shortcuts name
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-27 19:05:32 +03:00
904b128afe Merge pull request #1277 from Trial97/remove_mojang
Removed unused files
2023-06-26 23:09:27 +01:00
dffffc784e Removed unused files
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-26 22:33:10 +03:00
d25452a64e Merge pull request #1274 from TheKodeToad/java-signature 2023-06-26 11:19:53 +02:00
41d0f74750 Merge pull request #1275 from Scrumplex/git-blame-ignore 2023-06-26 10:56:26 +02:00
f8f1c3cf23 chore: add Git Blame ignore file
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2023-06-26 09:08:03 +02:00
ed4dce2fb6 fix: install logo to multimc theme in genicons.sh
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2023-06-26 08:59:48 +02:00
4d49486cc9 Merge pull request #1065 from leo78913/gamescope-close-button 2023-06-26 08:45:28 +02:00
8bebd7f042 Generate special signature composed of multiple elements instead of relying on timestamp for Java version cache invalidation
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-26 01:30:47 +01:00
903fae94be Merge pull request #1272 from Trial97/fix_selected
fix: Page container extra info set on logs page
2023-06-25 21:16:18 +00:00
6d0e255ca1 fix: Page container extra info set on logs page
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-25 22:00:33 +03:00
1bd778d0ae Merge pull request #1270 from TheKodeToad/fix-unchecked-value
Fix unsafe usage of std::optional::value in FlameAPI
2023-06-25 18:24:29 +01:00
4745ab64cd Deduplicate launcher icon
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-25 15:02:25 +01:00
Leo
953a2590e2 Add fixme comment for no SSD detection
Co-authored-by: Sefa Eyeoglu <contact@scrumplex.net>
Signed-off-by: Leo <leo3758@riseup.net>
2023-06-25 10:11:58 -03:00
02b628653b Fix markdown header
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-25 13:43:03 +01:00
c75ba0f855 Fix big mistake :iea:
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-25 12:46:07 +01:00
514080653f Fix unsafe usage of std::optional::value in FlameAPI
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-25 11:56:28 +01:00
603ed22015 Replace accidental usages of QAbstractButton::pressed
This signal is not usually what you want, and creates an inconsistent experience.

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-25 10:36:54 +01:00
5eb71fc6a9 Revert "feat(Mods): hide 'Provider' column when no mods have providers"
With Ryex's change, this causes issues. Apparently you need to sign off reverts! That's just weird...

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-24 23:36:32 +01:00
8a3aba1634 Fix Open Global Settings, why not
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-24 23:30:51 +01:00
bb8e6ef35e Fix flat white launcher icon
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-24 23:27:01 +01:00
529e2054ea A few tweaks, with inspiration from Zeke :3
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-24 23:26:27 +01:00
bcf45c74a1 Merge pull request #986 from Trial97/develop
Mod dependencies
2023-06-24 14:04:27 +01:00
8b576fd2bd Added translation
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-24 15:59:55 +03:00
7fdd68d768 Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into develop 2023-06-24 15:58:55 +03:00
df6d46de9e Merge pull request #1261 from telans/modrinthexport-url 2023-06-24 10:51:14 +02:00
4322222acc Merge pull request #1255 from Trial97/export4
Added Thumbs.db to excluded files in MrPackExport
2023-06-23 21:40:55 -07:00
cce6a54701 Merge pull request #1258 from leo78913/resources-pack-page-crash-fix
fix: fix crash when hiding columns on resource packs page
2023-06-24 00:39:47 -04:00
68865353cf Merge pull request #1259 from PrismLauncher/update-devs
Update developers
2023-06-23 21:38:16 -07:00
20ba6e5fb5 modrinth: use encoded url when exporting pack
Ensures that necessary url components such as spaces are encoded.
Prevents an error when submitting the resulting file to modrinth.

See https://discord.com/channels/734077874708938864/1120070731242410024

Fixes: #1226
Signed-off-by: James Beddek <telans@posteo.de>
2023-06-24 15:42:58 +12:00
d74a23d5b2 Update developers
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-23 21:00:55 +01:00
750209b8bb fix: fix crash when hiding columns on resource packs page
Signed-off-by: leo78913 <leo3758@riseup.net>
2023-06-23 16:55:51 -03:00
741c23a72a Merge pull request #1256 from Trial97/fix11
Fixed hashers
2023-06-23 19:53:44 +01:00
28de461067 Fixed hashers
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-23 21:38:41 +03:00
69c709b05a Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into develop
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-23 20:01:17 +03:00
046d510134 Merge pull request #1200 from Trial97/net_job_crash
Made ByteSynkArray to use shared_ptr
2023-06-23 09:13:52 -07:00
85bbab0e92 Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into net_job_crash 2023-06-23 14:38:30 +03:00
67db141203 Renamed getResults to resultsReady
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-23 14:38:23 +03:00
763b3c3236 Added Thumbs.db to excluded files in MrPackExport
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-23 10:38:26 +03:00
8df5ab8aa7 Merge pull request #1251 from getchoo/github-clarify
chore: add 'suggest a feature' message in help
2023-06-22 17:13:00 -04:00
83efdccfe1 Merge pull request #1252 from getchoo/import-hehe
chore: avoid confusion in file/url import dialog
2023-06-22 17:12:35 -04:00
f1ebec641a Merge pull request #1058 from Ryex/feature/images-for-resource-page
Feature: image coumn for Mod, Resource Pack, and Texturepack pages
2023-06-22 13:26:47 -07:00
a4521ac0bb chore: avoid confusion in file/url import dialog
Signed-off-by: seth <getchoo@tuta.io>
2023-06-22 16:15:03 -04:00
c5f16276d7 Merge pull request #1235 from ChrisLane/java-check-debug-msg-fix 2023-06-22 22:03:30 +02:00
03361e51ef chore: add 'suggest a feature' message in help
Signed-off-by: seth <getchoo@tuta.io>
2023-06-22 15:58:53 -04:00
05a8232a8f Merge pull request #1243 from Trial97/export
Added regex expresion to exclude .DS_Store files
2023-06-22 08:09:31 +02:00
2e82c1d40c Added regex expresion to exclude .DS_Store files
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-21 23:22:25 +03:00
0975dbc3dd Merge pull request #1228 from Trial97/curent_pack_crash
Fixes #1212
2023-06-21 15:35:09 +01:00
8aa02320e0 Merge pull request #1231 from telans/modrinth-default-icon
Modrinth: use default icon for non-managed packs
2023-06-21 12:25:26 +02:00
470518eb3a fix: resize columns on hide ^& uniform heights
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-06-21 02:31:40 -07:00
ca659136e2 Merge pull request #1119 from Trial97/mods_txt
Added dynamic page extra info
2023-06-20 19:56:38 -04:00
f1a6dc5332 Merge pull request #1184 from clickdevin/develop
Fix bugs when updating curseforge modpacks
2023-06-20 19:55:48 -04:00
4eaa7dc8b1 Merge pull request #1234 from Szowisz/develop
Fix compiling on FreeBSD
2023-06-20 19:55:03 -04:00
9ad29e8d85 Remove extra spaces in one more Java checker debug
Signed-off-by: Chris Lane <git@chrislane.com>
2023-06-20 15:51:31 +01:00
07f3d27fb8 Clean up 'Running java checker' debug msg code
Signed-off-by: Chris Lane <git@chrislane.com>
2023-06-20 15:36:25 +01:00
f2692e60f3 Add missing space in java checker debug message
Signed-off-by: Chris Lane <git@chrislane.com>
2023-06-20 12:44:44 +01:00
009623823d Modrinth: use default icon for non-managed packs
Fixes: #317
Signed-off-by: James Beddek <telans@posteo.de>
2023-06-20 23:00:13 +12:00
a32a3e25ad Fix compiling on FreeBSD
Signed-off-by: Jakub Wroński <kubawronski161@gmail.com>
2023-06-20 12:59:47 +02:00
3e3be9ae6f Added fallback for quilt if the API or Kotilin is not present
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-20 13:28:57 +03:00
3c648ff9fd Merge pull request #1233 from p2js/ui-consistency-fix
Remove inconsistent/unneeded question marks in UI
2023-06-20 11:05:26 +01:00
f769b0b4c6 Remove inconsistent/unneeded question marks in UI
Signed-off-by: P2 <tomarchioalfio5@gmail.com>
2023-06-20 10:40:27 +02:00
75bd626f33 Screenshots: do not retry image thumbnailing on null result
This causes the thumbnailing thread pool to spend a lot of time attempting to
retry an image that failed. A null result is common where the image is too large
to be allocated (>128MiB alloc).

The repeated retries continue after page delete, causing hangs if a user
tries to exit the application.

Fixes: #1201
Signed-off-by: James Beddek <telans@posteo.de>
2023-06-20 16:59:59 +12:00
f2471f0f68 Screenshots: clear the thumbnailing pool on page delete
Removes pending QThreadPool jobs which linger after page delete.
May help with #1201 by allowing the pool to finish earlier.

Signed-off-by: James Beddek <telans@posteo.de>
2023-06-20 16:54:15 +12:00
d9b24f7705 Screenshots: remove path from watcher if it no longer exists
Signed-off-by: James Beddek <telans@posteo.de>
2023-06-20 16:52:57 +12:00
b62e4c0cc7 Fixed build
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-19 21:32:19 +03:00
f6f0fbbd9f Fixed removeIf
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-19 21:23:48 +03:00
c13a90540c Added overide for Quilt/Fabric
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-19 21:20:35 +03:00
6fd729e285 Fixed regresion regarding modrinth project_id in dependence array
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-19 17:42:16 +03:00
8ad9692daa Changed qWarning to qDebug for raw data
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-19 14:10:29 +03:00
d02858040e Fixes #1212
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-19 13:09:37 +03:00
3ee0fec729 Removed mods from lambda
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-19 12:22:22 +03:00
ec063470d7 Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into mods_txt 2023-06-19 12:20:50 +03:00
b2ed2bf810 Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into net_job_crash 2023-06-19 12:01:35 +03:00
af6bf11793 Merge pull request #1224 from DioEgizio/add-appstream 2023-06-19 09:35:56 +02:00
d5b5f0503c chore: change xml name to what linuxdeploy wants
Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
2023-06-19 09:17:15 +02:00
555cb40efd chore: install appstream in appimage
Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
2023-06-19 09:17:15 +02:00
9d22fce53f Merge pull request #1214 from PrismLauncher/renovate/cachix-install-nix-action-22.x
chore(deps): update cachix/install-nix-action action to v22
2023-06-18 22:06:47 -04:00
aee0999daa Merge pull request #1210 from getchoo/opengl-appimage
fix(appimage): bundle generic opengl lib
2023-06-18 22:06:03 -04:00
1bdde1f947 Small fixes
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-19 00:36:37 +03:00
319ce8c19f Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into develop 2023-06-19 00:06:44 +03:00
4e66f55d84 Removed extra headers
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-18 23:32:17 +03:00
6826f1d605 Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into net_job_crash 2023-06-18 23:27:41 +03:00
0161520b33 Fixed leaks
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-18 23:27:26 +03:00
12cd8a7bea Merge pull request #1218 from flowln/bad_commit_string_in_bundled_package
Hide the Git commit entry on the About dialog when the build doesn't have git stuff support
2023-06-17 18:04:54 +02:00
2d00a727f6 fix: hide git commit when the build doesn't have git stuff support
This fixes the Git commit string being "GITDIR-NOTFOUND" on the About
page when building a package from the bundled source archive.

Signed-off-by: flow <flowlnlnln@gmail.com>
2023-06-17 11:09:05 -03:00
b123f4f948 chore(deps): update cachix/install-nix-action action to v22 2023-06-16 21:58:32 +00:00
fd9a8d1551 Merge pull request #1110 from TheKodeToad/version-search
Add a search bar to version lists
2023-06-16 14:47:39 -04:00
6bc1150ad0 Merge pull request #1187 from getchoo/rel-8.0
chore: bump to 8.0
2023-06-16 14:46:25 -04:00
45cce1d19a fix(appimage): bundle generic opengl lib
Signed-off-by: seth <getchoo@tuta.io>
2023-06-16 14:34:44 -04:00
147366bc0a Made ByteSynkArray to use shared_ptr
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-15 22:59:41 +03:00
6667ff9330 Updated text and fixed mod page text update
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-15 12:32:48 +03:00
5bf091149d Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into mods_txt 2023-06-15 11:46:44 +03:00
1ff8136f98 Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into scale_cat 2023-06-15 10:36:05 +03:00
182e19eae3 Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into develop 2023-06-15 10:35:26 +03:00
e8b0a7c6f0 chore: bump to 8.0
Signed-off-by: seth <getchoo@tuta.io>
2023-06-14 12:45:08 -04:00
d4f2059b78 Fix bugs when updating curseforge modpacks
Signed-off-by: clickdevin <git@clickdevin.me>
2023-06-14 10:42:37 -04:00
b7d82354cb [ci skip] License headers!! (yay)
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-10 14:43:58 +01:00
0b4807dc1f Questionable fix two
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-07 22:55:37 +01:00
d33de2e427 Made cat scalable
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-08 00:54:32 +03:00
1191c33c2b Remove flawed implementation
This seems to add the latest icon (bug) as a fallback if not provided...
but it mainly seems to cause problems... 🤷

I swear I did `git add .`.

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-07 22:45:21 +01:00
52054469cd Fix *bug* lol
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-07 22:39:23 +01:00
bbd9e4de9b Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into mods_txt 2023-06-07 20:18:56 +03:00
1e702ee40f Added dynamic page extra info
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-07 00:16:23 +03:00
a2d0d5a71d Allow arrow key movement, fix auto-focus
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-06 18:26:26 +01:00
c343036d3b Simplify
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-06 12:24:53 +01:00
7c5047b2ac cAsE iNsEnSiTiVe
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-05 23:12:47 +01:00
961285d6ab Add a search bar to version lists
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-06-05 22:47:42 +01:00
9ca74cd009 Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into develop
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-06-03 00:48:13 +03:00
5fe9e30f39 fix: use instance settings, make image column user resizeable, fix memory leak
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-05-28 14:53:15 -07:00
4eb9083ddc refactor: column names as class property, use string names in setting
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-05-28 13:00:08 -07:00
b28f682ad9 Merge branch 'develop' into feature/images-for-resource-page 2023-05-28 12:01:49 -07:00
51c39ec681 Merge branch 'Fix_Assert' of github.com:Trial97/PrismLauncher into develop 2023-05-28 19:16:14 +03:00
10436ed70c Fixed code quality
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-28 19:15:41 +03:00
775236a1b3 Merge branch 'Fix_Assert' of github.com:Trial97/PrismLauncher into develop
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-28 18:27:54 +03:00
b9503ff15f Added Q_DECLARE_METATYPE(ModPlatform::IndexedPack::Ptr)
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-28 18:27:02 +03:00
b4dff181f7 Fixed Ptr logic
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-28 18:22:55 +03:00
737fc1a2a4 Merge branch 'Fix_Assert' of github.com:Trial97/PrismLauncher into develop
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-28 18:01:39 +03:00
bdff8591aa Removed extra loop
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-28 17:54:46 +03:00
27c3775f99 Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into Fix_Assert
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-28 17:44:23 +03:00
bf0a577fb9 Fixed repaint issue
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-28 16:57:35 +03:00
7d79abb607 Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into Fix_Assert 2023-05-27 23:06:55 +03:00
086a7e19f0 feat: Column on left, hideable
- columns are hideable (saves to settings)
- image column moved to left
- datamodals can provide resize modes

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-05-24 20:15:34 -07:00
2f37cb31d9 Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into develop 2023-05-20 13:36:05 +03:00
e5534cd1f3 Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into Fix_Assert 2023-05-20 13:35:47 +03:00
4f0ec908ec feat: add a close button to the main toolbar when running on gamescope
Signed-off-by: leo78913 <leo3758@riseup.net>
2023-05-17 00:57:28 -03:00
94cd831e8d Made sure the metadata is valid when checking mod deps
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-14 22:13:53 +03:00
ffcc58cb3e Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into develop 2023-05-14 14:20:24 +03:00
8c7fd3327e Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into Fix_Assert 2023-05-14 14:20:04 +03:00
ef6cbdfa2a Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into develop 2023-05-07 19:01:20 +03:00
74e7c13a17 feat: display license and issue tracker
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-05-05 13:46:38 -07:00
3cfcc83ea9 change: don't toggle a resource's enabeling just by selecting it. only if they are on the checkbox.
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-05-05 13:46:01 -07:00
ee94be624e use 32x32 images for image column
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-05-05 11:28:19 -07:00
2fe3dc5960 fix: fix qchar conversion and codeql
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-05-05 11:13:36 -07:00
fd7338d3f3 fix: grow pixmapcache if it is evicting too fast
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-05-04 23:47:27 -07:00
ed185f047f feat(resourcePackPage): icon column
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-05-04 23:46:00 -07:00
d384d991fa feat(texturepackPage): icon column
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-05-04 23:45:24 -07:00
9913080a82 feat(modpage): mod icon in description and column
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-05-04 23:44:28 -07:00
ec157b766e feat(mod parsing): load extra mod details
- (image, license, issuetracker)

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
2023-05-04 23:42:42 -07:00
469ef3e06d Fixed code error
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-05 00:04:24 +03:00
107b470289 Updated required_by as dependency
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-04 23:54:46 +03:00
8b14b946e2 Merge branch 'Fix_Assert' of github.com:Trial97/PrismLauncher into develop
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-04 22:35:16 +03:00
f7b912fc9d Fixed comments
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-04 22:25:05 +03:00
9fbec3793f Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into develop 2023-05-04 20:29:33 +03:00
e4449a0ba3 Initialized variable
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-03 09:09:07 +03:00
42dc3ed03f Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into Fix_Assert 2023-05-03 00:56:26 +03:00
f6ed2036b3 Removed comment
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-03 00:55:18 +03:00
f8bf71e152 Moved the selected resources to one list
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-05-03 00:49:54 +03:00
61a2355618 Removed formated but not used files
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-27 01:41:26 +03:00
248920a221 Removed extra code
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-27 01:33:46 +03:00
b2ecb9ac09 Merge branch 'Fix_Assert' of github.com:Trial97/PrismLauncher into develop 2023-04-22 22:28:08 +03:00
f738d7566e Fixed code qulity
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-22 22:27:33 +03:00
1d167f8fda Merge branch 'Fix_Assert' of github.com:Trial97/PrismLauncher into develop
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-22 01:32:33 +03:00
460e83207f Fixed removeIf for Qt version
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-22 01:18:27 +03:00
75116364c6 Small Cleanup
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-22 00:55:11 +03:00
f7931c2ee2 Better version handling
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-22 00:47:51 +03:00
22bbf1bd1f Merge branch 'Fix_Assert' of github.com:Trial97/PrismLauncher into develop
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 21:11:09 +03:00
10aac4fe17 Fixed assert
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 21:03:01 +03:00
42bc91463e Updated links
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 20:37:17 +03:00
b4fa6e120a Fixed tipo
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 18:42:06 +03:00
2c744da9f7 More cleanup
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 18:42:06 +03:00
ffaa47bf54 Small cleanup
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 18:42:06 +03:00
63c4469475 Merge branch 'develop' of github.com:Trial97/PrismLauncher into develop 2023-04-21 18:42:06 +03:00
c1490cd627 Refator task to work with multiple providers
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 18:42:06 +03:00
1a390b6043 Merge branch 'PrismLauncher:develop' into develop 2023-04-21 18:42:06 +03:00
fac33498db Made some copy by reference
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 18:42:06 +03:00
31e84780a8 Hope to fix windows build errors
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 18:42:06 +03:00
f3f8f3574a Small headers removal
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 18:42:05 +03:00
7bd26ce468 Semi fixed the Modrinth dependency implementation
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 18:42:05 +03:00
bcea19b957 Tried to fix codeQL
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 18:42:05 +03:00
5079ce8d64 Fixed headers
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 18:42:05 +03:00
f231a33f6e Merge branch 'PrismLauncher:develop' into develop 2023-04-21 18:42:05 +03:00
4fe497cd68 First working version with curseforge mods
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 18:42:05 +03:00
5655a33515 Added Dependency API
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-21 18:42:05 +03:00
11f8d25d94 Added missing character
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-12 00:49:50 +03:00
4fbd5abe41 Added task to load dependencies
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-12 00:45:44 +03:00
d524935b67 Added task to load local mod information
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-11 20:55:10 +03:00
6d5c629b43 Added dependencies to the APIs
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2023-04-10 00:04:35 +03:00
147 changed files with 2843 additions and 1178 deletions

1
.envrc
View File

@ -1 +1,2 @@
use flake use flake

View File

@ -32,33 +32,33 @@ jobs:
matrix: matrix:
include: include:
- os: ubuntu-20.04 #- os: ubuntu-20.04
qt_ver: 5 # qt_ver: 5
- os: ubuntu-20.04 #- os: ubuntu-20.04
qt_ver: 6 # qt_ver: 6
qt_host: linux # qt_host: linux
qt_arch: '' # qt_arch: ''
qt_version: '6.2.4' # qt_version: '6.2.4'
qt_modules: 'qt5compat qtimageformats' # qt_modules: 'qt5compat qtimageformats'
qt_tools: '' # qt_tools: ''
- os: windows-2022 #- os: windows-2022
name: "Windows-MinGW-w64" # name: "Windows-MinGW-w64"
msystem: clang64 # msystem: clang64
vcvars_arch: 'amd64_x86' # vcvars_arch: 'amd64_x86'
- os: windows-2022 #- os: windows-2022
name: "Windows-MSVC-Legacy" # name: "Windows-MSVC-Legacy"
msystem: '' # msystem: ''
architecture: 'win32' # architecture: 'win32'
vcvars_arch: 'amd64_x86' # vcvars_arch: 'amd64_x86'
qt_ver: 5 # qt_ver: 5
qt_host: windows # qt_host: windows
qt_arch: 'win32_msvc2019' # qt_arch: 'win32_msvc2019'
qt_version: '5.15.2' # qt_version: '5.15.2'
qt_modules: '' # qt_modules: ''
qt_tools: 'tools_openssl_x86' # qt_tools: 'tools_openssl_x86'
- os: windows-2022 - os: windows-2022
name: "Windows-MSVC" name: "Windows-MSVC"
@ -71,6 +71,7 @@ jobs:
qt_version: '6.5.1' qt_version: '6.5.1'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
prism_version: '8.0'
- os: windows-2022 - os: windows-2022
name: "Windows-MSVC-arm64" name: "Windows-MSVC-arm64"
@ -83,25 +84,26 @@ jobs:
qt_version: '6.5.1' qt_version: '6.5.1'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
prism_version: '8.0'
- os: macos-12 #- os: macos-12
name: macOS # name: macOS
macosx_deployment_target: 11.0 # macosx_deployment_target: 11.0
qt_ver: 6 # qt_ver: 6
qt_host: mac # qt_host: mac
qt_arch: '' # qt_arch: ''
qt_version: '6.5.0' # qt_version: '6.5.0'
qt_modules: 'qt5compat qtimageformats' # qt_modules: 'qt5compat qtimageformats'
qt_tools: '' # qt_tools: ''
- os: macos-12 #- os: macos-12
name: macOS-Legacy # name: macOS-Legacy
macosx_deployment_target: 10.13 # macosx_deployment_target: 10.13
qt_ver: 5 # qt_ver: 5
qt_host: mac # qt_host: mac
qt_version: '5.15.2' # qt_version: '5.15.2'
qt_modules: '' # qt_modules: ''
qt_tools: '' # qt_tools: ''
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -264,23 +266,23 @@ jobs:
- name: Configure CMake (macOS) - name: Configure CMake (macOS)
if: runner.os == 'macOS' && matrix.qt_ver == 6 if: runner.os == 'macOS' && matrix.qt_ver == 6
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja
- name: Configure CMake (macOS-Legacy) - name: Configure CMake (macOS-Legacy)
if: runner.os == 'macOS' && matrix.qt_ver == 5 if: runner.os == 'macOS' && matrix.qt_ver == 5
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja
- name: Configure CMake (Windows MinGW-w64) - name: Configure CMake (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0} shell: msys2 {0}
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -G Ninja
- name: Configure CMake (Windows MSVC) - name: Configure CMake (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == '' if: runner.os == 'Windows' && matrix.msystem == ''
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
if ("${{ env.CCACHE_VAR }}") if ("${{ env.CCACHE_VAR }}")
{ {
@ -295,7 +297,7 @@ jobs:
- name: Configure CMake (Linux) - name: Configure CMake (Linux)
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=Linux -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja
## ##
# BUILD # BUILD
@ -428,13 +430,36 @@ jobs:
Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
- name: Package (Windows, installer) - name: Package (Windows, setup.exe)
if: runner.os == 'Windows' if: runner.os == 'Windows'
run: | run: |
cd ${{ env.INSTALL_DIR }} cd ${{ env.INSTALL_DIR }}
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi" makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
- name: Sign installer (Windows) - name: Package (Windows MSVC, MSIX)
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'win32'
shell: pwsh
run: |
Set-Location ${{ env.INSTALL_DIR }}
# MSIX only allows numbers in their versions,
# so we need to use another clear identifier
$runNumber = "${{ github.run_number }}"
if ($runNumber.Length -gt 3) {
$version = $runNumber.Substring(($runNumber.Length - 3))
} else {
$version = $runNumber
}
Copy-Item ${{ github.workspace }}\program_info\prismlauncher_*x*.png .
(Get-Content ${{ github.workspace }}\program_info\AppxManifest.xml) `
-replace "PRISM_VERSION_REPLACEME","${{ matrix.prism_version }}.0.$version" `
-replace "PRISM_ARCH_REPLACEME","${{ matrix.architecture }}" `
> .\AppxManifest.xml
makeappx.exe pack /v /h SHA256 /d . /p prismlauncher-${{ matrix.architecture }}.msix
- name: Sign installer (Windows, setup.exe)
if: runner.os == 'Windows' if: runner.os == 'Windows'
run: | run: |
if (Get-Content ./codesign.pfx){ if (Get-Content ./codesign.pfx){
@ -443,6 +468,15 @@ jobs:
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY ":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
} }
- name: Sign installer (Windows MSVC, MSIX)
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'win32'
run: |
if (Get-Content ./codesign.pfx){
SignTool sign /fd sha256 /td sha256 /f codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com ${{ env.INSTALL_DIR }}/prismlauncher-${{ matrix.architecture }}.msix
} else {
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
}
- name: Package (Linux) - name: Package (Linux)
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: | run: |
@ -521,13 +555,20 @@ jobs:
name: PrismLauncher-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }} name: PrismLauncher-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: ${{ env.INSTALL_PORTABLE_DIR }}/** path: ${{ env.INSTALL_PORTABLE_DIR }}/**
- name: Upload installer (Windows) - name: Upload setup.exe (Windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }} name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-Setup.exe path: PrismLauncher-Setup.exe
- name: Upload MSIX Package (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'win32'
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ matrix.name }}-MSIX-${{ env.VERSION }}-${{ inputs.build_type }}
path: ${{ env.INSTALL_DIR }}/prismlauncher-${{ matrix.architecture }}.msix
- name: Upload binary tarball (Linux, Qt 5) - name: Upload binary tarball (Linux, Qt 5)
if: runner.os == 'Linux' && matrix.qt_ver != 6 if: runner.os == 'Linux' && matrix.qt_ver != 6
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@ -569,20 +610,144 @@ jobs:
run: | run: |
ccache -s ccache -s
flatpak: appinstaller:
runs-on: ubuntu-latest runs-on: windows-latest
container: needs: build
image: bilelmoussaoui/flatpak-github-actions:kde-5.15-22.08 env:
options: --privileged BUNDLE_STAGING: "bundle_staging"
APPINSTALLER_STAGING: "appinstaller_staging"
PRISM_VERSION: '8.0'
steps: steps:
- name: Install MSVC
uses: ilammy/msvc-dev-cmd@v1
with:
vsversion: 2022
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
if: inputs.build_type == 'Debug'
- name: Set short version
shell: bash
run: |
ver_short=`git rev-parse --short HEAD`
echo "VERSION=$ver_short" >> $GITHUB_ENV
- name: Set Appx version
shell: pwsh
run: |
$runNumber = "${{ github.run_number }}"
if ($runNumber.Length -gt 3) {
$version = $runNumber.Substring(($runNumber.Length - 3))
} else {
$version = $runNumber
}
"APPX_VERSION=${{ env.PRISM_VERSION }}.0.$version" >> $env:GITHUB_ENV
- name: Create Bundle Staging Directory
shell: pwsh
run: |
New-Item -Type Directory -Path ${{ env.BUNDLE_STAGING }}
New-Item -Type Directory -Path ${{ env.APPINSTALLER_STAGING }}
- name: Download MSIX (x64)
uses: actions/download-artifact@v2
with: with:
submodules: 'true' name: PrismLauncher-Windows-MSVC-MSIX-${{ env.VERSION }}-${{ inputs.build_type }}
- name: Build Flatpak (Linux) path: ${{ env.BUNDLE_STAGING }}/
if: inputs.build_type == 'Debug'
uses: flatpak/flatpak-github-actions/flatpak-builder@v6 - name: Download MSIX (arm64)
uses: actions/download-artifact@v2
with: with:
bundle: "Prism Launcher.flatpak" name: PrismLauncher-Windows-MSVC-arm64-MSIX-${{ env.VERSION }}-${{ inputs.build_type }}
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml path: ${{ env.BUNDLE_STAGING }}/
- name: Create MSIXBundle
shell: pwsh
run: |
makeappx.exe bundle /bv ${{ env.APPX_VERSION }} /d ${{ env.BUNDLE_STAGING }} /p ${{ env.BUNDLE_STAGING }}\prismlauncher-${{ env.APPX_VERSION }}.msixbundle
- name: Update AppInstaller File
shell: pwsh
run: |
(Get-Content ${{ github.workspace }}\program_info\prismlauncher.AppInstaller) `
-replace "PRISM_VERSION_REPLACEME","${{ env.APPX_VERSION }}" `
> ${{ env.APPINSTALLER_STAGING }}\prismlauncher-${{ env.APPX_VERSION }}.appInstaller
- name: Fetch codesign certificate
shell: bash
run: |
echo '${{ secrets.WINDOWS_CODESIGN_CERT }}' | base64 --decode > codesign.pfx
- name: Sign MSIXBundle
shell: pwsh
run: |
if (Get-Content ./codesign.pfx) {
SignTool sign /fd sha256 /td sha256 /f codesign.pfx `
/p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' `
/tr http://timestamp.digicert.com `
${{ env.BUNDLE_STAGING }}\prismlauncher-${{ env.APPX_VERSION }}.msixbundle
} else {
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
}
- name: Upload MSIXBundle
uses: actions/upload-artifact@v2
with:
name: PrismLauncher-Windows-MSVC-Universal-MSIXBundle-${{ env.VERSION }}-${{ inputs.build_type }}
path: ${{ env.BUNDLE_STAGING }}/prismlauncher-${{ env.APPX_VERSION }}.msixbundle
- name: Upload AppInstaller
uses: actions/upload-artifact@v2
with:
name: PrismLauncher-Windows-MSVC-Universal-AppInstaller-${{ env.VERSION }}-${{ inputs.build_type }}
path: ${{ env.APPINSTALLER_STAGING }}/prismlauncher-${{ env.APPX_VERSION }}.AppInstaller
#flatpak:
# runs-on: ubuntu-latest
# container:
# image: bilelmoussaoui/flatpak-github-actions:kde-5.15-22.08
# options: --privileged
# steps:
# - name: Checkout
# uses: actions/checkout@v3
# if: inputs.build_type == 'Debug'
# with:
# submodules: 'true'
# - name: Build Flatpak (Linux)
# if: inputs.build_type == 'Debug'
# uses: flatpak/flatpak-github-actions/flatpak-builder@v6
# with:
# bundle: "Prism Launcher.flatpak"
# manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
#nix:
# runs-on: ubuntu-latest
# strategy:
# matrix:
# package:
# - prismlauncher
# - prismlauncher-qt5
# steps:
# - name: Clone repository
# if: inputs.build_type == 'Debug'
# uses: actions/checkout@v3
# with:
# submodules: 'true'
# - name: Install nix
# if: inputs.build_type == 'Debug'
# uses: cachix/install-nix-action@v22
# with:
# install_url: https://nixos.org/nix/install
# extra_nix_config: |
# auto-optimise-store = true
# experimental-features = nix-command flakes
# - uses: cachix/cachix-action@v12
# if: inputs.build_type == 'Debug'
# with:
# name: prismlauncher
# authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
# - name: Build
# if: inputs.build_type == 'Debug'
# run: nix build .#${{ matrix.package }} --print-build-logs

View File

@ -55,11 +55,17 @@ jobs:
ARM64="$(echo -n ${d} | grep -o arm64 || true)" ARM64="$(echo -n ${d} | grep -o arm64 || true)"
INST="$(echo -n ${d} | grep -o Setup || true)" INST="$(echo -n ${d} | grep -o Setup || true)"
PORT="$(echo -n ${d} | grep -o Portable || true)" PORT="$(echo -n ${d} | grep -o Portable || true)"
MSIX="$(echo -n ${d} | grep -o 'msix$' || true)"
MSIXBUNDLE="$(echo -n ${d} | grep -o 'msixbundle$' || true)"
APPINSTALLER="$(echo -n ${d} | grep -o AppInstaller || true)"
NAME="PrismLauncher-Windows-MSVC" NAME="PrismLauncher-Windows-MSVC"
test -z "${LEGACY}" || NAME="${NAME}-Legacy" test -z "${LEGACY}" || NAME="${NAME}-Legacy"
test -z "${ARM64}" || NAME="${NAME}-arm64" test -z "${ARM64}" || NAME="${NAME}-arm64"
test -z "${PORT}" || NAME="${NAME}-Portable" test -z "${PORT}" || NAME="${NAME}-Portable"
test -z "${INST}" || mv PrismLauncher-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe test -z "${INST}" || mv PrismLauncher-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
test -z "${MSIX}" || mv prismlauncher-*.msix ../${NAME}-${{ env.VERSION }}.msix
test -z "${MSIXBUNDLE}" || mv prismlauncher-*.msixbundle ../${NAME}-${{ env.VERSION }}.msixbundle
test -z "${APPINSTALLER}" || mv prismlauncher-*.AppInstaller ../${NAME}-${{ env.VERSION }}.AppInstaller
test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" * test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
cd .. cd ..
done done
@ -99,10 +105,13 @@ jobs:
PrismLauncher-Windows-MSVC-Legacy-Setup-${{ env.VERSION }}.exe PrismLauncher-Windows-MSVC-Legacy-Setup-${{ env.VERSION }}.exe
PrismLauncher-Windows-MSVC-arm64-${{ env.VERSION }}.zip PrismLauncher-Windows-MSVC-arm64-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip PrismLauncher-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-arm64-Setup-${{ env.VERSION }}.exe PrismLauncher-Windows-MSVC-arm64-${{ env.VERSION }}.msix
PrismLauncher-Windows-MSVC-arm64-${{ env.VERSION }}.msixbundle
PrismLauncher-Windows-MSVC-${{ env.VERSION }}.zip PrismLauncher-Windows-MSVC-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe PrismLauncher-Windows-MSVC-${{ env.VERSION }}.msix
PrismLauncher-Windows-MSVC-${{ env.VERSION }}.msixbundle
PrismLauncher-Windows-MSVC-${{ env.VERSION }}.AppInstaller
PrismLauncher-macOS-${{ env.VERSION }}.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}.tar.gz

View File

@ -7,12 +7,10 @@ on:
workflow_dispatch: workflow_dispatch:
permissions: permissions:
contents: write
pull-requests: write pull-requests: write
jobs: jobs:
update-flake: update-flake:
if: github.repository == 'PrismLauncher/PrismLauncher'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

View File

@ -11,5 +11,5 @@ jobs:
with: with:
identifier: PrismLauncher.PrismLauncher identifier: PrismLauncher.PrismLauncher
version: ${{ github.event.release.tag_name }} version: ${{ github.event.release.tag_name }}
installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$' installers-regex: 'PrismLauncher-Windows-MSVC(-arm64|-Legacy-Setup)?-\d.+\.(exe|msix)?$'
token: ${{ secrets.WINGET_TOKEN }} token: ${{ secrets.WINGET_TOKEN }}

4
.gitignore vendored
View File

@ -56,3 +56,7 @@ flatbuild
# Snap # Snap
*.snap *.snap
# msix
bundle_staging
microsoft.system.package.metadata

3
.gitmodules vendored
View File

@ -19,6 +19,3 @@
[submodule "libraries/cmark"] [submodule "libraries/cmark"]
path = libraries/cmark path = libraries/cmark
url = https://github.com/commonmark/cmark.git url = https://github.com/commonmark/cmark.git
[submodule "flatpak/shared-modules"]
path = flatpak/shared-modules
url = https://github.com/flathub/shared-modules.git

View File

@ -85,38 +85,6 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0")
# set CXXFLAGS for build targets # set CXXFLAGS for build targets
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
option(DEBUG_ADDRESS_SANITIZER "Enable Address Sanitizer in Debug builds" on)
# If this is a Debug build turn on address sanitiser
if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND DEBUG_ADDRESS_SANITIZER)
message(STATUS "Address Sanitizer enabled for Debug builds, Turn it off with -DDEBUG_ADDRESS_SANITIZER=off")
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
# using clang with clang-cl front end
message(STATUS "Address Sanitizer available on Clang MSVC frontend")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /O1 /Oy-")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /O1 /Oy-")
else()
# AppleClang and Clang
message(STATUS "Address Sanitizer available on Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -O1 -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -O1 -fno-omit-frame-pointer")
endif()
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
# GCC
message(STATUS "Address Sanitizer available on GCC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -O1 -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -O1 -fno-omit-frame-pointer")
link_libraries("asan")
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
message(STATUS "Address Sanitizer available on MSVC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /O1 /Oy-")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /O1 /Oy-")
else()
message(STATUS "Address Sanitizer not available on compiler ${CMAKE_CXX_COMPILER_ID}")
endif()
endif()
option(ENABLE_LTO "Enable Link Time Optimization" off) option(ENABLE_LTO "Enable Link Time Optimization" off)
if(ENABLE_LTO) if(ENABLE_LTO)
@ -170,15 +138,15 @@ set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL th
set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help") set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
######## Set version numbers ######## ######## Set version numbers ########
set(Launcher_VERSION_MAJOR 7) set(Launcher_VERSION_MAJOR 8)
set(Launcher_VERSION_MINOR 2) set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}") set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0") set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0")
set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},0,0") set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},0,0")
# Build platform. # Build platform.
set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.") set(Launcher_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
# Channel list URL # Channel list URL
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.") set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
@ -364,7 +332,7 @@ elseif(UNIX)
set(BINARY_DEST_DIR "bin") set(BINARY_DEST_DIR "bin")
set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}") set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}")
set(JARS_DEST_DIR "share/${Launcher_Name}") set(JARS_DEST_DIR "share/${Launcher_APP_BINARY_NAME}")
# install as bundle with no dependencies included # install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps") set(INSTALL_BUNDLE "nodeps")
@ -377,7 +345,7 @@ elseif(UNIX)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps") install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps")
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR}) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR})
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "share/${Launcher_Name}") install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "${KDE_INSTALL_DATADIR}/${Launcher_Name}")
if(Launcher_ManPage) if(Launcher_ManPage)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6")

View File

@ -1,3 +1,5 @@
test test
<p align="center"> <p align="center">
<picture> <picture>
<source media="(prefers-color-scheme: dark)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg"> <source media="(prefers-color-scheme: dark)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg">
@ -42,7 +44,7 @@ Feel free to create a GitHub issue if you find a bug or want to suggest a new fe
- **Our Matrix space:** - **Our Matrix space:**
[![Prism Launcher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge&label=Matrix%20Space&logo=matrix&color=purple)](https://prismlauncher.org/matrix) [![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge&label=Matrix%20Space&logo=matrix&color=purple)](https://prismlauncher.org/matrix)
- **Our Subreddit:** - **Our Subreddit:**
@ -50,7 +52,7 @@ Feel free to create a GitHub issue if you find a bug or want to suggest a new fe
## Translations ## Translations
The translation effort for Prism Launcher is hosted on [Weblate](https://hosted.weblate.org/projects/prismlauncher/launcher/) and information about translating Prism Launcher is available at <https://github.com/PrismLauncher/Translations> The translation effort for PrismLauncher is hosted on [Weblate](https://hosted.weblate.org/projects/prismlauncher/launcher/) and information about translating Prism Launcher is available at <https://github.com/PrismLauncher/Translations>
## Building ## Building
@ -82,16 +84,14 @@ Thanks to the awesome people over at [MacStadium](https://www.macstadium.com/),
## Forking/Redistributing/Custom builds policy ## Forking/Redistributing/Custom builds policy
You are free to fork, redistribute and provide custom builds as long as you follow the terms of the [license](LICENSE) (this is a legal responsibility), and if you made code changes rather than just packaging a custom build, please do the following as a basic courtesy: We don't care what you do with your fork/custom build as long as you follow the terms of the [license](LICENSE) (this is a legal responsibility), and if you made code changes rather than just packaging a custom build, please do the following as a basic courtesy:
- Make it clear that your fork is not Prism Launcher and is not endorsed by or affiliated with the Prism Launcher project (<https://prismlauncher.org>). - Make it clear that your fork is not PrismLauncher and is not endorsed by or affiliated with the PrismLauncher project (<https://prismlauncher.org>).
- Go through [CMakeLists.txt](CMakeLists.txt) and change Prism Launcher's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled). - Go through [CMakeLists.txt](CMakeLists.txt) and change PrismLauncher's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled).
If you have any questions or want any clarification on the above conditions please make an issue and ask us. If you have any questions or want any clarification on the above conditions please make an issue and ask us.
If you are just building Prism Launcher for your distribution, please make sure to set the `Launcher_BUILD_PLATFORM` to a slug representing your distribution. Examples are `archlinux`, `fedora` and `nixpkgs`. Be aware that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions:
Note that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions:
- [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use) - [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use)
- [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions) - [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions)

View File

@ -65,7 +65,7 @@ Config::Config()
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@"; MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@"; MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@";
if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty()) if (BUILD_PLATFORM == "macOS" && !MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty())
{ {
UPDATER_ENABLED = true; UPDATER_ENABLED = true;
} }

View File

@ -68,7 +68,7 @@ class Config {
bool UPDATER_ENABLED = false; bool UPDATER_ENABLED = false;
/// A short string identifying this build's platform or distribution. /// A short string identifying this build's platform. For example, "lin64" or "win32".
QString BUILD_PLATFORM; QString BUILD_PLATFORM;
/// A string containing the build timestamp /// A string containing the build timestamp

24
flake.lock generated
View File

@ -21,11 +21,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1688466019, "lastModified": 1688254665,
"narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=", "narHash": "sha256-8FHEgBrr7gYNiS/NzCxIO3m4hvtLRW9YY1nYo1ivm3o=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec", "rev": "267149c58a14d15f7f81b4d737308421de9d7152",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -76,11 +76,11 @@
"libnbtplusplus": { "libnbtplusplus": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1690036783, "lastModified": 1650031308,
"narHash": "sha256-A5kTgICnx+Qdq3Fir/bKTfdTt/T1NQP2SC+nhN1ENug=", "narHash": "sha256-TvVOjkUobYJD9itQYueELJX3wmecvEdCbJ0FinW2mL4=",
"owner": "PrismLauncher", "owner": "PrismLauncher",
"repo": "libnbtplusplus", "repo": "libnbtplusplus",
"rev": "a5e8fd52b8bf4ab5d5bcc042b2a247867589985f", "rev": "2203af7eeb48c45398139b583615134efd8d407f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -91,11 +91,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1690026219, "lastModified": 1688221086,
"narHash": "sha256-oOduRk/kzQxOBknZXTLSEYd7tk+GoKvr8wV6Ab+t4AU=", "narHash": "sha256-cdW6qUL71cNWhHCpMPOJjlw0wzSRP0pVlRn2vqX/VVg=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "f465da166263bc0d4b39dfd4ca28b777c92d4b73", "rev": "cd99c2b3c9f160cd004318e0697f90bbd5960825",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -138,11 +138,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1689668210, "lastModified": 1688386108,
"narHash": "sha256-XAATwDkaUxH958yXLs1lcEOmU6pSEIkatY3qjqk8X0E=", "narHash": "sha256-Vffto9QaVonzYAcPlAzd0soqWYpPpKk60dfNLSIXcFA=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "eb433bff05b285258be76513add6f6c57b441775", "rev": "42587d3414d1747999a5f71e92a83cf6547b62da",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -1,22 +0,0 @@
{
"name": "libdecor",
"buildsystem": "meson",
"config-opts": [
"-Ddemo=false"
],
"sources": [
{
"type": "git",
"url": "https://gitlab.freedesktop.org/libdecor/libdecor.git",
"commit": "73260393a97291c887e1074ab7f318e031be0ac6"
},
{
"type": "patch",
"path": "patches/weird_libdecor.patch"
}
],
"cleanup": [
"/include",
"/lib/pkgconfig"
]
}

View File

@ -5,6 +5,13 @@ sdk: org.kde.Sdk
sdk-extensions: sdk-extensions:
- org.freedesktop.Sdk.Extension.openjdk17 - org.freedesktop.Sdk.Extension.openjdk17
- org.freedesktop.Sdk.Extension.openjdk8 - org.freedesktop.Sdk.Extension.openjdk8
add-extensions:
com.valvesoftware.Steam.Utility.gamescope:
version: stable
add-ld-path: lib
no-autodownload: true
autodelete: false
directory: utils/gamescope
command: prismlauncher command: prismlauncher
finish-args: finish-args:
@ -19,31 +26,21 @@ finish-args:
# Mod drag&drop # Mod drag&drop
- --filesystem=xdg-download:ro - --filesystem=xdg-download:ro
cleanup:
- /lib/libGLU*
modules: modules:
# Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31)
- shared-modules/libusb/libusb.json
# Needed for proper Wayland support
- libdecor.json
- name: prismlauncher - name: prismlauncher
buildsystem: cmake-ninja buildsystem: cmake-ninja
builddir: true
config-opts: config-opts:
- -DLauncher_BUILD_PLATFORM=flatpak - -DLauncher_BUILD_PLATFORM=flatpak
- -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DCMAKE_BUILD_TYPE=Debug
- -DLauncher_QT_VERSION_MAJOR=5 - -DLauncher_QT_VERSION_MAJOR=5
build-options: build-options:
env: env:
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17 JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac
sources: sources:
- type: dir - type: dir
path: ../ path: ../
builddir: true
- name: openjdk - name: openjdk
buildsystem: simple buildsystem: simple
build-commands: build-commands:
@ -52,45 +49,14 @@ modules:
- mv /app/jre /app/jdk/17 - mv /app/jre /app/jdk/17
- /usr/lib/sdk/openjdk8/install.sh - /usr/lib/sdk/openjdk8/install.sh
- mv /app/jre /app/jdk/8 - mv /app/jre /app/jdk/8
cleanup: cleanup: [/jre]
- /jre
- name: glfw
buildsystem: cmake-ninja
config-opts:
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
- -DBUILD_SHARED_LIBS:BOOL=ON
- -DGLFW_USE_WAYLAND=ON
sources:
- type: git
url: https://github.com/glfw/glfw.git
commit: 3fa2360720eeba1964df3c0ecf4b5df8648a8e52
- type: patch
path: patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch
- type: patch
path: patches/0005-Add-warning-about-being-an-unofficial-patch.patch
- type: patch
path: patches/0007-Platform-Prefer-Wayland-over-X11.patch
cleanup:
- /include
- /lib/cmake
- /lib/pkgconfig
- name: xrandr - name: xrandr
buildsystem: autotools buildsystem: autotools
sources: sources:
- type: archive - type: archive
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.2.tar.xz url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.1.tar.xz
sha256: c8bee4790d9058bacc4b6246456c58021db58a87ddda1a9d0139bf5f18f1f240 sha256: 7bc76daf9d72f8aff885efad04ce06b90488a1a169d118dea8a2b661832e8762
x-checker-data: cleanup: [/share/man, /bin/xkeystone]
type: anitya
project-id: 14957
stable-only: true
url-template: https://xorg.freedesktop.org/archive/individual/app/xrandr-$version.tar.xz
cleanup:
- /share/man
- /bin/xkeystone
- name: gamemode - name: gamemode
buildsystem: meson buildsystem: meson
config-opts: config-opts:
@ -101,56 +67,19 @@ modules:
# post-install is running inside the build dir, we need it from the source though # post-install is running inside the build dir, we need it from the source though
- install -Dm755 ../data/gamemoderun -t /app/bin - install -Dm755 ../data/gamemoderun -t /app/bin
sources: sources:
- type: archive - type: git
archive-type: tar-gzip url: https://github.com/FeralInteractive/gamemode
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.7 tag: "1.7"
sha256: 57ce73ba605d1cf12f8d13725006a895182308d93eba0f69f285648449641803 commit: 4dc99dff76218718763a6b07fc1900fa6d1dafd9
x-checker-data:
type: json
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest
version-query: .tag_name
url-query: .tarball_url
timestamp-query: .published_at
cleanup:
- /include
- /lib/pkgconfig
- /lib/libgamemodeauto.a
- name: glxinfo
buildsystem: meson
config-opts:
- --bindir=/app/mesa-demos
- -Degl=disabled
- -Dglut=disabled
- -Dosmesa=disabled
- -Dvulkan=disabled
- -Dwayland=disabled
post-install:
- mv -v /app/mesa-demos/glxinfo /app/bin
sources:
- type: archive
url: https://archive.mesa3d.org/demos/mesa-demos-9.0.0.tar.xz
sha256: 3046a3d26a7b051af7ebdd257a5f23bfeb160cad6ed952329cdff1e9f1ed496b
x-checker-data:
type: anitya
project-id: 16781
stable-only: true
url-template: https://archive.mesa3d.org/demos/mesa-demos-$version.tar.xz
cleanup:
- /include
- /mesa-demos
- /share
modules:
- shared-modules/glu/glu-9.json
- name: enhance - name: enhance
buildsystem: simple buildsystem: simple
build-commands: build-commands:
- mkdir -p /app/utils/gamescope
- install -Dm755 prime-run /app/bin/prime-run - install -Dm755 prime-run /app/bin/prime-run
- mv /app/bin/prismlauncher /app/bin/prismrun - mv /app/bin/prismlauncher /app/bin/prismrun
- install -Dm755 prismlauncher /app/bin/prismlauncher - install -Dm755 prismlauncher /app/bin/prismlauncher
sources: sources:
- type: file - type: file
path: prime-run path: ../flatpak/prime-run
- type: file - type: file
path: prismlauncher path: ../flatpak/prismlauncher

View File

@ -1,24 +0,0 @@
diff --git a/src/wl_window.c b/src/wl_window.c
index 52d3b9eb..4ac4eb5d 100644
--- a/src/wl_window.c
+++ b/src/wl_window.c
@@ -2117,8 +2117,7 @@ void _glfwSetWindowTitleWayland(_GLFWwindow* window, const char* title)
void _glfwSetWindowIconWayland(_GLFWwindow* window,
int count, const GLFWimage* images)
{
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
- "Wayland: The platform does not support setting the window icon");
+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the window icon\n");
}
void _glfwGetWindowPosWayland(_GLFWwindow* window, int* xpos, int* ypos)
@@ -2361,8 +2360,7 @@ void _glfwRequestWindowAttentionWayland(_GLFWwindow* window)
void _glfwFocusWindowWayland(_GLFWwindow* window)
{
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
- "Wayland: The platform does not support setting the input focus");
+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the input focus\n");
}
void _glfwSetWindowMonitorWayland(_GLFWwindow* window,

View File

@ -1,17 +0,0 @@
diff --git a/src/init.c b/src/init.c
index 06dbb3f2..a7c6da86 100644
--- a/src/init.c
+++ b/src/init.c
@@ -449,6 +449,12 @@ GLFWAPI int glfwInit(void)
_glfw.initialized = GLFW_TRUE;
glfwDefaultWindowHints();
+
+ fprintf(stderr, "!!! Patched GLFW from https://github.com/Admicos/minecraft-wayland\n"
+ "!!! If any issues with the window, or some issues with rendering, occur, "
+ "first try with the built-in GLFW, and if that solves the issue, report there first.\n"
+ "!!! Use outside Minecraft is untested, and things might break.\n");
+
return GLFW_TRUE;
}

View File

@ -1,20 +0,0 @@
diff --git a/src/platform.c b/src/platform.c
index c5966ae7..3e7442f9 100644
--- a/src/platform.c
+++ b/src/platform.c
@@ -49,12 +49,12 @@ static const struct
#if defined(_GLFW_COCOA)
{ GLFW_PLATFORM_COCOA, _glfwConnectCocoa },
#endif
-#if defined(_GLFW_X11)
- { GLFW_PLATFORM_X11, _glfwConnectX11 },
-#endif
#if defined(_GLFW_WAYLAND)
{ GLFW_PLATFORM_WAYLAND, _glfwConnectWayland },
#endif
+#if defined(_GLFW_X11)
+ { GLFW_PLATFORM_X11, _glfwConnectX11 },
+#endif
};
GLFWbool _glfwSelectPlatform(int desiredID, _GLFWplatform* platform)

View File

@ -1,40 +0,0 @@
diff --git a/src/libdecor.c b/src/libdecor.c
index a9c1106..1aa38b3 100644
--- a/src/libdecor.c
+++ b/src/libdecor.c
@@ -1391,22 +1391,32 @@ calculate_priority(const struct libdecor_plugin_description *plugin_description)
static bool
check_symbol_conflicts(const struct libdecor_plugin_description *plugin_description)
{
+ bool ret = true;
char * const *symbol;
+ void* main_prog = dlopen(NULL, RTLD_LAZY);
+ if (!main_prog) {
+ fprintf(stderr, "Plugin \"%s\" couldn't check conflicting symbols: \"%s\".\n",
+ plugin_description->description, dlerror());
+ return false;
+ }
+
symbol = plugin_description->conflicting_symbols;
while (*symbol) {
dlerror();
- dlsym (RTLD_DEFAULT, *symbol);
+ dlsym (main_prog, *symbol);
if (!dlerror()) {
fprintf(stderr, "Plugin \"%s\" uses conflicting symbol \"%s\".\n",
plugin_description->description, *symbol);
- return false;
+ ret = false;
+ break;
}
symbol++;
}
- return true;
+ dlclose(main_prog);
+ return ret;
}
static struct plugin_loader *

View File

@ -5,7 +5,7 @@ for i in {0..9}; do
test -S "$XDG_RUNTIME_DIR"/discord-ipc-"$i" || ln -sf {app/com.discordapp.Discord,"$XDG_RUNTIME_DIR"}/discord-ipc-"$i"; test -S "$XDG_RUNTIME_DIR"/discord-ipc-"$i" || ln -sf {app/com.discordapp.Discord,"$XDG_RUNTIME_DIR"}/discord-ipc-"$i";
done done
export PATH="${PATH}${PATH:+:}/usr/lib/extensions/vulkan/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin" export PATH="${PATH}${PATH:+:}/app/utils/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin"
export VK_LAYER_PATH="/usr/lib/extensions/vulkan/share/vulkan/implicit_layer.d/" export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}${LD_LIBRARY_PATH:+:}/usr/lib/extensions/vulkan/MangoHud/\$LIB/"
exec /app/bin/prismrun "$@" exec /app/bin/prismrun "$@"

View File

@ -1,5 +0,0 @@
builds:
exclude: []
include:
- "devShells.*-linux.*"
- "packages.*-linux.*"

View File

@ -9,7 +9,6 @@
* Copyright (C) 2022 Tayou <tayou@gmx.net> * Copyright (C) 2022 Tayou <tayou@gmx.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
* Copyright (C) 2023 seth <getchoo at tuta dot io>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -434,11 +433,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
} }
// seach root path // seach root path
if(!foundLoggingRules) { if(!foundLoggingRules) {
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
logRulesPath = FS::PathCombine(m_rootPath, "share", BuildConfig.LAUNCHER_NAME, logRulesFile);
#else
logRulesPath = FS::PathCombine(m_rootPath, logRulesFile); logRulesPath = FS::PathCombine(m_rootPath, logRulesFile);
#endif
qDebug() << "Testing" << logRulesPath << "..."; qDebug() << "Testing" << logRulesPath << "...";
foundLoggingRules = QFile::exists(logRulesPath); foundLoggingRules = QFile::exists(logRulesPath);
} }
@ -476,7 +471,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT; qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
qDebug() << "Version : " << BuildConfig.printableVersionString(); qDebug() << "Version : " << BuildConfig.printableVersionString();
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC; qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
if (adjustedBy.size()) if (adjustedBy.size())
@ -574,7 +568,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Language // Language
m_settings->registerSetting("Language", QString()); m_settings->registerSetting("Language", QString());
m_settings->registerSetting("UseSystemLocale", false);
// Console // Console
m_settings->registerSetting("ShowConsole", false); m_settings->registerSetting("ShowConsole", false);
@ -611,9 +604,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("IgnoreJavaCompatibility", false); m_settings->registerSetting("IgnoreJavaCompatibility", false);
m_settings->registerSetting("IgnoreJavaWizard", false); m_settings->registerSetting("IgnoreJavaWizard", false);
// Mod loader settings
m_settings->registerSetting("DisableQuiltBeacon", false);
// Native library workarounds // Native library workarounds
m_settings->registerSetting("UseNativeOpenAL", false); m_settings->registerSetting("UseNativeOpenAL", false);
m_settings->registerSetting("UseNativeGLFW", false); m_settings->registerSetting("UseNativeGLFW", false);
@ -697,16 +687,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->reset("PastebinCustomAPIBase"); m_settings->reset("PastebinCustomAPIBase");
} }
} }
{ // meta URL
// Meta URL m_settings->registerSetting("MetaURLOverride", "");
m_settings->registerSetting("MetaURLOverride", "");
QUrl metaUrl(m_settings->get("MetaURLOverride").toString());
// get rid of invalid meta urls
if (!metaUrl.isValid() || metaUrl.scheme() != "http" || metaUrl.scheme() != "https")
m_settings->reset("MetaURLOverride");
}
m_settings->registerSetting("CloseAfterLaunch", false); m_settings->registerSetting("CloseAfterLaunch", false);
m_settings->registerSetting("QuitAfterGameStop", false); m_settings->registerSetting("QuitAfterGameStop", false);
@ -928,7 +910,12 @@ bool Application::createSetupWizard()
} }
return false; return false;
}(); }();
bool languageRequired = settings()->get("Language").toString().isEmpty(); bool languageRequired = [&]()
{
if (settings()->get("Language").toString().isEmpty())
return true;
return false;
}();
bool pasteInterventionRequired = settings()->get("PastebinURL") != ""; bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
bool themeInterventionRequired = settings()->get("ApplicationTheme") == ""; bool themeInterventionRequired = settings()->get("ApplicationTheme") == "";
bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired; bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired;
@ -1572,7 +1559,7 @@ QString Application::getJarPath(QString jarFile)
{ {
QStringList potentialPaths = { QStringList potentialPaths = {
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
FS::PathCombine(m_rootPath, "share", BuildConfig.LAUNCHER_NAME), FS::PathCombine(m_rootPath, "share/" + BuildConfig.LAUNCHER_APP_BINARY_NAME),
#endif #endif
FS::PathCombine(m_rootPath, "jars"), FS::PathCombine(m_rootPath, "jars"),
FS::PathCombine(applicationDirPath(), "jars"), FS::PathCombine(applicationDirPath(), "jars"),

View File

@ -15,15 +15,16 @@
#pragma once #pragma once
#include <QMetaType>
#include <QString>
#include <memory> #include <memory>
#include <QString>
#include <QMetaType>
/*! /*!
* An abstract base class for versions. * An abstract base class for versions.
*/ */
class BaseVersion { class BaseVersion
public: {
public:
using Ptr = std::shared_ptr<BaseVersion>; using Ptr = std::shared_ptr<BaseVersion>;
virtual ~BaseVersion() {} virtual ~BaseVersion() {}
/*! /*!
@ -44,8 +45,14 @@ class BaseVersion {
*/ */
virtual QString typeString() const = 0; virtual QString typeString() const = 0;
virtual bool operator<(BaseVersion& a) { return name() < a.name(); }; virtual bool operator<(BaseVersion &a)
virtual bool operator>(BaseVersion& a) { return name() > a.name(); }; {
return name() < a.name();
};
virtual bool operator>(BaseVersion &a)
{
return name() > a.name();
};
}; };
Q_DECLARE_METATYPE(BaseVersion::Ptr) Q_DECLARE_METATYPE(BaseVersion::Ptr)

View File

@ -362,6 +362,8 @@ set(MINECRAFT_SOURCES
minecraft/mod/tasks/LocalWorldSaveParseTask.cpp minecraft/mod/tasks/LocalWorldSaveParseTask.cpp
minecraft/mod/tasks/LocalResourceParse.h minecraft/mod/tasks/LocalResourceParse.h
minecraft/mod/tasks/LocalResourceParse.cpp minecraft/mod/tasks/LocalResourceParse.cpp
minecraft/mod/tasks/GetModDependenciesTask.h
minecraft/mod/tasks/GetModDependenciesTask.cpp
# Assets # Assets
minecraft/AssetsUtils.h minecraft/AssetsUtils.h

View File

@ -187,8 +187,8 @@ void LaunchController::login() {
switch(m_accountToUse->accountState()) { switch(m_accountToUse->accountState()) {
case AccountState::Offline: { case AccountState::Offline: {
m_session->wants_online = false; m_session->wants_online = false;
// NOTE: fallthrough is intentional
} }
/* fallthrough */
case AccountState::Online: { case AccountState::Online: {
if(!m_session->wants_online) { if(!m_session->wants_online) {
// we ask the user for a player name // we ask the user for a player name
@ -267,8 +267,8 @@ void LaunchController::login() {
// This means some sort of soft error that we can fix with a refresh ... so let's refresh. // This means some sort of soft error that we can fix with a refresh ... so let's refresh.
case AccountState::Unchecked: { case AccountState::Unchecked: {
m_accountToUse->refresh(); m_accountToUse->refresh();
// NOTE: fallthrough intentional
} }
/* fallthrough */
case AccountState::Working: { case AccountState::Working: {
// refresh is in progress, we need to wait for it to finish to proceed. // refresh is in progress, we need to wait for it to finish to proceed.
ProgressDialog progDialog(m_parentWidget); ProgressDialog progDialog(m_parentWidget);
@ -390,10 +390,7 @@ void LaunchController::launchInstance()
m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher)); m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher));
// Prepend Version // Prepend Version
{ m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), BuildConfig.LAUNCHER_DISPLAYNAME + " version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::Launcher));
auto versionString = QString("%1 version: %2 (%3)").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString(), BuildConfig.BUILD_PLATFORM);
m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), versionString + "\n\n", MessageLevel::Launcher));
}
m_launcher->start(); m_launcher->start();
} }

View File

@ -3,6 +3,8 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QPixmapCache> #include <QPixmapCache>
#include <QThread> #include <QThread>
#include <QTime>
#include <QDebug>
#define GET_TYPE() \ #define GET_TYPE() \
Qt::ConnectionType type; \ Qt::ConnectionType type; \
@ -60,6 +62,8 @@ class PixmapCache final : public QObject {
DEFINE_FUNC_ONE_PARAM(remove, bool, const QPixmapCache::Key&) DEFINE_FUNC_ONE_PARAM(remove, bool, const QPixmapCache::Key&)
DEFINE_FUNC_TWO_PARAM(replace, bool, const QPixmapCache::Key&, const QPixmap&) DEFINE_FUNC_TWO_PARAM(replace, bool, const QPixmapCache::Key&, const QPixmap&)
DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, int) DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, int)
DEFINE_FUNC_NO_PARAM(markCacheMissByEviciton, bool)
DEFINE_FUNC_ONE_PARAM(setFastEvictionThreshold, bool, int)
// NOTE: Every function returns something non-void to simplify the macros. // NOTE: Every function returns something non-void to simplify the macros.
private slots: private slots:
@ -90,6 +94,43 @@ class PixmapCache final : public QObject {
return true; return true;
} }
/**
* Mark that a cache miss occurred because of a eviction if too many of these occur too fast the cache size is increased
* @return if the cache size was increased
*/
bool _markCacheMissByEviciton()
{
auto now = QTime::currentTime();
if (!m_last_cache_miss_by_eviciton.isNull()) {
auto diff = m_last_cache_miss_by_eviciton.msecsTo(now);
if (diff < 1000) { // less than a second ago
++m_consecutive_fast_evicitons;
} else {
m_consecutive_fast_evicitons = 0;
}
}
m_last_cache_miss_by_eviciton = now;
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
// double the cache size
auto newSize = _cacheLimit() * 2;
qDebug() << m_consecutive_fast_evicitons << "pixmap cache misses by eviction happened too fast, doubling cache size to"
<< newSize;
_setCacheLimit(newSize);
m_consecutive_fast_evicitons = 0;
return true;
}
return false;
}
bool _setFastEvictionThreshold(int threshold)
{
m_consecutive_fast_evicitons_threshold = threshold;
return true;
}
private: private:
static PixmapCache* s_instance; static PixmapCache* s_instance;
QTime m_last_cache_miss_by_eviciton;
int m_consecutive_fast_evicitons = 0;
int m_consecutive_fast_evicitons_threshold = 15;
}; };

View File

@ -38,6 +38,8 @@ class ResourceDownloadTask : public SequentialTask {
const QString& getFilename() const { return m_pack_version.fileName; } const QString& getFilename() const { return m_pack_version.fileName; }
const QString& getCustomPath() const { return m_custom_target_folder; } const QString& getCustomPath() const { return m_custom_target_folder; }
const QVariant& getVersionID() const { return m_pack_version.fileId; } const QVariant& getVersionID() const { return m_pack_version.fileId; }
const ModPlatform::IndexedVersion& getVersion() const { return m_pack_version; }
const ModPlatform::ResourceProvider& getProvider() const { return m_pack->provider; }
const QString& getName() const { return m_pack->name; } const QString& getName() const { return m_pack->name; }
ModPlatform::IndexedPack::Ptr getPack() { return m_pack; } ModPlatform::IndexedPack::Ptr getPack() { return m_pack; }

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -54,9 +55,14 @@ public:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{ {
const auto &filters = m_parent->filters(); const auto &filters = m_parent->filters();
const QString &search = m_parent->search();
const QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
if (!search.isEmpty() && !sourceModel()->data(idx, BaseVersionList::VersionRole).toString().contains(search, Qt::CaseInsensitive))
return false;
for (auto it = filters.begin(); it != filters.end(); ++it) for (auto it = filters.begin(); it != filters.end(); ++it)
{ {
auto idx = sourceModel()->index(source_row, 0, source_parent);
auto data = sourceModel()->data(idx, it.key()); auto data = sourceModel()->data(idx, it.key());
auto match = data.toString(); auto match = data.toString();
if(!it.value()->accepts(match)) if(!it.value()->accepts(match))
@ -187,24 +193,31 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const
} }
case Qt::ToolTipRole: case Qt::ToolTipRole:
{ {
if(column == Name && hasRecommended) switch(column)
{ {
auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); case Name:
if(value.toBool())
{ {
return tr("Recommended"); if(hasRecommended)
} else if(hasLatest) {
auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
if(value.toBool())
{ {
return tr("Latest"); auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole);
if(value.toBool())
{
return tr("Recommended");
}
else if(hasLatest)
{
auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
if(value.toBool())
{
return tr("Latest");
}
}
} }
} else if(index.row() == 0)
{
return tr("Latest");
} }
} else { default:
return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole); {
return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole);
}
} }
} }
case Qt::DecorationRole: case Qt::DecorationRole:
@ -228,10 +241,6 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const
return APPLICATION->getThemedIcon("bug"); return APPLICATION->getThemedIcon("bug");
} }
} }
else if(index.row() == 0)
{
return APPLICATION->getThemedIcon("bug");
}
QPixmap pixmap; QPixmap pixmap;
QPixmapCache::find("placeholder", &pixmap); QPixmapCache::find("placeholder", &pixmap);
if(!pixmap) if(!pixmap)
@ -420,6 +429,7 @@ QModelIndex VersionProxyModel::getVersion(const QString& version) const
void VersionProxyModel::clearFilters() void VersionProxyModel::clearFilters()
{ {
m_filters.clear(); m_filters.clear();
m_search.clear();
filterModel->filterChanged(); filterModel->filterChanged();
} }
@ -429,11 +439,21 @@ void VersionProxyModel::setFilter(const BaseVersionList::ModelRoles column, Filt
filterModel->filterChanged(); filterModel->filterChanged();
} }
void VersionProxyModel::setSearch(const QString &search) {
m_search = search;
filterModel->filterChanged();
}
const VersionProxyModel::FilterMap &VersionProxyModel::filters() const const VersionProxyModel::FilterMap &VersionProxyModel::filters() const
{ {
return m_filters; return m_filters;
} }
const QString &VersionProxyModel::search() const
{
return m_search;
}
void VersionProxyModel::sourceAboutToBeReset() void VersionProxyModel::sourceAboutToBeReset()
{ {
beginResetModel(); beginResetModel();

View File

@ -38,7 +38,9 @@ public:
virtual void setSourceModel(QAbstractItemModel *sourceModel) override; virtual void setSourceModel(QAbstractItemModel *sourceModel) override;
const FilterMap &filters() const; const FilterMap &filters() const;
const QString &search() const;
void setFilter(const BaseVersionList::ModelRoles column, Filter * filter); void setFilter(const BaseVersionList::ModelRoles column, Filter * filter);
void setSearch(const QString &search);
void clearFilters(); void clearFilters();
QModelIndex getRecommended() const; QModelIndex getRecommended() const;
QModelIndex getVersion(const QString & version) const; QModelIndex getVersion(const QString & version) const;
@ -59,6 +61,7 @@ private slots:
private: private:
QList<Column> m_columns; QList<Column> m_columns;
FilterMap m_filters; FilterMap m_filters;
QString m_search;
BaseVersionList::RoleList roles; BaseVersionList::RoleList roles;
VersionFilterModel * filterModel; VersionFilterModel * filterModel;
bool hasRecommended = false; bool hasRecommended = false;

View File

@ -1,64 +1,29 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "JavaInstall.h" #include "JavaInstall.h"
#include "BaseVersion.h"
#include "StringUtils.h" #include "StringUtils.h"
bool JavaInstall::operator<(const JavaInstall& rhs) bool JavaInstall::operator<(const JavaInstall &rhs)
{ {
auto archCompare = StringUtils::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive); auto archCompare = StringUtils::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
if (archCompare != 0) if(archCompare != 0)
return archCompare < 0; return archCompare < 0;
if (id < rhs.id) { if(id < rhs.id)
{
return true; return true;
} }
if (id > rhs.id) { if(id > rhs.id)
{
return false; return false;
} }
return StringUtils::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0; return StringUtils::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
} }
bool JavaInstall::operator==(const JavaInstall& rhs) bool JavaInstall::operator==(const JavaInstall &rhs)
{ {
return arch == rhs.arch && id == rhs.id && path == rhs.path; return arch == rhs.arch && id == rhs.id && path == rhs.path;
} }
bool JavaInstall::operator>(const JavaInstall& rhs) bool JavaInstall::operator>(const JavaInstall &rhs)
{ {
return (!operator<(rhs)) && (!operator==(rhs)); return (!operator<(rhs)) && (!operator==(rhs));
} }
bool JavaInstall::operator<(BaseVersion& a)
{
try {
return operator<(dynamic_cast<JavaInstall&>(a));
} catch (const std::bad_cast& e) {
return BaseVersion::operator<(a);
}
}
bool JavaInstall::operator>(BaseVersion& a)
{
try {
return operator>(dynamic_cast<JavaInstall&>(a));
} catch (const std::bad_cast& e) {
return BaseVersion::operator>(a);
}
}

View File

@ -1,40 +1,33 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once #pragma once
#include "BaseVersion.h" #include "BaseVersion.h"
#include "JavaVersion.h" #include "JavaVersion.h"
struct JavaInstall : public BaseVersion { struct JavaInstall : public BaseVersion
JavaInstall() {} {
JavaInstall(QString id, QString arch, QString path) : id(id), arch(arch), path(path) {} JavaInstall(){}
virtual QString descriptor() { return id.toString(); } JavaInstall(QString id, QString arch, QString path)
: id(id), arch(arch), path(path)
{
}
virtual QString descriptor()
{
return id.toString();
}
virtual QString name() { return id.toString(); } virtual QString name()
{
return id.toString();
}
virtual QString typeString() const { return arch; } virtual QString typeString() const
{
return arch;
}
virtual bool operator<(BaseVersion& a) override; bool operator<(const JavaInstall & rhs);
virtual bool operator>(BaseVersion& a) override; bool operator==(const JavaInstall & rhs);
bool operator<(const JavaInstall& rhs); bool operator>(const JavaInstall & rhs);
bool operator==(const JavaInstall& rhs);
bool operator>(const JavaInstall& rhs);
JavaVersion id; JavaVersion id;
QString arch; QString arch;

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -98,6 +99,8 @@ QVariant JavaInstallList::data(const QModelIndex &index, int role) const
auto version = std::dynamic_pointer_cast<JavaInstall>(m_vlist[index.row()]); auto version = std::dynamic_pointer_cast<JavaInstall>(m_vlist[index.row()]);
switch (role) switch (role)
{ {
case SortRole:
return -index.row();
case VersionPointerRole: case VersionPointerRole:
return QVariant::fromValue(m_vlist[index.row()]); return QVariant::fromValue(m_vlist[index.row()]);
case VersionIdRole: case VersionIdRole:

View File

@ -45,10 +45,10 @@ QVariant Index::data(const QModelIndex &index, int role) const
switch (role) switch (role)
{ {
case Qt::DisplayRole: case Qt::DisplayRole:
if (index.column() == 0) { switch (index.column())
return list->humanReadable(); {
} else { case 0: return list->humanReadable();
break; default: break;
} }
case UidRole: return list->uid(); case UidRole: return list->uid();
case NameRole: return list->name(); case NameRole: return list->name();

View File

@ -4,7 +4,6 @@
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
* Copyright (c) 2023 seth <getchoo at tuta dot io>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -187,10 +186,6 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride); m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride); m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
// Mod loader specific options
auto modLoaderSettings = m_settings->registerSetting("OverrideModLoaderSettings", false);
m_settings->registerOverride(global_settings->getSetting("DisableQuiltBeacon"), modLoaderSettings);
m_settings->set("InstanceType", "OneSix"); m_settings->set("InstanceType", "OneSix");
} }
@ -396,12 +391,6 @@ QStringList MinecraftInstance::extraArguments()
agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath()); agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath());
list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument())); list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument()));
} }
{
const auto loaders = version->getModLoaders();
if (loaders.has_value() && loaders.value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool())
list.append("-Dloader.disable_beacon=true");
}
return list; return list;
} }
@ -843,7 +832,7 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
{ {
addToFilter(sessionRef.session, tr("<SESSION ID>")); addToFilter(sessionRef.session, tr("<SESSION ID>"));
} }
if (sessionRef.access_token != "0") { if (sessionRef.access_token != "offline") {
addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>")); addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
} }
if(sessionRef.client_token.size()) { if(sessionRef.client_token.size()) {

View File

@ -65,8 +65,7 @@
static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{
{"net.minecraftforge", ResourceAPI::Forge}, {"net.minecraftforge", ResourceAPI::Forge},
{"net.fabricmc.fabric-loader", ResourceAPI::Fabric}, {"net.fabricmc.fabric-loader", ResourceAPI::Fabric},
{"org.quiltmc.quilt-loader", ResourceAPI::Quilt}, {"org.quiltmc.quilt-loader", ResourceAPI::Quilt}
{"com.mumfrey.liteloader", ResourceAPI::LiteLoader}
}; };
PackProfile::PackProfile(MinecraftInstance * instance) PackProfile::PackProfile(MinecraftInstance * instance)

View File

@ -374,10 +374,6 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
} }
yggdrasilToken = tokenFromJSONV3(data, "ygg"); yggdrasilToken = tokenFromJSONV3(data, "ygg");
// versions before 7.2 used "offline" as the offline token
if (yggdrasilToken.token == "offline")
yggdrasilToken.token = "0";
minecraftProfile = profileFromJSONV3(data, "profile"); minecraftProfile = profileFromJSONV3(data, "profile");
if(!entitlementFromJSONV3(data, minecraftEntitlement)) { if(!entitlementFromJSONV3(data, minecraftEntitlement)) {
if(minecraftProfile.validity != Katabasis::Validity::None) { if(minecraftProfile.validity != Katabasis::Validity::None) {

View File

@ -328,9 +328,6 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
case AccountState::Gone: { case AccountState::Gone: {
return tr("Gone", "Account status"); return tr("Gone", "Account status");
} }
default: {
return tr("Unknown", "Account status");
}
} }
} }
@ -357,13 +354,12 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
return QVariant::fromValue(account); return QVariant::fromValue(account);
case Qt::CheckStateRole: case Qt::CheckStateRole:
if (index.column() == ProfileNameColumn) { switch (index.column())
return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked; {
} else { case ProfileNameColumn:
return QVariant(); return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked;
} }
default: default:
return QVariant(); return QVariant();
} }

View File

@ -26,7 +26,6 @@ bool AuthSession::MakeOffline(QString offline_playername)
return false; return false;
} }
session = "-"; session = "-";
access_token = "0";
player_name = offline_playername; player_name = offline_playername;
status = PlayableOffline; status = PlayableOffline;
return true; return true;

View File

@ -93,7 +93,7 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username)
{ {
auto account = makeShared<MinecraftAccount>(); auto account = makeShared<MinecraftAccount>();
account->data.type = AccountType::Offline; account->data.type = AccountType::Offline;
account->data.yggdrasilToken.token = "0"; account->data.yggdrasilToken.token = "offline";
account->data.yggdrasilToken.validity = Katabasis::Validity::Certain; account->data.yggdrasilToken.validity = Katabasis::Validity::Certain;
account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
account->data.yggdrasilToken.extra["userName"] = username; account->data.yggdrasilToken.extra["userName"] = username;

View File

@ -273,7 +273,6 @@ void Yggdrasil::processReply() {
AccountTaskState::STATE_FAILED_GONE, AccountTaskState::STATE_FAILED_GONE,
tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account.") tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account.")
); );
return;
} }
default: default:
changeState( changeState(

View File

@ -74,7 +74,6 @@ std::pair<int, bool> DataPack::compare(const Resource& other, SortType type) con
auto res = Resource::compare(other, type); auto res = Resource::compare(other, type);
if (res.first != 0) if (res.first != 0)
return res; return res;
break;
} }
case SortType::PACK_FORMAT: { case SortType::PACK_FORMAT: {
auto this_ver = packFormat(); auto this_ver = packFormat();
@ -84,7 +83,6 @@ std::pair<int, bool> DataPack::compare(const Resource& other, SortType type) con
return { 1, type == SortType::PACK_FORMAT }; return { 1, type == SortType::PACK_FORMAT };
if (this_ver < other_ver) if (this_ver < other_ver)
return { -1, type == SortType::PACK_FORMAT }; return { -1, type == SortType::PACK_FORMAT };
break;
} }
} }
return { 0, false }; return { 0, false };

View File

@ -41,9 +41,11 @@
#include <QString> #include <QString>
#include <QRegularExpression> #include <QRegularExpression>
#include "MTPixmapCache.h"
#include "MetadataHandler.h" #include "MetadataHandler.h"
#include "Version.h" #include "Version.h"
#include "minecraft/mod/ModDetails.h" #include "minecraft/mod/ModDetails.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
static ModPlatform::ProviderCapabilities ProviderCaps; static ModPlatform::ProviderCapabilities ProviderCaps;
@ -89,7 +91,6 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
auto res = Resource::compare(other, type); auto res = Resource::compare(other, type);
if (res.first != 0) if (res.first != 0)
return res; return res;
break;
} }
case SortType::VERSION: { case SortType::VERSION: {
auto this_ver = Version(version()); auto this_ver = Version(version());
@ -98,13 +99,11 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
return { 1, type == SortType::VERSION }; return { 1, type == SortType::VERSION };
if (this_ver < other_ver) if (this_ver < other_ver)
return { -1, type == SortType::VERSION }; return { -1, type == SortType::VERSION };
break;
} }
case SortType::PROVIDER: { case SortType::PROVIDER: {
auto compare_result = QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive); auto compare_result = QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive);
if (compare_result != 0) if (compare_result != 0)
return { compare_result, type == SortType::PROVIDER }; return { compare_result, type == SortType::PROVIDER };
break;
} }
} }
return { 0, false }; return { 0, false };
@ -124,7 +123,7 @@ bool Mod::applyFilter(QRegularExpression filter) const
return Resource::applyFilter(filter); return Resource::applyFilter(filter);
} }
auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool
{ {
if (!preserve_metadata) { if (!preserve_metadata) {
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name()); qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
@ -137,7 +136,7 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -
} }
} }
return Resource::destroy(attempt_trash); return Resource::destroy();
} }
auto Mod::details() const -> const ModDetails& auto Mod::details() const -> const ModDetails&
@ -204,7 +203,10 @@ void Mod::finishResolvingWithDetails(ModDetails&& details)
m_local_details = std::move(details); m_local_details = std::move(details);
if (metadata) if (metadata)
setMetadata(std::move(metadata)); setMetadata(std::move(metadata));
}; if (!iconPath().isEmpty()) {
m_pack_image_cache_key.was_read_attempt = false;
}
}
auto Mod::provider() const -> std::optional<QString> auto Mod::provider() const -> std::optional<QString>
{ {
@ -213,6 +215,56 @@ auto Mod::provider() const -> std::optional<QString>
return {}; return {};
} }
auto Mod::licenses() const -> const QList<ModLicense>&
{
return details().licenses;
}
auto Mod::issueTracker() const -> QString
{
return details().issue_tracker;
}
void Mod::setIcon(QImage new_image) const
{
QMutexLocker locker(&m_data_lock);
Q_ASSERT(!new_image.isNull());
if (m_pack_image_cache_key.key.isValid())
PixmapCache::remove(m_pack_image_cache_key.key);
// scale the image to avoid flooding the pixmapcache
auto pixmap = QPixmap::fromImage(new_image.scaled({64, 64}, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
m_pack_image_cache_key.was_read_attempt = true;
}
QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
{
QPixmap cached_image;
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
return cached_image.scaled(size, mode);
}
// No valid image we can get
if ((!m_pack_image_cache_key.was_ever_used && m_pack_image_cache_key.was_read_attempt) || iconPath().isEmpty())
return {};
if (m_pack_image_cache_key.was_ever_used) {
qDebug() << "Mod" << name() << "Had it's icon evicted form the cache. reloading...";
PixmapCache::markCacheMissByEviciton();
}
// Image got evicted from the cache or an attempt to load it has not been made. load it and retry.
m_pack_image_cache_key.was_read_attempt = true;
ModUtils::loadIconFile(*this);
return icon(size);
}
bool Mod::valid() const bool Mod::valid() const
{ {
return !m_local_details.mod_id.isEmpty(); return !m_local_details.mod_id.isEmpty();

View File

@ -38,6 +38,10 @@
#include <QDateTime> #include <QDateTime>
#include <QFileInfo> #include <QFileInfo>
#include <QList> #include <QList>
#include <QImage>
#include <QMutex>
#include <QPixmap>
#include <QPixmapCache>
#include <optional> #include <optional>
@ -64,6 +68,15 @@ public:
auto authors() const -> QStringList; auto authors() const -> QStringList;
auto status() const -> ModStatus; auto status() const -> ModStatus;
auto provider() const -> std::optional<QString>; auto provider() const -> std::optional<QString>;
auto licenses() const -> const QList<ModLicense>&;
auto issueTracker() const -> QString;
/** Get the intneral path to the mod's icon file*/
QString iconPath() const { return m_local_details.icon_file; };
/** Gets the icon of the mod, converted to a QPixmap for drawing, and scaled to size. */
[[nodiscard]] QPixmap icon(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
/** Thread-safe. */
void setIcon(QImage new_image) const;
auto metadata() -> std::shared_ptr<Metadata::ModStruct>; auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>; auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
@ -79,10 +92,19 @@ public:
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
// Delete all the files of this mod // Delete all the files of this mod
auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool; auto destroy(QDir& index_dir, bool preserve_metadata = false) -> bool;
void finishResolvingWithDetails(ModDetails&& details); void finishResolvingWithDetails(ModDetails&& details);
protected: protected:
ModDetails m_local_details; ModDetails m_local_details;
mutable QMutex m_data_lock;
struct {
QPixmapCache::Key key;
bool was_ever_used = false;
bool was_read_attempt = false;
} mutable m_pack_image_cache_key;
}; };

View File

@ -39,6 +39,7 @@
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
#include <QUrl>
#include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/MetadataHandler.h"
@ -49,6 +50,84 @@ enum class ModStatus {
Unknown, // Default status Unknown, // Default status
}; };
struct ModLicense {
QString name = {};
QString id = {};
QString url = {};
QString description = {};
ModLicense() {}
ModLicense(const QString license) {
// FIXME: come up with a better license parseing.
// handle SPDX identifiers? https://spdx.org/licenses/
auto parts = license.split(' ');
QStringList notNameParts = {};
for (auto part : parts) {
auto url = QUrl(part);
if (part.startsWith("(") && part.endsWith(")"))
url = QUrl(part.mid(1, part.size() - 2));
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
this->url = url.toString();
notNameParts.append(part);
continue;
}
}
for (auto part : notNameParts) {
parts.removeOne(part);
}
auto licensePart = parts.join(' ');
this->name = licensePart;
this->description = licensePart;
if (parts.size() == 1) {
this->id = parts.first();
}
}
ModLicense(const QString name, const QString id, const QString url, const QString description) {
this->name = name;
this->id = id;
this->url = url;
this->description = description;
}
ModLicense(const ModLicense& other)
: name(other.name)
, id(other.id)
, url(other.url)
, description(other.description)
{}
ModLicense& operator=(const ModLicense& other)
{
this->name = other.name;
this->id = other.id;
this->url = other.url;
this->description = other.description;
return *this;
}
ModLicense& operator=(const ModLicense&& other)
{
this->name = other.name;
this->id = other.id;
this->url = other.url;
this->description = other.description;
return *this;
}
bool isEmpty() {
return this->name.isEmpty() && this->id.isEmpty() && this->url.isEmpty() && this->description.isEmpty();
}
};
struct ModDetails struct ModDetails
{ {
/* Mod ID as defined in the ModLoader-specific metadata */ /* Mod ID as defined in the ModLoader-specific metadata */
@ -72,6 +151,15 @@ struct ModDetails
/* List of the author's names */ /* List of the author's names */
QStringList authors = {}; QStringList authors = {};
/* Issue Tracker URL */
QString issue_tracker = {};
/* License */
QList<ModLicense> licenses = {};
/* Path of mod logo */
QString icon_file = {};
/* Installation status of the mod */ /* Installation status of the mod */
ModStatus status = ModStatus::Unknown; ModStatus status = ModStatus::Unknown;
@ -89,6 +177,9 @@ struct ModDetails
, homeurl(other.homeurl) , homeurl(other.homeurl)
, description(other.description) , description(other.description)
, authors(other.authors) , authors(other.authors)
, issue_tracker(other.issue_tracker)
, licenses(other.licenses)
, icon_file(other.icon_file)
, status(other.status) , status(other.status)
{} {}
@ -101,6 +192,9 @@ struct ModDetails
this->homeurl = other.homeurl; this->homeurl = other.homeurl;
this->description = other.description; this->description = other.description;
this->authors = other.authors; this->authors = other.authors;
this->issue_tracker = other.issue_tracker;
this->licenses = other.licenses;
this->icon_file = other.icon_file;
this->status = other.status; this->status = other.status;
return *this; return *this;
@ -115,6 +209,9 @@ struct ModDetails
this->homeurl = other.homeurl; this->homeurl = other.homeurl;
this->description = other.description; this->description = other.description;
this->authors = other.authors; this->authors = other.authors;
this->issue_tracker = other.issue_tracker;
this->licenses = other.licenses;
this->icon_file = other.icon_file;
this->status = other.status; this->status = other.status;
return *this; return *this;

View File

@ -37,6 +37,7 @@
#include "ModFolderModel.h" #include "ModFolderModel.h"
#include <FileSystem.h> #include <FileSystem.h>
#include <qheaderview.h>
#include <QDebug> #include <QDebug>
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
#include <QIcon> #include <QIcon>
@ -52,12 +53,14 @@
#include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir) ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
{ {
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER }; m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME , SortType::VERSION, SortType::DATE, SortType::PROVIDER};
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents};
} }
QVariant ModFolderModel::data(const QModelIndex &index, int role) const QVariant ModFolderModel::data(const QModelIndex &index, int role) const
@ -118,7 +121,9 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
case Qt::DecorationRole: { case Qt::DecorationRole: {
if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow"); return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row)->icon({32, 32}, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {}; return {};
} }
case Qt::CheckStateRole: case Qt::CheckStateRole:
@ -142,15 +147,12 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
switch (section) switch (section)
{ {
case ActiveColumn: case ActiveColumn:
return QString();
case NameColumn: case NameColumn:
return tr("Name");
case VersionColumn: case VersionColumn:
return tr("Version");
case DateColumn: case DateColumn:
return tr("Last changed");
case ProviderColumn: case ProviderColumn:
return tr("Provider"); case ImageColumn:
return columnNames().at(section);
default: default:
return QVariant(); return QVariant();
} }
@ -197,10 +199,10 @@ Task* ModFolderModel::createParseTask(Resource& resource)
bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata) bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
{ {
for(auto mod : allMods()) { for(auto mod : allMods()){
if(mod->fileinfo().fileName() == filename) { if(mod->fileinfo().fileName() == filename){
auto index_dir = indexDir(); auto index_dir = indexDir();
mod->destroy(index_dir, preserve_metadata, false); mod->destroy(index_dir, preserve_metadata);
update(); update();

View File

@ -64,6 +64,7 @@ public:
enum Columns enum Columns
{ {
ActiveColumn = 0, ActiveColumn = 0,
ImageColumn,
NameColumn, NameColumn,
VersionColumn, VersionColumn,
DateColumn, DateColumn,
@ -77,6 +78,8 @@ public:
}; };
ModFolderModel(const QString &dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true); ModFolderModel(const QString &dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true);
virtual QString id() const override { return "mods"; }
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

View File

@ -71,7 +71,6 @@ std::pair<int, bool> Resource::compare(const Resource& other, SortType type) con
return { 1, type == SortType::ENABLED }; return { 1, type == SortType::ENABLED };
if (!enabled() && other.enabled()) if (!enabled() && other.enabled())
return { -1, type == SortType::ENABLED }; return { -1, type == SortType::ENABLED };
break;
case SortType::NAME: { case SortType::NAME: {
QString this_name{ name() }; QString this_name{ name() };
QString other_name{ other.name() }; QString other_name{ other.name() };
@ -82,14 +81,12 @@ std::pair<int, bool> Resource::compare(const Resource& other, SortType type) con
auto compare_result = QString::compare(this_name, other_name, Qt::CaseInsensitive); auto compare_result = QString::compare(this_name, other_name, Qt::CaseInsensitive);
if (compare_result != 0) if (compare_result != 0)
return { compare_result, type == SortType::NAME }; return { compare_result, type == SortType::NAME };
break;
} }
case SortType::DATE: case SortType::DATE:
if (dateTimeChanged() > other.dateTimeChanged()) if (dateTimeChanged() > other.dateTimeChanged())
return { 1, type == SortType::DATE }; return { 1, type == SortType::DATE };
if (dateTimeChanged() < other.dateTimeChanged()) if (dateTimeChanged() < other.dateTimeChanged())
return { -1, type == SortType::DATE }; return { -1, type == SortType::DATE };
break;
} }
return { 0, false }; return { 0, false };
@ -148,10 +145,14 @@ bool Resource::enable(EnableAction action)
return true; return true;
} }
bool Resource::destroy(bool attemptTrash) bool Resource::destroy()
{ {
m_type = ResourceType::UNKNOWN; m_type = ResourceType::UNKNOWN;
return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
if (FS::trash(m_file_info.filePath()))
return true;
return FS::deletePath(m_file_info.filePath());
} }
bool Resource::isSymLinkUnder(const QString& instPath) const bool Resource::isSymLinkUnder(const QString& instPath) const

View File

@ -92,7 +92,7 @@ class Resource : public QObject {
} }
// Delete all files of this resource. // Delete all files of this resource.
bool destroy(bool attemptTrash = true); bool destroy();
[[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); } [[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); }

View File

@ -8,12 +8,15 @@
#include <QStyle> #include <QStyle>
#include <QThreadPool> #include <QThreadPool>
#include <QUrl> #include <QUrl>
#include <QMenu>
#include "Application.h" #include "Application.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "QVariantUtils.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "settings/Setting.h"
#include "tasks/Task.h" #include "tasks/Task.h"
ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir) ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir)
@ -156,7 +159,7 @@ bool ResourceFolderModel::uninstallResource(QString file_name)
{ {
for (auto& resource : m_resources) { for (auto& resource : m_resources) {
if (resource->fileinfo().fileName() == file_name) { if (resource->fileinfo().fileName() == file_name) {
auto res = resource->destroy(false); auto res = resource->destroy();
update(); update();
@ -471,10 +474,10 @@ QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientatio
switch (role) { switch (role) {
case Qt::DisplayRole: case Qt::DisplayRole:
switch (section) { switch (section) {
case ACTIVE_COLUMN:
case NAME_COLUMN: case NAME_COLUMN:
return tr("Name");
case DATE_COLUMN: case DATE_COLUMN:
return tr("Last modified"); return columnNames().at(section);
default: default:
return {}; return {};
} }
@ -500,6 +503,75 @@ QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientatio
return {}; return {};
} }
void ResourceFolderModel::setupHeaderAction(QAction* act, int column)
{
Q_ASSERT(act);
act->setText(columnNames().at(column));
}
void ResourceFolderModel::saveHiddenColumn(int column, bool hidden)
{
auto const setting_name = QString("UI/%1_Page/HiddenColumns").arg(id());
auto setting = (m_instance->settings()->contains(setting_name)) ?
m_instance->settings()->getSetting(setting_name) : m_instance->settings()->registerSetting(setting_name);
auto hiddenColumns = setting->get().toStringList();
auto name = columnNames(false).at(column);
auto index = hiddenColumns.indexOf(name);
if (index >= 0 && !hidden) {
hiddenColumns.removeAt(index);
} else if ( index < 0 && hidden) {
hiddenColumns.append(name);
}
setting->set(hiddenColumns);
}
void ResourceFolderModel::loadHiddenColumns(QTreeView *tree)
{
auto const setting_name = QString("UI/%1_Page/HiddenColumns").arg(id());
auto setting = (m_instance->settings()->contains(setting_name)) ?
m_instance->settings()->getSetting(setting_name) : m_instance->settings()->registerSetting(setting_name);
auto hiddenColumns = setting->get().toStringList();
auto col_names = columnNames(false);
for (auto col_name : hiddenColumns) {
auto index = col_names.indexOf(col_name);
if (index >= 0)
tree->setColumnHidden(index, true);
}
}
QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
{
auto menu = new QMenu(tree);
menu->addSeparator()->setText(tr("Show / Hide Columns"));
for (int col = 0; col < columnCount(); ++col) {
auto act = new QAction(menu);
setupHeaderAction(act, col);
act->setCheckable(true);
act->setChecked(!tree->isColumnHidden(col));
connect(act, &QAction::toggled, tree, [this, col, tree](bool toggled){
tree->setColumnHidden(col, !toggled);
for(int c = 0; c < columnCount(); ++c) {
if (m_column_resize_modes.at(c) == QHeaderView::ResizeToContents)
tree->resizeColumnToContents(c);
}
saveHiddenColumn(col, !toggled);
});
menu->addAction(act);
}
return menu;
}
QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* parent) QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* parent)
{ {
return new ProxyModel(parent); return new ProxyModel(parent);

View File

@ -1,5 +1,8 @@
#pragma once #pragma once
#include <QHeaderView>
#include <QAction>
#include <QTreeView>
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QDir> #include <QDir>
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
@ -29,6 +32,8 @@ class ResourceFolderModel : public QAbstractListModel {
ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true); ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true);
~ResourceFolderModel() override; ~ResourceFolderModel() override;
virtual QString id() const { return "resource"; }
/** Starts watching the paths for changes. /** Starts watching the paths for changes.
* *
* Returns whether starting to watch all the paths was successful. * Returns whether starting to watch all the paths was successful.
@ -92,6 +97,7 @@ class ResourceFolderModel : public QAbstractListModel {
/* Basic columns */ /* Basic columns */
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS }; enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, 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()); } [[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
[[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; }; [[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; };
@ -110,6 +116,11 @@ class ResourceFolderModel : public QAbstractListModel {
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
void setupHeaderAction(QAction* act, int column);
void saveHiddenColumn(int column, bool hidden);
void loadHiddenColumns(QTreeView* tree);
QMenu* createHeaderContextMenu(QTreeView* tree);
/** This creates a proxy model to filter / sort the model for a UI. /** This creates a proxy model to filter / sort the model for a UI.
* *
* The actual comparisons and filtering are done directly by the Resource, so to modify behavior go there instead! * The actual comparisons and filtering are done directly by the Resource, so to modify behavior go there instead!
@ -117,6 +128,7 @@ class ResourceFolderModel : public QAbstractListModel {
QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr); QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr);
[[nodiscard]] SortType columnToSortKey(size_t column) const; [[nodiscard]] SortType columnToSortKey(size_t column) const;
[[nodiscard]] QList<QHeaderView::ResizeMode> columnResizeModes() const { return m_column_resize_modes; }
class ProxyModel : public QSortFilterProxyModel { class ProxyModel : public QSortFilterProxyModel {
public: public:
@ -187,6 +199,9 @@ class ResourceFolderModel : public QAbstractListModel {
// Represents the relationship between a column's index (represented by the list index), and it's sorting key. // 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! // As such, the order in with they appear is very important!
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE }; QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE };
QStringList m_column_names = {"Enable", "Name", "Last Modified"};
QStringList m_column_names_translated = {tr("Enable"), tr("Name"), tr("Last Modified")};
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch, QHeaderView::ResizeToContents };
bool m_can_interact = true; bool m_can_interact = true;

View File

@ -40,7 +40,7 @@ void ResourcePack::setDescription(QString new_description)
m_description = new_description; m_description = new_description;
} }
void ResourcePack::setImage(QImage new_image) void ResourcePack::setImage(QImage new_image) const
{ {
QMutexLocker locker(&m_data_lock); QMutexLocker locker(&m_data_lock);
@ -49,7 +49,10 @@ void ResourcePack::setImage(QImage new_image)
if (m_pack_image_cache_key.key.isValid()) if (m_pack_image_cache_key.key.isValid())
PixmapCache::instance().remove(m_pack_image_cache_key.key); PixmapCache::instance().remove(m_pack_image_cache_key.key);
m_pack_image_cache_key.key = PixmapCache::instance().insert(QPixmap::fromImage(new_image)); // scale the image to avoid flooding the pixmapcache
auto pixmap = QPixmap::fromImage(new_image.scaled({64, 64}, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap);
m_pack_image_cache_key.was_ever_used = true; m_pack_image_cache_key.was_ever_used = true;
// This can happen if the pixmap is too big to fit in the cache :c // This can happen if the pixmap is too big to fit in the cache :c
@ -59,21 +62,25 @@ void ResourcePack::setImage(QImage new_image)
} }
} }
QPixmap ResourcePack::image(QSize size) QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const
{ {
QPixmap cached_image; QPixmap cached_image;
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) { if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull()) if (size.isNull())
return cached_image; return cached_image;
return cached_image.scaled(size); return cached_image.scaled(size, mode);
} }
// No valid image we can get // No valid image we can get
if (!m_pack_image_cache_key.was_ever_used) if (!m_pack_image_cache_key.was_ever_used) {
return {}; return {};
} else {
qDebug() << "Resource Pack" << name() << "Had it's image evicted from the cache. reloading...";
PixmapCache::markCacheMissByEviciton();
}
// Imaged got evicted from the cache. Re-process it and retry. // Imaged got evicted from the cache. Re-process it and retry.
ResourcePackUtils::process(*this); ResourcePackUtils::processPackPNG(*this);
return image(size); return image(size);
} }
@ -95,7 +102,6 @@ std::pair<int, bool> ResourcePack::compare(const Resource& other, SortType type)
auto res = Resource::compare(other, type); auto res = Resource::compare(other, type);
if (res.first != 0) if (res.first != 0)
return res; return res;
break;
} }
case SortType::PACK_FORMAT: { case SortType::PACK_FORMAT: {
auto this_ver = packFormat(); auto this_ver = packFormat();
@ -105,7 +111,6 @@ std::pair<int, bool> ResourcePack::compare(const Resource& other, SortType type)
return { 1, type == SortType::PACK_FORMAT }; return { 1, type == SortType::PACK_FORMAT };
if (this_ver < other_ver) if (this_ver < other_ver)
return { -1, type == SortType::PACK_FORMAT }; return { -1, type == SortType::PACK_FORMAT };
break;
} }
} }
return { 0, false }; return { 0, false };

View File

@ -31,7 +31,7 @@ class ResourcePack : public Resource {
[[nodiscard]] QString description() const { return m_description; } [[nodiscard]] QString description() const { return m_description; }
/** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */ /** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */
[[nodiscard]] QPixmap image(QSize size); [[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
/** Thread-safe. */ /** Thread-safe. */
void setPackFormat(int new_format_id); void setPackFormat(int new_format_id);
@ -40,7 +40,7 @@ class ResourcePack : public Resource {
void setDescription(QString new_description); void setDescription(QString new_description);
/** Thread-safe. */ /** Thread-safe. */
void setImage(QImage new_image); void setImage(QImage new_image) const;
bool valid() const override; bool valid() const override;
@ -67,5 +67,5 @@ class ResourcePack : public Resource {
struct { struct {
QPixmapCache::Key key; QPixmapCache::Key key;
bool was_ever_used = false; bool was_ever_used = false;
} m_pack_image_cache_key; } mutable m_pack_image_cache_key;
}; };

View File

@ -35,6 +35,8 @@
*/ */
#include "ResourcePackFolderModel.h" #include "ResourcePackFolderModel.h"
#include <qnamespace.h>
#include <qsize.h>
#include <QIcon> #include <QIcon>
#include <QStyle> #include <QStyle>
@ -48,7 +50,11 @@
ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance) ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance)
: ResourceFolderModel(QDir(dir), instance) : ResourceFolderModel(QDir(dir), instance)
{ {
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE};
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents };
} }
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
@ -84,9 +90,11 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
return {}; return {};
} }
case Qt::DecorationRole: { case Qt::DecorationRole: {
if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow"); return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row)->image({32, 32}, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {}; return {};
} }
case Qt::ToolTipRole: { case Qt::ToolTipRole: {
@ -94,7 +102,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
//: The string being explained by this is in the format: ID (Lower version - Upper version) //: The string being explained by this is in the format: ID (Lower version - Upper version)
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for."); return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
} }
if (column == NAME_COLUMN) { if (column == NameColumn) {
if (at(row)->isSymLinkUnder(instDirPath())) { if (at(row)->isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() + return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
@ -126,13 +134,11 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
case Qt::DisplayRole: case Qt::DisplayRole:
switch (section) { switch (section) {
case ActiveColumn: case ActiveColumn:
return QString();
case NameColumn: case NameColumn:
return tr("Name");
case PackFormatColumn: case PackFormatColumn:
return tr("Pack Format");
case DateColumn: case DateColumn:
return tr("Last changed"); case ImageColumn:
return columnNames().at(section);
default: default:
return {}; return {};
} }
@ -151,6 +157,11 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
default: default:
return {}; return {};
} }
case Qt::SizeHintRole:
if (section == ImageColumn) {
return QSize(64,0);
}
return {};
default: default:
return {}; return {};
} }

View File

@ -11,6 +11,7 @@ public:
enum Columns enum Columns
{ {
ActiveColumn = 0, ActiveColumn = 0,
ImageColumn,
NameColumn, NameColumn,
PackFormatColumn, PackFormatColumn,
DateColumn, DateColumn,
@ -19,6 +20,8 @@ public:
explicit ResourcePackFolderModel(const QString &dir, BaseInstance* instance); explicit ResourcePackFolderModel(const QString &dir, BaseInstance* instance);
virtual QString id() const override { return "resourcepacks"; }
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; [[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]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

View File

@ -9,4 +9,6 @@ class ShaderPackFolderModel : public ResourceFolderModel {
explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance) explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance)
: ResourceFolderModel(QDir(dir), instance) : ResourceFolderModel(QDir(dir), instance)
{} {}
virtual QString id() const override { return "shaderpacks"; }
}; };

View File

@ -23,6 +23,8 @@
#include <QMap> #include <QMap>
#include <QRegularExpression> #include <QRegularExpression>
#include "MTPixmapCache.h"
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
void TexturePack::setDescription(QString new_description) void TexturePack::setDescription(QString new_description)
@ -32,34 +34,41 @@ void TexturePack::setDescription(QString new_description)
m_description = new_description; m_description = new_description;
} }
void TexturePack::setImage(QImage new_image) void TexturePack::setImage(QImage new_image) const
{ {
QMutexLocker locker(&m_data_lock); QMutexLocker locker(&m_data_lock);
Q_ASSERT(!new_image.isNull()); Q_ASSERT(!new_image.isNull());
if (m_pack_image_cache_key.key.isValid()) if (m_pack_image_cache_key.key.isValid())
QPixmapCache::remove(m_pack_image_cache_key.key); PixmapCache::remove(m_pack_image_cache_key.key);
m_pack_image_cache_key.key = QPixmapCache::insert(QPixmap::fromImage(new_image)); // scale the image to avoid flooding the pixmapcache
auto pixmap = QPixmap::fromImage(new_image.scaled({64, 64}, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
m_pack_image_cache_key.was_ever_used = true; m_pack_image_cache_key.was_ever_used = true;
} }
QPixmap TexturePack::image(QSize size) QPixmap TexturePack::image(QSize size, Qt::AspectRatioMode mode) const
{ {
QPixmap cached_image; QPixmap cached_image;
if (QPixmapCache::find(m_pack_image_cache_key.key, &cached_image)) { if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull()) if (size.isNull())
return cached_image; return cached_image;
return cached_image.scaled(size); return cached_image.scaled(size, mode);
} }
// No valid image we can get // No valid image we can get
if (!m_pack_image_cache_key.was_ever_used) if (!m_pack_image_cache_key.was_ever_used) {
return {}; return {};
} else {
qDebug() << "Texture Pack" << name() << "Had it's image evicted from the cache. reloading...";
PixmapCache::markCacheMissByEviciton();
}
// Imaged got evicted from the cache. Re-process it and retry. // Imaged got evicted from the cache. Re-process it and retry.
TexturePackUtils::process(*this); TexturePackUtils::processPackPNG(*this);
return image(size); return image(size);
} }

View File

@ -40,13 +40,13 @@ class TexturePack : public Resource {
[[nodiscard]] QString description() const { return m_description; } [[nodiscard]] QString description() const { return m_description; }
/** Gets the image of the texture pack, converted to a QPixmap for drawing, and scaled to size. */ /** Gets the image of the texture pack, converted to a QPixmap for drawing, and scaled to size. */
[[nodiscard]] QPixmap image(QSize size); [[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
/** Thread-safe. */ /** Thread-safe. */
void setDescription(QString new_description); void setDescription(QString new_description);
/** Thread-safe. */ /** Thread-safe. */
void setImage(QImage new_image); void setImage(QImage new_image) const;
bool valid() const override; bool valid() const override;
@ -65,5 +65,5 @@ class TexturePack : public Resource {
struct { struct {
QPixmapCache::Key key; QPixmapCache::Key key;
bool was_ever_used = false; bool was_ever_used = false;
} m_pack_image_cache_key; } mutable m_pack_image_cache_key;
}; };

View File

@ -33,6 +33,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#include <QCoreApplication>
#include "Application.h"
#include "TexturePackFolderModel.h" #include "TexturePackFolderModel.h"
@ -41,7 +44,13 @@
TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance) TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance)
: ResourceFolderModel(QDir(dir), instance) : ResourceFolderModel(QDir(dir), instance)
{} {
m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE };
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents};
}
Task* TexturePackFolderModel::createUpdateTask() Task* TexturePackFolderModel::createUpdateTask()
{ {
@ -52,3 +61,96 @@ Task* TexturePackFolderModel::createParseTask(Resource& resource)
{ {
return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast<TexturePack&>(resource)); return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast<TexturePack&>(resource));
} }
QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
{
if (!validateIndex(index))
return {};
int row = index.row();
int column = index.column();
switch (role) {
case Qt::DisplayRole:
switch (column) {
case NameColumn:
return m_resources[row]->name();
case DateColumn:
return m_resources[row]->dateTimeChanged();
default:
return {};
}
case Qt::ToolTipRole:
if (column == NameColumn) {
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());;
}
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()))
return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row)->image({32, 32}, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {};
}
case Qt::CheckStateRole:
if (column == ActiveColumn) {
return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
}
return {};
default:
return {};
}
}
QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:
switch (section) {
case ActiveColumn:
case NameColumn:
case DateColumn:
case ImageColumn:
return columnNames().at(section);
default:
return {};
}
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 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).");
default:
return {};
}
}
default:
break;
}
return {};
}
int TexturePackFolderModel::columnCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : NUM_COLUMNS;
}

View File

@ -38,12 +38,35 @@
#include "ResourceFolderModel.h" #include "ResourceFolderModel.h"
#include "TexturePack.h"
class TexturePackFolderModel : public ResourceFolderModel class TexturePackFolderModel : public ResourceFolderModel
{ {
Q_OBJECT Q_OBJECT
public: public:
enum Columns
{
ActiveColumn = 0,
ImageColumn,
NameColumn,
DateColumn,
NUM_COLUMNS
};
explicit TexturePackFolderModel(const QString &dir, std::shared_ptr<const BaseInstance> instance);
virtual QString id() const override { return "texturepacks"; }
[[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;
explicit TexturePackFolderModel(const QString &dir, BaseInstance* instance); explicit TexturePackFolderModel(const QString &dir, BaseInstance* instance);
[[nodiscard]] Task* createUpdateTask() override; [[nodiscard]] Task* createUpdateTask() override;
[[nodiscard]] Task* createParseTask(Resource&) override; [[nodiscard]] Task* createParseTask(Resource&) override;
RESOURCE_HELPERS(TexturePack)
}; };

View File

@ -0,0 +1,252 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "GetModDependenciesTask.h"
#include <QDebug>
#include <algorithm>
#include <memory>
#include "Json.h"
#include "QObjectPtr.h"
#include "minecraft/mod/MetadataHandler.h"
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/modrinth/ModrinthAPI.h"
#include "tasks/ConcurrentTask.h"
#include "tasks/SequentialTask.h"
#include "ui/pages/modplatform/ModModel.h"
#include "ui/pages/modplatform/flame/FlameResourceModels.h"
#include "ui/pages/modplatform/modrinth/ModrinthResourceModels.h"
static Version mcVersion(BaseInstance* inst)
{
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion();
}
static ResourceAPI::ModLoaderTypes mcLoaders(BaseInstance* inst)
{
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders().value();
}
GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
BaseInstance* instance,
ModFolderModel* folder,
QList<std::shared_ptr<PackDependency>> selected)
: SequentialTask(parent, tr("Get dependencies"))
, m_selected(selected)
, m_flame_provider{ ModPlatform::ResourceProvider::FLAME, std::make_shared<ResourceDownload::FlameModModel>(*instance),
std::make_shared<FlameAPI>() }
, m_modrinth_provider{ ModPlatform::ResourceProvider::MODRINTH, std::make_shared<ResourceDownload::ModrinthModModel>(*instance),
std::make_shared<ModrinthAPI>() }
, m_version(mcVersion(instance))
, m_loaderType(mcLoaders(instance))
{
for (auto mod : folder->allMods())
if (auto meta = mod->metadata(); meta)
m_mods.append(meta);
prepare();
};
void GetModDependenciesTask::prepare()
{
for (auto sel : m_selected) {
for (auto dep : getDependenciesForVersion(sel->version, sel->pack->provider)) {
addTask(prepareDependencyTask(dep, sel->pack->provider, 20));
}
}
}
ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep,
const ModPlatform::ResourceProvider providerName)
{
if (auto isQuilt = m_loaderType & ResourceAPI::Quilt; isQuilt || m_loaderType & ResourceAPI::Fabric) {
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) {
return o.provider == providerName && dep.addonId == (isQuilt ? o.fabric : o.quilt);
});
if (over != overide.cend()) {
return { isQuilt ? over->quilt : over->fabric, dep.type };
}
}
return dep;
}
QList<ModPlatform::Dependency> GetModDependenciesTask::getDependenciesForVersion(const ModPlatform::IndexedVersion& version,
const ModPlatform::ResourceProvider providerName)
{
QList<ModPlatform::Dependency> c_dependencies;
for (auto ver_dep : version.dependencies) {
if (ver_dep.type != ModPlatform::DependencyType::REQUIRED)
continue;
auto isOnlyVersion = providerName == ModPlatform::ResourceProvider::MODRINTH && ver_dep.addonId.toString().isEmpty();
if (auto dep = std::find_if(c_dependencies.begin(), c_dependencies.end(),
[&ver_dep, isOnlyVersion](const ModPlatform::Dependency& i) {
return isOnlyVersion ? i.version == ver_dep.version : i.addonId == ver_dep.addonId;
});
dep != c_dependencies.end())
continue; // check the current dependency list
if (auto dep = std::find_if(m_selected.begin(), m_selected.end(),
[&ver_dep, providerName, isOnlyVersion](std::shared_ptr<PackDependency> i) {
return i->pack->provider == providerName && (isOnlyVersion ? i->version.version == ver_dep.version
: i->pack->addonId == ver_dep.addonId);
});
dep != m_selected.end())
continue; // check the selected versions
if (auto dep = std::find_if(m_mods.begin(), m_mods.end(),
[&ver_dep, providerName, isOnlyVersion](std::shared_ptr<Metadata::ModStruct> i) {
return i->provider == providerName &&
(isOnlyVersion ? i->file_id == ver_dep.version : i->project_id == ver_dep.addonId);
});
dep != m_mods.end())
continue; // check the existing mods
if (auto dep = std::find_if(m_pack_dependencies.begin(), m_pack_dependencies.end(),
[&ver_dep, providerName, isOnlyVersion](std::shared_ptr<PackDependency> i) {
return i->pack->provider == providerName && (isOnlyVersion ? i->version.version == ver_dep.addonId
: i->pack->addonId == ver_dep.addonId);
});
dep != m_pack_dependencies.end()) // check loaded dependencies
continue;
c_dependencies.append(getOverride(ver_dep, providerName));
}
return c_dependencies;
};
Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr<PackDependency> pDep)
{
auto provider = pDep->pack->provider == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
auto responseInfo = std::make_shared<QByteArray>();
auto info = provider.api->getProject(pDep->pack->addonId.toString(), responseInfo);
QObject::connect(info.get(), &NetJob::succeeded, [responseInfo, provider, pDep] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*responseInfo, &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() << *responseInfo;
return;
}
try {
auto obj = provider.name == ModPlatform::ResourceProvider::FLAME ? Json::requireObject(Json::requireObject(doc), "data")
: Json::requireObject(doc);
provider.mod->loadIndexedPack(*pDep->pack, obj);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading mod info: " << e.cause();
}
});
return info;
}
Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Dependency& dep,
const ModPlatform::ResourceProvider providerName,
int level)
{
auto pDep = std::make_shared<PackDependency>();
pDep->dependency = dep;
pDep->pack = std::make_shared<ModPlatform::IndexedPack>();
pDep->pack->addonId = dep.addonId;
pDep->pack->provider = providerName;
m_pack_dependencies.append(pDep);
auto provider = providerName == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
auto tasks = makeShared<SequentialTask>(
this, QString("DependencyInfo: %1").arg(dep.addonId.toString().isEmpty() ? dep.version : dep.addonId.toString()));
if (!dep.addonId.toString().isEmpty()) {
tasks->addTask(getProjectInfoTask(pDep));
}
ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType };
ResourceAPI::DependencySearchCallbacks callbacks;
callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, auto& pack) {
try {
QJsonArray arr;
if (dep.version.length() != 0 && doc.isObject()) {
arr.append(doc.object());
} else {
arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array();
}
pDep->version = provider.mod->loadDependencyVersions(dep, arr);
if (!pDep->version.addonId.isValid()) {
if (m_loaderType & ResourceAPI::Quilt) { // falback for quilt
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(),
[dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; });
if (over != overide.cend()) {
removePack(dep.addonId);
addTask(prepareDependencyTask({ over->fabric, dep.type }, provider.name, level));
return;
}
}
qWarning() << "Error while reading mod version empty ";
qDebug() << doc;
return;
}
pDep->version.is_currently_selected = true;
pDep->pack->versions = { pDep->version };
pDep->pack->versionsLoaded = true;
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading mod version: " << e.cause();
return;
}
if (level == 0) {
qWarning() << "Dependency cycle exeeded";
return;
}
if (dep.addonId.toString().isEmpty() && !pDep->version.addonId.toString().isEmpty()) {
pDep->pack->addonId = pDep->version.addonId;
auto dep = getOverride({ pDep->version.addonId, pDep->dependency.type }, provider.name);
if (dep.addonId != pDep->version.addonId) {
removePack(pDep->version.addonId);
addTask(prepareDependencyTask(dep, provider.name, level));
} else
addTask(getProjectInfoTask(pDep));
}
for (auto dep : getDependenciesForVersion(pDep->version, provider.name)) {
addTask(prepareDependencyTask(dep, provider.name, level - 1));
}
};
auto version = provider.api->getDependencyVersion(std::move(args), std::move(callbacks));
tasks->addTask(version);
return tasks;
};
void GetModDependenciesTask::removePack(const QVariant addonId)
{
auto pred = [addonId](const std::shared_ptr<PackDependency>& v) { return v->pack->addonId == addonId; };
#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
m_pack_dependencies.removeIf(pred);
#else
for (auto it = m_pack_dependencies.begin(); it != m_pack_dependencies.end();)
if (pred(*it))
it = m_pack_dependencies.erase(it);
else
++it;
#endif
}

View File

@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QDir>
#include <QEventLoop>
#include <QList>
#include <QVariant>
#include <functional>
#include <memory>
#include "minecraft/mod/MetadataHandler.h"
#include "minecraft/mod/ModFolderModel.h"
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "tasks/SequentialTask.h"
#include "tasks/Task.h"
#include "ui/pages/modplatform/ModModel.h"
class GetModDependenciesTask : public SequentialTask {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<GetModDependenciesTask>;
struct PackDependency {
ModPlatform::Dependency dependency;
ModPlatform::IndexedPack::Ptr pack;
ModPlatform::IndexedVersion version;
PackDependency() = default;
PackDependency(const ModPlatform::IndexedPack::Ptr p, const ModPlatform::IndexedVersion& v)
{
pack = p;
version = v;
}
};
struct Provider {
ModPlatform::ResourceProvider name;
std::shared_ptr<ResourceDownload::ModModel> mod;
std::shared_ptr<ResourceAPI> api;
};
explicit GetModDependenciesTask(QObject* parent,
BaseInstance* instance,
ModFolderModel* folder,
QList<std::shared_ptr<PackDependency>> selected);
auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; }
protected slots:
Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider, int);
QList<ModPlatform::Dependency> getDependenciesForVersion(const ModPlatform::IndexedVersion&,
const ModPlatform::ResourceProvider providerName);
void prepare();
Task::Ptr getProjectInfoTask(std::shared_ptr<PackDependency> pDep);
ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider providerName);
void removePack(const QVariant addonId);
private:
QList<std::shared_ptr<PackDependency>> m_pack_dependencies;
QList<std::shared_ptr<Metadata::ModStruct>> m_mods;
QList<std::shared_ptr<PackDependency>> m_selected;
Provider m_flame_provider;
Provider m_modrinth_provider;
Version m_version;
ResourceAPI::ModLoaderTypes m_loaderType;
};

View File

@ -52,6 +52,10 @@ ModDetails ReadMCModInfo(QByteArray contents)
authors = firstObj.value("authors").toArray(); authors = firstObj.value("authors").toArray();
} }
if (firstObj.contains("logoFile")) {
details.icon_file = firstObj.value("logoFile").toString();
}
for (auto author : authors) { for (auto author : authors) {
details.authors.append(author.toString()); details.authors.append(author.toString());
} }
@ -166,6 +170,31 @@ ModDetails ReadMCModTOML(QByteArray contents)
} }
details.homeurl = homeurl; details.homeurl = homeurl;
QString issueTrackerURL = "";
if (auto issueTrackerURLDatum = tomlData["issueTrackerURL"].as_string()) {
issueTrackerURL = QString::fromStdString(issueTrackerURLDatum->get());
} else if (auto issueTrackerURLDatum = (*modsTable)["issueTrackerURL"].as_string()) {
issueTrackerURL = QString::fromStdString(issueTrackerURLDatum->get());
}
details.issue_tracker = issueTrackerURL;
QString license = "";
if (auto licenseDatum = tomlData["license"].as_string()) {
license = QString::fromStdString(licenseDatum->get());
} else if (auto licenseDatum =(*modsTable)["license"].as_string()) {
license = QString::fromStdString(licenseDatum->get());
}
if (!license.isEmpty())
details.licenses.append(ModLicense(license));
QString logoFile = "";
if (auto logoFileDatum = tomlData["logoFile"].as_string()) {
logoFile = QString::fromStdString(logoFileDatum->get());
} else if (auto logoFileDatum =(*modsTable)["logoFile"].as_string()) {
logoFile = QString::fromStdString(logoFileDatum->get());
}
details.icon_file = logoFile;
return details; return details;
} }
@ -201,6 +230,57 @@ ModDetails ReadFabricModInfo(QByteArray contents)
if (contact.contains("homepage")) { if (contact.contains("homepage")) {
details.homeurl = contact.value("homepage").toString(); details.homeurl = contact.value("homepage").toString();
} }
if (contact.contains("issues")) {
details.issue_tracker = contact.value("issues").toString();
}
}
if (object.contains("license")) {
auto license = object.value("license");
if (license.isArray()) {
for (auto l : license.toArray()) {
if (l.isString()) {
details.licenses.append(ModLicense(l.toString()));
} else if (l.isObject()) {
auto obj = l.toObject();
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(),
obj.value("url").toString(), obj.value("description").toString()));
}
}
} else if (license.isString()) {
details.licenses.append(ModLicense(license.toString()));
} else if (license.isObject()) {
auto obj = license.toObject();
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), obj.value("url").toString(),
obj.value("description").toString()));
}
}
if (object.contains("icon")) {
auto icon = object.value("icon");
if (icon.isObject()) {
auto obj = icon.toObject();
// take the largest icon
int largest = 0;
for (auto key : obj.keys()) {
auto size = key.split('x').first().toInt();
if (size > largest) {
largest = size;
}
}
if (largest > 0) {
auto key = QString::number(largest) + "x" + QString::number(largest);
details.icon_file = obj.value(key).toString();
} else { // parsing the sizes failed
// take the first
for (auto i : obj) {
details.icon_file = i.toString();
break;
}
}
} else if (icon.isString()) {
details.icon_file = icon.toString();
}
} }
} }
return details; return details;
@ -238,6 +318,58 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
if (modContact.contains("homepage")) { if (modContact.contains("homepage")) {
details.homeurl = Json::requireString(modContact.value("homepage")); details.homeurl = Json::requireString(modContact.value("homepage"));
} }
if (modContact.contains("issues")) {
details.issue_tracker = Json::requireString(modContact.value("issues"));
}
if (modMetadata.contains("license")) {
auto license = modMetadata.value("license");
if (license.isArray()) {
for (auto l : license.toArray()) {
if (l.isString()) {
details.licenses.append(ModLicense(l.toString()));
} else if (l.isObject()) {
auto obj = l.toObject();
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(),
obj.value("url").toString(), obj.value("description").toString()));
}
}
} else if (license.isString()) {
details.licenses.append(ModLicense(license.toString()));
} else if (license.isObject()) {
auto obj = license.toObject();
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), obj.value("url").toString(),
obj.value("description").toString()));
}
}
if (modMetadata.contains("icon")) {
auto icon = modMetadata.value("icon");
if (icon.isObject()) {
auto obj = icon.toObject();
// take the largest icon
int largest = 0;
for (auto key : obj.keys()) {
auto size = key.split('x').first().toInt();
if (size > largest) {
largest = size;
}
}
if (largest > 0) {
auto key = QString::number(largest) + "x" + QString::number(largest);
details.icon_file = obj.value(key).toString();
} else { // parsing the sizes failed
// take the first
for (auto i : obj) {
details.icon_file = i.toString();
break;
}
}
} else if (icon.isString()) {
details.icon_file = icon.toString();
}
}
} }
return details; return details;
} }
@ -515,6 +647,85 @@ bool validate(QFileInfo file)
return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid(); return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid();
} }
bool processIconPNG(const Mod& mod, QByteArray&& raw_data)
{
auto img = QImage::fromData(raw_data);
if (!img.isNull()) {
mod.setIcon(img);
} else {
qWarning() << "Failed to parse mod logo:" << mod.iconPath() << "from" << mod.name();
return false;
}
return true;
}
bool loadIconFile(const Mod& mod) {
if (mod.iconPath().isEmpty()) {
qWarning() << "No Iconfile set, be sure to parse the mod first";
return false;
}
auto png_invalid = [&mod]() {
qWarning() << "Mod at" << mod.fileinfo().filePath() << "does not have a valid icon";
return false;
};
switch (mod.type()) {
case ResourceType::FOLDER:
{
QFileInfo icon_info(FS::PathCombine(mod.fileinfo().filePath(), mod.iconPath()));
if (icon_info.exists() && icon_info.isFile()) {
QFile icon(icon_info.filePath());
if (!icon.open(QIODevice::ReadOnly))
return false;
auto data = icon.readAll();
bool icon_result = ModUtils::processIconPNG(mod, std::move(data));
icon.close();
if (!icon_result) {
return png_invalid(); // icon invalid
}
}
}
case ResourceType::ZIPFILE:
{
QuaZip zip(mod.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return false;
QuaZipFile file(&zip);
if (zip.setCurrentFile(mod.iconPath())) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
return png_invalid();
}
auto data = file.readAll();
bool icon_result = ModUtils::processIconPNG(mod, std::move(data));
file.close();
if (!icon_result) {
return png_invalid(); // icon png invalid
}
} else {
return png_invalid(); // could not set icon as current file.
}
}
case ResourceType::LITEMOD:
{
return false; // can lightmods even have icons?
}
default:
qWarning() << "Invalid type for mod, can not load icon.";
return false;
}
}
} // namespace ModUtils } // namespace ModUtils
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile) LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)

View File

@ -25,6 +25,9 @@ bool processLitemod(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
/** Checks whether a file is valid as a mod or not. */ /** Checks whether a file is valid as a mod or not. */
bool validate(QFileInfo file); bool validate(QFileInfo file);
bool processIconPNG(const Mod& mod, QByteArray&& raw_data);
bool loadIconFile(const Mod& mod);
} // namespace ModUtils } // namespace ModUtils
class LocalModParseTask : public Task { class LocalModParseTask : public Task {

View File

@ -1,25 +1,24 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3. * the Free Software Foundation, version 3.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "LocalModUpdateTask.h" #include "LocalModUpdateTask.h"
#include "Application.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/MetadataHandler.h"

View File

@ -165,15 +165,16 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level)
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data)); bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
file.close(); file.close();
zip.close();
if (!pack_png_result) { if (!pack_png_result) {
return png_invalid(); // pack.png invalid return png_invalid(); // pack.png invalid
} }
} else { } else {
zip.close();
return png_invalid(); // could not set pack.mcmeta as current file. return png_invalid(); // could not set pack.mcmeta as current file.
} }
zip.close(); zip.close();
return true; return true;
} }
@ -193,7 +194,7 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
return true; return true;
} }
bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data) bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data)
{ {
auto img = QImage::fromData(raw_data); auto img = QImage::fromData(raw_data);
if (!img.isNull()) { if (!img.isNull()) {
@ -205,6 +206,68 @@ bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
return true; return true;
} }
bool processPackPNG(const ResourcePack& pack)
{
auto png_invalid = [&pack]() {
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
return false;
};
switch (pack.type()) {
case ResourceType::FOLDER:
{
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
if (image_file_info.exists() && image_file_info.isFile()) {
QFile pack_png_file(image_file_info.filePath());
if (!pack_png_file.open(QIODevice::ReadOnly))
return png_invalid(); // can't open pack.png file
auto data = pack_png_file.readAll();
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
pack_png_file.close();
if (!pack_png_result) {
return png_invalid(); // pack.png invalid
}
} else {
return png_invalid(); // pack.png does not exists or is not a valid file.
}
}
case ResourceType::ZIPFILE:
{
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return false; // can't open zip file
QuaZipFile file(&zip);
if (zip.setCurrentFile("pack.png")) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
return png_invalid();
}
auto data = file.readAll();
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
file.close();
if (!pack_png_result) {
return png_invalid(); // pack.png invalid
}
} else {
return png_invalid(); // could not set pack.mcmeta as current file.
}
}
default:
qWarning() << "Invalid type for resource pack parse task!";
return false;
}
}
bool validate(QFileInfo file) bool validate(QFileInfo file)
{ {
ResourcePack rp{ file }; ResourcePack rp{ file };

View File

@ -35,7 +35,10 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Ful
bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data); bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data); bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data);
/// processes ONLY the pack.png (rest of the pack may be invalid)
bool processPackPNG(const ResourcePack& pack);
/** Checks whether a file is valid as a resource pack or not. */ /** Checks whether a file is valid as a resource pack or not. */
bool validate(QFileInfo file); bool validate(QFileInfo file);

View File

@ -44,11 +44,7 @@ static const QMap<PackedResourceType, QString> s_packed_type_names = {
namespace ResourceUtils { namespace ResourceUtils {
PackedResourceType identify(QFileInfo file){ PackedResourceType identify(QFileInfo file){
if (file.exists() && file.isFile()) { if (file.exists() && file.isFile()) {
if (ModUtils::validate(file)) { if (ResourcePackUtils::validate(file)) {
// mods can contain resource and data packs so they must be tested first
qDebug() << file.fileName() << "is a mod";
return PackedResourceType::Mod;
} else if (ResourcePackUtils::validate(file)) {
qDebug() << file.fileName() << "is a resource pack"; qDebug() << file.fileName() << "is a resource pack";
return PackedResourceType::ResourcePack; return PackedResourceType::ResourcePack;
} else if (TexturePackUtils::validate(file)) { } else if (TexturePackUtils::validate(file)) {
@ -57,6 +53,9 @@ PackedResourceType identify(QFileInfo file){
} else if (DataPackUtils::validate(file)) { } else if (DataPackUtils::validate(file)) {
qDebug() << file.fileName() << "is a data pack"; qDebug() << file.fileName() << "is a data pack";
return PackedResourceType::DataPack; return PackedResourceType::DataPack;
} else if (ModUtils::validate(file)) {
qDebug() << file.fileName() << "is a mod";
return PackedResourceType::Mod;
} else if (WorldSaveUtils::validate(file)) { } else if (WorldSaveUtils::validate(file)) {
qDebug() << file.fileName() << "is a world save"; qDebug() << file.fileName() << "is a world save";
return PackedResourceType::WorldSave; return PackedResourceType::WorldSave;

View File

@ -131,6 +131,7 @@ bool processZIP(TexturePack& pack, ProcessingLevel level)
bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data)); bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
file.close(); file.close();
zip.close();
if (!packPNG_result) { if (!packPNG_result) {
return false; return false;
} }
@ -147,7 +148,7 @@ bool processPackTXT(TexturePack& pack, QByteArray&& raw_data)
return true; return true;
} }
bool processPackPNG(TexturePack& pack, QByteArray&& raw_data) bool processPackPNG(const TexturePack& pack, QByteArray&& raw_data)
{ {
auto img = QImage::fromData(raw_data); auto img = QImage::fromData(raw_data);
if (!img.isNull()) { if (!img.isNull()) {
@ -159,6 +160,70 @@ bool processPackPNG(TexturePack& pack, QByteArray&& raw_data)
return true; return true;
} }
bool processPackPNG(const TexturePack& pack)
{
auto png_invalid = [&pack]() {
qWarning() << "Texture pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
return false;
};
switch (pack.type()) {
case ResourceType::FOLDER:
{
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
if (image_file_info.exists() && image_file_info.isFile()) {
QFile pack_png_file(image_file_info.filePath());
if (!pack_png_file.open(QIODevice::ReadOnly))
return png_invalid(); // can't open pack.png file
auto data = pack_png_file.readAll();
bool pack_png_result = TexturePackUtils::processPackPNG(pack, std::move(data));
pack_png_file.close();
if (!pack_png_result) {
return png_invalid(); // pack.png invalid
}
} else {
return png_invalid(); // pack.png does not exists or is not a valid file.
}
}
case ResourceType::ZIPFILE:
{
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return false; // can't open zip file
QuaZipFile file(&zip);
if (zip.setCurrentFile("pack.png")) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
return png_invalid();
}
auto data = file.readAll();
bool pack_png_result = TexturePackUtils::processPackPNG(pack, std::move(data));
file.close();
if (!pack_png_result) {
zip.close();
return png_invalid(); // pack.png invalid
}
} else {
zip.close();
return png_invalid(); // could not set pack.mcmeta as current file.
}
}
default:
qWarning() << "Invalid type for resource pack parse task!";
return false;
}
}
bool validate(QFileInfo file) bool validate(QFileInfo file)
{ {
TexturePack rp{ file }; TexturePack rp{ file };

View File

@ -36,7 +36,10 @@ bool processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full
bool processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processPackTXT(TexturePack& pack, QByteArray&& raw_data); bool processPackTXT(TexturePack& pack, QByteArray&& raw_data);
bool processPackPNG(TexturePack& pack, QByteArray&& raw_data); bool processPackPNG(const TexturePack& pack, QByteArray&& raw_data);
/// processes ONLY the pack.png (rest of the pack may be invalid)
bool processPackPNG(const TexturePack& pack);
/** Checks whether a file is valid as a texture pack or not. */ /** Checks whether a file is valid as a texture pack or not. */
bool validate(QFileInfo file); bool validate(QFileInfo file);

View File

@ -103,7 +103,7 @@ void ModFolderLoadTask::executeTask()
while (iter.hasNext()) { while (iter.hasNext()) {
auto mod = iter.next().value(); auto mod = iter.next().value();
if (mod->status() == ModStatus::NotInstalled) { if (mod->status() == ModStatus::NotInstalled) {
mod->destroy(m_index_dir, false, false); mod->destroy(m_index_dir, false);
iter.remove(); iter.remove();
} }
} }

View File

@ -145,8 +145,7 @@ void EnsureMetadataTask::executeTask()
connect(project_task.get(), &Task::finished, this, [=] { connect(project_task.get(), &Task::finished, this, [=] {
invalidade_leftover(); invalidade_leftover();
project_task->deleteLater(); project_task->deleteLater();
if (m_current_task) m_current_task = nullptr;
m_current_task.reset();
}); });
m_current_task = project_task; m_current_task = project_task;
@ -155,8 +154,7 @@ void EnsureMetadataTask::executeTask()
connect(version_task.get(), &Task::finished, [=] { connect(version_task.get(), &Task::finished, [=] {
version_task->deleteLater(); version_task->deleteLater();
if (m_current_task) m_current_task = nullptr;
m_current_task.reset();
}); });
if (m_mods.size() > 1) if (m_mods.size() > 1)

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -33,6 +34,8 @@ enum class ResourceProvider { MODRINTH, FLAME };
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK }; enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN };
class ProviderCapabilities { class ProviderCapabilities {
public: public:
auto name(ResourceProvider) -> const char*; auto name(ResourceProvider) -> const char*;
@ -52,6 +55,12 @@ struct DonationData {
QString url; QString url;
}; };
struct Dependency {
QVariant addonId;
DependencyType type;
QString version;
};
struct IndexedVersion { struct IndexedVersion {
QVariant addonId; QVariant addonId;
QVariant fileId; QVariant fileId;
@ -66,6 +75,7 @@ struct IndexedVersion {
QString hash; QString hash;
bool is_preferred = true; bool is_preferred = true;
QString changelog; QString changelog;
QList<Dependency> dependencies;
// For internal use, not provided by APIs // For internal use, not provided by APIs
bool is_currently_selected = false; bool is_currently_selected = false;
@ -119,6 +129,22 @@ struct IndexedPack {
} }
}; };
struct OverrideDep {
QString quilt;
QString fabric;
QString slug;
ModPlatform::ResourceProvider provider;
};
inline auto getOverrideDeps() -> QList<OverrideDep>
{
return { { "634179", "306612", "API", ModPlatform::ResourceProvider::FLAME },
{ "720410", "308769", "KotlinLibraries", ModPlatform::ResourceProvider::FLAME },
{ "qvIfYCYJ", "P7dR8mSH", "API", ModPlatform::ResourceProvider::MODRINTH },
{ "lwVhp9o5", "Ha28R6CL", "KotlinLibraries", ModPlatform::ResourceProvider::MODRINTH } };
};
} // namespace ModPlatform } // namespace ModPlatform
Q_DECLARE_METATYPE(ModPlatform::IndexedPack) Q_DECLARE_METATYPE(ModPlatform::IndexedPack)

View File

@ -111,6 +111,16 @@ class ResourceAPI {
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed; std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
}; };
struct DependencySearchArgs {
ModPlatform::Dependency dependency;
Version mcVersion;
ModLoaderTypes loader;
};
struct DependencySearchCallbacks {
std::function<void(QJsonDocument&, const ModPlatform::Dependency&)> on_succeed;
};
public: public:
/** Gets a list of available sorting methods for this API. */ /** Gets a list of available sorting methods for this API. */
[[nodiscard]] virtual auto getSortingMethods() const -> QList<SortingMethod> = 0; [[nodiscard]] virtual auto getSortingMethods() const -> QList<SortingMethod> = 0;
@ -143,6 +153,12 @@ class ResourceAPI {
return nullptr; return nullptr;
} }
[[nodiscard]] virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, DependencySearchCallbacks&&) const
{
qWarning() << "TODO";
return nullptr;
}
static auto getModLoaderString(ModLoaderType type) -> const QString static auto getModLoaderString(ModLoaderType type) -> const QString
{ {
switch (type) { switch (type) {

View File

@ -21,10 +21,6 @@ bool Flame::FileResolvingTask::abort()
void Flame::FileResolvingTask::executeTask() void Flame::FileResolvingTask::executeTask()
{ {
if (m_toProcess.files.isEmpty()) { // no file to resolve so leave it empty and emit success immediately
emitSucceeded();
return;
}
setStatus(tr("Resolving mod IDs...")); setStatus(tr("Resolving mod IDs..."));
setProgress(0, 3); setProgress(0, 3);
m_dljob.reset(new NetJob("Mod id resolver", m_network)); m_dljob.reset(new NetJob("Mod id resolver", m_network));
@ -132,13 +128,12 @@ void Flame::FileResolvingTask::netJobFinished()
m_checkJob->start(); m_checkJob->start();
} }
void Flame::FileResolvingTask::modrinthCheckFinished() void Flame::FileResolvingTask::modrinthCheckFinished() {
{
setProgress(2, 3); setProgress(2, 3);
qDebug() << "Finished with blocked mods : " << blockedProjects.size(); qDebug() << "Finished with blocked mods : " << blockedProjects.size();
for (auto it = blockedProjects.keyBegin(); it != blockedProjects.keyEnd(); it++) { for (auto it = blockedProjects.keyBegin(); it != blockedProjects.keyEnd(); it++) {
auto& out = *it; auto &out = *it;
auto bytes = blockedProjects[out]; auto bytes = blockedProjects[out];
if (!out->resolved) { if (!out->resolved) {
continue; continue;
@ -158,13 +153,15 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
out->resolved = false; out->resolved = false;
} }
} }
// copy to an output list and filter out projects found on modrinth //copy to an output list and filter out projects found on modrinth
auto block = std::make_shared<QList<File*>>(); auto block = std::make_shared<QList<File*>>();
auto it = blockedProjects.keys(); auto it = blockedProjects.keys();
std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File* f) { return !f->resolved; }); std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) {
// Display not found mods early return !f->resolved;
});
//Display not found mods early
if (!block->empty()) { if (!block->empty()) {
// blocked mods found, we need the slug for displaying.... we need another job :D ! //blocked mods found, we need the slug for displaying.... we need another job :D !
m_slugJob.reset(new NetJob("Slug Job", m_network)); m_slugJob.reset(new NetJob("Slug Job", m_network));
int index = 0; int index = 0;
for (auto mod : *block) { for (auto mod : *block) {
@ -176,8 +173,8 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() { QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() {
auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done
auto json = QJsonDocument::fromJson(*output); auto json = QJsonDocument::fromJson(*output);
auto base = auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"),
Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json), "data"), "links"), "websiteUrl"); "websiteUrl");
auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId)); auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId));
mod->websiteUrl = link; mod->websiteUrl = link;
}); });

View File

@ -4,8 +4,10 @@
#pragma once #pragma once
#include <algorithm>
#include <memory> #include <memory>
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "modplatform/helpers/NetworkResourceAPI.h" #include "modplatform/helpers/NetworkResourceAPI.h"
class FlameAPI : public NetworkResourceAPI { class FlameAPI : public NetworkResourceAPI {
@ -21,8 +23,6 @@ class FlameAPI : public NetworkResourceAPI {
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override; [[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (Forge | Fabric | Quilt); }
private: private:
static int getClassId(ModPlatform::ResourceType type) static int getClassId(ModPlatform::ResourceType type)
{ {
@ -77,14 +77,48 @@ class FlameAPI : public NetworkResourceAPI {
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override [[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
{ {
QString url{ QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.pack.addonId.toString()) }; auto addonId = args.pack.addonId.toString();
QString url{ QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(addonId) };
QStringList get_parameters; QStringList get_parameters;
if (args.mcVersions.has_value()) if (args.mcVersions.has_value())
get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString())); get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString()));
if (args.loaders.has_value())
get_parameters.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value()))); if (args.loaders.has_value()) {
int mappedModLoader = getMappedModLoader(args.loaders.value());
if (args.loaders.value() & Quilt) {
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
});
if (over != overide.cend()) {
mappedModLoader = 5;
}
}
get_parameters.append(QString("modLoaderType=%1").arg(mappedModLoader));
}
return url + get_parameters.join('&'); return url + get_parameters.join('&');
}; };
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{
auto mappedModLoader = getMappedModLoader(args.loader);
auto addonId = args.dependency.addonId.toString();
if (args.loader & Quilt) {
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
});
if (over != overide.cend()) {
mappedModLoader = 5;
}
}
return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2&modLoaderType=%3")
.arg(addonId)
.arg(args.mcVersion.toString())
.arg(mappedModLoader);
};
}; };

View File

@ -470,9 +470,8 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
switch (result.type) { switch (result.type) {
case Flame::File::Type::Folder: { case Flame::File::Type::Folder: {
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
// fallthrough intentional, we treat these as plain old mods and dump them wherever. // fall-through intentional, we treat these as plain old mods and dump them wherever.
} }
/* fallthrough */
case Flame::File::Type::SingleFile: case Flame::File::Type::SingleFile:
case Flame::File::Type::Mod: { case Flame::File::Type::Mod: {
if (!result.url.isEmpty()) { if (!result.url.isEmpty()) {
@ -563,8 +562,6 @@ void FlameCreationTask::validateZIPResouces()
if (FS::move(localPath, destPath)) { if (FS::move(localPath, destPath)) {
return destPath; return destPath;
} }
} else {
qDebug() << "Target folder of" << fileName << "is correct at" << targetFolder;
} }
return localPath; return localPath;
}; };
@ -586,9 +583,6 @@ void FlameCreationTask::validateZIPResouces()
QString worldPath; QString worldPath;
switch (type) { switch (type) {
case PackedResourceType::Mod :
validatePath(fileName, targetFolder, "mods");
break;
case PackedResourceType::ResourcePack : case PackedResourceType::ResourcePack :
validatePath(fileName, targetFolder, "resourcepacks"); validatePath(fileName, targetFolder, "resourcepacks");
break; break;
@ -598,6 +592,9 @@ void FlameCreationTask::validateZIPResouces()
case PackedResourceType::DataPack : case PackedResourceType::DataPack :
validatePath(fileName, targetFolder, "datapacks"); validatePath(fileName, targetFolder, "datapacks");
break; break;
case PackedResourceType::Mod :
validatePath(fileName, targetFolder, "mods");
break;
case PackedResourceType::ShaderPack : case PackedResourceType::ShaderPack :
// in theroy flame API can't do this but who knows, that *may* change ? // in theroy flame API can't do this but who knows, that *may* change ?
// better to handle it if it *does* occure in the future // better to handle it if it *does* occure in the future

View File

@ -39,15 +39,15 @@ void FlameMod::loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj)
auto links_obj = Json::ensureObject(obj, "links"); auto links_obj = Json::ensureObject(obj, "links");
pack.extraData.issuesUrl = Json::ensureString(links_obj, "issuesUrl"); pack.extraData.issuesUrl = Json::ensureString(links_obj, "issuesUrl");
if(pack.extraData.issuesUrl.endsWith('/')) if (pack.extraData.issuesUrl.endsWith('/'))
pack.extraData.issuesUrl.chop(1); pack.extraData.issuesUrl.chop(1);
pack.extraData.sourceUrl = Json::ensureString(links_obj, "sourceUrl"); pack.extraData.sourceUrl = Json::ensureString(links_obj, "sourceUrl");
if(pack.extraData.sourceUrl.endsWith('/')) if (pack.extraData.sourceUrl.endsWith('/'))
pack.extraData.sourceUrl.chop(1); pack.extraData.sourceUrl.chop(1);
pack.extraData.wikiUrl = Json::ensureString(links_obj, "wikiUrl"); pack.extraData.wikiUrl = Json::ensureString(links_obj, "wikiUrl");
if(pack.extraData.wikiUrl.endsWith('/')) if (pack.extraData.wikiUrl.endsWith('/'))
pack.extraData.wikiUrl.chop(1); pack.extraData.wikiUrl.chop(1);
if (!pack.extraData.body.isEmpty()) if (!pack.extraData.body.isEmpty())
@ -56,7 +56,7 @@ void FlameMod::loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj)
void FlameMod::loadBody(ModPlatform::IndexedPack& pack, QJsonObject& obj) void FlameMod::loadBody(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{ {
pack.extraData.body = api.getModDescription(pack.addonId.toInt()); pack.extraData.body = api.getModDescription(pack.addonId.toInt());
if (!pack.extraData.issuesUrl.isEmpty() || !pack.extraData.sourceUrl.isEmpty() || !pack.extraData.wikiUrl.isEmpty()) if (!pack.extraData.issuesUrl.isEmpty() || !pack.extraData.sourceUrl.isEmpty() || !pack.extraData.wikiUrl.isEmpty())
pack.extraDataLoaded = true; pack.extraDataLoaded = true;
@ -64,12 +64,12 @@ void FlameMod::loadBody(ModPlatform::IndexedPack& pack, QJsonObject& obj)
static QString enumToString(int hash_algorithm) static QString enumToString(int hash_algorithm)
{ {
switch(hash_algorithm){ switch (hash_algorithm) {
default: default:
case 1: case 1:
return "sha1"; return "sha1";
case 2: case 2:
return "md5"; return "md5";
} }
} }
@ -86,10 +86,10 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
auto obj = versionIter.toObject(); auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj); auto file = loadIndexedPackVersion(obj);
if(!file.addonId.isValid()) if (!file.addonId.isValid())
file.addonId = pack.addonId; file.addonId = pack.addonId;
if(file.fileId.isValid()) // Heuristic to check if the returned value is valid if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
unsortedVersions.append(file); unsortedVersions.append(file);
} }
@ -136,8 +136,61 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
} }
} }
if(load_changelog) auto dependencies = Json::ensureArray(obj, "dependencies");
for (auto d : dependencies) {
auto dep = Json::ensureObject(d);
ModPlatform::Dependency dependency;
dependency.addonId = Json::requireInteger(dep, "modId");
switch (Json::requireInteger(dep, "relationType")) {
case 1: // EmbeddedLibrary
dependency.type = ModPlatform::DependencyType::EMBEDDED;
break;
case 2: // OptionalDependency
dependency.type = ModPlatform::DependencyType::OPTIONAL;
break;
case 3: // RequiredDependency
dependency.type = ModPlatform::DependencyType::REQUIRED;
break;
case 4: // Tool
dependency.type = ModPlatform::DependencyType::TOOL;
break;
case 5: // Incompatible
dependency.type = ModPlatform::DependencyType::INCOMPATIBLE;
break;
case 6: // Include
dependency.type = ModPlatform::DependencyType::INCLUDE;
break;
default:
dependency.type = ModPlatform::DependencyType::UNKNOWN;
break;
}
file.dependencies.append(dependency);
}
if (load_changelog)
file.changelog = api.getModFileChangelog(file.addonId.toInt(), file.fileId.toInt()); file.changelog = api.getModFileChangelog(file.addonId.toInt(), file.fileId.toInt());
return file; return file;
} }
ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr)
{
QVector<ModPlatform::IndexedVersion> versions;
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj);
if (!file.addonId.isValid())
file.addonId = m.addonId;
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
versions.append(file);
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
// dates are in RFC 3339 format
return a.date > b.date;
};
std::sort(versions.begin(), versions.end(), orderSortPredicate);
return versions.front();
};

View File

@ -6,8 +6,8 @@
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "BaseInstance.h"
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include "BaseInstance.h"
namespace FlameMod { namespace FlameMod {
@ -19,5 +19,5 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
const shared_qobject_ptr<QNetworkAccessManager>& network, const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst); const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion; auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
} // namespace FlameMod } // namespace FlameMod

View File

@ -4,6 +4,7 @@
#include <QMetaType> #include <QMetaType>
#include <QString> #include <QString>
#include <QVector> #include <QVector>
#include "modplatform/ModIndex.h"
namespace Flame { namespace Flame {
@ -27,8 +28,7 @@ struct ModpackExtra {
QString sourceUrl; QString sourceUrl;
}; };
struct IndexedPack struct IndexedPack {
{
int addonId; int addonId;
QString name; QString name;
QString description; QString description;
@ -43,9 +43,9 @@ struct IndexedPack
ModpackExtra extra; ModpackExtra extra;
}; };
void loadIndexedPack(IndexedPack & m, QJsonObject & obj); void loadIndexedPack(IndexedPack& m, QJsonObject& obj);
void loadIndexedInfo(IndexedPack&, QJsonObject&); void loadIndexedInfo(IndexedPack&, QJsonObject&);
void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr); void loadIndexedPackVersions(IndexedPack& m, QJsonArray& arr);
} } // namespace Flame
Q_DECLARE_METATYPE(Flame::IndexedPack) Q_DECLARE_METATYPE(Flame::IndexedPack)

View File

@ -76,8 +76,13 @@ bool Flame::File::parseFromObject(const QJsonObject& obj, bool throw_on_blocked
// It is also optional // It is also optional
type = File::Type::SingleFile; type = File::Type::SingleFile;
targetFolder = "mods"; if (fileName.endsWith(".zip")) {
// this is probably a resource pack
targetFolder = "resourcepacks";
} else {
// this is probably a mod, dunno what else could modpacks download
targetFolder = "mods";
}
// get the hash // get the hash
hash = QString(); hash = QString();
auto hashes = Json::ensureArray(obj, "hashes"); auto hashes = Json::ensureArray(obj, "hashes");

View File

@ -117,3 +117,32 @@ Task::Ptr NetworkResourceAPI::getProject(QString addonId, std::shared_ptr<QByteA
return netJob; return netJob;
} }
Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args, DependencySearchCallbacks&& callbacks) const
{
auto versions_url_optional = getDependencyURL(args);
if (!versions_url_optional.has_value())
return nullptr;
auto versions_url = versions_url_optional.value();
auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network());
auto response = std::make_shared<QByteArray>();
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
QObject::connect(netJob.get(), &NetJob::succeeded, [=] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << *response;
return;
}
callbacks.on_succeed(doc, args.dependency);
});
return netJob;
};

View File

@ -15,9 +15,11 @@ class NetworkResourceAPI : public ResourceAPI {
Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const override; Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const override;
Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override; Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override;
Task::Ptr getDependencyVersion(DependencySearchArgs&&, DependencySearchCallbacks&&) const override;
protected: protected:
[[nodiscard]] virtual auto getSearchURL(SearchArgs const& args) const -> std::optional<QString> = 0; [[nodiscard]] virtual auto getSearchURL(SearchArgs const& args) const -> std::optional<QString> = 0;
[[nodiscard]] virtual auto getInfoURL(QString const& id) const -> std::optional<QString> = 0; [[nodiscard]] virtual auto getInfoURL(QString const& id) const -> std::optional<QString> = 0;
[[nodiscard]] virtual auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional<QString> = 0; [[nodiscard]] virtual auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional<QString> = 0;
[[nodiscard]] virtual auto getDependencyURL(DependencySearchArgs const& args) const -> std::optional<QString> = 0;
}; };

View File

@ -37,16 +37,16 @@
#include <QtConcurrent> #include <QtConcurrent>
#include "MMCZip.h"
#include "BaseInstance.h" #include "BaseInstance.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "MMCZip.h" #include "settings/INISettingsObject.h"
#include "minecraft/GradleSpecifier.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "settings/INISettingsObject.h" #include "minecraft/GradleSpecifier.h"
#include "Application.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Application.h"
namespace LegacyFTB { namespace LegacyFTB {
@ -65,7 +65,6 @@ void PackInstallTask::executeTask()
void PackInstallTask::downloadPack() void PackInstallTask::downloadPack()
{ {
setStatus(tr("Downloading zip for %1").arg(m_pack.name)); setStatus(tr("Downloading zip for %1").arg(m_pack.name));
setProgress(1, 4);
setAbortable(false); setAbortable(false);
archivePath = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file); archivePath = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file);
@ -79,10 +78,11 @@ void PackInstallTask::downloadPack()
} }
netJobContainer->addNetAction(Net::Download::makeFile(url, archivePath)); netJobContainer->addNetAction(Net::Download::makeFile(url, archivePath));
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip); connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed); connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress);
connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress);
connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::emitAborted); connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted);
netJobContainer->start(); netJobContainer->start();
@ -90,6 +90,27 @@ void PackInstallTask::downloadPack()
progress(1, 4); progress(1, 4);
} }
void PackInstallTask::onDownloadSucceeded()
{
unzip();
}
void PackInstallTask::onDownloadFailed(QString reason)
{
emitFailed(reason);
}
void PackInstallTask::onDownloadProgress(qint64 current, qint64 total)
{
progress(current, total * 4);
setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10));
}
void PackInstallTask::onDownloadAborted()
{
emitAborted();
}
void PackInstallTask::unzip() void PackInstallTask::unzip()
{ {
setStatus(tr("Extracting modpack")); setStatus(tr("Extracting modpack"));
@ -99,17 +120,16 @@ void PackInstallTask::unzip()
QDir extractDir(m_stagingPath); QDir extractDir(m_stagingPath);
m_packZip.reset(new QuaZip(archivePath)); m_packZip.reset(new QuaZip(archivePath));
if (!m_packZip->open(QuaZip::mdUnzip)) { if(!m_packZip->open(QuaZip::mdUnzip))
{
emitFailed(tr("Failed to open modpack file %1!").arg(archivePath)); emitFailed(tr("Failed to open modpack file %1!").arg(archivePath));
return; return;
} }
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath, m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/unzip");
extractDir.absolutePath() + "/unzip");
#else #else
m_extractFuture = m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip");
QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip");
#endif #endif
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished); connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished);
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled); connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled);
@ -131,9 +151,11 @@ void PackInstallTask::install()
setStatus(tr("Installing modpack")); setStatus(tr("Installing modpack"));
progress(3, 4); progress(3, 4);
QDir unzipMcDir(m_stagingPath + "/unzip/minecraft"); QDir unzipMcDir(m_stagingPath + "/unzip/minecraft");
if (unzipMcDir.exists()) { if(unzipMcDir.exists())
// ok, found minecraft dir, move contents to instance dir {
if (!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) { //ok, found minecraft dir, move contents to instance dir
if(!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft"))
{
emitFailed(tr("Failed to move unzipped Minecraft!")); emitFailed(tr("Failed to move unzipped Minecraft!"));
return; return;
} }
@ -150,20 +172,23 @@ void PackInstallTask::install()
bool fallback = true; bool fallback = true;
// handle different versions //handle different versions
QFile packJson(m_stagingPath + "/.minecraft/pack.json"); QFile packJson(m_stagingPath + "/.minecraft/pack.json");
QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods"); QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods");
if (packJson.exists()) { if(packJson.exists())
{
packJson.open(QIODevice::ReadOnly | QIODevice::Text); packJson.open(QIODevice::ReadOnly | QIODevice::Text);
QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll()); QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll());
packJson.close(); packJson.close();
// we only care about the libs //we only care about the libs
QJsonArray libs = doc.object().value("libraries").toArray(); QJsonArray libs = doc.object().value("libraries").toArray();
foreach (const QJsonValue& value, libs) { foreach (const QJsonValue &value, libs)
{
QString nameValue = value.toObject().value("name").toString(); QString nameValue = value.toObject().value("name").toString();
if (!nameValue.startsWith("net.minecraftforge")) { if(!nameValue.startsWith("net.minecraftforge"))
{
continue; continue;
} }
@ -174,13 +199,16 @@ void PackInstallTask::install()
fallback = false; fallback = false;
break; break;
} }
} }
if (jarmodDir.exists()) { if(jarmodDir.exists())
{
qDebug() << "Found jarmods, installing..."; qDebug() << "Found jarmods, installing...";
QStringList jarmods; QStringList jarmods;
for (auto info : jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) { for (auto info: jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files))
{
qDebug() << "Jarmod:" << info.fileName(); qDebug() << "Jarmod:" << info.fileName();
jarmods.push_back(info.absoluteFilePath()); jarmods.push_back(info.absoluteFilePath());
} }
@ -189,11 +217,12 @@ void PackInstallTask::install()
fallback = false; fallback = false;
} }
// just nuke unzip directory, it s not needed anymore //just nuke unzip directory, it s not needed anymore
FS::deletePath(m_stagingPath + "/unzip"); FS::deletePath(m_stagingPath + "/unzip");
if (fallback) { if(fallback)
// TODO: Some fallback mechanism... or just keep failing! {
//TODO: Some fallback mechanism... or just keep failing!
emitFailed(tr("No installation method found!")); emitFailed(tr("No installation method found!"));
return; return;
} }
@ -203,7 +232,8 @@ void PackInstallTask::install()
progress(4, 4); progress(4, 4);
instance.setName(name()); instance.setName(name());
if (m_instIcon == "default") { if(m_instIcon == "default")
{
m_instIcon = "ftb_logo"; m_instIcon = "ftb_logo";
} }
instance.setIconKey(m_instIcon); instance.setIconKey(m_instIcon);
@ -222,4 +252,4 @@ bool PackInstallTask::abort()
return InstanceTask::abort(); return InstanceTask::abort();
} }
} // namespace LegacyFTB }

View File

@ -1,12 +1,12 @@
#pragma once #pragma once
#include "InstanceTask.h"
#include "net/NetJob.h"
#include <quazip/quazip.h> #include <quazip/quazip.h>
#include <quazip/quazipdir.h> #include <quazip/quazipdir.h>
#include "InstanceTask.h"
#include "PackHelpers.h"
#include "meta/Index.h" #include "meta/Index.h"
#include "meta/Version.h" #include "meta/Version.h"
#include "meta/VersionList.h" #include "meta/VersionList.h"
#include "net/NetJob.h" #include "PackHelpers.h"
#include "net/NetJob.h" #include "net/NetJob.h"
@ -14,31 +14,36 @@
namespace LegacyFTB { namespace LegacyFTB {
class PackInstallTask : public InstanceTask { class PackInstallTask : public InstanceTask
{
Q_OBJECT Q_OBJECT
public: public:
explicit PackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, Modpack pack, QString version); explicit PackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, Modpack pack, QString version);
virtual ~PackInstallTask() {} virtual ~PackInstallTask(){}
bool canAbort() const override { return true; } bool canAbort() const override { return true; }
bool abort() override; bool abort() override;
protected: protected:
//! Entry point for tasks. //! Entry point for tasks.
virtual void executeTask() override; virtual void executeTask() override;
private: private:
void downloadPack(); void downloadPack();
void unzip(); void unzip();
void install(); void install();
private slots: private slots:
void onDownloadSucceeded();
void onDownloadFailed(QString reason);
void onDownloadProgress(qint64 current, qint64 total);
void onDownloadAborted();
void onUnzipFinished(); void onUnzipFinished();
void onUnzipCanceled(); void onUnzipCanceled();
private: /* data */ private: /* data */
shared_qobject_ptr<QNetworkAccessManager> m_network; shared_qobject_ptr<QNetworkAccessManager> m_network;
bool abortable = false; bool abortable = false;
std::unique_ptr<QuaZip> m_packZip; std::unique_ptr<QuaZip> m_packZip;
@ -51,4 +56,4 @@ class PackInstallTask : public InstanceTask {
QString m_version; QString m_version;
}; };
} // namespace LegacyFTB }

View File

@ -38,7 +38,7 @@ class ModrinthAPI : public NetworkResourceAPI {
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
{ {
QStringList l; QStringList l;
for (auto loader : { Forge, Fabric, Quilt, LiteLoader }) { for (auto loader : { Forge, Fabric, Quilt }) {
if (types & loader) { if (types & loader) {
l << getModLoaderString(loader); l << getModLoaderString(loader);
} }
@ -51,8 +51,7 @@ class ModrinthAPI : public NetworkResourceAPI {
static auto getModLoaderFilters(ModLoaderTypes types) -> const QString static auto getModLoaderFilters(ModLoaderTypes types) -> const QString
{ {
QStringList l; QStringList l;
for (auto loader : getModLoaderStrings(types)) for (auto loader : getModLoaderStrings(types)) {
{
l << QString("\"categories:%1\"").arg(loader); l << QString("\"categories:%1\"").arg(loader);
} }
return l.join(','); return l.join(',');
@ -93,7 +92,7 @@ class ModrinthAPI : public NetworkResourceAPI {
{ {
if (args.loaders.has_value()) { if (args.loaders.has_value()) {
if (!validateModLoaders(args.loaders.value())) { if (!validateModLoaders(args.loaders.value())) {
qWarning() << "Modrinth - or our interface - does not support any the provided mod loaders!"; qWarning() << "Modrinth only have Forge and Fabric-compatible mods!";
return {}; return {};
} }
} }
@ -135,13 +134,22 @@ class ModrinthAPI : public NetworkResourceAPI {
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString
{ {
QString s; QString s;
for(auto& ver : mcVersions){ for (auto& ver : mcVersions) {
s += QString("\"versions:%1\",").arg(ver.toString()); s += QString("\"versions:%1\",").arg(ver.toString());
} }
s.remove(s.length() - 1, 1); //remove last comma s.remove(s.length() - 1, 1); // remove last comma
return s.isEmpty() ? QString() : s; return s.isEmpty() ? QString() : s;
} }
static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (Forge | Fabric | Quilt | LiteLoader); } inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool { return loaders & (Forge | Fabric | Quilt); }
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{
return args.dependency.version.length() != 0 ? QString("%1/version/%2").arg(BuildConfig.MODRINTH_PROD_URL, args.dependency.version)
: QString("%1/project/%2/version?game_versions=[\"%3\"]&loaders=[\"%4\"]")
.arg(BuildConfig.MODRINTH_PROD_URL)
.arg(args.dependency.addonId.toString())
.arg(args.mcVersion.toString())
.arg(getModLoaderStrings(args.loader).join("\",\""));
};
}; };

View File

@ -263,13 +263,13 @@ void ModrinthPackExportTask::finish()
QByteArray ModrinthPackExportTask::generateIndex() QByteArray ModrinthPackExportTask::generateIndex()
{ {
QJsonObject out; QJsonObject obj;
out["formatVersion"] = 1; obj["formatVersion"] = 1;
out["game"] = "minecraft"; obj["game"] = "minecraft";
out["name"] = name; obj["name"] = name;
out["versionId"] = version; obj["versionId"] = version;
if (!summary.isEmpty()) if (!summary.isEmpty())
out["summary"] = summary; obj["summary"] = summary;
if (mcInstance) { if (mcInstance) {
auto profile = mcInstance->getPackProfile(); auto profile = mcInstance->getPackProfile();
@ -290,40 +290,30 @@ QByteArray ModrinthPackExportTask::generateIndex()
if (forge != nullptr) if (forge != nullptr)
dependencies["forge"] = forge->m_version; dependencies["forge"] = forge->m_version;
out["dependencies"] = dependencies; obj["dependencies"] = dependencies;
} }
QJsonArray filesOut; QJsonArray files;
for (auto iterator = resolvedFiles.constBegin(); iterator != resolvedFiles.constEnd(); iterator++) { QMapIterator<QString, ResolvedFile> iterator(resolvedFiles);
QJsonObject fileOut; while (iterator.hasNext()) {
iterator.next();
QString path = iterator.key();
const ResolvedFile& value = iterator.value(); const ResolvedFile& value = iterator.value();
// detect disabled mod QJsonObject file;
const QFileInfo pathInfo(path); file["path"] = iterator.key();
if (pathInfo.suffix() == "disabled") { file["downloads"] = QJsonArray({ iterator.value().url });
// rename it
path = pathInfo.dir().filePath(pathInfo.completeBaseName());
// ...and make it optional
QJsonObject env;
env["client"] = "optional";
env["server"] = "optional";
fileOut["env"] = env;
}
fileOut["path"] = path;
fileOut["downloads"] = QJsonArray{ iterator.value().url };
QJsonObject hashes; QJsonObject hashes;
hashes["sha1"] = value.sha1; hashes["sha1"] = value.sha1;
hashes["sha512"] = value.sha512; hashes["sha512"] = value.sha512;
fileOut["hashes"] = hashes;
fileOut["fileSize"] = value.size; file["hashes"] = hashes;
filesOut << fileOut; file["fileSize"] = value.size;
files << file;
} }
out["files"] = filesOut; obj["files"] = files;
return QJsonDocument(out).toJson(QJsonDocument::Compact); return QJsonDocument(obj).toJson(QJsonDocument::Compact);
} }

View File

@ -22,7 +22,7 @@
#include "Json.h" #include "Json.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "net/NetJob.h" #include "modplatform/ModIndex.h"
static ModrinthAPI api; static ModrinthAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps; static ModPlatform::ProviderCapabilities ProviderCaps;
@ -140,6 +140,28 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
file.version_number = Json::requireString(obj, "version_number"); file.version_number = Json::requireString(obj, "version_number");
file.changelog = Json::requireString(obj, "changelog"); file.changelog = Json::requireString(obj, "changelog");
auto dependencies = Json::ensureArray(obj, "dependencies");
for (auto d : dependencies) {
auto dep = Json::ensureObject(d);
ModPlatform::Dependency dependency;
dependency.addonId = Json::ensureString(dep, "project_id");
dependency.version = Json::ensureString(dep, "version_id");
auto depType = Json::requireString(dep, "dependency_type");
if (depType == "required")
dependency.type = ModPlatform::DependencyType::REQUIRED;
else if (depType == "optional")
dependency.type = ModPlatform::DependencyType::OPTIONAL;
else if (depType == "incompatible")
dependency.type = ModPlatform::DependencyType::INCOMPATIBLE;
else if (depType == "embedded")
dependency.type = ModPlatform::DependencyType::EMBEDDED;
else
dependency.type = ModPlatform::DependencyType::UNKNOWN;
file.dependencies.append(dependency);
}
auto files = Json::requireArray(obj, "files"); auto files = Json::requireArray(obj, "files");
int i = 0; int i = 0;
@ -195,3 +217,22 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
return {}; return {};
} }
auto Modrinth::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
{
QVector<ModPlatform::IndexedVersion> versions;
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj);
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
versions.append(file);
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
// dates are in RFC 3339 format
return a.date > b.date;
};
std::sort(versions.begin(), versions.end(), orderSortPredicate);
return versions.length() != 0 ? versions.front() : ModPlatform::IndexedVersion();
}

View File

@ -19,8 +19,8 @@
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "BaseInstance.h"
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include "BaseInstance.h"
namespace Modrinth { namespace Modrinth {
@ -31,5 +31,6 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
const shared_qobject_ptr<QNetworkAccessManager>& network, const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst); const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion; auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion;
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
} // namespace Modrinth } // namespace Modrinth

View File

@ -42,7 +42,6 @@
#include <QDir> #include <QDir>
#include <QLibraryInfo> #include <QLibraryInfo>
#include <QDebug> #include <QDebug>
#include <locale>
#include "FileSystem.h" #include "FileSystem.h"
#include "net/NetJob.h" #include "net/NetJob.h"
@ -455,7 +454,6 @@ QVariant TranslationsModel::data(const QModelIndex& index, int role) const
return QString("%1%").arg(lang.percentTranslated(), 3, 'f', 1); return QString("%1%").arg(lang.percentTranslated(), 3, 'f', 1);
} }
} }
qWarning("TranslationModel::data not implemented when role is DisplayRole");
} }
case Qt::ToolTipRole: case Qt::ToolTipRole:
{ {
@ -528,34 +526,34 @@ Language * TranslationsModel::findLanguage(const QString& key)
} }
} }
void TranslationsModel::setUseSystemLocale(bool useSystemLocale)
{
APPLICATION->settings()->set("UseSystemLocale", useSystemLocale);
QLocale::setDefault(QLocale(useSystemLocale ? QString::fromStdString(std::locale().name()) : defaultLangCode));
}
bool TranslationsModel::selectLanguage(QString key) bool TranslationsModel::selectLanguage(QString key)
{ {
QString& langCode = key; QString &langCode = key;
auto langPtr = findLanguage(key); auto langPtr = findLanguage(key);
if (langCode.isEmpty()) { if (langCode.isEmpty())
{
d->no_language_set = true; d->no_language_set = true;
} }
if (!langPtr) { if(!langPtr)
{
qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode; qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode;
langCode = defaultLangCode; langCode = defaultLangCode;
} else { }
else
{
langCode = langPtr->key; langCode = langPtr->key;
} }
// uninstall existing translators if there are any // uninstall existing translators if there are any
if (d->m_app_translator) { if (d->m_app_translator)
{
QCoreApplication::removeTranslator(d->m_app_translator.get()); QCoreApplication::removeTranslator(d->m_app_translator.get());
d->m_app_translator.reset(); d->m_app_translator.reset();
} }
if (d->m_qt_translator) { if (d->m_qt_translator)
{
QCoreApplication::removeTranslator(d->m_qt_translator.get()); QCoreApplication::removeTranslator(d->m_qt_translator.get());
d->m_qt_translator.reset(); d->m_qt_translator.reset();
} }
@ -565,9 +563,8 @@ bool TranslationsModel::selectLanguage(QString key)
* In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created. * In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created.
* This function is not reentrant. * This function is not reentrant.
*/ */
QLocale::setDefault( QLocale locale = QLocale(langCode);
QLocale(APPLICATION->settings()->get("UseSystemLocale").toBool() ? QString::fromStdString(std::locale().name()) : langCode)); QLocale::setDefault(locale);
// if it's the default UI language, finish // if it's the default UI language, finish
if(langCode == defaultLangCode) if(langCode == defaultLangCode)

View File

@ -20,16 +20,17 @@
struct Language; struct Language;
class TranslationsModel : public QAbstractListModel { class TranslationsModel : public QAbstractListModel
{
Q_OBJECT Q_OBJECT
public: public:
explicit TranslationsModel(QString path, QObject* parent = 0); explicit TranslationsModel(QString path, QObject *parent = 0);
virtual ~TranslationsModel(); virtual ~TranslationsModel();
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent) const override; int columnCount(const QModelIndex & parent) const override;
bool selectLanguage(QString key); bool selectLanguage(QString key);
void updateLanguage(QString key); void updateLanguage(QString key);
@ -37,27 +38,27 @@ class TranslationsModel : public QAbstractListModel {
QString selectedLanguage(); QString selectedLanguage();
void downloadIndex(); void downloadIndex();
void setUseSystemLocale(bool useSystemLocale);
private: private:
Language* findLanguage(const QString& key); Language *findLanguage(const QString & key);
void reloadLocalFiles(); void reloadLocalFiles();
void downloadTranslation(QString key); void downloadTranslation(QString key);
void downloadNext(); void downloadNext();
// hide copy constructor // hide copy constructor
TranslationsModel(const TranslationsModel&) = delete; TranslationsModel(const TranslationsModel &) = delete;
// hide assign op // hide assign op
TranslationsModel& operator=(const TranslationsModel&) = delete; TranslationsModel &operator=(const TranslationsModel &) = delete;
private slots: private slots:
void indexReceived(); void indexReceived();
void indexFailed(QString reason); void indexFailed(QString reason);
void dlFailed(QString reason); void dlFailed(QString reason);
void dlGood(); void dlGood();
void translationDirChanged(const QString& path); void translationDirChanged(const QString &path);
private: /* data */
private: /* data */
struct Private; struct Private;
std::unique_ptr<Private> d; std::unique_ptr<Private> d;
}; };

View File

@ -1279,17 +1279,7 @@ void MainWindow::globalSettingsClosed()
void MainWindow::on_actionEditInstance_triggered() void MainWindow::on_actionEditInstance_triggered()
{ {
APPLICATION->showInstanceWindow(m_selectedInstance);
if (!m_selectedInstance)
return;
if (m_selectedInstance->canEdit()) {
APPLICATION->showInstanceWindow(m_selectedInstance);
} else {
CustomMessageBox::selectable(this, tr("Instance not editable"),
tr("This instance is not editable. It may be broken, invalid, or too old. Check logs for details."),
QMessageBox::Critical)->show();
}
} }
void MainWindow::on_actionManageAccounts_triggered() void MainWindow::on_actionManageAccounts_triggered()

View File

@ -66,8 +66,6 @@ ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget* parent
auto prefix = QDir(instance->instanceRoot()).relativeFilePath(instance->gameRoot()); auto prefix = QDir(instance->instanceRoot()).relativeFilePath(instance->gameRoot());
proxyModel->ignoreFilesWithPath().insert({ FS::PathCombine(prefix, "logs"), FS::PathCombine(prefix, "crash-reports") }); proxyModel->ignoreFilesWithPath().insert({ FS::PathCombine(prefix, "logs"), FS::PathCombine(prefix, "crash-reports") });
proxyModel->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" }); proxyModel->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" });
proxyModel->ignoreFilesWithPath().insert(
{ FS::PathCombine(prefix, ".cache"), FS::PathCombine(prefix, ".fabric"), FS::PathCombine(prefix, ".quilt") });
loadPackIgnore(); loadPackIgnore();
ui->treeView->setModel(proxyModel); ui->treeView->setModel(proxyModel);

View File

@ -52,7 +52,7 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent)
// use the game root - everything outside cannot be exported // use the game root - everything outside cannot be exported
const QDir root(instance->gameRoot()); const QDir root(instance->gameRoot());
proxy = new FileIgnoreProxy(instance->gameRoot(), this); proxy = new FileIgnoreProxy(instance->gameRoot(), this);
proxy->ignoreFilesWithPath().insert({ "logs", "crash-reports", ".cache", ".fabric", ".quilt" }); proxy->ignoreFilesWithPath().insert({ "logs", "crash-reports" });
proxy->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" }); proxy->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" });
proxy->setSourceModel(model); proxy->setSourceModel(model);

View File

@ -34,7 +34,6 @@
*/ */
#include "ProgressDialog.h" #include "ProgressDialog.h"
#include <QPoint>
#include "ui_ProgressDialog.h" #include "ui_ProgressDialog.h"
#include <limits> #include <limits>
@ -67,9 +66,8 @@ ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Pr
ui->taskProgressScrollArea->setHidden(true); ui->taskProgressScrollArea->setHidden(true);
this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
setAttribute(Qt::WidgetAttribute::WA_QuitOnClose, true); setAttribute(Qt::WidgetAttribute::WA_QuitOnClose, true);
changeProgress(0, 100);
updateSize(true);
setSkipButton(false); setSkipButton(false);
changeProgress(0, 100);
} }
void ProgressDialog::setSkipButton(bool present, QString label) void ProgressDialog::setSkipButton(bool present, QString label)
@ -95,39 +93,25 @@ ProgressDialog::~ProgressDialog()
delete ui; delete ui;
} }
void ProgressDialog::updateSize(bool recenterParent) void ProgressDialog::updateSize()
{ {
QSize lastSize = this->size(); QSize lastSize = this->size();
QPoint lastPos = this->pos(); QSize qSize = QSize(480, minimumSizeHint().height());
int minHeight = ui->globalStatusDetailsLabel->minimumSize().height() + (ui->verticalLayout->spacing() * 2);
minHeight += ui->globalProgressBar->minimumSize().height() + ui->verticalLayout->spacing();
if (!ui->taskProgressScrollArea->isHidden())
minHeight += ui->taskProgressScrollArea->minimumSizeHint().height() + ui->verticalLayout->spacing();
if (ui->skipButton->isVisible())
minHeight += ui->skipButton->height() + ui->verticalLayout->spacing();
minHeight = std::max(minHeight, 60);
QSize minSize = QSize(480, minHeight);
setMinimumSize(minSize); // if the current window is too small
adjustSize(); if ((lastSize != qSize) && (lastSize.height() < qSize.height()))
QSize newSize = this->size();
// if the current window is a different size
auto parent = this->parentWidget();
if (recenterParent && parent) {
auto newX = std::max(0, parent->x() + ((parent->width() - newSize.width()) / 2));
auto newY = std::max(0, parent->y() + ((parent->height() - newSize.height()) / 2));
this->move(newX, newY);
}
else if (lastSize != newSize)
{ {
// center on old position after resize resize(qSize);
QSize sizeDiff = lastSize - newSize; // last size was smaller, the results should be negative
auto newX = std::max(0, lastPos.x() + (sizeDiff.width() / 2)); // keep the dialog in the center after a resize
auto newY = std::max(0, lastPos.y() + (sizeDiff.height() / 2)); this->move(
this->move(newX, newY); this->parentWidget()->x() + (this->parentWidget()->width() - this->width()) / 2,
this->parentWidget()->y() + (this->parentWidget()->height() - this->height()) / 2
);
} }
setMinimumSize(qSize);
} }
int ProgressDialog::execWithTask(Task* task) int ProgressDialog::execWithTask(Task* task)
@ -217,9 +201,7 @@ void ProgressDialog::onTaskSucceeded()
void ProgressDialog::changeStatus(const QString& status) void ProgressDialog::changeStatus(const QString& status)
{ {
ui->globalStatusLabel->setText(task->getStatus()); ui->globalStatusLabel->setText(task->getStatus());
ui->globalStatusLabel->adjustSize();
ui->globalStatusDetailsLabel->setText(task->getDetails()); ui->globalStatusDetailsLabel->setText(task->getDetails());
ui->globalStatusDetailsLabel->adjustSize();
updateSize(); updateSize();
} }

View File

@ -62,7 +62,7 @@ public:
explicit ProgressDialog(QWidget *parent = 0); explicit ProgressDialog(QWidget *parent = 0);
~ProgressDialog(); ~ProgressDialog();
void updateSize(bool recenterParent = false); void updateSize();
int execWithTask(Task* task); int execWithTask(Task* task);
int execWithTask(std::unique_ptr<Task> &&task); int execWithTask(std::unique_ptr<Task> &&task);

View File

@ -18,6 +18,8 @@
*/ */
#include "ResourceDownloadDialog.h" #include "ResourceDownloadDialog.h"
#include <QEventLoop>
#include <QList>
#include <QPushButton> #include <QPushButton>
#include <algorithm> #include <algorithm>
@ -30,6 +32,10 @@
#include "minecraft/mod/ShaderPackFolderModel.h" #include "minecraft/mod/ShaderPackFolderModel.h"
#include "minecraft/mod/TexturePackFolderModel.h" #include "minecraft/mod/TexturePackFolderModel.h"
#include "minecraft/mod/tasks/GetModDependenciesTask.h"
#include "modplatform/ModIndex.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/ReviewMessageBox.h" #include "ui/dialogs/ReviewMessageBox.h"
#include "ui/pages/modplatform/ResourcePage.h" #include "ui/pages/modplatform/ResourcePage.h"
@ -37,8 +43,6 @@
#include "ui/pages/modplatform/flame/FlameResourcePages.h" #include "ui/pages/modplatform/flame/FlameResourcePages.h"
#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" #include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/modrinth/ModrinthAPI.h"
#include "ui/widgets/PageContainer.h" #include "ui/widgets/PageContainer.h"
namespace ResourceDownload { namespace ResourceDownload {
@ -119,18 +123,71 @@ void ResourceDownloadDialog::connectButtons()
connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help);
} }
static ModPlatform::ProviderCapabilities ProviderCaps;
QStringList getRequiredBy(QList<ResourceDownloadDialog::DownloadTaskPtr> tasks, ResourceDownloadDialog::DownloadTaskPtr pack)
{
auto addonId = pack->getPack()->addonId;
auto provider = pack->getPack()->provider;
auto version = pack->getVersionID();
auto req = QStringList();
for (auto& task : tasks) {
if (provider != task->getPack()->provider)
continue;
auto deps = task->getVersion().dependencies;
if (auto dep = std::find_if(deps.begin(), deps.end(),
[addonId, provider, version](const ModPlatform::Dependency& d) {
return d.type == ModPlatform::DependencyType::REQUIRED &&
(provider == ModPlatform::ResourceProvider::MODRINTH && d.addonId.toString().isEmpty()
? version == d.version
: d.addonId == addonId);
});
dep != deps.end()) {
req.append(task->getName());
}
}
return req;
}
void ResourceDownloadDialog::confirm() void ResourceDownloadDialog::confirm()
{ {
auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString()));
confirm_dialog->retranslateUi(resourcesString());
if (auto task = getModDependenciesTask(); task) {
connect(task.get(), &Task::failed, this,
[&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
connect(task.get(), &Task::succeeded, this, [&]() {
QStringList warnings = task->warnings();
if (warnings.count()) {
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec();
}
});
// Check for updates
ProgressDialog progress_dialog(this);
progress_dialog.setSkipButton(true, tr("Abort"));
progress_dialog.setWindowTitle(tr("Checking for dependencies..."));
auto ret = progress_dialog.execWithTask(task.get());
// If the dialog was skipped / some download error happened
if (ret == QDialog::DialogCode::Rejected) {
QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
return;
} else {
for (auto dep : task->getDependecies())
addResource(dep->pack, dep->version);
}
}
auto selected = getTasks(); auto selected = getTasks();
std::sort(selected.begin(), selected.end(), [](const DownloadTaskPtr& a, const DownloadTaskPtr& b) { std::sort(selected.begin(), selected.end(), [](const DownloadTaskPtr& a, const DownloadTaskPtr& b) {
return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0; return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0;
}); });
auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString()));
confirm_dialog->retranslateUi(resourcesString());
for (auto& task : selected) { for (auto& task : selected) {
confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath() }); confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(),
ProviderCaps.name(task->getProvider()), getRequiredBy(selected, task) });
} }
if (confirm_dialog->exec()) { if (confirm_dialog->exec()) {
@ -224,11 +281,8 @@ QList<BasePage*> ModDownloadDialog::getPages()
{ {
QList<BasePage*> pages; QList<BasePage*> pages;
auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getModLoaders().value(); pages.append(ModrinthModPage::create(this, *m_instance));
if (APPLICATION->capabilities() & Application::SupportsFlame)
if (ModrinthAPI::validateModLoaders(loaders))
pages.append(ModrinthModPage::create(this, *m_instance));
if (APPLICATION->capabilities() & Application::SupportsFlame && FlameAPI::validateModLoaders(loaders))
pages.append(FlameModPage::create(this, *m_instance)); pages.append(FlameModPage::create(this, *m_instance));
m_selectedPage = dynamic_cast<ModPage*>(pages[0]); m_selectedPage = dynamic_cast<ModPage*>(pages[0]);
@ -236,6 +290,19 @@ QList<BasePage*> ModDownloadDialog::getPages()
return pages; return pages;
} }
GetModDependenciesTask::Ptr ModDownloadDialog::getModDependenciesTask()
{
if (auto model = dynamic_cast<ModFolderModel*>(getBaseModel().get()); model) {
QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> selectedVers;
for (auto& selected : getTasks()) {
selectedVers.append(std::make_shared<GetModDependenciesTask::PackDependency>(selected->getPack(), selected->getVersion()));
}
return makeShared<GetModDependenciesTask>(this, m_instance, model, selectedVers);
}
return nullptr;
};
ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent, ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent,
const std::shared_ptr<ResourcePackFolderModel>& resource_packs, const std::shared_ptr<ResourcePackFolderModel>& resource_packs,
BaseInstance* instance) BaseInstance* instance)

View File

@ -25,6 +25,7 @@
#include <QLayout> #include <QLayout>
#include "QObjectPtr.h" #include "QObjectPtr.h"
#include "minecraft/mod/tasks/GetModDependenciesTask.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "ui/pages/BasePageProvider.h" #include "ui/pages/BasePageProvider.h"
@ -81,6 +82,8 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider {
[[nodiscard]] virtual QString geometrySaveKey() const { return ""; } [[nodiscard]] virtual QString geometrySaveKey() const { return ""; }
void setButtonStatus(); void setButtonStatus();
[[nodiscard]] virtual GetModDependenciesTask::Ptr getModDependenciesTask() { return nullptr; }
protected: protected:
const std::shared_ptr<ResourceFolderModel> m_base_model; const std::shared_ptr<ResourceFolderModel> m_base_model;
@ -103,6 +106,7 @@ class ModDownloadDialog final : public ResourceDownloadDialog {
[[nodiscard]] QString geometrySaveKey() const override { return "ModDownloadGeometry"; } [[nodiscard]] QString geometrySaveKey() const override { return "ModDownloadGeometry"; }
QList<BasePage*> getPages() override; QList<BasePage*> getPages() override;
GetModDependenciesTask::Ptr getModDependenciesTask() override;
private: private:
BaseInstance* m_instance; BaseInstance* m_instance;

View File

@ -40,7 +40,8 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
auto filenameItem = new QTreeWidgetItem(itemTop); auto filenameItem = new QTreeWidgetItem(itemTop);
filenameItem->setText(0, tr("Filename: %1").arg(info.filename)); filenameItem->setText(0, tr("Filename: %1").arg(info.filename));
itemTop->insertChildren(0, { filenameItem }); auto childIndx = 0;
itemTop->insertChildren(childIndx++, { filenameItem });
if (!info.custom_file_path.isEmpty()) { if (!info.custom_file_path.isEmpty()) {
auto customPathItem = new QTreeWidgetItem(itemTop); auto customPathItem = new QTreeWidgetItem(itemTop);
@ -49,7 +50,31 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
itemTop->insertChildren(1, { customPathItem }); itemTop->insertChildren(1, { customPathItem });
itemTop->setIcon(1, QIcon(APPLICATION->getThemedIcon("status-yellow"))); itemTop->setIcon(1, QIcon(APPLICATION->getThemedIcon("status-yellow")));
itemTop->setToolTip(1, tr("This file will be downloaded to a folder location different from the default, possibly due to its loader requiring it.")); itemTop->setToolTip(
childIndx++,
tr("This file will be downloaded to a folder location different from the default, possibly due to its loader requiring it."));
}
auto providerItem = new QTreeWidgetItem(itemTop);
providerItem->setText(0, tr("Provider: %1").arg(info.provider));
itemTop->insertChildren(childIndx++, { providerItem });
if (!info.required_by.isEmpty()) {
auto requiredByItem = new QTreeWidgetItem(itemTop);
if (info.required_by.length() == 1) {
requiredByItem->setText(0, tr("Required by: %1").arg(info.required_by.back()));
} else {
requiredByItem->setText(0, tr("Required by:"));
auto i = 0;
for (auto req : info.required_by) {
auto reqItem = new QTreeWidgetItem(requiredByItem);
reqItem->setText(0, req);
reqItem->insertChildren(i++, { reqItem });
}
}
itemTop->insertChildren(childIndx++, { requiredByItem });
} }
ui->modTreeWidget->addTopLevelItem(itemTop); ui->modTreeWidget->addTopLevelItem(itemTop);

View File

@ -15,7 +15,9 @@ class ReviewMessageBox : public QDialog {
using ResourceInformation = struct res_info { using ResourceInformation = struct res_info {
QString name; QString name;
QString filename; QString filename;
QString custom_file_path {}; QString custom_file_path{};
QString provider;
QStringList required_by;
}; };
void appendResource(ResourceInformation&& info); void appendResource(ResourceInformation&& info);

View File

@ -1,16 +1,36 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU General Public License as published by
* You may obtain a copy of the License at * the Free Software Foundation, version 3.
* *
* http://www.apache.org/licenses/LICENSE-2.0 * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* *
* Unless required by applicable law or agreed to in writing, software * You should have received a copy of the GNU General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and * This file incorporates work covered by the following copyright and
* limitations under the License. * permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ */
#include "VersionSelectDialog.h" #include "VersionSelectDialog.h"
@ -22,15 +42,10 @@
#include <QtWidgets/QVBoxLayout> #include <QtWidgets/QVBoxLayout>
#include <QDebug> #include <QDebug>
#include "ui/dialogs/ProgressDialog.h"
#include "ui/widgets/VersionSelectWidget.h" #include "ui/widgets/VersionSelectWidget.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "BaseVersion.h" #include "BaseVersion.h"
#include "BaseVersionList.h" #include "BaseVersionList.h"
#include "tasks/Task.h"
#include "Application.h"
#include "VersionProxyModel.h"
VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent, bool cancelable) VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent, bool cancelable)
: QDialog(parent) : QDialog(parent)
@ -40,7 +55,7 @@ VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title,
m_verticalLayout = new QVBoxLayout(this); m_verticalLayout = new QVBoxLayout(this);
m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); m_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
m_versionWidget = new VersionSelectWidget(parent); m_versionWidget = new VersionSelectWidget(true, parent);
m_verticalLayout->addWidget(m_versionWidget); m_verticalLayout->addWidget(m_versionWidget);
m_horizontalLayout = new QHBoxLayout(); m_horizontalLayout = new QHBoxLayout();

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