Compare commits

...

266 Commits

Author SHA1 Message Date
Evan Goode
8555ede5c4 Fix flatpak and nix workflows 2025-04-06 18:30:54 -04:00
Evan Goode
35131193dd Merge remote-tracking branch 'upstream/release-9.x' into develop 2025-04-06 18:23:05 -04:00
Seth Flynn
ce0a730531
[Backport release-9.x] Use default palette on all system themes (#3585) 2025-04-06 15:57:45 -04:00
Seth Flynn
f00226f797 fix(SystemTheme): use default palette on all system themes
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit e5861129ad58a8b5cdf55ed69bb9a7ce92811b87)
2025-04-06 19:55:13 +00:00
Seth Flynn
a96a915d79
[Backport release-9.x] Nix Flake spring cleaning (#3576) 2025-04-04 15:04:53 -04:00
Seth Flynn
83f79c93f8
chore(nix): set stable version
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-04-04 05:24:38 -04:00
Seth Flynn
b228800de1
chore(gitignore): add more nix-related files
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit de08d7c3644e96a3694f232b53c81f53c40c077a)
2025-04-04 05:07:19 -04:00
Seth Flynn
e8bdc72476
refactor(nix): use date for version
Helps avoid needless rebuilds where only the revision changed. Also
better conforms to Nixpkgs' version standards

Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit e9cac2e0e37c4531f662545bdc45b72499654642)
2025-04-04 05:07:19 -04:00
Seth Flynn
da9c359184
chore(nix): clone git submodules automatically
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 9b38226f8cb0e2f82bcb017c686fd03520990dd1)
2025-04-04 05:07:19 -04:00
Seth Flynn
b02718ee57
chore(nix): add nice welcome message to dev shell
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 38ec7def324891c5062d9b58c1180d44494f91d1)
2025-04-04 05:07:19 -04:00
Seth Flynn
ddcd61c808
refactor(nix): rely more on setup hooks in dev shell
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit de923a07d8a4b7c9f8cd688b9682587c78116e14)
2025-04-04 05:07:18 -04:00
Seth Flynn
760fbdf54f
build(nix): properly wrap development shell
Allows actually running the executables built in the development shell

Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 2d4bc09cb9bec621997e00c23acac0b7cf3e99d7)
2025-04-04 05:07:18 -04:00
Seth Flynn
5c4bd3db52
fix(nix): only create compile_commands.json if it doesn't exist
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 58579539d071c569800ba0370a6d5918de025e33)
2025-04-04 05:07:18 -04:00
Seth Flynn
87c5b6cc83
refactor(nix): cleanup flake
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 32b49ecb84da5683bce42e2e888319f4517f1d53)
2025-04-04 05:07:16 -04:00
Seth Flynn
80ff62c96d
refactor(nix): pin flake-compat in default.nix
Avoids polluting downstream flake.locks

Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit c367f48ec99f048f8d4bcaf0634370be33b29c62)
2025-04-04 05:06:05 -04:00
Seth Flynn
b5fc23952e
Reapply "refactor(nix): nix-filter -> lib.fileset"
After extensive (5 minutes) of testing, it seems we don't actually come
across any Nix bugs with lib.fileset! (aside from those inherit to
it...but 🤷)

This reverts commit a49a58bc45.

Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 99852c972c6e9fc6a8caca727946a71a85c1672f)
2025-04-04 05:06:01 -04:00
Alexandru Ionut Tripon
5c8ef3a003
[Backport release-9.x] feat: add updater dialogues (#3568) 2025-04-01 18:31:09 +03:00
Sticks
b0ee4f6e58 fix: remove qWarning for unknown state
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Sticks <sticks@teamhydra.dev>
(cherry picked from commit d4e1851e6779d99e3ffde79c7e4e8edb568415ce)
2025-04-01 15:10:15 +00:00
Sticks
25ce20a877 fix: Grammar correction in failed message for update dialogues
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Sticks <sticks@teamhydra.dev>
(cherry picked from commit ea5458ed2253a4e8a9ef50d076fe72c57d2cb669)
2025-04-01 15:10:15 +00:00
sticks
2952b1d6b6 feat: add updater dialogues
Signed-off-by: sticks <tanner@teamhydra.dev>
(cherry picked from commit 998bc660fa97f4527443dd826eede5e8eab23651)
2025-04-01 15:10:15 +00:00
Alexandru Ionut Tripon
416495cda7
[Backport release-9.x] Fix typo in NetRequest.cpp (#3562) 2025-03-31 08:20:18 +03:00
LAHarbottle
6de8ed91a3 Fix typo in NetRequest.cpp
Signed-off-by: LAHarbottle <87842870+LAHarbottle@users.noreply.github.com>
(cherry picked from commit 66c6399adedcb52c2b7391cdbf38602f8b2814a6)
2025-03-31 05:13:04 +00:00
Alexandru Ionut Tripon
f9ab98f2cf
[Backport release-9.x] fix themes leak (#3556) 2025-03-30 01:55:52 +02:00
Trial97
fcc1e80237 fix themes leak
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 5b12d3cfff9092c0364e455c211fc189d87039cd)
2025-03-29 23:55:27 +00:00
Alexandru Ionut Tripon
1a56396e2e
[Backport release-9.x] Pin AppImage tooling in CI, drop libfuse2 (#3551) 2025-03-27 09:51:49 +02:00
Seth Flynn
0f21e01b46
ci: don't install libfuse2
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 671aad88f52e3fbba872e80f2805c0c5c64b7a8d)
2025-03-26 21:15:39 -04:00
Seth Flynn
88109eaa71
ci: pin appimage tooling
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 5d5155bb22f9935e17e33008a644736404629928)
2025-03-26 21:15:39 -04:00
Seth Flynn
1425c9801e
ci: use bundled linuxdeploy appimage plugin
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 6ef59fe984777e2620b445a78493afb86074eb8b)
2025-03-26 21:15:39 -04:00
Evan Goode
808277d2f8 Bump to 9.2.4 2025-03-25 16:57:13 -04:00
Evan Goode
3ced5d6546 Support authlib-injector skin upload 2025-03-25 16:16:40 -04:00
Evan Goode
43cd5853c1 clang-format 2025-03-24 21:57:40 -04:00
Evan Goode
9116d373e7 Create authlib-injector account with punycoded URL
Related: https://github.com/yushijinhun/authlib-injector/issues/270
2025-03-24 20:20:36 -04:00
Alexandru Ionut Tripon
1a06e34fe4
[Backport release-9.x] Use correct colours for all system themes (#3539) 2025-03-24 19:25:27 +02:00
TheKodeToad
d0a77df703 Try best to avoid regression
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 513959750f23719b55030cd34b9a8f5b248289c5)
2025-03-24 17:23:35 +00:00
TheKodeToad
57120e0c9d Use correct colours for all system themes
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit f08478c7ece0cd4e13b0bc188d0dcb9ea04c3199)
2025-03-24 17:23:35 +00:00
Evan Goode
0e943b4bb1 Add back AuthlibInjectorMetadataStep
Apparently this was forgotten during the 9.0 merging.
2025-03-24 13:15:00 -04:00
Alexandru Ionut Tripon
57981a070f
[Backport release-9.x] Fix crash with low disk space (#3538) 2025-03-24 19:04:38 +02:00
Alexandru Ionut Tripon
be1a1baba4
[Backport release-9.x] fix account help link (#3537) 2025-03-24 19:04:25 +02:00
Trial97
9686a3c81c fix crash on accessing reseted output
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 9ce5587f60d637d48bf20a8b9a40f020898710de)
2025-03-24 17:03:13 +00:00
Trial97
b348114dee rename variable
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 269938dfd866df5918eb44779667142f7a157c22)
2025-03-24 17:03:13 +00:00
Trial97
07604c9271 fix account help link
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit a4472406b96faabf2bdb78d152fc0e66bfe4ac63)
2025-03-24 16:58:49 +00:00
Evan Goode
fde597aa0a Bump to 9.2.3 2025-03-23 20:27:57 -04:00
Evan Goode
84e9606e4f legacy: handle URL-encoded session ID 2025-03-23 19:38:16 -04:00
Evan Goode
d9ca7d2e74 legacy: rewrite http://session.minecraft.net/game/joinserver.jsp 2025-03-23 19:38:16 -04:00
Evan Goode
859ab72ea1 Update flake.lock 2025-03-23 15:25:49 -04:00
Alexandru Ionut Tripon
b49e9763bf
[Backport release-9.x] Fix crash with invalid quilt mod info (#3506) 2025-03-15 00:12:19 +02:00
Alexandru Ionut Tripon
b9d15a4781 Update launcher/minecraft/mod/tasks/LocalModParseTask.cpp
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
(cherry picked from commit 88bdb6541e3a57d6f84e75080dd6d24c1589a73d)
2025-03-14 22:08:50 +00:00
Trial97
190d17acd3 Fix crash with invalid quilt mod info
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 0626e354a026f9a2a35a4d6e28a0af105cb22986)
2025-03-14 22:08:50 +00:00
Alexandru Ionut Tripon
5de91076ea
[Backport release-9.x] fix mod load with empty gameversions (#3490) 2025-03-10 14:41:07 +02:00
Trial97
299cbf8009 fix mod load with empty gameversions
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 29b3eb5cc6fe3ba48a28598222212966bade1444)
2025-03-10 12:40:50 +00:00
Alexandru Ionut Tripon
70ca7af205
[Backport release-9.x] fix beginResetModel called before endResetModel (#3486) 2025-03-09 14:05:42 +02:00
Alexandru Ionut Tripon
19aaaa9988
[Backport release-9.x] fix null mainwindow in case of login on setup (#3485) 2025-03-09 14:05:35 +02:00
Trial97
fc639c0902 fix beginResetModel called before endResetModel
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit c441ae05df58eecf315c1379962a59d52c263f14)
2025-03-09 12:05:09 +00:00
Trial97
2bb094a90b fix null mainwindow in case of login on setup
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 7db717ee7c503aef3f3b70bbc4becb8c20485897)
2025-03-09 12:05:03 +00:00
Trial97
c206064976 rename some variables
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 162bbcfe19e67f23bf8d21142fdad253500a3b64)
2025-03-09 12:05:03 +00:00
Evan Goode
15aff73aa1 Show CurseForge in new instance dialog even if no API key 2025-03-08 13:04:43 -05:00
Alexandru Ionut Tripon
d8f51e900a
[Backport release-9.x] chore: Update year range (#3462) 2025-03-03 08:27:33 +02:00
txtsd
b85e53eaf9 chore: Update year range
Signed-off-by: txtsd <code@ihavea.quest>
(cherry picked from commit f99b7e9ebd17129448aabd630c87e8a15fb2734c)
2025-03-03 06:27:10 +00:00
Alexandru Ionut Tripon
0073d82863
[Backport release-9.x] Default to Fusion based Dark/Bright themes on Windows (#3457) 2025-03-02 08:52:23 +02:00
TheKodeToad
70db5a2f92 Default to Fusion based Dark/Bright themes on Windows
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 5261b615d72eb0adbeb9c53e30b73448d722cf11)
2025-03-02 06:49:59 +00:00
Alexandru Ionut Tripon
07010f226e
[Backport release-9.x] Recognize riscv64 as a 64-bit platform (#3428) 2025-02-17 20:35:08 +02:00
thonkdifferent
d75b23c092 Recognize riscv64 as a 64-bit platform
Currently PrismLauncher complains about the installed JDK being a 32-bit version, despite it being compiled for 64-bit RISC-V `riscv64`.

This PR fixes this issue.

Signed-off-by: thonkdifferent <41342923+thonkdifferent@users.noreply.github.com>
(cherry picked from commit 8f1750df51668a94ab750dd05345ec5cc4e124c2)
2025-02-17 18:33:29 +00:00
Alexandru Ionut Tripon
748b92571c
[Backport release-9.x] make sure if user changes java path also disable java management (#3426) 2025-02-17 20:32:54 +02:00
Trial97
97180324ee
make sure if user changes java path also disable java management
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-17 16:14:15 +02:00
Evan Goode
ec0baa6a1e Bump to 9.2.2 2025-02-16 19:45:59 -05:00
Alexandru Ionut Tripon
2bf6d66615 Update .github/workflows/build.yml
Co-authored-by: Seth Flynn <getchoo@tuta.io>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
(cherry picked from commit db766574a42a4b7db6b320b6d469b233ce5f3100)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-02-16 15:54:15 -05:00
Alexandru Ionut Tripon
abd55c9d38 Update .github/workflows/build.yml
Co-authored-by: seth <getchoo@tuta.io>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
(cherry picked from commit 1b5d3c2bf903bbcade7b38c22c0b103a0c6999f8)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-02-16 15:54:15 -05:00
Trial97
c45000eac3 remove specific step for qt6
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit be3eca8c97fdbb8daede84cab20fbd7695334242)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-02-16 15:54:15 -05:00
Trial97
162188eaec fix appimage
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit bca517b8d3cd685540beebf69fbc1f80fe0a6ce4)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-02-16 15:54:15 -05:00
Evan Goode
9842c2f24f
Merge pull request #52 from unmojang/update_flake_lock_action
chore(nix): update lockfile
2025-02-16 15:52:32 -05:00
Evan Goode
86903bfc7f
Merge pull request #56 from unmojang/evan-goode/feed-the-beast
Use api.feed-the-beast.com, not modpacks.ch
2025-02-16 15:52:23 -05:00
Evan Goode
61892f857f Use api.feed-the-beast.com, not modpacks.ch 2025-02-15 19:45:07 -05:00
github-actions[bot]
63edfdf7ac chore(nix): update lockfile
Flake lock file updates:

• Updated input 'flake-compat':
    'github:edolstra/flake-compat/9ed2ac151eada2306ca8c418ebd97807bb08f6ac?narHash=sha256-HRJ/18p%2BWoXpWJkcdsk9St5ZiukCqSDgbOGFa8Okehg%3D' (2024-11-27)
  → 'github:edolstra/flake-compat/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec?narHash=sha256-NeCCThCEP3eCl2l/%2B27kNNK7QrwZB1IJCrXfrbv5oqU%3D' (2024-12-04)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/fa42b5a5f401aab8a32bd33c9a4de0738180dc59?narHash=sha256-RzaI1RO0UXqLjydtz3GAXSTzHkpb/lLD1JD8a0W4Wpo%3D' (2024-11-28)
  → 'github:NixOS/nixpkgs/2ff53fe64443980e139eaa286017f53f88336dd0?narHash=sha256-%2B/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc%3D' (2025-02-13)
2025-02-16 00:17:12 +00:00
Rachel Powers
ec7aaaadf3
[Backport release-9.x] add libMangoHud_shim.so to mangohub preloadlist (#3418) 2025-02-13 14:42:35 -07:00
Rachel Powers
7d786987ad
[Backport release-9.x] make sure if user changes java path also disable java management (#3417) 2025-02-13 14:42:26 -07:00
Trial97
1ab6ce42b5 add libMangoHud_shim.so to mangohub preloadlist
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 9e954548dd702c05fbf33474c9c86f52fb67d4b4)
2025-02-13 21:41:14 +00:00
Trial97
05bf9c3689 make sure if user changes java path also disable java management
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 174cddcf42f26b24a5a74ce2eaf4923b5b269d47)
2025-02-13 21:40:54 +00:00
Alexandru Ionut Tripon
f760f08df6
[Backport release-9.x] Fix some undefined behaviour (#3408) 2025-02-11 21:11:17 +02:00
Alexandru Ionut Tripon
9456140155 Update launcher/ui/widgets/CheckComboBox.cpp
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
(cherry picked from commit 22b59e760c0f39749916052b9323e55f4217dd72)
2025-02-11 19:10:21 +00:00
Alexandru Ionut Tripon
159771f283 Apply suggestions from code review
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
(cherry picked from commit 1d1a4f1b30837e13ebd99c016af171d79697f22f)
2025-02-11 19:10:21 +00:00
Trial97
9616d898d4 Fix some undefined behaviour
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit bf1084d7fa6503b65bdb63374e5f92020ae4a3d1)
2025-02-11 19:10:21 +00:00
Alexandru Ionut Tripon
e2526c80c8
Fixed when game crashes, it doesn't log any time played (#3392) (#3395) 2025-02-06 09:41:41 +02:00
Alexandru Ionut Tripon
a37cac6f9b
Fixed when game crashes, it doesn't log any time played (#3392)
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-02-06 09:41:19 +02:00
Alexandru Ionut Tripon
2f889429da
Merge pull request #3376 from PrismLauncher/backport-3373-to-release-9.x
[Backport release-9.x] Show warning when adding new resources with no instances
2025-01-31 23:04:21 +02:00
Trial97
c7831fd697 fix add resource with no instance
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit c5efe081b4ff9ec81b882e6deabcbfc5be269329)
2025-01-31 21:04:07 +00:00
Alexandru Ionut Tripon
b4c3123957
Merge pull request #3375 from PrismLauncher/backport-3260-to-release-9.x
[Backport release-9.x] Map Minecraft snapshots to Modrinth
2025-01-31 23:03:25 +02:00
Trial97
ae3e1a262e ensure that the snapshot mapping is on all apis
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 8e5a7c6e333969b5412efd1349f9430ced5654c8)
2025-01-31 21:03:11 +00:00
Trial97
f8dc58665b map modrinth snapshots
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 1d8bf1d5a788e689d26e0afa8f5cfca0e3859e5d)
2025-01-31 21:03:11 +00:00
Alexandru Ionut Tripon
19247bad14
Merge pull request #3367 from PrismLauncher/backport-3359-to-release-9.x
[Backport release-9.x] Publish on FlakeHub
2025-01-30 18:31:44 +02:00
Seth Flynn
4371933a84 feat: publish on flakehub
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 86cc6d3c5ee7157d5f9978580e94b94360a119f1)
2025-01-30 16:31:27 +00:00
Seth Flynn
dd90049b30 ci: use generic workflow for publishing
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 61d7f088838e25ddb0a7089a6fa3f7d232331c24)
2025-01-30 16:31:27 +00:00
Alexandru Ionut Tripon
18170cc900
Merge pull request #3364 from PrismLauncher/backport-3314-to-release-9.x
[Backport release-9.x] update submodules
2025-01-29 23:41:13 +02:00
Trial97
71eced3e61 update submodules
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit dc00c47f2e2b79705c32f438298d968aaa4fdf26)
2025-01-29 21:40:21 +00:00
Alexandru Ionut Tripon
cb49b27513
Merge pull request #3363 from PrismLauncher/backport-3362-to-release-9.x
[Backport release-9.x] ci: use flakehub cache
2025-01-29 23:34:39 +02:00
Seth Flynn
7b502fe8c9 ci: run nix workflow on pull_request_target
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 169f5728b1019582db150f3b1618515eb1438517)
2025-01-29 21:34:15 +00:00
Seth Flynn
55578bf949 ci: use flakehub cache
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 8816be166821d5ce1a4cd8edfbe7b91727b6eb34)
2025-01-29 21:34:15 +00:00
Alexandru Ionut Tripon
ee33bf50c2
Merge pull request #3361 from getchoo-contrib/backport-3360-to-release-9.x
[Backport release-9.x] Split Flatpak and Nix CI jobs from main build workflow
2025-01-29 23:12:18 +02:00
Seth Flynn
702db71f61
ci: separate flatpak job
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 49d734f3142328a71506b28c8c1cdcb99788c34d)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 16:06:27 -05:00
Seth Flynn
8e8efc304f
ci: separate nix job
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit 417688089d756da23ca3f482a32871963d289211)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-29 16:06:27 -05:00
Alexandru Ionut Tripon
2b204c8169
Merge pull request #3333 from PrismLauncher/backport-3330-to-release-9.x
[Backport release-9.x] Use better compile flags for MINGW
2025-01-29 22:52:29 +02:00
Alexandru Ionut Tripon
ed2eddc258
Merge pull request #3335 from PrismLauncher/backport-3294-to-release-9.x
[Backport release-9.x] Fix updating Curseforge mods with missing mod loader
2025-01-21 23:45:40 +02:00
Trial97
855e49bda0 fix curseforge with empty loader as newest version
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit e4ad4051c8b87bd5102a76a860408de2b447d74d)
2025-01-21 21:44:27 +00:00
Seth Flynn
bbde47842b feat: use better compile flags for mingw
Signed-off-by: Seth Flynn <getchoo@tuta.io>
(cherry picked from commit e66f447ce5bca996b0a12fac02216b2b6ab8ccb6)
2025-01-21 13:18:57 +00:00
Alexandru Ionut Tripon
0508bbd05f
Merge pull request #3331 from getchoo-contrib/backport-ubuntu-22.04
[Backport release-9.x] Use Ubuntu 22.04 for Linux builds
2025-01-21 09:08:46 +02:00
Alexandru Ionut Tripon
808978080e
Merge pull request #3329 from getchoo-contrib/backport-qt-6.8
[Backport release-9.x] chore: update to qt 6.8.x
2025-01-21 09:07:58 +02:00
Alexandru Ionut Tripon
ea0d710aaf
Update .github/workflows/build.yml
Co-authored-by: Seth Flynn <getchoo@tuta.io>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
(cherry picked from commit db766574a42a4b7db6b320b6d469b233ce5f3100)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 23:47:00 -05:00
Alexandru Ionut Tripon
02e1988779
Update .github/workflows/build.yml
Co-authored-by: seth <getchoo@tuta.io>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
(cherry picked from commit 1b5d3c2bf903bbcade7b38c22c0b103a0c6999f8)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 23:46:59 -05:00
Trial97
fdab044c04
remove specific step for qt6
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit be3eca8c97fdbb8daede84cab20fbd7695334242)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 23:46:59 -05:00
Trial97
e77ac36c3f
fix appimage
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit bca517b8d3cd685540beebf69fbc1f80fe0a6ce4)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 23:46:59 -05:00
Edgars Cirulis
0d06fae7ae
chore: update Qt to 6.8.1
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
(cherry picked from commit eb8c375ec5023bbc7c02e45d63da866058e56b26)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 19:09:02 -05:00
Edgars Cirulis
a0c4cc59ac
ci: uprev qt install action to v4
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
(cherry picked from commit 0beaa94311a00cdc02ef5ff7d182849fce419aa9)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 19:09:02 -05:00
Edgars Cirulis
bd7371dbf1
ci: fix qt-6.8 workflow
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
(cherry picked from commit b39098dbc5b371e31ce585f7534ffdb00096c2b5)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 19:09:02 -05:00
Edgars Cirulis
d7756d29dc
chore: update Qt to 6.8.0
Signed-off-by: Edgars Cirulis <edgarsciruliss@gmail.com>
(cherry picked from commit 5b6d551650e4590567321647ce68ae2043a8180c)
Signed-off-by: Seth Flynn <getchoo@tuta.io>
2025-01-20 19:09:01 -05:00
Alexandru Ionut Tripon
0a7325ac12
Merge pull request #3324 from getchoo-contrib/backport-arm-stuff
[Backport release-9.x] Build Nix packages and Flatpaks for ARM
2025-01-20 09:21:29 +02:00
seth
ed2b5d4b0e
ci: build flatpaks for arm
Signed-off-by: seth <getchoo@tuta.io>
(cherry picked from commit 66f0397087d9b88c93e69c5e26a138d235344546)
Signed-off-by: seth <getchoo@tuta.io>
2025-01-19 19:47:44 -05:00
seth
9f199a470c
ci: build nix packages for aarch64-linux
Signed-off-by: seth <getchoo@tuta.io>
(cherry picked from commit 8e8538b506b5f3ae2b2424599078966600bb764c)
Signed-off-by: seth <getchoo@tuta.io>
2025-01-19 19:47:39 -05:00
Alexandru Ionut Tripon
1a98ef8bb7
Merge pull request #3309 from Trial97/bump9.3
bump to 9.3
2025-01-18 13:47:11 +02:00
Alexandru Ionut Tripon
d25d0d7ee6
Merge pull request #3312 from PrismLauncher/backport-1689-to-release-9.x
[Backport release-9.x] Added mouse interactions for labels in ProgressDialog
2025-01-17 21:10:03 +02:00
Trial97
beac093d62 Added mouse interactions for labels in ProgressDialog
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 08f5148a9a4e574dd3ef4e7dc1e46a05ffd9dd5e)
2025-01-17 19:00:06 +00:00
Trial97
81318d4953
bump to 9.3
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2025-01-17 09:17:49 +02:00
Alexandru Ionut Tripon
8c6602147b
Merge pull request #3308 from PrismLauncher/backport-3254-to-release-9.x
[Backport release-9.x] remove EditAccountDialog
2025-01-17 09:02:31 +02:00
Alexandru Ionut Tripon
9d8c3f251f
Merge pull request #3307 from PrismLauncher/backport-2737-to-release-9.x
[Backport release-9.x] Truncate logs for mclo.gs to fit 25k line limit
2025-01-17 09:02:01 +02:00
Alexandru Ionut Tripon
c787e65ed4
Merge pull request #3306 from PrismLauncher/backport-1555-to-release-9.x
[Backport release-9.x] Correctly expand environment variables for pre/post launch and wrapper commands
2025-01-17 09:01:21 +02:00
Alexandru Ionut Tripon
ae84a78470
Merge pull request #3303 from getchoo-contrib/backport-3179-to-release-9.x
[Backport release-9.x] Fix $INST_JAVA not being set for auto download java
2025-01-17 08:56:39 +02:00
Trial97
820b9e3fd0 remove EditAccountDialog
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit e01df73debc8cb578e3e1b1e557388a8d0a7a052)
2025-01-17 06:56:28 +00:00
maskers
c8c3370d36 fix off by one error
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
(cherry picked from commit 6a12c43c787db4a64748d6c3d03b34981948d7ce)
2025-01-17 06:50:00 +00:00
TheKodeToad
cffa9a2da0 Confirm Truncate -> Truncation
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 43fc9ba932ec65d274b60bafb4d3c3f1d5bf55f0)
2025-01-17 06:50:00 +00:00
maskers
01397896eb don't mention prism in the middle of the log
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
(cherry picked from commit 762d24bd02ae6a0c0f241626c9c06deeaae86574)
2025-01-17 06:50:00 +00:00
maskers
3048e43900 apply suggestion from code review
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: maskers <97827489+maskersss@users.noreply.github.com>
(cherry picked from commit a910337e9d148937fc6a7339a7bcdd9a2db1ec03)
2025-01-17 06:50:00 +00:00
maskers
417bb2fce6 fix formatting
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
(cherry picked from commit 6d017b5f0b9fd878d387bf33585221ea079e305b)
2025-01-17 06:50:00 +00:00
maskers
e234c66818 add a Cancel option
Co-authored-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
Signed-off-by: maskers <97827489+maskersss@users.noreply.github.com>
(cherry picked from commit 65f852615247f6c1ba3cd81cf9e51ebc0220341b)
2025-01-17 06:50:00 +00:00
maskers
589ee73bfd Apply suggestions from code review
Co-authored-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Signed-off-by: maskers <97827489+maskersss@users.noreply.github.com>
(cherry picked from commit 99bd4a89373416cea641f1421d1082ff823eba2e)
2025-01-17 06:50:00 +00:00
maskers
0cb5ac7a06 add a warning about the log being too large
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
(cherry picked from commit cf914526bf5330600f3cadb9f4baddb5144af21f)
2025-01-17 06:50:00 +00:00
maskers
db35f47105 fix formatting
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
(cherry picked from commit 858f6aa9b8adcb5d2ac9eadd2702a21a88f9e4c4)
2025-01-17 06:50:00 +00:00
maskers
cf8a82e78a fix formatting
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
(cherry picked from commit e6f30c0ebe0ddd43b493f27156d6c0765105ded5)
2025-01-17 06:50:00 +00:00
maskers
15887c2d92 truncate logs for mclo.gs upload to fit 25k line limit
Signed-off-by: maskers <97827489+mskrss@users.noreply.github.com>
(cherry picked from commit 014fc142915ec4a0f96b3082f76d4d32ce7596aa)
2025-01-17 06:50:00 +00:00
Trial97
b7b5630588 expand env from wrapped cmd
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 09a118e85e3646ded9d7b2704d2f95a589ca19b9)
2025-01-17 06:47:08 +00:00
Trial97
43834e2148 correctly expand env vars
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit c12beb43a0f3dad6e66fd42192dbbf1f78fb6c53)
2025-01-17 06:47:08 +00:00
sshcrack
feb275a580
load meta first
Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com>
(cherry picked from commit cf2dcbd431b0c4bb03ff527465f972d72f713b0c)
Signed-off-by: seth <getchoo@tuta.io>
2025-01-16 21:46:13 -05:00
sshcrack
759db0c90f
change order of steps
Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com>
(cherry picked from commit dedb7a2343f7d433492dee5930802ed47294b722)
Signed-off-by: seth <getchoo@tuta.io>
2025-01-16 21:03:52 -05:00
seth
b2c75dde95
Merge pull request #3302 from PrismLauncher/backport-3292-to-release-9.x
[Backport release-9.x] Update Modrinth API authentication link
2025-01-16 20:55:38 -05:00
Trial97
62ed3f8406 fix modrinth link
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 3ee73916ca31666abc48f45e8b0bfcf8d678e6cf)
2025-01-17 01:42:14 +00:00
Evan Goode
bf48b45ff1 Bump to 9.2.1 2025-01-13 12:23:55 -05:00
Evan Goode
d931e8fa49
Merge pull request #51 from unmojang/evan-goode/fix-selectedProfile
Only send `selectedProfile` when selecting a profile
2025-01-13 11:52:09 -05:00
Evan Goode
a108c8678e Remove irrelevant FIXME comment 2025-01-13 11:51:33 -05:00
Alexandru Ionut Tripon
5e227ceaae
Merge pull request #3281 from PrismLauncher/backport-3255-to-release-9.x
[Backport release-9.x] propagate load component error
2025-01-13 08:18:00 +02:00
Evan Goode
a5fcdd03bf Only send selectedProfile when selecting a profile
Some authentication servers (Blessing Skin) care when selectedProfile is
sent on POST /refresh but the clientToken is already bound to a profile.
We should only include selectedProfile in POST /refresh during the
initial "Add authlib-injector account" process when selecting a profile
from multiple availableProfiles.

For https://github.com/unmojang/FjordLauncher/issues/50
2025-01-12 22:54:25 -05:00
Evan Goode
d2d6320e44
README: Don't mention fjordlauncher-bin MPR package
prismlauncher-bin [1] is out of date and so fjordlauncher-bin also hasn't been updated in a while. Not sure whether -bin packages are dangerous on MPR as they are on AUR, so we'll just omit it from the install instructions for now.

[1] https://mpr.makedeb.org/packages/prismlauncher-bin
2025-01-12 04:02:16 +00:00
Evan Goode
8cd807c2d6 Fix FetchFlameAPIKey compile error 2025-01-11 19:37:53 -05:00
Evan Goode
ccc886b51f Re-enable built-in updater 2025-01-11 19:19:36 -05:00
Evan Goode
594fa981ea Merge remote-tracking branch 'prismlauncher/release-9.x' into develop 2025-01-11 19:05:25 -05:00
Trial97
a739db8bdb propagate load component error
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 6ebfcb91cf954048061ff39719629e7f581f1997)
2025-01-08 22:22:36 +00:00
Evan Goode
936252e445
Not yet in nixpkgs
Resolves #46
2024-12-31 11:28:38 -05:00
Alexandru Ionut Tripon
fdfdf2eef1
Merge pull request #3224 from Trial97/backport_fix_auto_mod_provider
[backport]Fix automatically choose mod provider option
2024-12-19 10:34:01 +02:00
Trial97
bbfa5a5ed1
Fix automatically choose mod provider option
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-19 09:23:51 +02:00
Alexandru Ionut Tripon
43dc330e55
Merge pull request #3223 from Trial97/backport_fix_flame_loader_match
[backport]fix the flame loaders match
2024-12-18 23:30:10 +02:00
Trial97
0c962dff59
fix the flame loaders match
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-12-18 22:53:18 +02:00
timoreo
ca0dd583ba
Merge pull request #3222 from PrismLauncher/backport-3197-to-release-9.x
[Backport release-9.x] Do not fail curseforge import if modrinth file check fails
2024-12-18 19:53:32 +01:00
Trial97
7bb686c85b Do not fail curseforge import if modrinth file check fails
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit a1c1c0b769d77eb8c44cef3dc0966e7184b2ce78)
2024-12-18 18:53:13 +00:00
timoreo
f8d3d8399d
Merge pull request #3221 from PrismLauncher/backport-3214-to-release-9.x
[Backport release-9.x] Correct symbolic link destination on manifest (Mojang) Java download
2024-12-18 19:42:58 +01:00
Kenneth Chew
9cb2a3ab2d Correct symbolic link destination on manifest Java download
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
(cherry picked from commit 8d53242952ceeed11bd1a57c9ac43344d854ac8c)
2024-12-18 18:42:39 +00:00
Alexandru Ionut Tripon
072c495ec6
Merge pull request #3206 from PrismLauncher/backport-3202-to-release-9.x
[Backport release-9.x] [macOS] Update Sparkle to v2.6.4
2024-12-14 18:39:04 +02:00
Kenneth Chew
335941fb46 Update Sparkle to v2.6.4
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
(cherry picked from commit b6cd46ad27e8046962dcb7b3b43a973ff08564eb)
2024-12-14 16:36:04 +00:00
Alexandru Ionut Tripon
ebeff36da5
Merge pull request #3205 from PrismLauncher/backport-3199-to-release-9.x
[Backport release-9.x] Fix tab order in launcher settings
2024-12-14 18:34:38 +02:00
Kenneth Chew
1d29f63bec Fix tab order in launcher settings
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
(cherry picked from commit c3712ba6483f6386e2930ec0ed059d8c94f671b6)
2024-12-14 16:32:21 +00:00
Alexandru Ionut Tripon
87dc1bb6a6
Merge pull request #3204 from PrismLauncher/backport-3198-to-release-9.x
[Backport release-9.x] [macOS] Add local network usage description
2024-12-14 18:19:20 +02:00
Alexandru Ionut Tripon
a85720b8d6
Merge pull request #3203 from PrismLauncher/backport-3181-to-release-9.x
[Backport release-9.x] fix crash with invalid mrpack format
2024-12-14 18:19:09 +02:00
Kenneth Chew
4c28a94de1 Add local network usage description
Signed-off-by: Kenneth Chew <79120643+kthchew@users.noreply.github.com>
(cherry picked from commit c3e44554abccfd16ff7f84740932848e59d28474)
2024-12-14 16:16:49 +00:00
Trial97
344ae87f25 fix crash with invalid mrpack format
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 1dd0728a58159bc835b2e7724a707776ae0b93ce)
2024-12-14 16:16:06 +00:00
Alexandru Ionut Tripon
577999bd35
Merge pull request #3183 from PrismLauncher/backport-3170-to-release-9.x
[Backport release-9.x] [Linux] Don't use fallback icon search paths
2024-12-09 09:15:53 +02:00
leia uwu
887db29f35 fix icon theme search paths
using fallback search paths breaks qadwaitadecorations

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

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

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

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

Signed-off-by: leia uwu <leia@tutamail.com>
(cherry picked from commit fd9c80db62436b1a364cb8342ba45a059c53f899)
2024-12-02 14:00:59 +00:00
Evan Goode
6879344334 chore(nix): nixfmt 2024-11-30 17:29:26 -05:00
Evan Goode
a5bca15d46 chore(nix): sync with upstream Prism Launcher 2024-11-30 17:24:43 -05:00
Evan Goode
7b5a2baa36
Merge pull request #42 from ryze312/nix
Nix: Update lock file and switch to addDriverRunpath
2024-11-30 16:20:14 -05:00
Ryze
70e662ca7d
nix: update lock file and switch to addDriverRunpath
Signed-off-by: Ryze <50497128+ryze312@users.noreply.github.com>
2024-11-30 11:49:20 +03:00
Evan Goode
7516b3c70c
Merge pull request #41 from unmojang/evan-goode/select-profile
Add dialog to select profile if multiple availableProfiles
2024-11-27 13:34:10 -05:00
Evan Goode
8f44b6513f Add dialog to select profile if multiple availableProfiles
Resolves https://github.com/unmojang/FjordLauncher/issues/29 and
obsoletes https://github.com/unmojang/FjordLauncher/pull/34.

This implementation follows the authlib-injector specification:
https://github.com/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#%E8%B4%A6%E6%88%B7%E7%9A%84%E6%B7%BB%E5%8A%A0
([Google translate to English](https://github-com.translate.goog/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=en-US))
2024-11-25 17:42:38 -05:00
Alexandru Ionut Tripon
e0c323a190
Merge pull request #3138 from PrismLauncher/backport-3130-to-release-9.x
[Backport release-9.x] Improve MANIFEST.MF parsing
2024-11-25 12:09:01 +02:00
Kationor
a914747416 Improve MANIFEST.MF parsing
Previously, we would only properly parse LF-encoded manifests, and even
those only if they used the recommended casing.

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

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

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

View File

@ -39,9 +39,6 @@ on:
APPLE_NOTARIZE_PASSWORD: APPLE_NOTARIZE_PASSWORD:
description: Password used for notarizing macOS builds description: Password used for notarizing macOS builds
required: false required: false
CACHIX_AUTH_TOKEN:
description: Private token for authenticating against Cachix cache
required: false
GPG_PRIVATE_KEY: GPG_PRIVATE_KEY:
description: Private key for AppImage signing description: Private key for AppImage signing
required: false required: false
@ -59,15 +56,18 @@ jobs:
qt_ver: 5 qt_ver: 5
qt_host: linux qt_host: linux
qt_arch: "" qt_arch: ""
qt_version: "5.12.8" qt_version: "5.15.2"
qt_modules: "qtnetworkauth" qt_modules: "qtnetworkauth"
- os: ubuntu-20.04 - os: ubuntu-22.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.5.3"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
linuxdeploy_hash: "4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage"
linuxdeploy_qt_hash: "15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage"
appimageupdate_hash: "f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage"
- os: windows-2022 - os: windows-2022
name: "Windows-MinGW-w64" name: "Windows-MinGW-w64"
@ -80,9 +80,9 @@ jobs:
architecture: "x64" architecture: "x64"
vcvars_arch: "amd64" vcvars_arch: "amd64"
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: "windows"
qt_arch: "" qt_arch: "win64_msvc2022_64"
qt_version: "6.7.3" qt_version: "6.8.1"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
nscurl_tag: "v24.9.26.122" nscurl_tag: "v24.9.26.122"
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0" nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
@ -93,9 +93,9 @@ jobs:
architecture: "arm64" architecture: "arm64"
vcvars_arch: "amd64_arm64" vcvars_arch: "amd64_arm64"
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: "windows"
qt_arch: "win64_msvc2019_arm64" qt_arch: "win64_msvc2022_arm64_cross_compiled"
qt_version: "6.7.3" qt_version: "6.8.1"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
nscurl_tag: "v24.9.26.122" nscurl_tag: "v24.9.26.122"
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0" nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
@ -106,7 +106,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: mac qt_host: mac
qt_arch: "" qt_arch: ""
qt_version: "6.7.3" qt_version: "6.8.1"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: macos-14 - os: macos-14
@ -173,7 +173,7 @@ jobs:
- name: Retrieve ccache cache (Windows MinGW-w64) - name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
uses: actions/cache@v4.1.1 uses: actions/cache@v4.2.0
with: with:
path: '${{ github.workspace }}\.ccache' path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
@ -206,7 +206,7 @@ jobs:
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: | run: |
sudo apt-get -y update sudo apt-get -y update
sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream libxcb-cursor-dev
- name: Install Dependencies (macOS) - name: Install Dependencies (macOS)
if: runner.os == 'macOS' if: runner.os == 'macOS'
@ -216,14 +216,14 @@ jobs:
- name: Install host Qt (Windows MSVC arm64) - name: Install host Qt (Windows MSVC arm64)
if: runner.os == 'Windows' && matrix.architecture == 'arm64' if: runner.os == 'Windows' && matrix.architecture == 'arm64'
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v4
with: with:
aqtversion: "==3.1.*" aqtversion: "==3.1.*"
py7zrversion: ">=0.20.2" py7zrversion: ">=0.20.2"
version: ${{ matrix.qt_version }} version: ${{ matrix.qt_version }}
host: "windows" host: "windows"
target: "desktop" target: "desktop"
arch: "" arch: ${{ matrix.qt_arch }}
modules: ${{ matrix.qt_modules }} modules: ${{ matrix.qt_modules }}
cache: ${{ inputs.is_qt_cached }} cache: ${{ inputs.is_qt_cached }}
cache-key-prefix: host-qt-arm64-windows cache-key-prefix: host-qt-arm64-windows
@ -232,7 +232,7 @@ jobs:
- name: Install Qt (macOS, Linux & Windows MSVC) - name: Install Qt (macOS, Linux & Windows MSVC)
if: matrix.msystem == '' if: matrix.msystem == ''
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v4
with: with:
aqtversion: "==3.1.*" aqtversion: "==3.1.*"
py7zrversion: ">=0.20.2" py7zrversion: ">=0.20.2"
@ -252,19 +252,26 @@ jobs:
- name: Prepare AppImage (Linux) - name: Prepare AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5 if: runner.os == 'Linux' && matrix.qt_ver != 5
env:
APPIMAGEUPDATE_HASH: ${{ matrix.appimageupdate_hash }}
LINUXDEPLOY_HASH: ${{ matrix.linuxdeploy_hash }}
LINUXDEPLOY_QT_HASH: ${{ matrix.linuxdeploy_qt_hash }}
run: | run: |
wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage"
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage" wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage"
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage" wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage"
sudo apt install libopengl0 sha256sum -c - <<< "$LINUXDEPLOY_HASH"
sha256sum -c - <<< "$LINUXDEPLOY_QT_HASH"
sha256sum -c - <<< "$APPIMAGEUPDATE_HASH"
sudo apt install libopengl0 libfuse2
- name: Add QT_HOST_PATH var (Windows MSVC arm64) - name: Add QT_HOST_PATH var (Windows MSVC arm64)
if: runner.os == 'Windows' && matrix.architecture == 'arm64' if: runner.os == 'Windows' && matrix.architecture == 'arm64'
run: | run: |
echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2019_64" >> $env:GITHUB_ENV echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2022_64" >> $env:GITHUB_ENV
- name: Setup java (macOS) - name: Setup java (macOS)
if: runner.os == 'macOS' if: runner.os == 'macOS'
@ -519,8 +526,8 @@ jobs:
cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
@ -555,9 +562,9 @@ jobs:
mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib
cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
cp /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib cp /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_PORTABLE_DIR }}/lib cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_PORTABLE_DIR }}/lib cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
cp /usr/lib/x86_64-linux-gnu/libffi.so.7 ${{ env.INSTALL_PORTABLE_DIR }}/lib cp /usr/lib/x86_64-linux-gnu/libffi.so.*.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ env.INSTALL_PORTABLE_DIR }}/lib mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ env.INSTALL_PORTABLE_DIR }}/lib
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
@ -629,71 +636,3 @@ jobs:
shell: msys2 {0} shell: msys2 {0}
run: | run: |
ccache -s ccache -s
flatpak:
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:kde-6.7
options: --privileged
steps:
- name: Checkout
uses: actions/checkout@v4
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: "Fjord Launcher.flatpak"
manifest-path: flatpak/org.unmojang.FjordLauncher.yml
nix:
name: Nix (${{ matrix.system }})
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
system: x86_64-linux
- os: macos-13
system: x86_64-darwin
- os: macos-14
system: aarch64-darwin
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v30
# For PRs
- name: Setup Nix Magic Cache
uses: DeterminateSystems/magic-nix-cache-action@v8
# For in-tree builds
- name: Setup Cachix
uses: cachix/cachix-action@v15
with:
name: unmojang
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Run flake checks
run: |
nix flake check --print-build-logs --show-trace
- name: Build debug package
if: ${{ inputs.build_type == 'Debug' }}
run: |
nix build --print-build-logs .#fjordlauncher-debug
- name: Build release package
if: ${{ inputs.build_type != 'Debug' }}
run: |
nix build --print-build-logs .#fjordlauncher

62
.github/workflows/flatpak.yml vendored Normal file
View File

@ -0,0 +1,62 @@
name: Flatpak
on:
push:
paths-ignore:
- "**.md"
- "**/LICENSE"
- ".github/ISSUE_TEMPLATE/**"
- ".markdownlint**"
- "nix/**"
# We don't do anything with these artifacts on releases. They go to Flathub
tags-ignore:
- "*"
pull_request:
paths-ignore:
- "**.md"
- "**/LICENSE"
- ".github/ISSUE_TEMPLATE/**"
- ".markdownlint**"
- "nix/**"
workflow_dispatch:
permissions:
contents: read
jobs:
build:
name: Build (${{ matrix.arch }})
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
arch: x86_64
- os: ubuntu-22.04-arm
arch: aarch64
runs-on: ${{ matrix.os }}
container:
image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.8
options: --privileged
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: true
- name: Set short version
shell: bash
run: |
echo "VERSION=${GITHUB_SHA::7}" >> "$GITHUB_ENV"
- name: Build Flatpak
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: FjordLauncher-${{ runner.os }}-${{ env.VERSION }}-Flatpak.flatpak
manifest-path: flatpak/org.unmojang.FjordLauncher.yml
arch: ${{ matrix.arch }}

88
.github/workflows/nix.yml vendored Normal file
View File

@ -0,0 +1,88 @@
name: Nix
on:
push:
paths-ignore:
- "**.md"
- "**/LICENSE"
- ".github/ISSUE_TEMPLATE/**"
- ".markdownlint**"
- "flatpak/**"
tags:
- "*"
pull_request_target:
paths-ignore:
- "**.md"
- "**/LICENSE"
- ".github/ISSUE_TEMPLATE/**"
- ".markdownlint**"
- "flatpak/**"
workflow_dispatch:
permissions:
contents: read
env:
DEBUG: ${{ github.ref_type != 'tag' }}
USE_DETERMINATE: ${{ github.event_name == 'pull_request' }}
jobs:
build:
name: Build (${{ matrix.system }})
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
system: x86_64-linux
- os: ubuntu-22.04-arm
system: aarch64-linux
- os: macos-13
system: x86_64-darwin
- os: macos-14
system: aarch64-darwin
runs-on: ${{ matrix.os }}
permissions:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v16
with:
determinate: ${{ env.USE_DETERMINATE }}
# For PRs
- name: Setup Nix Magic Cache
if: ${{ env.USE_DETERMINATE }}
uses: DeterminateSystems/flakehub-cache-action@v1
# For in-tree builds
- name: Setup Cachix
if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
uses: cachix/cachix-action@v15
with:
name: unmojang
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Run Flake checks
run: |
nix flake check --print-build-logs --show-trace
- name: Build debug package
if: ${{ env.DEBUG }}
run: |
nix build --print-build-logs .#fjordlauncher-debug
- name: Build release package
if: ${{ !env.DEBUG }}
run: |
nix build --print-build-logs .#fjordlauncher

45
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Publish
on:
release:
types: [ released ]
permissions:
contents: read
jobs:
flakehub:
name: FlakeHub
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
- name: Install Nix
uses: cachix/install-nix-action@v30
- name: Publish on FlakeHub
uses: determinatesystems/flakehub-push@v5
with:
visibility: "public"
winget:
name: Winget
runs-on: windows-latest
steps:
- name: Publish on Winget
uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: PrismLauncher.PrismLauncher
version: ${{ github.event.release.tag_name }}
installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$'
token: ${{ secrets.WINGET_TOKEN }}

View File

@ -38,6 +38,5 @@ jobs:
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }} APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }} APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }} APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }} GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}

View File

@ -22,7 +22,6 @@ jobs:
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }} APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }} APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }} APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }} GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}

View File

@ -1,15 +0,0 @@
name: Publish to WinGet
on:
release:
types: [released]
jobs:
publish:
runs-on: windows-latest
steps:
- uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: PrismLauncher.PrismLauncher
version: ${{ github.event.release.tag_name }}
installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$'
token: ${{ secrets.WINGET_TOKEN }}

6
.gitignore vendored
View File

@ -47,8 +47,12 @@ run/
# Nix/NixOS # Nix/NixOS
.direnv/ .direnv/
.pre-commit-config.yaml ## Used when manually invoking stdenv phases
outputs/
## Regular artifacts
result result
result-*
repl-result-*
# Flatpak # Flatpak
.flatpak-builder .flatpak-builder

View File

@ -78,6 +78,13 @@ else()
# ATL's pack list needs more than the default 1 Mib stack on windows # ATL's pack list needs more than the default 1 Mib stack on windows
if(WIN32) if(WIN32)
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}") set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}")
# -ffunction-sections and -fdata-sections help reduce binary size
# -mguard=cf enables Control Flow Guard
# TODO: Look into -gc-sections to further reduce binary size
foreach(lang C CXX)
set("CMAKE_${lang}_FLAGS_RELEASE" "-ffunction-sections -fdata-sections -mguard=cf")
endforeach()
endif() endif()
endif() endif()
@ -106,14 +113,14 @@ if ((CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebI
else() else()
# AppleClang and Clang # AppleClang and Clang
message(STATUS "Address Sanitizer available on Clang") message(STATUS "Address Sanitizer available on Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=null")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=null")
endif() endif()
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
# GCC # GCC
message(STATUS "Address Sanitizer available on GCC") message(STATUS "Address Sanitizer available on GCC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover")
link_libraries("asan") link_libraries("asan")
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
message(STATUS "Address Sanitizer available on MSVC") message(STATUS "Address Sanitizer available on MSVC")
@ -181,7 +188,7 @@ set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE S
######## Set version numbers ######## ######## Set version numbers ########
set(Launcher_VERSION_MAJOR 9) set(Launcher_VERSION_MAJOR 9)
set(Launcher_VERSION_MINOR 1) set(Launcher_VERSION_MINOR 3)
set(Launcher_VERSION_PATCH 0) set(Launcher_VERSION_PATCH 0)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}") set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}")
@ -192,7 +199,7 @@ set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_M
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 "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
# Github repo URL with releases for updater # Github repo URL with releases for updater
set(Launcher_UPDATER_GITHUB_REPO "" CACHE STRING "Base github URL for the updater.") set(Launcher_UPDATER_GITHUB_REPO "https://github.com/unmojang/FjordLauncher" CACHE STRING "Base github URL for the updater.")
# Name to help updater identify valid artifacts # Name to help updater identify valid artifacts
set(Launcher_BUILD_ARTIFACT "" CACHE STRING "Artifact name to help the updater identify valid artifacts.") set(Launcher_BUILD_ARTIFACT "" CACHE STRING "Artifact name to help the updater identify valid artifacts.")
@ -390,8 +397,8 @@ if(UNIX AND APPLE)
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "" CACHE STRING "Public key for Sparkle update feed") set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "" CACHE STRING "Public key for Sparkle update feed")
set(MACOSX_SPARKLE_UPDATE_FEED_URL "" CACHE STRING "URL for Sparkle update feed") set(MACOSX_SPARKLE_UPDATE_FEED_URL "" CACHE STRING "URL for Sparkle update feed")
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.5.2/Sparkle-2.5.2.tar.xz" CACHE STRING "URL to Sparkle release archive") set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.6.4/Sparkle-2.6.4.tar.xz" CACHE STRING "URL to Sparkle release archive")
set(MACOSX_SPARKLE_SHA256 "572dd67ae398a466f19f343a449e1890bac1ef74885b4739f68f979a8a89884b" CACHE STRING "SHA256 checksum for Sparkle release archive") set(MACOSX_SPARKLE_SHA256 "50612a06038abc931f16011d7903b8326a362c1074dabccb718404ce8e585f0b" CACHE STRING "SHA256 checksum for Sparkle release archive")
set(MACOSX_SPARKLE_DIR "${CMAKE_BINARY_DIR}/frameworks/Sparkle") set(MACOSX_SPARKLE_DIR "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
# directories to look for dependencies # directories to look for dependencies

View File

@ -1,7 +1,7 @@
## Fjord Launcher ## Fjord Launcher
Fjord Launcher - Minecraft Launcher Fjord Launcher - Minecraft Launcher
Copyright (C) 2024-2024 Fjord Launcher Contributors Copyright (C) 2024-2025 Fjord Launcher Contributors
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
@ -69,7 +69,7 @@
## Prism Launcher ## Prism Launcher
Prism Launcher - Minecraft Launcher Prism Launcher - Minecraft Launcher
Copyright (C) 2022-2024 Prism Launcher Contributors Copyright (C) 2022-2025 Prism Launcher Contributors
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

View File

@ -72,7 +72,6 @@ Install from the MPR with [Mist](https://docs.makedeb.org/using-the-mpr/mist-the
```Shell ```Shell
mist install fjordlauncher mist install fjordlauncher
mist install fjordlauncher-bin # binary package
mist install fjordlauncher-git # build latest Git commit from source mist install fjordlauncher-git # build latest Git commit from source
``` ```

View File

@ -181,7 +181,7 @@ class Config {
QString FMLLIBS_BASE_URL; QString FMLLIBS_BASE_URL;
QString TRANSLATION_FILES_URL; QString TRANSLATION_FILES_URL;
QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/"; QString MODPACKSCH_API_BASE_URL = "https://api.feed-the-beast.com/v1/modpacks/";
QString LEGACY_FTB_CDN_BASE_URL = "https://dist.creeper.host/FTB2/"; QString LEGACY_FTB_CDN_BASE_URL = "https://dist.creeper.host/FTB2/";

View File

@ -8,6 +8,8 @@
<string>A Minecraft mod wants to access your microphone.</string> <string>A Minecraft mod wants to access your microphone.</string>
<key>NSDownloadsFolderUsageDescription</key> <key>NSDownloadsFolderUsageDescription</key>
<string>Fjord uses access to your Downloads folder to help you more quickly add mods that can't be automatically downloaded to your instance. You can change where Fjord scans for downloaded mods in Settings or the prompt that appears.</string> <string>Fjord uses access to your Downloads folder to help you more quickly add mods that can't be automatically downloaded to your instance. You can change where Fjord scans for downloaded mods in Settings or the prompt that appears.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Minecraft uses the local network to find and connect to LAN servers.</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>
<string>NSApplication</string> <string>NSApplication</string>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>

View File

@ -1,9 +1,4 @@
(import ( (import (fetchTarball {
let url = "https://github.com/edolstra/flake-compat/archive/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec.tar.gz";
lock = builtins.fromJSON (builtins.readFile ./flake.lock); sha256 = "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=";
in }) { src = ./.; }).defaultNix
fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash;
}
) { src = ./.; }).defaultNix

39
flake.lock generated
View File

@ -1,21 +1,5 @@
{ {
"nodes": { "nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"libnbtplusplus": { "libnbtplusplus": {
"flake": false, "flake": false,
"locked": { "locked": {
@ -32,28 +16,13 @@
"type": "github" "type": "github"
} }
}, },
"nix-filter": {
"locked": {
"lastModified": 1710156097,
"narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "3342559a24e85fc164b295c3444e8a139924675b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "nix-filter",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1729256560, "lastModified": 1743827369,
"narHash": "sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c+cHUJwA=", "narHash": "sha256-rpqepOZ8Eo1zg+KJeWoq1HAOgoMCDloqv5r2EAa9TSA=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0", "rev": "42a1c966be226125b48c384171c44c651c236c22",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -65,9 +34,7 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"flake-compat": "flake-compat",
"libnbtplusplus": "libnbtplusplus", "libnbtplusplus": "libnbtplusplus",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
} }
} }

155
flake.nix
View File

@ -15,28 +15,6 @@
url = "github:PrismLauncher/libnbtplusplus"; url = "github:PrismLauncher/libnbtplusplus";
flake = false; flake = false;
}; };
nix-filter.url = "github:numtide/nix-filter";
/*
Inputs below this are optional and can be removed
```
{
inputs.fjordlauncher = {
url = "github:unmojang/FjordLauncher";
inputs = {
flake-compat.follows = "";
};
};
}
```
*/
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
}; };
outputs = outputs =
@ -44,9 +22,8 @@
self, self,
nixpkgs, nixpkgs,
libnbtplusplus, libnbtplusplus,
nix-filter,
...
}: }:
let let
inherit (nixpkgs) lib; inherit (nixpkgs) lib;
@ -58,53 +35,128 @@
forAllSystems = lib.genAttrs systems; forAllSystems = lib.genAttrs systems;
nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system}); nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system});
in in
{ {
checks = forAllSystems ( checks = forAllSystems (
system: system:
let let
checks' = nixpkgsFor.${system}.callPackage ./nix/checks.nix { inherit self; }; pkgs = nixpkgsFor.${system};
llvm = pkgs.llvmPackages_19;
in in
lib.filterAttrs (_: lib.isDerivation) checks'
{
formatting =
pkgs.runCommand "check-formatting"
{
nativeBuildInputs = with pkgs; [
deadnix
llvm.clang-tools
markdownlint-cli
nixfmt-rfc-style
statix
];
}
''
cd ${self}
echo "Running clang-format...."
clang-format --dry-run --style='file' --Werror */**.{c,cc,cpp,h,hh,hpp}
echo "Running deadnix..."
deadnix --fail
echo "Running markdownlint..."
markdownlint --dot .
echo "Running nixfmt..."
find -type f -name '*.nix' -exec nixfmt --check {} +
echo "Running statix"
statix check .
touch $out
'';
}
); );
devShells = forAllSystems ( devShells = forAllSystems (
system: system:
let let
pkgs = nixpkgsFor.${system}; pkgs = nixpkgsFor.${system};
llvm = pkgs.llvmPackages_19;
packages' = self.packages.${system};
# Re-use our package wrapper to wrap our development environment
qt-wrapper-env = packages'.fjordlauncher.overrideAttrs (old: {
name = "qt-wrapper-env";
# Required to use script-based makeWrapper below
strictDeps = true;
# We don't need/want the unwrapped Fjord package
paths = [ ];
nativeBuildInputs = old.nativeBuildInputs or [ ] ++ [
# Ensure the wrapper is script based so it can be sourced
pkgs.makeWrapper
];
# Inspired by https://discourse.nixos.org/t/python-qt-woes/11808/10
buildCommand = ''
makeQtWrapper ${lib.getExe pkgs.runtimeShellPackage} "$out"
sed -i '/^exec/d' "$out"
'';
});
in in
{ {
default = pkgs.mkShell { default = pkgs.mkShell {
inputsFrom = [ self.packages.${system}.fjordlauncher-unwrapped ]; inputsFrom = [ packages'.fjordlauncher-unwrapped ];
buildInputs = with pkgs; [
packages = with pkgs; [
ccache ccache
ninja llvm.clang-tools
]; ];
cmakeBuildType = "Debug";
cmakeFlags = [ "-GNinja" ] ++ packages'.fjordlauncher.cmakeFlags;
dontFixCmake = true;
shellHook = ''
echo "Sourcing ${qt-wrapper-env}"
source ${qt-wrapper-env}
git submodule update --init --force
if [ ! -f compile_commands.json ]; then
cmakeConfigurePhase
cd ..
ln -s "$cmakeBuildDir"/compile_commands.json compile_commands.json
fi
'';
}; };
} }
); );
formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style); formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style);
overlays.default = overlays.default = final: prev: {
final: prev: fjordlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
let inherit
version = builtins.substring 0 8 self.lastModifiedDate or "dirty"; libnbtplusplus
in self
{ ;
fjordlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
inherit
libnbtplusplus
nix-filter
self
version
;
};
fjordlauncher = final.callPackage ./nix/wrapper.nix { };
}; };
fjordlauncher = final.callPackage ./nix/wrapper.nix { };
};
packages = forAllSystems ( packages = forAllSystems (
system: system:
let let
pkgs = nixpkgsFor.${system}; pkgs = nixpkgsFor.${system};
@ -117,6 +169,7 @@
default = fjordPackages.fjordlauncher; default = fjordPackages.fjordlauncher;
}; };
in in
# Only output them if they're available on the current system # Only output them if they're available on the current system
lib.filterAttrs (_: lib.meta.availableOn pkgs.stdenv.hostPlatform) packages lib.filterAttrs (_: lib.meta.availableOn pkgs.stdenv.hostPlatform) packages
); );
@ -124,16 +177,18 @@
# We put these under legacyPackages as they are meant for CI, not end user consumption # We put these under legacyPackages as they are meant for CI, not end user consumption
legacyPackages = forAllSystems ( legacyPackages = forAllSystems (
system: system:
let let
fjordPackages = self.packages.${system}; packages' = self.packages.${system};
legacyPackages = self.legacyPackages.${system}; legacyPackages' = self.legacyPackages.${system};
in in
{ {
fjordlauncher-debug = fjordPackages.fjordlauncher.override { fjordlauncher-debug = packages'.fjordlauncher.override {
fjordlauncher-unwrapped = legacyPackages.fjordlauncher-unwrapped-debug; fjordlauncher-unwrapped = legacyPackages'.fjordlauncher-unwrapped-debug;
}; };
fjordlauncher-unwrapped-debug = fjordPackages.fjordlauncher-unwrapped.overrideAttrs { fjordlauncher-unwrapped-debug = packages'.fjordlauncher-unwrapped.overrideAttrs {
cmakeBuildType = "Debug"; cmakeBuildType = "Debug";
dontStrip = true; dontStrip = true;
}; };

20
flatpak/flite.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "flite",
"config-opts": [
"--enable-shared",
"--with-audio=pulseaudio"
],
"no-parallel-make": true,
"sources": [
{
"type": "git",
"url": "https://github.com/festvox/flite.git",
"tag": "v2.2",
"commit": "e9e2e37c329dbe98bfeb27a1828ef9a71fa84f88",
"x-checker-data": {
"type": "git",
"tag-pattern": "^v([\\d.]+)$"
}
}
]
}

View File

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

View File

@ -1,6 +1,6 @@
id: org.unmojang.FjordLauncher id: org.unmojang.FjordLauncher
runtime: org.kde.Platform runtime: org.kde.Platform
runtime-version: 6.7 runtime-version: '6.8'
sdk: org.kde.Sdk sdk: org.kde.Sdk
sdk-extensions: sdk-extensions:
- org.freedesktop.Sdk.Extension.openjdk17 - org.freedesktop.Sdk.Extension.openjdk17
@ -19,6 +19,12 @@ finish-args:
- --filesystem=xdg-download:ro - --filesystem=xdg-download:ro
# FTBApp import # FTBApp import
- --filesystem=~/.ftba:ro - --filesystem=~/.ftba:ro
# Userspace visibility for manual hugepages configuration
# Required for -XX:+UseLargePages
- --filesystem=/sys/kernel/mm/hugepages:ro
# Userspace visibility for transparent hugepages configuration
# Required for -XX:+UseTransparentHugePages
- --filesystem=/sys/kernel/mm/transparent_hugepage:ro
modules: modules:
# Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31) # Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31)
@ -27,11 +33,16 @@ modules:
# Needed for proper Wayland support # Needed for proper Wayland support
- libdecor.json - libdecor.json
# Text to Speech in the game
- flite.json
- name: fjordlauncher - name: fjordlauncher
buildsystem: cmake-ninja buildsystem: cmake-ninja
builddir: true builddir: true
config-opts: config-opts:
- -DLauncher_BUILD_PLATFORM=flatpak - -DLauncher_BUILD_PLATFORM=flatpak
# This allows us to manage and update Java independently of this Flatpak
- -DLauncher_ENABLE_JAVA_DOWNLOADER=ON
- -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DCMAKE_BUILD_TYPE=RelWithDebInfo
build-options: build-options:
env: env:
@ -47,18 +58,14 @@ modules:
config-opts: config-opts:
- -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DCMAKE_BUILD_TYPE=RelWithDebInfo
- -DBUILD_SHARED_LIBS:BOOL=ON - -DBUILD_SHARED_LIBS:BOOL=ON
- -DGLFW_USE_WAYLAND:BOOL=ON - -DGLFW_BUILD_WAYLAND:BOOL=ON
- -DGLFW_BUILD_DOCS:BOOL=OFF - -DGLFW_BUILD_DOCS:BOOL=OFF
sources: sources:
- type: git - type: git
url: https://github.com/glfw/glfw.git url: https://github.com/glfw/glfw.git
commit: 3fa2360720eeba1964df3c0ecf4b5df8648a8e52 commit: 7b6aead9fb88b3623e3b3725ebb42670cbe4c579 # 3.4
- type: patch - type: patch
path: patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch path: patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.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: cleanup:
- /include - /include
- /lib/cmake - /lib/cmake
@ -68,8 +75,8 @@ modules:
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.3.tar.xz
sha256: c8bee4790d9058bacc4b6246456c58021db58a87ddda1a9d0139bf5f18f1f240 sha256: f8dd7566adb74147fab9964680b6bbadee87cf406a7fcff51718a5e6949b841c
x-checker-data: x-checker-data:
type: anitya type: anitya
project-id: 14957 project-id: 14957
@ -91,8 +98,8 @@ modules:
sources: sources:
- type: archive - type: archive
dest-filename: gamemode.tar.gz dest-filename: gamemode.tar.gz
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.1 url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.2
sha256: 969cf85b5ca3944f3e315cd73a0ee9bea4f9c968cd7d485e9f4745bc1e679c4e sha256: 2886d4ce543c78bd2a364316d5e7fd59ef06b71de63f896b37c6d3dc97658f60
x-checker-data: x-checker-data:
type: json type: json
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest

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

@ -0,0 +1,59 @@
From 9997ae55a47de469ea26f8437c30b51483abda5f Mon Sep 17 00:00:00 2001
From: Dan Klishch <danilklishch@gmail.com>
Date: Sat, 30 Sep 2023 23:38:05 -0400
Subject: Defer setting cursor position until the cursor is locked
---
src/wl_platform.h | 3 +++
src/wl_window.c | 14 ++++++++++++--
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/src/wl_platform.h b/src/wl_platform.h
index ca34f66e..cd1f227f 100644
--- a/src/wl_platform.h
+++ b/src/wl_platform.h
@@ -403,6 +403,9 @@ typedef struct _GLFWwindowWayland
int scaleSize;
int compositorPreferredScale;
+ double askedCursorPosX, askedCursorPosY;
+ GLFWbool didAskForSetCursorPos;
+
struct zwp_relative_pointer_v1* relativePointer;
struct zwp_locked_pointer_v1* lockedPointer;
struct zwp_confined_pointer_v1* confinedPointer;
diff --git a/src/wl_window.c b/src/wl_window.c
index 1de26558..0df16747 100644
--- a/src/wl_window.c
+++ b/src/wl_window.c
@@ -2586,8 +2586,9 @@ void _glfwGetCursorPosWayland(_GLFWwindow* window, double* xpos, double* ypos)
void _glfwSetCursorPosWayland(_GLFWwindow* window, double x, double y)
{
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
- "Wayland: The platform does not support setting the cursor position");
+ window->wl.didAskForSetCursorPos = true;
+ window->wl.askedCursorPosX = x;
+ window->wl.askedCursorPosY = y;
}
void _glfwSetCursorModeWayland(_GLFWwindow* window, int mode)
@@ -2819,6 +2820,15 @@ static const struct zwp_relative_pointer_v1_listener relativePointerListener =
static void lockedPointerHandleLocked(void* userData,
struct zwp_locked_pointer_v1* lockedPointer)
{
+ _GLFWwindow* window = userData;
+
+ if (window->wl.didAskForSetCursorPos)
+ {
+ window->wl.didAskForSetCursorPos = false;
+ zwp_locked_pointer_v1_set_cursor_position_hint(window->wl.lockedPointer,
+ wl_fixed_from_double(window->wl.askedCursorPosX),
+ wl_fixed_from_double(window->wl.askedCursorPosY));
+ }
}
static void lockedPointerHandleUnlocked(void* userData,
--
2.42.0

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 *

@ -1 +1 @@
Subproject commit f2b0c16a2a217a1822ce5a6538ba8f755ed1dd32 Subproject commit f5d368a31d6ef046eb2955c74ec6f54f32ed5c4e

View File

@ -48,6 +48,7 @@
#include "net/PasteUpload.h" #include "net/PasteUpload.h"
#include "pathmatcher/MultiMatcher.h" #include "pathmatcher/MultiMatcher.h"
#include "pathmatcher/SimplePrefixMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h"
#include "tasks/Task.h"
#include "tools/GenericProfiler.h" #include "tools/GenericProfiler.h"
#include "ui/InstanceWindow.h" #include "ui/InstanceWindow.h"
#include "ui/MainWindow.h" #include "ui/MainWindow.h"
@ -159,6 +160,7 @@
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
#include <windows.h> #include <windows.h>
#include <QStyleHints>
#include "WindowsConsole.h" #include "WindowsConsole.h"
#endif #endif
@ -230,7 +232,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString())); setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()));
setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT); setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT);
setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME); setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
startTime = QDateTime::currentDateTime(); m_startTime = QDateTime::currentDateTime();
// Don't quit on hiding the last window // Don't quit on hiding the last window
this->setQuitOnLastWindowClosed(false); this->setQuitOnLastWindowClosed(false);
@ -1092,6 +1094,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
bool Application::createSetupWizard() bool Application::createSetupWizard()
{ {
bool javaRequired = [&]() { bool javaRequired = [&]() {
if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_settings->get("AutomaticJavaDownload").toBool()) {
return false;
}
bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool(); bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool();
if (ignoreJavaWizard) { if (ignoreJavaWizard) {
return false; return false;
@ -1104,10 +1109,7 @@ bool Application::createSetupWizard()
} }
QString currentJavaPath = settings()->get("JavaPath").toString(); QString currentJavaPath = settings()->get("JavaPath").toString();
QString actualPath = FS::ResolveExecutable(currentJavaPath); QString actualPath = FS::ResolveExecutable(currentJavaPath);
if (actualPath.isNull()) { return actualPath.isNull();
return true;
}
return false;
}(); }();
bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() && bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() &&
!m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool(); !m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool();
@ -1122,8 +1124,16 @@ bool Application::createSetupWizard()
// set default theme after going into theme wizard // set default theme after going into theme wizard
if (!validIcons) if (!validIcons)
settings()->set("IconTheme", QString("pe_colored")); settings()->set("IconTheme", QString("pe_colored"));
if (!validWidgets) if (!validWidgets) {
settings()->set("ApplicationTheme", QString("system")); #if defined(Q_OS_WIN32) && QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
const QString style =
QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark ? QStringLiteral("dark") : QStringLiteral("bright");
#else
const QString style = QStringLiteral("system");
#endif
settings()->set("ApplicationTheme", style);
}
m_themeManager->applyCurrentlySelectedTheme(true); m_themeManager->applyCurrentlySelectedTheme(true);
@ -1190,6 +1200,9 @@ bool Application::event(QEvent* event)
#endif #endif
if (event->type() == QEvent::FileOpen) { if (event->type() == QEvent::FileOpen) {
if (!m_mainWindow) {
showMainWindow(false);
}
auto ev = static_cast<QFileOpenEvent*>(event); auto ev = static_cast<QFileOpenEvent*>(event);
m_mainWindow->processURLs({ ev->url() }); m_mainWindow->processURLs({ ev->url() });
} }
@ -1348,6 +1361,9 @@ void Application::messageReceived(const QByteArray& message)
qWarning() << "Received" << command << "message without a zip path/URL."; qWarning() << "Received" << command << "message without a zip path/URL.";
return; return;
} }
if (!m_mainWindow) {
showMainWindow(false);
}
m_mainWindow->processURLs({ normalizeImportUrl(url) }); m_mainWindow->processURLs({ normalizeImportUrl(url) });
} else if (command == "launch") { } else if (command == "launch") {
QString id = received.args["id"]; QString id = received.args["id"];
@ -1426,6 +1442,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
if (m_updateRunning) { if (m_updateRunning) {
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed."; qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
} else if (instance->canLaunch()) { } else if (instance->canLaunch()) {
QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[instance->id()]; auto& extras = m_instanceExtras[instance->id()];
auto window = extras.window; auto window = extras.window;
if (window) { if (window) {
@ -1450,7 +1467,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed); connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed);
connect(controller.get(), &LaunchController::aborted, this, [this] { controllerFailed(tr("Aborted")); }); connect(controller.get(), &LaunchController::aborted, this, [this] { controllerFailed(tr("Aborted")); });
addRunningInstance(); addRunningInstance();
controller->start(); QMetaObject::invokeMethod(controller.get(), &Task::start, Qt::QueuedConnection);
return true; return true;
} else if (instance->isRunning()) { } else if (instance->isRunning()) {
showInstanceWindow(instance, "console"); showInstanceWindow(instance, "console");
@ -1468,9 +1485,11 @@ bool Application::kill(InstancePtr instance)
qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running."; qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running.";
return false; return false;
} }
QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[instance->id()]; auto& extras = m_instanceExtras[instance->id()];
// NOTE: copy of the shared pointer keeps it alive // NOTE: copy of the shared pointer keeps it alive
auto controller = extras.controller; auto controller = extras.controller;
locker.unlock();
if (controller) { if (controller) {
return controller->abort(); return controller->abort();
} }
@ -1524,12 +1543,14 @@ void Application::controllerSucceeded()
if (!controller) if (!controller)
return; return;
auto id = controller->id(); auto id = controller->id();
QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[id]; auto& extras = m_instanceExtras[id];
// on success, do... // on success, do...
if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) { if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) {
if (extras.window) { if (extras.window) {
extras.window->close(); QMetaObject::invokeMethod(extras.window, &QWidget::close, Qt::QueuedConnection);
} }
} }
extras.controller.reset(); extras.controller.reset();
@ -1549,6 +1570,7 @@ void Application::controllerFailed(const QString& error)
if (!controller) if (!controller)
return; return;
auto id = controller->id(); auto id = controller->id();
QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[id]; auto& extras = m_instanceExtras[id];
// on failure, do... nothing // on failure, do... nothing
@ -1606,6 +1628,7 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
if (!instance) if (!instance)
return nullptr; return nullptr;
auto id = instance->id(); auto id = instance->id();
QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[id]; auto& extras = m_instanceExtras[id];
auto& window = extras.window; auto& window = extras.window;
@ -1643,6 +1666,7 @@ void Application::on_windowClose()
m_openWindows--; m_openWindows--;
auto instWindow = qobject_cast<InstanceWindow*>(QObject::sender()); auto instWindow = qobject_cast<InstanceWindow*>(QObject::sender());
if (instWindow) { if (instWindow) {
QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[instWindow->instanceId()]; auto& extras = m_instanceExtras[instWindow->instanceId()];
extras.window = nullptr; extras.window = nullptr;
if (extras.controller) { if (extras.controller) {
@ -1890,7 +1914,7 @@ bool Application::handleDataMigration(const QString& currentData,
matcher->add(std::make_shared<SimplePrefixMatcher>("themes/")); matcher->add(std::make_shared<SimplePrefixMatcher>("themes/"));
ProgressDialog diag; ProgressDialog diag;
DataMigrationTask task(nullptr, oldData, currentData, matcher); DataMigrationTask task(oldData, currentData, matcher);
if (diag.execWithTask(&task)) { if (diag.execWithTask(&task)) {
qDebug() << "<> Migration succeeded"; qDebug() << "<> Migration succeeded";
setDoNotMigrate(); setDoNotMigrate();
@ -1929,3 +1953,31 @@ const QString Application::javaPath()
{ {
return m_settings->get("JavaDir").toString(); return m_settings->get("JavaDir").toString();
} }
void Application::addQSavePath(QString path)
{
QMutexLocker locker(&m_qsaveResourcesMutex);
m_qsaveResources[path] = m_qsaveResources.value(path, 0) + 1;
}
void Application::removeQSavePath(QString path)
{
QMutexLocker locker(&m_qsaveResourcesMutex);
auto count = m_qsaveResources.value(path, 0) - 1;
if (count <= 0) {
m_qsaveResources.remove(path);
} else {
m_qsaveResources[path] = count;
}
}
bool Application::checkQSavePath(QString path)
{
QMutexLocker locker(&m_qsaveResourcesMutex);
for (auto partialPath : m_qsaveResources.keys()) {
if (path.startsWith(partialPath) && m_qsaveResources.value(partialPath, 0) > 0) {
return true;
}
}
return false;
}

View File

@ -42,6 +42,7 @@
#include <QDebug> #include <QDebug>
#include <QFlag> #include <QFlag>
#include <QIcon> #include <QIcon>
#include <QMutex>
#include <QUrl> #include <QUrl>
#include <memory> #include <memory>
@ -111,7 +112,7 @@ class Application : public QApplication {
std::shared_ptr<SettingsObject> settings() const { return m_settings; } std::shared_ptr<SettingsObject> settings() const { return m_settings; }
qint64 timeSinceStart() const { return startTime.msecsTo(QDateTime::currentDateTime()); } qint64 timeSinceStart() const { return m_startTime.msecsTo(QDateTime::currentDateTime()); }
QIcon getThemedIcon(const QString& name); QIcon getThemedIcon(const QString& name);
@ -235,7 +236,7 @@ class Application : public QApplication {
bool shouldExitNow() const; bool shouldExitNow() const;
private: private:
QDateTime startTime; QDateTime m_startTime;
shared_qobject_ptr<QNetworkAccessManager> m_network; shared_qobject_ptr<QNetworkAccessManager> m_network;
@ -278,6 +279,7 @@ class Application : public QApplication {
shared_qobject_ptr<LaunchController> controller; shared_qobject_ptr<LaunchController> controller;
}; };
std::map<QString, InstanceXtras> m_instanceExtras; std::map<QString, InstanceXtras> m_instanceExtras;
mutable QMutex m_instanceExtrasMutex;
// main state variables // main state variables
size_t m_openWindows = 0; size_t m_openWindows = 0;
@ -303,4 +305,13 @@ class Application : public QApplication {
QList<QUrl> m_urlsToImport; QList<QUrl> m_urlsToImport;
QString m_instanceIdToShowWindowOf; QString m_instanceIdToShowWindowOf;
std::unique_ptr<QFile> logFile; std::unique_ptr<QFile> logFile;
public:
void addQSavePath(QString);
void removeQSavePath(QString);
bool checkQSavePath(QString);
private:
QHash<QString, int> m_qsaveResources;
mutable QMutex m_qsaveResourcesMutex;
}; };

View File

@ -30,6 +30,7 @@ set(CORE_SOURCES
StringUtils.cpp StringUtils.cpp
QVariantUtils.h QVariantUtils.h
RuntimeContext.h RuntimeContext.h
PSaveFile.h
# Basic instance manipulation tasks (derived from InstanceTask) # Basic instance manipulation tasks (derived from InstanceTask)
InstanceCreationTask.h InstanceCreationTask.h
@ -237,6 +238,8 @@ set(MINECRAFT_SOURCES
minecraft/auth/AuthFlow.cpp minecraft/auth/AuthFlow.cpp
minecraft/auth/AuthFlow.h minecraft/auth/AuthFlow.h
minecraft/auth/steps/AuthlibInjectorMetadataStep.cpp
minecraft/auth/steps/AuthlibInjectorMetadataStep.h
minecraft/auth/steps/EntitlementsStep.cpp minecraft/auth/steps/EntitlementsStep.cpp
minecraft/auth/steps/EntitlementsStep.h minecraft/auth/steps/EntitlementsStep.h
minecraft/auth/steps/GetSkinStep.cpp minecraft/auth/steps/GetSkinStep.cpp
@ -384,6 +387,10 @@ set(MINECRAFT_SOURCES
minecraft/AssetsUtils.cpp minecraft/AssetsUtils.cpp
# Minecraft skins # Minecraft skins
minecraft/skins/AuthlibInjectorTextureDelete.cpp
minecraft/skins/AuthlibInjectorTextureDelete.h
minecraft/skins/AuthlibInjectorTextureUpload.cpp
minecraft/skins/AuthlibInjectorTextureUpload.h
minecraft/skins/CapeChange.cpp minecraft/skins/CapeChange.cpp
minecraft/skins/CapeChange.h minecraft/skins/CapeChange.h
minecraft/skins/SkinUpload.cpp minecraft/skins/SkinUpload.cpp
@ -1059,8 +1066,6 @@ SET(LAUNCHER_SOURCES
ui/dialogs/CopyInstanceDialog.h ui/dialogs/CopyInstanceDialog.h
ui/dialogs/CustomMessageBox.cpp ui/dialogs/CustomMessageBox.cpp
ui/dialogs/CustomMessageBox.h ui/dialogs/CustomMessageBox.h
ui/dialogs/EditAccountDialog.cpp
ui/dialogs/EditAccountDialog.h
ui/dialogs/ExportInstanceDialog.cpp ui/dialogs/ExportInstanceDialog.cpp
ui/dialogs/ExportInstanceDialog.h ui/dialogs/ExportInstanceDialog.h
ui/dialogs/ExportPackDialog.cpp ui/dialogs/ExportPackDialog.cpp
@ -1250,7 +1255,6 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/OfflineLoginDialog.ui ui/dialogs/OfflineLoginDialog.ui
ui/dialogs/AuthlibInjectorLoginDialog.ui ui/dialogs/AuthlibInjectorLoginDialog.ui
ui/dialogs/AboutDialog.ui ui/dialogs/AboutDialog.ui
ui/dialogs/EditAccountDialog.ui
ui/dialogs/ReviewMessageBox.ui ui/dialogs/ReviewMessageBox.ui
ui/dialogs/ScrollMessageBox.ui ui/dialogs/ScrollMessageBox.ui
ui/dialogs/BlockedModsDialog.ui ui/dialogs/BlockedModsDialog.ui

View File

@ -12,11 +12,8 @@
#include <QtConcurrent> #include <QtConcurrent>
DataMigrationTask::DataMigrationTask(QObject* parent, DataMigrationTask::DataMigrationTask(const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathMatcher)
const QString& sourcePath, : Task(), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
const QString& targetPath,
const IPathMatcher::Ptr pathMatcher)
: Task(parent), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
{ {
m_copy.matcher(m_pathMatcher.get()).whitelist(true); m_copy.matcher(m_pathMatcher.get()).whitelist(true);
} }

View File

@ -18,7 +18,7 @@
class DataMigrationTask : public Task { class DataMigrationTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher); explicit DataMigrationTask(const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
~DataMigrationTask() override = default; ~DataMigrationTask() override = default;
protected: protected:

View File

@ -45,7 +45,6 @@
#include <QDirIterator> #include <QDirIterator>
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QSaveFile>
#include <QStandardPaths> #include <QStandardPaths>
#include <QStorageInfo> #include <QStorageInfo>
#include <QTextStream> #include <QTextStream>
@ -54,6 +53,7 @@
#include <system_error> #include <system_error>
#include "DesktopServices.h" #include "DesktopServices.h"
#include "PSaveFile.h"
#include "StringUtils.h" #include "StringUtils.h"
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
@ -191,8 +191,8 @@ void ensureExists(const QDir& dir)
void write(const QString& filename, const QByteArray& data) void write(const QString& filename, const QByteArray& data)
{ {
ensureExists(QFileInfo(filename).dir()); ensureExists(QFileInfo(filename).dir());
QSaveFile file(filename); PSaveFile file(filename);
if (!file.open(QSaveFile::WriteOnly)) { if (!file.open(PSaveFile::WriteOnly)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString()); throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
} }
if (data.size() != file.write(data)) { if (data.size() != file.write(data)) {
@ -213,8 +213,8 @@ void appendSafe(const QString& filename, const QByteArray& data)
buffer = QByteArray(); buffer = QByteArray();
} }
buffer.append(data); buffer.append(data);
QSaveFile file(filename); PSaveFile file(filename);
if (!file.open(QSaveFile::WriteOnly)) { if (!file.open(PSaveFile::WriteOnly)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString()); throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
} }
if (buffer.size() != file.write(buffer)) { if (buffer.size() != file.write(buffer)) {
@ -971,8 +971,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (!args.empty()) if (!args.empty())
argstring = " \"" + args.join("\" \"") + "\""; argstring = " \"" + args.join("\" \"") + "\"";
stream << "#!/bin/bash" stream << "#!/bin/bash" << "\n";
<< "\n";
stream << "\"" << target << "\" " << argstring << "\n"; stream << "\"" << target << "\" " << argstring << "\n";
stream.flush(); stream.flush();
@ -1016,12 +1015,9 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (!args.empty()) if (!args.empty())
argstring = " '" + args.join("' '") + "'"; argstring = " '" + args.join("' '") + "'";
stream << "[Desktop Entry]" stream << "[Desktop Entry]" << "\n";
<< "\n"; stream << "Type=Application" << "\n";
stream << "Type=Application" stream << "Categories=Game;ActionGame;AdventureGame;Simulation" << "\n";
<< "\n";
stream << "Categories=Game;ActionGame;AdventureGame;Simulation"
<< "\n";
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n"; stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
stream << "Name=" << name.toLocal8Bit() << "\n"; stream << "Name=" << name.toLocal8Bit() << "\n";
if (!icon.isEmpty()) { if (!icon.isEmpty()) {

View File

@ -72,7 +72,8 @@ auto GetAuthlibInjectorApiLocation::Sink::finalize(QNetworkReply& reply) -> Task
qDebug() << "X-Authlib-Injector-API-Location header not found!"; qDebug() << "X-Authlib-Injector-API-Location header not found!";
} }
m_outer.m_account.reset(MinecraftAccount::createFromUsernameAuthlibInjector(m_outer.m_username, url.toString())); const auto& encodedUrl = url.toEncoded(QUrl::FullyEncoded);
m_outer.m_account.reset(MinecraftAccount::createFromUsernameAuthlibInjector(m_outer.m_username, encodedUrl));
return Task::State::Succeeded; return Task::State::Succeeded;
} }

View File

@ -61,6 +61,6 @@ void InstanceCreationTask::executeTask()
return; return;
} }
} }
if (!m_abort)
emitSucceeded(); emitSucceeded();
} }

View File

@ -69,9 +69,11 @@ bool InstanceImportTask::abort()
if (!canAbort()) if (!canAbort())
return false; return false;
if (task) bool wasAborted = false;
task->abort(); if (m_task)
return Task::abort(); wasAborted = m_task->abort();
Task::abort();
return wasAborted;
} }
void InstanceImportTask::executeTask() void InstanceImportTask::executeTask()
@ -104,7 +106,7 @@ void InstanceImportTask::downloadFromUrl()
connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress); connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed); connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed);
connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted); connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted);
task.reset(filesNetJob); m_task.reset(filesNetJob);
filesNetJob->start(); filesNetJob->start();
} }
@ -193,7 +195,7 @@ void InstanceImportTask::processZipPack()
stepProgress(*progressStep); stepProgress(*progressStep);
}); });
connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished); connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished, Qt::QueuedConnection);
connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted); connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) { connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
progressStep->state = TaskStepState::Failed; progressStep->state = TaskStepState::Failed;
@ -210,12 +212,13 @@ void InstanceImportTask::processZipPack()
progressStep->status = status; progressStep->status = status;
stepProgress(*progressStep); stepProgress(*progressStep);
}); });
task.reset(zipTask); m_task.reset(zipTask);
zipTask->start(); zipTask->start();
} }
void InstanceImportTask::extractFinished() void InstanceImportTask::extractFinished()
{ {
setAbortable(false);
QDir extractDir(m_stagingPath); QDir extractDir(m_stagingPath);
qDebug() << "Fixing permissions for extracted pack files..."; qDebug() << "Fixing permissions for extracted pack files...";
@ -289,8 +292,11 @@ void InstanceImportTask::processFlame()
inst_creation_task->setGroup(m_instGroup); inst_creation_task->setGroup(m_instGroup);
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate()); inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
connect(inst_creation_task.get(), &Task::succeeded, this, [this, inst_creation_task] { auto weak = inst_creation_task.toWeakRef();
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID()); connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
if (auto sp = weak.lock()) {
setOverride(sp->shouldOverride(), sp->originalInstanceID());
}
emitSucceeded(); emitSucceeded();
}); });
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed); connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
@ -299,11 +305,12 @@ void InstanceImportTask::processFlame()
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus); connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails); connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
connect(this, &Task::aborted, inst_creation_task.get(), &InstanceCreationTask::abort);
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort); connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable); connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
inst_creation_task->start(); m_task.reset(inst_creation_task);
setAbortable(true);
m_task->start();
} }
void InstanceImportTask::processTechnic() void InstanceImportTask::processTechnic()
@ -350,7 +357,7 @@ void InstanceImportTask::processMultiMC()
void InstanceImportTask::processModrinth() void InstanceImportTask::processModrinth()
{ {
ModrinthCreationTask* inst_creation_task = nullptr; shared_qobject_ptr<ModrinthCreationTask> inst_creation_task = nullptr;
if (!m_extra_info.isEmpty()) { if (!m_extra_info.isEmpty()) {
auto pack_id_it = m_extra_info.constFind("pack_id"); auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd()); Q_ASSERT(pack_id_it != m_extra_info.constEnd());
@ -367,7 +374,7 @@ void InstanceImportTask::processModrinth()
original_instance_id = original_instance_id_it.value(); original_instance_id = original_instance_id_it.value();
inst_creation_task = inst_creation_task =
new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); makeShared<ModrinthCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
} else { } else {
QString pack_id; QString pack_id;
if (!m_sourceUrl.isEmpty()) { if (!m_sourceUrl.isEmpty()) {
@ -376,7 +383,7 @@ void InstanceImportTask::processModrinth()
} }
// FIXME: Find a way to get the ID in directly imported ZIPs // FIXME: Find a way to get the ID in directly imported ZIPs
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id); inst_creation_task = makeShared<ModrinthCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id);
} }
inst_creation_task->setName(*this); inst_creation_task->setName(*this);
@ -384,20 +391,23 @@ void InstanceImportTask::processModrinth()
inst_creation_task->setGroup(m_instGroup); inst_creation_task->setGroup(m_instGroup);
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate()); inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] { auto weak = inst_creation_task.toWeakRef();
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID()); connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
if (auto sp = weak.lock()) {
setOverride(sp->shouldOverride(), sp->originalInstanceID());
}
emitSucceeded(); emitSucceeded();
}); });
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress);
connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress); connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails); connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
connect(inst_creation_task, &Task::aborted, this, &Task::abort); connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable);
inst_creation_task->start(); m_task.reset(inst_creation_task);
setAbortable(true);
m_task->start();
} }

View File

@ -40,16 +40,13 @@
#include <QUrl> #include <QUrl>
#include "InstanceTask.h" #include "InstanceTask.h"
#include <memory>
#include <optional>
class QuaZip; class QuaZip;
class InstanceImportTask : public InstanceTask { class InstanceImportTask : public InstanceTask {
Q_OBJECT Q_OBJECT
public: public:
explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {}); explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
virtual ~InstanceImportTask() = default;
bool abort() override; bool abort() override;
protected: protected:
@ -70,7 +67,7 @@ class InstanceImportTask : public InstanceTask {
private: /* data */ private: /* data */
QUrl m_sourceUrl; QUrl m_sourceUrl;
QString m_archivePath; QString m_archivePath;
Task::Ptr task; Task::Ptr m_task;
enum class ModpackType { enum class ModpackType {
Unknown, Unknown,
MultiMC, MultiMC,

View File

@ -116,7 +116,7 @@ void JavaCommon::TestCheck::run()
emit finished(); emit finished();
return; return;
} }
checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0, this)); checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0));
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished); connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
checker->start(); checker->start();
} }
@ -128,7 +128,7 @@ void JavaCommon::TestCheck::checkFinished(const JavaChecker::Result& result)
emit finished(); emit finished();
return; return;
} }
checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0, this)); checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0));
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs); connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
checker->start(); checker->start();
} }

View File

@ -64,7 +64,7 @@
#include "launch/steps/TextPrint.h" #include "launch/steps/TextPrint.h"
#include "tasks/Task.h" #include "tasks/Task.h"
LaunchController::LaunchController(QObject* parent) : Task(parent) {} LaunchController::LaunchController() : Task() {}
void LaunchController::executeTask() void LaunchController::executeTask()
{ {

View File

@ -49,7 +49,7 @@ class LaunchController : public Task {
public: public:
void executeTask() override; void executeTask() override;
LaunchController(QObject* parent = nullptr); LaunchController();
virtual ~LaunchController() = default; virtual ~LaunchController() = default;
void setInstance(InstancePtr instance) { m_instance = instance; } void setInstance(InstancePtr instance) { m_instance = instance; }

View File

@ -39,8 +39,16 @@ if [ "x$DEPS_LIST" = "x" ]; then
# Just to be sure... # Just to be sure...
chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}"
ARGS=("${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}")
if [ -f portable.txt ]; then
ARGS+=("-d" "${LAUNCHER_DIR}")
fi
ARGS+=("$@")
# Run the launcher # Run the launcher
exec -a "${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@" exec -a "${ARGS[@]}"
# Run the launcher in valgrind # Run the launcher in valgrind
# valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@" # valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"

View File

@ -378,7 +378,7 @@ std::optional<QStringList> extractDir(QString fileCompressed, QString dir)
if (fileInfo.size() == 22) { if (fileInfo.size() == 22) {
return QStringList(); return QStringList();
} }
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
; ;
return std::nullopt; return std::nullopt;
} }
@ -395,7 +395,7 @@ std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QS
if (fileInfo.size() == 22) { if (fileInfo.size() == 22) {
return QStringList(); return QStringList();
} }
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
; ;
return std::nullopt; return std::nullopt;
} }
@ -412,7 +412,7 @@ bool extractFile(QString fileCompressed, QString file, QString target)
if (fileInfo.size() == 22) { if (fileInfo.size() == 22) {
return true; return true;
} }
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
return false; return false;
} }
return extractRelFile(&zip, file, target); return extractRelFile(&zip, file, target);
@ -577,7 +577,7 @@ auto ExtractZipTask::extractZip() -> ZipResult
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size())); auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size()));
auto original_name = relative_file_name; auto original_name = relative_file_name;
setStatus("Unziping: " + relative_file_name); setStatus("Unpacking: " + relative_file_name);
// Fix subdirs/files ending with a / getting transformed into absolute paths // Fix subdirs/files ending with a / getting transformed into absolute paths
if (relative_file_name.startsWith('/')) if (relative_file_name.startsWith('/'))

71
launcher/PSaveFile.h Normal file
View File

@ -0,0 +1,71 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 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 <QFileInfo>
#include <QSaveFile>
#include "Application.h"
#if defined(LAUNCHER_APPLICATION)
/* PSaveFile
* A class that mimics QSaveFile for Windows.
*
* When reading resources, we need to avoid accessing temporary files
* generated by QSaveFile. If we start reading such a file, we may
* inadvertently keep it open while QSaveFile is trying to remove it,
* or we might detect the file just before it is removed, leading to
* race conditions and errors.
*
* Unfortunately, QSaveFile doesn't provide a way to retrieve the
* temporary file name or to set a specific template for the temporary
* file name it uses. By default, QSaveFile appends a `.XXXXXX` suffix
* to the original file name, where the `XXXXXX` part is dynamically
* generated to ensure uniqueness.
*
* This class acts like a lock by adding and removing the target file
* name into/from a global string set, helping to manage access to
* files during critical operations.
*
* Note: Please do not use the `setFileName` function directly, as it
* is not virtual and cannot be overridden.
*/
class PSaveFile : public QSaveFile {
public:
PSaveFile(const QString& name) : QSaveFile(name) { addPath(name); }
PSaveFile(const QString& name, QObject* parent) : QSaveFile(name, parent) { addPath(name); }
virtual ~PSaveFile()
{
if (auto app = APPLICATION_DYN) {
app->removeQSavePath(m_absoluteFilePath);
}
}
private:
void addPath(const QString& path)
{
m_absoluteFilePath = QFileInfo(path).absoluteFilePath() + "."; // add dot for tmp files only
if (auto app = APPLICATION_DYN) {
app->addQSavePath(m_absoluteFilePath);
}
}
QString m_absoluteFilePath;
};
#else
#define PSaveFile QSaveFile
#endif

View File

@ -81,9 +81,9 @@ QString getSupportedJavaArchitecture()
if (arch == "arm64") if (arch == "arm64")
return "mac-os-arm64"; return "mac-os-arm64";
if (arch.contains("64")) if (arch.contains("64"))
return "mac-os-64"; return "mac-os-x64";
if (arch.contains("86")) if (arch.contains("86"))
return "mac-os-86"; return "mac-os-x86";
// Unknown, maybe something new, appending arch // Unknown, maybe something new, appending arch
return "mac-os-" + arch; return "mac-os-" + arch;
} else if (sys == "linux") { } else if (sys == "linux") {

View File

@ -307,6 +307,7 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw)
if (!replacing) { if (!replacing) {
roles.clear(); roles.clear();
filterModel->setSourceModel(replacing); filterModel->setSourceModel(replacing);
endResetModel();
return; return;
} }

View File

@ -44,8 +44,8 @@
#include "FileSystem.h" #include "FileSystem.h"
#include "java/JavaUtils.h" #include "java/JavaUtils.h"
JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id, QObject* parent) JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id)
: Task(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id) : Task(), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id)
{} {}
void JavaChecker::executeTask() void JavaChecker::executeTask()
@ -171,7 +171,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
auto os_arch = results["os.arch"]; auto os_arch = results["os.arch"];
auto java_version = results["java.version"]; auto java_version = results["java.version"];
auto java_vendor = results["java.vendor"]; auto java_vendor = results["java.vendor"];
bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64"; bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64" || os_arch == "riscv64";
result.validity = Result::Validity::Valid; result.validity = Result::Validity::Valid;
result.is_64bit = is_64; result.is_64bit = is_64;

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <QProcess> #include <QProcess>
#include <QTimer> #include <QTimer>
#include <memory>
#include "JavaVersion.h" #include "JavaVersion.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
@ -26,7 +25,7 @@ class JavaChecker : public Task {
enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored; enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
}; };
explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0, QObject* parent = 0); explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0);
signals: signals:
void checkFinished(const Result& result); void checkFinished(const Result& result);

View File

@ -163,7 +163,7 @@ void JavaListLoadTask::executeTask()
JavaUtils ju; JavaUtils ju;
QList<QString> candidate_paths = m_only_managed_versions ? getPrismJavaBundle() : ju.FindJavaPaths(); QList<QString> candidate_paths = m_only_managed_versions ? getPrismJavaBundle() : ju.FindJavaPaths();
ConcurrentTask::Ptr job(new ConcurrentTask(this, "Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); ConcurrentTask::Ptr job(new ConcurrentTask("Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
m_job.reset(job); m_job.reset(job);
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
connect(m_job.get(), &Task::progress, this, &Task::setProgress); connect(m_job.get(), &Task::progress, this, &Task::setProgress);
@ -171,7 +171,7 @@ void JavaListLoadTask::executeTask()
qDebug() << "Probing the following Java paths: "; qDebug() << "Probing the following Java paths: ";
int id = 0; int id = 0;
for (QString candidate : candidate_paths) { for (QString candidate : candidate_paths) {
auto checker = new JavaChecker(candidate, "", 0, 0, 0, id, this); auto checker = new JavaChecker(candidate, "", 0, 0, 0, id);
connect(checker, &JavaChecker::checkFinished, [this](const JavaChecker::Result& result) { m_results << result; }); connect(checker, &JavaChecker::checkFinished, [this](const JavaChecker::Result& result) { m_results << result; });
job->addTask(Task::Ptr(checker)); job->addTask(Task::Ptr(checker));
id++; id++;

View File

@ -102,6 +102,8 @@ QProcessEnvironment CleanEnviroment()
QString newValue = stripVariableEntries(key, value, rawenv.value("LAUNCHER_" + key)); QString newValue = stripVariableEntries(key, value, rawenv.value("LAUNCHER_" + key));
qDebug() << "Env: stripped" << key << value << "to" << newValue; qDebug() << "Env: stripped" << key << value << "to" << newValue;
value = newValue;
} }
#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)
// Strip IBus // Strip IBus

View File

@ -86,11 +86,10 @@ void ManifestDownloadTask::downloadJava(const QJsonDocument& doc)
if (type == "directory") { if (type == "directory") {
FS::ensureFolderPathExists(file); FS::ensureFolderPathExists(file);
} else if (type == "link") { } else if (type == "link") {
// this is linux only ! // this is *nix only !
auto path = Json::ensureString(meta, "target"); auto path = Json::ensureString(meta, "target");
if (!path.isEmpty()) { if (!path.isEmpty()) {
auto target = FS::PathCombine(file, "../" + path); QFile::link(path, file);
QFile(target).link(file);
} }
} else if (type == "file") { } else if (type == "file") {
// TODO download compressed version if it exists ? // TODO download compressed version if it exists ?

View File

@ -16,7 +16,7 @@
#include "LaunchStep.h" #include "LaunchStep.h"
#include "LaunchTask.h" #include "LaunchTask.h"
LaunchStep::LaunchStep(LaunchTask* parent) : Task(parent), m_parent(parent) LaunchStep::LaunchStep(LaunchTask* parent) : Task(), m_parent(parent)
{ {
connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch); connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch);
connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine); connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);

View File

@ -254,20 +254,60 @@ void LaunchTask::emitFailed(QString reason)
Task::emitFailed(reason); Task::emitFailed(reason);
} }
void LaunchTask::substituteVariables(QStringList& args) const QString expandVariables(const QString& input, QProcessEnvironment dict)
{ {
auto env = m_instance->createEnvironment(); QString result = input;
for (auto key : env.keys()) { enum { base, maybeBrace, variable, brace } state = base;
args.replaceInStrings("$" + key, env.value(key)); int startIdx = -1;
for (int i = 0; i < result.length();) {
QChar c = result.at(i++);
switch (state) {
case base:
if (c == '$')
state = maybeBrace;
break;
case maybeBrace:
if (c == '{') {
state = brace;
startIdx = i;
} else if (c.isLetterOrNumber() || c == '_') {
state = variable;
startIdx = i - 1;
} else {
state = base;
}
break;
case brace:
if (c == '}') {
const auto res = dict.value(result.mid(startIdx, i - 1 - startIdx), "");
if (!res.isEmpty()) {
result.replace(startIdx - 2, i - startIdx + 2, res);
i = startIdx - 2 + res.length();
}
state = base;
}
break;
case variable:
if (!c.isLetterOrNumber() && c != '_') {
const auto res = dict.value(result.mid(startIdx, i - startIdx - 1), "");
if (!res.isEmpty()) {
result.replace(startIdx - 1, i - startIdx, res);
i = startIdx - 1 + res.length();
}
state = base;
}
break;
}
} }
if (state == variable) {
if (const auto res = dict.value(result.mid(startIdx), ""); !res.isEmpty())
result.replace(startIdx - 1, result.length() - startIdx + 1, res);
}
return result;
} }
void LaunchTask::substituteVariables(QString& cmd) const QString LaunchTask::substituteVariables(QString& cmd, bool isLaunch) const
{ {
auto env = m_instance->createEnvironment(); return expandVariables(cmd, isLaunch ? m_instance->createLaunchEnvironment() : m_instance->createEnvironment());
for (auto key : env.keys()) {
cmd.replace("$" + key, env.value(key));
}
} }

View File

@ -87,8 +87,7 @@ class LaunchTask : public Task {
shared_qobject_ptr<LogModel> getLogModel(); shared_qobject_ptr<LogModel> getLogModel();
public: public:
void substituteVariables(QStringList& args) const; QString substituteVariables(QString& cmd, bool isLaunch = false) const;
void substituteVariables(QString& cmd) const;
QString censorPrivateInfo(QString in); QString censorPrivateInfo(QString in);
protected: /* methods */ protected: /* methods */

View File

@ -32,7 +32,7 @@ class LogModel : public QAbstractListModel {
private /* types */: private /* types */:
struct entry { struct entry {
MessageLevel::Enum level; MessageLevel::Enum level = MessageLevel::Enum::Unknown;
QString line; QString line;
}; };

View File

@ -94,7 +94,7 @@ void CheckJava::executeTask()
// if timestamps are not the same, or something is missing, check! // if timestamps are not the same, or something is missing, check!
if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 || if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 ||
storedRealArchitecture.size() == 0 || storedVendor.size() == 0) { storedRealArchitecture.size() == 0 || storedVendor.size() == 0) {
m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0, this)); m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0));
emit logLine(QString("Checking Java version..."), MessageLevel::Launcher); emit logLine(QString("Checking Java version..."), MessageLevel::Launcher);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished); connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
m_JavaChecker->start(); m_JavaChecker->start();

View File

@ -47,19 +47,15 @@ PostLaunchCommand::PostLaunchCommand(LaunchTask* parent) : LaunchStep(parent)
void PostLaunchCommand::executeTask() void PostLaunchCommand::executeTask()
{ {
// FIXME: where to put this? auto cmd = m_parent->substituteVariables(m_command);
emit logLine(tr("Running Post-Launch command: %1").arg(cmd), MessageLevel::Launcher);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
auto args = QProcess::splitCommand(m_command); auto args = QProcess::splitCommand(cmd);
m_parent->substituteVariables(args);
emit logLine(tr("Running Post-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher);
const QString program = args.takeFirst(); const QString program = args.takeFirst();
m_process.start(program, args); m_process.start(program, args);
#else #else
m_parent->substituteVariables(m_command); m_process.start(cmd);
emit logLine(tr("Running Post-Launch command: %1").arg(m_command), MessageLevel::Launcher);
m_process.start(m_command);
#endif #endif
} }

View File

@ -47,19 +47,14 @@ PreLaunchCommand::PreLaunchCommand(LaunchTask* parent) : LaunchStep(parent)
void PreLaunchCommand::executeTask() void PreLaunchCommand::executeTask()
{ {
// FIXME: where to put this? auto cmd = m_parent->substituteVariables(m_command);
emit logLine(tr("Running Pre-Launch command: %1").arg(cmd), MessageLevel::Launcher);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
auto args = QProcess::splitCommand(m_command); auto args = QProcess::splitCommand(cmd);
m_parent->substituteVariables(args);
emit logLine(tr("Running Pre-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher);
const QString program = args.takeFirst(); const QString program = args.takeFirst();
m_process.start(program, args); m_process.start(program, args);
#else #else
m_parent->substituteVariables(m_command); m_process.start(cmd);
emit logLine(tr("Running Pre-Launch command: %1").arg(m_command), MessageLevel::Launcher);
m_process.start(m_command);
#endif #endif
} }

View File

@ -140,8 +140,8 @@ Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mo
} }
auto versionList = get(uid); auto versionList = get(uid);
auto loadTask = makeShared<SequentialTask>( auto loadTask =
this, tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version)); makeShared<SequentialTask>(tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version));
if (status() != BaseEntity::LoadStatus::Remote || force) { if (status() != BaseEntity::LoadStatus::Remote || force) {
loadTask->addTask(this->loadTask(mode)); loadTask->addTask(this->loadTask(mode));
} }

View File

@ -34,8 +34,7 @@ VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList(
Task::Ptr VersionList::getLoadTask() Task::Ptr VersionList::getLoadTask()
{ {
auto loadTask = auto loadTask = makeShared<SequentialTask>(tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
makeShared<SequentialTask>(this, tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online)); loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online));
loadTask->addTask(this->loadTask(Net::Mode::Online)); loadTask->addTask(this->loadTask(Net::Mode::Online));
return loadTask; return loadTask;

View File

@ -222,11 +222,12 @@ bool Component::isMoveable()
return true; return true;
} }
bool Component::isVersionChangeable() bool Component::isVersionChangeable(bool wait)
{ {
auto list = getVersionList(); auto list = getVersionList();
if (list) { if (list) {
list->waitToLoad(); if (wait)
list->waitToLoad();
return list->count() != 0; return list->count() != 0;
} }
return false; return false;

View File

@ -72,7 +72,7 @@ class Component : public QObject, public ProblemProvider {
bool isRevertible(); bool isRevertible();
bool isRemovable(); bool isRemovable();
bool isCustom(); bool isCustom();
bool isVersionChangeable(); bool isVersionChangeable(bool wait = true);
bool isKnownModloader(); bool isKnownModloader();
QStringList knownConflictingComponents(); QStringList knownConflictingComponents();

View File

@ -38,7 +38,7 @@
* If the component list changes, start over. * If the component list changes, start over.
*/ */
ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent) : Task(parent) ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list) : Task()
{ {
d.reset(new ComponentUpdateTaskData); d.reset(new ComponentUpdateTaskData);
d->m_profile = list; d->m_profile = list;

View File

@ -14,7 +14,7 @@ class ComponentUpdateTask : public Task {
enum class Mode { Launch, Resolution }; enum class Mode { Launch, Resolution };
public: public:
explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent = 0); explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list);
virtual ~ComponentUpdateTask(); virtual ~ComponentUpdateTask();
protected: protected:

View File

@ -654,6 +654,7 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
// dlsym variant is only needed for OpenGL and not included in the vulkan layer // dlsym variant is only needed for OpenGL and not included in the vulkan layer
appendLib("libMangoHud_dlsym.so"); appendLib("libMangoHud_dlsym.so");
appendLib("libMangoHud_opengl.so"); appendLib("libMangoHud_opengl.so");
appendLib("libMangoHud_shim.so");
preloadList << mangoHudLibString; preloadList << mangoHudLibString;
} }
@ -1131,17 +1132,10 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(step); process->appendStep(step);
} }
// run pre-launch command if that's needed
if (getPreLaunchCommand().size()) {
auto step = makeShared<PreLaunchCommand>(pptr);
step->setWorkingDirectory(gameRoot());
process->appendStep(step);
}
// load meta // load meta
{ {
auto mode = session->status != AuthSession::PlayableOffline ? Net::Mode::Online : Net::Mode::Offline; auto mode = session->status != AuthSession::PlayableOffline ? Net::Mode::Online : Net::Mode::Offline;
process->appendStep(makeShared<TaskStepWrapper>(pptr, makeShared<MinecraftLoadAndCheck>(this, mode, pptr))); process->appendStep(makeShared<TaskStepWrapper>(pptr, makeShared<MinecraftLoadAndCheck>(this, mode)));
} }
// check java // check java
@ -1150,6 +1144,13 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(makeShared<CheckJava>(pptr)); process->appendStep(makeShared<CheckJava>(pptr));
} }
// run pre-launch command if that's needed
if (getPreLaunchCommand().size()) {
auto step = makeShared<PreLaunchCommand>(pptr);
step->setWorkingDirectory(gameRoot());
process->appendStep(step);
}
// if we aren't in offline mode,. // if we aren't in offline mode,.
if (session->status != AuthSession::PlayableOffline) { if (session->status != AuthSession::PlayableOffline) {
if (!session->demo) { if (!session->demo) {

View File

@ -2,15 +2,16 @@
#include "MinecraftInstance.h" #include "MinecraftInstance.h"
#include "PackProfile.h" #include "PackProfile.h"
MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode, QObject* parent) MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode) : m_inst(inst), m_netmode(netmode) {}
: Task(parent), m_inst(inst), m_netmode(netmode)
{}
void MinecraftLoadAndCheck::executeTask() void MinecraftLoadAndCheck::executeTask()
{ {
// add offline metadata load task // add offline metadata load task
auto components = m_inst->getPackProfile(); auto components = m_inst->getPackProfile();
components->reload(m_netmode); if (auto result = components->reload(m_netmode); !result) {
emitFailed(result.error);
return;
}
m_task = components->getCurrentTask(); m_task = components->getCurrentTask();
if (!m_task) { if (!m_task) {

View File

@ -23,7 +23,7 @@ class MinecraftInstance;
class MinecraftLoadAndCheck : public Task { class MinecraftLoadAndCheck : public Task {
Q_OBJECT Q_OBJECT
public: public:
explicit MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode, QObject* parent = nullptr); explicit MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode);
virtual ~MinecraftLoadAndCheck() = default; virtual ~MinecraftLoadAndCheck() = default;
void executeTask() override; void executeTask() override;

View File

@ -180,29 +180,32 @@ static bool savePackProfile(const QString& filename, const ComponentContainer& c
} }
// Read the given file into component containers // Read the given file into component containers
static bool loadPackProfile(PackProfile* parent, static PackProfile::Result loadPackProfile(PackProfile* parent,
const QString& filename, const QString& filename,
const QString& componentJsonPattern, const QString& componentJsonPattern,
ComponentContainer& container) ComponentContainer& container)
{ {
QFile componentsFile(filename); QFile componentsFile(filename);
if (!componentsFile.exists()) { if (!componentsFile.exists()) {
qCWarning(instanceProfileC) << "Components file" << filename << "doesn't exist. This should never happen."; auto message = QObject::tr("Components file %1 doesn't exist. This should never happen.").arg(filename);
return false; qCWarning(instanceProfileC) << message;
return PackProfile::Result::Error(message);
} }
if (!componentsFile.open(QFile::ReadOnly)) { if (!componentsFile.open(QFile::ReadOnly)) {
qCCritical(instanceProfileC) << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString(); auto message = QObject::tr("Couldn't open %1 for reading: %2").arg(componentsFile.fileName(), componentsFile.errorString());
qCCritical(instanceProfileC) << message;
qCWarning(instanceProfileC) << "Ignoring overridden order"; qCWarning(instanceProfileC) << "Ignoring overridden order";
return false; return PackProfile::Result::Error(message);
} }
// and it's valid JSON // and it's valid JSON
QJsonParseError error; QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error); QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error);
if (error.error != QJsonParseError::NoError) { if (error.error != QJsonParseError::NoError) {
qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString(); auto message = QObject::tr("Couldn't parse %1 as json: %2").arg(componentsFile.fileName(), error.errorString());
qCCritical(instanceProfileC) << message;
qCWarning(instanceProfileC) << "Ignoring overridden order"; qCWarning(instanceProfileC) << "Ignoring overridden order";
return false; return PackProfile::Result::Error(message);
} }
// and then read it and process it if all above is true. // and then read it and process it if all above is true.
@ -219,11 +222,13 @@ static bool loadPackProfile(PackProfile* parent,
container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj)); container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj));
} }
} catch ([[maybe_unused]] const JSONValidationError& err) { } catch ([[maybe_unused]] const JSONValidationError& err) {
qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ": bad file format"; auto message = QObject::tr("Couldn't parse %1 : bad file format").arg(componentsFile.fileName());
qCCritical(instanceProfileC) << message;
qCWarning(instanceProfileC) << "error:" << err.what();
container.clear(); container.clear();
return false; return PackProfile::Result::Error(message);
} }
return true; return PackProfile::Result::Success();
} }
// END: component file format // END: component file format
@ -290,44 +295,43 @@ void PackProfile::save_internal()
d->dirty = false; d->dirty = false;
} }
bool PackProfile::load() PackProfile::Result PackProfile::load()
{ {
auto filename = componentsFilePath(); auto filename = componentsFilePath();
// load the new component list and swap it with the current one... // load the new component list and swap it with the current one...
ComponentContainer newComponents; ComponentContainer newComponents;
if (!loadPackProfile(this, filename, patchesPattern(), newComponents)) { if (auto result = loadPackProfile(this, filename, patchesPattern(), newComponents); !result) {
qCritical() << d->m_instance->name() << "|" << "Failed to load the component config"; qCritical() << d->m_instance->name() << "|" << "Failed to load the component config";
return false; return result;
} else {
// FIXME: actually use fine-grained updates, not this...
beginResetModel();
// disconnect all the old components
for (auto component : d->components) {
disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
}
d->components.clear();
d->componentIndex.clear();
for (auto component : newComponents) {
if (d->componentIndex.contains(component->m_uid)) {
qWarning() << d->m_instance->name() << "|" << "Ignoring duplicate component entry" << component->m_uid;
continue;
}
connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
d->components.append(component);
d->componentIndex[component->m_uid] = component;
}
endResetModel();
d->loaded = true;
return true;
} }
// FIXME: actually use fine-grained updates, not this...
beginResetModel();
// disconnect all the old components
for (auto component : d->components) {
disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
}
d->components.clear();
d->componentIndex.clear();
for (auto component : newComponents) {
if (d->componentIndex.contains(component->m_uid)) {
qWarning() << d->m_instance->name() << "|" << "Ignoring duplicate component entry" << component->m_uid;
continue;
}
connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
d->components.append(component);
d->componentIndex[component->m_uid] = component;
}
endResetModel();
d->loaded = true;
return Result::Success();
} }
void PackProfile::reload(Net::Mode netmode) PackProfile::Result PackProfile::reload(Net::Mode netmode)
{ {
// Do not reload when the update/resolve task is running. It is in control. // Do not reload when the update/resolve task is running. It is in control.
if (d->m_updateTask) { if (d->m_updateTask) {
return; return Result::Success();
} }
// flush any scheduled saves to not lose state // flush any scheduled saves to not lose state
@ -336,9 +340,11 @@ void PackProfile::reload(Net::Mode netmode)
// FIXME: differentiate when a reapply is required by propagating state from components // FIXME: differentiate when a reapply is required by propagating state from components
invalidateLaunchProfile(); invalidateLaunchProfile();
if (load()) { if (auto result = load(); !result) {
resolve(netmode); return result;
} }
resolve(netmode);
return Result::Success();
} }
Task::Ptr PackProfile::getCurrentTask() Task::Ptr PackProfile::getCurrentTask()

View File

@ -62,6 +62,19 @@ class PackProfile : public QAbstractListModel {
public: public:
enum Columns { NameColumn = 0, VersionColumn, NUM_COLUMNS }; enum Columns { NameColumn = 0, VersionColumn, NUM_COLUMNS };
struct Result {
bool success;
QString error;
// Implicit conversion to bool
operator bool() const { return success; }
// Factory methods for convenience
static Result Success() { return { true, "" }; }
static Result Error(const QString& errorMessage) { return { false, errorMessage }; }
};
explicit PackProfile(MinecraftInstance* instance); explicit PackProfile(MinecraftInstance* instance);
virtual ~PackProfile(); virtual ~PackProfile();
@ -102,7 +115,7 @@ class PackProfile : public QAbstractListModel {
bool revertToBase(int index); bool revertToBase(int index);
/// reload the list, reload all components, resolve dependencies /// reload the list, reload all components, resolve dependencies
void reload(Net::Mode netmode); Result reload(Net::Mode netmode);
// reload all components, resolve dependencies // reload all components, resolve dependencies
void resolve(Net::Mode netmode); void resolve(Net::Mode netmode);
@ -169,7 +182,7 @@ class PackProfile : public QAbstractListModel {
void disableInteraction(bool disable); void disableInteraction(bool disable);
private: private:
bool load(); Result load();
bool installJarMods_internal(QStringList filepaths); bool installJarMods_internal(QStringList filepaths);
bool installCustomJar_internal(QString filepath); bool installCustomJar_internal(QString filepath);
bool installAgents_internal(QStringList filepaths); bool installAgents_internal(QStringList filepaths);

View File

@ -38,7 +38,6 @@
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
#include <QDirIterator> #include <QDirIterator>
#include <QSaveFile>
#include <QString> #include <QString>
#include <FileSystem.h> #include <FileSystem.h>
@ -57,6 +56,7 @@
#include <optional> #include <optional>
#include "FileSystem.h" #include "FileSystem.h"
#include "PSaveFile.h"
using std::nullopt; using std::nullopt;
using std::optional; using std::optional;
@ -183,7 +183,7 @@ bool putLevelDatDataToFS(const QFileInfo& file, QByteArray& data)
if (fullFilePath.isNull()) { if (fullFilePath.isNull()) {
return false; return false;
} }
QSaveFile f(fullFilePath); PSaveFile f(fullFilePath);
if (!f.open(QIODevice::WriteOnly)) { if (!f.open(QIODevice::WriteOnly)) {
return false; return false;
} }

View File

@ -133,6 +133,8 @@ void profileToJSONV3(QJsonObject& parent, MinecraftProfile p, const char* tokenN
out["skin"] = skinObj; out["skin"] = skinObj;
} }
out["canUploadSkins"] = p.canUploadSkins;
QJsonArray capesArray; QJsonArray capesArray;
for (auto& cape : p.capes) { for (auto& cape : p.capes) {
QJsonObject capeObj; QJsonObject capeObj;
@ -195,6 +197,11 @@ MinecraftProfile profileFromJSONV3(const QJsonObject& parent, const char* tokenN
} }
} }
out.canUploadSkins = true;
if (tokenObject.value("canUploadSkins").isBool()) {
out.canUploadSkins = tokenObject.value("canUploadskins").toBool();
}
{ {
auto capesV = tokenObject.value("capes"); auto capesV = tokenObject.value("capes");
if (!capesV.isArray()) { if (!capesV.isArray()) {
@ -383,9 +390,9 @@ bool AccountData::usesCustomApiServers() const
return type == AccountType::AuthlibInjector; return type == AccountType::AuthlibInjector;
} }
bool AccountData::supportsSkinManagement() const bool AccountData::canUploadSkins() const
{ {
return type == AccountType::MSA; return minecraftProfile.canUploadSkins;
} }
QString AccountData::authServerUrl() const QString AccountData::authServerUrl() const

View File

@ -82,6 +82,7 @@ struct MinecraftEntitlement {
struct MinecraftProfile { struct MinecraftProfile {
QString id; QString id;
QString name; QString name;
bool canUploadSkins = true;
Skin skin; Skin skin;
QString currentCape; QString currentCape;
QMap<QString, Cape> capes; QMap<QString, Cape> capes;
@ -96,7 +97,7 @@ struct AccountData {
QJsonObject saveState() const; QJsonObject saveState() const;
bool resumeStateFromV3(QJsonObject data); bool resumeStateFromV3(QJsonObject data);
bool supportsSkinManagement() const; bool canUploadSkins() const;
bool usesCustomApiServers() const; bool usesCustomApiServers() const;
QString authServerUrl() const; QString authServerUrl() const;
QString accountServerUrl() const; QString accountServerUrl() const;

View File

@ -4,6 +4,7 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include "minecraft/auth/AccountData.h" #include "minecraft/auth/AccountData.h"
#include "minecraft/auth/steps/AuthlibInjectorMetadataStep.h"
#include "minecraft/auth/steps/EntitlementsStep.h" #include "minecraft/auth/steps/EntitlementsStep.h"
#include "minecraft/auth/steps/GetSkinStep.h" #include "minecraft/auth/steps/GetSkinStep.h"
#include "minecraft/auth/steps/LauncherLoginStep.h" #include "minecraft/auth/steps/LauncherLoginStep.h"
@ -21,7 +22,7 @@
#include <Application.h> #include <Application.h>
AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent, const std::optional<QString> password) : Task(parent), m_data(data) AuthFlow::AuthFlow(AccountData* data, Action action, const std::optional<QString> password) : Task(), m_data(data)
{ {
if (data->type == AccountType::MSA) { if (data->type == AccountType::MSA) {
if (action == Action::DeviceCode) { if (action == Action::DeviceCode) {
@ -46,6 +47,7 @@ AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent, const std:
} else if (data->type == AccountType::AuthlibInjector) { } else if (data->type == AccountType::AuthlibInjector) {
m_steps.append(makeShared<YggdrasilStep>(m_data, password)); m_steps.append(makeShared<YggdrasilStep>(m_data, password));
m_steps.append(makeShared<YggdrasilMinecraftProfileStep>(m_data)); m_steps.append(makeShared<YggdrasilMinecraftProfileStep>(m_data));
m_steps.append(makeShared<AuthlibInjectorMetadataStep>(m_data));
m_steps.append(makeShared<GetSkinStep>(m_data)); m_steps.append(makeShared<GetSkinStep>(m_data));
} }
changeState(AccountTaskState::STATE_CREATED); changeState(AccountTaskState::STATE_CREATED);

View File

@ -20,7 +20,6 @@ class AuthFlow : public Task {
explicit AuthFlow(AccountData* data, explicit AuthFlow(AccountData* data,
Action action = Action::Refresh, Action action = Action::Refresh,
QObject* parent = 0,
std::optional<QString> password = std::nullopt); std::optional<QString> password = std::nullopt);
virtual ~AuthFlow() = default; virtual ~AuthFlow() = default;

View File

@ -55,7 +55,7 @@
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
{ {
data.internalId = QUuid::createUuid().toString(QUuid::Id128); data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
} }
MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json)
@ -138,7 +138,7 @@ shared_qobject_ptr<AuthFlow> MinecraftAccount::login(bool useDeviceCode, std::op
{ {
Q_ASSERT(m_currentTask.get() == nullptr); Q_ASSERT(m_currentTask.get() == nullptr);
m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login, this, password)); m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login, password));
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); }); connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
@ -152,7 +152,7 @@ shared_qobject_ptr<AuthFlow> MinecraftAccount::refresh()
return m_currentTask; return m_currentTask;
} }
m_currentTask.reset(new AuthFlow(&data, AuthFlow::Action::Refresh, this)); m_currentTask.reset(new AuthFlow(&data, AuthFlow::Action::Refresh));
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);

View File

@ -119,7 +119,7 @@ class MinecraftAccount : public QObject, public Usable {
bool usesCustomApiServers() const { return data.usesCustomApiServers(); } bool usesCustomApiServers() const { return data.usesCustomApiServers(); }
bool supportsSkinManagement() const { return data.supportsSkinManagement(); } bool canUploadSkins() const { return data.canUploadSkins(); }
QString accountDisplayString() const { return data.accountDisplayString(); } QString accountDisplayString() const { return data.accountDisplayString(); }

View File

@ -188,6 +188,9 @@ bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output)
output.skin = skinOut; output.skin = skinOut;
break; break;
} }
output.canUploadSkins = true;
auto capesArray = obj.value("capes").toArray(); auto capesArray = obj.value("capes").toArray();
QString currentCape; QString currentCape;
@ -306,26 +309,38 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output)
auto propsArray = obj.value("properties").toArray(); auto propsArray = obj.value("properties").toArray();
QByteArray texturePayload; QByteArray texturePayload;
bool canUploadSkins = true;
for (auto p : propsArray) { for (auto p : propsArray) {
auto pObj = p.toObject(); auto pObj = p.toObject();
auto name = pObj.value("name"); auto name = pObj.value("name");
if (!name.isString() || name.toString() != "textures") { if (!name.isString()) {
continue; continue;
} }
auto value = pObj.value("value"); const auto& nameString = name.toString();
if (value.isString()) { if (nameString == "textures") {
auto value = pObj.value("value");
if (value.isString()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
texturePayload = QByteArray::fromBase64(value.toString().toUtf8(), QByteArray::AbortOnBase64DecodingErrors); texturePayload = QByteArray::fromBase64(value.toString().toUtf8(), QByteArray::AbortOnBase64DecodingErrors);
#else #else
texturePayload = QByteArray::fromBase64(value.toString().toUtf8()); texturePayload = QByteArray::fromBase64(value.toString().toUtf8());
#endif #endif
} }
} else if (nameString == "uploadableTextures") {
if (!texturePayload.isEmpty()) { // https://github.com/yushijinhun/authlib-injector/wiki/Yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#uploadabletextures-%E5%8F%AF%E4%B8%8A%E4%BC%A0%E7%9A%84%E6%9D%90%E8%B4%A8%E7%B1%BB%E5%9E%8B
break; const auto& value = pObj.value("value");
if (value.isString()) {
canUploadSkins = false;
for (const auto& textureType : value.toString().split(",")) {
if (textureType == "skin") {
canUploadSkins = true;
}
}
}
} }
} }
output.canUploadSkins = canUploadSkins;
if (texturePayload.isNull()) { if (texturePayload.isNull()) {
qWarning() << "No texture payload data"; qWarning() << "No texture payload data";
@ -379,7 +394,6 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output)
// we don't know the cape ID as it is not returned from the session server // we don't know the cape ID as it is not returned from the session server
// so just fake it - changing capes is probably locked anyway :( // so just fake it - changing capes is probably locked anyway :(
capeOut.alias = "cape"; capeOut.alias = "cape";
capeOut.id = "00000000-0000-0000-0000-000000000000";
} }
} }
} }

View File

@ -2,9 +2,7 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QNetworkRequest> #include <QNetworkRequest>
#include "Application.h"
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
AuthlibInjectorMetadataStep::AuthlibInjectorMetadataStep(AccountData* data) : AuthStep(data) {} AuthlibInjectorMetadataStep::AuthlibInjectorMetadataStep(AccountData* data) : AuthStep(data) {}
@ -21,29 +19,32 @@ void AuthlibInjectorMetadataStep::perform()
emit finished(AccountTaskState::STATE_WORKING, tr("Account has no authlib-injector URL.")); emit finished(AccountTaskState::STATE_WORKING, tr("Account has no authlib-injector URL."));
return; return;
} }
QNetworkRequest request = QNetworkRequest(m_data->customAuthlibInjectorUrl);
AuthRequest* requestor = new AuthRequest(this); QUrl url{m_data->customAuthlibInjectorUrl};
connect(requestor, &AuthRequest::finished, this, &AuthlibInjectorMetadataStep::onRequestDone);
requestor->get(request); m_response.reset(new QByteArray());
m_request = Net::Download::makeByteArray(url, m_response);
m_task.reset(new NetJob("AuthlibInjectorMetadataStep", APPLICATION->network()));
m_task->setAskRetry(false);
m_task->setAutoRetryLimit(0);
m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &AuthlibInjectorMetadataStep::onRequestDone);
m_task->start();
} }
void AuthlibInjectorMetadataStep::rehydrate() {} void AuthlibInjectorMetadataStep::onRequestDone()
void AuthlibInjectorMetadataStep::onRequestDone(QNetworkReply::NetworkError error,
QByteArray data,
QList<QNetworkReply::RawHeaderPair> headers)
{ {
auto requestor = qobject_cast<AuthRequest*>(QObject::sender()); if (m_request->error() == QNetworkReply::NoError && m_response->size() > 0) {
requestor->deleteLater();
if (error == QNetworkReply::NoError && data.size() > 0) {
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(*m_response, &jsonError);
if (jsonError.error == QJsonParseError::NoError) { if (jsonError.error == QJsonParseError::NoError) {
m_data->authlibInjectorMetadata = data.toBase64(); m_data->authlibInjectorMetadata = m_response->toBase64();
emit finished(AccountTaskState::STATE_WORKING, tr("Got authlib-injector metadata.")); emit finished(AccountTaskState::STATE_WORKING, tr("Got authlib-injector metadata."));
return; return;
} }
} }
emit finished(AccountTaskState::STATE_WORKING, tr("Didn't get authlib-injector metadata, continuing anyway.")); emit finished(AccountTaskState::STATE_WORKING, tr("Couldn't get authlib-injector metadata, continuing anyway."));
} }

View File

@ -1,8 +1,9 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include "QObjectPtr.h"
#include "minecraft/auth/AuthStep.h" #include "minecraft/auth/AuthStep.h"
#include "net/NetJob.h"
#include "net/Download.h"
class AuthlibInjectorMetadataStep : public AuthStep { class AuthlibInjectorMetadataStep : public AuthStep {
Q_OBJECT Q_OBJECT
@ -12,10 +13,14 @@ class AuthlibInjectorMetadataStep : public AuthStep {
virtual ~AuthlibInjectorMetadataStep() noexcept; virtual ~AuthlibInjectorMetadataStep() noexcept;
void perform() override; void perform() override;
void rehydrate() override;
QString describe() override; QString describe() override;
private slots: private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); void onRequestDone();
private:
std::shared_ptr<QByteArray> m_response;
Net::Download::Ptr m_request;
NetJob::Ptr m_task;
}; };

View File

@ -75,12 +75,12 @@ void MSADeviceCodeStep::perform()
m_task->setAskRetry(false); m_task->setAskRetry(false);
m_task->addNetAction(m_request); m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAutorizationFinished); connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAuthorizationFinished);
m_task->start(); m_task->start();
} }
struct DeviceAutorizationResponse { struct DeviceAuthorizationResponse {
QString device_code; QString device_code;
QString user_code; QString user_code;
QString verification_uri; QString verification_uri;
@ -91,17 +91,17 @@ struct DeviceAutorizationResponse {
QString error_description; QString error_description;
}; };
DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& data) DeviceAuthorizationResponse parseDeviceAuthorizationResponse(const QByteArray& data)
{ {
QJsonParseError err; QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(data, &err); QJsonDocument doc = QJsonDocument::fromJson(data, &err);
if (err.error != QJsonParseError::NoError) { if (err.error != QJsonParseError::NoError) {
qWarning() << "Failed to parse device autorization response due to err:" << err.errorString(); qWarning() << "Failed to parse device authorization response due to err:" << err.errorString();
return {}; return {};
} }
if (!doc.isObject()) { if (!doc.isObject()) {
qWarning() << "Device autorization response is not an object"; qWarning() << "Device authorization response is not an object";
return {}; return {};
} }
auto obj = doc.object(); auto obj = doc.object();
@ -112,9 +112,9 @@ DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& dat
}; };
} }
void MSADeviceCodeStep::deviceAutorizationFinished() void MSADeviceCodeStep::deviceAuthorizationFinished()
{ {
auto rsp = parseDeviceAutorizationResponse(*m_response); auto rsp = parseDeviceAuthorizationResponse(*m_response);
if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) { if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) {
qWarning() << "Device authorization failed:" << rsp.error; qWarning() << "Device authorization failed:" << rsp.error;
emit finished(AccountTaskState::STATE_FAILED_HARD, emit finished(AccountTaskState::STATE_FAILED_HARD,
@ -210,12 +210,12 @@ AuthenticationResponse parseAuthenticationResponse(const QByteArray& data)
QJsonParseError err; QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(data, &err); QJsonDocument doc = QJsonDocument::fromJson(data, &err);
if (err.error != QJsonParseError::NoError) { if (err.error != QJsonParseError::NoError) {
qWarning() << "Failed to parse device autorization response due to err:" << err.errorString(); qWarning() << "Failed to parse device authorization response due to err:" << err.errorString();
return {}; return {};
} }
if (!doc.isObject()) { if (!doc.isObject()) {
qWarning() << "Device autorization response is not an object"; qWarning() << "Device authorization response is not an object";
return {}; return {};
} }
auto obj = doc.object(); auto obj = doc.object();

View File

@ -58,7 +58,7 @@ class MSADeviceCodeStep : public AuthStep {
void authorizeWithBrowser(QString url, QString code, int expiresIn); void authorizeWithBrowser(QString url, QString code, int expiresIn);
private slots: private slots:
void deviceAutorizationFinished(); void deviceAuthorizationFinished();
void startPoolTimer(); void startPoolTimer();
void authenticateUser(); void authenticateUser();
void authenticationFinished(); void authenticationFinished();

View File

@ -85,8 +85,7 @@ class CustomOAuthOobReplyHandler : public QOAuthOobReplyHandler {
MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent) MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent)
{ {
m_clientId = APPLICATION->getMSAClientID(); m_clientId = APPLICATION->getMSAClientID();
if (QCoreApplication::applicationFilePath().startsWith("/tmp/.mount_") || if (QCoreApplication::applicationFilePath().startsWith("/tmp/.mount_") || APPLICATION->isPortable() || !isSchemeHandlerRegistered())
QFile::exists(FS::PathCombine(APPLICATION->root(), "portable.txt")) || !isSchemeHandlerRegistered())
{ {
auto replyHandler = new QOAuthHttpServerReplyHandler(this); auto replyHandler = new QOAuthHttpServerReplyHandler(this);
@ -100,44 +99,44 @@ MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(sile
</script> </script>
)XXX") )XXX")
.arg(BuildConfig.LOGIN_CALLBACK_URL)); .arg(BuildConfig.LOGIN_CALLBACK_URL));
oauth2.setReplyHandler(replyHandler); m_oauth2.setReplyHandler(replyHandler);
} else { } else {
oauth2.setReplyHandler(new CustomOAuthOobReplyHandler(this)); m_oauth2.setReplyHandler(new CustomOAuthOobReplyHandler(this));
} }
oauth2.setAuthorizationUrl(QUrl("https://login.live.com/oauth20_connect.srf")); m_oauth2.setAuthorizationUrl(QUrl("https://login.live.com/oauth20_connect.srf"));
oauth2.setAccessTokenUrl(QUrl("https://login.live.com/oauth20_token.srf")); m_oauth2.setAccessTokenUrl(QUrl("https://login.live.com/oauth20_token.srf"));
const auto& scope = "service::user.auth.xboxlive.com::MBI_SSL"; const auto& scope = "service::user.auth.xboxlive.com::MBI_SSL";
oauth2.setScope(scope); m_oauth2.setScope(scope);
// QOAuth2AuthorizationCodeFlow doesn't pass a "scope" when refreshing access tokens, but Microsoft expects it. // QOAuth2AuthorizationCodeFlow doesn't pass a "scope" when refreshing access tokens, but Microsoft expects it.
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QVariantMap* parameters) { m_oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QVariantMap* parameters) {
if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) { if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) {
(*parameters)["scope"] = scope; (*parameters)["scope"] = scope;
} }
}); });
#else #else
oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* parameters) { m_oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* parameters) {
if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) { if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) {
(*parameters).insert("scope", scope); (*parameters).insert("scope", scope);
} }
}); });
#endif #endif
oauth2.setClientIdentifier(m_clientId); m_oauth2.setClientIdentifier(m_clientId);
oauth2.setNetworkAccessManager(APPLICATION->network().get()); m_oauth2.setNetworkAccessManager(APPLICATION->network().get());
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::granted, this, [this] { connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::granted, this, [this] {
m_data->msaClientID = oauth2.clientIdentifier(); m_data->msaClientID = m_oauth2.clientIdentifier();
m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc(); m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc();
m_data->msaToken.notAfter = oauth2.expirationAt(); m_data->msaToken.notAfter = m_oauth2.expirationAt();
m_data->msaToken.extra = oauth2.extraTokens(); m_data->msaToken.extra = m_oauth2.extraTokens();
m_data->msaToken.refresh_token = oauth2.refreshToken(); m_data->msaToken.refresh_token = m_oauth2.refreshToken();
m_data->msaToken.token = oauth2.token(); m_data->msaToken.token = m_oauth2.token();
emit finished(AccountTaskState::STATE_WORKING, tr("Got ")); emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
}); });
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &MSAStep::authorizeWithBrowser); connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &MSAStep::authorizeWithBrowser);
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this, silent](const QAbstractOAuth2::Error err) { connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this, silent](const QAbstractOAuth2::Error err) {
auto state = AccountTaskState::STATE_FAILED_HARD; auto state = AccountTaskState::STATE_FAILED_HARD;
if (oauth2.status() == QAbstractOAuth::Status::Granted || silent) { if (m_oauth2.status() == QAbstractOAuth::Status::Granted || silent) {
if (err == QAbstractOAuth2::Error::NetworkError) { if (err == QAbstractOAuth2::Error::NetworkError) {
state = AccountTaskState::STATE_OFFLINE; state = AccountTaskState::STATE_OFFLINE;
} else { } else {
@ -151,16 +150,16 @@ MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(sile
qWarning() << message; qWarning() << message;
emit finished(state, message); emit finished(state, message);
}); });
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::error, this, connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::error, this,
[this](const QString& error, const QString& errorDescription, const QUrl& uri) { [this](const QString& error, const QString& errorDescription, const QUrl& uri) {
qWarning() << "Failed to login because" << error << errorDescription; qWarning() << "Failed to login because" << error << errorDescription;
emit finished(AccountTaskState::STATE_FAILED_HARD, errorDescription); emit finished(AccountTaskState::STATE_FAILED_HARD, errorDescription);
}); });
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::extraTokensChanged, this, connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::extraTokensChanged, this,
[this](const QVariantMap& tokens) { m_data->msaToken.extra = tokens; }); [this](const QVariantMap& tokens) { m_data->msaToken.extra = tokens; });
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::clientIdentifierChanged, this, connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::clientIdentifierChanged, this,
[this](const QString& clientIdentifier) { m_data->msaClientID = clientIdentifier; }); [this](const QString& clientIdentifier) { m_data->msaClientID = clientIdentifier; });
} }
@ -181,20 +180,20 @@ void MSAStep::perform()
emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - refresh token is empty.")); emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - refresh token is empty."));
return; return;
} }
oauth2.setRefreshToken(m_data->msaToken.refresh_token); m_oauth2.setRefreshToken(m_data->msaToken.refresh_token);
oauth2.refreshAccessToken(); m_oauth2.refreshAccessToken();
} else { } else {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0
oauth2.setModifyParametersFunction( m_oauth2.setModifyParametersFunction(
[](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* map) { map->insert("prompt", "select_account"); }); [](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* map) { map->insert("prompt", "select_account"); });
#else #else
oauth2.setModifyParametersFunction( m_oauth2.setModifyParametersFunction(
[](QAbstractOAuth::Stage stage, QMap<QString, QVariant>* map) { map->insert("prompt", "select_account"); }); [](QAbstractOAuth::Stage stage, QMap<QString, QVariant>* map) { map->insert("prompt", "select_account"); });
#endif #endif
*m_data = AccountData(); *m_data = AccountData();
m_data->msaClientID = m_clientId; m_data->msaClientID = m_clientId;
oauth2.grant(); m_oauth2.grant();
} }
} }

View File

@ -55,5 +55,5 @@ class MSAStep : public AuthStep {
private: private:
bool m_silent; bool m_silent;
QString m_clientId; QString m_clientId;
QOAuth2AuthorizationCodeFlow oauth2; QOAuth2AuthorizationCodeFlow m_oauth2;
}; };

View File

@ -1,4 +1,5 @@
#include "YggdrasilStep.h" #include "YggdrasilStep.h"
#include <QInputDialog>
#include "Application.h" #include "Application.h"
#include "net/RawHeaderProxy.h" #include "net/RawHeaderProxy.h"
@ -48,10 +49,9 @@ void YggdrasilStep::login(QString password)
req.insert("username", m_data->userName()); req.insert("username", m_data->userName());
req.insert("password", password); req.insert("password", password);
req.insert("requestUser", false); req.insert("requestUser", false);
//
// If we already have a client token, give it to the server. // If we already have a client token, give it to the server.
// Otherwise, let the server give us one. // Otherwise, let the server give us one.
m_data->generateClientTokenIfMissing(); m_data->generateClientTokenIfMissing();
req.insert("clientToken", m_data->clientToken()); req.insert("clientToken", m_data->clientToken());
@ -79,19 +79,26 @@ void YggdrasilStep::refresh()
/* /*
* { * {
* "clientToken": "client identifier" * "clientToken": "client identifier",
* "accessToken": "current access token to be refreshed" * "accessToken": "current access token to be refreshed",
* "selectedProfile": // specifying this causes errors * "selectedProfile": {
* { * "id": "profile ID",
* "id": "profile ID" * "name": "profile name"
* "name": "profile name" * },
* }
* "requestUser": true/false // request the user structure * "requestUser": true/false // request the user structure
* } * }
*/ */
QJsonObject req; QJsonObject req;
req.insert("clientToken", m_data->clientToken()); req.insert("clientToken", m_data->clientToken());
req.insert("accessToken", m_data->accessToken()); req.insert("accessToken", m_data->accessToken());
if (m_didSelectProfile) {
QJsonObject selectedProfile;
selectedProfile.insert("id", m_data->profileId());
selectedProfile.insert("name", m_data->profileName());
req.insert("selectedProfile", selectedProfile);
}
req.insert("requestUser", false); req.insert("requestUser", false);
QJsonDocument doc(req); QJsonDocument doc(req);
@ -235,20 +242,51 @@ void YggdrasilStep::processResponse(QJsonObject responseData)
m_data->yggdrasilToken.validity = Validity::Certain; m_data->yggdrasilToken.validity = Validity::Certain;
m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
// Get UUID here since we need it for later // Select a profile
// FIXME: Here is a simple workaround for now,, which uses the first available profile when selectedProfile is not provided
auto profile = responseData.value("selectedProfile"); auto profile = responseData.value("selectedProfile");
if (!profile.isObject()) { if (profile.isObject()) {
m_didSelectProfile = false;
} else {
if (m_didSelectProfile) {
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't save our selected profile."));
}
auto profiles = responseData.value("availableProfiles"); auto profiles = responseData.value("availableProfiles");
if (!profiles.isArray()) { if (!profiles.isArray()) {
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send available profiles.")); emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send available profiles."));
return; return;
} else { } else {
if (profiles.toArray().isEmpty()) { const auto& profilesArray = profiles.toArray();
if (profilesArray.isEmpty()) {
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Account has no available profile.")); emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Account has no available profile."));
return; return;
} else { } else if (profilesArray.size() == 1) {
profile = profiles.toArray().first(); profile = profiles.toArray().first();
} else {
std::map<QString, QJsonValue> profileMap;
QStringList profileNames;
const auto& invalidProfileMessage = tr("Authentication server sent an invalid available profile.");
for (const auto& profileValue : profilesArray) {
if (!profileValue.isObject()) {
emit finished(AccountTaskState::STATE_FAILED_HARD, invalidProfileMessage);
}
const auto& profileNameValue = profileValue.toObject().value("name");
if (!profileNameValue.isString()) {
emit finished(AccountTaskState::STATE_FAILED_HARD, invalidProfileMessage);
}
const auto& profileName = profileNameValue.toString();
profileMap.insert({ profileName, profileValue });
profileNames.append(profileName);
}
bool ok;
const auto& profileName =
QInputDialog::getItem(nullptr, "Select a player", "Select a player:", profileNames, 0, false, &ok);
if (!ok || profileName.isEmpty()) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication cancelled."));
}
profile = profileMap[profileName];
m_didSelectProfile = true;
} }
} }
} }
@ -267,6 +305,12 @@ void YggdrasilStep::processResponse(QJsonObject responseData)
return; return;
} }
if (m_didSelectProfile) {
// The authlib-injector specification requires that we refresh immediately after the user has selected a profile:
// https://github.com/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#%E8%B4%A6%E6%88%B7%E7%9A%84%E6%B7%BB%E5%8A%A0
return refresh();
}
emit finished(AccountTaskState::STATE_WORKING, "Logged in"); emit finished(AccountTaskState::STATE_WORKING, "Logged in");
} }

View File

@ -33,4 +33,5 @@ class YggdrasilStep : public AuthStep {
std::shared_ptr<QByteArray> m_response; std::shared_ptr<QByteArray> m_response;
Net::Upload::Ptr m_request; Net::Upload::Ptr m_request;
NetJob::Ptr m_task; NetJob::Ptr m_task;
bool m_didSelectProfile = false;
}; };

View File

@ -57,9 +57,7 @@
#include "tasks/SequentialTask.h" #include "tasks/SequentialTask.h"
AutoInstallJava::AutoInstallJava(LaunchTask* parent) AutoInstallJava::AutoInstallJava(LaunchTask* parent)
: LaunchStep(parent) : LaunchStep(parent), m_instance(m_parent->instance()), m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {};
, m_instance(m_parent->instance())
, m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {};
void AutoInstallJava::executeTask() void AutoInstallJava::executeTask()
{ {
@ -78,7 +76,7 @@ void AutoInstallJava::executeTask()
auto java = std::dynamic_pointer_cast<JavaInstall>(javas->at(i)); auto java = std::dynamic_pointer_cast<JavaInstall>(javas->at(i));
if (java && packProfile->getProfile()->getCompatibleJavaMajors().contains(java->id.major())) { if (java && packProfile->getProfile()->getCompatibleJavaMajors().contains(java->id.major())) {
if (!java->is_64bit) { if (!java->is_64bit) {
emit logLine(tr("The automatic Java mechanism detected a 32-bit installation of Java."), MessageLevel::Info); emit logLine(tr("The automatic Java mechanism detected a 32-bit installation of Java."), MessageLevel::Launcher);
} }
setJavaPath(java->path); setJavaPath(java->path);
return; return;
@ -136,7 +134,7 @@ void AutoInstallJava::setJavaPath(QString path)
settings->set("OverrideJavaLocation", true); settings->set("OverrideJavaLocation", true);
settings->set("JavaPath", path); settings->set("JavaPath", path);
settings->set("AutomaticJava", true); settings->set("AutomaticJava", true);
emit logLine(tr("Compatible Java found at: %1.").arg(path), MessageLevel::Info); emit logLine(tr("Compatible Java found at: %1.").arg(path), MessageLevel::Launcher);
emitSucceeded(); emitSucceeded();
} }
@ -179,7 +177,7 @@ void AutoInstallJava::downloadJava(Meta::Version::Ptr version, QString javaName)
return; return;
} }
#if defined(Q_OS_MACOS) #if defined(Q_OS_MACOS)
auto seq = makeShared<SequentialTask>(this, tr("Install Java")); auto seq = makeShared<SequentialTask>(tr("Install Java"));
seq->addTask(m_current_task); seq->addTask(m_current_task);
seq->addTask(makeShared<Java::SymlinkTask>(final_path)); seq->addTask(makeShared<Java::SymlinkTask>(final_path));
m_current_task = seq; m_current_task = seq;

View File

@ -132,6 +132,7 @@ void LauncherPartLaunch::executeTask()
QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
if (!wrapperCommandStr.isEmpty()) { if (!wrapperCommandStr.isEmpty()) {
wrapperCommandStr = m_parent->substituteVariables(wrapperCommandStr);
auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr);
auto wrapperCommand = wrapperArgs.takeFirst(); auto wrapperCommand = wrapperArgs.takeFirst();
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
@ -171,6 +172,7 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state)
case LoggedProcess::Aborted: case LoggedProcess::Aborted:
case LoggedProcess::Crashed: { case LoggedProcess::Crashed: {
m_parent->setPid(-1); m_parent->setPid(-1);
m_parent->instance()->setMinecraftRunning(false);
emitFailed(tr("Game crashed.")); emitFailed(tr("Game crashed."));
return; return;
} }

View File

@ -261,7 +261,7 @@ bool ResourceFolderModel::update()
return true; return true;
} }
void ResourceFolderModel::resolveResource(Resource* res) void ResourceFolderModel::resolveResource(Resource::Ptr res)
{ {
if (!res->shouldResolve()) { if (!res->shouldResolve()) {
return; return;
@ -277,11 +277,14 @@ void ResourceFolderModel::resolveResource(Resource* res)
m_active_parse_tasks.insert(ticket, task); m_active_parse_tasks.insert(ticket, task);
connect( connect(
task.get(), &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); task.get(), &Task::succeeded, this, [this, ticket, res] { onParseSucceeded(ticket, res->internal_id()); },
connect(task.get(), &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); Qt::ConnectionType::QueuedConnection);
connect(
task.get(), &Task::failed, this, [this, ticket, res] { onParseFailed(ticket, res->internal_id()); },
Qt::ConnectionType::QueuedConnection);
connect( connect(
task.get(), &Task::finished, this, task.get(), &Task::finished, this,
[=] { [this, ticket] {
m_active_parse_tasks.remove(ticket); m_active_parse_tasks.remove(ticket);
emit parseFinished(); emit parseFinished();
}, },
@ -317,7 +320,7 @@ void ResourceFolderModel::onUpdateSucceeded()
void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id) void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
{ {
auto iter = m_active_parse_tasks.constFind(ticket); auto iter = m_active_parse_tasks.constFind(ticket);
if (iter == m_active_parse_tasks.constEnd()) if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id))
return; return;
int row = m_resources_index[resource_id]; int row = m_resources_index[resource_id];
@ -629,7 +632,7 @@ QString ResourceFolderModel::instDirPath() const
void ResourceFolderModel::onParseFailed(int ticket, QString resource_id) void ResourceFolderModel::onParseFailed(int ticket, QString resource_id)
{ {
auto iter = m_active_parse_tasks.constFind(ticket); auto iter = m_active_parse_tasks.constFind(ticket);
if (iter == m_active_parse_tasks.constEnd()) if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id))
return; return;
auto removed_index = m_resources_index[resource_id]; auto removed_index = m_resources_index[resource_id];

View File

@ -76,7 +76,7 @@ class ResourceFolderModel : public QAbstractListModel {
virtual bool update(); virtual bool update();
/** Creates a new parse task, if needed, for 'res' and start it.*/ /** Creates a new parse task, if needed, for 'res' and start it.*/
virtual void resolveResource(Resource* res); virtual void resolveResource(Resource::Ptr res);
[[nodiscard]] qsizetype size() const { return m_resources.size(); } [[nodiscard]] qsizetype size() const { return m_resources.size(); }
[[nodiscard]] bool empty() const { return size() == 0; } [[nodiscard]] bool empty() const { return size() == 0; }
@ -285,7 +285,7 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>
} }
m_resources[row].reset(new_resource); m_resources[row].reset(new_resource);
resolveResource(m_resources.at(row).get()); resolveResource(m_resources.at(row));
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
} }
} }
@ -333,7 +333,7 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>
for (auto& added : added_set) { for (auto& added : added_set) {
auto res = new_resources[added]; auto res = new_resources[added];
m_resources.append(res); m_resources.append(res);
resolveResource(m_resources.last().get()); resolveResource(m_resources.last());
} }
endInsertRows(); endInsertRows();

View File

@ -7,6 +7,7 @@
#include <memory> #include <memory>
#include "Application.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "minecraft/mod/Resource.h" #include "minecraft/mod/Resource.h"
@ -25,16 +26,12 @@ class BasicFolderLoadTask : public Task {
[[nodiscard]] ResultPtr result() const { return m_result; } [[nodiscard]] ResultPtr result() const { return m_result; }
public: public:
BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread()) BasicFolderLoadTask(QDir dir) : Task(false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread())
{ {
m_create_func = [](QFileInfo const& entry) -> Resource::Ptr { return makeShared<Resource>(entry); }; m_create_func = [](QFileInfo const& entry) -> Resource::Ptr { return makeShared<Resource>(entry); };
} }
BasicFolderLoadTask(QDir dir, std::function<Resource::Ptr(QFileInfo const&)> create_function) BasicFolderLoadTask(QDir dir, std::function<Resource::Ptr(QFileInfo const&)> create_function)
: Task(nullptr, false) : Task(false), m_dir(dir), m_result(new Result), m_create_func(std::move(create_function)), m_thread_to_spawn_into(thread())
, m_dir(dir)
, m_result(new Result)
, m_create_func(std::move(create_function))
, m_thread_to_spawn_into(thread())
{} {}
[[nodiscard]] bool canAbort() const override { return true; } [[nodiscard]] bool canAbort() const override { return true; }
@ -52,6 +49,9 @@ class BasicFolderLoadTask : public Task {
m_dir.refresh(); m_dir.refresh();
for (auto entry : m_dir.entryInfoList()) { for (auto entry : m_dir.entryInfoList()) {
auto filePath = entry.absoluteFilePath(); auto filePath = entry.absoluteFilePath();
if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) {
continue;
}
auto newFilePath = FS::getUniqueResourceName(filePath); auto newFilePath = FS::getUniqueResourceName(filePath);
if (newFilePath != filePath) { if (newFilePath != filePath) {
FS::move(filePath, newFilePath); FS::move(filePath, newFilePath);

View File

@ -52,11 +52,10 @@ static bool checkDependencies(std::shared_ptr<GetModDependenciesTask::PackDepend
(!loaders || !sel->version.loaders || sel->version.loaders & loaders); (!loaders || !sel->version.loaders || sel->version.loaders & loaders);
} }
GetModDependenciesTask::GetModDependenciesTask(QObject* parent, GetModDependenciesTask::GetModDependenciesTask(BaseInstance* instance,
BaseInstance* instance,
ModFolderModel* folder, ModFolderModel* folder,
QList<std::shared_ptr<PackDependency>> selected) QList<std::shared_ptr<PackDependency>> selected)
: SequentialTask(parent, tr("Get dependencies")) : SequentialTask(tr("Get dependencies"))
, m_selected(selected) , m_selected(selected)
, m_flame_provider{ ModPlatform::ResourceProvider::FLAME, std::make_shared<ResourceDownload::FlameModModel>(*instance), , m_flame_provider{ ModPlatform::ResourceProvider::FLAME, std::make_shared<ResourceDownload::FlameModModel>(*instance),
std::make_shared<FlameAPI>() } std::make_shared<FlameAPI>() }
@ -185,7 +184,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
auto provider = providerName == m_flame_provider.name ? m_flame_provider : m_modrinth_provider; auto provider = providerName == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
auto tasks = makeShared<SequentialTask>( auto tasks = makeShared<SequentialTask>(
this, QString("DependencyInfo: %1").arg(dep.addonId.toString().isEmpty() ? dep.version : dep.addonId.toString())); QString("DependencyInfo: %1").arg(dep.addonId.toString().isEmpty() ? dep.version : dep.addonId.toString()));
if (!dep.addonId.toString().isEmpty()) { if (!dep.addonId.toString().isEmpty()) {
tasks->addTask(getProjectInfoTask(pDep)); tasks->addTask(getProjectInfoTask(pDep));

View File

@ -61,10 +61,7 @@ class GetModDependenciesTask : public SequentialTask {
std::shared_ptr<ResourceAPI> api; std::shared_ptr<ResourceAPI> api;
}; };
explicit GetModDependenciesTask(QObject* parent, explicit GetModDependenciesTask(BaseInstance* instance, ModFolderModel* folder, QList<std::shared_ptr<PackDependency>> selected);
BaseInstance* instance,
ModFolderModel* folder,
QList<std::shared_ptr<PackDependency>> selected);
auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; } auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; }
QHash<QString, PackDependencyExtraInfo> getExtraInfo(); QHash<QString, PackDependencyExtraInfo> getExtraInfo();

View File

@ -157,7 +157,7 @@ bool validate(QFileInfo file)
} // namespace DataPackUtils } // namespace DataPackUtils
LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) : Task(nullptr, false), m_token(token), m_data_pack(dp) {} LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) : Task(false), m_token(token), m_data_pack(dp) {}
bool LocalDataPackParseTask::abort() bool LocalDataPackParseTask::abort()
{ {

View File

@ -8,6 +8,7 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonValue> #include <QJsonValue>
#include <QRegularExpression>
#include <QString> #include <QString>
#include "FileSystem.h" #include "FileSystem.h"
@ -15,6 +16,8 @@
#include "minecraft/mod/ModDetails.h" #include "minecraft/mod/ModDetails.h"
#include "settings/INIFile.h" #include "settings/INIFile.h"
static QRegularExpression newlineRegex("\r\n|\n|\r");
namespace ModUtils { namespace ModUtils {
// NEW format // NEW format
@ -290,86 +293,90 @@ ModDetails ReadFabricModInfo(QByteArray contents)
// https://github.com/QuiltMC/rfcs/blob/master/specification/0002-quilt.mod.json.md // https://github.com/QuiltMC/rfcs/blob/master/specification/0002-quilt.mod.json.md
ModDetails ReadQuiltModInfo(QByteArray contents) ModDetails ReadQuiltModInfo(QByteArray contents)
{ {
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
auto object = Json::requireObject(jsonDoc, "quilt.mod.json");
auto schemaVersion = Json::ensureInteger(object.value("schema_version"), 0, "Quilt schema_version");
ModDetails details; ModDetails details;
try {
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
auto object = Json::requireObject(jsonDoc, "quilt.mod.json");
auto schemaVersion = Json::ensureInteger(object.value("schema_version"), 0, "Quilt schema_version");
// https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md // https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md
if (schemaVersion == 1) { if (schemaVersion == 1) {
auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info"); auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info");
details.mod_id = Json::requireString(modInfo.value("id"), "Mod ID"); details.mod_id = Json::requireString(modInfo.value("id"), "Mod ID");
details.version = Json::requireString(modInfo.value("version"), "Mod version"); details.version = Json::requireString(modInfo.value("version"), "Mod version");
auto modMetadata = Json::ensureObject(modInfo.value("metadata")); auto modMetadata = Json::ensureObject(modInfo.value("metadata"));
details.name = Json::ensureString(modMetadata.value("name"), details.mod_id); details.name = Json::ensureString(modMetadata.value("name"), details.mod_id);
details.description = Json::ensureString(modMetadata.value("description")); details.description = Json::ensureString(modMetadata.value("description"));
auto modContributors = Json::ensureObject(modMetadata.value("contributors")); auto modContributors = Json::ensureObject(modMetadata.value("contributors"));
// We don't really care about the role of a contributor here // We don't really care about the role of a contributor here
details.authors += modContributors.keys(); details.authors += modContributors.keys();
auto modContact = Json::ensureObject(modMetadata.value("contact")); auto modContact = Json::ensureObject(modMetadata.value("contact"));
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")) { if (modContact.contains("issues")) {
details.issue_tracker = Json::requireString(modContact.value("issues")); details.issue_tracker = Json::requireString(modContact.value("issues"));
} }
if (modMetadata.contains("license")) { if (modMetadata.contains("license")) {
auto license = modMetadata.value("license"); auto license = modMetadata.value("license");
if (license.isArray()) { if (license.isArray()) {
for (auto l : license.toArray()) { for (auto l : license.toArray()) {
if (l.isString()) { if (l.isString()) {
details.licenses.append(ModLicense(l.toString())); details.licenses.append(ModLicense(l.toString()));
} else if (l.isObject()) { } else if (l.isObject()) {
auto obj = l.toObject(); auto obj = l.toObject();
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(),
obj.value("url").toString(), obj.value("description").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();
} }
} 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")) { } catch (const Exception& e) {
auto icon = modMetadata.value("icon"); qWarning() << "Unable to parse mod info:" << e.cause();
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;
} }
@ -487,11 +494,11 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level)
} }
// quick and dirty line-by-line parser // quick and dirty line-by-line parser
auto manifestLines = file.readAll().split('\n'); auto manifestLines = QString(file.readAll()).split(newlineRegex);
QString manifestVersion = ""; QString manifestVersion = "";
for (auto& line : manifestLines) { for (auto& line : manifestLines) {
if (QString(line).startsWith("Implementation-Version: ")) { if (line.startsWith("Implementation-Version: ", Qt::CaseInsensitive)) {
manifestVersion = QString(line).remove("Implementation-Version: "); manifestVersion = line.remove("Implementation-Version: ", Qt::CaseInsensitive);
break; break;
} }
} }
@ -730,7 +737,7 @@ bool loadIconFile(const Mod& mod, QPixmap* pixmap)
} // namespace ModUtils } // namespace ModUtils
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile) LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
: Task(nullptr, false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result()) : Task(false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result())
{} {}
bool LocalModParseTask::abort() bool LocalModParseTask::abort()

View File

@ -358,9 +358,7 @@ bool validate(QFileInfo file)
} // namespace ResourcePackUtils } // namespace ResourcePackUtils
LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp) LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp) : Task(false), m_token(token), m_resource_pack(rp) {}
: Task(nullptr, false), m_token(token), m_resource_pack(rp)
{}
bool LocalResourcePackParseTask::abort() bool LocalResourcePackParseTask::abort()
{ {

View File

@ -93,7 +93,7 @@ bool validate(QFileInfo file)
} // namespace ShaderPackUtils } // namespace ShaderPackUtils
LocalShaderPackParseTask::LocalShaderPackParseTask(int token, ShaderPack& sp) : Task(nullptr, false), m_token(token), m_shader_pack(sp) {} LocalShaderPackParseTask::LocalShaderPackParseTask(int token, ShaderPack& sp) : Task(false), m_token(token), m_shader_pack(sp) {}
bool LocalShaderPackParseTask::abort() bool LocalShaderPackParseTask::abort()
{ {

View File

@ -230,8 +230,7 @@ bool validate(QFileInfo file)
} // namespace TexturePackUtils } // namespace TexturePackUtils
LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp) : Task(nullptr, false), m_token(token), m_texture_pack(rp) LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp) : Task(false), m_token(token), m_texture_pack(rp) {}
{}
bool LocalTexturePackParseTask::abort() bool LocalTexturePackParseTask::abort()
{ {

View File

@ -170,7 +170,7 @@ bool validate(QFileInfo file)
} // namespace WorldSaveUtils } // namespace WorldSaveUtils
LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) : Task(nullptr, false), m_token(token), m_save(save) {} LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) : Task(false), m_token(token), m_save(save) {}
bool LocalWorldSaveParseTask::abort() bool LocalWorldSaveParseTask::abort()
{ {

View File

@ -36,13 +36,14 @@
#include "ModFolderLoadTask.h" #include "ModFolderLoadTask.h"
#include "Application.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/MetadataHandler.h"
#include <QThread> #include <QThread>
ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan) ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan)
: Task(nullptr, false) : Task(false)
, m_mods_dir(mods_dir) , m_mods_dir(mods_dir)
, m_index_dir(index_dir) , m_index_dir(index_dir)
, m_is_indexed(is_indexed) , m_is_indexed(is_indexed)
@ -65,6 +66,9 @@ void ModFolderLoadTask::executeTask()
m_mods_dir.refresh(); m_mods_dir.refresh();
for (auto entry : m_mods_dir.entryInfoList()) { for (auto entry : m_mods_dir.entryInfoList()) {
auto filePath = entry.absoluteFilePath(); auto filePath = entry.absoluteFilePath();
if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) {
continue;
}
auto newFilePath = FS::getUniqueResourceName(filePath); auto newFilePath = FS::getUniqueResourceName(filePath);
if (newFilePath != filePath) { if (newFilePath != filePath) {
FS::move(filePath, newFilePath); FS::move(filePath, newFilePath);

View File

@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Fjord Launcher - Minecraft Launcher
* Copyright (C) 2024 Evan Goode <mail@evangoo.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AuthlibInjectorTextureDelete.h"
#include "net/ByteArraySink.h"
#include "net/RawHeaderProxy.h"
AuthlibInjectorTextureDelete::AuthlibInjectorTextureDelete(QString textureType) : NetRequest(), m_textureType(textureType)
{
logCat = taskMCSkinsLogC;
}
QNetworkReply* AuthlibInjectorTextureDelete::getReply(QNetworkRequest& request)
{
setStatus(tr("Deleting texture"));
return m_network->deleteResource(request);
}
AuthlibInjectorTextureDelete::Ptr AuthlibInjectorTextureDelete::make(MinecraftAccountPtr account, QString textureType)
{
auto up = makeShared<AuthlibInjectorTextureDelete>(textureType);
QString token = account->accessToken();
up->m_url = QUrl(account->accountServerUrl() + "/user/profile/" + account->profileId() + "/" + textureType);
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
{ "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() },
}));
return up;
}

View File

@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Fjord Launcher - Minecraft Launcher
* Copyright (C) 2024 Evan Goode <mail@evangoo.de>
*
* 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 <minecraft/auth/MinecraftAccount.h>
#include "net/NetRequest.h"
class AuthlibInjectorTextureDelete : public Net::NetRequest {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<AuthlibInjectorTextureDelete>;
AuthlibInjectorTextureDelete(QString textureType);
virtual ~AuthlibInjectorTextureDelete() = default;
static AuthlibInjectorTextureDelete::Ptr make(MinecraftAccountPtr account, QString textureType);
protected:
virtual QNetworkReply* getReply(QNetworkRequest&) override;
private:
QString m_textureType;
};

View File

@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Fjord Launcher - Minecraft Launcher
* Copyright (C) 2024 Evan Goode <mail@evangoo.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AuthlibInjectorTextureUpload.h"
#include <QHttpMultiPart>
#include "FileSystem.h"
#include "net/ByteArraySink.h"
#include "net/RawHeaderProxy.h"
AuthlibInjectorTextureUpload::AuthlibInjectorTextureUpload(QString path, std::optional<QString> skin_variant) : NetRequest(), m_path(path), m_skin_variant(skin_variant)
{
logCat = taskMCSkinsLogC;
}
QNetworkReply* AuthlibInjectorTextureUpload::getReply(QNetworkRequest& request)
{
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this);
QHttpPart file;
file.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png"));
file.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"texture.png\""));
file.setBody(FS::read(m_path));
multiPart->append(file);
if (m_skin_variant.has_value()) {
QHttpPart model;
model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"model\""));
model.setBody(m_skin_variant->toUtf8());
multiPart->append(model);
}
setStatus(tr("Uploading texture"));
return m_network->put(request, multiPart);
}
AuthlibInjectorTextureUpload::Ptr AuthlibInjectorTextureUpload::make(MinecraftAccountPtr account, QString path, std::optional<QString> skin_variant)
{
auto up = makeShared<AuthlibInjectorTextureUpload>(path, skin_variant);
QString token = account->accessToken();
QString textureType = skin_variant.has_value() ? "skin" : "cape";
up->m_url = QUrl(account->accountServerUrl() + "/user/profile/" + account->profileId() + "/" + textureType);
up->setObjectName(QString("BYTES:") + up->m_url.toString());
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
{ "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() },
}));
return up;
}

View File

@ -0,0 +1,41 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Fjord Launcher - Minecraft Launcher
* Copyright (C) 2024 Evan Goode <mail@evangoo.de>
*
* 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 <minecraft/auth/MinecraftAccount.h>
#include "net/NetRequest.h"
class AuthlibInjectorTextureUpload : public Net::NetRequest {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<AuthlibInjectorTextureUpload>;
// Note this class takes ownership of the file.
AuthlibInjectorTextureUpload(QString path, std::optional<QString> skin_variant);
virtual ~AuthlibInjectorTextureUpload() = default;
static AuthlibInjectorTextureUpload::Ptr make(MinecraftAccountPtr account, QString path, std::optional<QString> skin_variant);
protected:
virtual QNetworkReply* getReply(QNetworkRequest&) override;
private:
QString m_path;
std::optional<QString> m_skin_variant;
};

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