Compare commits

..

103 commits

Author SHA1 Message Date
Sefa Eyeoglu
770d5c92bc
Merge pull request #1453 from PrismLauncher/backport-1437-to-release-7.x 2023-07-27 09:42:42 +02:00
LostLuma
f326db11f1 Ignore cache files entirely, also apply to modpack export
Signed-off-by: LostLuma <lilly@lostluma.net>
(cherry picked from commit 4a9ea832ff)
2023-07-27 07:40:51 +00:00
LostLuma
f8c6a33134 Address review comments
Signed-off-by: LostLuma <lilly@lostluma.net>
(cherry picked from commit a9fefb2eeb)
2023-07-27 07:40:51 +00:00
LostLuma
64228bdddf Ignore cache files when exporting instances
Signed-off-by: LostLuma <lilly@lostluma.net>
(cherry picked from commit 361583edfc)
2023-07-27 07:40:51 +00:00
Sefa Eyeoglu
88be40c4bf
Merge pull request #1444 from PrismLauncher/backport-1438-to-release-7.x 2023-07-26 20:48:01 +02:00
Sefa Eyeoglu
d6095358ad fix: fix typo in README
Co-authored-by: Tayou <31988415+TayouVR@users.noreply.github.com>
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit 4ad448993c)
2023-07-26 18:30:21 +00:00
Sefa Eyeoglu
f7f2049223 chore: update information for downstreams
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit 6f652e1d2f)
2023-07-26 18:30:21 +00:00
Sefa Eyeoglu
cc43485650
Merge pull request #1442 from PrismLauncher/backport-1434-to-release-7.x 2023-07-26 16:23:21 +02:00
seth
b92d617df0 chore(ci): remove nix job
we're using garnix now

Signed-off-by: seth <getchoo@tuta.io>
(cherry picked from commit fbf29274e8)
2023-07-26 13:47:58 +00:00
seth
7b971a08a8 feat(ci): init garnix.yaml
Signed-off-by: seth <getchoo@tuta.io>
(cherry picked from commit 3c35d647b8)
2023-07-26 13:47:58 +00:00
Sefa Eyeoglu
7cc3d34498
Merge pull request #1441 from PrismLauncher/backport-1427-to-release-7.x 2023-07-26 14:46:53 +02:00
tjw123hh
d9df60368c
Delete some incorrect notr="true" tags
Signed-off-by: tjw123hh <tjw123hh@outlook.com>
(cherry picked from commit 280f041acb)
2023-07-26 14:45:59 +02:00
tjw123hh
ba49afcc6a
Update org.prismlauncher.PrismLauncher.desktop.in
Signed-off-by: tjw123hh <56749271+tjw123hh@users.noreply.github.com>
(cherry picked from commit 4ab630b832)
2023-07-26 14:45:59 +02:00
tjw123hh
685badb8cf
DCO Remediation Commit for tjw123hh <tjw123hh@outlook.com>
I, tjw123hh <tjw123hh@outlook.com>, hereby add my Signed-off-by to this commit: 6a01c277e8

Signed-off-by: tjw123hh <tjw123hh@outlook.com>
(cherry picked from commit a32ca43288)
2023-07-26 14:45:50 +02:00
tjw123hh
29fde6a322
Delete some incorrect tags
(cherry picked from commit 6a01c277e8)
2023-07-26 14:45:42 +02:00
Sefa Eyeoglu
e65d48bf36
Merge pull request #1440 from PrismLauncher/backport-1420-to-release-7.x 2023-07-26 14:41:17 +02:00
Sefa Eyeoglu
38144b3661
Merge pull request #1439 from PrismLauncher/backport-1419-to-release-7.x 2023-07-26 14:41:08 +02:00
github-actions[bot]
3a0e30684e chore(nix): update lockfile
Flake lock file updates:

• Updated input 'libnbtplusplus':
    'github:PrismLauncher/libnbtplusplus/2203af7eeb48c45398139b583615134efd8d407f' (2022-04-15)
  → 'github:PrismLauncher/libnbtplusplus/a5e8fd52b8bf4ab5d5bcc042b2a247867589985f' (2023-07-22)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/46ed466081b9cad1125b11f11a2af5cc40b942c7' (2023-07-15)
  → 'github:nixos/nixpkgs/f465da166263bc0d4b39dfd4ca28b777c92d4b73' (2023-07-22)
• Updated input 'pre-commit-hooks':
    'github:cachix/pre-commit-hooks.nix/5e28316db471d1ac234beb70031b635437421dd6' (2023-07-14)
  → 'github:cachix/pre-commit-hooks.nix/eb433bff05b285258be76513add6f6c57b441775' (2023-07-18)

(cherry picked from commit 6f6b0b9661)
2023-07-26 12:40:00 +00:00
PandaNinjas
0fe184251d Update libnbtplusplus submodule
Signed-off-by: PandaNinjas <admin@malwarefight.wip.la>
(cherry picked from commit 2253100ac6)
2023-07-26 12:39:57 +00:00
Tayou
f51a66dad5
Merge pull request #1432 from TheKodeToad/revert-revert-hide-provider-column 2023-07-25 14:32:07 +02:00
TheKodeToad
071e86fe68 Revert "Revert "feat(Mods): hide 'Provider' column when no mods have providers""
This reverts commit 5eb71fc6a9.

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-07-25 12:27:50 +01:00
Sefa Eyeoglu
d7a02ee456
Merge pull request #1428 from PrismLauncher/backport-1426-to-release-7.x 2023-07-24 20:42:05 +02:00
TheKodeToad
648a69594f Fix token "0" being replaced
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit f505d43fc0)
2023-07-24 18:41:43 +00:00
Sefa Eyeoglu
70fdfd1526
Merge pull request #1416 from PrismLauncher/bump-7.2
Bump to 7.2
2023-07-21 23:01:04 +02:00
TheKodeToad
1e324e3e2f Bump to 7.2
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
2023-07-21 21:44:51 +01:00
Sefa Eyeoglu
8bbe307a31
chore: trigger build
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2023-07-21 21:54:44 +02:00
Sefa Eyeoglu
c946b2c4fb
Merge pull request #1408 from PrismLauncher/backport-1372-to-release-7.x 2023-07-19 08:36:56 +02:00
github-actions[bot]
2dcfab0a19 chore(nix): update lockfile
Flake lock file updates:

• Updated input 'flake-parts':
    'github:hercules-ci/flake-parts/267149c58a14d15f7f81b4d737308421de9d7152' (2023-07-01)
  → 'github:hercules-ci/flake-parts/8e8d955c22df93dbe24f19ea04f47a74adbdc5ec' (2023-07-04)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/cd99c2b3c9f160cd004318e0697f90bbd5960825' (2023-07-01)
  → 'github:nixos/nixpkgs/46ed466081b9cad1125b11f11a2af5cc40b942c7' (2023-07-15)
• Updated input 'pre-commit-hooks':
    'github:cachix/pre-commit-hooks.nix/42587d3414d1747999a5f71e92a83cf6547b62da' (2023-07-03)
  → 'github:cachix/pre-commit-hooks.nix/5e28316db471d1ac234beb70031b635437421dd6' (2023-07-14)

(cherry picked from commit 6597a5c860)
2023-07-18 22:17:26 +00:00
TheKodeToad
b0b9b89bce
Merge pull request #1406 from PrismLauncher/backport-1397-to-release-7.x
[Backport release-7.x] Make Launch Offline not launch online in 1.6+
2023-07-18 22:46:05 +01:00
Josiah Glosson
862f4fb061 Make Launch Offline not launch online in 1.6+
Signed-off-by: Josiah Glosson <soujournme@gmail.com>
(cherry picked from commit 1fbb41f5e2)
2023-07-18 21:40:28 +00:00
Sefa Eyeoglu
7c5d07e74d
Merge pull request #1404 from PrismLauncher/backport-1385-to-release-7.x 2023-07-18 22:46:25 +02:00
TheKodeToad
fe1ea7240e
Merge pull request #1334 from TheKodeToad/litemod-dl
LiteMod downloading
2023-07-18 22:43:35 +02:00
Sefa Eyeoglu
6f1d594f1c
Merge pull request #1128 from pandaninjas/fix-implicit-fallthrough 2023-07-18 22:43:35 +02:00
Dallas Strouse
7baaf83f1d Update Flatpak manifest
Signed-off-by: Dallas Strouse <dastrouses@gmail.com>
(cherry picked from commit 2eff1de560)
2023-07-18 20:41:33 +00:00
Sefa Eyeoglu
bab7105820
Merge pull request #1403 from PrismLauncher/backport-1362-to-release-7.x 2023-07-18 22:29:16 +02:00
Trial97
1389b74a8a fixed warning
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 440afcedb0)
2023-07-18 20:28:46 +00:00
Trial97
5aa3aabdf9 insert header
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit b0a21c9389)
2023-07-18 20:28:46 +00:00
Trial97
42d2c7446a format
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit fc4a1ef193)
2023-07-18 20:28:46 +00:00
TheKodeToad
71a7358287
Merge pull request #1395 from PrismLauncher/backport-1387-to-release-7.x
[Backport release-7.x] Check if any modloader is installed
2023-07-17 17:42:00 +01:00
Sefa Eyeoglu
eaf125c31a fix: don't take modloaders by reference
Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit 2be630904f)
2023-07-17 16:41:05 +00:00
Sefa Eyeoglu
f7e018d41a fix: check if any modloader is installed
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit 54a091ca59)
2023-07-17 16:41:05 +00:00
Sefa Eyeoglu
7d6e07ea71
Merge pull request #1384 from PrismLauncher/backport-1357-to-release-7.x
[Backport release-7.x] feat: add toggle for quilt beacon
2023-07-17 06:43:57 +02:00
seth
cd011a097b chore: better explain quilt loader beacon
Signed-off-by: seth <getchoo@tuta.io>
(cherry picked from commit a2a09ffe01)
2023-07-17 04:43:06 +00:00
seth
055bcc2721 feat: add toggle for quilt beacon
Signed-off-by: seth <getchoo@tuta.io>
(cherry picked from commit 89aaedc06c)
2023-07-17 04:43:06 +00:00
Sefa Eyeoglu
4bc4b29d5b
Merge pull request #1380 from PrismLauncher/backport-1276-to-release-7.x 2023-07-16 23:20:05 +02:00
Sefa Eyeoglu
82a9e7d372 fix(actions): set all build platforms to official
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit 40fb387185)
2023-07-16 21:16:11 +00:00
Sefa Eyeoglu
cb81cadee3 fix: set default platform to "unknown"
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit 63acf0a7b4)
2023-07-16 21:16:11 +00:00
Sefa Eyeoglu
0d8283df97 feat: print build platform in application log
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit fce000206f)
2023-07-16 21:16:11 +00:00
Sefa Eyeoglu
d4014534eb feat: print build platform in log
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit f5e4171df4)
2023-07-16 21:16:11 +00:00
Sefa Eyeoglu
3c5ec5d967 chore: remove obsolete macOS warning
We don't support that macOS version. This check also never worked, as we
never set the platform to that value.

Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit 0aaec9ae4f)
2023-07-16 21:16:11 +00:00
Sefa Eyeoglu
e3625cad91
Merge pull request #1369 from Trial97/autoselect 2023-07-16 20:43:16 +02:00
Tayou
bb945c5165
Merge pull request #1368 from Trial97/crash_curse_import 2023-07-16 20:43:16 +02:00
Rachel Powers
daa5fcce67
Merge pull request #1351 from Trial97/ftb_import
fixed substatus on ftb_import
2023-07-16 20:43:16 +02:00
seth
2bcebe2989
Merge pull request #1336 from Ryex/packaging/fix-duplicate-share-directories
packaging: fix duplicate share directories (use only lowercase)
2023-07-16 20:43:15 +02:00
Sefa Eyeoglu
27f6debdaf
Merge pull request #1306 from Ryex/ci/address-sanitiser_on_debug_builds 2023-07-16 20:43:15 +02:00
seth
7926170073
Merge pull request #1335 from Ryex/fix/gh-1322-old-zip-mods-in-wrong-place
fix(flame install): don't assume .zip is a resource pack. default to mod
2023-07-16 20:43:15 +02:00
Rachel Powers
79537f2948
Merge pull request #1352 from Trial97/crash_after_abort
Do not reset shared pointer if it's already empty
2023-07-16 20:43:15 +02:00
Tayou
2b3021b7c2
Merge pull request #1345 from Trial97/time2
feat:Added option to use system locale
2023-07-16 20:43:15 +02:00
Rachel Powers
b11b86e026
Merge pull request #1350 from Trial97/fix_managed_pack_crash
fixed crash if no version is loaded on managed page
2023-07-16 20:43:15 +02:00
Sefa Eyeoglu
a0ddd85b32
Merge pull request #1333 from Ryex/fix/null_instance_edit_crash 2023-07-16 20:43:15 +02:00
seth
6f86e8b66e
Merge pull request #1331 from TheKodeToad/hungry-trash
Fix updating trashing resources
2023-07-16 20:43:15 +02:00
Rachel Powers
6cd259becd
Merge pull request #1337 from getchoo/fix-flake-workflow
fix(actions): give update-flake content write perms
2023-07-16 20:43:10 +02:00
Rachel Powers
d9de326f22
Merge pull request #1321 from TheKodeToad/mr-optional
Optional mods in mrpack export
2023-07-16 20:43:02 +02:00
seth
1d4cf0fd03
Merge pull request #1329 from Ryex/fix/progress_dialog_centering
fix(progress dialog): if there is a parent center on creation
2023-07-16 20:43:02 +02:00
Rachel Powers
a65e4af365
Merge pull request #1302 from Ryex/fix/progress-dialog-segfault
fix: segfault in progress dialog
2023-07-16 20:43:02 +02:00
seth
721ac015f3
Merge pull request #1325 from Scrumplex/validate-meta-url
Validate Meta URL
2023-07-16 20:42:55 +02:00
seth
c5572a5e0b
Merge pull request #1310 from getchoo/autoupdate-flake
feat(actions): add update-flake-lock
2023-07-16 20:42:49 +02:00
Sefa Eyeoglu
81757717f7
Merge pull request #1284 from Ryex/fix/properly-track-failed-copies-and-clones 2023-07-16 20:42:48 +02:00
seth
1ab35357e9
Merge pull request #1304 from Scrumplex/chore-flake-update-1
flake.lock: Update
2023-07-16 20:42:48 +02:00
Tayou
7025f75903
Merge pull request #1127 from Trial97/scale_cat 2023-07-16 20:42:48 +02:00
TheKodeToad
3cc68fcea4
Merge pull request #1232 from telans/screenshots-update
ScreenshotsPage fixes
2023-07-16 20:42:48 +02:00
Sefa Eyeoglu
34be098f12
Merge pull request #1298 from TurboWafflz/develop 2023-07-16 20:42:48 +02:00
seth
fb5655085f
Merge pull request #1292 from Trial97/export5
Removed logs from instance export
2023-07-16 20:42:48 +02:00
Sefa Eyeoglu
316ef9b725
Merge pull request #1266 from TheKodeToad/smol-tweaks 2023-07-16 20:42:48 +02:00
seth
5fdbc9d75e
Merge pull request #1280 from Trial97/shortcut
Fixed illegal characters in shortcuts name
2023-07-16 20:42:48 +02:00
seth
6464127d05
Merge pull request #1281 from Trial97/screenshot
Added more information to the screenshot upload warning
2023-07-16 20:42:48 +02:00
TheKodeToad
c349eff522
Merge pull request #1277 from Trial97/remove_mojang
Removed unused files
2023-07-16 20:42:47 +02:00
Sefa Eyeoglu
5fdf73a2ff
Merge pull request #1274 from TheKodeToad/java-signature 2023-07-16 20:42:47 +02:00
Sefa Eyeoglu
6963bfc6c9
Merge pull request #1275 from Scrumplex/git-blame-ignore 2023-07-16 20:42:47 +02:00
Sefa Eyeoglu
a25a5a7c9f
Merge pull request #1065 from leo78913/gamescope-close-button 2023-07-16 20:42:47 +02:00
Sefa Eyeoglu
20e9bf0e11
Merge pull request #1261 from telans/modrinthexport-url 2023-07-16 20:42:26 +02:00
Rachel Powers
21ccf47ea7
Merge pull request #1255 from Trial97/export4
Added Thumbs.db to excluded files in MrPackExport
2023-07-16 20:42:26 +02:00
Rachel Powers
6856c2f922
Merge pull request #1259 from PrismLauncher/update-devs
Update developers
2023-07-16 20:42:26 +02:00
TheKodeToad
e52fd9d4fe
Merge pull request #1256 from Trial97/fix11
Fixed hashers
2023-07-16 20:42:25 +02:00
Rachel Powers
05056e1abf
Merge pull request #1200 from Trial97/net_job_crash
Made ByteSynkArray to use shared_ptr
2023-07-16 20:42:25 +02:00
seth
9df3c5d3c0
Merge pull request #1251 from getchoo/github-clarify
chore: add 'suggest a feature' message in help
2023-07-16 20:42:25 +02:00
seth
1a4ea3b1cd
Merge pull request #1252 from getchoo/import-hehe
chore: avoid confusion in file/url import dialog
2023-07-16 20:42:25 +02:00
Tayou
0a7a7d9bfd
Merge pull request #1235 from ChrisLane/java-check-debug-msg-fix 2023-07-16 20:42:25 +02:00
Sefa Eyeoglu
8b017f9a5f
Merge pull request #1243 from Trial97/export
Added regex expresion to exclude .DS_Store files
2023-07-16 20:42:25 +02:00
TheKodeToad
b1fe4d1d93
Merge pull request #1228 from Trial97/curent_pack_crash
Fixes #1212
2023-07-16 20:42:25 +02:00
DioEgizio
80463f9761
Merge pull request #1231 from telans/modrinth-default-icon
Modrinth: use default icon for non-managed packs
2023-07-16 20:42:25 +02:00
TheKodeToad
567af5b22d
Merge pull request #1233 from p2js/ui-consistency-fix
Remove inconsistent/unneeded question marks in UI
2023-07-16 20:42:24 +02:00
seth
7ed15b2687
Merge pull request #1214 from PrismLauncher/renovate/cachix-install-nix-action-22.x
chore(deps): update cachix/install-nix-action action to v22
2023-07-16 20:42:24 +02:00
Sefa Eyeoglu
3d502b12a9
Merge pull request #1224 from DioEgizio/add-appstream 2023-07-16 19:59:30 +02:00
seth
f0d1df9139
Merge pull request #1210 from getchoo/opengl-appimage
fix(appimage): bundle generic opengl lib
2023-07-16 19:59:30 +02:00
Sefa Eyeoglu
0fcaa336dc
Merge pull request #1377 from PrismLauncher/backport-1184-to-release-7.x 2023-07-16 19:55:45 +02:00
clickdevin
949da6b50e Fix bugs when updating curseforge modpacks
Signed-off-by: clickdevin <git@clickdevin.me>
(cherry picked from commit d4f2059b78)
2023-07-16 17:55:05 +00:00
Sefa Eyeoglu
fd0ba70080
Merge pull request #1376 from PrismLauncher/backport-1234-to-release-7.x 2023-07-16 19:52:08 +02:00
Jakub Wroński
89dd981dd7 Fix compiling on FreeBSD
Signed-off-by: Jakub Wroński <kubawronski161@gmail.com>
(cherry picked from commit a32a3e25ad)
2023-07-16 17:51:53 +00:00
Sefa Eyeoglu
30fda57c64
Merge pull request #1375 from PrismLauncher/backport-1218-to-release-7.x 2023-07-16 19:49:45 +02:00
flow
1d75c58d6c fix: hide git commit when the build doesn't have git stuff support
This fixes the Git commit string being "GITDIR-NOTFOUND" on the About
page when building a package from the bundled source archive.

Signed-off-by: flow <flowlnlnln@gmail.com>
(cherry picked from commit 2d00a727f6)
2023-07-16 17:49:15 +00:00
Sefa Eyeoglu
34d80a8c75
Merge pull request #1181 from DioEgizio/bump-7.1 2023-06-16 13:07:25 +02:00
DioEgizio
05360c1103
chore: bump to 7.1
Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
2023-06-16 12:50:08 +02:00
1231 changed files with 34206 additions and 59194 deletions

View file

@ -1,17 +1,16 @@
---
BasedOnStyle: Chromium
Language: Cpp
BasedOnStyle: Chromium
IndentWidth: 4
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AllowShortIfStatementsOnASingleLine: false
ColumnLimit: 140
---
Language: Cpp
AlignConsecutiveMacros: None
AlignConsecutiveAssignments: None
BraceWrapping:
AfterFunction: true
AfterFunction: true
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBraces: Custom
BreakConstructorInitializers: BeforeComma
ColumnLimit: 140
Cpp11BracedListStyle: false

View file

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

View file

@ -1,8 +0,0 @@
# EditorConfig specs and documentation: https://EditorConfig.org
# top-most EditorConfig file
root = true
# C++ Code Style settings
[*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}]
cpp_generate_documentation_comments = doxygen_slash_star

1
.envrc
View file

@ -1,2 +1 @@
use flake
watch_file nix/*.nix

View file

@ -2,6 +2,3 @@
# tabs -> spaces
bbb3b3e6f6e3c0f95873f22e6d0a4aaf350f49d9
# (nix) alejandra -> nixfmt
4c81d8c53d09196426568c4a31a4e752ed05397a

41
.github/scripts/prepare_JREs.sh vendored Executable file
View file

@ -0,0 +1,41 @@
#!/usr/bin/env bash
URL_JDK8="https://api.adoptium.net/v3/binary/version/jdk8u312-b07/linux/x64/jre/hotspot/normal/eclipse"
URL_JDK17="https://api.adoptium.net/v3/binary/latest/17/ga/linux/x64/jre/hotspot/normal/eclipse"
mkdir -p JREs
pushd JREs
wget --content-disposition "$URL_JDK8"
wget --content-disposition "$URL_JDK17"
for file in *;
do
mkdir temp
re='(OpenJDK([[:digit:]]+)U-jre_x64_linux_hotspot_([[:digit:]]+)(.*).tar.gz)'
if [[ $file =~ $re ]];
then
version_major=${BASH_REMATCH[2]}
version_trailing=${BASH_REMATCH[4]}
if [ $version_major = 17 ];
then
hyphen='-'
else
hyphen=''
fi
version_edit=$(echo $version_trailing | sed -e 's/_/+/g' | sed -e 's/b/-b/g')
dir_name=jdk$hyphen$version_major$version_edit-jre
mkdir jre$version_major
tar -xzf $file -C temp
pushd temp/$dir_name
cp -r . ../../jre$version_major
popd
fi
rm -rf temp
done
popd

View file

@ -1,33 +0,0 @@
name: Backport
on:
pull_request_target:
types: [closed, labeled]
# WARNING:
# When extending this action, be aware that $GITHUB_TOKEN allows write access to
# the GitHub repository. This means that it should not evaluate user input in a
# way that allows code injection.
permissions:
contents: read
jobs:
backport:
permissions:
contents: write # for korthout/backport-action to create branch
pull-requests: write # for korthout/backport-action to create PR to backport
actions: write # for korthout/backport-action to create PR with workflow changes
name: Backport Pull Request
if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs
uses: korthout/backport-action@v3.1.0
with:
# Config README: https://github.com/korthout/backport-action#backport-action
pull_description: |-
Bot-based backport to `${target_branch}`, triggered by a label in #${pull_number}.

View file

@ -21,29 +21,8 @@ on:
WINDOWS_CODESIGN_PASSWORD:
description: Password for signing Windows builds
required: false
APPLE_CODESIGN_CERT:
description: Certificate for signing macOS builds
required: false
APPLE_CODESIGN_PASSWORD:
description: Password for signing macOS builds
required: false
APPLE_CODESIGN_ID:
description: Certificate ID for signing macOS builds
required: false
APPLE_NOTARIZE_APPLE_ID:
description: Apple ID used for notarizing macOS builds
required: false
APPLE_NOTARIZE_TEAM_ID:
description: Team ID used for notarizing macOS builds
required: false
APPLE_NOTARIZE_PASSWORD:
description: Password used for notarizing macOS builds
required: false
GPG_PRIVATE_KEY:
description: Private key for AppImage signing
required: false
GPG_PRIVATE_KEY_ID:
description: ID for the GPG_PRIVATE_KEY, to select the signing key
CACHIX_AUTH_TOKEN:
description: Private token for authenticating against Cachix cache
required: false
jobs:
@ -52,67 +31,77 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-20.04
qt_ver: 5
qt_host: linux
qt_arch: ""
qt_version: "5.15.2"
qt_modules: "qtnetworkauth"
- os: ubuntu-22.04
- os: ubuntu-20.04
qt_ver: 6
qt_host: linux
qt_arch: ""
qt_version: "6.5.3"
qt_modules: "qt5compat qtimageformats qtnetworkauth"
qt_arch: ''
qt_version: '6.2.4'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
- os: windows-2022
name: "Windows-MinGW-w64"
msystem: clang64
vcvars_arch: "amd64_x86"
vcvars_arch: 'amd64_x86'
- os: windows-2022
name: "Windows-MSVC-Legacy"
msystem: ''
architecture: 'win32'
vcvars_arch: 'amd64_x86'
qt_ver: 5
qt_host: windows
qt_arch: 'win32_msvc2019'
qt_version: '5.15.2'
qt_modules: ''
qt_tools: 'tools_openssl_x86'
- os: windows-2022
name: "Windows-MSVC"
msystem: ""
architecture: "x64"
vcvars_arch: "amd64"
msystem: ''
architecture: 'x64'
vcvars_arch: 'amd64'
qt_ver: 6
qt_host: "windows"
qt_arch: "win64_msvc2022_64"
qt_version: "6.8.1"
qt_modules: "qt5compat qtimageformats qtnetworkauth"
nscurl_tag: "v24.9.26.122"
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
qt_host: windows
qt_arch: ''
qt_version: '6.5.1'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
- os: windows-2022
name: "Windows-MSVC-arm64"
msystem: ""
architecture: "arm64"
vcvars_arch: "amd64_arm64"
msystem: ''
architecture: 'arm64'
vcvars_arch: 'amd64_arm64'
qt_ver: 6
qt_host: "windows"
qt_arch: "win64_msvc2022_arm64_cross_compiled"
qt_version: "6.8.1"
qt_modules: "qt5compat qtimageformats qtnetworkauth"
nscurl_tag: "v24.9.26.122"
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
qt_host: windows
qt_arch: 'win64_msvc2019_arm64'
qt_version: '6.5.1'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
- os: macos-14
- os: macos-12
name: macOS
macosx_deployment_target: 11.0
qt_ver: 6
qt_host: mac
qt_arch: ""
qt_version: "6.8.1"
qt_modules: "qt5compat qtimageformats qtnetworkauth"
qt_arch: ''
qt_version: '6.5.0'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
- os: macos-14
- os: macos-12
name: macOS-Legacy
macosx_deployment_target: 10.13
qt_ver: 5
qt_host: mac
qt_version: "5.15.2"
qt_modules: "qtnetworkauth"
qt_version: '5.15.2'
qt_modules: ''
qt_tools: ''
runs-on: ${{ matrix.os }}
@ -130,11 +119,11 @@ jobs:
# PREPARE
##
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
submodules: "true"
submodules: 'true'
- name: "Setup MSYS2"
- name: 'Setup MSYS2'
if: runner.os == 'Windows' && matrix.msystem != ''
uses: msys2/setup-msys2@v2
with:
@ -154,7 +143,6 @@ jobs:
quazip-qt6:p
ccache:p
qt6-5compat:p
qt6-networkauth:p
cmark:p
- name: Force newer ccache
@ -164,18 +152,18 @@ jobs:
- name: Setup ccache
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
uses: hendrikmuhs/ccache-action@v1.2.17
uses: hendrikmuhs/ccache-action@v1.2.9
with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
- name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
uses: actions/cache@v4.2.1
uses: actions/cache@v3.3.1
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
restore-keys: |
${{ matrix.os }}-mingw-w64-ccache
${{ matrix.os }}-mingw-w64-ccache
- name: Setup ccache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
@ -203,7 +191,7 @@ jobs:
if: runner.os == 'Linux'
run: |
sudo apt-get -y update
sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream libxcb-cursor-dev
sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream
- name: Install Dependencies (macOS)
if: runner.os == 'macOS'
@ -211,37 +199,44 @@ jobs:
brew update
brew install ninja extra-cmake-modules
- name: Install Qt (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 6
run: |
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
- name: Install host Qt (Windows MSVC arm64)
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
uses: jurplel/install-qt-action@v4
uses: jurplel/install-qt-action@v3
with:
aqtversion: "==3.1.*"
py7zrversion: ">=0.20.2"
version: ${{ matrix.qt_version }}
host: "windows"
target: "desktop"
arch: ${{ matrix.qt_arch }}
modules: ${{ matrix.qt_modules }}
cache: ${{ inputs.is_qt_cached }}
cache-key-prefix: host-qt-arm64-windows
dir: ${{ github.workspace }}\HostQt
set-env: false
aqtversion: '==3.1.*'
py7zrversion: '>=0.20.2'
version: ${{ matrix.qt_version }}
host: 'windows'
target: 'desktop'
arch: ''
modules: ${{ matrix.qt_modules }}
tools: ${{ matrix.qt_tools }}
cache: ${{ inputs.is_qt_cached }}
cache-key-prefix: host-qt-arm64-windows
dir: ${{ github.workspace }}\HostQt
set-env: false
- name: Install Qt (macOS, Linux & Windows MSVC)
if: matrix.msystem == ''
uses: jurplel/install-qt-action@v4
- name: Install Qt (macOS, Linux, Qt 6 & Windows MSVC)
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '')
uses: jurplel/install-qt-action@v3
with:
aqtversion: "==3.1.*"
py7zrversion: ">=0.20.2"
version: ${{ matrix.qt_version }}
target: "desktop"
arch: ${{ matrix.qt_arch }}
modules: ${{ matrix.qt_modules }}
tools: ${{ matrix.qt_tools }}
cache: ${{ inputs.is_qt_cached }}
aqtversion: '==3.1.*'
py7zrversion: '>=0.20.2'
version: ${{ matrix.qt_version }}
host: ${{ matrix.qt_host }}
target: 'desktop'
arch: ${{ matrix.qt_arch }}
modules: ${{ matrix.qt_modules }}
tools: ${{ matrix.qt_tools }}
cache: ${{ inputs.is_qt_cached }}
- name: Install MSVC (Windows MSVC)
if: runner.os == 'Windows' # We want this for MinGW builds as well, as we need SignTool
if: runner.os == 'Windows' # We want this for MinGW builds as well, as we need SignTool
uses: ilammy/msvc-dev-cmd@v1
with:
vsversion: 2022
@ -254,21 +249,14 @@ jobs:
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/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage"
sudo apt install libopengl0 libfuse2
${{ github.workspace }}/.github/scripts/prepare_JREs.sh
sudo apt install libopengl0
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
run: |
echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2022_64" >> $env:GITHUB_ENV
echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2019_64" >> $env:GITHUB_ENV
- name: Setup java (macOS)
if: runner.os == 'macOS'
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "17"
##
# CONFIGURE
##
@ -276,23 +264,23 @@ jobs:
- name: Configure CMake (macOS)
if: runner.os == 'macOS' && matrix.qt_ver == 6
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja
- name: Configure CMake (macOS-Legacy)
if: runner.os == 'macOS' && matrix.qt_ver == 5
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -DCMAKE_OSX_ARCHITECTURES="x86_64" -G Ninja
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja
- name: Configure CMake (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0}
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -G Ninja
- name: Configure CMake (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == ''
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }}
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
if ("${{ env.CCACHE_VAR }}")
{
@ -307,7 +295,7 @@ jobs:
- name: Configure CMake (Linux)
if: runner.os == 'Linux'
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -G Ninja
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja
##
# BUILD
@ -347,26 +335,12 @@ jobs:
- name: Test (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
run: |
ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
##
# PACKAGE BUILDS
##
- name: Fetch codesign certificate (macOS)
if: runner.os == 'macOS'
run: |
echo '${{ secrets.APPLE_CODESIGN_CERT }}' | base64 --decode > codesign.p12
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
security create-keychain -p '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain
security import codesign.p12 -k build.keychain -P '${{ secrets.APPLE_CODESIGN_PASSWORD }}' -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain
else
echo ":warning: Using ad-hoc code signing for macOS, as certificate was not present." >> $GITHUB_STEP_SUMMARY
fi
- name: Package (macOS)
if: runner.os == 'macOS'
run: |
@ -374,43 +348,17 @@ jobs:
cd ${{ env.INSTALL_DIR }}
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher"
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}'
ENTITLEMENTS_FILE='../program_info/App.entitlements'
else
APPLE_CODESIGN_ID='-'
ENTITLEMENTS_FILE='../program_info/AdhocSignedApp.entitlements'
fi
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "$ENTITLEMENTS_FILE" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
mv "PrismLauncher.app" "Prism Launcher.app"
- name: Notarize (macOS)
if: runner.os == 'macOS'
run: |
cd ${{ env.INSTALL_DIR }}
if [ -n '${{ secrets.APPLE_NOTARIZE_PASSWORD }}' ]; then
ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip
xcrun notarytool submit ../PrismLauncher.zip \
--wait --progress \
--apple-id '${{ secrets.APPLE_NOTARIZE_APPLE_ID }}' \
--team-id '${{ secrets.APPLE_NOTARIZE_TEAM_ID }}' \
--password '${{ secrets.APPLE_NOTARIZE_PASSWORD }}'
xcrun stapler staple "Prism Launcher.app"
else
echo ":warning: Skipping notarization as credentials are not present." >> $GITHUB_STEP_SUMMARY
fi
ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip
tar -czf ../PrismLauncher.tar.gz *
- name: Make Sparkle signature (macOS)
if: matrix.name == 'macOS'
run: |
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
brew install openssl@3
echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem
signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.tar.gz -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
rm ed25519-priv.pem
cat >> $GITHUB_STEP_SUMMARY << EOF
### Artifact Information :information_source:
@ -429,20 +377,27 @@ jobs:
run: |
cmake --install ${{ env.BUILD_DIR }}
touch ${{ env.INSTALL_DIR }}/manifest.txt
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt
- name: Package (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == ''
run: |
cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
cd ${{ env.INSTALL_DIR }}
if ("${{ matrix.qt_ver }}" -eq "5")
{
Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll
Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll
}
cd ${{ github.workspace }}
Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
- name: Fetch codesign certificate (Windows)
if: runner.os == 'Windows'
shell: bash # yes, we are not using MSYS2 or PowerShell here
shell: bash # yes, we are not using MSYS2 or PowerShell here
run: |
echo '${{ secrets.WINDOWS_CODESIGN_CERT }}' | base64 --decode > codesign.pfx
@ -452,7 +407,7 @@ jobs:
if (Get-Content ./codesign.pfx){
cd ${{ env.INSTALL_DIR }}
# We ship the exact same executable for portable and non-portable editions, so signing just once is fine
SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_updater.exe prismlauncher_filelink.exe
SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_filelink.exe
} else {
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
}
@ -470,22 +425,12 @@ jobs:
run: |
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
- name: Package (Windows, installer)
if: runner.os == 'Windows'
run: |
if ('${{ matrix.nscurl_tag }}') {
New-Item -Name NSISPlugins -ItemType Directory
Invoke-Webrequest https://github.com/negrutiu/nsis-nscurl/releases/download/${{ matrix.nscurl_tag }}/NScurl.zip -OutFile NSISPlugins\NScurl.zip
$nscurl_hash = Get-FileHash NSISPlugins\NScurl.zip -Algorithm Sha256 | Select-Object -ExpandProperty Hash
if ( $nscurl_hash -ne "${{ matrix.nscurl_sha256 }}") {
echo "::error:: NSCurl.zip sha256 mismatch"
exit 1
}
Expand-Archive -Path NSISPlugins\NScurl.zip -DestinationPath NSISPlugins\NScurl
}
cd ${{ env.INSTALL_DIR }}
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
@ -498,133 +443,146 @@ jobs:
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
}
- name: Package AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
shell: bash
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
- name: Package (Linux)
if: runner.os == 'Linux'
run: |
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_DIR }}/manifest.txt
mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml
export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated
export OUTPUT="PrismLauncher-Linux-x86_64.AppImage"
chmod +x linuxdeploy-*.AppImage
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib
mkdir -p ${{ 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.* ${{ 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/
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
export LD_LIBRARY_PATH
chmod +x AppImageUpdate-x86_64.AppImage
cp AppImageUpdate-x86_64.AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync"
if [ '${{ secrets.GPG_PRIVATE_KEY_ID }}' != '' ]; then
export SIGN=1
export SIGN_KEY=${{ secrets.GPG_PRIVATE_KEY_ID }}
mkdir -p ~/.gnupg/
echo "$GPG_PRIVATE_KEY" > ~/.gnupg/private.key
gpg --import ~/.gnupg/private.key
else
echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY
fi
./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg
mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
cd ${{ env.INSTALL_DIR }}
tar --owner root --group root -czf ../PrismLauncher.tar.gz *
- name: Package (Linux, portable)
if: runner.os == 'Linux'
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_PORTABLE_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -DINSTALL_BUNDLE=full -G Ninja
cmake --install ${{ env.BUILD_DIR }}
cmake --install ${{ env.BUILD_DIR }} --component portable
mkdir ${{ 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/libcrypto.so.* ${{ 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.*.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ env.INSTALL_PORTABLE_DIR }}/lib
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }}
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
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
cd ${{ env.INSTALL_PORTABLE_DIR }}
tar -czf ../PrismLauncher-portable.tar.gz *
- name: Package AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
shell: bash
run: |
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml
export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated
export OUTPUT="PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
chmod +x linuxdeploy-*.AppImage
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-{8,17}-openjdk
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
cp -r ${{ github.workspace }}/JREs/jre8/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk
cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk
cp -r /home/runner/work/PrismLauncher/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/libssl.so.1.1 ${{ 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/jvm/java-8-openjdk/lib/amd64/server"
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64"
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib/server"
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib"
export LD_LIBRARY_PATH
./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg
##
# UPLOAD BUILDS
##
- name: Upload binary tarball (macOS)
if: runner.os == 'macOS'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher.zip
path: PrismLauncher.tar.gz
- name: Upload binary zip (Windows)
if: runner.os == 'Windows'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: ${{ env.INSTALL_DIR }}/**
- name: Upload binary zip (Windows, portable)
if: runner.os == 'Windows'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: ${{ env.INSTALL_PORTABLE_DIR }}/**
- name: Upload installer (Windows)
if: runner.os == 'Windows'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-Setup.exe
- name: Upload binary tarball (Linux, Qt 5)
if: runner.os == 'Linux' && matrix.qt_ver != 6
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher.tar.gz
- name: Upload binary tarball (Linux, portable, Qt 5)
if: runner.os == 'Linux' && matrix.qt_ver != 6
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
name: PrismLauncher-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-portable.tar.gz
- name: Upload binary tarball (Linux, Qt 6)
if: runner.os == 'Linux' && matrix.qt_ver !=5
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ runner.os }}-Qt6-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher.tar.gz
- name: Upload binary tarball (Linux, portable, Qt 6)
if: runner.os == 'Linux' && matrix.qt_ver != 5
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-portable.tar.gz
- name: Upload AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
- name: Upload AppImage Zsync (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
uses: actions/upload-artifact@v4
with:
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage.zsync
path: PrismLauncher-Linux-x86_64.AppImage.zsync
- name: ccache stats (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0}
run: |
ccache -s
flatpak:
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:kde-5.15-22.08
options: --privileged
steps:
- name: Checkout
uses: actions/checkout@v3
if: inputs.build_type == 'Debug'
with:
submodules: 'true'
- name: Build Flatpak (Linux)
if: inputs.build_type == 'Debug'
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: "Prism Launcher.flatpak"
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml

View file

@ -8,12 +8,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
submodules: 'true'
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v2
with:
config-file: ./.github/codeql/codeql-config.yml
queries: security-and-quality
@ -23,7 +23,7 @@ jobs:
run:
sudo apt-get -y update
sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 libqt5networkauth5 libqt5networkauth5-dev
sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
- name: Configure and Build
run: |
@ -32,4 +32,4 @@ jobs:
cmake --build build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v2

View file

@ -1,62 +0,0 @@
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: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-Flatpak.flatpak
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
arch: ${{ matrix.arch }}

View file

@ -1,90 +0,0 @@
name: Nix
on:
push:
paths-ignore:
- "**.md"
- "**/LICENSE"
- ".github/ISSUE_TEMPLATE/**"
- ".markdownlint**"
- "flatpak/**"
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: prismlauncher
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 \
--no-link --print-build-logs --print-out-paths \
.#prismlauncher-debug >> "$GITHUB_STEP_SUMMARY"
- name: Build release package
if: ${{ !env.DEBUG }}
run: |
nix build \
--no-link --print-build-logs --print-out-paths \
.#prismlauncher >> "$GITHUB_STEP_SUMMARY"

View file

@ -1,45 +0,0 @@
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

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

View file

@ -3,25 +3,26 @@ name: Build Application
on:
push:
branches-ignore:
- "renovate/**"
- 'renovate/**'
paths-ignore:
- "**.md"
- "**/LICENSE"
- "flake.lock"
- "packages/**"
- ".github/ISSUE_TEMPLATE/**"
- ".markdownlint**"
- '**.md'
- '**/LICENSE'
- 'flake.lock'
- 'packages/**'
- '.github/ISSUE_TEMPLATE/**'
- '.markdownlint**'
pull_request:
paths-ignore:
- "**.md"
- "**/LICENSE"
- "flake.lock"
- "packages/**"
- ".github/ISSUE_TEMPLATE/**"
- ".markdownlint**"
- '**.md'
- '**/LICENSE'
- 'flake.lock'
- 'packages/**'
- '.github/ISSUE_TEMPLATE/**'
- '.markdownlint**'
workflow_dispatch:
jobs:
build_debug:
name: Build Debug
uses: ./.github/workflows/build.yml
@ -32,11 +33,4 @@ jobs:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }}
APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }}
APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }}
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}

View file

@ -3,9 +3,10 @@ name: Build Application and Make Release
on:
push:
tags:
- "*"
- '*'
jobs:
build_release:
name: Build Release
uses: ./.github/workflows/build.yml
@ -16,14 +17,7 @@ jobs:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }}
APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }}
APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }}
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
create_release:
needs: build_release
@ -32,12 +26,12 @@ jobs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
submodules: "true"
path: "PrismLauncher-source"
submodules: 'true'
path: 'PrismLauncher-source'
- name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
- name: Grab and store version
run: |
tag_name=$(echo ${{ github.ref }} | grep -oE "[^/]+$")
@ -46,11 +40,12 @@ jobs:
run: |
mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }}
mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
mv PrismLauncher-macOS-Legacy*/PrismLauncher.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip
mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
mv PrismLauncher-Linux-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
mv PrismLauncher-Linux*/PrismLauncher.tar.gz PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage
mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
@ -82,27 +77,32 @@ jobs:
- name: Create release
id: create_release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ github.ref }}
name: Prism Launcher ${{ env.VERSION }}
draft: true
prerelease: false
files: |
PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-x86_64.AppImage
PrismLauncher-Linux-x86_64.AppImage.zsync
PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage
PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe
PrismLauncher-Windows-MSVC-Legacy-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Legacy-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Legacy-Setup-${{ env.VERSION }}.exe
PrismLauncher-Windows-MSVC-arm64-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-arm64-Setup-${{ env.VERSION }}.exe
PrismLauncher-Windows-MSVC-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe
PrismLauncher-macOS-${{ env.VERSION }}.zip
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
PrismLauncher-${{ env.VERSION }}.tar.gz

View file

@ -16,15 +16,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@08dcb3a5e62fa31e2da3d490afc4176ef55ecd72 # v30
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22
- uses: DeterminateSystems/update-flake-lock@v24
- uses: DeterminateSystems/update-flake-lock@v19
with:
commit-msg: "chore(nix): update lockfile"
pr-title: "chore(nix): update lockfile"
pr-labels: |
Linux
packaging
simple change
changelog:omit

15
.github/workflows/winget.yml vendored Normal file
View file

@ -0,0 +1,15 @@
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 }}

3
BUILD.md Normal file
View file

@ -0,0 +1,3 @@
# Build Instructions
Full build instructions are available on [the website](https://prismlauncher.org/wiki/development/build-instructions/).

View file

@ -33,13 +33,6 @@ if(MSVC)
# Use /W4 as /Wall includes unnesserey warnings such as added padding to structs
set(CMAKE_CXX_FLAGS "/GS /permissive- /W4 ${CMAKE_CXX_FLAGS}")
# /EHs Enables stack unwind semantics for standard C++ exceptions to ensure stackframes are unwound
# and object deconstructors are called when an exception is caught.
# without it memory leaks and a warning is printed
# /EHc tells the compiler to assume that functions declared as extern "C" never throw a C++ exception
# This appears to not always be a defualt compiler option in CMAKE
set(CMAKE_CXX_FLAGS "/EHsc ${CMAKE_CXX_FLAGS}")
# LINK accepts /SUBSYSTEM whics sets if we are a WINDOWS (gui) or a CONSOLE programs
# This implicitly selects an entrypoint specific to the subsystem selected
# qtmain/QtEntryPointLib provides the correct entrypoint (wWinMain) for gui programs
@ -78,13 +71,6 @@ else()
# ATL's pack list needs more than the default 1 Mib stack on windows
if(WIN32)
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()
@ -99,39 +85,38 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0")
# set CXXFLAGS for build targets
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
option(DEBUG_ADDRESS_SANITIZER "Enable Address Sanitizer in Debug builds" OFF)
option(DEBUG_ADDRESS_SANITIZER "Enable Address Sanitizer in Debug builds" on)
# If this is a Debug build turn on address sanitiser
if ((CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") AND DEBUG_ADDRESS_SANITIZER)
if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND DEBUG_ADDRESS_SANITIZER)
message(STATUS "Address Sanitizer enabled for Debug builds, Turn it off with -DDEBUG_ADDRESS_SANITIZER=off")
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
# using clang with clang-cl front end
# using clang with clang-cl front end
message(STATUS "Address Sanitizer available on Clang MSVC frontend")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /Oy-")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /Oy-")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /O1 /Oy-")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /O1 /Oy-")
else()
# AppleClang and Clang
message(STATUS "Address Sanitizer available on Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=null")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=null")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -O1 -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -O1 -fno-omit-frame-pointer")
endif()
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
# GCC
message(STATUS "Address Sanitizer available on GCC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -O1 -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -O1 -fno-omit-frame-pointer")
link_libraries("asan")
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
message(STATUS "Address Sanitizer available on MSVC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /Oy-")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /Oy-")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /O1 /Oy-")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /O1 /Oy-")
else()
message(STATUS "Address Sanitizer not available on compiler ${CMAKE_CXX_COMPILER_ID}")
endif()
endif()
option(ENABLE_LTO "Enable Link Time Optimization" off)
if(ENABLE_LTO)
@ -183,12 +168,10 @@ endif()
set(Launcher_NEWS_RSS_URL "https://prismlauncher.org/feed/feed.xml" CACHE STRING "URL to fetch Prism Launcher's news RSS feed from.")
set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL that gets opened when the user clicks 'More News'")
set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
set(Launcher_LOGIN_CALLBACK_URL "https://prismlauncher.org/successful-login" CACHE STRING "URL that gets opened when the user successfully logins.")
set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for FML Libraries.")
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 10)
set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_MAJOR 7)
set(Launcher_VERSION_MINOR 2)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0")
@ -197,11 +180,8 @@ set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_M
# Build platform.
set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
# Github repo URL with releases for updater
set(Launcher_UPDATER_GITHUB_REPO "https://github.com/PrismLauncher/PrismLauncher" CACHE STRING "Base github URL for the updater.")
# Name to help updater identify valid artifacts
set(Launcher_BUILD_ARTIFACT "" CACHE STRING "Artifact name to help the updater identify valid artifacts.")
# Channel list URL
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
# The metadata server
set(Launcher_META_URL "https://meta.prismlauncher.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.")
@ -214,7 +194,6 @@ set(Launcher_BUG_TRACKER_URL "https://github.com/PrismLauncher/PrismLauncher/iss
# Translations Platform URL
set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/prismlauncher/launcher/" CACHE STRING "URL for the translations platform.")
set(Launcher_TRANSLATION_FILES_URL "https://i18n.prismlauncher.org/" CACHE STRING "URL for the translations files.")
# Matrix Space
set(Launcher_MATRIX_URL "https://prismlauncher.org/matrix" CACHE STRING "URL to the Matrix Space")
@ -229,31 +208,6 @@ set(Launcher_SUBREDDIT_URL "https://prismlauncher.org/reddit" CACHE STRING "URL
set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against")
# Java downloader
set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT ON)
# Although we recommend enabling this, we cannot guarantee binary compatibility on
# differing Linux/BSD/etc distributions. Downstream packagers should be explicitly opt-ing into this
# feature if they know it will work with their distribution.
if(UNIX AND NOT APPLE)
set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT OFF)
endif()
# Java downloader
option(Launcher_ENABLE_JAVA_DOWNLOADER "Build the java downloader feature" ${Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT})
# Native libraries
if(UNIX AND APPLE)
set(Launcher_GLFW_LIBRARY_NAME "libglfw.dylib" CACHE STRING "Name of native glfw library")
set(Launcher_OPENAL_LIBRARY_NAME "libopenal.dylib" CACHE STRING "Name of native openal library")
elseif(UNIX)
set(Launcher_GLFW_LIBRARY_NAME "libglfw.so" CACHE STRING "Name of native glfw library")
set(Launcher_OPENAL_LIBRARY_NAME "libopenal.so" CACHE STRING "Name of native openal library")
elseif(WIN32)
set(Launcher_GLFW_LIBRARY_NAME "glfw.dll" CACHE STRING "Name of native glfw library")
set(Launcher_OPENAL_LIBRARY_NAME "OpenAL.dll" CACHE STRING "Name of native openal library")
endif()
# API Keys
# NOTE: These API keys are here for convenience. If you rebrand this software or intend to break the terms of service
# of these platforms, please change these API keys beforehand.
@ -271,11 +225,6 @@ set(Launcher_MSA_CLIENT_ID "c36a9fb6-4f2a-41ff-90bd-ae7cc92031eb" CACHE STRING "
# This key was issued specifically for Prism Launcher
set(Launcher_CURSEFORGE_API_KEY "$2a$10$wuAJuNZuted3NORVmpgUC.m8sI.pv1tOPKZyBgLFGjxFp/br0lZCC" CACHE STRING "API key for the CurseForge platform")
set(Launcher_COMPILER_NAME ${CMAKE_CXX_COMPILER_ID})
set(Launcher_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION})
set(Launcher_COMPILER_TARGET_SYSTEM ${CMAKE_SYSTEM_NAME})
set(Launcher_COMPILER_TARGET_SYSTEM_VERSION ${CMAKE_SYSTEM_VERSION})
set(Launcher_COMPILER_TARGET_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR})
#### Check the current Git commit and branch
include(GetGitRevisionDescription)
@ -305,9 +254,7 @@ endif()
include(QtVersionlessBackport)
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
set(QT_VERSION_MAJOR 5)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth)
find_package(Qt5 COMPONENTS DBus)
list(APPEND Launcher_QT_DBUS Qt5::DBus)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml)
if(NOT Launcher_FORCE_BUNDLED_LIBS)
find_package(QuaZip-Qt5 1.3 QUIET)
@ -321,9 +268,7 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE")
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
set(QT_VERSION_MAJOR 6)
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth)
find_package(Qt6 COMPONENTS DBus)
list(APPEND Launcher_QT_DBUS Qt6::DBus)
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat)
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
if(NOT Launcher_FORCE_BUNDLED_LIBS)
@ -373,13 +318,6 @@ add_subdirectory(program_info)
####################################### Install layout #######################################
set(Launcher_ENABLE_UPDATER NO)
set(Launcher_BUILD_UPDATER NO)
if (NOT APPLE AND (NOT Launcher_UPDATER_GITHUB_REPO STREQUAL "" AND NOT Launcher_BUILD_ARTIFACT STREQUAL ""))
set(Launcher_BUILD_UPDATER YES)
endif()
if(NOT (UNIX AND APPLE))
# Install "portable.txt" if selected component is "portable"
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_Portable_File}" DESTINATION "." COMPONENT portable EXCLUDE_FROM_ALL)
@ -404,23 +342,19 @@ if(UNIX AND APPLE)
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
set(MACOSX_BUNDLE_COPYRIGHT "${Launcher_Copyright_Mac}")
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed")
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed")
set(MACOSX_BUNDLE_COPYRIGHT "© 2022 ${Launcher_Copyright_Mac}")
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=")
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml")
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 "50612a06038abc931f16011d7903b8326a362c1074dabccb718404ce8e585f0b" CACHE STRING "SHA256 checksum for Sparkle release archive")
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.1.0/Sparkle-2.1.0.tar.xz" CACHE STRING "URL to Sparkle release archive")
set(MACOSX_SPARKLE_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive")
set(MACOSX_SPARKLE_DIR "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ${MACOSX_SPARKLE_DIR})
if(NOT MACOSX_SPARKLE_UPDATE_PUBLIC_KEY STREQUAL "" AND NOT MACOSX_SPARKLE_UPDATE_FEED_URL STREQUAL "")
set(Launcher_ENABLE_UPDATER YES)
endif()
# install as bundle
set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies")
set(INSTALL_BUNDLE "full")
# Add the icon
install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns)
@ -433,7 +367,7 @@ elseif(UNIX)
set(JARS_DEST_DIR "share/${Launcher_Name}")
# install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps" CACHE STRING "Use fixup_bundle to bundle dependencies")
set(INSTALL_BUNDLE "nodeps")
# Set RPATH
SET(Launcher_BINARY_RPATH "$ORIGIN/")
@ -444,19 +378,7 @@ elseif(UNIX)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR})
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "share/${Launcher_Name}")
if (INSTALL_BUNDLE STREQUAL full)
set(PLUGIN_DEST_DIR "plugins")
set(BUNDLE_DEST_DIR ".")
set(RESOURCES_DEST_DIR ".")
# Apps to bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${Launcher_APP_BINARY_NAME}")
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
endif()
if(Launcher_ManPage)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6")
endif()
@ -479,7 +401,7 @@ elseif(WIN32)
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
# install as bundle
set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies")
set(INSTALL_BUNDLE "full")
else()
message(FATAL_ERROR "Platform not supported")
endif()
@ -506,7 +428,7 @@ if(FORCE_BUNDLED_ZLIB)
set(SKIP_INSTALL_ALL ON)
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL)
# On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not.
# On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not.
# We cannot safely undo the rename on those systems, and they generally have packages for zlib anyway.
check_include_file(unistd.h NEED_GENERATED_ZCONF)
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" AND NOT NEED_GENERATED_ZCONF)
@ -543,15 +465,15 @@ else()
endif()
if(NOT cmark_FOUND)
message(STATUS "Using bundled cmark")
set(ORIGINAL_BUILD_TESTING ${BUILD_TESTING})
set(BUILD_TESTING 0)
set(BUILD_SHARED_LIBS 0)
set(CMARK_STATIC ON CACHE BOOL "Build static libcmark library" FORCE)
set(CMARK_SHARED OFF CACHE BOOL "Build shared libcmark library" FORCE)
set(CMARK_TESTS OFF CACHE BOOL "Build cmark tests and enable testing" FORCE)
add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser
add_library(cmark::cmark ALIAS cmark)
set(BUILD_TESTING ${ORIGINAL_BUILD_TESTING})
add_library(cmark::cmark ALIAS cmark_static)
else()
message(STATUS "Using system cmark")
endif()
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
add_subdirectory(libraries/gamemode)
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
if (NOT ghc_filesystem_FOUND)

View file

@ -2,59 +2,16 @@
## Code formatting
All files are formatted with `clang-format` using the configuration in `.clang-format`. Ensure it is run on changed files before committing!
Try to follow the existing formatting.
If there is no existing formatting, you may use `clang-format` with our included `.clang-format` configuration.
Please also follow the project's conventions for C++:
In general, in order of importance:
- Class and type names should be formatted as `PascalCase`: `MyClass`.
- Private or protected class data members should be formatted as `camelCase` prefixed with `m_`: `m_myCounter`.
- Private or protected `static` class data members should be formatted as `camelCase` prefixed with `s_`: `s_instance`.
- Public class data members should be formatted as `camelCase` without the prefix: `dateOfBirth`.
- Public, private or protected `static const` class data members should be formatted as `SCREAMING_SNAKE_CASE`: `MAX_VALUE`.
- Class function members should be formatted as `camelCase` without a prefix: `incrementCounter`.
- Global functions and non-`const` global variables should be formatted as `camelCase` without a prefix: `globalData`.
- `const` global variables, macros, and enum constants should be formatted as `SCREAMING_SNAKE_CASE`: `LIGHT_GRAY`.
- Avoid inventing acronyms or abbreviations especially for a name of multiple words - like `tp` for `texturePack`.
Most of these rules are included in the `.clang-tidy` file, so you can run `clang-tidy` to check for any violations.
Here is what these conventions with the formatting configuration look like:
```c++
#define AWESOMENESS 10
constexpr double PI = 3.14159;
enum class PizzaToppings { HAM_AND_PINEAPPLE, OREO_AND_KETCHUP };
struct Person {
QString name;
QDateTime dateOfBirth;
long daysOld() const { return dateOfBirth.daysTo(QDateTime::currentDateTime()); }
};
class ImportantClass {
public:
void incrementCounter()
{
if (m_counter + 1 > MAX_COUNTER_VALUE)
throw std::runtime_error("Counter has reached limit!");
++m_counter;
}
int counter() const { return m_counter; }
private:
static constexpr int MAX_COUNTER_VALUE = 100;
int m_counter;
};
ImportantClass importantClassInstance;
```
If you see any names which do not follow these conventions, it is preferred that you leave them be - renames increase the number of changes therefore make reviewing harder and make your PR more prone to conflicts. However, if you're refactoring a whole class anyway, it's fine.
- Make sure your IDE is not messing up line endings or whitespace and avoid using linters.
- Prefer readability over dogma.
- Keep to the existing formatting.
- Indent with 4 space unless it's in a submodule.
- Keep lists (of arguments, parameters, initializers...) as lists, not paragraphs. It should either read from top to bottom, or left to right. Not both.
## Signing your work
@ -62,7 +19,7 @@ In an effort to ensure that the code you contribute is actually compatible with
This can be done by appending `-s` to your `git commit` call, or by manually appending the following text to your commit message:
```text
```
<commit message>
Signed-off-by: Author name <Author email>
@ -70,7 +27,7 @@ Signed-off-by: Author name <Author email>
By signing off your work, you agree to the terms below:
```text
```
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
@ -104,9 +61,3 @@ As a bonus, you can also [cryptographically sign your commits][gh-signing-commit
[gh-signing-commits]: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits
[gh-vigilant-mode]: https://docs.github.com/en/authentication/managing-commit-signature-verification/displaying-verification-statuses-for-all-of-your-commits
## Backporting to Release Branches
We use [automated backports](https://github.com/PrismLauncher/PrismLauncher/blob/develop/.github/workflows/backport.yml) to merge specific contributions from develop into `release` branches.
This is done when pull requests are merged and have labels such as `backport release-7.x` - which should be added along with the milestone for the release.

View file

@ -1,7 +1,7 @@
## Prism Launcher
Prism Launcher - Minecraft Launcher
Copyright (C) 2022-2024 Prism Launcher Contributors
Copyright (C) 2022-2023 Prism Launcher Contributors
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
@ -333,6 +333,32 @@
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## O2 (Katabasis fork)
Copyright (c) 2012, Akos Polster
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## Gamemode
Copyright (c) 2017-2022, Feral Interactive
@ -410,7 +436,7 @@
Copyright (C) 2007 Johann Ollivier Lapeyre <johann@oxygen-icons.org>
Copyright (C) 2007 Kenneth Wimer <kwwii@bootsplash.org>
Copyright (C) 2007 Riccardo Iaconelli <riccardo@oxygen-icons.org>
and others
This library is free software; you can redistribute it and/or

View file

@ -1,5 +1,3 @@
<p align="center"><b><i>This is a fork of PrismLauncher, unlocked to be able to use offline accounts without owning the game.</b></i></p>
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg">
@ -20,18 +18,11 @@
</a>
- All downloads and instructions for Prism Launcher can be found on our [Website](https://prismlauncher.org/download).
- Last build status can be found in the [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions) tab (this also includes the pull requests status).
- Last build status can be found in the [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions).
### Development Builds
Please understand that these builds are not intended for most users. There may be bugs, and other instabilities. You have been warned.
There are development builds available through:
- [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions) (includes builds from pull requests opened by contribuitors)
- [nightly.link](https://nightly.link/PrismLauncher/PrismLauncher/workflows/trigger_builds/develop) (this will always point only to the latest version of develop)
These have debug information in the binaries, so their file sizes are relatively larger.
There are development builds available [here](https://github.com/PrismLauncher/PrismLauncher/actions). These have debug information in the binaries, so their file sizes are relatively larger.
Prebuilt Development builds are provided for **Linux**, **Windows** and **macOS**.
@ -39,7 +30,7 @@ For **Arch**, **Debian**, **Fedora**, **OpenSUSE (Tumbleweed)** and **Gentoo**,
[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-1793D1?label=AUR&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-git) [![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--qt5--git-1793D1?label=AUR&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-qt5-git) [![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-A80030?label=MPR&logo=debian&logoColor=white)](https://mpr.makedeb.org/packages/prismlauncher-git)<br />[![prismlauncher-nightly](https://img.shields.io/badge/copr-prismlauncher--nightly-51A2DA?label=COPR&logo=fedora&logoColor=white)](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [![prismlauncher-nightly](https://img.shields.io/badge/OBS-prismlauncher--nightly-3AB6A9?logo=opensuse&logoColor=white)](https://build.opensuse.org/project/show/home:getchoo) [![prismlauncher-9999](https://img.shields.io/badge/gentoo-prismlauncher--9999-4D4270?label=Gentoo&logo=gentoo&logoColor=white)](https://packages.gentoo.org/packages/games-action/prismlauncher)
These packages are also available to all the distributions based on the ones mentioned above.
These packages are also availiable to all the distributions based on the ones mentioned above.
## Community & Support
@ -59,16 +50,11 @@ Feel free to create a GitHub issue if you find a bug or want to suggest a new fe
## Translations
The translation effort for Prism Launcher is hosted on [Weblate](https://hosted.weblate.org/projects/prismlauncher/launcher/) and information about translating Prism Launcher is available at <https://github.com/PrismLauncher/Translations>.
The translation effort for Prism Launcher is hosted on [Weblate](https://hosted.weblate.org/projects/prismlauncher/launcher/) and information about translating Prism Launcher is available at <https://github.com/PrismLauncher/Translations>
## Building
If you want to build Prism Launcher yourself, check the build instructions:
- [Windows](https://prismlauncher.org/wiki/development/build-instructions/windows/)
- [Linux](https://prismlauncher.org/wiki/development/build-instructions/linux/)
- [MacOS](https://prismlauncher.org/wiki/development/build-instructions/macos/)
- [OpenBSD](https://prismlauncher.org/wiki/development/build-instructions/openbsd/)
If you want to build Prism Launcher yourself, check the [Build Instructions](https://prismlauncher.org/wiki/development/build-instructions/).
## Sponsors & Partners

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -33,7 +33,6 @@
* limitations under the License.
*/
#include <qstringliteral.h>
#include "BuildConfig.h"
#include <QObject>
@ -60,16 +59,8 @@ Config::Config()
VERSION_MINOR = @Launcher_VERSION_MINOR@;
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
BUILD_ARTIFACT = "@Launcher_BUILD_ARTIFACT@";
BUILD_DATE = "@Launcher_BUILD_TIMESTAMP@";
UPDATER_GITHUB_REPO = "@Launcher_UPDATER_GITHUB_REPO@";
COMPILER_NAME = "@Launcher_COMPILER_NAME@";
COMPILER_VERSION = "@Launcher_COMPILER_VERSION@";
COMPILER_TARGET_SYSTEM = "@Launcher_COMPILER_TARGET_SYSTEM@";
COMPILER_TARGET_SYSTEM_VERSION = "@Launcher_COMPILER_TARGET_SYSTEM_VERSION@";
COMPILER_TARGET_SYSTEM_PROCESSOR = "@Launcher_COMPILER_TARGET_PROCESSOR@";
UPDATER_BASE = "@Launcher_UPDATER_BASE@";
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@";
@ -77,13 +68,8 @@ Config::Config()
if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty())
{
UPDATER_ENABLED = true;
} else if(!UPDATER_GITHUB_REPO.isEmpty() && !BUILD_ARTIFACT.isEmpty()) {
UPDATER_ENABLED = true;
}
#cmakedefine01 Launcher_ENABLE_JAVA_DOWNLOADER
JAVA_DOWNLOADER_ENABLED = Launcher_ENABLE_JAVA_DOWNLOADER;
GIT_COMMIT = "@Launcher_GIT_COMMIT@";
GIT_TAG = "@Launcher_GIT_TAG@";
GIT_REFSPEC = "@Launcher_GIT_REFSPEC@";
@ -102,7 +88,10 @@ Config::Config()
if (GIT_REFSPEC.startsWith("refs/heads/"))
{
VERSION_CHANNEL = GIT_REFSPEC;
VERSION_CHANNEL.remove("refs/heads/");
VERSION_CHANNEL.remove("refs/heads/");
if(!UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty()) {
UPDATER_ENABLED = true;
}
}
else if (!GIT_COMMIT.isEmpty())
{
@ -116,19 +105,13 @@ Config::Config()
NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@";
NEWS_OPEN_URL = "@Launcher_NEWS_OPEN_URL@";
HELP_URL = "@Launcher_HELP_URL@";
LOGIN_CALLBACK_URL = "@Launcher_LOGIN_CALLBACK_URL@";
IMGUR_CLIENT_ID = "@Launcher_IMGUR_CLIENT_ID@";
MSA_CLIENT_ID = "@Launcher_MSA_CLIENT_ID@";
FLAME_API_KEY = "@Launcher_CURSEFORGE_API_KEY@";
META_URL = "@Launcher_META_URL@";
FMLLIBS_BASE_URL = "@Launcher_FMLLIBS_BASE_URL@";
GLFW_LIBRARY_NAME = "@Launcher_GLFW_LIBRARY_NAME@";
OPENAL_LIBRARY_NAME = "@Launcher_OPENAL_LIBRARY_NAME@";
BUG_TRACKER_URL = "@Launcher_BUG_TRACKER_URL@";
TRANSLATIONS_URL = "@Launcher_TRANSLATIONS_URL@";
TRANSLATION_FILES_URL = "@Launcher_TRANSLATION_FILES_URL@";
MATRIX_URL = "@Launcher_MATRIX_URL@";
DISCORD_URL = "@Launcher_DISCORD_URL@";
SUBREDDIT_URL = "@Launcher_SUBREDDIT_URL@";
@ -150,16 +133,3 @@ QString Config::printableVersionString() const
}
return vstr;
}
QString Config::compilerID() const
{
if (COMPILER_VERSION.isEmpty())
return COMPILER_NAME;
return QStringLiteral("%1 - %2").arg(COMPILER_NAME).arg(COMPILER_VERSION);
}
QString Config::systemID() const
{
return QStringLiteral("%1 %2 %3").arg(COMPILER_TARGET_SYSTEM, COMPILER_TARGET_SYSTEM_VERSION, COMPILER_TARGET_SYSTEM_PROCESSOR);
}

View file

@ -36,8 +36,8 @@
*/
#pragma once
#include <QList>
#include <QString>
#include <QList>
/**
* \brief The Config class holds all the build-time information passed from the build system.
@ -67,34 +67,15 @@ class Config {
QString VERSION_CHANNEL;
bool UPDATER_ENABLED = false;
bool JAVA_DOWNLOADER_ENABLED = false;
/// A short string identifying this build's platform or distribution.
QString BUILD_PLATFORM;
/// A short string identifying this build's valid artifacts int he updater. For example, "lin64" or "win32".
QString BUILD_ARTIFACT;
/// A string containing the build timestamp
QString BUILD_DATE;
/// A string identifying the compiler use to build
QString COMPILER_NAME;
/// A string identifying the compiler version used to build
QString COMPILER_VERSION;
/// A string identifying the compiler target system os
QString COMPILER_TARGET_SYSTEM;
/// A String identifying the compiler target system version
QString COMPILER_TARGET_SYSTEM_VERSION;
/// A String identifying the compiler target processor
QString COMPILER_TARGET_SYSTEM_PROCESSOR;
/// URL for the updater's channel
QString UPDATER_GITHUB_REPO;
QString UPDATER_BASE;
/// The public key used to sign releases for the Sparkle updater appcast
QString MAC_SPARKLE_PUB_KEY;
@ -133,11 +114,6 @@ class Config {
*/
QString HELP_URL;
/**
* URL that gets opened when the user succesfully logins.
*/
QString LOGIN_CALLBACK_URL;
/**
* Client ID you can get from Imgur when you register an application
*/
@ -158,9 +134,6 @@ class Config {
*/
QString META_URL;
QString GLFW_LIBRARY_NAME;
QString OPENAL_LIBRARY_NAME;
QString BUG_TRACKER_URL;
QString TRANSLATIONS_URL;
QString MATRIX_URL;
@ -169,9 +142,10 @@ class Config {
QString RESOURCE_BASE = "https://resources.download.minecraft.net/";
QString LIBRARY_BASE = "https://libraries.minecraft.net/";
QString AUTH_BASE = "https://authserver.mojang.com/";
QString IMGUR_BASE_URL = "https://api.imgur.com/3/";
QString FMLLIBS_BASE_URL;
QString TRANSLATION_FILES_URL;
QString FMLLIBS_BASE_URL = "https://files.prismlauncher.org/fmllibs/"; // FIXME: move into CMakeLists
QString TRANSLATIONS_BASE_URL = "https://i18n.prismlauncher.org/"; // FIXME: move into CMakeLists
QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/";
@ -188,7 +162,7 @@ class Config {
QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2";
QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2";
QStringList MODRINTH_MRPACK_HOSTS{ "cdn.modrinth.com", "github.com", "raw.githubusercontent.com", "gitlab.com" };
QStringList MODRINTH_MRPACK_HOSTS{"cdn.modrinth.com", "github.com", "raw.githubusercontent.com", "gitlab.com"};
QString FLAME_BASE_URL = "https://api.curseforge.com/v1";
@ -198,18 +172,6 @@ class Config {
* \return The version number in string format (major.minor.revision.build).
*/
QString printableVersionString() const;
/**
* \brief Compiler ID String
* \return a string of the form "Name - Version" of just "Name" if the version is empty
*/
QString compilerID() const;
/**
* \brief System ID String
* \return a string of the form "OS Verison Processor"
*/
QString systemID() const;
};
extern const Config BuildConfig;

View file

@ -1,163 +0,0 @@
#
# Function to set compiler warnings with reasonable defaults at the project level.
# Taken from https://github.com/aminya/project_options/blob/main/src/CompilerWarnings.cmake
# under the folowing license:
#
# MIT License
#
# Copyright (c) 2022-2100 Amin Yahyaabadi
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
include_guard()
function(_set_project_warnings_add_target_link_option TARGET OPTIONS)
target_link_options(${_project_name} INTERFACE ${OPTIONS})
endfunction()
# Set the compiler warnings
#
# https://clang.llvm.org/docs/DiagnosticsReference.html
# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md
function(
set_project_warnings
_project_name
MSVC_WARNINGS
CLANG_WARNINGS
GCC_WARNINGS
)
if("${MSVC_WARNINGS}" STREQUAL "")
set(MSVC_WARNINGS
/W4 # Baseline reasonable warnings
/w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data
/w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
/w14263 # 'function': member function does not override any base class virtual member function
/w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not
# be destructed correctly
/w14287 # 'operator': unsigned/negative constant mismatch
/we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside
# the for-loop scope
/w14296 # 'operator': expression is always 'boolean_value'
/w14311 # 'variable': pointer truncation from 'type1' to 'type2'
/w14545 # expression before comma evaluates to a function which is missing an argument list
/w14546 # function call before comma missing argument list
/w14547 # 'operator': operator before comma has no effect; expected operator with side-effect
/w14549 # 'operator': operator before comma has no effect; did you intend 'operator'?
/w14555 # expression has no effect; expected expression with side- effect
/w14619 # pragma warning: there is no warning number 'number'
/w14640 # Enable warning on thread un-safe static member initialization
/w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior.
/w14905 # wide string literal cast to 'LPSTR'
/w14906 # string literal cast to 'LPWSTR'
/w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied
/permissive- # standards conformance mode for MSVC compiler.
/we4062 # forbid omitting a possible value of an enum in a switch statement
)
endif()
if("${CLANG_WARNINGS}" STREQUAL "")
set(CLANG_WARNINGS
-Wall
-Wextra # reasonable and standard
-Wshadow # warn the user if a variable declaration shadows one from a parent context
-Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps
# catch hard to track down memory errors
-Wold-style-cast # warn for c-style casts
-Wcast-align # warn for potential performance problem casts
-Wunused # warn on anything being unused
-Woverloaded-virtual # warn if you overload (not override) a virtual function
-Wpedantic # warn if non-standard C++ is used
-Wconversion # warn on type conversions that may lose data
-Wsign-conversion # warn on sign conversions
-Wnull-dereference # warn if a null dereference is detected
-Wdouble-promotion # warn if float is implicit promoted to double
-Wformat=2 # warn on security issues around functions that format output (ie printf)
-Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation
# -Wgnu-zero-variadic-macro-arguments (part of -pedantic) is triggered by every qCDebug() call and therefore results
# in a lot of noise. This warning is only notifying us that clang is emulating the GCC behaviour
# instead of the exact standard wording so we can safely ignore it
-Wno-gnu-zero-variadic-macro-arguments
-Werror=switch # forbid omitting a possible value of an enum in a switch statement
)
endif()
if("${GCC_WARNINGS}" STREQUAL "")
set(GCC_WARNINGS
${CLANG_WARNINGS}
-Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist
-Wduplicated-cond # warn if if / else chain has duplicated conditions
-Wduplicated-branches # warn if if / else branches have duplicated code
-Wlogical-op # warn about logical operations being used where bitwise were probably wanted
-Wuseless-cast # warn if you perform a cast to the same type
-Werror=switch # forbid omitting a possible value of an enum in a switch statement
)
endif()
if(MSVC)
set(PROJECT_WARNINGS_CXX ${MSVC_WARNINGS})
elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
set(PROJECT_WARNINGS_CXX ${CLANG_WARNINGS})
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(PROJECT_WARNINGS_CXX ${GCC_WARNINGS})
else()
message(AUTHOR_WARNING "No compiler warnings set for CXX compiler: '${CMAKE_CXX_COMPILER_ID}'")
# TODO support Intel compiler
endif()
# Add C warnings
set(PROJECT_WARNINGS_C "${PROJECT_WARNINGS_CXX}")
list(
REMOVE_ITEM
PROJECT_WARNINGS_C
-Wnon-virtual-dtor
-Wold-style-cast
-Woverloaded-virtual
-Wuseless-cast
-Wextra-semi
-Werror=switch # forbid omitting a possible value of an enum in a switch statement
)
target_compile_options(
${_project_name}
INTERFACE # C++ warnings
$<$<COMPILE_LANGUAGE:CXX>:${PROJECT_WARNINGS_CXX}>
# C warnings
$<$<COMPILE_LANGUAGE:C>:${PROJECT_WARNINGS_C}>
)
# If we are using the compiler as a linker driver pass the warnings to it
# (most useful when using LTO or warnings as errors)
if(CMAKE_CXX_LINK_EXECUTABLE MATCHES "^<CMAKE_CXX_COMPILER>")
_set_project_warnings_add_target_link_option(
${_project_name} "$<$<COMPILE_LANGUAGE:CXX>:${PROJECT_WARNINGS_CXX}>"
)
endif()
if(CMAKE_C_LINK_EXECUTABLE MATCHES "^<CMAKE_C_COMPILER>")
_set_project_warnings_add_target_link_option(
${_project_name} "$<$<COMPILE_LANGUAGE:C>:${PROJECT_WARNINGS_C}>"
)
endif()
endfunction()

View file

@ -6,10 +6,6 @@
<string>A Minecraft mod wants to access your camera.</string>
<key>NSMicrophoneUsageDescription</key>
<string>A Minecraft mod wants to access your microphone.</string>
<key>NSDownloadsFolderUsageDescription</key>
<string>Prism 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 Prism 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>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
@ -71,24 +67,5 @@
<string>Alternate</string>
</dict>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Curseforge</string>
<key>CFBundleURLSchemes</key>
<array>
<string>curseforge</string>
</array>
</dict>
<dict>
<key>CFBundleURLName</key>
<string>Prismlauncher</string>
<key>CFBundleURLSchemes</key>
<array>
<string>prismlauncher</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -1,9 +1,14 @@
(import (
let
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
in
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
(
import
(
let
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
in
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

158
flake.lock generated
View file

@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
@ -16,14 +16,71 @@
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1688466019,
"narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1685518550,
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1660459072,
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"libnbtplusplus": {
"flake": false,
"locked": {
"lastModified": 1699286814,
"narHash": "sha256-yy0q+bky80LtK1GWzz7qpM+aAGrOqLuewbid8WT1ilk=",
"lastModified": 1690036783,
"narHash": "sha256-A5kTgICnx+Qdq3Fir/bKTfdTt/T1NQP2SC+nhN1ENug=",
"owner": "PrismLauncher",
"repo": "libnbtplusplus",
"rev": "23b955121b8217c1c348a9ed2483167a6f3ff4ad",
"rev": "a5e8fd52b8bf4ab5d5bcc042b2a247867589985f",
"type": "github"
},
"original": {
@ -32,43 +89,90 @@
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1731533336,
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "nix-filter",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1739446958,
"narHash": "sha256-+/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc=",
"owner": "NixOS",
"lastModified": 1690026219,
"narHash": "sha256-oOduRk/kzQxOBknZXTLSEYd7tk+GoKvr8wV6Ab+t4AU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "2ff53fe64443980e139eaa286017f53f88336dd0",
"rev": "f465da166263bc0d4b39dfd4ca28b777c92d4b73",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"dir": "lib",
"lastModified": 1688049487,
"narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4bc72cae107788bf3f24f30db2e2f685c9298dc9",
"type": "github"
},
"original": {
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": [
"flake-compat"
],
"flake-utils": "flake-utils",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1689668210,
"narHash": "sha256-XAATwDkaUxH958yXLs1lcEOmU6pSEIkatY3qjqk8X0E=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "eb433bff05b285258be76513add6f6c57b441775",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"libnbtplusplus": "libnbtplusplus",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs"
"nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},

140
flake.nix
View file

@ -1,137 +1,27 @@
{
description = "A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once (Fork of MultiMC)";
nixConfig = {
extra-substituters = [ "https://prismlauncher.cachix.org" ];
extra-trusted-public-keys = [
"prismlauncher.cachix.org-1:9/n/FGyABA2jLUVfY+DEp4hKds/rwO+SCOtbOkDzd+c="
];
};
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
libnbtplusplus = {
url = "github:PrismLauncher/libnbtplusplus";
flake = false;
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
pre-commit-hooks = {
url = "github:cachix/pre-commit-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs";
inputs.nixpkgs-stable.follows = "nixpkgs";
inputs.flake-compat.follows = "flake-compat";
};
nix-filter.url = "github:numtide/nix-filter";
/*
Inputs below this are optional and can be removed
```
{
inputs.prismlauncher = {
url = "github:PrismLauncher/PrismLauncher";
inputs = {
flake-compat.follows = "";
};
};
}
```
*/
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
libnbtplusplus = {
url = "github:PrismLauncher/libnbtplusplus";
flake = false;
};
};
outputs =
{
self,
nixpkgs,
libnbtplusplus,
nix-filter,
...
}:
let
inherit (nixpkgs) lib;
# While we only officially support aarch and x86_64 on Linux and MacOS,
# we expose a reasonable amount of other systems for users who want to
# build for most exotic platforms
systems = lib.systems.flakeExposed;
forAllSystems = lib.genAttrs systems;
nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system});
in
{
checks = forAllSystems (
system:
let
checks' = nixpkgsFor.${system}.callPackage ./nix/checks.nix { inherit self; };
in
lib.filterAttrs (_: lib.isDerivation) checks'
);
devShells = forAllSystems (
system:
let
pkgs = nixpkgsFor.${system};
in
{
default = pkgs.mkShell {
inputsFrom = [ self.packages.${system}.prismlauncher-unwrapped ];
buildInputs = with pkgs; [
ccache
ninja
];
};
}
);
formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style);
overlays.default = final: prev: {
prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
inherit
libnbtplusplus
nix-filter
self
;
};
prismlauncher = final.callPackage ./nix/wrapper.nix { };
};
packages = forAllSystems (
system:
let
pkgs = nixpkgsFor.${system};
# Build a scope from our overlay
prismPackages = lib.makeScope pkgs.newScope (final: self.overlays.default final pkgs);
# Grab our packages from it and set the default
packages = {
inherit (prismPackages) prismlauncher-unwrapped prismlauncher;
default = prismPackages.prismlauncher;
};
in
# Only output them if they're available on the current system
lib.filterAttrs (_: lib.meta.availableOn pkgs.stdenv.hostPlatform) packages
);
# We put these under legacyPackages as they are meant for CI, not end user consumption
legacyPackages = forAllSystems (
system:
let
prismPackages = self.packages.${system};
legacyPackages = self.legacyPackages.${system};
in
{
prismlauncher-debug = prismPackages.prismlauncher.override {
prismlauncher-unwrapped = legacyPackages.prismlauncher-unwrapped-debug;
};
prismlauncher-unwrapped-debug = prismPackages.prismlauncher-unwrapped.overrideAttrs {
cmakeBuildType = "Debug";
dontStrip = true;
};
}
);
};
outputs = inputs:
inputs.flake-parts.lib.mkFlake
{inherit inputs;}
{imports = [./nix];};
}

View file

@ -1,20 +0,0 @@
{
"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

@ -8,7 +8,11 @@
{
"type": "git",
"url": "https://gitlab.freedesktop.org/libdecor/libdecor.git",
"commit": "c2bd8ad6fa42c0cb17553ce77ad8a87d1f543b1f"
"commit": "73260393a97291c887e1074ab7f318e031be0ac6"
},
{
"type": "patch",
"path": "patches/weird_libdecor.patch"
}
],
"cleanup": [

View file

@ -1,9 +1,10 @@
id: org.prismlauncher.PrismLauncher
runtime: org.kde.Platform
runtime-version: '6.8'
runtime-version: "5.15-22.08"
sdk: org.kde.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.openjdk17
- org.freedesktop.Sdk.Extension.openjdk8
command: prismlauncher
finish-args:
@ -17,14 +18,9 @@ finish-args:
- --filesystem=xdg-run/app/com.discordapp.Discord:create
# Mod drag&drop
- --filesystem=xdg-download:ro
# FTBApp import
- --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
cleanup:
- /lib/libGLU*
modules:
# Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31)
@ -33,39 +29,48 @@ modules:
# Needed for proper Wayland support
- libdecor.json
# Text to Speech in the game
- flite.json
- name: prismlauncher
buildsystem: cmake-ninja
builddir: true
config-opts:
- -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
- -DLauncher_QT_VERSION_MAJOR=5
build-options:
env:
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac
run-tests: true
sources:
- type: dir
path: ../
- name: openjdk
buildsystem: simple
build-commands:
- mkdir -p /app/jdk/
- /usr/lib/sdk/openjdk17/install.sh
- mv /app/jre /app/jdk/17
- /usr/lib/sdk/openjdk8/install.sh
- mv /app/jre /app/jdk/8
cleanup:
- /jre
- name: glfw
buildsystem: cmake-ninja
config-opts:
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
- -DBUILD_SHARED_LIBS:BOOL=ON
- -DGLFW_BUILD_WAYLAND:BOOL=ON
- -DGLFW_BUILD_DOCS:BOOL=OFF
- -DGLFW_USE_WAYLAND=ON
sources:
- type: git
url: https://github.com/glfw/glfw.git
commit: 7b6aead9fb88b3623e3b3725ebb42670cbe4c579 # 3.4
commit: 3fa2360720eeba1964df3c0ecf4b5df8648a8e52
- type: patch
path: patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch
path: patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch
- type: patch
path: patches/0005-Add-warning-about-being-an-unofficial-patch.patch
- type: patch
path: patches/0007-Platform-Prefer-Wayland-over-X11.patch
cleanup:
- /include
- /lib/cmake
@ -75,8 +80,8 @@ modules:
buildsystem: autotools
sources:
- type: archive
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.3.tar.xz
sha256: f8dd7566adb74147fab9964680b6bbadee87cf406a7fcff51718a5e6949b841c
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.2.tar.xz
sha256: c8bee4790d9058bacc4b6246456c58021db58a87ddda1a9d0139bf5f18f1f240
x-checker-data:
type: anitya
project-id: 14957
@ -97,9 +102,9 @@ modules:
- install -Dm755 ../data/gamemoderun -t /app/bin
sources:
- type: archive
dest-filename: gamemode.tar.gz
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.2
sha256: 2886d4ce543c78bd2a364316d5e7fd59ef06b71de63f896b37c6d3dc97658f60
archive-type: tar-gzip
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.7
sha256: 57ce73ba605d1cf12f8d13725006a895182308d93eba0f69f285648449641803
x-checker-data:
type: json
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest

View file

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

View file

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

View file

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

View file

@ -1,59 +0,0 @@
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

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

@ -1 +1 @@
Subproject commit f5d368a31d6ef046eb2955c74ec6f54f32ed5c4e
Subproject commit 45094ca570be383d06df729b6972830ec63bd3df

5
garnix.yaml Normal file
View file

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

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Tayou <git@tayou.org>
* Copyright (C) 2022 Tayou <tayou@gmx.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
@ -38,17 +38,16 @@
#pragma once
#include <QApplication>
#include <QDateTime>
#include <memory>
#include <QDebug>
#include <QFlag>
#include <QIcon>
#include <QMutex>
#include <QDateTime>
#include <QUrl>
#include <memory>
#include <BaseInstance.h>
#include "minecraft/launch/MinecraftTarget.h"
#include "minecraft/launch/MinecraftServerTarget.h"
class LaunchController;
class LocalPeer;
@ -71,28 +70,27 @@ class TranslationsModel;
class ITheme;
class MCEditTool;
class ThemeManager;
class IconTheme;
namespace Meta {
class Index;
class Index;
}
#if defined(APPLICATION)
#undef APPLICATION
#endif
#define APPLICATION (static_cast<Application*>(QCoreApplication::instance()))
#define APPLICATION (static_cast<Application *>(QCoreApplication::instance()))
// Used for checking if is a test
#if defined(APPLICATION_DYN)
#undef APPLICATION_DYN
#endif
#define APPLICATION_DYN (dynamic_cast<Application*>(QCoreApplication::instance()))
class Application : public QApplication {
class Application : public QApplication
{
// friends for the purpose of limiting access to deprecated stuff
Q_OBJECT
public:
enum Status { StartingUp, Failed, Succeeded, Initialized };
public:
enum Status {
StartingUp,
Failed,
Succeeded,
Initialized
};
enum Capability {
None = 0,
@ -104,21 +102,33 @@ class Application : public QApplication {
};
Q_DECLARE_FLAGS(Capabilities, Capability)
public:
Application(int& argc, char** argv);
public:
Application(int &argc, char **argv);
virtual ~Application();
bool event(QEvent* event) override;
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 startTime.msecsTo(QDateTime::currentDateTime());
}
QIcon getThemedIcon(const QString& name);
ThemeManager* themeManager() { return m_themeManager.get(); }
void setIconTheme(const QString& name);
shared_qobject_ptr<ExternalUpdater> updater() { return m_updater; }
void applyCurrentlySelectedTheme(bool initial = false);
QList<ITheme*> getValidApplicationThemes();
void setApplicationTheme(const QString& name);
shared_qobject_ptr<ExternalUpdater> updater() {
return m_updater;
}
void triggerUpdateCheck();
@ -126,17 +136,29 @@ class Application : public QApplication {
std::shared_ptr<JavaInstallList> javalist();
std::shared_ptr<InstanceList> instances() const { return m_instances; }
std::shared_ptr<InstanceList> instances() const {
return m_instances;
}
std::shared_ptr<IconList> icons() const { return m_icons; }
std::shared_ptr<IconList> icons() const {
return m_icons;
}
MCEditTool* mcedit() const { return m_mcedit.get(); }
MCEditTool *mcedit() const {
return m_mcedit.get();
}
shared_qobject_ptr<AccountList> accounts() const { return m_accounts; }
shared_qobject_ptr<AccountList> accounts() const {
return m_accounts;
}
Status status() const { return m_status; }
Status status() const {
return m_status;
}
const QMap<QString, std::shared_ptr<BaseProfilerFactory>>& profilers() const { return m_profilers; }
const QMap<QString, std::shared_ptr<BaseProfilerFactory>> &profilers() const {
return m_profilers;
}
void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password);
@ -148,8 +170,6 @@ class Application : public QApplication {
void updateCapabilities();
void detectLibraries();
/*!
* Finds and returns the full path to a jar file.
* Returns a null-string if it could not be found.
@ -163,80 +183,77 @@ class Application : public QApplication {
QString getUserAgentUncached();
/// this is the root of the 'installation'. Used for automatic updates
const QString& root() { return m_rootPath; }
const QString &root() {
return m_rootPath;
}
/// the data path the application is using
const QString& dataRoot() { return m_dataPath; }
bool isPortable() {
return m_portable;
}
/// the java installed path the application is using
const QString javaPath();
bool isPortable() { return m_portable; }
const Capabilities capabilities() { return m_capabilities; }
const Capabilities capabilities() {
return m_capabilities;
}
/*!
* Opens a json file using either a system default editor, or, if not empty, the editor
* specified in the settings
*/
bool openJsonEditor(const QString& filename);
bool openJsonEditor(const QString &filename);
InstanceWindow* showInstanceWindow(InstancePtr instance, QString page = QString());
MainWindow* showMainWindow(bool minimized = false);
InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString());
MainWindow *showMainWindow(bool minimized = false);
void updateIsRunning(bool running);
bool updatesAreAllowed();
void ShowGlobalSettings(class QWidget* parent, QString open_page = QString());
void ShowGlobalSettings(class QWidget * parent, QString open_page = QString());
bool updaterEnabled();
QString updaterBinaryName();
int suitableMaxMem();
QUrl normalizeImportUrl(QString const& url);
signals:
signals:
void updateAllowedChanged(bool status);
void globalSettingsAboutToOpen();
void globalSettingsClosed();
int currentCatChanged(int index);
void oauthReplyRecieved(QVariantMap);
#ifdef Q_OS_MACOS
void clickedOnDock();
#endif
public slots:
bool launch(InstancePtr instance,
bool online = true,
bool demo = false,
MinecraftTarget::Ptr targetToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr,
const QString& offlineName = QString());
public slots:
bool launch(
InstancePtr instance,
bool online = true,
bool demo = false,
BaseProfilerFactory *profiler = nullptr,
MinecraftServerTargetPtr serverToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr
);
bool kill(InstancePtr instance);
void closeCurrentWindow();
private slots:
private slots:
void on_windowClose();
void messageReceived(const QByteArray& message);
void messageReceived(const QByteArray & message);
void controllerSucceeded();
void controllerFailed(const QString& error);
void controllerFailed(const QString & error);
void setupWizardFinished(int status);
private:
bool handleDataMigration(const QString& currentData, const QString& oldData, const QString& name, const QString& configFile) const;
private:
bool handleDataMigration(const QString & currentData, const QString & oldData, const QString & name, const QString & configFile) const;
bool createSetupWizard();
void performMainStartupAction();
// sets the fatal error message and m_status to Failed.
void showFatalErrorMessage(const QString& title, const QString& content);
void showFatalErrorMessage(const QString & title, const QString & content);
private:
private:
void addRunningInstance();
void subRunningInstance();
bool shouldExitNow() const;
private:
private:
QDateTime startTime;
shared_qobject_ptr<QNetworkAccessManager> m_network;
@ -260,10 +277,9 @@ class Application : public QApplication {
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;
QString m_rootPath;
QString m_dataPath;
Status m_status = Application::StartingUp;
Capabilities m_capabilities;
bool m_portable = false;
bool m_portable = false;
#ifdef Q_OS_MACOS
Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;
@ -276,11 +292,10 @@ class Application : public QApplication {
// FIXME: attach to instances instead.
struct InstanceXtras {
InstanceWindow* window = nullptr;
InstanceWindow * window = nullptr;
shared_qobject_ptr<LaunchController> controller;
};
std::map<QString, InstanceXtras> m_instanceExtras;
mutable QMutex m_instanceExtrasMutex;
// main state variables
size_t m_openWindows = 0;
@ -288,33 +303,18 @@ class Application : public QApplication {
bool m_updateRunning = false;
// main window, if any
MainWindow* m_mainWindow = nullptr;
MainWindow * m_mainWindow = nullptr;
// peer launcher instance connector - used to implement single instance launcher and signalling
LocalPeer* m_peerInstance = nullptr;
LocalPeer * m_peerInstance = nullptr;
SetupWizard* m_setupWizard = nullptr;
public:
QString m_detectedGLFWPath;
QString m_detectedOpenALPath;
SetupWizard * m_setupWizard = nullptr;
public:
QString m_instanceIdToLaunch;
QString m_serverToJoin;
QString m_worldToJoin;
QString m_profileToUse;
bool m_offline = false;
QString m_offlineName;
bool m_liveCheck = false;
QList<QUrl> m_urlsToImport;
QList<QUrl> m_zipsToImport;
QString m_instanceIdToShowWindowOf;
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

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -39,8 +39,7 @@
#include <QJsonObject>
#include "Json.h"
void ApplicationMessage::parse(const QByteArray& input)
{
void ApplicationMessage::parse(const QByteArray & input) {
auto doc = Json::requireDocument(input, "ApplicationMessage");
auto root = Json::requireObject(doc, "ApplicationMessage");
@ -48,13 +47,12 @@ void ApplicationMessage::parse(const QByteArray& input)
args.clear();
auto parsedArgs = root.value("args").toObject();
for (auto iter = parsedArgs.constBegin(); iter != parsedArgs.constEnd(); iter++) {
for(auto iter = parsedArgs.constBegin(); iter != parsedArgs.constEnd(); iter++) {
args.insert(iter.key(), iter.value().toString());
}
}
QByteArray ApplicationMessage::serialize()
{
QByteArray ApplicationMessage::serialize() {
QJsonObject root;
root.insert("command", command);
QJsonObject outArgs;

View file

@ -1,13 +1,13 @@
#pragma once
#include <QByteArray>
#include <QHash>
#include <QString>
#include <QHash>
#include <QByteArray>
struct ApplicationMessage {
QString command;
QHash<QString, QString> args;
QByteArray serialize();
void parse(const QByteArray& input);
void parse(const QByteArray & input);
};

View file

@ -16,24 +16,29 @@
#include <QFile>
#include "BaseInstaller.h"
#include "FileSystem.h"
#include "minecraft/MinecraftInstance.h"
BaseInstaller::BaseInstaller() {}
BaseInstaller::BaseInstaller()
{
bool BaseInstaller::isApplied(MinecraftInstance* on)
}
bool BaseInstaller::isApplied(MinecraftInstance *on)
{
return QFile::exists(filename(on->instanceRoot()));
}
bool BaseInstaller::add(MinecraftInstance* to)
bool BaseInstaller::add(MinecraftInstance *to)
{
if (!patchesDir(to->instanceRoot()).exists()) {
if (!patchesDir(to->instanceRoot()).exists())
{
QDir(to->instanceRoot()).mkdir("patches");
}
if (isApplied(to)) {
if (!remove(to)) {
if (isApplied(to))
{
if (!remove(to))
{
return false;
}
}
@ -41,16 +46,16 @@ bool BaseInstaller::add(MinecraftInstance* to)
return true;
}
bool BaseInstaller::remove(MinecraftInstance* from)
bool BaseInstaller::remove(MinecraftInstance *from)
{
return FS::deletePath(filename(from->instanceRoot()));
return QFile::remove(filename(from->instanceRoot()));
}
QString BaseInstaller::filename(const QString& root) const
QString BaseInstaller::filename(const QString &root) const
{
return patchesDir(root).absoluteFilePath(id() + ".json");
}
QDir BaseInstaller::patchesDir(const QString& root) const
QDir BaseInstaller::patchesDir(const QString &root) const
{
return QDir(root + "/patches/");
}

View file

@ -26,19 +26,20 @@ class QObject;
class Task;
class BaseVersion;
class BaseInstaller {
public:
class BaseInstaller
{
public:
BaseInstaller();
virtual ~BaseInstaller() {};
bool isApplied(MinecraftInstance* on);
virtual ~BaseInstaller(){};
bool isApplied(MinecraftInstance *on);
virtual bool add(MinecraftInstance* to);
virtual bool remove(MinecraftInstance* from);
virtual bool add(MinecraftInstance *to);
virtual bool remove(MinecraftInstance *from);
virtual Task* createInstallTask(MinecraftInstance* instance, BaseVersion::Ptr version, QObject* parent) = 0;
virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersion::Ptr version, QObject *parent) = 0;
protected:
protected:
virtual QString id() const = 0;
QString filename(const QString& root) const;
QDir patchesDir(const QString& root) const;
QString filename(const QString &root) const;
QDir patchesDir(const QString &root) const;
};

View file

@ -1,9 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -37,22 +36,23 @@
#include "BaseInstance.h"
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QDir>
#include <QDebug>
#include <QRegularExpression>
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegularExpression>
#include "settings/INISettingsObject.h"
#include "settings/OverrideSetting.h"
#include "settings/Setting.h"
#include "settings/OverrideSetting.h"
#include "BuildConfig.h"
#include "Commandline.h"
#include "FileSystem.h"
#include "Commandline.h"
#include "BuildConfig.h"
BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir) : QObject()
BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
: QObject()
{
m_settings = settings;
m_global_settings = globalSettings;
@ -64,8 +64,6 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0);
if (m_settings->get("totalTimePlayed").toLongLong() < 0)
m_settings->reset("totalTimePlayed");
m_settings->registerSetting("lastTimePlayed", 0);
m_settings->registerSetting("linkedInstances", "[]");
@ -81,7 +79,7 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("InstanceType", "");
// Custom Commands
auto commandSetting = m_settings->registerSetting({ "OverrideCommands", "OverrideLaunchCmd" }, false);
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting);
m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting);
m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting);
@ -103,8 +101,6 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("ManagedPackName", "");
m_settings->registerSetting("ManagedPackVersionID", "");
m_settings->registerSetting("ManagedPackVersionName", "");
m_settings->registerSetting("Profiler", "");
}
QString BaseInstance::getPreLaunchCommand()
@ -152,11 +148,7 @@ QString BaseInstance::getManagedPackVersionName() const
return m_settings->get("ManagedPackVersionName").toString();
}
void BaseInstance::setManagedPack(const QString& type,
const QString& id,
const QString& name,
const QString& versionId,
const QString& version)
void BaseInstance::setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version)
{
m_settings->set("ManagedPack", true);
m_settings->set("ManagedPackType", type);
@ -181,7 +173,8 @@ int BaseInstance::getConsoleMaxLines() const
auto lineSetting = m_settings->getSetting("ConsoleMaxLines");
bool conversionOk = false;
int maxLines = lineSetting->get().toInt(&conversionOk);
if (!conversionOk) {
if(!conversionOk)
{
maxLines = lineSetting->defValue().toInt();
qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines;
}
@ -227,7 +220,8 @@ bool BaseInstance::isLinkedToInstanceId(const QString& id) const
void BaseInstance::iconUpdated(QString key)
{
if (iconKey() == key) {
if(iconKey() == key)
{
emit propertiesChanged(this);
}
}
@ -241,7 +235,8 @@ void BaseInstance::invalidate()
void BaseInstance::changeStatus(BaseInstance::Status newStatus)
{
Status status = currentStatus();
if (status != newStatus) {
if(status != newStatus)
{
m_status = newStatus;
emit statusChanged(status, newStatus);
}
@ -264,24 +259,23 @@ bool BaseInstance::isRunning() const
void BaseInstance::setRunning(bool running)
{
if (running == m_isRunning)
if(running == m_isRunning)
return;
m_isRunning = running;
emit runningStatusChanged(running);
}
void BaseInstance::setMinecraftRunning(bool running)
{
if (!settings()->get("RecordGameTime").toBool()) {
if(!m_settings->get("RecordGameTime").toBool())
{
emit runningStatusChanged(running);
return;
}
if (running) {
if(running)
{
m_timeStarted = QDateTime::currentDateTime();
setLastLaunch(m_timeStarted.toMSecsSinceEpoch());
} else {
}
else
{
QDateTime timeEnded = QDateTime::currentDateTime();
qint64 current = settings()->get("totalTimePlayed").toLongLong();
@ -290,12 +284,15 @@ void BaseInstance::setMinecraftRunning(bool running)
emit propertiesChanged(this);
}
emit runningStatusChanged(running);
}
int64_t BaseInstance::totalTimePlayed() const
{
qint64 current = m_settings->get("totalTimePlayed").toLongLong();
if (m_isRunning) {
if(m_isRunning)
{
QDateTime timeNow = QDateTime::currentDateTime();
return current + m_timeStarted.secsTo(timeNow);
}
@ -304,7 +301,8 @@ int64_t BaseInstance::totalTimePlayed() const
int64_t BaseInstance::lastTimePlayed() const
{
if (m_isRunning) {
if(m_isRunning)
{
QDateTime timeNow = QDateTime::currentDateTime();
return m_timeStarted.secsTo(timeNow);
}
@ -351,14 +349,14 @@ qint64 BaseInstance::lastLaunch() const
void BaseInstance::setLastLaunch(qint64 val)
{
// FIXME: if no change, do not set. setting involves saving a file.
//FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("lastLaunchTime", val);
emit propertiesChanged(this);
}
void BaseInstance::setNotes(QString val)
{
// FIXME: if no change, do not set. setting involves saving a file.
//FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("notes", val);
}
@ -369,7 +367,7 @@ QString BaseInstance::notes() const
void BaseInstance::setIconKey(QString val)
{
// FIXME: if no change, do not set. setting involves saving a file.
//FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("iconKey", val);
emit propertiesChanged(this);
}
@ -381,7 +379,7 @@ QString BaseInstance::iconKey() const
void BaseInstance::setName(QString val)
{
// FIXME: if no change, do not set. setting involves saving a file.
//FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("name", val);
emit propertiesChanged(this);
}
@ -393,7 +391,7 @@ QString BaseInstance::name() const
QString BaseInstance::windowTitle() const
{
return BuildConfig.LAUNCHER_DISPLAYNAME + ": " + name();
return BuildConfig.LAUNCHER_DISPLAYNAME + ": " + name().replace(QRegularExpression("\\s+"), " ");
}
// FIXME: why is this here? move it to MinecraftInstance!!!
@ -411,8 +409,3 @@ void BaseInstance::updateRuntimeContext()
{
// NOOP
}
bool BaseInstance::isLegacy()
{
return traits().contains("legacyLaunch") || traits().contains("alphaLaunch");
}

View file

@ -1,9 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@ -38,25 +37,24 @@
#pragma once
#include <cassert>
#include <QDateTime>
#include <QMenu>
#include <QObject>
#include <QProcess>
#include <QSet>
#include "QObjectPtr.h"
#include <QDateTime>
#include <QSet>
#include <QProcess>
#include "settings/SettingsObject.h"
#include "BaseVersionList.h"
#include "MessageLevel.h"
#include "minecraft/auth/MinecraftAccount.h"
#include "pathmatcher/IPathMatcher.h"
#include "settings/INIFile.h"
#include "BaseVersionList.h"
#include "minecraft/auth/MinecraftAccount.h"
#include "MessageLevel.h"
#include "pathmatcher/IPathMatcher.h"
#include "net/Mode.h"
#include "minecraft/launch/MinecraftServerTarget.h"
#include "RuntimeContext.h"
#include "minecraft/launch/MinecraftTarget.h"
class QDir;
class Task;
@ -64,7 +62,7 @@ class LaunchTask;
class BaseInstance;
// pointer for lazy people
using InstancePtr = std::shared_ptr<BaseInstance>;
typedef std::shared_ptr<BaseInstance> InstancePtr;
/*!
* \brief Base class for instances.
@ -74,21 +72,23 @@ using InstancePtr = std::shared_ptr<BaseInstance>;
* To create a new instance type, create a new class inheriting from this class
* and implement the pure virtual functions.
*/
class BaseInstance : public QObject, public std::enable_shared_from_this<BaseInstance> {
class BaseInstance : public QObject, public std::enable_shared_from_this<BaseInstance>
{
Q_OBJECT
protected:
protected:
/// no-touchy!
BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir);
BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
public: /* types */
enum class Status {
public: /* types */
enum class Status
{
Present,
Gone // either nuked or invalidated
Gone // either nuked or invalidated
};
public:
public:
/// virtual destructor to make sure the destruction is COMPLETE
virtual ~BaseInstance() {}
virtual ~BaseInstance() {};
virtual void saveNow() = 0;
@ -104,7 +104,6 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
/// be unique.
virtual QString id() const;
void setMinecraftRunning(bool running);
void setRunning(bool running);
bool isRunning() const;
int64_t totalTimePlayed() const;
@ -118,7 +117,10 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
QString instanceRoot() const;
/// Path to the instance's game root directory.
virtual QString gameRoot() const { return instanceRoot(); }
virtual QString gameRoot() const
{
return instanceRoot();
}
/// Path to the instance's mods directory.
virtual QString modsRoot() const = 0;
@ -149,12 +151,15 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
void copyManagedPack(BaseInstance& other);
/// guess log level from a line of game log
virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString& line, MessageLevel::Enum level) { return level; }
virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString &line, MessageLevel::Enum level)
{
return level;
};
virtual QStringList extraArguments();
/// Traits. Normally inside the version, depends on instance implementation.
virtual QSet<QString> traits() const = 0;
virtual QSet <QString> traits() const = 0;
/**
* Gets the time that the instance was last launched.
@ -181,10 +186,11 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
virtual void loadSpecificSettings() = 0;
/// returns a valid update task
virtual QList<Task::Ptr> createUpdateTask() = 0;
virtual Task::Ptr createUpdateTask(Net::Mode mode) = 0;
/// returns a valid launcher (task container)
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) = 0;
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(
AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) = 0;
/// returns the current launch task (if any)
shared_qobject_ptr<LaunchTask> getLaunchTask();
@ -215,31 +221,46 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
virtual QString typeName() const = 0;
virtual void updateRuntimeContext();
RuntimeContext runtimeContext() const { return m_runtimeContext; }
void updateRuntimeContext();
RuntimeContext runtimeContext() const
{
return m_runtimeContext;
}
bool hasVersionBroken() const { return m_hasBrokenVersion; }
bool hasVersionBroken() const
{
return m_hasBrokenVersion;
}
void setVersionBroken(bool value)
{
if (m_hasBrokenVersion != value) {
if(m_hasBrokenVersion != value)
{
m_hasBrokenVersion = value;
emit propertiesChanged(this);
}
}
bool hasUpdateAvailable() const { return m_hasUpdate; }
bool hasUpdateAvailable() const
{
return m_hasUpdate;
}
void setUpdateAvailable(bool value)
{
if (m_hasUpdate != value) {
if(m_hasUpdate != value)
{
m_hasUpdate = value;
emit propertiesChanged(this);
}
}
bool hasCrashed() const { return m_crashed; }
bool hasCrashed() const
{
return m_crashed;
}
void setCrashed(bool value)
{
if (m_crashed != value) {
if(m_crashed != value)
{
m_crashed = value;
emit propertiesChanged(this);
}
@ -249,14 +270,12 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
virtual bool canEdit() const = 0;
virtual bool canExport() const = 0;
virtual void populateLaunchMenu(QMenu* menu) = 0;
bool reloadSettings();
/**
* 'print' a verbose description of the instance into a QStringList
*/
virtual QStringList verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) = 0;
virtual QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) = 0;
Status currentStatus() const;
@ -269,34 +288,30 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
bool removeLinkedInstanceId(const QString& id);
bool isLinkedToInstanceId(const QString& id) const;
bool isLegacy();
protected:
protected:
void changeStatus(Status newStatus);
SettingsObjectPtr globalSettings() const { return m_global_settings.lock(); }
SettingsObjectPtr globalSettings() const { return m_global_settings.lock(); };
bool isSpecificSettingsLoaded() const { return m_specific_settings_loaded; }
void setSpecificSettingsLoaded(bool loaded) { m_specific_settings_loaded = loaded; }
signals:
signals:
/*!
* \brief Signal emitted when properties relevant to the instance view change
*/
void propertiesChanged(BaseInstance* inst);
void propertiesChanged(BaseInstance *inst);
void launchTaskChanged(shared_qobject_ptr<LaunchTask>);
void runningStatusChanged(bool running);
void profilerChanged();
void statusChanged(Status from, Status to);
protected slots:
protected slots:
void iconUpdated(QString key);
protected: /* data */
protected: /* data */
QString m_rootDir;
SettingsObjectPtr m_settings;
// InstanceFlags m_flags;
@ -305,7 +320,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
QDateTime m_timeStarted;
RuntimeContext m_runtimeContext;
private: /* data */
private: /* data */
Status m_status = Status::Present;
bool m_crashed = false;
bool m_hasUpdate = false;
@ -313,8 +328,9 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
SettingsObjectWeakPtr m_global_settings;
bool m_specific_settings_loaded = false;
};
Q_DECLARE_METATYPE(shared_qobject_ptr<BaseInstance>)
// Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
// Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)
//Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
//Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)

View file

@ -43,8 +43,9 @@ class BaseVersion {
* the kind of version this is (Stable, Beta, Snapshot, whatever)
*/
virtual QString typeString() const = 0;
virtual bool operator<(BaseVersion& a) { return name() < a.name(); }
virtual bool operator>(BaseVersion& a) { return name() > a.name(); }
virtual bool operator<(BaseVersion& a) { return name() < a.name(); };
virtual bool operator>(BaseVersion& a) { return name() > a.name(); };
};
Q_DECLARE_METATYPE(BaseVersion::Ptr)

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -36,11 +36,14 @@
#include "BaseVersionList.h"
#include "BaseVersion.h"
BaseVersionList::BaseVersionList(QObject* parent) : QAbstractListModel(parent) {}
BaseVersion::Ptr BaseVersionList::findVersion(const QString& descriptor)
BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent)
{
for (int i = 0; i < count(); i++) {
}
BaseVersion::Ptr BaseVersionList::findVersion(const QString &descriptor)
{
for (int i = 0; i < count(); i++)
{
if (at(i)->descriptor() == descriptor)
return at(i);
}
@ -55,7 +58,7 @@ BaseVersion::Ptr BaseVersionList::getRecommended() const
return at(0);
}
QVariant BaseVersionList::data(const QModelIndex& index, int role) const
QVariant BaseVersionList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
@ -65,44 +68,37 @@ QVariant BaseVersionList::data(const QModelIndex& index, int role) const
BaseVersion::Ptr version = at(index.row());
switch (role) {
case VersionPointerRole:
return QVariant::fromValue(version);
switch (role)
{
case VersionPointerRole:
return QVariant::fromValue(version);
case VersionRole:
return version->name();
case VersionRole:
return version->name();
case VersionIdRole:
return version->descriptor();
case VersionIdRole:
return version->descriptor();
case TypeRole:
return version->typeString();
case TypeRole:
return version->typeString();
case JavaMajorRole: {
auto major = version->name();
if (major.startsWith("java")) {
major = "Java " + major.mid(4);
}
return major;
}
default:
return QVariant();
default:
return QVariant();
}
}
BaseVersionList::RoleList BaseVersionList::providesRoles() const
{
return { VersionPointerRole, VersionRole, VersionIdRole, TypeRole };
return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole};
}
int BaseVersionList::rowCount(const QModelIndex& parent) const
int BaseVersionList::rowCount(const QModelIndex &parent) const
{
// Return count
return parent.isValid() ? 0 : count();
}
int BaseVersionList::columnCount(const QModelIndex& parent) const
int BaseVersionList::columnCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : 1;
}
@ -118,8 +114,6 @@ QHash<int, QByteArray> BaseVersionList::roleNames() const
roles.insert(TypeRole, "type");
roles.insert(BranchRole, "branch");
roles.insert(PathRole, "path");
roles.insert(JavaNameRole, "javaName");
roles.insert(CPUArchitectureRole, "architecture");
roles.insert(JavaMajorRole, "javaMajor");
roles.insert(ArchitectureRole, "architecture");
return roles;
}

View file

@ -15,13 +15,13 @@
#pragma once
#include <QAbstractListModel>
#include <QObject>
#include <QVariant>
#include <QAbstractListModel>
#include "BaseVersion.h"
#include "QObjectPtr.h"
#include "tasks/Task.h"
#include "QObjectPtr.h"
/*!
* \brief Class that each instance type's version list derives from.
@ -35,10 +35,12 @@
* all have a default implementation, but they can be overridden by plugins to
* change the behavior of the list.
*/
class BaseVersionList : public QAbstractListModel {
class BaseVersionList : public QAbstractListModel
{
Q_OBJECT
public:
enum ModelRoles {
public:
enum ModelRoles
{
VersionPointerRole = Qt::UserRole,
VersionRole,
VersionIdRole,
@ -48,14 +50,12 @@ class BaseVersionList : public QAbstractListModel {
TypeRole,
BranchRole,
PathRole,
JavaNameRole,
JavaMajorRole,
CPUArchitectureRole,
ArchitectureRole,
SortRole
};
using RoleList = QList<int>;
typedef QList<int> RoleList;
explicit BaseVersionList(QObject* parent = 0);
explicit BaseVersionList(QObject *parent = 0);
/*!
* \brief Gets a task that will reload the version list.
@ -66,7 +66,7 @@ class BaseVersionList : public QAbstractListModel {
virtual Task::Ptr getLoadTask() = 0;
//! Checks whether or not the list is loaded. If this returns false, the list should be
// loaded.
//loaded.
virtual bool isLoaded() = 0;
//! Gets the version at the given index.
@ -76,9 +76,9 @@ class BaseVersionList : public QAbstractListModel {
virtual int count() const = 0;
//////// List Model Functions ////////
QVariant data(const QModelIndex& index, int role) const override;
int rowCount(const QModelIndex& parent) const override;
int columnCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QHash<int, QByteArray> roleNames() const override;
//! which roles are provided by this version list?
@ -90,7 +90,7 @@ class BaseVersionList : public QAbstractListModel {
* \return A const pointer to the version with the given descriptor. NULL if
* one doesn't exist.
*/
virtual BaseVersion::Ptr findVersion(const QString& descriptor);
virtual BaseVersion::Ptr findVersion(const QString &descriptor);
/*!
* \brief Gets the recommended version from this list
@ -103,7 +103,8 @@ class BaseVersionList : public QAbstractListModel {
*/
virtual void sortVersions() = 0;
protected slots:
protected
slots:
/*!
* Updates this list with the given list of versions.
* This is done by copying each version in the given list and inserting it

View file

@ -24,13 +24,10 @@ set(CORE_SOURCES
NullInstance.h
MMCZip.h
MMCZip.cpp
Untar.h
Untar.cpp
StringUtils.h
StringUtils.cpp
QVariantUtils.h
RuntimeContext.h
PSaveFile.h
# Basic instance manipulation tasks (derived from InstanceTask)
InstanceCreationTask.h
@ -129,6 +126,7 @@ set(NET_SOURCES
net/MetaCacheSink.h
net/Logging.h
net/Logging.cpp
net/NetAction.h
net/NetJob.cpp
net/NetJob.h
net/NetUtils.h
@ -138,15 +136,6 @@ set(NET_SOURCES
net/Validator.h
net/Upload.cpp
net/Upload.h
net/HeaderProxy.h
net/RawHeaderProxy.h
net/ApiHeaderProxy.h
net/ApiDownload.h
net/ApiDownload.cpp
net/ApiUpload.cpp
net/ApiUpload.h
net/NetRequest.cpp
net/NetRequest.h
)
# Game launch logic
@ -161,18 +150,16 @@ set(LAUNCH_SOURCES
launch/steps/PreLaunchCommand.h
launch/steps/TextPrint.cpp
launch/steps/TextPrint.h
launch/steps/Update.cpp
launch/steps/Update.h
launch/steps/QuitAfterGameStop.cpp
launch/steps/QuitAfterGameStop.h
launch/steps/PrintServers.cpp
launch/steps/PrintServers.h
launch/LaunchStep.cpp
launch/LaunchStep.h
launch/LaunchTask.cpp
launch/LaunchTask.h
launch/LogModel.cpp
launch/LogModel.h
launch/TaskStepWrapper.cpp
launch/TaskStepWrapper.h
)
# Old update system
@ -185,11 +172,6 @@ set(MAC_UPDATE_SOURCES
updater/MacSparkleUpdater.mm
)
set(PRISM_UPDATE_SOURCES
updater/PrismExternalUpdater.h
updater/PrismExternalUpdater.cpp
)
# Backend for the news bar... there's usually no news.
set(NEWS_SOURCES
# News System
@ -208,37 +190,49 @@ set(ICONS_SOURCES
# Support for Minecraft instances and launch
set(MINECRAFT_SOURCES
# Logging
minecraft/Logging.h
minecraft/Logging.cpp
# Minecraft support
minecraft/auth/AccountData.cpp
minecraft/auth/AccountData.h
minecraft/auth/AccountList.cpp
minecraft/auth/AccountList.h
minecraft/auth/AccountTask.cpp
minecraft/auth/AccountTask.h
minecraft/auth/AuthRequest.cpp
minecraft/auth/AuthRequest.h
minecraft/auth/AuthSession.cpp
minecraft/auth/AuthSession.h
minecraft/auth/AuthStep.cpp
minecraft/auth/AuthStep.h
minecraft/auth/MinecraftAccount.cpp
minecraft/auth/MinecraftAccount.h
minecraft/auth/Parsers.cpp
minecraft/auth/Parsers.h
minecraft/auth/Yggdrasil.cpp
minecraft/auth/Yggdrasil.h
minecraft/auth/AuthFlow.cpp
minecraft/auth/AuthFlow.h
minecraft/auth/flows/AuthFlow.cpp
minecraft/auth/flows/AuthFlow.h
minecraft/auth/flows/Mojang.cpp
minecraft/auth/flows/Mojang.h
minecraft/auth/flows/MSA.cpp
minecraft/auth/flows/MSA.h
minecraft/auth/flows/Offline.cpp
minecraft/auth/flows/Offline.h
minecraft/auth/steps/OfflineStep.cpp
minecraft/auth/steps/OfflineStep.h
minecraft/auth/steps/EntitlementsStep.cpp
minecraft/auth/steps/EntitlementsStep.h
minecraft/auth/steps/GetSkinStep.cpp
minecraft/auth/steps/GetSkinStep.h
minecraft/auth/steps/LauncherLoginStep.cpp
minecraft/auth/steps/LauncherLoginStep.h
minecraft/auth/steps/MigrationEligibilityStep.cpp
minecraft/auth/steps/MigrationEligibilityStep.h
minecraft/auth/steps/MinecraftProfileStep.cpp
minecraft/auth/steps/MinecraftProfileStep.h
minecraft/auth/steps/MSADeviceCodeStep.cpp
minecraft/auth/steps/MSADeviceCodeStep.h
minecraft/auth/steps/MinecraftProfileStepMojang.cpp
minecraft/auth/steps/MinecraftProfileStepMojang.h
minecraft/auth/steps/MSAStep.cpp
minecraft/auth/steps/MSAStep.h
minecraft/auth/steps/XboxAuthorizationStep.cpp
@ -247,6 +241,8 @@ set(MINECRAFT_SOURCES
minecraft/auth/steps/XboxProfileStep.h
minecraft/auth/steps/XboxUserStep.cpp
minecraft/auth/steps/XboxUserStep.h
minecraft/auth/steps/YggdrasilStep.cpp
minecraft/auth/steps/YggdrasilStep.h
minecraft/gameoptions/GameOptions.h
minecraft/gameoptions/GameOptions.cpp
@ -266,12 +262,14 @@ set(MINECRAFT_SOURCES
minecraft/launch/CreateGameFolders.h
minecraft/launch/ModMinecraftJar.cpp
minecraft/launch/ModMinecraftJar.h
minecraft/launch/DirectJavaLaunch.cpp
minecraft/launch/DirectJavaLaunch.h
minecraft/launch/ExtractNatives.cpp
minecraft/launch/ExtractNatives.h
minecraft/launch/LauncherPartLaunch.cpp
minecraft/launch/LauncherPartLaunch.h
minecraft/launch/MinecraftTarget.cpp
minecraft/launch/MinecraftTarget.h
minecraft/launch/MinecraftServerTarget.cpp
minecraft/launch/MinecraftServerTarget.h
minecraft/launch/PrintInstanceInfo.cpp
minecraft/launch/PrintInstanceInfo.h
minecraft/launch/ReconstructAssets.cpp
@ -280,8 +278,6 @@ set(MINECRAFT_SOURCES
minecraft/launch/ScanModFolders.h
minecraft/launch/VerifyJavaInstall.cpp
minecraft/launch/VerifyJavaInstall.h
minecraft/launch/AutoInstallJava.cpp
minecraft/launch/AutoInstallJava.h
minecraft/GradleSpecifier.h
minecraft/MinecraftInstance.cpp
@ -296,6 +292,8 @@ set(MINECRAFT_SOURCES
minecraft/ComponentUpdateTask.h
minecraft/MinecraftLoadAndCheck.h
minecraft/MinecraftLoadAndCheck.cpp
minecraft/MinecraftUpdate.h
minecraft/MinecraftUpdate.cpp
minecraft/MojangVersionFormat.cpp
minecraft/MojangVersionFormat.h
minecraft/Rule.cpp
@ -345,12 +343,13 @@ set(MINECRAFT_SOURCES
minecraft/mod/TexturePackFolderModel.h
minecraft/mod/TexturePackFolderModel.cpp
minecraft/mod/ShaderPackFolderModel.h
minecraft/mod/tasks/ResourceFolderLoadTask.h
minecraft/mod/tasks/ResourceFolderLoadTask.cpp
minecraft/mod/tasks/BasicFolderLoadTask.h
minecraft/mod/tasks/ModFolderLoadTask.h
minecraft/mod/tasks/ModFolderLoadTask.cpp
minecraft/mod/tasks/LocalModParseTask.h
minecraft/mod/tasks/LocalModParseTask.cpp
minecraft/mod/tasks/LocalResourceUpdateTask.h
minecraft/mod/tasks/LocalResourceUpdateTask.cpp
minecraft/mod/tasks/LocalModUpdateTask.h
minecraft/mod/tasks/LocalModUpdateTask.cpp
minecraft/mod/tasks/LocalDataPackParseTask.h
minecraft/mod/tasks/LocalDataPackParseTask.cpp
minecraft/mod/tasks/LocalResourcePackParseTask.h
@ -363,24 +362,18 @@ set(MINECRAFT_SOURCES
minecraft/mod/tasks/LocalWorldSaveParseTask.cpp
minecraft/mod/tasks/LocalResourceParse.h
minecraft/mod/tasks/LocalResourceParse.cpp
minecraft/mod/tasks/GetModDependenciesTask.h
minecraft/mod/tasks/GetModDependenciesTask.cpp
# Assets
minecraft/AssetsUtils.h
minecraft/AssetsUtils.cpp
# Minecraft skins
minecraft/skins/CapeChange.cpp
minecraft/skins/CapeChange.h
minecraft/skins/SkinUpload.cpp
minecraft/skins/SkinUpload.h
minecraft/skins/SkinDelete.cpp
minecraft/skins/SkinDelete.h
minecraft/skins/SkinModel.cpp
minecraft/skins/SkinModel.h
minecraft/skins/SkinList.cpp
minecraft/skins/SkinList.h
# Minecraft services
minecraft/services/CapeChange.cpp
minecraft/services/CapeChange.h
minecraft/services/SkinUpload.cpp
minecraft/services/SkinUpload.h
minecraft/services/SkinDelete.cpp
minecraft/services/SkinDelete.h
minecraft/Agent.h)
@ -424,6 +417,8 @@ set(SETTINGS_SOURCES
set(JAVA_SOURCES
java/JavaChecker.h
java/JavaChecker.cpp
java/JavaCheckerJob.h
java/JavaCheckerJob.cpp
java/JavaInstall.h
java/JavaInstall.cpp
java/JavaInstallList.h
@ -432,20 +427,6 @@ set(JAVA_SOURCES
java/JavaUtils.cpp
java/JavaVersion.h
java/JavaVersion.cpp
java/JavaMetadata.h
java/JavaMetadata.cpp
java/download/ArchiveDownloadTask.cpp
java/download/ArchiveDownloadTask.h
java/download/ManifestDownloadTask.cpp
java/download/ManifestDownloadTask.h
java/download/SymlinkTask.cpp
java/download/SymlinkTask.h
ui/java/InstallJavaDialog.h
ui/java/InstallJavaDialog.cpp
ui/java/VersionList.h
ui/java/VersionList.cpp
)
set(TRANSLATIONS_SOURCES
@ -467,8 +448,6 @@ set(TOOLS_SOURCES
tools/JVisualVM.h
tools/MCEditTool.cpp
tools/MCEditTool.h
tools/GenericProfiler.cpp
tools/GenericProfiler.h
)
set(META_SOURCES
@ -506,9 +485,6 @@ set(API_SOURCES
modplatform/helpers/HashUtils.cpp
modplatform/helpers/OverrideUtils.h
modplatform/helpers/OverrideUtils.cpp
modplatform/helpers/ExportToModList.h
modplatform/helpers/ExportToModList.cpp
)
set(FTB_SOURCES
@ -520,11 +496,6 @@ set(FTB_SOURCES
modplatform/legacy_ftb/PrivatePackManager.cpp
modplatform/legacy_ftb/PackHelpers.h
modplatform/import_ftb/PackInstallTask.h
modplatform/import_ftb/PackInstallTask.cpp
modplatform/import_ftb/PackHelpers.h
modplatform/import_ftb/PackHelpers.cpp
)
set(FLAME_SOURCES
@ -541,8 +512,6 @@ set(FLAME_SOURCES
modplatform/flame/FlameCheckUpdate.h
modplatform/flame/FlameInstanceCreationTask.h
modplatform/flame/FlameInstanceCreationTask.cpp
modplatform/flame/FlamePackExportTask.h
modplatform/flame/FlamePackExportTask.cpp
)
set(MODRINTH_SOURCES
@ -587,9 +556,6 @@ set(ATLAUNCHER_SOURCES
)
set(LINKEXE_SOURCES
WindowsConsole.cpp
WindowsConsole.h
filelink/FileLink.h
filelink/FileLink.cpp
FileSystem.h
@ -601,62 +567,6 @@ set(LINKEXE_SOURCES
DesktopServices.cpp
)
set(PRISMUPDATER_SOURCES
updater/prismupdater/PrismUpdater.h
updater/prismupdater/PrismUpdater.cpp
updater/prismupdater/UpdaterDialogs.h
updater/prismupdater/UpdaterDialogs.cpp
updater/prismupdater/GitHubRelease.h
updater/prismupdater/GitHubRelease.cpp
Json.h
Json.cpp
FileSystem.h
FileSystem.cpp
StringUtils.h
StringUtils.cpp
DesktopServices.h
DesktopServices.cpp
Version.h
Version.cpp
Markdown.h
Markdown.cpp
# Zip
MMCZip.h
MMCZip.cpp
# Time
MMCTime.h
MMCTime.cpp
net/ByteArraySink.h
net/ChecksumValidator.h
net/Download.cpp
net/Download.h
net/FileSink.cpp
net/FileSink.h
net/HttpMetaCache.cpp
net/HttpMetaCache.h
net/Logging.h
net/Logging.cpp
net/NetRequest.cpp
net/NetRequest.h
net/NetJob.cpp
net/NetJob.h
net/NetUtils.h
net/Sink.h
net/Validator.h
net/HeaderProxy.h
net/RawHeaderProxy.h
ui/dialogs/ProgressDialog.cpp
ui/dialogs/ProgressDialog.h
ui/widgets/SubTaskProgressBar.h
ui/widgets/SubTaskProgressBar.cpp
)
######## Logging categories ########
ecm_qt_declare_logging_category(CORE_SOURCES
@ -668,22 +578,6 @@ ecm_qt_declare_logging_category(CORE_SOURCES
EXPORT "${Launcher_Name}"
)
ecm_qt_export_logging_category(
IDENTIFIER instanceProfileC
CATEGORY_NAME "launcher.instance.profile"
DEFAULT_SEVERITY Debug
DESCRIPTION "Profile actions"
EXPORT "${Launcher_Name}"
)
ecm_qt_export_logging_category(
IDENTIFIER instanceProfileResolveC
CATEGORY_NAME "launcher.instance.profile.resolve"
DEFAULT_SEVERITY Debug
DESCRIPTION "Profile component resolusion actions"
EXPORT "${Launcher_Name}"
)
ecm_qt_export_logging_category(
IDENTIFIER taskLogC
CATEGORY_NAME "launcher.task"
@ -696,7 +590,7 @@ ecm_qt_export_logging_category(
IDENTIFIER taskNetLogC
CATEGORY_NAME "launcher.task.net"
DEFAULT_SEVERITY Debug
DESCRIPTION "Task network action"
DESCRIPTION "task network action"
EXPORT "${Launcher_Name}"
)
@ -704,14 +598,14 @@ ecm_qt_export_logging_category(
IDENTIFIER taskDownloadLogC
CATEGORY_NAME "launcher.task.net.download"
DEFAULT_SEVERITY Debug
DESCRIPTION "Task network download actions"
DESCRIPTION "task network download actions"
EXPORT "${Launcher_Name}"
)
ecm_qt_export_logging_category(
IDENTIFIER taskUploadLogC
CATEGORY_NAME "launcher.task.net.upload"
DEFAULT_SEVERITY Debug
DESCRIPTION "Task network upload actions"
DESCRIPTION "task network upload actions"
EXPORT "${Launcher_Name}"
)
@ -767,10 +661,8 @@ set(LOGIC_SOURCES
${ATLAUNCHER_SOURCES}
)
if(APPLE AND Launcher_ENABLE_UPDATER)
if(APPLE)
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES})
else()
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${PRISM_UPDATE_SOURCES})
endif()
SET(LAUNCHER_SOURCES
@ -781,8 +673,6 @@ SET(LAUNCHER_SOURCES
DataMigrationTask.cpp
ApplicationMessage.h
ApplicationMessage.cpp
SysInfo.h
SysInfo.cpp
# GUI - general utilities
DesktopServices.h
@ -821,12 +711,16 @@ SET(LAUNCHER_SOURCES
# GUI - windows
ui/GuiUtil.h
ui/GuiUtil.cpp
ui/ColorCache.h
ui/ColorCache.cpp
ui/MainWindow.h
ui/MainWindow.cpp
ui/InstanceWindow.h
ui/InstanceWindow.cpp
# FIXME: maybe find a better home for this.
SkinUtils.cpp
SkinUtils.h
FileIgnoreProxy.cpp
FileIgnoreProxy.h
FastFileIconProvider.cpp
@ -844,10 +738,6 @@ SET(LAUNCHER_SOURCES
ui/setupwizard/PasteWizardPage.h
ui/setupwizard/ThemeWizardPage.cpp
ui/setupwizard/ThemeWizardPage.h
ui/setupwizard/AutoJavaWizardPage.cpp
ui/setupwizard/AutoJavaWizardPage.h
ui/setupwizard/LoginWizardPage.cpp
ui/setupwizard/LoginWizardPage.h
# GUI - themes
ui/themes/FusionTheme.cpp
@ -860,16 +750,10 @@ SET(LAUNCHER_SOURCES
ui/themes/DarkTheme.h
ui/themes/ITheme.cpp
ui/themes/ITheme.h
ui/themes/HintOverrideProxyStyle.cpp
ui/themes/HintOverrideProxyStyle.h
ui/themes/SystemTheme.cpp
ui/themes/SystemTheme.h
ui/themes/IconTheme.cpp
ui/themes/IconTheme.h
ui/themes/ThemeManager.cpp
ui/themes/ThemeManager.h
ui/themes/CatPack.cpp
ui/themes/CatPack.h
# Processes
LaunchController.h
@ -908,6 +792,7 @@ SET(LAUNCHER_SOURCES
ui/pages/instance/NotesPage.h
ui/pages/instance/LogPage.cpp
ui/pages/instance/LogPage.h
ui/pages/instance/InstanceSettingsPage.cpp
ui/pages/instance/InstanceSettingsPage.h
ui/pages/instance/ScreenshotsPage.cpp
ui/pages/instance/ScreenshotsPage.h
@ -921,12 +806,15 @@ SET(LAUNCHER_SOURCES
# GUI - global settings pages
ui/pages/global/AccountListPage.cpp
ui/pages/global/AccountListPage.h
ui/pages/global/CustomCommandsPage.cpp
ui/pages/global/CustomCommandsPage.h
ui/pages/global/ExternalToolsPage.cpp
ui/pages/global/ExternalToolsPage.h
ui/pages/global/JavaPage.cpp
ui/pages/global/JavaPage.h
ui/pages/global/LanguagePage.cpp
ui/pages/global/LanguagePage.h
ui/pages/global/MinecraftPage.cpp
ui/pages/global/MinecraftPage.h
ui/pages/global/LauncherPage.cpp
ui/pages/global/LauncherPage.h
@ -959,9 +847,6 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/ShaderPackPage.cpp
ui/pages/modplatform/ShaderPackModel.cpp
ui/pages/modplatform/ModpackProviderBasePage.h
ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
ui/pages/modplatform/atlauncher/AtlFilterModel.h
ui/pages/modplatform/atlauncher/AtlListModel.cpp
@ -978,11 +863,6 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/legacy_ftb/ListModel.h
ui/pages/modplatform/legacy_ftb/ListModel.cpp
ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
ui/pages/modplatform/import_ftb/ImportFTBPage.h
ui/pages/modplatform/import_ftb/ListModel.h
ui/pages/modplatform/import_ftb/ListModel.cpp
ui/pages/modplatform/flame/FlameModel.cpp
ui/pages/modplatform/flame/FlameModel.h
ui/pages/modplatform/flame/FlamePage.cpp
@ -1005,9 +885,6 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/ImportPage.cpp
ui/pages/modplatform/ImportPage.h
ui/pages/modplatform/OptionalModDialog.cpp
ui/pages/modplatform/OptionalModDialog.h
ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
ui/pages/modplatform/modrinth/ModrinthResourceModels.h
ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
@ -1024,16 +901,18 @@ SET(LAUNCHER_SOURCES
ui/dialogs/CopyInstanceDialog.h
ui/dialogs/CustomMessageBox.cpp
ui/dialogs/CustomMessageBox.h
ui/dialogs/EditAccountDialog.cpp
ui/dialogs/EditAccountDialog.h
ui/dialogs/ExportInstanceDialog.cpp
ui/dialogs/ExportInstanceDialog.h
ui/dialogs/ExportPackDialog.cpp
ui/dialogs/ExportPackDialog.h
ui/dialogs/ExportToModListDialog.cpp
ui/dialogs/ExportToModListDialog.h
ui/dialogs/ExportMrPackDialog.cpp
ui/dialogs/ExportMrPackDialog.h
ui/dialogs/IconPickerDialog.cpp
ui/dialogs/IconPickerDialog.h
ui/dialogs/ImportResourceDialog.cpp
ui/dialogs/ImportResourceDialog.h
ui/dialogs/LoginDialog.cpp
ui/dialogs/LoginDialog.h
ui/dialogs/MSALoginDialog.cpp
ui/dialogs/MSALoginDialog.h
ui/dialogs/OfflineLoginDialog.cpp
@ -1052,6 +931,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ReviewMessageBox.h
ui/dialogs/VersionSelectDialog.cpp
ui/dialogs/VersionSelectDialog.h
ui/dialogs/SkinUploadDialog.cpp
ui/dialogs/SkinUploadDialog.h
ui/dialogs/ResourceDownloadDialog.cpp
ui/dialogs/ResourceDownloadDialog.h
ui/dialogs/ScrollMessageBox.cpp
@ -1060,29 +941,22 @@ SET(LAUNCHER_SOURCES
ui/dialogs/BlockedModsDialog.h
ui/dialogs/ChooseProviderDialog.h
ui/dialogs/ChooseProviderDialog.cpp
ui/dialogs/ResourceUpdateDialog.cpp
ui/dialogs/ResourceUpdateDialog.h
ui/dialogs/InstallLoaderDialog.cpp
ui/dialogs/InstallLoaderDialog.h
ui/dialogs/skins/SkinManageDialog.cpp
ui/dialogs/skins/SkinManageDialog.h
ui/dialogs/ModUpdateDialog.cpp
ui/dialogs/ModUpdateDialog.h
# GUI - widgets
ui/widgets/CheckComboBox.cpp
ui/widgets/CheckComboBox.h
ui/widgets/Common.cpp
ui/widgets/Common.h
ui/widgets/CustomCommands.cpp
ui/widgets/CustomCommands.h
ui/widgets/EnvironmentVariables.cpp
ui/widgets/EnvironmentVariables.h
ui/widgets/DropLabel.cpp
ui/widgets/DropLabel.h
ui/widgets/FocusLineEdit.cpp
ui/widgets/FocusLineEdit.h
ui/widgets/IconLabel.cpp
ui/widgets/IconLabel.h
ui/widgets/JavaWizardWidget.cpp
ui/widgets/JavaWizardWidget.h
ui/widgets/JavaSettingsWidget.cpp
ui/widgets/JavaSettingsWidget.h
ui/widgets/LabeledToolButton.cpp
ui/widgets/LabeledToolButton.h
ui/widgets/LanguageSelectionWidget.cpp
@ -1118,10 +992,6 @@ SET(LAUNCHER_SOURCES
ui/widgets/WideBar.cpp
ui/widgets/ThemeCustomizationWidget.h
ui/widgets/ThemeCustomizationWidget.cpp
ui/widgets/MinecraftSettingsWidget.h
ui/widgets/MinecraftSettingsWidget.cpp
ui/widgets/JavaSettingsWidget.h
ui/widgets/JavaSettingsWidget.cpp
# GUI - instance group view
ui/instanceview/InstanceProxyModel.cpp
@ -1137,34 +1007,16 @@ SET(LAUNCHER_SOURCES
ui/instanceview/VisualGroup.h
)
if (NOT Apple)
set(LAUNCHER_SOURCES
${LAUNCHER_SOURCES}
ui/dialogs/UpdateAvailableDialog.h
ui/dialogs/UpdateAvailableDialog.cpp
)
endif()
if(WIN32)
set(LAUNCHER_SOURCES
WindowsConsole.cpp
WindowsConsole.h
${LAUNCHER_SOURCES}
)
endif()
qt_wrap_ui(LAUNCHER_UI
ui/MainWindow.ui
ui/setupwizard/PasteWizardPage.ui
ui/setupwizard/AutoJavaWizardPage.ui
ui/setupwizard/LoginWizardPage.ui
ui/setupwizard/ThemeWizardPage.ui
ui/pages/global/AccountListPage.ui
ui/pages/global/JavaPage.ui
ui/pages/global/LauncherPage.ui
ui/pages/global/APIPage.ui
ui/pages/global/ProxyPage.ui
ui/pages/global/MinecraftPage.ui
ui/pages/global/ExternalToolsPage.ui
ui/pages/instance/ExternalResourcesPage.ui
ui/pages/instance/NotesPage.ui
@ -1172,6 +1024,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/instance/ServersPage.ui
ui/pages/instance/GameOptionsPage.ui
ui/pages/instance/OtherLogsPage.ui
ui/pages/instance/InstanceSettingsPage.ui
ui/pages/instance/VersionPage.ui
ui/pages/instance/ManagedPackPage.ui
ui/pages/instance/WorldListPage.ui
@ -1182,20 +1035,15 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/modplatform/ResourcePage.ui
ui/pages/modplatform/flame/FlamePage.ui
ui/pages/modplatform/legacy_ftb/Page.ui
ui/pages/modplatform/import_ftb/ImportFTBPage.ui
ui/pages/modplatform/ImportPage.ui
ui/pages/modplatform/OptionalModDialog.ui
ui/pages/modplatform/modrinth/ModrinthPage.ui
ui/pages/modplatform/technic/TechnicPage.ui
ui/widgets/InstanceCardWidget.ui
ui/widgets/CustomCommands.ui
ui/widgets/EnvironmentVariables.ui
ui/widgets/InfoFrame.ui
ui/widgets/ModFilterWidget.ui
ui/widgets/SubTaskProgressBar.ui
ui/widgets/ThemeCustomizationWidget.ui
ui/widgets/MinecraftSettingsWidget.ui
ui/widgets/JavaSettingsWidget.ui
ui/dialogs/CopyInstanceDialog.ui
ui/dialogs/ProfileSetupDialog.ui
ui/dialogs/ProgressDialog.ui
@ -1203,29 +1051,22 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/NewComponentDialog.ui
ui/dialogs/NewsDialog.ui
ui/dialogs/ProfileSelectDialog.ui
ui/dialogs/SkinUploadDialog.ui
ui/dialogs/ExportInstanceDialog.ui
ui/dialogs/ExportPackDialog.ui
ui/dialogs/ExportToModListDialog.ui
ui/dialogs/ExportMrPackDialog.ui
ui/dialogs/IconPickerDialog.ui
ui/dialogs/ImportResourceDialog.ui
ui/dialogs/MSALoginDialog.ui
ui/dialogs/OfflineLoginDialog.ui
ui/dialogs/AboutDialog.ui
ui/dialogs/LoginDialog.ui
ui/dialogs/EditAccountDialog.ui
ui/dialogs/ReviewMessageBox.ui
ui/dialogs/ScrollMessageBox.ui
ui/dialogs/BlockedModsDialog.ui
ui/dialogs/ChooseProviderDialog.ui
ui/dialogs/skins/SkinManageDialog.ui
)
qt_wrap_ui(PRISM_UPDATE_UI
ui/dialogs/UpdateAvailableDialog.ui
)
if (NOT Apple)
set (LAUNCHER_UI ${LAUNCHER_UI} ${PRISM_UPDATE_UI})
endif()
qt_add_resources(LAUNCHER_RESOURCES
resources/backgrounds/backgrounds.qrc
resources/multimc/multimc.qrc
@ -1242,27 +1083,14 @@ qt_add_resources(LAUNCHER_RESOURCES
../${Launcher_Branding_LogoQRC}
)
qt_wrap_ui(PRISMUPDATER_UI
updater/prismupdater/SelectReleaseDialog.ui
ui/widgets/SubTaskProgressBar.ui
ui/dialogs/ProgressDialog.ui
)
######## Windows resource files ########
if(WIN32)
set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
endif()
include(CompilerWarnings)
# Add executable
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
set_project_warnings(Launcher_logic
"${Launcher_MSVC_WARNINGS}"
"${Launcher_CLANG_WARNINGS}"
"${Launcher_GCC_WARNINGS}")
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
target_link_libraries(Launcher_logic
systeminfo
Launcher_murmur2
@ -1271,6 +1099,7 @@ target_link_libraries(Launcher_logic
tomlplusplus::tomlplusplus
qdcss
BuildConfig
Katabasis
Qt${QT_VERSION_MAJOR}::Widgets
ghcFilesystem::ghc_filesystem
)
@ -1288,8 +1117,6 @@ target_link_libraries(Launcher_logic
Qt${QT_VERSION_MAJOR}::Concurrent
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::NetworkAuth
${Launcher_QT_DBUS}
${Launcher_QT_LIBS}
)
target_link_libraries(Launcher_logic
@ -1298,31 +1125,21 @@ target_link_libraries(Launcher_logic
LocalPeer
Launcher_rainbow
)
if (TARGET ${Launcher_QT_DBUS})
add_compile_definitions(WITH_QTDBUS)
endif()
if(APPLE)
set(CMAKE_MACOSX_RPATH 1)
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/")
if(Launcher_ENABLE_UPDATER)
file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256})
file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle)
find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
add_compile_definitions(SPARKLE_ENABLED)
endif()
file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256})
file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle)
find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
target_link_libraries(Launcher_logic
"-framework AppKit"
"-framework Carbon"
"-framework Foundation"
"-framework ApplicationServices"
)
if(Launcher_ENABLE_UPDATER)
target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
endif()
target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
endif()
target_link_libraries(Launcher_logic)
@ -1349,50 +1166,8 @@ install(TARGETS ${Launcher_Name}
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
)
if(Launcher_BUILD_UPDATER)
# Updater
add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI})
target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(prism_updater_logic
QuaZip::QuaZip
${ZLIB_LIBRARIES}
systeminfo
BuildConfig
ghcFilesystem::ghc_filesystem
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Network
${Launcher_QT_LIBS}
cmark::cmark
)
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
target_sources("${Launcher_Name}_updater" PRIVATE updater/prismupdater/updater.exe.manifest)
target_link_libraries("${Launcher_Name}_updater" prism_updater_logic)
if(DEFINED Launcher_APP_BINARY_NAME)
set_target_properties("${Launcher_Name}_updater" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_updater")
endif()
if(DEFINED Launcher_BINARY_RPATH)
SET_TARGET_PROPERTIES("${Launcher_Name}_updater" PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}")
endif()
install(TARGETS "${Launcher_Name}_updater"
BUNDLE DESTINATION "." COMPONENT Runtime
LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime
RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
)
endif()
if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER))
# File link
if(WIN32)
add_library(filelink_logic STATIC ${LINKEXE_SOURCES})
set_project_warnings(filelink_logic
"${Launcher_MSVC_WARNINGS}"
"${Launcher_CLANG_WARNINGS}"
"${Launcher_GCC_WARNINGS}")
target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(filelink_logic
systeminfo
@ -1405,7 +1180,7 @@ if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER))
${Launcher_QT_LIBS}
)
add_executable("${Launcher_Name}_filelink" WIN32 filelink/filelink_main.cpp)
add_executable("${Launcher_Name}_filelink" WIN32 filelink/main.cpp)
target_sources("${Launcher_Name}_filelink" PRIVATE filelink/filelink.exe.manifest)
@ -1426,7 +1201,7 @@ if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER))
)
endif()
if (UNIX AND APPLE AND Launcher_ENABLE_UPDATER)
if (UNIX AND APPLE)
# Add Sparkle updater
# It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of
# the framework aren't installed
@ -1528,6 +1303,7 @@ if(INSTALL_BUNDLE STREQUAL "full")
CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
PATTERN "*qopensslbackend*" EXCLUDE
PATTERN "*qcertonlybackend*" EXCLUDE
)
install(
@ -1538,78 +1314,10 @@ if(INSTALL_BUNDLE STREQUAL "full")
REGEX "dd\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
PATTERN "*qopensslbackend*" EXCLUDE
PATTERN "*qcertonlybackend*" EXCLUDE
)
endif()
# Wayland support
if(EXISTS "${QT_PLUGINS_DIR}/wayland-graphics-integration-client")
install(
DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-client"
CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
)
install(
DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-client"
CONFIGURATIONS Release MinSizeRel
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "dd\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
)
endif()
if(EXISTS "${QT_PLUGINS_DIR}/wayland-graphics-integration-server")
install(
DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-server"
CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
)
install(
DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-server"
CONFIGURATIONS Release MinSizeRel
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "dd\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
)
endif()
if(EXISTS "${QT_PLUGINS_DIR}/wayland-decoration-client")
install(
DIRECTORY "${QT_PLUGINS_DIR}/wayland-decoration-client"
CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
)
install(
DIRECTORY "${QT_PLUGINS_DIR}/wayland-decoration-client"
CONFIGURATIONS Release MinSizeRel
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "dd\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
)
endif()
if(EXISTS "${QT_PLUGINS_DIR}/wayland-shell-integration")
install(
DIRECTORY "${QT_PLUGINS_DIR}/wayland-shell-integration"
CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
)
install(
DIRECTORY "${QT_PLUGINS_DIR}/wayland-shell-integration"
CONFIGURATIONS Release MinSizeRel
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "dd\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
)
endif()
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake"

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -41,7 +41,8 @@
* @file libutil/src/cmdutils.cpp
*/
namespace Commandline {
namespace Commandline
{
// commandline splitter
QStringList splitArgs(QString args)
@ -50,15 +51,19 @@ QStringList splitArgs(QString args)
QString current;
bool escape = false;
QChar inquotes;
for (int i = 0; i < args.length(); i++) {
for (int i = 0; i < args.length(); i++)
{
QChar cchar = args.at(i);
// \ escaped
if (escape) {
if (escape)
{
current += cchar;
escape = false;
// in "quotes"
} else if (!inquotes.isNull()) {
}
else if (!inquotes.isNull())
{
if (cchar == '\\')
escape = true;
else if (cchar == inquotes)
@ -66,13 +71,18 @@ QStringList splitArgs(QString args)
else
current += cchar;
// otherwise
} else {
if (cchar == ' ') {
if (!current.isEmpty()) {
}
else
{
if (cchar == ' ')
{
if (!current.isEmpty())
{
argv << current;
current.clear();
}
} else if (cchar == '"' || cchar == '\'')
}
else if (cchar == '"' || cchar == '\'')
inquotes = cchar;
else
current += cchar;
@ -82,4 +92,4 @@ QStringList splitArgs(QString args)
argv << current;
return argv;
}
} // namespace Commandline
}

View file

@ -25,7 +25,8 @@
* @brief commandline parsing and processing utilities
*/
namespace Commandline {
namespace Commandline
{
/**
* @brief split a string into argv items like a shell would do
@ -33,4 +34,4 @@ namespace Commandline {
* @return a QStringList containing all arguments
*/
QStringList splitArgs(QString args);
} // namespace Commandline
}

View file

@ -12,8 +12,11 @@
#include <QtConcurrent>
DataMigrationTask::DataMigrationTask(const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathMatcher)
: Task(), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
DataMigrationTask::DataMigrationTask(QObject* parent,
const QString& sourcePath,
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);
}
@ -24,7 +27,7 @@ void DataMigrationTask::executeTask()
// 1. Scan
// Check how many files we gotta copy
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
return m_copy(true); // dry run to collect amount of files
});
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
@ -57,7 +60,7 @@ void DataMigrationTask::dryRunFinished()
setProgress(m_copy.totalCopied(), m_toCopy);
setStatus(tr("Copying %1…").arg(shortenedName));
});
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
return m_copy(false); // actually copy now
});
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);

View file

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

View file

@ -1,21 +1,33 @@
#pragma once
template <typename T>
class DefaultVariable {
public:
DefaultVariable(const T& value) { defaultValue = value; }
DefaultVariable<T>& operator=(const T& value)
class DefaultVariable
{
public:
DefaultVariable(const T & value)
{
defaultValue = value;
}
DefaultVariable<T> & operator =(const T & value)
{
currentValue = value;
is_default = currentValue == defaultValue;
is_explicit = true;
return *this;
}
operator const T&() const { return is_default ? defaultValue : currentValue; }
bool isDefault() const { return is_default; }
bool isExplicit() const { return is_explicit; }
private:
operator const T &() const
{
return is_default ? defaultValue : currentValue;
}
bool isDefault() const
{
return is_default;
}
bool isExplicit() const
{
return is_explicit;
}
private:
T currentValue;
T defaultValue;
bool is_default = true;

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 dada513 <dada513@protonmail.com>
*
* This program is free software: you can redistribute it and/or modify
@ -33,37 +33,186 @@
* limitations under the License.
*/
#include "DesktopServices.h"
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QDesktopServices>
#include <QProcess>
#include "FileSystem.h"
#include <QDebug>
/**
* This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing.
*/
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
template <typename T>
bool IndirectOpen(T callable, qint64 *pid_forked = nullptr)
{
auto pid = fork();
if(pid_forked)
{
if(pid > 0)
*pid_forked = pid;
else
*pid_forked = 0;
}
if(pid == -1)
{
qWarning() << "IndirectOpen failed to fork: " << errno;
return false;
}
// child - do the stuff
if(pid == 0)
{
// unset all this garbage so it doesn't get passed to the child process
qunsetenv("LD_PRELOAD");
qunsetenv("LD_LIBRARY_PATH");
qunsetenv("LD_DEBUG");
qunsetenv("QT_PLUGIN_PATH");
qunsetenv("QT_FONTPATH");
// open the URL
auto status = callable();
// detach from the parent process group.
setsid();
// die. now. do not clean up anything, it would just hang forever.
_exit(status ? 0 : 1);
}
else
{
//parent - assume it worked.
int status;
while (waitpid(pid, &status, 0))
{
if(WIFEXITED(status))
{
return WEXITSTATUS(status) == 0;
}
if(WIFSIGNALED(status))
{
return false;
}
}
return true;
}
}
#endif
namespace DesktopServices {
bool openPath(const QFileInfo& path, bool ensureFolderPathExists)
bool openDirectory(const QString &path, bool ensureExists)
{
qDebug() << "Opening path" << path;
if (ensureFolderPathExists) {
FS::ensureFolderPathExists(path);
qDebug() << "Opening directory" << path;
QDir parentPath;
QDir dir(path);
if (!dir.exists())
{
parentPath.mkpath(dir.absolutePath());
}
return openUrl(QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()));
auto f = [&]()
{
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak())
{
return IndirectOpen(f);
}
else
{
return f();
}
#else
return f();
#endif
}
bool openPath(const QString& path, bool ensureFolderPathExists)
bool openFile(const QString &path)
{
return openPath(QFileInfo(path), ensureFolderPathExists);
qDebug() << "Opening file" << path;
auto f = [&]()
{
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak())
{
return IndirectOpen(f);
}
else
{
return f();
}
#else
return f();
#endif
}
bool run(const QString& application, const QStringList& args, const QString& workingDirectory, qint64* pid)
bool openFile(const QString &application, const QString &path, const QString &workingDirectory, qint64 *pid)
{
qDebug() << "Opening file" << path << "using" << application;
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
if(!isFlatpak())
{
return IndirectOpen([&]()
{
return QProcess::startDetached(application, QStringList() << path, workingDirectory);
}, pid);
}
else
{
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
}
#else
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
#endif
}
bool run(const QString &application, const QStringList &args, const QString &workingDirectory, qint64 *pid)
{
qDebug() << "Running" << application << "with args" << args.join(' ');
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak())
{
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
return IndirectOpen([&]()
{
return QProcess::startDetached(application, args, workingDirectory);
}, pid);
}
else
{
return QProcess::startDetached(application, args, workingDirectory, pid);
}
#else
return QProcess::startDetached(application, args, workingDirectory, pid);
#endif
}
bool openUrl(const QUrl& url)
bool openUrl(const QUrl &url)
{
qDebug() << "Opening URL" << url.toString();
return QDesktopServices::openUrl(url);
auto f = [&]()
{
return QDesktopServices::openUrl(url);
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak())
{
return IndirectOpen(f);
}
else
{
return f();
}
#else
return f();
#endif
}
bool isFlatpak()
@ -75,13 +224,4 @@ bool isFlatpak()
#endif
}
bool isSnap()
{
#ifdef Q_OS_LINUX
return getenv("SNAP");
#else
return false;
#endif
}
} // namespace DesktopServices

View file

@ -1,44 +1,38 @@
#pragma once
#include <QString>
#include <QUrl>
class QFileInfo;
#include <QString>
/**
* This wraps around QDesktopServices and adds workarounds where needed
* Use this instead of QDesktopServices!
*/
namespace DesktopServices {
/**
* Open a path in whatever application is applicable.
* @param ensureFolderPathExists Make sure the path exists
*/
bool openPath(const QFileInfo& path, bool ensureFolderPathExists = false);
namespace DesktopServices
{
/**
* Open a file in whatever application is applicable
*/
bool openFile(const QString &path);
/**
* Open a path in whatever application is applicable.
* @param ensureFolderPathExists Make sure the path exists
*/
bool openPath(const QString& path, bool ensureFolderPathExists = false);
/**
* Open a file in the specified application
*/
bool openFile(const QString &application, const QString &path, const QString & workingDirectory = QString(), qint64 *pid = 0);
/**
* Run an application
*/
bool run(const QString& application, const QStringList& args, const QString& workingDirectory = QString(), qint64* pid = 0);
/**
* Run an application
*/
bool run(const QString &application,const QStringList &args, const QString & workingDirectory = QString(), qint64 *pid = 0);
/**
* Open the URL, most likely in a browser. Maybe.
*/
bool openUrl(const QUrl& url);
/**
* Open a directory
*/
bool openDirectory(const QString &path, bool ensureExists = false);
/**
* Determine whether the launcher is running in a Flatpak environment
*/
bool isFlatpak();
/**
* Open the URL, most likely in a browser. Maybe.
*/
bool openUrl(const QUrl &url);
/**
* Determine whether the launcher is running in a Snap environment
*/
bool isSnap();
} // namespace DesktopServices
bool isFlatpak();
}

View file

@ -1,52 +1,32 @@
// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2024 TheKodeToad <TheKodeToad@proton.me>
*
* 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.
*/
// Licensed under the Apache-2.0 license. See README.md for details.
#pragma once
#include <QDebug>
#include <QString>
#include <QDebug>
#include <exception>
class Exception : public std::exception {
public:
Exception(const QString& message) : std::exception(), m_message(message.toUtf8()) { qCritical() << "Exception:" << message; }
Exception(const Exception& other) : std::exception(), m_message(other.m_message) {}
class Exception : public std::exception
{
public:
Exception(const QString &message) : std::exception(), m_message(message)
{
qCritical() << "Exception:" << message;
}
Exception(const Exception &other)
: std::exception(), m_message(other.cause())
{
}
virtual ~Exception() noexcept {}
const char* what() const noexcept { return m_message.constData(); }
QString cause() const { return QString::fromUtf8(m_message); }
const char *what() const noexcept
{
return m_message.toLatin1().constData();
}
QString cause() const
{
return m_message;
}
private:
QByteArray m_message;
private:
QString m_message;
};

View file

@ -4,24 +4,31 @@
template <typename T>
inline void clamp(T& current, T min, T max)
{
if (current < min) {
if (current < min)
{
current = min;
} else if (current > max) {
}
else if(current > max)
{
current = max;
}
}
// List of numbers from min to max. Next is exponent times bigger than previous.
class ExponentialSeries {
public:
class ExponentialSeries
{
public:
ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2)
{
m_current = m_min = min;
m_max = max;
m_exponent = exponent;
}
void reset() { m_current = m_min; }
void reset()
{
m_current = m_min;
}
unsigned operator()()
{
unsigned retval = m_current;

View file

@ -267,7 +267,10 @@ bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceP
bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
{
return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()));
auto fileName = fileInfo.fileName();
auto path = relPath(fileInfo.absoluteFilePath());
return std::any_of(m_ignoreFiles.cbegin(), m_ignoreFiles.cend(), [fileName](auto iFileName) { return fileName == iFileName; }) ||
m_ignoreFilePaths.covers(path);
}
bool FileIgnoreProxy::filterFile(const QString& fileName) const

View file

@ -45,6 +45,7 @@
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QSaveFile>
#include <QStandardPaths>
#include <QStorageInfo>
#include <QTextStream>
@ -53,7 +54,6 @@
#include <system_error>
#include "DesktopServices.h"
#include "PSaveFile.h"
#include "StringUtils.h"
#if defined Q_OS_WIN32
@ -123,35 +123,26 @@ namespace fs = ghc::filesystem;
#if defined(__MINGW32__)
struct _DUPLICATE_EXTENTS_DATA {
typedef struct _DUPLICATE_EXTENTS_DATA {
HANDLE FileHandle;
LARGE_INTEGER SourceFileOffset;
LARGE_INTEGER TargetFileOffset;
LARGE_INTEGER ByteCount;
};
} DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA;
using DUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA;
using PDUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA*;
struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
WORD Reserved; // Must be 0
DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
DWORD ChecksumChunkSizeInBytes;
DWORD ClusterSizeInBytes;
};
} FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER;
using FSCTL_GET_INTEGRITY_INFORMATION_BUFFER = _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER;
using PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER = _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER*;
struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER {
typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER {
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
WORD Reserved; // Must be 0
DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
};
using FSCTL_SET_INTEGRITY_INFORMATION_BUFFER = _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER;
using PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER = _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER*;
} FSCTL_SET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER;
#endif
@ -191,8 +182,8 @@ void ensureExists(const QDir& dir)
void write(const QString& filename, const QByteArray& data)
{
ensureExists(QFileInfo(filename).dir());
PSaveFile file(filename);
if (!file.open(PSaveFile::WriteOnly)) {
QSaveFile file(filename);
if (!file.open(QSaveFile::WriteOnly)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
}
if (data.size() != file.write(data)) {
@ -203,40 +194,6 @@ void write(const QString& filename, const QByteArray& data)
}
}
void appendSafe(const QString& filename, const QByteArray& data)
{
ensureExists(QFileInfo(filename).dir());
QByteArray buffer;
try {
buffer = read(filename);
} catch (FileSystemException&) {
buffer = QByteArray();
}
buffer.append(data);
PSaveFile file(filename);
if (!file.open(PSaveFile::WriteOnly)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
}
if (buffer.size() != file.write(buffer)) {
throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
}
if (!file.commit()) {
throw FileSystemException("Error while committing data to " + filename + ": " + file.errorString());
}
}
void append(const QString& filename, const QByteArray& data)
{
ensureExists(QFileInfo(filename).dir());
QFile file(filename);
if (!file.open(QFile::Append)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
}
if (data.size() != file.write(data)) {
throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
}
}
QByteArray read(const QString& filename)
{
QFile file(filename);
@ -272,44 +229,15 @@ bool ensureFilePathExists(QString filenamepath)
return success;
}
bool ensureFolderPathExists(const QFileInfo folderPath)
bool ensureFolderPathExists(QString foldernamepath)
{
QFileInfo a(foldernamepath);
QDir dir;
QString ensuredPath = folderPath.filePath();
if (folderPath.exists())
return true;
QString ensuredPath = a.filePath();
bool success = dir.mkpath(ensuredPath);
return success;
}
bool ensureFolderPathExists(const QString folderPathName)
{
return ensureFolderPathExists(QFileInfo(folderPathName));
}
bool copyFileAttributes(QString src, QString dst)
{
#ifdef Q_OS_WIN32
auto attrs = GetFileAttributesW(src.toStdWString().c_str());
if (attrs == INVALID_FILE_ATTRIBUTES)
return false;
return SetFileAttributesW(dst.toStdWString().c_str(), attrs);
#endif
return true;
}
// needs folders to exists
void copyFolderAttributes(QString src, QString dst, QString relative)
{
auto path = PathCombine(src, relative);
QDir dsrc(src);
while ((path = QFileInfo(path).path()).length() >= src.length()) {
auto dst_path = PathCombine(dst, dsrc.relativeFilePath(path));
copyFileAttributes(path, dst_path);
}
}
/**
* @brief Copies a directory and it's contents from src to dest
* @param offset subdirectory form src to copy to dest
@ -337,20 +265,14 @@ bool copy::operator()(const QString& offset, bool dryRun)
if (!m_followSymlinks)
opt |= copy_opts::copy_symlinks;
if (m_overwrite)
opt |= copy_opts::overwrite_existing;
// Function that'll do the actual copying
auto copy_file = [this, dryRun, src, dst, opt, &err](QString src_path, QString relative_dst_path) {
auto copy_file = [&](QString src_path, QString relative_dst_path) {
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
return;
auto dst_path = PathCombine(dst, relative_dst_path);
if (!dryRun) {
ensureFilePathExists(dst_path);
#ifdef Q_OS_WIN32
copyFolderAttributes(src, dst, relative_dst_path);
#endif
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
}
if (err) {
@ -428,7 +350,7 @@ void create_link::make_link_list(const QString& offset)
m_recursive = true;
// Function that'll do the actual linking
auto link_file = [this, dst](QString src_path, QString relative_dst_path) {
auto link_file = [&](QString src_path, QString relative_dst_path) {
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) {
qDebug() << "path" << relative_dst_path << "in black list or not in whitelist";
return;
@ -523,7 +445,7 @@ void create_link::runPrivileged(const QString& offset)
QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric();
connect(&m_linkServer, &QLocalServer::newConnection, this, [this, &gotResults]() {
connect(&m_linkServer, &QLocalServer::newConnection, this, [&]() {
qDebug() << "Client connected, sending out pairs";
// construct block of data to send
QByteArray block;
@ -605,7 +527,7 @@ void create_link::runPrivileged(const QString& offset)
}
ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this);
connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [this, gotResults]() { emit finishedPrivileged(gotResults); });
connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivileged(gotResults); });
connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater);
linkFileProcess->start();
@ -650,19 +572,6 @@ void ExternalLinkFileProcess::runLinkFile()
qDebug() << "Process exited";
}
bool moveByCopy(const QString& source, const QString& dest)
{
if (!copy(source, dest)()) { // copy
qDebug() << "Copy of" << source << "to" << dest << "failed!";
return false;
}
if (!deletePath(source)) { // remove original
qDebug() << "Deletion of" << source << "failed!";
return false;
};
return true;
}
bool move(const QString& source, const QString& dest)
{
std::error_code err;
@ -670,14 +579,13 @@ bool move(const QString& source, const QString& dest)
ensureFilePathExists(dest);
fs::rename(StringUtils::toStdString(source), StringUtils::toStdString(dest), err);
if (err.value() != 0) {
if (moveByCopy(source, dest))
return true;
qDebug() << "Move of" << source << "to" << dest << "failed!";
qWarning() << "Failed to move file:" << QString::fromStdString(err.message()) << QString::number(err.value());
return false;
if (err) {
qWarning() << "Failed to move file:" << QString::fromStdString(err.message());
qDebug() << "Source file:" << source;
qDebug() << "Destination file:" << dest;
}
return true;
return err.value() == 0;
}
bool deletePath(QString path)
@ -818,68 +726,16 @@ QString NormalizePath(QString path)
}
}
static const QString BAD_WIN_CHARS = "<>:\"|?*\r\n";
static const QString BAD_NTFS_CHARS = "<>:\"|?*";
static const QString BAD_HFS_CHARS = ":";
static const QString BAD_FILENAME_CHARS = BAD_WIN_CHARS + "\\/";
QString badFilenameChars = "\"\\/?<>:;*|!+\r\n";
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
{
for (int i = 0; i < string.length(); i++)
if (string.at(i) < ' ' || BAD_FILENAME_CHARS.contains(string.at(i)))
for (int i = 0; i < string.length(); i++) {
if (badFilenameChars.contains(string[i])) {
string[i] = replaceWith;
return string;
}
QString RemoveInvalidPathChars(QString path, QChar replaceWith)
{
QString invalidChars;
#ifdef Q_OS_WIN
invalidChars = BAD_WIN_CHARS;
#endif
// the null character is ignored in this check as it was not a problem until now
switch (statFS(path).fsType) {
case FilesystemType::FAT: // similar to NTFS
/* fallthrough */
case FilesystemType::NTFS:
/* fallthrough */
case FilesystemType::REFS: // similar to NTFS(should be available only on windows)
invalidChars += BAD_NTFS_CHARS;
break;
// case FilesystemType::EXT:
// case FilesystemType::EXT_2_OLD:
// case FilesystemType::EXT_2_3_4:
// case FilesystemType::XFS:
// case FilesystemType::BTRFS:
// case FilesystemType::NFS:
// case FilesystemType::ZFS:
case FilesystemType::APFS:
/* fallthrough */
case FilesystemType::HFS:
/* fallthrough */
case FilesystemType::HFSPLUS:
/* fallthrough */
case FilesystemType::HFSX:
invalidChars += BAD_HFS_CHARS;
break;
// case FilesystemType::FUSEBLK:
// case FilesystemType::F2FS:
// case FilesystemType::UNKNOWN:
default:
break;
}
if (invalidChars.size() != 0) {
for (int i = 0; i < path.length(); i++) {
if (path.at(i) < ' ' || invalidChars.contains(path.at(i))) {
path[i] = replaceWith;
}
}
}
return path;
return string;
}
QString DirNameFromString(QString string, QString inDir)
@ -921,49 +777,10 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (destination.isEmpty()) {
destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name));
}
if (!ensureFilePathExists(destination)) {
qWarning() << "Destination path can't be created!";
return false;
}
#if defined(Q_OS_MACOS)
// Create the Application
QDir applicationDirectory =
QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/" + BuildConfig.LAUNCHER_NAME + " Instances/";
destination += ".command";
if (!applicationDirectory.mkpath(".")) {
qWarning() << "Couldn't create application directory";
return false;
}
QDir application = applicationDirectory.path() + "/" + name + ".app/";
if (application.exists()) {
qWarning() << "Application already exists!";
return false;
}
if (!application.mkpath(".")) {
qWarning() << "Couldn't create application";
return false;
}
QDir content = application.path() + "/Contents/";
QDir resources = content.path() + "/Resources/";
QDir binaryDir = content.path() + "/MacOS/";
QFile info = content.path() + "/Info.plist";
if (!(content.mkpath(".") && resources.mkpath(".") && binaryDir.mkpath("."))) {
qWarning() << "Couldn't create directories within application";
return false;
}
info.open(QIODevice::WriteOnly | QIODevice::Text);
QFile(icon).rename(resources.path() + "/Icon.icns");
// Create the Command file
QString exec = binaryDir.path() + "/Run.command";
QFile f(exec);
QFile f(destination);
f.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream stream(&f);
@ -971,7 +788,8 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (!args.empty())
argstring = " \"" + args.join("\" \"") + "\"";
stream << "#!/bin/bash" << "\n";
stream << "#!/bin/bash"
<< "\n";
stream << "\"" << target << "\" " << argstring << "\n";
stream.flush();
@ -979,30 +797,6 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
// Generate the Info.plist
QTextStream infoStream(&info);
infoStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \n"
"<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
"\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
"<plist version=\"1.0\">\n"
"<dict>\n"
" <key>CFBundleExecutable</key>\n"
" <string>Run.command</string>\n" // The path to the executable
" <key>CFBundleIconFile</key>\n"
" <string>Icon.icns</string>\n"
" <key>CFBundleName</key>\n"
" <string>"
<< name
<< "</string>\n" // Name of the application
" <key>CFBundlePackageType</key>\n"
" <string>APPL</string>\n"
" <key>CFBundleShortVersionString</key>\n"
" <string>1.0</string>\n"
" <key>CFBundleVersion</key>\n"
" <string>1.0</string>\n"
"</dict>\n"
"</plist>";
return true;
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
if (!destination.endsWith(".desktop")) // in case of isFlatpak destination is already populated
@ -1015,9 +809,10 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (!args.empty())
argstring = " '" + args.join("' '") + "'";
stream << "[Desktop Entry]" << "\n";
stream << "Type=Application" << "\n";
stream << "Categories=Game;ActionGame;AdventureGame;Simulation" << "\n";
stream << "[Desktop Entry]"
<< "\n";
stream << "Type=Application"
<< "\n";
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
stream << "Name=" << name.toLocal8Bit() << "\n";
if (!icon.isEmpty()) {
@ -1295,7 +1090,7 @@ bool clone::operator()(const QString& offset, bool dryRun)
std::error_code err;
// Function that'll do the actual cloneing
auto cloneFile = [this, dryRun, dst, &err](QString src_path, QString relative_dst_path) {
auto cloneFile = [&](QString src_path, QString relative_dst_path) {
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
return;
@ -1654,70 +1449,4 @@ uintmax_t hardLinkCount(const QString& path)
return count;
}
#ifdef Q_OS_WIN
// returns 8.3 file format from long path
QString shortPathName(const QString& file)
{
auto input = file.toStdWString();
std::wstring output;
long length = GetShortPathNameW(input.c_str(), NULL, 0);
if (length == 0)
return {};
// NOTE: this resizing might seem weird...
// when GetShortPathNameW fails, it returns length including null character
// when it succeeds, it returns length excluding null character
// See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx
output.resize(length);
if (GetShortPathNameW(input.c_str(), (LPWSTR)output.c_str(), length) == 0)
return {};
output.resize(length - 1);
QString ret = QString::fromStdWString(output);
return ret;
}
// if the string survives roundtrip through local 8bit encoding...
bool fitsInLocal8bit(const QString& string)
{
return string == QString::fromLocal8Bit(string.toLocal8Bit());
}
QString getPathNameInLocal8bit(const QString& file)
{
if (!fitsInLocal8bit(file)) {
auto path = shortPathName(file);
if (!path.isEmpty()) {
return path;
}
// in case shortPathName fails just return the path as is
}
return file;
}
#endif
QString getUniqueResourceName(const QString& filePath)
{
auto newFileName = filePath;
if (!newFileName.endsWith(".disabled")) {
return newFileName; // prioritize enabled mods
}
newFileName.chop(9);
if (!QFile::exists(newFileName)) {
return filePath;
}
QFileInfo fileInfo(filePath);
auto baseName = fileInfo.completeBaseName();
auto path = fileInfo.absolutePath();
int counter = 1;
do {
if (counter == 1) {
newFileName = FS::PathCombine(path, baseName + ".duplicate");
} else {
newFileName = FS::PathCombine(path, baseName + ".duplicate" + QString::number(counter));
}
counter++;
} while (QFile::exists(newFileName));
return newFileName;
}
} // namespace FS

View file

@ -43,10 +43,10 @@
#include <system_error>
#include <QDir>
#include <QPair>
#include <QFlags>
#include <QLocalServer>
#include <QObject>
#include <QPair>
#include <QThread>
namespace FS {
@ -62,17 +62,7 @@ class FileSystemException : public ::Exception {
void write(const QString& filename, const QByteArray& data);
/**
* append data to a file safely
*/
void appendSafe(const QString& filename, const QByteArray& data);
/**
* append data to a file
*/
void append(const QString& filename, const QByteArray& data);
/**
* read data from a file safely
* read data from a file safely\
*/
QByteArray read(const QString& filename);
@ -91,13 +81,7 @@ bool ensureFilePathExists(QString filenamepath);
* Creates all the folders in a path for the specified path
* last segment of the path is treated as a folder name and is created!
*/
bool ensureFolderPathExists(const QFileInfo folderPath);
/**
* Creates all the folders in a path for the specified path
* last segment of the path is treated as a folder name and is created!
*/
bool ensureFolderPathExists(const QString folderPathName);
bool ensureFolderPathExists(QString filenamepath);
/**
* @brief Copies a directory and it's contents from src to dest
@ -125,16 +109,11 @@ class copy : public QObject {
m_whitelist = whitelist;
return *this;
}
copy& overwrite(const bool overwrite)
{
m_overwrite = overwrite;
return *this;
}
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
qsizetype totalCopied() { return m_copied; }
qsizetype totalFailed() { return m_failedPaths.length(); }
int totalCopied() { return m_copied; }
int totalFailed() { return m_failedPaths.length(); }
QStringList failed() { return m_failedPaths; }
signals:
@ -149,10 +128,9 @@ class copy : public QObject {
bool m_followSymlinks = true;
const IPathMatcher* m_matcher = nullptr;
bool m_whitelist = false;
bool m_overwrite = false;
QDir m_src;
QDir m_dst;
qsizetype m_copied;
int m_copied;
QStringList m_failedPaths;
};
@ -240,7 +218,6 @@ class create_link : public QObject {
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
int totalLinked() { return m_linked; }
int totalToLink() { return static_cast<int>(m_links_to_make.size()); }
void runPrivileged() { runPrivileged(QString()); }
void runPrivileged(const QString& offset);
@ -343,8 +320,6 @@ QString NormalizePath(QString path);
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith = '-');
QString RemoveInvalidPathChars(QString string, QChar replaceWith = '-');
QString DirNameFromString(QString string, QString inDir = ".");
/// Checks if the a given Path contains "!"
@ -379,7 +354,6 @@ enum class FilesystemType {
HFSX,
FUSEBLK,
F2FS,
BCACHEFS,
UNKNOWN
};
@ -391,25 +365,25 @@ enum class FilesystemType {
* QMap is ordered
*
*/
static const QMap<FilesystemType, QStringList> s_filesystem_type_names = { { FilesystemType::FAT, { "FAT" } },
{ FilesystemType::NTFS, { "NTFS" } },
{ FilesystemType::REFS, { "REFS" } },
{ FilesystemType::EXT_2_OLD, { "EXT_2_OLD", "EXT2_OLD" } },
{ FilesystemType::EXT_2_3_4,
{ "EXT2/3/4", "EXT_2_3_4", "EXT2", "EXT3", "EXT4" } },
{ FilesystemType::EXT, { "EXT" } },
{ FilesystemType::XFS, { "XFS" } },
{ FilesystemType::BTRFS, { "BTRFS" } },
{ FilesystemType::NFS, { "NFS" } },
{ FilesystemType::ZFS, { "ZFS" } },
{ FilesystemType::APFS, { "APFS" } },
{ FilesystemType::HFS, { "HFS" } },
{ FilesystemType::HFSPLUS, { "HFSPLUS" } },
{ FilesystemType::HFSX, { "HFSX" } },
{ FilesystemType::FUSEBLK, { "FUSEBLK" } },
{ FilesystemType::F2FS, { "F2FS" } },
{ FilesystemType::BCACHEFS, { "BCACHEFS" } },
{ FilesystemType::UNKNOWN, { "UNKNOWN" } } };
static const QMap<FilesystemType, QStringList> s_filesystem_type_names = {
{FilesystemType::FAT, { "FAT" }},
{FilesystemType::NTFS, { "NTFS" }},
{FilesystemType::REFS, { "REFS" }},
{FilesystemType::EXT_2_OLD, { "EXT_2_OLD", "EXT2_OLD" }},
{FilesystemType::EXT_2_3_4, { "EXT2/3/4", "EXT_2_3_4", "EXT2", "EXT3", "EXT4" }},
{FilesystemType::EXT, { "EXT" }},
{FilesystemType::XFS, { "XFS" }},
{FilesystemType::BTRFS, { "BTRFS" }},
{FilesystemType::NFS, { "NFS" }},
{FilesystemType::ZFS, { "ZFS" }},
{FilesystemType::APFS, { "APFS" }},
{FilesystemType::HFS, { "HFS" }},
{FilesystemType::HFSPLUS, { "HFSPLUS" }},
{FilesystemType::HFSX, { "HFSX" }},
{FilesystemType::FUSEBLK, { "FUSEBLK" }},
{FilesystemType::F2FS, { "F2FS" }},
{FilesystemType::UNKNOWN, { "UNKNOWN" }}
};
/**
* @brief Get the string name of Filesystem enum object
@ -461,7 +435,7 @@ QString nearestExistentAncestor(const QString& path);
FilesystemInfo statFS(const QString& path);
static const QList<FilesystemType> s_clone_filesystems = { FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS,
FilesystemType::XFS, FilesystemType::REFS, FilesystemType::BCACHEFS };
FilesystemType::XFS, FilesystemType::REFS };
/**
* @brief if the Filesystem is reflink/clone capable
@ -501,8 +475,8 @@ class clone : public QObject {
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
qsizetype totalCloned() { return m_cloned; }
qsizetype totalFailed() { return m_failedClones.length(); }
int totalCloned() { return m_cloned; }
int totalFailed() { return m_failedClones.length(); }
QList<QPair<QString, QString>> failed() { return m_failedClones; }
@ -518,7 +492,7 @@ class clone : public QObject {
bool m_whitelist = false;
QDir m_src;
QDir m_dst;
qsizetype m_cloned;
int m_cloned;
QList<QPair<QString, QString>> m_failedClones;
};
@ -556,10 +530,4 @@ bool canLink(const QString& src, const QString& dst);
uintmax_t hardLinkCount(const QString& path);
#ifdef Q_OS_WIN
QString getPathNameInLocal8bit(const QString& file);
#endif
QString getUniqueResourceName(const QString& filePath);
} // namespace FS

View file

@ -1,37 +1,31 @@
#include "Filter.h"
ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern) {}
Filter::~Filter(){}
ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern){}
ContainsFilter::~ContainsFilter(){}
bool ContainsFilter::accepts(const QString& value)
{
return value.contains(pattern);
}
ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern) {}
ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern){}
ExactFilter::~ExactFilter(){}
bool ExactFilter::accepts(const QString& value)
{
return value == pattern;
}
ExactIfPresentFilter::ExactIfPresentFilter(const QString& pattern) : pattern(pattern) {}
bool ExactIfPresentFilter::accepts(const QString& value)
{
return value.isEmpty() || value == pattern;
}
RegexpFilter::RegexpFilter(const QString& regexp, bool invert) : invert(invert)
RegexpFilter::RegexpFilter(const QString& regexp, bool invert)
:invert(invert)
{
pattern.setPattern(regexp);
pattern.optimize();
}
RegexpFilter::~RegexpFilter(){}
bool RegexpFilter::accepts(const QString& value)
{
auto match = pattern.match(value);
bool matched = match.hasMatch();
return invert ? (!matched) : (matched);
}
ExactListFilter::ExactListFilter(const QStringList& pattern) : m_pattern(pattern) {}
bool ExactListFilter::accepts(const QString& value)
{
return m_pattern.isEmpty() || m_pattern.contains(value);
}

View file

@ -1,61 +1,42 @@
#pragma once
#include <QRegularExpression>
#include <QString>
#include <QRegularExpression>
class Filter {
public:
virtual ~Filter() = default;
virtual bool accepts(const QString& value) = 0;
class Filter
{
public:
virtual ~Filter();
virtual bool accepts(const QString & value) = 0;
};
class ContainsFilter : public Filter {
public:
ContainsFilter(const QString& pattern);
virtual ~ContainsFilter() = default;
bool accepts(const QString& value) override;
private:
class ContainsFilter: public Filter
{
public:
ContainsFilter(const QString &pattern);
virtual ~ContainsFilter();
bool accepts(const QString & value) override;
private:
QString pattern;
};
class ExactFilter : public Filter {
public:
ExactFilter(const QString& pattern);
virtual ~ExactFilter() = default;
bool accepts(const QString& value) override;
private:
class ExactFilter: public Filter
{
public:
ExactFilter(const QString &pattern);
virtual ~ExactFilter();
bool accepts(const QString & value) override;
private:
QString pattern;
};
class ExactIfPresentFilter : public Filter {
public:
ExactIfPresentFilter(const QString& pattern);
virtual ~ExactIfPresentFilter() override = default;
bool accepts(const QString& value) override;
private:
QString pattern;
};
class RegexpFilter : public Filter {
public:
RegexpFilter(const QString& regexp, bool invert);
virtual ~RegexpFilter() = default;
bool accepts(const QString& value) override;
private:
class RegexpFilter: public Filter
{
public:
RegexpFilter(const QString &regexp, bool invert);
virtual ~RegexpFilter();
bool accepts(const QString & value) override;
private:
QRegularExpression pattern;
bool invert = false;
};
class ExactListFilter : public Filter {
public:
ExactListFilter(const QStringList& pattern = {});
virtual ~ExactListFilter() = default;
bool accepts(const QString& value) override;
private:
QStringList m_pattern;
};

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -37,9 +37,10 @@
#include <zlib.h>
#include <QByteArray>
bool GZip::unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes)
bool GZip::unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes)
{
if (compressedBytes.size() == 0) {
if (compressedBytes.size() == 0)
{
uncompressedBytes = compressedBytes;
return true;
}
@ -50,37 +51,42 @@ bool GZip::unzip(const QByteArray& compressedBytes, QByteArray& uncompressedByte
z_stream strm;
memset(&strm, 0, sizeof(strm));
strm.next_in = (Bytef*)compressedBytes.data();
strm.next_in = (Bytef *)compressedBytes.data();
strm.avail_in = compressedBytes.size();
bool done = false;
if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK) {
if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK)
{
return false;
}
int err = Z_OK;
while (!done) {
while (!done)
{
// If our output buffer is too small
if (strm.total_out >= uncompLength) {
if (strm.total_out >= uncompLength)
{
uncompressedBytes.resize(uncompLength * 2);
uncompLength *= 2;
}
strm.next_out = reinterpret_cast<Bytef*>((uncompressedBytes.data() + strm.total_out));
strm.next_out = reinterpret_cast<Bytef *>((uncompressedBytes.data() + strm.total_out));
strm.avail_out = uncompLength - strm.total_out;
// Inflate another chunk.
err = inflate(&strm, Z_SYNC_FLUSH);
if (err == Z_STREAM_END)
done = true;
else if (err != Z_OK) {
else if (err != Z_OK)
{
break;
}
}
if (inflateEnd(&strm) != Z_OK || !done) {
if (inflateEnd(&strm) != Z_OK || !done)
{
return false;
}
@ -88,9 +94,10 @@ bool GZip::unzip(const QByteArray& compressedBytes, QByteArray& uncompressedByte
return true;
}
bool GZip::zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes)
bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes)
{
if (uncompressedBytes.size() == 0) {
if (uncompressedBytes.size() == 0)
{
compressedBytes = uncompressedBytes;
return true;
}
@ -102,7 +109,8 @@ bool GZip::zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes)
z_stream zs;
memset(&zs, 0, sizeof(zs));
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK) {
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK)
{
return false;
}
@ -114,9 +122,11 @@ bool GZip::zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes)
unsigned offset = 0;
unsigned temp = 0;
do {
do
{
auto remaining = compressedBytes.size() - offset;
if (remaining < 1) {
if(remaining < 1)
{
compressedBytes.resize(compressedBytes.size() * 2);
}
zs.next_out = reinterpret_cast<Bytef*>((compressedBytes.data() + offset));
@ -127,11 +137,13 @@ bool GZip::zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes)
compressedBytes.resize(offset);
if (deflateEnd(&zs) != Z_OK) {
if (deflateEnd(&zs) != Z_OK)
{
return false;
}
if (ret != Z_STREAM_END) {
if (ret != Z_STREAM_END)
{
return false;
}
return true;

View file

@ -1,8 +1,10 @@
#pragma once
#include <QByteArray>
class GZip {
public:
static bool unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes);
static bool zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes);
class GZip
{
public:
static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes);
static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes);
};

View file

@ -6,10 +6,17 @@
bool InstanceCopyPrefs::allTrue() const
{
return copySaves && keepPlaytime && copyGameOptions && copyResourcePacks && copyShaderPacks && copyServers && copyMods &&
copyScreenshots;
return copySaves &&
keepPlaytime &&
copyGameOptions &&
copyResourcePacks &&
copyShaderPacks &&
copyServers &&
copyMods &&
copyScreenshots;
}
// Returns a single RegEx string of the selected folders/files to filter out (ex: ".minecraft/saves|.minecraft/server.dat")
QString InstanceCopyPrefs::getSelectedFiltersAsRegex() const
{
@ -19,30 +26,25 @@ QString InstanceCopyPrefs::getSelectedFiltersAsRegex(const QStringList& addition
{
QStringList filters;
if (!copySaves)
if(!copySaves)
filters << "saves";
if (!copyGameOptions)
if(!copyGameOptions)
filters << "options.txt";
if (!copyResourcePacks)
filters << "resourcepacks"
<< "texturepacks";
if(!copyResourcePacks)
filters << "resourcepacks" << "texturepacks";
if (!copyShaderPacks)
if(!copyShaderPacks)
filters << "shaderpacks";
if (!copyServers)
filters << "servers.dat"
<< "servers.dat_old"
<< "server-resource-packs";
if(!copyServers)
filters << "servers.dat" << "servers.dat_old" << "server-resource-packs";
if (!copyMods)
filters << "coremods"
<< "mods"
<< "config";
if(!copyMods)
filters << "coremods" << "mods" << "config";
if (!copyScreenshots)
if(!copyScreenshots)
filters << "screenshots";
for (auto filter : additionalFilters) {

View file

@ -40,7 +40,7 @@ struct InstanceCopyPrefs {
void enableDontLinkSaves(bool b);
void enableUseClone(bool b);
protected: // data
protected: // data
bool copySaves = true;
bool keepPlaytime = true;
bool copyGameOptions = true;

View file

@ -1,12 +1,10 @@
#include "InstanceCopyTask.h"
#include <QDebug>
#include <QtConcurrentRun>
#include <memory>
#include "FileSystem.h"
#include "NullInstance.h"
#include "pathmatcher/RegexpMatcher.h"
#include "settings/INISettingsObject.h"
#include "tasks/Task.h"
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs)
{
@ -40,50 +38,38 @@ void InstanceCopyTask::executeTask()
{
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
auto copySaves = [&]() {
QFileInfo mcDir(FS::PathCombine(m_stagingPath, "minecraft"));
QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft"));
QString staging_mc_dir;
if (mcDir.exists() && !dotMCDir.exists())
staging_mc_dir = mcDir.filePath();
else
staging_mc_dir = dotMCDir.filePath();
FS::copy savesCopy(FS::PathCombine(m_origInstance->gameRoot(), "saves"), FS::PathCombine(staging_mc_dir, "saves"));
savesCopy.followSymlinks(true);
return savesCopy();
};
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves] {
if (m_useClone) {
FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath);
folderClone.matcher(m_matcher.get());
folderClone(true);
setProgress(0, folderClone.totalCloned());
connect(&folderClone, &FS::clone::fileCloned,
[this](QString src, QString dst) { setProgress(m_progress + 1, m_progressTotal); });
return folderClone();
}
if (m_useLinks || m_useHardLinks) {
std::unique_ptr<FS::copy> savesCopy;
if (m_copySaves) {
QFileInfo mcDir(FS::PathCombine(m_stagingPath, "minecraft"));
QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft"));
QString staging_mc_dir;
if (dotMCDir.exists() && !mcDir.exists())
staging_mc_dir = dotMCDir.filePath();
else
staging_mc_dir = mcDir.filePath();
savesCopy = std::make_unique<FS::copy>(FS::PathCombine(m_origInstance->gameRoot(), "saves"),
FS::PathCombine(staging_mc_dir, "saves"));
savesCopy->followSymlinks(true);
(*savesCopy)(true);
setProgress(0, savesCopy->totalCopied());
connect(savesCopy.get(), &FS::copy::fileCopied, [this](QString src) { setProgress(m_progress + 1, m_progressTotal); });
}
} else if (m_useLinks || m_useHardLinks) {
FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder
folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
folderLink(true);
setProgress(0, m_progressTotal + folderLink.totalToLink());
connect(&folderLink, &FS::create_link::fileLinked,
[this](QString src, QString dst) { setProgress(m_progress + 1, m_progressTotal); });
bool there_were_errors = false;
if (!folderLink()) {
#if defined Q_OS_WIN32
if (!m_useHardLinks) {
setProgress(0, m_progressTotal);
qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
qDebug() << "attempting to run with privelage";
@ -91,7 +77,7 @@ void InstanceCopyTask::executeTask()
QEventLoop loop;
bool got_priv_results = false;
connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&got_priv_results, &loop](bool gotResults) {
connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults) {
if (!gotResults) {
qDebug() << "Privileged run exited without results!";
}
@ -108,11 +94,13 @@ void InstanceCopyTask::executeTask()
}
}
if (savesCopy) {
there_were_errors |= !(*savesCopy)();
if (m_copySaves) {
there_were_errors |= !copySaves();
}
return got_priv_results && !there_were_errors;
} else {
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
}
#else
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
@ -120,19 +108,17 @@ void InstanceCopyTask::executeTask()
return false;
}
if (savesCopy) {
there_were_errors |= !(*savesCopy)();
if (m_copySaves) {
there_were_errors |= !copySaves();
}
return !there_were_errors;
}
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
folderCopy.followSymlinks(false).matcher(m_matcher.get());
} else {
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
folderCopy.followSymlinks(false).matcher(m_matcher.get());
folderCopy(true);
setProgress(0, folderCopy.totalCopied());
connect(&folderCopy, &FS::copy::fileCopied, [this](QString src) { setProgress(m_progress + 1, m_progressTotal); });
return folderCopy();
return folderCopy();
}
});
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
@ -156,8 +142,9 @@ void InstanceCopyTask::copyFinished()
if (!m_keepPlaytime) {
inst->resetTimePlayed();
}
if (m_useLinks) {
if (m_useLinks)
inst->addLinkedInstanceId(m_origInstance->id());
if (m_useLinks) {
auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt"));
QByteArray allowed_symlinks;
@ -169,15 +156,10 @@ void InstanceCopyTask::copyFinished()
allowed_symlinks.append(m_origInstance->gameRoot().toUtf8());
allowed_symlinks.append("\n");
if (allowed_symlinks_file.isSymLink())
FS::deletePath(
allowed_symlinks_file
.filePath()); // we dont want to modify the original. also make sure the resulting file is not itself a link.
FS::deletePath(allowed_symlinks_file
.filePath()); // we dont want to modify the original. also make sure the resulting file is not itself a link.
try {
FS::write(allowed_symlinks_file.filePath(), allowed_symlinks);
} catch (const FS::FileSystemException& e) {
qCritical() << "Failed to write symlink :" << e.cause();
}
FS::write(allowed_symlinks_file.filePath(), allowed_symlinks);
}
emitSucceeded();
@ -188,14 +170,3 @@ void InstanceCopyTask::copyAborted()
emitFailed(tr("Instance folder copy has been aborted."));
return;
}
bool InstanceCopyTask::abort()
{
if (m_copyFutureWatcher.isRunning()) {
m_copyFutureWatcher.cancel();
// NOTE: Here we don't do `emitAborted()` because it will be done when `m_copyFutureWatcher` actually cancels, which may not occur
// immediately.
return true;
}
return false;
}

View file

@ -11,19 +11,19 @@
#include "settings/SettingsObject.h"
#include "tasks/Task.h"
class InstanceCopyTask : public InstanceTask {
class InstanceCopyTask : public InstanceTask
{
Q_OBJECT
public:
public:
explicit InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs);
protected:
protected:
//! Entry point for tasks.
virtual void executeTask() override;
bool abort() override;
void copyFinished();
void copyAborted();
private:
private:
/* data */
InstancePtr m_origInstance;
QFuture<bool> m_copyFuture;

View file

@ -2,7 +2,8 @@
#include <QDebug>
#include <QFile>
#include "FileSystem.h"
InstanceCreationTask::InstanceCreationTask() = default;
void InstanceCreationTask::executeTask()
{
@ -38,29 +39,22 @@ void InstanceCreationTask::executeTask()
// files scheduled to, and we'd better not let the user abort in the middle of it, since it'd
// put the instance in an invalid state.
if (shouldOverride()) {
bool deleteFailed = false;
setAbortable(false);
setStatus(tr("Removing old conflicting files..."));
qDebug() << "Removing old files";
for (const QString& path : m_files_to_remove) {
for (auto path : m_files_to_remove) {
if (!QFile::exists(path))
continue;
qDebug() << "Removing" << path;
if (!QFile::remove(path)) {
qCritical() << "Could not remove" << path;
deleteFailed = true;
qCritical() << "Couldn't remove the old conflicting files.";
emitFailed(tr("Failed to remove old conflicting files."));
return;
}
}
if (deleteFailed) {
emitFailed(tr("Failed to remove old conflicting files."));
return;
}
}
if (!m_abort)
emitSucceeded();
emitSucceeded();
return;
}

View file

@ -6,7 +6,7 @@
class InstanceCreationTask : public InstanceTask {
Q_OBJECT
public:
InstanceCreationTask() = default;
InstanceCreationTask();
virtual ~InstanceCreationTask() = default;
protected:

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
@ -45,22 +45,18 @@
#include "icons/IconList.h"
#include "icons/IconUtils.h"
#include "modplatform/flame/FlameInstanceCreationTask.h"
#include "modplatform/modrinth/ModrinthInstanceCreationTask.h"
#include "modplatform/technic/TechnicPackProcessor.h"
#include "modplatform/modrinth/ModrinthInstanceCreationTask.h"
#include "modplatform/flame/FlameInstanceCreationTask.h"
#include "settings/INISettingsObject.h"
#include "tasks/Task.h"
#include "net/ApiDownload.h"
#include <QtConcurrentRun>
#include <algorithm>
#include <memory>
#include <quazip/quazipdir.h>
InstanceImportTask::InstanceImportTask(const QUrl& sourceUrl, QWidget* parent, QMap<QString, QString>&& extra_info)
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent, QMap<QString, QString>&& extra_info)
: m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent)
{}
@ -69,11 +65,16 @@ bool InstanceImportTask::abort()
if (!canAbort())
return false;
bool wasAborted = false;
if (m_task)
wasAborted = m_task->abort();
Task::abort();
return wasAborted;
if (m_filesNetJob)
m_filesNetJob->abort();
if (m_extractFuture.isRunning()) {
// NOTE: The tasks created by QtConcurrent::run() can't actually get cancelled,
// but we can use this call to check the state when the extraction finishes.
m_extractFuture.cancel();
m_extractFuture.waitForFinished();
}
return Task::abort();
}
void InstanceImportTask::executeTask()
@ -85,166 +86,161 @@ void InstanceImportTask::executeTask()
processZipPack();
} else {
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
m_downloadRequired = true;
downloadFromUrl();
const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path());
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
entry->setStale(true);
m_archivePath = entry->getFullPath();
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propogateStepProgress);
connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
m_filesNetJob->start();
}
}
void InstanceImportTask::downloadFromUrl()
void InstanceImportTask::downloadSucceeded()
{
const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path());
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
entry->setStale(true);
m_archivePath = entry->getFullPath();
auto filesNetJob = makeShared<NetJob>(tr("Modpack download"), APPLICATION->network());
filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
connect(filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::processZipPack);
connect(filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::setProgress);
connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed);
connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted);
m_task.reset(filesNetJob);
filesNetJob->start();
processZipPack();
m_filesNetJob.reset();
}
QString InstanceImportTask::getRootFromZip(QuaZip* zip, const QString& root)
void InstanceImportTask::downloadFailed(QString reason)
{
if (!isRunning()) {
return {};
}
QuaZipDir rootDir(zip, root);
for (auto&& fileName : rootDir.entryList(QDir::Files)) {
setDetails(fileName);
if (fileName == "instance.cfg") {
qDebug() << "MultiMC:" << true;
m_modpackType = ModpackType::MultiMC;
return root;
}
if (fileName == "manifest.json") {
qDebug() << "Flame:" << true;
m_modpackType = ModpackType::Flame;
return root;
}
emitFailed(reason);
m_filesNetJob.reset();
}
QCoreApplication::processEvents();
}
void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
{
setProgress(current, total);
}
// Recurse the search to non-ignored subfolders
for (auto&& fileName : rootDir.entryList(QDir::Dirs)) {
if ("overrides/" == fileName)
continue;
QString result = getRootFromZip(zip, root + fileName);
if (!result.isEmpty())
return result;
}
return {};
void InstanceImportTask::downloadAborted()
{
emitAborted();
m_filesNetJob.reset();
}
void InstanceImportTask::processZipPack()
{
setStatus(tr("Attempting to determine instance type"));
setStatus(tr("Extracting modpack"));
QDir extractDir(m_stagingPath);
qDebug() << "Attempting to create instance from" << m_archivePath;
// open the zip and find relevant files in it
auto packZip = std::make_shared<QuaZip>(m_archivePath);
if (!packZip->open(QuaZip::mdUnzip)) {
m_packZip.reset(new QuaZip(m_archivePath));
if (!m_packZip->open(QuaZip::mdUnzip))
{
emitFailed(tr("Unable to open supplied modpack zip file."));
return;
}
QuaZipDir packZipDir(packZip.get());
qDebug() << "Attempting to determine instance type";
QuaZipDir packZipDir(m_packZip.get());
// https://docs.modrinth.com/docs/modpacks/format_definition/#storage
bool modrinthFound = packZipDir.exists("/modrinth.index.json");
bool technicFound = packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json");
QString root;
// NOTE: Prioritize modpack platforms that aren't searched for recursively.
// Especially Flame has a very common filename for its manifest, which may appear inside overrides for example
// https://docs.modrinth.com/docs/modpacks/format_definition/#storage
if (packZipDir.exists("/modrinth.index.json")) {
if(modrinthFound)
{
// process as Modrinth pack
qDebug() << "Modrinth:" << true;
qDebug() << "Modrinth:" << modrinthFound;
m_modpackType = ModpackType::Modrinth;
} else if (packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json")) {
// process as Technic pack
qDebug() << "Technic:" << true;
extractDir.mkpath("minecraft");
extractDir.cd("minecraft");
m_modpackType = ModpackType::Technic;
} else {
root = getRootFromZip(packZip.get());
setDetails("");
}
if (m_modpackType == ModpackType::Unknown) {
else if (technicFound)
{
// process as Technic pack
qDebug() << "Technic:" << technicFound;
extractDir.mkpath(".minecraft");
extractDir.cd(".minecraft");
m_modpackType = ModpackType::Technic;
}
else
{
QStringList paths_to_ignore { "overrides/" };
if (QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg", paths_to_ignore); !mmcRoot.isNull()) {
// process as MultiMC instance/pack
qDebug() << "MultiMC:" << mmcRoot;
root = mmcRoot;
m_modpackType = ModpackType::MultiMC;
} else if (QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json", paths_to_ignore); !flameRoot.isNull()) {
// process as Flame pack
qDebug() << "Flame:" << flameRoot;
root = flameRoot;
m_modpackType = ModpackType::Flame;
}
}
if(m_modpackType == ModpackType::Unknown)
{
emitFailed(tr("Archive does not contain a recognized modpack type."));
return;
}
setStatus(tr("Extracting modpack"));
// make sure we extract just the pack
auto zipTask = makeShared<MMCZip::ExtractZipTask>(packZip, extractDir, root);
auto progressStep = std::make_shared<TaskStepProgress>();
connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
progressStep->state = TaskStepState::Succeeded;
stepProgress(*progressStep);
});
connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished, Qt::QueuedConnection);
connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
progressStep->state = TaskStepState::Failed;
stepProgress(*progressStep);
emitFailed(reason);
});
connect(zipTask.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
progressStep->update(current, total);
stepProgress(*progressStep);
});
connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) {
progressStep->status = status;
stepProgress(*progressStep);
});
m_task.reset(zipTask);
zipTask->start();
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath());
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished);
m_extractFutureWatcher.setFuture(m_extractFuture);
}
void InstanceImportTask::extractFinished()
{
setAbortable(false);
m_packZip.reset();
if (m_extractFuture.isCanceled())
return;
if (!m_extractFuture.result().has_value()) {
emitFailed(tr("Failed to extract modpack"));
return;
}
QDir extractDir(m_stagingPath);
qDebug() << "Fixing permissions for extracted pack files...";
QDirIterator it(extractDir, QDirIterator::Subdirectories);
while (it.hasNext()) {
while (it.hasNext())
{
auto filepath = it.next();
QFileInfo file(filepath);
auto permissions = QFile::permissions(filepath);
auto origPermissions = permissions;
if (file.isDir()) {
if(file.isDir())
{
// Folder +rwx for current user
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser;
} else {
}
else
{
// File +rw for current user
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
}
if (origPermissions != permissions) {
if (!QFile::setPermissions(filepath, permissions)) {
if(origPermissions != permissions)
{
if(!QFile::setPermissions(filepath, permissions))
{
logWarning(tr("Could not fix permissions for %1").arg(filepath));
} else {
}
else
{
qDebug() << "Fixed" << filepath;
}
}
}
switch (m_modpackType) {
switch(m_modpackType)
{
case ModpackType::MultiMC:
processMultiMC();
return;
@ -280,8 +276,7 @@ void InstanceImportTask::processFlame()
if (original_instance_id_it != m_extra_info.constEnd())
original_instance_id = original_instance_id_it.value();
inst_creation_task =
makeShared<FlameCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
inst_creation_task = makeShared<FlameCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
} else {
// FIXME: Find a way to get IDs in directly imported ZIPs
inst_creation_task = makeShared<FlameCreationTask>(m_stagingPath, m_globalSettings, m_parent, QString(), QString());
@ -291,26 +286,22 @@ void InstanceImportTask::processFlame()
inst_creation_task->setIcon(m_instIcon);
inst_creation_task->setGroup(m_instGroup);
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
auto weak = inst_creation_task.toWeakRef();
connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
if (auto sp = weak.lock()) {
setOverride(sp->shouldOverride(), sp->originalInstanceID());
}
connect(inst_creation_task.get(), &Task::succeeded, this, [this, inst_creation_task] {
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
emitSucceeded();
});
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress);
connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress);
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
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::abortStatusChanged, this, &Task::setAbortable);
m_task.reset(inst_creation_task);
setAbortable(true);
m_task->start();
inst_creation_task->start();
}
void InstanceImportTask::processTechnic()
@ -341,15 +332,13 @@ void InstanceImportTask::processMultiMC()
m_instIcon = instance.iconKey();
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
if (importIconPath.isNull() || !QFile::exists(importIconPath))
importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), "icon.png");
if (!importIconPath.isNull() && QFile::exists(importIconPath)) {
// import icon
auto iconList = APPLICATION->icons();
if (iconList->iconFileExists(m_instIcon)) {
iconList->deleteIcon(m_instIcon);
}
iconList->installIcon(importIconPath, m_instIcon);
iconList->installIcons({ importIconPath });
}
}
emitSucceeded();
@ -357,7 +346,7 @@ void InstanceImportTask::processMultiMC()
void InstanceImportTask::processModrinth()
{
shared_qobject_ptr<ModrinthCreationTask> inst_creation_task = nullptr;
ModrinthCreationTask* inst_creation_task = nullptr;
if (!m_extra_info.isEmpty()) {
auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
@ -373,8 +362,7 @@ void InstanceImportTask::processModrinth()
if (original_instance_id_it != m_extra_info.constEnd())
original_instance_id = original_instance_id_it.value();
inst_creation_task =
makeShared<ModrinthCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
} else {
QString pack_id;
if (!m_sourceUrl.isEmpty()) {
@ -383,31 +371,28 @@ void InstanceImportTask::processModrinth()
}
// FIXME: Find a way to get the ID in directly imported ZIPs
inst_creation_task = makeShared<ModrinthCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id);
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id);
}
inst_creation_task->setName(*this);
inst_creation_task->setIcon(m_instIcon);
inst_creation_task->setGroup(m_instGroup);
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
auto weak = inst_creation_task.toWeakRef();
connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
if (auto sp = weak.lock()) {
setOverride(sp->shouldOverride(), sp->originalInstanceID());
}
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
emitSucceeded();
});
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress);
connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress);
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails);
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort);
connect(inst_creation_task, &Task::aborted, this, &Task::abort);
connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable);
m_task.reset(inst_creation_task);
setAbortable(true);
m_task->start();
inst_creation_task->start();
}

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -35,40 +35,64 @@
#pragma once
#include "InstanceTask.h"
#include "net/NetJob.h"
#include <QUrl>
#include <QFuture>
#include <QFutureWatcher>
#include <QUrl>
#include "InstanceTask.h"
#include "settings/SettingsObject.h"
#include "QObjectPtr.h"
#include "modplatform/flame/PackManifest.h"
#include <optional>
class QuaZip;
namespace Flame
{
class FileResolvingTask;
}
class InstanceImportTask : public InstanceTask {
class InstanceImportTask : public InstanceTask
{
Q_OBJECT
public:
explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
virtual ~InstanceImportTask() = default;
bool abort() override;
public:
explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
protected:
bool abort() override;
const QVector<Flame::File> &getBlockedFiles() const
{
return m_blockedMods;
}
protected:
//! Entry point for tasks.
virtual void executeTask() override;
private:
private:
void processZipPack();
void processMultiMC();
void processTechnic();
void processFlame();
void processModrinth();
QString getRootFromZip(QuaZip* zip, const QString& root = "");
private slots:
void processZipPack();
private slots:
void downloadSucceeded();
void downloadFailed(QString reason);
void downloadProgressChanged(qint64 current, qint64 total);
void downloadAborted();
void extractFinished();
private: /* data */
private: /* data */
NetJob::Ptr m_filesNetJob;
shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver;
QUrl m_sourceUrl;
QString m_archivePath;
Task::Ptr m_task;
enum class ModpackType {
bool m_downloadRequired = false;
std::unique_ptr<QuaZip> m_packZip;
QFuture<std::optional<QStringList>> m_extractFuture;
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
QVector<Flame::File> m_blockedMods;
enum class ModpackType{
Unknown,
MultiMC,
Technic,
@ -80,7 +104,6 @@ class InstanceImportTask : public InstanceTask {
// the source URL / the resource it points to alone.
QMap<QString, QString> m_extra_info;
// FIXME: nuke
//FIXME: nuke
QWidget* m_parent;
void downloadFromUrl();
};

View file

@ -1,8 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -38,14 +37,13 @@
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QFileSystemWatcher>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMimeData>
#include <QPair>
#include <QSet>
#include <QStack>
#include <QPair>
#include <QTextStream>
#include <QThread>
#include <QTimer>
@ -98,11 +96,7 @@ Qt::DropActions InstanceList::supportedDropActions() const
return Qt::MoveAction;
}
bool InstanceList::canDropMimeData(const QMimeData* data,
[[maybe_unused]] Qt::DropAction action,
[[maybe_unused]] int row,
[[maybe_unused]] int column,
[[maybe_unused]] const QModelIndex& parent) const
bool InstanceList::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
{
if (data && data->hasFormat("application/x-instanceid")) {
return true;
@ -110,11 +104,7 @@ bool InstanceList::canDropMimeData(const QMimeData* data,
return false;
}
bool InstanceList::dropMimeData(const QMimeData* data,
[[maybe_unused]] Qt::DropAction action,
[[maybe_unused]] int row,
[[maybe_unused]] int column,
[[maybe_unused]] const QModelIndex& parent)
bool InstanceList::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
{
if (data && data->hasFormat("application/x-instanceid")) {
return true;
@ -139,7 +129,7 @@ QMimeData* InstanceList::mimeData(const QModelIndexList& indexes) const
return mimeData;
}
QStringList InstanceList::getLinkedInstancesById(const QString& id) const
QStringList InstanceList::getLinkedInstancesById(const QString &id) const
{
QStringList linkedInstances;
for (auto inst : m_instances) {
@ -168,34 +158,42 @@ QVariant InstanceList::data(const QModelIndex& index, int role) const
if (!index.isValid()) {
return QVariant();
}
BaseInstance* pdata = static_cast<BaseInstance*>(index.internalPointer());
switch (role) {
case InstancePointerRole: {
QVariant v = QVariant::fromValue((void*)pdata);
return v;
}
case InstanceIDRole: {
return pdata->id();
}
case Qt::EditRole:
case Qt::DisplayRole: {
return pdata->name();
}
case Qt::AccessibleTextRole: {
return tr("%1 Instance").arg(pdata->name());
}
case Qt::ToolTipRole: {
return pdata->instanceRoot();
}
case Qt::DecorationRole: {
return pdata->iconKey();
}
// HACK: see InstanceView.h in gui!
case GroupRole: {
return getInstanceGroup(pdata->id());
}
default:
break;
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
switch (role)
{
case InstancePointerRole:
{
QVariant v = QVariant::fromValue((void *)pdata);
return v;
}
case InstanceIDRole:
{
return pdata->id();
}
case Qt::EditRole:
case Qt::DisplayRole:
{
return pdata->name();
}
case Qt::AccessibleTextRole:
{
return tr("%1 Instance").arg(pdata->name());
}
case Qt::ToolTipRole:
{
return pdata->instanceRoot();
}
case Qt::DecorationRole:
{
return pdata->iconKey();
}
// HACK: see InstanceView.h in gui!
case GroupRole:
{
return getInstanceGroup(pdata->id());
}
default:
break;
}
return QVariant();
}
@ -239,11 +237,8 @@ GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
return GroupId();
}
void InstanceList::setInstanceGroup(const InstanceId& id, GroupId name)
void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
{
if (name.isEmpty() && !name.isNull())
name = QString();
auto inst = getInstanceById(id);
if (!inst) {
qDebug() << "Attempt to set a null instance's group";
@ -254,7 +249,6 @@ void InstanceList::setInstanceGroup(const InstanceId& id, GroupId name)
auto iter = m_instanceGroupIndex.find(inst->id());
if (iter != m_instanceGroupIndex.end()) {
if (*iter != name) {
decreaseGroupCount(*iter);
*iter = name;
changed = true;
}
@ -264,7 +258,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, GroupId name)
}
if (changed) {
increaseGroupCount(name);
m_groupNameCache.insert(name);
auto idx = getInstIndex(inst.get());
emit dataChanged(index(idx), index(idx), { GroupRole });
saveGroupList();
@ -273,55 +267,29 @@ void InstanceList::setInstanceGroup(const InstanceId& id, GroupId name)
QStringList InstanceList::getGroups()
{
return m_groupNameCache.keys();
return m_groupNameCache.values();
}
void InstanceList::deleteGroup(const GroupId& name)
void InstanceList::deleteGroup(const QString& name)
{
m_groupNameCache.remove(name);
m_collapsedGroups.remove(name);
bool removed = false;
qDebug() << "Delete group" << name;
for (auto& instance : m_instances) {
const QString& instID = instance->id();
const QString instGroupName = getInstanceGroup(instID);
const auto& instID = instance->id();
auto instGroupName = getInstanceGroup(instID);
if (instGroupName == name) {
m_instanceGroupIndex.remove(instID);
qDebug() << "Remove" << instID << "from group" << name;
removed = true;
auto idx = getInstIndex(instance.get());
if (idx >= 0)
if (idx > 0) {
emit dataChanged(index(idx), index(idx), { GroupRole });
}
}
}
if (removed)
if (removed) {
saveGroupList();
}
void InstanceList::renameGroup(const QString& src, const QString& dst)
{
m_groupNameCache.remove(src);
if (m_collapsedGroups.remove(src))
m_collapsedGroups.insert(dst);
bool modified = false;
qDebug() << "Rename group" << src << "to" << dst;
for (auto& instance : m_instances) {
const QString& instID = instance->id();
const QString instGroupName = getInstanceGroup(instID);
if (instGroupName == src) {
m_instanceGroupIndex[instID] = dst;
increaseGroupCount(dst);
qDebug() << "Set" << instID << "group to" << dst;
modified = true;
auto idx = getInstIndex(instance.get());
if (idx >= 0)
emit dataChanged(index(idx), index(idx), { GroupRole });
}
}
if (modified)
saveGroupList();
}
bool InstanceList::isGroupCollapsed(const QString& group)
@ -337,13 +305,12 @@ bool InstanceList::trashInstance(const InstanceId& id)
return false;
}
QString cachedGroupId = m_instanceGroupIndex[id];
auto cachedGroupId = m_instanceGroupIndex[id];
qDebug() << "Will trash instance" << id;
QString trashedLoc;
if (m_instanceGroupIndex.remove(id)) {
decreaseGroupCount(cachedGroupId);
saveGroupList();
}
@ -353,18 +320,16 @@ bool InstanceList::trashInstance(const InstanceId& id)
}
qDebug() << "Instance" << id << "has been trashed by the launcher.";
m_trashHistory.push({ id, inst->instanceRoot(), trashedLoc, cachedGroupId });
m_trashHistory.push({id, inst->instanceRoot(), trashedLoc, cachedGroupId});
return true;
}
bool InstanceList::trashedSomething()
{
bool InstanceList::trashedSomething() {
return !m_trashHistory.empty();
}
void InstanceList::undoTrashInstance()
{
void InstanceList::undoTrashInstance() {
if (m_trashHistory.empty()) {
qWarning() << "Nothing to recover from trash.";
return;
@ -372,16 +337,16 @@ void InstanceList::undoTrashInstance()
auto top = m_trashHistory.pop();
while (QDir(top.path).exists()) {
while (QDir(top.polyPath).exists()) {
top.id += "1";
top.path += "1";
top.polyPath += "1";
}
qDebug() << "Moving" << top.trashPath << "back to" << top.path;
QFile(top.trashPath).rename(top.path);
qDebug() << "Moving" << top.trashPath << "back to" << top.polyPath;
QFile(top.trashPath).rename(top.polyPath);
m_instanceGroupIndex[top.id] = top.groupName;
increaseGroupCount(top.groupName);
m_groupNameCache.insert(top.groupName);
saveGroupList();
emit instancesChanged();
@ -395,10 +360,7 @@ void InstanceList::deleteInstance(const InstanceId& id)
return;
}
QString cachedGroupId = m_instanceGroupIndex[id];
if (m_instanceGroupIndex.remove(id)) {
decreaseGroupCount(cachedGroupId);
saveGroupList();
}
@ -487,7 +449,7 @@ InstanceList::InstListError InstanceList::loadList()
int front_bookmark = -1;
int back_bookmark = -1;
int currentItem = -1;
auto removeNow = [this, &front_bookmark, &back_bookmark, &currentItem]() {
auto removeNow = [&]() {
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
endRemoveRows();
@ -596,7 +558,7 @@ InstancePtr InstanceList::getInstanceByManagedName(const QString& managed_name)
return {};
}
QModelIndex InstanceList::getInstanceIndexById(const QString& id) const
QModelIndex InstanceList::getInstanceIndexById(const QString &id) const
{
return index(getInstIndex(getInstanceById(id).get()));
}
@ -635,36 +597,19 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
QString inst_type = instanceSettings->get("InstanceType").toString();
// NOTE: Some launcher versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a
// OneSix instance
if (inst_type == "OneSix" || inst_type.isEmpty()) {
// NOTE: Some PolyMC versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a OneSix instance
if (inst_type == "OneSix" || inst_type.isEmpty())
{
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
} else {
}
else
{
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
}
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
return inst;
}
void InstanceList::increaseGroupCount(const QString& group)
{
if (group.isEmpty())
return;
++m_groupNameCache[group];
}
void InstanceList::decreaseGroupCount(const QString& group)
{
if (group.isEmpty())
return;
if (--m_groupNameCache[group] < 1) {
m_groupNameCache.remove(group);
m_collapsedGroups.remove(group);
}
}
void InstanceList::saveGroupList()
{
qDebug() << "Will save group list now.";
@ -676,7 +621,7 @@ void InstanceList::saveGroupList()
QString groupFileName = m_instDir + "/instgroups.json";
QMap<QString, QSet<QString>> reverseGroupMap;
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) {
const QString& id = iter.key();
QString id = iter.key();
QString group = iter.value();
if (group.isEmpty())
continue;
@ -710,12 +655,6 @@ void InstanceList::saveGroupList()
groupsArr.insert(name, groupObj);
}
toplevel.insert("groups", groupsArr);
// empty string represents ungrouped "group"
if (m_collapsedGroups.contains("")) {
QJsonObject ungrouped;
ungrouped.insert("hidden", QJsonValue(true));
toplevel.insert("ungrouped", ungrouped);
}
QJsonDocument doc(toplevel);
try {
FS::write(groupFileName, doc.toJson());
@ -772,22 +711,17 @@ void InstanceList::loadGroupList()
return;
}
QSet<QString> groupSet;
m_instanceGroupIndex.clear();
m_groupNameCache.clear();
// Iterate through all the groups.
QJsonObject groupMapping = rootObj.value("groups").toObject();
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) {
QString groupName = iter.key();
if (iter.key().isEmpty()) {
qWarning() << "Redundant empty group found";
continue;
}
// If not an object, complain and skip to the next one.
if (!iter.value().isObject()) {
qWarning() << QString("Group '%1' in the group list should be an object").arg(groupName).toUtf8();
qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
continue;
}
@ -799,29 +733,23 @@ void InstanceList::loadGroupList()
continue;
}
// keep a list/set of groups for choosing
groupSet.insert(groupName);
auto hidden = groupObj.value("hidden").toBool(false);
if (hidden)
if (hidden) {
m_collapsedGroups.insert(groupName);
}
// Iterate through the list of instances in the group.
QJsonArray instancesArray = groupObj.value("instances").toArray();
for (auto value : instancesArray) {
m_instanceGroupIndex[value.toString()] = groupName;
increaseGroupCount(groupName);
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++) {
m_instanceGroupIndex[(*iter2).toString()] = groupName;
}
}
bool ungroupedHidden = false;
if (rootObj.value("ungrouped").isObject()) {
QJsonObject ungrouped = rootObj.value("ungrouped").toObject();
ungroupedHidden = ungrouped.value("hidden").toBool(false);
}
if (ungroupedHidden) {
// empty string represents ungrouped "group"
m_collapsedGroups.insert("");
}
m_groupsLoaded = true;
m_groupNameCache.unite(groupSet);
qDebug() << "Group list loaded.";
}
@ -831,7 +759,7 @@ void InstanceList::instanceDirContentsChanged(const QString& path)
emit instancesChanged();
}
void InstanceList::on_InstFolderChanged([[maybe_unused]] const Setting& setting, QVariant value)
void InstanceList::on_InstFolderChanged(const Setting& setting, QVariant value)
{
QString newInstDir = QDir(value.toString()).canonicalPath();
if (newInstDir != m_instDir) {
@ -840,9 +768,6 @@ void InstanceList::on_InstFolderChanged([[maybe_unused]] const Setting& setting,
}
m_instDir = newInstDir;
m_groupsLoaded = false;
beginRemoveRows(QModelIndex(), 0, count());
m_instances.erase(m_instances.begin(), m_instances.end());
endRemoveRows();
emit instancesChanged();
}
}
@ -862,30 +787,23 @@ class InstanceStaging : public Task {
Q_OBJECT
const unsigned minBackoff = 1;
const unsigned maxBackoff = 16;
public:
InstanceStaging(InstanceList* parent, InstanceTask* child, SettingsObjectPtr settings)
: m_parent(parent), backoff(minBackoff, maxBackoff)
InstanceStaging(InstanceList* parent, InstanceTask* child, QString stagingPath, InstanceName const& instanceName, QString groupName)
: m_parent(parent), backoff(minBackoff, maxBackoff), m_stagingPath(std::move(stagingPath)), m_instance_name(std::move(instanceName)), m_groupName(std::move(groupName))
{
m_stagingPath = parent->getStagedInstancePath();
m_child.reset(child);
m_child->setStagingPath(m_stagingPath);
m_child->setParentSettings(std::move(settings));
connect(child, &Task::succeeded, this, &InstanceStaging::childSucceeded);
connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded);
connect(child, &Task::failed, this, &InstanceStaging::childFailed);
connect(child, &Task::aborted, this, &InstanceStaging::childAborted);
connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable);
connect(child, &Task::status, this, &InstanceStaging::setStatus);
connect(child, &Task::details, this, &InstanceStaging::setDetails);
connect(child, &Task::progress, this, &InstanceStaging::setProgress);
connect(child, &Task::stepProgress, this, &InstanceStaging::propagateStepProgress);
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceeded);
connect(child, &Task::stepProgress, this, &InstanceStaging::propogateStepProgress);
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
}
virtual ~InstanceStaging() {}
virtual ~InstanceStaging(){};
// FIXME/TODO: add ability to abort during instance commit retries
bool abort() override
@ -897,25 +815,21 @@ class InstanceStaging : public Task {
return Task::abort();
}
bool canAbort() const override { return (m_child && m_child->canAbort()); }
bool canAbort() const override
{
return (m_child && m_child->canAbort());
}
protected:
virtual void executeTask() override
{
if (m_stagingPath.isNull()) {
emitFailed(tr("Could not create staging folder"));
return;
}
m_child->start();
}
virtual void executeTask() override { m_child->start(); }
QStringList warnings() const override { return m_child->warnings(); }
private slots:
void childSucceeded()
void childSucceded()
{
unsigned sleepTime = backoff();
if (m_parent->commitStagedInstance(m_stagingPath, *m_child.get(), m_child->group(), *m_child.get())) {
if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, *m_child.get()))
{
emitSucceeded();
return;
}
@ -924,7 +838,7 @@ class InstanceStaging : public Task {
emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something."));
return;
}
qDebug() << "Failed to commit instance" << m_child->name() << "Initiating backoff:" << sleepTime;
qDebug() << "Failed to commit instance" << m_instance_name.name() << "Initiating backoff:" << sleepTime;
m_backoffTimer.start(sleepTime * 500);
}
void childFailed(const QString& reason)
@ -935,12 +849,11 @@ class InstanceStaging : public Task {
void childAborted()
{
m_parent->destroyStagingPath(m_stagingPath);
emitAborted();
}
private:
InstanceList* m_parent;
private:
InstanceList * m_parent;
/*
* WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows.
* Basically, it starts messing things up while the launcher is extracting/creating instances
@ -949,45 +862,39 @@ class InstanceStaging : public Task {
ExponentialSeries backoff;
QString m_stagingPath;
unique_qobject_ptr<InstanceTask> m_child;
InstanceName m_instance_name;
QString m_groupName;
QTimer m_backoffTimer;
};
Task* InstanceList::wrapInstanceTask(InstanceTask* task)
{
return new InstanceStaging(this, task, m_globalSettings);
auto stagingPath = getStagedInstancePath();
task->setStagingPath(stagingPath);
task->setParentSettings(m_globalSettings);
return new InstanceStaging(this, task, stagingPath, *task, task->group());
}
QString InstanceList::getStagedInstancePath()
{
const QString tempRoot = FS::PathCombine(m_instDir, ".tmp");
QString result;
int tries = 0;
do {
if (++tries > 256)
return {};
const QString key = QUuid::createUuid().toString(QUuid::Id128).left(6);
result = FS::PathCombine(tempRoot, key);
} while (QFileInfo::exists(result));
if (!QDir::current().mkpath(result))
return {};
QString key = QUuid::createUuid().toString(QUuid::WithoutBraces);
QString tempDir = ".LAUNCHER_TEMP/";
QString relPath = FS::PathCombine(tempDir, key);
QDir rootPath(m_instDir);
auto path = FS::PathCombine(m_instDir, relPath);
if (!rootPath.mkpath(relPath)) {
return QString();
}
#ifdef Q_OS_WIN32
SetFileAttributesA(tempRoot.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
auto tempPath = FS::PathCombine(m_instDir, tempDir);
SetFileAttributesA(tempPath.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
#endif
return result;
return path;
}
bool InstanceList::commitStagedInstance(const QString& path,
InstanceName const& instanceName,
QString groupName,
InstanceTask const& commiting)
bool InstanceList::commitStagedInstance(const QString& path, InstanceName const& instanceName, const QString& groupName, InstanceTask const& commiting)
{
if (groupName.isEmpty() && !groupName.isNull())
groupName = QString();
QDir dir;
QString instID;
InstancePtr inst;
@ -1011,13 +918,13 @@ bool InstanceList::commitStagedInstance(const QString& path,
return false;
}
} else {
if (!FS::move(path, destination)) {
if (!dir.rename(path, destination)) {
qWarning() << "Failed to move" << path << "to" << destination;
return false;
}
m_instanceGroupIndex[instID] = groupName;
increaseGroupCount(groupName);
m_groupNameCache.insert(groupName);
}
instanceSet.insert(instID);

View file

@ -1,46 +1,26 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
/* Copyright 2013-2021 MultiMC Contributors
*
* 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.
* 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
*
* 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.
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QAbstractListModel>
#include <QList>
#include <QObject>
#include <QPair>
#include <QAbstractListModel>
#include <QSet>
#include <QList>
#include <QStack>
#include <QPair>
#include "BaseInstance.h"
@ -52,47 +32,71 @@ using InstanceId = QString;
using GroupId = QString;
using InstanceLocator = std::pair<InstancePtr, int>;
enum class InstCreateError { NoCreateError = 0, NoSuchVersion, UnknownCreateError, InstExists, CantCreateDir };
enum class InstCreateError
{
NoCreateError = 0,
NoSuchVersion,
UnknownCreateError,
InstExists,
CantCreateDir
};
enum class GroupsState { NotLoaded, Steady, Dirty };
enum class GroupsState
{
NotLoaded,
Steady,
Dirty
};
struct TrashHistoryItem {
QString id;
QString path;
QString polyPath;
QString trashPath;
QString groupName;
};
class InstanceList : public QAbstractListModel {
class InstanceList : public QAbstractListModel
{
Q_OBJECT
public:
explicit InstanceList(SettingsObjectPtr settings, const QString& instDir, QObject* parent = 0);
public:
explicit InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent = 0);
virtual ~InstanceList();
public:
QModelIndex index(int row, int column = 0, const QModelIndex& parent = QModelIndex()) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
public:
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex& index, const QVariant& value, int role) override;
bool setData(const QModelIndex & index, const QVariant & value, int role) override;
enum AdditionalRoles {
enum AdditionalRoles
{
GroupRole = Qt::UserRole,
InstancePointerRole = 0x34B1CB48, ///< Return pointer to real instance
InstanceIDRole = 0x34B1CB49 ///< Return id if the instance
InstancePointerRole = 0x34B1CB48, ///< Return pointer to real instance
InstanceIDRole = 0x34B1CB49 ///< Return id if the instance
};
/*!
* \brief Error codes returned by functions in the InstanceList class.
* NoError Indicates that no error occurred.
* UnknownError indicates that an unspecified error occurred.
*/
enum InstListError { NoError = 0, UnknownError };
enum InstListError
{
NoError = 0,
UnknownError
};
InstancePtr at(int i) const { return m_instances.at(i); }
InstancePtr at(int i) const
{
return m_instances.at(i);
}
int count() const { return m_instances.count(); }
int count() const
{
return m_instances.count();
}
InstListError loadList();
void saveNow();
@ -101,22 +105,21 @@ class InstanceList : public QAbstractListModel {
InstancePtr getInstanceById(QString id) const;
/* O(n) */
InstancePtr getInstanceByManagedName(const QString& managed_name) const;
QModelIndex getInstanceIndexById(const QString& id) const;
QModelIndex getInstanceIndexById(const QString &id) const;
QStringList getGroups();
bool isGroupCollapsed(const QString& groupName);
bool isGroupCollapsed(const QString &groupName);
GroupId getInstanceGroup(const InstanceId& id) const;
void setInstanceGroup(const InstanceId& id, GroupId name);
GroupId getInstanceGroup(const InstanceId & id) const;
void setInstanceGroup(const InstanceId & id, const GroupId& name);
void deleteGroup(const GroupId& name);
void renameGroup(const GroupId& src, const GroupId& dst);
bool trashInstance(const InstanceId& id);
void deleteGroup(const GroupId & name);
bool trashInstance(const InstanceId &id);
bool trashedSomething();
void undoTrashInstance();
void deleteInstance(const InstanceId& id);
void deleteInstance(const InstanceId & id);
// Wrap an instance creation task in some more task machinery and make it ready to be used
Task* wrapInstanceTask(InstanceTask* task);
Task * wrapInstanceTask(InstanceTask * task);
/**
* Create a new empty staging area for instance creation and @return a path/key top commit it later.
@ -130,13 +133,13 @@ class InstanceList : public QAbstractListModel {
* should_override is used when another similar instance already exists, and we want to override it
* - for instance, when updating it.
*/
bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, QString groupName, const InstanceTask&);
bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, const QString& groupName, const InstanceTask&);
/**
* Destroy a previously created staging area given by @keyPath - used when creation fails.
* Used by instance manipulation tasks.
*/
bool destroyStagingPath(const QString& keyPath);
bool destroyStagingPath(const QString & keyPath);
int getTotalPlayTime();
@ -144,55 +147,51 @@ class InstanceList : public QAbstractListModel {
Qt::DropActions supportedDropActions() const override;
bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override;
bool canDropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) const override;
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override;
bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override;
QStringList mimeTypes() const override;
QMimeData* mimeData(const QModelIndexList& indexes) const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
QStringList getLinkedInstancesById(const QString& id) const;
QStringList getLinkedInstancesById(const QString &id) const;
signals:
signals:
void dataIsInvalid();
void instancesChanged();
void instanceSelectRequest(QString instanceId);
void groupsChanged(QSet<QString> groups);
public slots:
void on_InstFolderChanged(const Setting& setting, QVariant value);
void on_GroupStateChanged(const QString& group, bool collapsed);
public slots:
void on_InstFolderChanged(const Setting &setting, QVariant value);
void on_GroupStateChanged(const QString &group, bool collapsed);
private slots:
void propertiesChanged(BaseInstance* inst);
private slots:
void propertiesChanged(BaseInstance *inst);
void providerUpdated();
void instanceDirContentsChanged(const QString& path);
void instanceDirContentsChanged(const QString &path);
private:
int getInstIndex(BaseInstance* inst) const;
private:
int getInstIndex(BaseInstance *inst) const;
void updateTotalPlayTime();
void suspendWatch();
void resumeWatch();
void add(const QList<InstancePtr>& list);
void add(const QList<InstancePtr> &list);
void loadGroupList();
void saveGroupList();
QList<InstanceId> discoverInstances();
InstancePtr loadInstance(const InstanceId& id);
void increaseGroupCount(const QString& group);
void decreaseGroupCount(const QString& group);
private:
private:
int m_watchLevel = 0;
int totalPlayTime = 0;
bool m_dirty = false;
QList<InstancePtr> m_instances;
// id -> refs
QMap<QString, int> m_groupNameCache;
QSet<QString> m_groupNameCache;
SettingsObjectPtr m_globalSettings;
QString m_instDir;
QFileSystemWatcher* m_watcher;
QFileSystemWatcher * m_watcher;
// FIXME: this is so inefficient that looking at it is almost painful.
QSet<QString> m_collapsedGroups;
QMap<InstanceId, GroupId> m_instanceGroupIndex;

View file

@ -1,31 +1,36 @@
#pragma once
#include <FileSystem.h>
#include "minecraft/MinecraftInstance.h"
#include <FileSystem.h>
#include "ui/pages/BasePage.h"
#include "ui/pages/BasePageProvider.h"
#include "ui/pages/instance/InstanceSettingsPage.h"
#include "ui/pages/instance/LogPage.h"
#include "ui/pages/instance/VersionPage.h"
#include "ui/pages/instance/ManagedPackPage.h"
#include "ui/pages/instance/ModFolderPage.h"
#include "ui/pages/instance/NotesPage.h"
#include "ui/pages/instance/OtherLogsPage.h"
#include "ui/pages/instance/ResourcePackPage.h"
#include "ui/pages/instance/ScreenshotsPage.h"
#include "ui/pages/instance/ServersPage.h"
#include "ui/pages/instance/ShaderPackPage.h"
#include "ui/pages/instance/TexturePackPage.h"
#include "ui/pages/instance/VersionPage.h"
#include "ui/pages/instance/ShaderPackPage.h"
#include "ui/pages/instance/NotesPage.h"
#include "ui/pages/instance/ScreenshotsPage.h"
#include "ui/pages/instance/InstanceSettingsPage.h"
#include "ui/pages/instance/OtherLogsPage.h"
#include "ui/pages/instance/WorldListPage.h"
#include "ui/pages/instance/ServersPage.h"
#include "ui/pages/instance/GameOptionsPage.h"
class InstancePageProvider : protected QObject, public BasePageProvider {
class InstancePageProvider : public QObject, public BasePageProvider
{
Q_OBJECT
public:
explicit InstancePageProvider(InstancePtr parent) { inst = parent; }
virtual ~InstancePageProvider() = default;
virtual QList<BasePage*> getPages() override
public:
explicit InstancePageProvider(InstancePtr parent)
{
QList<BasePage*> values;
inst = parent;
}
virtual ~InstancePageProvider() {};
virtual QList<BasePage *> getPages() override
{
QList<BasePage *> values;
values.append(new LogPage(inst));
std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst);
values.append(new VersionPage(onesix.get()));
@ -39,20 +44,24 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
values.append(new TexturePackPage(onesix.get(), onesix->texturePackList()));
values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList()));
values.append(new NotesPage(onesix.get()));
values.append(new WorldListPage(onesix, onesix->worldList()));
values.append(new WorldListPage(onesix.get(), onesix->worldList()));
values.append(new ServersPage(onesix));
// values.append(new GameOptionsPage(onesix.get()));
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
values.append(new InstanceSettingsPage(onesix));
values.append(new InstanceSettingsPage(onesix.get()));
auto logMatcher = inst->getLogFileMatcher();
if (logMatcher) {
if(logMatcher)
{
values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher));
}
return values;
}
virtual QString dialogTitle() override { return tr("Edit Instance (%1)").arg(inst->name()); }
protected:
virtual QString dialogTitle() override
{
return tr("Edit Instance (%1)").arg(inst->name());
}
protected:
InstancePtr inst;
};

View file

@ -1,11 +1,7 @@
#include "InstanceTask.h"
#include "Application.h"
#include "settings/SettingsObject.h"
#include "ui/dialogs/CustomMessageBox.h"
#include <QPushButton>
InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name)
{
auto dialog =
@ -22,29 +18,27 @@ InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& ol
return InstanceNameChange::ShouldKeep;
}
ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name)
ShouldUpdate askIfShouldUpdate(QWidget *parent, QString original_version_name)
{
if (APPLICATION->settings()->get("SkipModpackUpdatePrompt").toBool())
return ShouldUpdate::SkipUpdating;
auto info = CustomMessageBox::selectable(
parent, QObject::tr("Similar modpack was found!"),
QObject::tr(
"One or more of your instances are from this same modpack%1. Do you want to create a "
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
QObject::tr("One or more of your instances are from this same modpack%1. Do you want to create a "
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
.arg(original_version_name),
QMessageBox::Information, QMessageBox::Cancel);
QAbstractButton* update = info->addButton(QObject::tr("Update existing instance"), QMessageBox::AcceptRole);
QAbstractButton* skip = info->addButton(QObject::tr("Create new instance"), QMessageBox::ResetRole);
QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
info->setButtonText(QMessageBox::Ok, QObject::tr("Update existing instance"));
info->setButtonText(QMessageBox::Abort, QObject::tr("Create new instance"));
info->setButtonText(QMessageBox::Reset, QObject::tr("Cancel"));
info->exec();
if (info->clickedButton() == update)
if (info->clickedButton() == info->button(QMessageBox::Ok))
return ShouldUpdate::Update;
if (info->clickedButton() == skip)
if (info->clickedButton() == info->button(QMessageBox::Abort))
return ShouldUpdate::SkipUpdating;
return ShouldUpdate::Cancel;
}
QString InstanceName::name() const

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -39,39 +39,43 @@
#include <QRegularExpression>
bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent)
bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent)
{
if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegularExpression("-Xm[sx]")) || jvmargs.contains("-XX-MaxHeapSize") ||
jvmargs.contains("-XX:InitialHeapSize")) {
if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegularExpression("-Xm[sx]"))
|| jvmargs.contains("-XX-MaxHeapSize") || jvmargs.contains("-XX:InitialHeapSize"))
{
auto warnStr = QObject::tr(
"You tried to manually set a JVM memory option (using \"-XX:PermSize\", \"-XX-MaxHeapSize\", \"-XX:InitialHeapSize\", \"-Xmx\" "
"or \"-Xms\").\n"
"You tried to manually set a JVM memory option (using \"-XX:PermSize\", \"-XX-MaxHeapSize\", \"-XX:InitialHeapSize\", \"-Xmx\" or \"-Xms\").\n"
"There are dedicated boxes for these in the settings (Java tab, in the Memory group at the top).\n"
"This message will be displayed until you remove them from the JVM arguments.");
CustomMessageBox::selectable(parent, QObject::tr("JVM arguments warning"), warnStr, QMessageBox::Warning)->exec();
CustomMessageBox::selectable(
parent, QObject::tr("JVM arguments warning"),
warnStr,
QMessageBox::Warning)->exec();
return false;
}
// block lunacy with passing required version to the JVM
if (jvmargs.contains(QRegularExpression("-version:.*"))) {
auto warnStr = QObject::tr(
"You tried to pass required Java version argument to the JVM (using \"-version:xxx\"). This is not safe and will not be "
"allowed.\n"
"You tried to pass required Java version argument to the JVM (using \"-version:xxx\"). This is not safe and will not be allowed.\n"
"This message will be displayed until you remove this from the JVM arguments.");
CustomMessageBox::selectable(parent, QObject::tr("JVM arguments warning"), warnStr, QMessageBox::Warning)->exec();
CustomMessageBox::selectable(
parent, QObject::tr("JVM arguments warning"),
warnStr,
QMessageBox::Warning)->exec();
return false;
}
return true;
}
void JavaCommon::javaWasOk(QWidget* parent, const JavaChecker::Result& result)
void JavaCommon::javaWasOk(QWidget *parent, JavaCheckResult result)
{
QString text;
text += QObject::tr(
"Java test succeeded!<br />Platform reported: %1<br />Java version "
"reported: %2<br />Java vendor "
"reported: %3<br />")
.arg(result.realPlatform, result.javaVersion.toString(), result.javaVendor);
if (result.errorLog.size()) {
text += QObject::tr("Java test succeeded!<br />Platform reported: %1<br />Java version "
"reported: %2<br />Java vendor "
"reported: %3<br />").arg(result.realPlatform, result.javaVersion.toString(), result.javaVendor);
if (result.errorLog.size())
{
auto htmlError = result.errorLog;
htmlError.replace('\n', "<br />");
text += QObject::tr("<br />Warnings:<br /><font color=\"orange\">%1</font>").arg(htmlError);
@ -79,7 +83,7 @@ void JavaCommon::javaWasOk(QWidget* parent, const JavaChecker::Result& result)
CustomMessageBox::selectable(parent, QObject::tr("Java test success"), text, QMessageBox::Information)->show();
}
void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaChecker::Result& result)
void JavaCommon::javaArgsWereBad(QWidget *parent, JavaCheckResult result)
{
auto htmlError = result.errorLog;
QString text;
@ -89,7 +93,7 @@ void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaChecker::Result& res
CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show();
}
void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaChecker::Result& result)
void JavaCommon::javaBinaryWasBad(QWidget *parent, JavaCheckResult result)
{
QString text;
text += QObject::tr(
@ -98,7 +102,7 @@ void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaChecker::Result& re
CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show();
}
void JavaCommon::javaCheckNotFound(QWidget* parent)
void JavaCommon::javaCheckNotFound(QWidget *parent)
{
QString text;
text += QObject::tr("Java checker library could not be found. Please check your installation.");
@ -107,7 +111,8 @@ void JavaCommon::javaCheckNotFound(QWidget* parent)
void JavaCommon::TestCheck::run()
{
if (!JavaCommon::checkJVMArgs(m_args, m_parent)) {
if (!JavaCommon::checkJVMArgs(m_args, m_parent))
{
emit finished();
return;
}
@ -116,26 +121,37 @@ void JavaCommon::TestCheck::run()
emit finished();
return;
}
checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0));
checker.reset(new JavaChecker());
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
checker->start();
checker->m_path = m_path;
checker->performCheck();
}
void JavaCommon::TestCheck::checkFinished(const JavaChecker::Result& result)
void JavaCommon::TestCheck::checkFinished(JavaCheckResult result)
{
if (result.validity != JavaChecker::Result::Validity::Valid) {
if (result.validity != JavaCheckResult::Validity::Valid)
{
javaBinaryWasBad(m_parent, result);
emit finished();
return;
}
checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0));
checker.reset(new JavaChecker());
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
checker->start();
checker->m_path = m_path;
checker->m_args = m_args;
checker->m_minMem = m_minMem;
checker->m_maxMem = m_maxMem;
if (result.javaVersion.requiresPermGen())
{
checker->m_permGen = m_permGen;
}
checker->performCheck();
}
void JavaCommon::TestCheck::checkFinishedWithArgs(const JavaChecker::Result& result)
void JavaCommon::TestCheck::checkFinishedWithArgs(JavaCheckResult result)
{
if (result.validity == JavaChecker::Result::Validity::Valid) {
if (result.validity == JavaCheckResult::Validity::Valid)
{
javaWasOk(m_parent, result);
emit finished();
return;
@ -143,3 +159,4 @@ void JavaCommon::TestCheck::checkFinishedWithArgs(const JavaChecker::Result& res
javaArgsWereBad(m_parent, result);
emit finished();
}

View file

@ -6,42 +6,45 @@ class QWidget;
/**
* Common UI bits for the java pages to use.
*/
namespace JavaCommon {
bool checkJVMArgs(QString args, QWidget* parent);
namespace JavaCommon
{
bool checkJVMArgs(QString args, QWidget *parent);
// Show a dialog saying that the Java binary was usable
void javaWasOk(QWidget* parent, const JavaChecker::Result& result);
// Show a dialog saying that the Java binary was not usable because of bad options
void javaArgsWereBad(QWidget* parent, const JavaChecker::Result& result);
// Show a dialog saying that the Java binary was not usable
void javaBinaryWasBad(QWidget* parent, const JavaChecker::Result& result);
// Show a dialog if we couldn't find Java Checker
void javaCheckNotFound(QWidget* parent);
// Show a dialog saying that the Java binary was usable
void javaWasOk(QWidget *parent, JavaCheckResult result);
// Show a dialog saying that the Java binary was not usable because of bad options
void javaArgsWereBad(QWidget *parent, JavaCheckResult result);
// Show a dialog saying that the Java binary was not usable
void javaBinaryWasBad(QWidget *parent, JavaCheckResult result);
// Show a dialog if we couldn't find Java Checker
void javaCheckNotFound(QWidget *parent);
class TestCheck : public QObject {
Q_OBJECT
public:
TestCheck(QWidget* parent, QString path, QString args, int minMem, int maxMem, int permGen)
: m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen)
{}
virtual ~TestCheck() {};
class TestCheck : public QObject
{
Q_OBJECT
public:
TestCheck(QWidget *parent, QString path, QString args, int minMem, int maxMem, int permGen)
:m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen)
{
}
virtual ~TestCheck() {};
void run();
void run();
signals:
void finished();
signals:
void finished();
private slots:
void checkFinished(const JavaChecker::Result& result);
void checkFinishedWithArgs(const JavaChecker::Result& result);
private slots:
void checkFinished(JavaCheckResult result);
void checkFinishedWithArgs(JavaCheckResult result);
private:
JavaChecker::Ptr checker;
QWidget* m_parent = nullptr;
QString m_path;
QString m_args;
int m_minMem = 0;
int m_maxMem = 0;
int m_permGen = 64;
};
} // namespace JavaCommon
private:
std::shared_ptr<JavaChecker> checker;
QWidget *m_parent = nullptr;
QString m_path;
QString m_args;
int m_minMem = 0;
int m_maxMem = 0;
int m_permGen = 64;
};
}

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -37,246 +37,257 @@
#include <QFile>
#include <math.h>
#include "FileSystem.h"
#include <math.h>
namespace Json {
void write(const QJsonDocument& doc, const QString& filename)
namespace Json
{
void write(const QJsonDocument &doc, const QString &filename)
{
FS::write(filename, doc.toJson());
}
void write(const QJsonObject& object, const QString& filename)
void write(const QJsonObject &object, const QString &filename)
{
write(QJsonDocument(object), filename);
}
void write(const QJsonArray& array, const QString& filename)
void write(const QJsonArray &array, const QString &filename)
{
write(QJsonDocument(array), filename);
}
QByteArray toText(const QJsonObject& obj)
QByteArray toText(const QJsonObject &obj)
{
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
}
QByteArray toText(const QJsonArray& array)
QByteArray toText(const QJsonArray &array)
{
return QJsonDocument(array).toJson(QJsonDocument::Compact);
}
static bool isBinaryJson(const QByteArray& data)
static bool isBinaryJson(const QByteArray &data)
{
decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag;
return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0;
}
QJsonDocument requireDocument(const QByteArray& data, const QString& what)
QJsonDocument requireDocument(const QByteArray &data, const QString &what)
{
if (isBinaryJson(data)) {
if (isBinaryJson(data))
{
// FIXME: Is this needed?
throw JsonException(what + ": Invalid JSON. Binary JSON unsupported");
} else {
}
else
{
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
if (error.error != QJsonParseError::NoError)
{
throw JsonException(what + ": Error parsing JSON: " + error.errorString());
}
return doc;
}
}
QJsonDocument requireDocument(const QString& filename, const QString& what)
QJsonDocument requireDocument(const QString &filename, const QString &what)
{
return requireDocument(FS::read(filename), what);
}
QJsonObject requireObject(const QJsonDocument& doc, const QString& what)
QJsonObject requireObject(const QJsonDocument &doc, const QString &what)
{
if (!doc.isObject()) {
if (!doc.isObject())
{
throw JsonException(what + " is not an object");
}
return doc.object();
}
QJsonArray requireArray(const QJsonDocument& doc, const QString& what)
QJsonArray requireArray(const QJsonDocument &doc, const QString &what)
{
if (!doc.isArray()) {
if (!doc.isArray())
{
throw JsonException(what + " is not an array");
}
return doc.array();
}
void writeString(QJsonObject& to, const QString& key, const QString& value)
void writeString(QJsonObject &to, const QString &key, const QString &value)
{
if (!value.isEmpty()) {
if (!value.isEmpty())
{
to.insert(key, value);
}
}
void writeStringList(QJsonObject& to, const QString& key, const QStringList& values)
void writeStringList(QJsonObject &to, const QString &key, const QStringList &values)
{
if (!values.isEmpty()) {
if (!values.isEmpty())
{
QJsonArray array;
for (auto value : values) {
for(auto value: values)
{
array.append(value);
}
to.insert(key, array);
}
}
template <>
QJsonValue toJson<QUrl>(const QUrl& url)
template<>
QJsonValue toJson<QUrl>(const QUrl &url)
{
return QJsonValue(url.toString(QUrl::FullyEncoded));
}
template <>
QJsonValue toJson<QByteArray>(const QByteArray& data)
template<>
QJsonValue toJson<QByteArray>(const QByteArray &data)
{
return QJsonValue(QString::fromLatin1(data.toHex()));
}
template <>
QJsonValue toJson<QDateTime>(const QDateTime& datetime)
template<>
QJsonValue toJson<QDateTime>(const QDateTime &datetime)
{
return QJsonValue(datetime.toString(Qt::ISODate));
}
template <>
QJsonValue toJson<QDir>(const QDir& dir)
template<>
QJsonValue toJson<QDir>(const QDir &dir)
{
return QDir::current().relativeFilePath(dir.absolutePath());
}
template <>
QJsonValue toJson<QUuid>(const QUuid& uuid)
template<>
QJsonValue toJson<QUuid>(const QUuid &uuid)
{
return uuid.toString();
}
template <>
QJsonValue toJson<QVariant>(const QVariant& variant)
template<>
QJsonValue toJson<QVariant>(const QVariant &variant)
{
return QJsonValue::fromVariant(variant);
}
template <>
QByteArray requireIsType<QByteArray>(const QJsonValue& value, const QString& what)
template<> QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what)
{
const QString string = ensureIsType<QString>(value, what);
// ensure that the string can be safely cast to Latin1
if (string != QString::fromLatin1(string.toLatin1())) {
if (string != QString::fromLatin1(string.toLatin1()))
{
throw JsonException(what + " is not encodable as Latin1");
}
return QByteArray::fromHex(string.toLatin1());
}
template <>
QJsonArray requireIsType<QJsonArray>(const QJsonValue& value, const QString& what)
template<> QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what)
{
if (!value.isArray()) {
if (!value.isArray())
{
throw JsonException(what + " is not an array");
}
return value.toArray();
}
template <>
QString requireIsType<QString>(const QJsonValue& value, const QString& what)
template<> QString requireIsType<QString>(const QJsonValue &value, const QString &what)
{
if (!value.isString()) {
if (!value.isString())
{
throw JsonException(what + " is not a string");
}
return value.toString();
}
template <>
bool requireIsType<bool>(const QJsonValue& value, const QString& what)
template<> bool requireIsType<bool>(const QJsonValue &value, const QString &what)
{
if (!value.isBool()) {
if (!value.isBool())
{
throw JsonException(what + " is not a bool");
}
return value.toBool();
}
template <>
double requireIsType<double>(const QJsonValue& value, const QString& what)
template<> double requireIsType<double>(const QJsonValue &value, const QString &what)
{
if (!value.isDouble()) {
if (!value.isDouble())
{
throw JsonException(what + " is not a double");
}
return value.toDouble();
}
template <>
int requireIsType<int>(const QJsonValue& value, const QString& what)
template<> int requireIsType<int>(const QJsonValue &value, const QString &what)
{
const double doubl = requireIsType<double>(value, what);
if (fmod(doubl, 1) != 0) {
if (fmod(doubl, 1) != 0)
{
throw JsonException(what + " is not an integer");
}
return int(doubl);
}
template <>
QDateTime requireIsType<QDateTime>(const QJsonValue& value, const QString& what)
template<> QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what)
{
const QString string = requireIsType<QString>(value, what);
const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate);
if (!datetime.isValid()) {
if (!datetime.isValid())
{
throw JsonException(what + " is not a ISO formatted date/time value");
}
return datetime;
}
template <>
QUrl requireIsType<QUrl>(const QJsonValue& value, const QString& what)
template<> QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what)
{
const QString string = ensureIsType<QString>(value, what);
if (string.isEmpty()) {
if (string.isEmpty())
{
return QUrl();
}
const QUrl url = QUrl(string, QUrl::StrictMode);
if (!url.isValid()) {
if (!url.isValid())
{
throw JsonException(what + " is not a correctly formatted URL");
}
return url;
}
template <>
QDir requireIsType<QDir>(const QJsonValue& value, const QString& what)
template<> QDir requireIsType<QDir>(const QJsonValue &value, const QString &what)
{
const QString string = requireIsType<QString>(value, what);
// FIXME: does not handle invalid characters!
return QDir::current().absoluteFilePath(string);
}
template <>
QUuid requireIsType<QUuid>(const QJsonValue& value, const QString& what)
template<> QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what)
{
const QString string = requireIsType<QString>(value, what);
const QUuid uuid = QUuid(string);
if (uuid.toString() != string) // converts back => valid
if (uuid.toString() != string) // converts back => valid
{
throw JsonException(what + " is not a valid UUID");
}
return uuid;
}
template <>
QJsonObject requireIsType<QJsonObject>(const QJsonValue& value, const QString& what)
template<> QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what)
{
if (!value.isObject()) {
if (!value.isObject())
{
throw JsonException(what + " is not an object");
}
return value.toObject();
}
template <>
QVariant requireIsType<QVariant>(const QJsonValue& value, const QString& what)
template<> QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what)
{
if (value.isNull() || value.isUndefined()) {
if (value.isNull() || value.isUndefined())
{
throw JsonException(what + " is null or undefined");
}
return value.toVariant();
}
template <>
QJsonValue requireIsType<QJsonValue>(const QJsonValue& value, const QString& what)
template<> QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what)
{
if (value.isNull() || value.isUndefined()) {
if (value.isNull() || value.isUndefined())
{
throw JsonException(what + " is null or undefined");
}
return value;
}
} // namespace Json
}

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -35,71 +35,74 @@
#pragma once
#include <QDateTime>
#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QDateTime>
#include <QUrl>
#include <QDir>
#include <QUuid>
#include <QVariant>
#include <memory>
#include "Exception.h"
namespace Json {
class JsonException : public ::Exception {
public:
JsonException(const QString& message) : Exception(message) {}
namespace Json
{
class JsonException : public ::Exception
{
public:
JsonException(const QString &message) : Exception(message) {}
};
/// @throw FileSystemException
void write(const QJsonDocument& doc, const QString& filename);
void write(const QJsonDocument &doc, const QString &filename);
/// @throw FileSystemException
void write(const QJsonObject& object, const QString& filename);
void write(const QJsonObject &object, const QString &filename);
/// @throw FileSystemException
void write(const QJsonArray& array, const QString& filename);
void write(const QJsonArray &array, const QString &filename);
QByteArray toText(const QJsonObject& obj);
QByteArray toText(const QJsonArray& array);
QByteArray toText(const QJsonObject &obj);
QByteArray toText(const QJsonArray &array);
/// @throw JsonException
QJsonDocument requireDocument(const QByteArray& data, const QString& what = "Document");
QJsonDocument requireDocument(const QByteArray &data, const QString &what = "Document");
/// @throw JsonException
QJsonDocument requireDocument(const QString& filename, const QString& what = "Document");
QJsonDocument requireDocument(const QString &filename, const QString &what = "Document");
/// @throw JsonException
QJsonObject requireObject(const QJsonDocument& doc, const QString& what = "Document");
QJsonObject requireObject(const QJsonDocument &doc, const QString &what = "Document");
/// @throw JsonException
QJsonArray requireArray(const QJsonDocument& doc, const QString& what = "Document");
QJsonArray requireArray(const QJsonDocument &doc, const QString &what = "Document");
/////////////////// WRITING ////////////////////
void writeString(QJsonObject& to, const QString& key, const QString& value);
void writeStringList(QJsonObject& to, const QString& key, const QStringList& values);
void writeString(QJsonObject & to, const QString &key, const QString &value);
void writeStringList(QJsonObject & to, const QString &key, const QStringList &values);
template <typename T>
QJsonValue toJson(const T& t)
template<typename T>
QJsonValue toJson(const T &t)
{
return QJsonValue(t);
}
template <>
QJsonValue toJson<QUrl>(const QUrl& url);
template <>
QJsonValue toJson<QByteArray>(const QByteArray& data);
template <>
QJsonValue toJson<QDateTime>(const QDateTime& datetime);
template <>
QJsonValue toJson<QDir>(const QDir& dir);
template <>
QJsonValue toJson<QUuid>(const QUuid& uuid);
template <>
QJsonValue toJson<QVariant>(const QVariant& variant);
template<>
QJsonValue toJson<QUrl>(const QUrl &url);
template<>
QJsonValue toJson<QByteArray>(const QByteArray &data);
template<>
QJsonValue toJson<QDateTime>(const QDateTime &datetime);
template<>
QJsonValue toJson<QDir>(const QDir &dir);
template<>
QJsonValue toJson<QUuid>(const QUuid &uuid);
template<>
QJsonValue toJson<QVariant>(const QVariant &variant);
template <typename T>
QJsonArray toJsonArray(const QList<T>& container)
template<typename T>
QJsonArray toJsonArray(const QList<T> &container)
{
QJsonArray array;
for (const T item : container) {
for (const T item : container)
{
array.append(toJson<T>(item));
}
return array;
@ -109,110 +112,106 @@ QJsonArray toJsonArray(const QList<T>& container)
/// @throw JsonException
template <typename T>
T requireIsType(const QJsonValue& value, const QString& what = "Value");
T requireIsType(const QJsonValue &value, const QString &what = "Value");
/// @throw JsonException
template <>
double requireIsType<double>(const QJsonValue& value, const QString& what);
template<> double requireIsType<double>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
bool requireIsType<bool>(const QJsonValue& value, const QString& what);
template<> bool requireIsType<bool>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
int requireIsType<int>(const QJsonValue& value, const QString& what);
template<> int requireIsType<int>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QJsonObject requireIsType<QJsonObject>(const QJsonValue& value, const QString& what);
template<> QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QJsonArray requireIsType<QJsonArray>(const QJsonValue& value, const QString& what);
template<> QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QJsonValue requireIsType<QJsonValue>(const QJsonValue& value, const QString& what);
template<> QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QByteArray requireIsType<QByteArray>(const QJsonValue& value, const QString& what);
template<> QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QDateTime requireIsType<QDateTime>(const QJsonValue& value, const QString& what);
template<> QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QVariant requireIsType<QVariant>(const QJsonValue& value, const QString& what);
template<> QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QString requireIsType<QString>(const QJsonValue& value, const QString& what);
template<> QString requireIsType<QString>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QUuid requireIsType<QUuid>(const QJsonValue& value, const QString& what);
template<> QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QDir requireIsType<QDir>(const QJsonValue& value, const QString& what);
template<> QDir requireIsType<QDir>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QUrl requireIsType<QUrl>(const QJsonValue& value, const QString& what);
template<> QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what);
// the following functions are higher level functions, that make use of the above functions for
// type conversion
template <typename T>
T ensureIsType(const QJsonValue& value, const T default_ = T(), const QString& what = "Value")
T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &what = "Value")
{
if (value.isUndefined() || value.isNull()) {
if (value.isUndefined() || value.isNull())
{
return default_;
}
try {
try
{
return requireIsType<T>(value, what);
} catch (const JsonException&) {
}
catch (const JsonException &)
{
return default_;
}
}
/// @throw JsonException
template <typename T>
T requireIsType(const QJsonObject& parent, const QString& key, const QString& what = "__placeholder__")
T requireIsType(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key)) {
if (!parent.contains(key))
{
throw JsonException(localWhat + "s parent does not contain " + localWhat);
}
return requireIsType<T>(parent.value(key), localWhat);
}
template <typename T>
T ensureIsType(const QJsonObject& parent, const QString& key, const T default_ = T(), const QString& what = "__placeholder__")
T ensureIsType(const QJsonObject &parent, const QString &key, const T default_ = T(), const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key)) {
if (!parent.contains(key))
{
return default_;
}
return ensureIsType<T>(parent.value(key), default_, localWhat);
}
template <typename T>
QVector<T> requireIsArrayOf(const QJsonDocument& doc)
QVector<T> requireIsArrayOf(const QJsonDocument &doc)
{
const QJsonArray array = requireArray(doc);
QVector<T> out;
for (const QJsonValue val : array) {
for (const QJsonValue val : array)
{
out.append(requireIsType<T>(val, "Document"));
}
return out;
}
template <typename T>
QVector<T> ensureIsArrayOf(const QJsonValue& value, const QString& what = "Value")
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value")
{
const QJsonArray array = ensureIsType<QJsonArray>(value, QJsonArray(), what);
QVector<T> out;
for (const QJsonValue val : array) {
for (const QJsonValue val : array)
{
out.append(requireIsType<T>(val, what));
}
return out;
}
template <typename T>
QVector<T> ensureIsArrayOf(const QJsonValue& value, const QVector<T> default_, const QString& what = "Value")
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QVector<T> default_, const QString &what = "Value")
{
if (value.isUndefined()) {
if (value.isUndefined())
{
return default_;
}
return ensureIsArrayOf<T>(value, what);
@ -220,46 +219,45 @@ QVector<T> ensureIsArrayOf(const QJsonValue& value, const QVector<T> default_, c
/// @throw JsonException
template <typename T>
QVector<T> requireIsArrayOf(const QJsonObject& parent, const QString& key, const QString& what = "__placeholder__")
QVector<T> requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key)) {
if (!parent.contains(key))
{
throw JsonException(localWhat + "s parent does not contain " + localWhat);
}
return ensureIsArrayOf<T>(parent.value(key), localWhat);
}
template <typename T>
QVector<T> ensureIsArrayOf(const QJsonObject& parent,
const QString& key,
const QVector<T>& default_ = QVector<T>(),
const QString& what = "__placeholder__")
QVector<T> ensureIsArrayOf(const QJsonObject &parent, const QString &key,
const QVector<T> &default_ = QVector<T>(), const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key)) {
if (!parent.contains(key))
{
return default_;
}
return ensureIsArrayOf<T>(parent.value(key), default_, localWhat);
}
// this macro part could be replaced by variadic functions that just pass on their arguments, but that wouldn't work well with IDE helpers
#define JSON_HELPERFUNCTIONS(NAME, TYPE) \
inline TYPE require##NAME(const QJsonValue& value, const QString& what = "Value") \
{ \
return requireIsType<TYPE>(value, what); \
} \
inline TYPE ensure##NAME(const QJsonValue& value, const TYPE default_ = TYPE(), const QString& what = "Value") \
{ \
return ensureIsType<TYPE>(value, default_, what); \
} \
inline TYPE require##NAME(const QJsonObject& parent, const QString& key, const QString& what = "__placeholder__") \
{ \
return requireIsType<TYPE>(parent, key, what); \
} \
inline TYPE ensure##NAME(const QJsonObject& parent, const QString& key, const TYPE default_ = TYPE(), \
const QString& what = "__placeholder") \
{ \
return ensureIsType<TYPE>(parent, key, default_, what); \
#define JSON_HELPERFUNCTIONS(NAME, TYPE) \
inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \
{ \
return requireIsType<TYPE>(value, what); \
} \
inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \
{ \
return ensureIsType<TYPE>(value, default_, what); \
} \
inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \
{ \
return requireIsType<TYPE>(parent, key, what); \
} \
inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \
{ \
return ensureIsType<TYPE>(parent, key, default_, what); \
}
JSON_HELPERFUNCTIONS(Array, QJsonArray)
@ -278,5 +276,5 @@ JSON_HELPERFUNCTIONS(Variant, QVariant)
#undef JSON_HELPERFUNCTIONS
} // namespace Json
}
using JSONValidationError = Json::JsonException;

View file

@ -1,26 +1,42 @@
#include "KonamiCode.h"
#include <QDebug>
#include <array>
#include <QDebug>
namespace {
const std::array<Qt::Key, 10> konamiCode = { { Qt::Key_Up, Qt::Key_Up, Qt::Key_Down, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right,
Qt::Key_Left, Qt::Key_Right, Qt::Key_B, Qt::Key_A } };
const std::array<Qt::Key, 10> konamiCode =
{
{
Qt::Key_Up, Qt::Key_Up,
Qt::Key_Down, Qt::Key_Down,
Qt::Key_Left, Qt::Key_Right,
Qt::Key_Left, Qt::Key_Right,
Qt::Key_B, Qt::Key_A
}
};
}
KonamiCode::KonamiCode(QObject* parent) : QObject(parent)
{
}
KonamiCode::KonamiCode(QObject* parent) : QObject(parent) {}
void KonamiCode::input(QEvent* event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if( event->type() == QEvent::KeyPress )
{
QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event );
auto key = Qt::Key(keyEvent->key());
if (key == konamiCode[m_progress]) {
m_progress++;
} else {
if(key == konamiCode[m_progress])
{
m_progress ++;
}
else
{
m_progress = 0;
}
if (m_progress == static_cast<int>(konamiCode.size())) {
if(m_progress == static_cast<int>(konamiCode.size()))
{
m_progress = 0;
emit triggered();
}

View file

@ -2,15 +2,16 @@
#include <QKeyEvent>
class KonamiCode : public QObject {
class KonamiCode : public QObject
{
Q_OBJECT
public:
KonamiCode(QObject* parent = 0);
void input(QEvent* event);
public:
KonamiCode(QObject *parent = 0);
void input(QEvent *event);
signals:
signals:
void triggered();
private:
private:
int m_progress = 0;
};

View file

@ -1,8 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -35,43 +34,44 @@
*/
#include "LaunchController.h"
#include "Application.h"
#include "launch/steps/PrintServers.h"
#include "minecraft/auth/AccountData.h"
#include "minecraft/auth/AccountList.h"
#include "Application.h"
#include "ui/InstanceWindow.h"
#include "ui/MainWindow.h"
#include "ui/InstanceWindow.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/MSALoginDialog.h"
#include "ui/dialogs/ProfileSelectDialog.h"
#include "ui/dialogs/ProfileSetupDialog.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ProfileSetupDialog.h"
#include <QHostAddress>
#include <QHostInfo>
#include <QInputDialog>
#include <QLineEdit>
#include <QList>
#include <QPushButton>
#include <QRegularExpression>
#include <QInputDialog>
#include <QStringList>
#include <QHostInfo>
#include <QList>
#include <QHostAddress>
#include <QPushButton>
#include "BuildConfig.h"
#include "JavaCommon.h"
#include "launch/steps/TextPrint.h"
#include "tasks/Task.h"
#include "minecraft/auth/AccountTask.h"
#include "launch/steps/TextPrint.h"
LaunchController::LaunchController() : Task() {}
LaunchController::LaunchController(QObject *parent) : Task(parent)
{
}
void LaunchController::executeTask()
{
if (!m_instance) {
if (!m_instance)
{
emitFailed(tr("No instance specified!"));
return;
}
if (!JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget)) {
if(!JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget)) {
emitFailed(tr("Invalid Java arguments specified. Please fix this first."));
return;
}
@ -81,25 +81,32 @@ void LaunchController::executeTask()
void LaunchController::decideAccount()
{
if (m_accountToUse) {
if(m_accountToUse) {
return;
}
// Find an account to use.
auto accounts = APPLICATION->accounts();
if (accounts->count() <= 0 || !accounts->anyAccountIsValid()) {
if (accounts->count() <= 0)
{
// Tell the user they need to log in at least one account in order to play.
auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"),
tr("In order to play Minecraft, you must have at least one Microsoft "
"account which owns Minecraft logged in. "
"Would you like to open the account manager to add an account now?"),
QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)
->exec();
auto reply = CustomMessageBox::selectable(
m_parentWidget,
tr("No Accounts"),
tr("In order to play Minecraft, you must have at least one Microsoft or Mojang "
"account logged in. Mojang accounts can only be used offline. "
"Would you like to open the account manager to add an account now?"),
QMessageBox::Information,
QMessageBox::Yes | QMessageBox::No
)->exec();
if (reply == QMessageBox::Yes) {
if (reply == QMessageBox::Yes)
{
// Open the account manager.
APPLICATION->ShowGlobalSettings(m_parentWidget, "accounts");
} else if (reply == QMessageBox::No) {
}
else if (reply == QMessageBox::No)
{
// Do not open "profile select" dialog.
return;
}
@ -108,16 +115,20 @@ void LaunchController::decideAccount()
// Select the account to use. If the instance has a specific account set, that will be used. Otherwise, the default account will be used
auto instanceAccountId = m_instance->settings()->get("InstanceAccountId").toString();
auto instanceAccountIndex = accounts->findAccountByProfileId(instanceAccountId);
if (instanceAccountIndex == -1 || instanceAccountId.isEmpty()) {
if (instanceAccountIndex == -1) {
m_accountToUse = accounts->defaultAccount();
} else {
m_accountToUse = accounts->at(instanceAccountIndex);
}
if (!m_accountToUse) {
if (!m_accountToUse)
{
// If no default account is set, ask the user which one to use.
ProfileSelectDialog selectDialog(tr("Which account would you like to use?"), ProfileSelectDialog::GlobalDefaultCheckbox,
m_parentWidget);
ProfileSelectDialog selectDialog(
tr("Which account would you like to use?"),
ProfileSelectDialog::GlobalDefaultCheckbox,
m_parentWidget
);
selectDialog.exec();
@ -131,63 +142,13 @@ void LaunchController::decideAccount()
}
}
bool LaunchController::askPlayDemo()
{
QMessageBox box(m_parentWidget);
box.setWindowTitle(tr("Play demo?"));
box.setText(
tr("This account does not own Minecraft.\nYou need to purchase the game first to play it.\n\nDo you want to play "
"the demo?"));
box.setIcon(QMessageBox::Warning);
auto demoButton = box.addButton(tr("Play Demo"), QMessageBox::ButtonRole::YesRole);
auto cancelButton = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::NoRole);
box.setDefaultButton(cancelButton);
box.exec();
return box.clickedButton() == demoButton;
}
QString LaunchController::askOfflineName(QString playerName, bool demo, bool& ok)
{
// we ask the user for a player name
QString message = tr("Choose your offline mode player name.");
if (demo) {
message = tr("Choose your demo mode player name.");
}
QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString();
QString usedname = lastOfflinePlayerName.isEmpty() ? playerName : lastOfflinePlayerName;
QString name = QInputDialog::getText(m_parentWidget, tr("Player name"), message, QLineEdit::Normal, usedname, &ok);
if (!ok)
return {};
if (name.length()) {
usedname = name;
APPLICATION->settings()->set("LastOfflinePlayerName", usedname);
}
return usedname;
}
void LaunchController::login()
{
void LaunchController::login() {
decideAccount();
if (!m_accountToUse) {
// if no account is selected, ask about demo
if (!m_demo) {
m_demo = askPlayDemo();
}
if (m_demo) {
// we ask the user for a player name
bool ok = false;
auto name = askOfflineName("Player", m_demo, ok);
if (ok) {
m_session = std::make_shared<AuthSession>();
m_session->MakeDemo(name, MinecraftAccount::uuidFromUsername(name).toString().remove(QRegularExpression("[{}-]")));
launchInstance();
return;
}
}
// if no account is selected, we bail
// if no account is selected, we bail
if (!m_accountToUse)
{
emitFailed(tr("No account selected for launch."));
return;
}
@ -196,18 +157,15 @@ void LaunchController::login()
bool tryagain = true;
unsigned int tries = 0;
if ((m_accountToUse->accountType() != AccountType::Offline && m_accountToUse->accountState() == AccountState::Offline) ||
m_accountToUse->shouldRefresh()) {
// Force account refresh on the account used to launch the instance updating the AccountState
// only on first try and if it is not meant to be offline
auto accounts = APPLICATION->accounts();
accounts->requestRefresh(m_accountToUse->internalId());
}
while (tryagain) {
while (tryagain)
{
if (tries > 0 && tries % 3 == 0) {
auto result =
QMessageBox::question(m_parentWidget, tr("Continue launch?"),
tr("It looks like we couldn't launch after %1 tries. Do you want to continue trying?").arg(tries));
auto result = QMessageBox::question(
m_parentWidget,
tr("Continue launch?"),
tr("It looks like we couldn't launch after %1 tries. Do you want to continue trying?")
.arg(tries)
);
if (result == QMessageBox::No) {
emitAborted();
@ -221,41 +179,60 @@ void LaunchController::login()
m_accountToUse->fillSession(m_session);
// Launch immediately in true offline mode
if (m_accountToUse->accountType() == AccountType::Offline) {
if(m_accountToUse->isOffline()) {
launchInstance();
return;
}
switch (m_accountToUse->accountState()) {
switch(m_accountToUse->accountState()) {
case AccountState::Offline: {
m_session->wants_online = false;
}
/* fallthrough */
case AccountState::Online: {
if (!m_session->wants_online) {
if(!m_session->wants_online) {
// we ask the user for a player name
bool ok = false;
QString name;
if (m_offlineName.isEmpty()) {
name = askOfflineName(m_session->player_name, m_session->demo, ok);
if (!ok) {
tryagain = false;
break;
}
} else {
name = m_offlineName;
QString message = tr("Choose your offline mode player name.");
if(m_session->demo) {
message = tr("Choose your demo mode player name.");
}
m_session->MakeOffline(name);
QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString();
QString usedname = lastOfflinePlayerName.isEmpty() ? m_session->player_name : lastOfflinePlayerName;
QString name = QInputDialog::getText(
m_parentWidget,
tr("Player name"),
message,
QLineEdit::Normal,
usedname,
&ok
);
if (!ok)
{
tryagain = false;
break;
}
if (name.length())
{
usedname = name;
APPLICATION->settings()->set("LastOfflinePlayerName", usedname);
}
m_session->MakeOffline(usedname);
// offline flavored game from here :3
}
if (m_accountToUse->ownsMinecraft()) {
if (!m_accountToUse->hasProfile()) {
if(m_accountToUse->ownsMinecraft()) {
if(!m_accountToUse->hasProfile()) {
// Now handle setting up a profile name here...
ProfileSetupDialog dialog(m_accountToUse, m_parentWidget);
if (dialog.exec() == QDialog::Accepted) {
if (dialog.exec() == QDialog::Accepted)
{
tryagain = true;
continue;
} else {
}
else
{
emitFailed(tr("Received undetermined session status during login."));
return;
}
@ -263,14 +240,24 @@ void LaunchController::login()
// we own Minecraft, there is a profile, it's all ready to go!
launchInstance();
return;
} else {
}
else {
// play demo ?
if (!m_session->demo) {
m_session->demo = askPlayDemo();
}
if (m_session->demo) { // play demo here
QMessageBox box(m_parentWidget);
box.setWindowTitle(tr("Play demo?"));
box.setText(tr("This account does not own Minecraft.\nYou need to purchase the game first to play it.\n\nDo you want to play the demo?"));
box.setIcon(QMessageBox::Warning);
auto demoButton = box.addButton(tr("Play Demo"), QMessageBox::ButtonRole::YesRole);
auto cancelButton = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::NoRole);
box.setDefaultButton(cancelButton);
box.exec();
if(box.clickedButton() == demoButton) {
// play demo here
m_session->MakeDemo();
launchInstance();
} else {
}
else {
emitFailed(tr("Launch cancelled - account does not own Minecraft."));
}
}
@ -285,31 +272,53 @@ void LaunchController::login()
case AccountState::Working: {
// refresh is in progress, we need to wait for it to finish to proceed.
ProgressDialog progDialog(m_parentWidget);
if (m_online) {
if (m_online)
{
progDialog.setSkipButton(true, tr("Play Offline"));
}
auto task = m_accountToUse->currentTask();
progDialog.execWithTask(task.get());
continue;
}
// FIXME: this is missing - the meaning is that the account is queued for refresh and we should wait for that
/*
case AccountState::Queued: {
return;
}
*/
case AccountState::Expired: {
if (reauthenticateCurrentAccount())
continue;
auto errorString = tr("The account has expired and needs to be logged into manually again.");
QMessageBox::warning(
m_parentWidget,
tr("Account refresh failed"),
errorString,
QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok
);
emitFailed(errorString);
return;
}
case AccountState::Disabled: {
auto errorString = tr("The launcher's client identification has changed. Please remove this account and add it again.");
QMessageBox::warning(m_parentWidget, tr("Client identification changed"), errorString, QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok);
QMessageBox::warning(
m_parentWidget,
tr("Client identification changed"),
errorString,
QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok
);
emitFailed(errorString);
return;
}
case AccountState::Gone: {
auto errorString =
tr("The account no longer exists on the servers. It may have been migrated, in which case please add the new account "
"you migrated this one to.");
QMessageBox::warning(m_parentWidget, tr("Account gone"), errorString, QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok);
auto errorString = tr("The account no longer exists on the servers. It may have been migrated, in which case please add the new account you migrated this one to.");
QMessageBox::warning(
m_parentWidget,
tr("Account gone"),
errorString,
QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok
);
emitFailed(errorString);
return;
}
@ -318,80 +327,71 @@ void LaunchController::login()
emitFailed(tr("Failed to launch."));
}
bool LaunchController::reauthenticateCurrentAccount()
{
auto button =
QMessageBox::warning(m_parentWidget, tr("Account refresh failed"),
tr("The account has expired and needs to be reauthenticated. Do you want to reauthenticate this account?"),
QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::StandardButton::Yes);
if (button == QMessageBox::StandardButton::Yes) {
auto accounts = APPLICATION->accounts();
bool isDefault = accounts->defaultAccount() == m_accountToUse;
accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(m_accountToUse->profileId())));
if (m_accountToUse->accountType() == AccountType::MSA) {
auto newAccount = MSALoginDialog::newAccount(m_parentWidget);
accounts->addAccount(newAccount);
if (isDefault) {
accounts->setDefaultAccount(newAccount);
}
m_accountToUse = nullptr;
decideAccount();
return true;
}
emitFailed(tr("Account expired and re-login attempt failed"));
} else {
emitFailed(tr("The account has expired and needs to be reauthenticated"));
}
return false;
}
void LaunchController::launchInstance()
{
Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL");
Q_ASSERT_X(m_session.get() != nullptr, "launchInstance", "session is NULL");
if (!m_instance->reloadSettings()) {
if(!m_instance->reloadSettings())
{
QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't load the instance profile."));
emitFailed(tr("Couldn't load the instance profile."));
return;
}
m_launcher = m_instance->createLaunchTask(m_session, m_targetToJoin);
if (!m_launcher) {
m_launcher = m_instance->createLaunchTask(m_session, m_serverToJoin);
if (!m_launcher)
{
emitFailed(tr("Couldn't instantiate a launcher."));
return;
}
auto console = qobject_cast<InstanceWindow*>(m_parentWidget);
auto console = qobject_cast<InstanceWindow *>(m_parentWidget);
auto showConsole = m_instance->settings()->get("ShowConsole").toBool();
if (!console && showConsole) {
if(!console && showConsole)
{
APPLICATION->showInstanceWindow(m_instance);
}
connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch);
connect(m_launcher.get(), &LaunchTask::succeeded, this, &LaunchController::onSucceeded);
connect(m_launcher.get(), &LaunchTask::failed, this, &LaunchController::onFailed);
connect(m_launcher.get(), &LaunchTask::failed, this, &LaunchController::onFailed);
connect(m_launcher.get(), &LaunchTask::requestProgress, this, &LaunchController::onProgressRequested);
// Prepend Online and Auth Status
QString online_mode;
if (m_session->wants_online) {
if(m_session->wants_online) {
online_mode = "online";
// Prepend Server Status
QStringList servers = { "login.microsoftonline.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com" };
QStringList servers = {"authserver.mojang.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com"};
QString resolved_servers = "";
QHostInfo host_info;
m_launcher->prependStep(makeShared<PrintServers>(m_launcher.get(), servers));
for(QString server : servers) {
host_info = QHostInfo::fromName(server);
resolved_servers = resolved_servers + server + " resolves to:\n [";
if(!host_info.addresses().isEmpty()) {
for(QHostAddress address : host_info.addresses()) {
resolved_servers = resolved_servers + address.toString();
if(!host_info.addresses().endsWith(address)) {
resolved_servers = resolved_servers + ", ";
}
}
} else {
resolved_servers = resolved_servers + "N/A";
}
resolved_servers = resolved_servers + "]\n\n";
}
m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), resolved_servers, MessageLevel::Launcher));
} else {
online_mode = m_demo ? "demo" : "offline";
}
m_launcher->prependStep(
makeShared<TextPrint>(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher));
m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher));
// Prepend Version
{
auto versionString = QString("%1 version: %2 (%3)")
.arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString(), BuildConfig.BUILD_PLATFORM);
auto versionString = QString("%1 version: %2 (%3)").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString(), BuildConfig.BUILD_PLATFORM);
m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), versionString + "\n\n", MessageLevel::Launcher));
}
m_launcher->start();
@ -399,33 +399,37 @@ void LaunchController::launchInstance()
void LaunchController::readyForLaunch()
{
if (!m_profiler) {
if (!m_profiler)
{
m_launcher->proceed();
return;
}
QString error;
if (!m_profiler->check(&error)) {
if (!m_profiler->check(&error))
{
m_launcher->abort();
QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't start profiler: %1").arg(error));
emitFailed("Profiler startup failed!");
QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Profiler check for %1 failed: %2").arg(m_profiler->name(), error));
return;
}
BaseProfiler* profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this);
BaseProfiler *profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this);
connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString& message) {
QMessageBox msg(m_parentWidget);
connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString & message)
{
QMessageBox msg;
msg.setText(tr("The game launch is delayed until you press the "
"button. This is the right time to setup the profiler, as the "
"profiler server is running now.\n\n%1")
.arg(message));
"button. This is the right time to setup the profiler, as the "
"profiler server is running now.\n\n%1").arg(message));
msg.setWindowTitle(tr("Waiting."));
msg.setIcon(QMessageBox::Information);
msg.addButton(tr("&Launch"), QMessageBox::AcceptRole);
msg.addButton(tr("Launch"), QMessageBox::AcceptRole);
msg.setModal(true);
msg.exec();
m_launcher->proceed();
});
connect(profilerInstance, &BaseProfiler::abortLaunch, [this](const QString& message) {
connect(profilerInstance, &BaseProfiler::abortLaunch, [this](const QString & message)
{
QMessageBox msg;
msg.setText(tr("Couldn't start the profiler: %1").arg(message));
msg.setWindowTitle(tr("Error"));
@ -446,7 +450,8 @@ void LaunchController::onSucceeded()
void LaunchController::onFailed(QString reason)
{
if (m_instance->settings()->get("ShowConsoleOnError").toBool()) {
if(m_instance->settings()->get("ShowConsoleOnError").toBool())
{
APPLICATION->showInstanceWindow(m_instance, "console");
}
emitFailed(reason);
@ -462,18 +467,21 @@ void LaunchController::onProgressRequested(Task* task)
bool LaunchController::abort()
{
if (!m_launcher) {
if(!m_launcher)
{
return true;
}
if (!m_launcher->canAbort()) {
if(!m_launcher->canAbort())
{
return false;
}
auto response = CustomMessageBox::selectable(m_parentWidget, tr("Kill Minecraft?"),
tr("This can cause the instance to get corrupted and should only be used if Minecraft "
"is frozen for some reason"),
QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)
->exec();
if (response == QMessageBox::Yes) {
auto response = CustomMessageBox::selectable(
m_parentWidget, tr("Kill Minecraft?"),
tr("This can cause the instance to get corrupted and should only be used if Minecraft "
"is frozen for some reason"),
QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec();
if (response == QMessageBox::Yes)
{
return m_launcher->abort();
}
return false;

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -34,69 +34,83 @@
*/
#pragma once
#include <QObject>
#include <BaseInstance.h>
#include <tools/BaseProfiler.h>
#include <QObject>
#include "minecraft/launch/MinecraftServerTarget.h"
#include "minecraft/auth/MinecraftAccount.h"
#include "minecraft/launch/MinecraftTarget.h"
class InstanceWindow;
class LaunchController : public Task {
class LaunchController: public Task
{
Q_OBJECT
public:
public:
void executeTask() override;
LaunchController();
virtual ~LaunchController() = default;
LaunchController(QObject * parent = nullptr);
virtual ~LaunchController(){};
void setInstance(InstancePtr instance) { m_instance = instance; }
void setInstance(InstancePtr instance) {
m_instance = instance;
}
InstancePtr instance() { return m_instance; }
InstancePtr instance() {
return m_instance;
}
void setOnline(bool online) { m_online = online; }
void setOnline(bool online) {
m_online = online;
}
void setOfflineName(const QString& offlineName) { m_offlineName = offlineName; }
void setDemo(bool demo) {
m_demo = demo;
}
void setDemo(bool demo) { m_demo = demo; }
void setProfiler(BaseProfilerFactory *profiler) {
m_profiler = profiler;
}
void setProfiler(BaseProfilerFactory* profiler) { m_profiler = profiler; }
void setParentWidget(QWidget * widget) {
m_parentWidget = widget;
}
void setParentWidget(QWidget* widget) { m_parentWidget = widget; }
void setServerToJoin(MinecraftServerTargetPtr serverToJoin) {
m_serverToJoin = std::move(serverToJoin);
}
void setTargetToJoin(MinecraftTarget::Ptr targetToJoin) { m_targetToJoin = std::move(targetToJoin); }
void setAccountToUse(MinecraftAccountPtr accountToUse) {
m_accountToUse = std::move(accountToUse);
}
void setAccountToUse(MinecraftAccountPtr accountToUse) { m_accountToUse = std::move(accountToUse); }
QString id() { return m_instance->id(); }
QString id()
{
return m_instance->id();
}
bool abort() override;
private:
private:
void login();
void launchInstance();
void decideAccount();
bool askPlayDemo();
QString askOfflineName(QString playerName, bool demo, bool& ok);
bool reauthenticateCurrentAccount();
private slots:
private slots:
void readyForLaunch();
void onSucceeded();
void onFailed(QString reason);
void onProgressRequested(Task* task);
void onProgressRequested(Task *task);
private:
BaseProfilerFactory* m_profiler = nullptr;
private:
BaseProfilerFactory *m_profiler = nullptr;
bool m_online = true;
QString m_offlineName;
bool m_demo = false;
InstancePtr m_instance;
QWidget* m_parentWidget = nullptr;
InstanceWindow* m_console = nullptr;
QWidget * m_parentWidget = nullptr;
InstanceWindow *m_console = nullptr;
MinecraftAccountPtr m_accountToUse = nullptr;
AuthSessionPtr m_session;
shared_qobject_ptr<LaunchTask> m_launcher;
MinecraftTarget::Ptr m_targetToJoin;
MinecraftServerTargetPtr m_serverToJoin;
};

View file

@ -39,16 +39,8 @@ if [ "x$DEPS_LIST" = "x" ]; then
# Just to be sure...
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
exec -a "${ARGS[@]}"
exec -a "${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"
# Run the launcher in valgrind
# valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"

View file

@ -2,7 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022,2023 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2023 flowln <flowlnlnln@gmail.com>
* Copyright (c) 2023 flowln <flowlnlnln@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
@ -39,8 +39,7 @@
#include <QTextDecoder>
#include "MessageLevel.h"
LoggedProcess::LoggedProcess(const QTextCodec* output_codec, QObject* parent)
: QProcess(parent), m_err_decoder(output_codec), m_out_decoder(output_codec)
LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
{
// QProcess has a strange interface... let's map a lot of those into a few.
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
@ -52,7 +51,8 @@ LoggedProcess::LoggedProcess(const QTextCodec* output_codec, QObject* parent)
LoggedProcess::~LoggedProcess()
{
if (m_is_detachable) {
if(m_is_detachable)
{
setProcessState(QProcess::NotRunning);
}
}
@ -66,9 +66,14 @@ QStringList LoggedProcess::reprocess(const QByteArray& data, QTextDecoder& decod
m_leftover_line = "";
}
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed);
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, QString::SkipEmptyParts);
#else
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, Qt::SkipEmptyParts);
#endif
m_leftover_line = lines.takeLast();
if (!str.endsWith(QChar::LineFeed))
m_leftover_line = lines.takeLast();
return lines;
}
@ -90,31 +95,39 @@ void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status)
m_exit_code = exit_code;
// based on state, send signals
if (!m_is_aborting) {
if (status == QProcess::NormalExit) {
if (!m_is_aborting)
{
if (status == QProcess::NormalExit)
{
//: Message displayed on instance exit
emit log({ tr("Process exited with code %1.").arg(exit_code) }, MessageLevel::Launcher);
emit log({tr("Process exited with code %1.").arg(exit_code)}, MessageLevel::Launcher);
changeState(LoggedProcess::Finished);
} else {
}
else
{
//: Message displayed on instance crashed
if (exit_code == -1)
emit log({ tr("Process crashed.") }, MessageLevel::Launcher);
if(exit_code == -1)
emit log({tr("Process crashed.")}, MessageLevel::Launcher);
else
emit log({ tr("Process crashed with exitcode %1.").arg(exit_code) }, MessageLevel::Launcher);
emit log({tr("Process crashed with exitcode %1.").arg(exit_code)}, MessageLevel::Launcher);
changeState(LoggedProcess::Crashed);
}
} else {
}
else
{
//: Message displayed after the instance exits due to kill request
emit log({ tr("Process was killed by user.") }, MessageLevel::Error);
emit log({tr("Process was killed by user.")}, MessageLevel::Error);
changeState(LoggedProcess::Aborted);
}
}
void LoggedProcess::on_error(QProcess::ProcessError error)
{
switch (error) {
case QProcess::FailedToStart: {
emit log({ tr("The process failed to start.") }, MessageLevel::Fatal);
switch(error)
{
case QProcess::FailedToStart:
{
emit log({tr("The process failed to start.")}, MessageLevel::Fatal);
changeState(LoggedProcess::FailedToStart);
break;
}
@ -141,7 +154,7 @@ int LoggedProcess::exitCode() const
void LoggedProcess::changeState(LoggedProcess::State state)
{
if (state == m_state)
if(state == m_state)
return;
m_state = state;
emit stateChanged(m_state);
@ -154,19 +167,24 @@ LoggedProcess::State LoggedProcess::state() const
void LoggedProcess::on_stateChange(QProcess::ProcessState state)
{
switch (state) {
switch(state)
{
case QProcess::NotRunning:
break; // let's not - there are too many that handle this already.
case QProcess::Starting: {
if (m_state != LoggedProcess::NotRunning) {
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int)LoggedProcess::Starting;
break; // let's not - there are too many that handle this already.
case QProcess::Starting:
{
if(m_state != LoggedProcess::NotRunning)
{
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Starting;
}
changeState(LoggedProcess::Starting);
return;
}
case QProcess::Running: {
if (m_state != LoggedProcess::Starting) {
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int)LoggedProcess::Running;
case QProcess::Running:
{
if(m_state != LoggedProcess::Starting)
{
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Running;
}
changeState(LoggedProcess::Running);
return;

View file

@ -43,13 +43,23 @@
* This is a basic process.
* It has line-based logging support and hides some of the nasty bits.
*/
class LoggedProcess : public QProcess {
Q_OBJECT
public:
enum State { NotRunning, Starting, FailedToStart, Running, Finished, Crashed, Aborted };
class LoggedProcess : public QProcess
{
Q_OBJECT
public:
enum State
{
NotRunning,
Starting,
FailedToStart,
Running,
Finished,
Crashed,
Aborted
};
public:
explicit LoggedProcess(const QTextCodec* output_codec = QTextCodec::codecForLocale(), QObject* parent = 0);
public:
explicit LoggedProcess(QObject* parent = 0);
virtual ~LoggedProcess();
State state() const;
@ -57,31 +67,32 @@ class LoggedProcess : public QProcess {
void setDetachable(bool detachable);
signals:
signals:
void log(QStringList lines, MessageLevel::Enum level);
void stateChanged(LoggedProcess::State state);
public slots:
public slots:
/**
* @brief kill the process - equivalent to kill -9
*/
void kill();
private slots:
private slots:
void on_stdErr();
void on_stdOut();
void on_exit(int exit_code, QProcess::ExitStatus status);
void on_error(QProcess::ProcessError error);
void on_stateChange(QProcess::ProcessState);
private:
private:
void changeState(LoggedProcess::State state);
QStringList reprocess(const QByteArray& data, QTextDecoder& decoder);
private:
QTextDecoder m_err_decoder;
QTextDecoder m_out_decoder;
private:
QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale());
QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale());
QString m_leftover_line;
bool m_killed = false;
State m_state = NotRunning;

View file

@ -16,31 +16,31 @@
*/
#include <MMCTime.h>
#include <qobject.h>
#include <QDateTime>
#include <QObject>
#include <QDateTime>
#include <QTextStream>
QString Time::prettifyDuration(int64_t duration, bool noDays)
{
int seconds = (int)(duration % 60);
QString Time::prettifyDuration(int64_t duration) {
int seconds = (int) (duration % 60);
duration /= 60;
int minutes = (int)(duration % 60);
int minutes = (int) (duration % 60);
duration /= 60;
int hours = (int)(noDays ? duration : (duration % 24));
int days = (int)(noDays ? 0 : (duration / 24));
if ((hours == 0) && (days == 0)) {
int hours = (int) (duration % 24);
int days = (int) (duration / 24);
if((hours == 0)&&(days == 0))
{
return QObject::tr("%1min %2s").arg(minutes).arg(seconds);
}
if (days == 0) {
if (days == 0)
{
return QObject::tr("%1h %2min").arg(hours).arg(minutes);
}
return QObject::tr("%1d %2h %3min").arg(days).arg(hours).arg(minutes);
}
QString Time::humanReadableDuration(double duration, int precision)
{
QString Time::humanReadableDuration(double duration, int precision) {
using days = std::chrono::duration<int, std::ratio<86400>>;
QString outStr;
@ -48,10 +48,10 @@ QString Time::humanReadableDuration(double duration, int precision)
bool neg = false;
if (duration < 0) {
neg = true; // flag
duration *= -1; // invert
neg = true; // flag
duration *= -1; // invert
}
auto std_duration = std::chrono::duration<double>(duration);
auto d = std::chrono::duration_cast<days>(std_duration);
std_duration -= d;
@ -78,22 +78,22 @@ QString Time::humanReadableDuration(double duration, int precision)
if (hc) {
if (dc)
os << " ";
os << qSetFieldWidth(2) << hc << QObject::tr("h"); // hours
os << qSetFieldWidth(2) << hc << QObject::tr("h"); // hours
}
if (mc) {
if (dc || hc)
os << " ";
os << qSetFieldWidth(2) << mc << QObject::tr("m"); // minutes
os << qSetFieldWidth(2) << mc << QObject::tr("m"); // minutes
}
if (dc || hc || mc || sc) {
if (dc || hc || mc)
os << " ";
os << qSetFieldWidth(2) << sc << QObject::tr("s"); // seconds
os << qSetFieldWidth(2) << sc << QObject::tr("s"); // seconds
}
if ((msc && (precision > 0)) || !(dc || hc || mc || sc)) {
if (dc || hc || mc || sc)
os << " ";
os << qSetFieldWidth(0) << qSetRealNumberPrecision(precision) << msc << QObject::tr("ms"); // miliseconds
os << qSetFieldWidth(0) << qSetRealNumberPrecision(precision) << msc << QObject::tr("ms"); // miliseconds
}
os.flush();

View file

@ -20,15 +20,15 @@
namespace Time {
QString prettifyDuration(int64_t duration, bool noDays = false);
QString prettifyDuration(int64_t duration);
/**
* @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`.
* miliseconds are only included if `precision` is greater than 0.
*
*
* @param duration a number of seconds as floating point
* @param precision number of decmial points to display on fractons of a second, defualts to 0.
* @return QString
* @return QString
*/
QString humanReadableDuration(double duration, int precision = 0);
} // namespace Time
}

View file

@ -1,8 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* 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
@ -34,55 +33,56 @@
* limitations under the License.
*/
#include "MMCZip.h"
#include <quazip/quazip.h>
#include <quazip/quazipdir.h>
#include <quazip/quazipfile.h>
#include "MMCZip.h"
#include "FileSystem.h"
#include <QCoreApplication>
#include <QDebug>
#include <QFileInfo>
#include <QUrl>
#if defined(LAUNCHER_APPLICATION)
#include <QtConcurrentRun>
#endif
namespace MMCZip {
// ours
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction& filter)
bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, const FilterFunction filter)
{
QuaZip modZip(from.filePath());
modZip.open(QuaZip::mdUnzip);
QuaZipFile fileInsideMod(&modZip);
QuaZipFile zipOutFile(into);
for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) {
for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile())
{
QString filename = modZip.getCurrentFileName();
if (filter && !filter(filename)) {
qDebug() << "Skipping file " << filename << " from " << from.fileName() << " - filtered";
if (filter && !filter(filename))
{
qDebug() << "Skipping file " << filename << " from "
<< from.fileName() << " - filtered";
continue;
}
if (contained.contains(filename)) {
qDebug() << "Skipping already contained file " << filename << " from " << from.fileName();
if (contained.contains(filename))
{
qDebug() << "Skipping already contained file " << filename << " from "
<< from.fileName();
continue;
}
contained.insert(filename);
if (!fileInsideMod.open(QIODevice::ReadOnly)) {
if (!fileInsideMod.open(QIODevice::ReadOnly))
{
qCritical() << "Failed to open " << filename << " from " << from.fileName();
return false;
}
QuaZipNewInfo info_out(fileInsideMod.getActualFileName());
if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) {
if (!zipOutFile.open(QIODevice::WriteOnly, info_out))
{
qCritical() << "Failed to open " << filename << " in the jar";
fileInsideMod.close();
return false;
}
if (!JlCompress::copyData(fileInsideMod, zipOutFile)) {
if (!JlCompress::copyData(fileInsideMod, zipOutFile))
{
zipOutFile.close();
fileInsideMod.close();
qCritical() << "Failed to copy data of " << filename << " into the jar";
@ -94,11 +94,10 @@ bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const
return true;
}
bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool followSymlinks)
bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, bool followSymlinks)
{
QDir directory(dir);
if (!directory.exists())
return false;
if (!directory.exists()) return false;
for (auto e : files) {
auto filePath = directory.relativeFilePath(e.absoluteFilePath());
@ -110,42 +109,39 @@ bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool follow
srcPath = e.canonicalFilePath();
}
}
if (!JlCompress::compressFile(zip, srcPath, filePath))
return false;
if( !JlCompress::compressFile(zip, srcPath, filePath)) return false;
}
return true;
}
bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks)
bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks)
{
QuaZip zip(fileCompressed);
zip.setUtf8Enabled(true);
QDir().mkpath(QFileInfo(fileCompressed).absolutePath());
if (!zip.open(QuaZip::mdCreate)) {
FS::deletePath(fileCompressed);
if(!zip.open(QuaZip::mdCreate)) {
QFile::remove(fileCompressed);
return false;
}
auto result = compressDirFiles(&zip, dir, files, followSymlinks);
zip.close();
if (zip.getZipError() != 0) {
FS::deletePath(fileCompressed);
if(zip.getZipError()!=0) {
QFile::remove(fileCompressed);
return false;
}
return result;
}
#if defined(LAUNCHER_APPLICATION)
// ours
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods)
bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods)
{
QuaZip zipOut(targetJarPath);
zipOut.setUtf8Enabled(true);
if (!zipOut.open(QuaZip::mdCreate)) {
FS::deletePath(targetJarPath);
if (!zipOut.open(QuaZip::mdCreate))
{
QFile::remove(targetJarPath);
qCritical() << "Failed to open the minecraft.jar for modding";
return false;
}
@ -155,29 +151,37 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
// Modify the jar
// This needs to be done in reverse-order to ensure we respect the loading order of components
for (auto i = mods.crbegin(); i != mods.crend(); i++) {
for (auto i = mods.crbegin(); i != mods.crend(); i++)
{
const auto* mod = *i;
// do not merge disabled mods.
if (!mod->enabled())
continue;
if (mod->type() == ResourceType::ZIPFILE) {
if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) {
if (mod->type() == ResourceType::ZIPFILE)
{
if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles))
{
zipOut.close();
FS::deletePath(targetJarPath);
QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
return false;
}
} else if (mod->type() == ResourceType::SINGLEFILE) {
}
else if (mod->type() == ResourceType::SINGLEFILE)
{
// FIXME: buggy - does not work with addedFiles
auto filename = mod->fileinfo();
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) {
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName()))
{
zipOut.close();
FS::deletePath(targetJarPath);
QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
return false;
}
addedFiles.insert(filename.fileName());
} else if (mod->type() == ResourceType::FOLDER) {
}
else if (mod->type() == ResourceType::FOLDER)
{
// untested, but seems to be unused / not possible to reach
// FIXME: buggy - does not work with addedFiles
auto filename = mod->fileinfo();
@ -186,49 +190,54 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
dir.cdUp();
QString parent_dir = dir.absolutePath();
auto files = QFileInfoList();
collectFileListRecursively(what_to_zip, nullptr, &files, nullptr);
MMCZip::collectFileListRecursively(what_to_zip, nullptr, &files, nullptr);
for (auto e : files) {
if (addedFiles.contains(e.filePath()))
files.removeAll(e);
}
if (!compressDirFiles(&zipOut, parent_dir, files)) {
if (!MMCZip::compressDirFiles(&zipOut, parent_dir, files))
{
zipOut.close();
FS::deletePath(targetJarPath);
QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
return false;
}
qDebug() << "Adding folder " << filename.fileName() << " from " << filename.absoluteFilePath();
} else {
qDebug() << "Adding folder " << filename.fileName() << " from "
<< filename.absoluteFilePath();
}
else
{
// Make sure we do not continue launching when something is missing or undefined...
zipOut.close();
FS::deletePath(targetJarPath);
QFile::remove(targetJarPath);
qCritical() << "Failed to add unknown mod type" << mod->fileinfo().fileName() << "to the jar.";
return false;
}
}
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key) { return !key.contains("META-INF"); })) {
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key){return !key.contains("META-INF");}))
{
zipOut.close();
FS::deletePath(targetJarPath);
QFile::remove(targetJarPath);
qCritical() << "Failed to insert minecraft.jar contents.";
return false;
}
// Recompress the jar
zipOut.close();
if (zipOut.getZipError() != 0) {
FS::deletePath(targetJarPath);
if (zipOut.getZipError() != 0)
{
QFile::remove(targetJarPath);
qCritical() << "Failed to finalize minecraft.jar!";
return false;
}
return true;
}
#endif
// ours
QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root)
QString MMCZip::findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root)
{
QuaZipDir rootDir(zip, root);
for (auto&& fileName : rootDir.entryList(QDir::Files)) {
@ -252,23 +261,27 @@ QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringLis
}
// ours
bool findFilesInZip(QuaZip* zip, const QString& what, QStringList& result, const QString& root)
bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root)
{
QuaZipDir rootDir(zip, root);
for (auto fileName : rootDir.entryList(QDir::Files)) {
if (fileName == what) {
for(auto fileName: rootDir.entryList(QDir::Files))
{
if(fileName == what)
{
result.append(root);
return true;
}
}
for (auto fileName : rootDir.entryList(QDir::Dirs)) {
for(auto fileName: rootDir.entryList(QDir::Dirs))
{
findFilesInZip(zip, what, result, root + fileName);
}
return !result.isEmpty();
}
// ours
std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, const QString& target)
std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
{
auto target_top_dir = QUrl::fromLocalFile(target);
@ -276,24 +289,26 @@ std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, con
qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target;
auto numEntries = zip->getEntriesCount();
if (numEntries < 0) {
if(numEntries < 0) {
qWarning() << "Failed to enumerate files in archive";
return std::nullopt;
} else if (numEntries == 0) {
}
else if(numEntries == 0) {
qDebug() << "Extracting empty archives seems odd...";
return extracted;
} else if (!zip->goToFirstFile()) {
}
else if (!zip->goToFirstFile())
{
qWarning() << "Failed to seek to first file in zip";
return std::nullopt;
}
do {
QString file_name = zip->getCurrentFileName();
file_name = FS::RemoveInvalidPathChars(file_name);
if (!file_name.startsWith(subdir))
continue;
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(subdir.size()));
auto relative_file_name = QDir::fromNativeSeparators(file_name.remove(0, subdir.size()));
auto original_name = relative_file_name;
// Fix subdirs/files ending with a / getting transformed into absolute paths
@ -319,8 +334,7 @@ std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, con
}
if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) {
qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path"
<< target;
qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path" << target;
return std::nullopt;
}
@ -331,31 +345,8 @@ std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, con
}
extracted.append(target_file_path);
auto fileInfo = QFileInfo(target_file_path);
if (fileInfo.isFile()) {
auto permissions = fileInfo.permissions();
auto maxPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser |
QFileDevice::Permission::ReadGroup | QFileDevice::Permission::ReadOther;
auto minPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
QFile::setPermissions(target_file_path, QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser);
auto newPermisions = (permissions & maxPermisions) | minPermisions;
if (newPermisions != permissions) {
if (!QFile::setPermissions(target_file_path, newPermisions)) {
qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path));
}
}
} else if (fileInfo.isDir()) {
// Ensure the folder has the minimal required permissions
QFile::Permissions minimalPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup |
QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther;
QFile::Permissions currentPermissions = fileInfo.permissions();
if ((currentPermissions & minimalPermissions) != minimalPermissions) {
if (!QFile::setPermissions(target_file_path, minimalPermissions)) {
qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path));
}
}
}
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
} while (zip->goToNextFile());
@ -363,66 +354,66 @@ std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, con
}
// ours
bool extractRelFile(QuaZip* zip, const QString& file, const QString& target)
bool MMCZip::extractRelFile(QuaZip *zip, const QString &file, const QString &target)
{
return JlCompress::extractFile(zip, file, target);
}
// ours
std::optional<QStringList> extractDir(QString fileCompressed, QString dir)
std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir)
{
QuaZip zip(fileCompressed);
if (!zip.open(QuaZip::mdUnzip)) {
if (!zip.open(QuaZip::mdUnzip))
{
// check if this is a minimum size empty zip file...
QFileInfo fileInfo(fileCompressed);
if (fileInfo.size() == 22) {
if(fileInfo.size() == 22) {
return QStringList();
}
qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
;
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
return std::nullopt;
}
return extractSubDir(&zip, "", dir);
return MMCZip::extractSubDir(&zip, "", dir);
}
// ours
std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir)
std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir)
{
QuaZip zip(fileCompressed);
if (!zip.open(QuaZip::mdUnzip)) {
if (!zip.open(QuaZip::mdUnzip))
{
// check if this is a minimum size empty zip file...
QFileInfo fileInfo(fileCompressed);
if (fileInfo.size() == 22) {
if(fileInfo.size() == 22) {
return QStringList();
}
qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
;
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
return std::nullopt;
}
return extractSubDir(&zip, subdir, dir);
return MMCZip::extractSubDir(&zip, subdir, dir);
}
// ours
bool extractFile(QString fileCompressed, QString file, QString target)
bool MMCZip::extractFile(QString fileCompressed, QString file, QString target)
{
QuaZip zip(fileCompressed);
if (!zip.open(QuaZip::mdUnzip)) {
if (!zip.open(QuaZip::mdUnzip))
{
// check if this is a minimum size empty zip file...
QFileInfo fileInfo(fileCompressed);
if (fileInfo.size() == 22) {
if(fileInfo.size() == 22) {
return true;
}
qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
return false;
}
return extractRelFile(&zip, file, target);
return MMCZip::extractRelFile(&zip, file, target);
}
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter)
{
bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList *files,
MMCZip::FilterFunction excludeFilter) {
QDir rootDirectory(rootDir);
if (!rootDirectory.exists())
return false;
if (!rootDirectory.exists()) return false;
QDir directory;
if (subDir == nullptr)
@ -430,241 +421,25 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q
else
directory = QDir(subDir);
if (!directory.exists())
return false; // shouldn't ever happen
if (!directory.exists()) return false; // shouldn't ever happen
// recurse directories
QFileInfoList entries = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden);
for (const auto& e : entries) {
for (const auto& e: entries) {
if (!collectFileListRecursively(rootDir, e.filePath(), files, excludeFilter))
return false;
}
// collect files
entries = directory.entryInfoList(QDir::Files);
for (const auto& e : entries) {
for (const auto& e: entries) {
QString relativeFilePath = rootDirectory.relativeFilePath(e.absoluteFilePath());
if (excludeFilter && excludeFilter(relativeFilePath)) {
qDebug() << "Skipping file " << relativeFilePath;
continue;
}
files->append(e); // we want the original paths for compressDirFiles
files->append(e); // we want the original paths for MMCZip::compressDirFiles
}
return true;
}
#if defined(LAUNCHER_APPLICATION)
void ExportToZipTask::executeTask()
{
setStatus("Adding files...");
setProgress(0, m_files.length());
m_build_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return exportZip(); });
connect(&m_build_zip_watcher, &QFutureWatcher<ZipResult>::finished, this, &ExportToZipTask::finish);
m_build_zip_watcher.setFuture(m_build_zip_future);
}
auto ExportToZipTask::exportZip() -> ZipResult
{
if (!m_dir.exists()) {
return ZipResult(tr("Folder doesn't exist"));
}
if (!m_output.isOpen() && !m_output.open(QuaZip::mdCreate)) {
return ZipResult(tr("Could not create file"));
}
for (auto fileName : m_extra_files.keys()) {
if (m_build_zip_future.isCanceled())
return ZipResult();
QuaZipFile indexFile(&m_output);
if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileName))) {
return ZipResult(tr("Could not create:") + fileName);
}
indexFile.write(m_extra_files[fileName]);
}
for (const QFileInfo& file : m_files) {
if (m_build_zip_future.isCanceled())
return ZipResult();
auto absolute = file.absoluteFilePath();
auto relative = m_dir.relativeFilePath(absolute);
setStatus("Compressing: " + relative);
setProgress(m_progress + 1, m_progressTotal);
if (m_follow_symlinks) {
if (file.isSymLink())
absolute = file.symLinkTarget();
else
absolute = file.canonicalFilePath();
}
if (!m_exclude_files.contains(relative) && !JlCompress::compressFile(&m_output, absolute, m_destination_prefix + relative)) {
return ZipResult(tr("Could not read and compress %1").arg(relative));
}
}
m_output.close();
if (m_output.getZipError() != 0) {
return ZipResult(tr("A zip error occurred"));
}
return ZipResult();
}
void ExportToZipTask::finish()
{
if (m_build_zip_future.isCanceled()) {
FS::deletePath(m_output_path);
emitAborted();
} else if (auto result = m_build_zip_future.result(); result.has_value()) {
FS::deletePath(m_output_path);
emitFailed(result.value());
} else {
emitSucceeded();
}
}
bool ExportToZipTask::abort()
{
if (m_build_zip_future.isRunning()) {
m_build_zip_future.cancel();
// NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur
// immediately.
return true;
}
return false;
}
void ExtractZipTask::executeTask()
{
if (!m_input->isOpen() && !m_input->open(QuaZip::mdUnzip)) {
emitFailed(tr("Unable to open supplied zip file."));
return;
}
m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); });
connect(&m_zip_watcher, &QFutureWatcher<ZipResult>::finished, this, &ExtractZipTask::finish);
m_zip_watcher.setFuture(m_zip_future);
}
auto ExtractZipTask::extractZip() -> ZipResult
{
auto target = m_output_dir.absolutePath();
auto target_top_dir = QUrl::fromLocalFile(target);
QStringList extracted;
qDebug() << "Extracting subdir" << m_subdirectory << "from" << m_input->getZipName() << "to" << target;
auto numEntries = m_input->getEntriesCount();
if (numEntries < 0) {
return ZipResult(tr("Failed to enumerate files in archive"));
}
if (numEntries == 0) {
logWarning(tr("Extracting empty archives seems odd..."));
return ZipResult();
}
if (!m_input->goToFirstFile()) {
return ZipResult(tr("Failed to seek to first file in zip"));
}
setStatus("Extracting files...");
setProgress(0, numEntries);
do {
if (m_zip_future.isCanceled())
return ZipResult();
setProgress(m_progress + 1, m_progressTotal);
QString file_name = m_input->getCurrentFileName();
if (!file_name.startsWith(m_subdirectory))
continue;
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size()));
auto original_name = relative_file_name;
setStatus("Unpacking: " + relative_file_name);
// Fix subdirs/files ending with a / getting transformed into absolute paths
if (relative_file_name.startsWith('/'))
relative_file_name = relative_file_name.mid(1);
// Fix weird "folders with a single file get squashed" thing
QString sub_path;
if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) {
sub_path = relative_file_name.section('/', 0, -2) + '/';
FS::ensureFolderPathExists(FS::PathCombine(target, sub_path));
relative_file_name = relative_file_name.split('/').last();
}
QString target_file_path;
if (relative_file_name.isEmpty()) {
target_file_path = target + '/';
} else {
target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name);
if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/'))
target_file_path += '/';
}
if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) {
return ZipResult(tr("Extracting %1 was cancelled, because it was effectively outside of the target path %2")
.arg(relative_file_name, target));
}
if (!JlCompress::extractFile(m_input.get(), "", target_file_path)) {
JlCompress::removeFile(extracted);
return ZipResult(tr("Failed to extract file %1 to %2").arg(original_name, target_file_path));
}
extracted.append(target_file_path);
auto fileInfo = QFileInfo(target_file_path);
if (fileInfo.isFile()) {
auto permissions = fileInfo.permissions();
auto maxPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser |
QFileDevice::Permission::ReadGroup | QFileDevice::Permission::ReadOther;
auto minPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
auto newPermisions = (permissions & maxPermisions) | minPermisions;
if (newPermisions != permissions) {
if (!QFile::setPermissions(target_file_path, newPermisions)) {
logWarning(tr("Could not fix permissions for %1").arg(target_file_path));
}
}
} else if (fileInfo.isDir()) {
// Ensure the folder has the minimal required permissions
QFile::Permissions minimalPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup |
QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther;
QFile::Permissions currentPermissions = fileInfo.permissions();
if ((currentPermissions & minimalPermissions) != minimalPermissions) {
if (!QFile::setPermissions(target_file_path, minimalPermissions)) {
logWarning(tr("Could not fix permissions for %1").arg(target_file_path));
}
}
}
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
} while (m_input->goToNextFile());
return ZipResult();
}
void ExtractZipTask::finish()
{
if (m_zip_future.isCanceled()) {
emitAborted();
} else if (auto result = m_zip_future.result(); result.has_value()) {
emitFailed(result.value());
} else {
emitSucceeded();
}
}
bool ExtractZipTask::abort()
{
if (m_zip_future.isRunning()) {
m_zip_future.cancel();
// NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur
// immediately.
return true;
}
return false;
}
#endif
} // namespace MMCZip

View file

@ -1,8 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* 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
@ -36,204 +35,110 @@
#pragma once
#include <quazip.h>
#include <quazip/JlCompress.h>
#include <QDir>
#include <QFileInfo>
#include <QFuture>
#include <QFutureWatcher>
#include <QHash>
#include <QSet>
#include <QString>
#include <QFileInfo>
#include <QSet>
#include "minecraft/mod/Mod.h"
#include <functional>
#include <memory>
#include <quazip/JlCompress.h>
#include <optional>
#if defined(LAUNCHER_APPLICATION)
#include "minecraft/mod/Mod.h"
#endif
#include "tasks/Task.h"
namespace MMCZip
{
using FilterFunction = std::function<bool(const QString &)>;
namespace MMCZip {
using FilterFunction = std::function<bool(const QString&)>;
/**
* Merge two zip files, using a filter function
*/
bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
const FilterFunction filter = nullptr);
/**
* Merge two zip files, using a filter function
*/
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction& filter = nullptr);
/**
* Compress directory, by providing a list of files to compress
* \param zip target archive
* \param dir directory that will be compressed (to compress with relative paths)
* \param files list of files to compress
* \param followSymlinks should follow symlinks when compressing file data
* \return true for success or false for failure
*/
bool compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, bool followSymlinks = false);
/**
* Compress directory, by providing a list of files to compress
* \param zip target archive
* \param dir directory that will be compressed (to compress with relative paths)
* \param files list of files to compress
* \param followSymlinks should follow symlinks when compressing file data
* \return true for success or false for failure
*/
bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool followSymlinks = false);
/**
* Compress directory, by providing a list of files to compress
* \param fileCompressed target archive file
* \param dir directory that will be compressed (to compress with relative paths)
* \param files list of files to compress
* \param followSymlinks should follow symlinks when compressing file data
* \return true for success or false for failure
*/
bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false);
/**
* Compress directory, by providing a list of files to compress
* \param fileCompressed target archive file
* \param dir directory that will be compressed (to compress with relative paths)
* \param files list of files to compress
* \param followSymlinks should follow symlinks when compressing file data
* \return true for success or false for failure
*/
bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false);
/**
* take a source jar, add mods to it, resulting in target jar
*/
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods);
#if defined(LAUNCHER_APPLICATION)
/**
* take a source jar, add mods to it, resulting in target jar
*/
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods);
#endif
/**
* Find a single file in archive by file name (not path)
*
* \param ignore_paths paths to skip when recursing the search
*
* \return the path prefix where the file is
*/
QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths = {}, const QString& root = QString(""));
/**
* Find a single file in archive by file name (not path)
*
* \param ignore_paths paths to skip when recursing the search
*
* \return the path prefix where the file is
*/
QString findFolderOfFileInZip(QuaZip * zip, const QString & what, const QStringList& ignore_paths = {}, const QString &root = QString(""));
/**
* Find a multiple files of the same name in archive by file name
* If a file is found in a path, no deeper paths are searched
*
* \return true if anything was found
*/
bool findFilesInZip(QuaZip* zip, const QString& what, QStringList& result, const QString& root = QString());
/**
* Find a multiple files of the same name in archive by file name
* If a file is found in a path, no deeper paths are searched
*
* \return true if anything was found
*/
bool findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString());
/**
* Extract a subdirectory from an archive
*/
std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, const QString& target);
/**
* Extract a subdirectory from an archive
*/
std::optional<QStringList> extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
bool extractRelFile(QuaZip* zip, const QString& file, const QString& target);
bool extractRelFile(QuaZip *zip, const QString & file, const QString &target);
/**
* Extract a whole archive.
*
* \param fileCompressed The name of the archive.
* \param dir The directory to extract to, the current directory if left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
std::optional<QStringList> extractDir(QString fileCompressed, QString dir);
/**
* Extract a whole archive.
*
* \param fileCompressed The name of the archive.
* \param dir The directory to extract to, the current directory if left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
std::optional<QStringList> extractDir(QString fileCompressed, QString dir);
/**
* Extract a subdirectory from an archive
*
* \param fileCompressed The name of the archive.
* \param subdir The directory within the archive to extract
* \param dir The directory to extract to, the current directory if left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir);
/**
* Extract a subdirectory from an archive
*
* \param fileCompressed The name of the archive.
* \param subdir The directory within the archive to extract
* \param dir The directory to extract to, the current directory if left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir);
/**
* Extract a single file from an archive into a directory
*
* \param fileCompressed The name of the archive.
* \param file The file within the archive to extract
* \param dir The directory to extract to, the current directory if left empty.
* \return true for success or false for failure
*/
bool extractFile(QString fileCompressed, QString file, QString dir);
/**
* Extract a single file from an archive into a directory
*
* \param fileCompressed The name of the archive.
* \param file The file within the archive to extract
* \param dir The directory to extract to, the current directory if left empty.
* \return true for success or false for failure
*/
bool extractFile(QString fileCompressed, QString file, QString dir);
/**
* Populate a QFileInfoList with a directory tree recursively, while allowing to excludeFilter what shouldn't be included.
* \param rootDir directory to start off
* \param subDir subdirectory, should be nullptr for first invocation
* \param files resulting list of QFileInfo
* \param excludeFilter function to excludeFilter which files shouldn't be included (returning true means to excude)
* \return true for success or false for failure
*/
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter);
#if defined(LAUNCHER_APPLICATION)
class ExportToZipTask : public Task {
Q_OBJECT
public:
ExportToZipTask(QString outputPath,
QDir dir,
QFileInfoList files,
QString destinationPrefix = "",
bool followSymlinks = false,
bool utf8Enabled = false)
: m_output_path(outputPath)
, m_output(outputPath)
, m_dir(dir)
, m_files(files)
, m_destination_prefix(destinationPrefix)
, m_follow_symlinks(followSymlinks)
{
setAbortable(true);
m_output.setUtf8Enabled(utf8Enabled);
};
ExportToZipTask(QString outputPath,
QString dir,
QFileInfoList files,
QString destinationPrefix = "",
bool followSymlinks = false,
bool utf8Enabled = false)
: ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks, utf8Enabled) {};
virtual ~ExportToZipTask() = default;
void setExcludeFiles(QStringList excludeFiles) { m_exclude_files = excludeFiles; }
void addExtraFile(QString fileName, QByteArray data) { m_extra_files.insert(fileName, data); }
using ZipResult = std::optional<QString>;
protected:
virtual void executeTask() override;
bool abort() override;
ZipResult exportZip();
void finish();
private:
QString m_output_path;
QuaZip m_output;
QDir m_dir;
QFileInfoList m_files;
QString m_destination_prefix;
bool m_follow_symlinks;
QStringList m_exclude_files;
QHash<QString, QByteArray> m_extra_files;
QFuture<ZipResult> m_build_zip_future;
QFutureWatcher<ZipResult> m_build_zip_watcher;
};
class ExtractZipTask : public Task {
Q_OBJECT
public:
ExtractZipTask(QString input, QDir outputDir, QString subdirectory = "")
: ExtractZipTask(std::make_shared<QuaZip>(input), outputDir, subdirectory)
{}
ExtractZipTask(std::shared_ptr<QuaZip> input, QDir outputDir, QString subdirectory = "")
: m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory)
{}
virtual ~ExtractZipTask() = default;
using ZipResult = std::optional<QString>;
protected:
virtual void executeTask() override;
bool abort() override;
ZipResult extractZip();
void finish();
private:
std::shared_ptr<QuaZip> m_input;
QDir m_output_dir;
QString m_subdirectory;
QFuture<ZipResult> m_zip_future;
QFutureWatcher<ZipResult> m_zip_watcher;
};
#endif
} // namespace MMCZip
/**
* Populate a QFileInfoList with a directory tree recursively, while allowing to excludeFilter what shouldn't be included.
* \param rootDir directory to start off
* \param subDir subdirectory, should be nullptr for first invocation
* \param files resulting list of QFileInfo
* \param excludeFilter function to excludeFilter which files shouldn't be included (returning true means to excude)
* \return true for success or false for failure
*/
bool collectFileListRecursively(const QString &rootDir, const QString &subDir, QFileInfoList *files, FilterFunction excludeFilter);
}

View file

@ -1,11 +1,8 @@
#pragma once
#include <QCoreApplication>
#include <QDebug>
#include <QPixmapCache>
#include <QThread>
#include <QTime>
#include <limits>
#define GET_TYPE() \
Qt::ConnectionType type; \
@ -63,8 +60,6 @@ class PixmapCache final : public QObject {
DEFINE_FUNC_ONE_PARAM(remove, bool, const QPixmapCache::Key&)
DEFINE_FUNC_TWO_PARAM(replace, bool, const QPixmapCache::Key&, const QPixmap&)
DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, int)
DEFINE_FUNC_NO_PARAM(markCacheMissByEviciton, bool)
DEFINE_FUNC_ONE_PARAM(setFastEvictionThreshold, bool, int)
// NOTE: Every function returns something non-void to simplify the macros.
private slots:
@ -95,53 +90,6 @@ class PixmapCache final : public QObject {
return true;
}
/**
* Mark that a cache miss occurred because of a eviction if too many of these occur too fast the cache size is increased
* @return if the cache size was increased
*/
bool _markCacheMissByEviciton()
{
static constexpr uint maxCache = static_cast<uint>(std::numeric_limits<int>::max()) / 4;
static constexpr uint step = 10240;
static constexpr int oneSecond = 1000;
auto now = QTime::currentTime();
if (!m_last_cache_miss_by_eviciton.isNull()) {
auto diff = m_last_cache_miss_by_eviciton.msecsTo(now);
if (diff < oneSecond) { // less than a second ago
++m_consecutive_fast_evicitons;
} else {
m_consecutive_fast_evicitons = 0;
}
}
m_last_cache_miss_by_eviciton = now;
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
// increase the cache size
uint newSize = _cacheLimit() + step;
if (newSize >= maxCache) { // increase it until you overflow :D
newSize = maxCache;
qDebug() << m_consecutive_fast_evicitons
<< tr("pixmap cache misses by eviction happened too fast, doing nothing as the cache size reached it's limit");
} else {
qDebug() << m_consecutive_fast_evicitons
<< tr("pixmap cache misses by eviction happened too fast, increasing cache size to") << static_cast<int>(newSize);
}
_setCacheLimit(static_cast<int>(newSize));
m_consecutive_fast_evicitons = 0;
return true;
}
return false;
}
bool _setFastEvictionThreshold(int threshold)
{
m_consecutive_fast_evicitons_threshold = threshold;
return true;
}
private:
static PixmapCache* s_instance;
QTime m_last_cache_miss_by_eviciton;
int m_consecutive_fast_evicitons = 0;
int m_consecutive_fast_evicitons_threshold = 15;
};

View file

@ -16,32 +16,22 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <QDebug>
#include <QStringList>
#include <QDir>
#include <QString>
#include <QStringList>
#include <QSysInfo>
#include <QtGlobal>
#include "MangoHud.h"
#include "FileSystem.h"
#include "Json.h"
#include "MangoHud.h"
#ifdef __GLIBC__
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#define UNDEF_GNU_SOURCE
#endif
#include <dlfcn.h>
#include <linux/limits.h>
#endif
namespace MangoHud {
QString getLibraryString()
{
/**
* Guess MangoHud install location by searching for vulkan layers in this order:
/*
* Check for vulkan layers in this order:
*
* $VK_LAYER_PATH
* $XDG_DATA_DIRS (/usr/local/share/:/usr/share/)
@ -49,9 +39,8 @@ QString getLibraryString()
* /etc
* $XDG_CONFIG_DIRS (/etc/xdg)
* $XDG_CONFIG_HOME (~/.config)
*
* @returns Absolute path of libMangoHud.so if found and empty QString otherwise.
*/
QStringList vkLayerList;
{
QString home = QDir::homePath();
@ -86,7 +75,7 @@ QString getLibraryString()
vkLayerList << FS::PathCombine(xdgConfigHome, "vulkan", "implicit_layer.d");
}
for (const QString& vkLayer : vkLayerList) {
for (QString vkLayer : vkLayerList) {
// prefer to use architecture specific vulkan layers
QString currentArch = QSysInfo::currentCpuArchitecture();
@ -96,8 +85,8 @@ QString getLibraryString()
QStringList manifestNames = { QString("MangoHud.%1.json").arg(currentArch), "MangoHud.json" };
QString filePath{};
for (const QString& manifestName : manifestNames) {
QString filePath = "";
for (QString manifestName : manifestNames) {
QString tryPath = FS::PathCombine(vkLayer, manifestName);
if (QFile::exists(tryPath)) {
filePath = tryPath;
@ -108,66 +97,13 @@ QString getLibraryString()
if (filePath.isEmpty()) {
continue;
}
try {
auto conf = Json::requireDocument(filePath, vkLayer);
auto confObject = Json::requireObject(conf, vkLayer);
auto layer = Json::ensureObject(confObject, "layer");
QString libraryName = Json::ensureString(layer, "library_path");
if (libraryName.isEmpty()) {
continue;
}
if (QFileInfo(libraryName).isAbsolute()) {
return libraryName;
}
#ifdef __GLIBC__
// Check whether mangohud is usable on a glibc based system
QString libraryPath = findLibrary(libraryName);
if (!libraryPath.isEmpty()) {
return libraryPath;
}
#else
// Without glibc return recorded shared library as-is.
return libraryName;
#endif
} catch (const Exception& e) {
}
auto conf = Json::requireDocument(filePath, vkLayer);
auto confObject = Json::requireObject(conf, vkLayer);
auto layer = Json::ensureObject(confObject, "layer");
return Json::ensureString(layer, "library_path");
}
return {};
}
QString findLibrary(QString libName)
{
#ifdef __GLIBC__
const char* library = libName.toLocal8Bit().constData();
void* handle = dlopen(library, RTLD_NOW);
if (!handle) {
qCritical() << "dlopen() failed:" << dlerror();
return {};
}
char path[PATH_MAX];
if (dlinfo(handle, RTLD_DI_ORIGIN, path) == -1) {
qCritical() << "dlinfo() failed:" << dlerror();
dlclose(handle);
return {};
}
auto fullPath = FS::PathCombine(QString(path), libName);
dlclose(handle);
return fullPath;
#else
qWarning() << "MangoHud::findLibrary is not implemented on this platform";
return {};
#endif
return QString();
}
} // namespace MangoHud
#ifdef UNDEF_GNU_SOURCE
#undef _GNU_SOURCE
#undef UNDEF_GNU_SOURCE
#endif

View file

@ -24,6 +24,4 @@
namespace MangoHud {
QString getLibraryString();
QString findLibrary(QString libName);
} // namespace MangoHud
}

View file

@ -18,7 +18,7 @@
#pragma once
#include <cmark.h>
#include <QString>
#include <cmark.h>
QString markdownToHTML(const QString& markdown);

View file

@ -22,11 +22,12 @@ MessageLevel::Enum MessageLevel::getLevel(const QString& levelName)
return MessageLevel::Unknown;
}
MessageLevel::Enum MessageLevel::fromLine(QString& line)
MessageLevel::Enum MessageLevel::fromLine(QString &line)
{
// Level prefix
int endmark = line.indexOf("]!");
if (line.startsWith("!![") && endmark != -1) {
if (line.startsWith("!![") && endmark != -1)
{
auto level = MessageLevel::getLevel(line.left(endmark).mid(3));
line = line.mid(endmark + 2);
return level;

View file

@ -6,21 +6,23 @@
* @brief the MessageLevel Enum
* defines what level a log message is
*/
namespace MessageLevel {
enum Enum {
Unknown, /**< No idea what this is or where it came from */
StdOut, /**< Undetermined stderr messages */
StdErr, /**< Undetermined stdout messages */
namespace MessageLevel
{
enum Enum
{
Unknown, /**< No idea what this is or where it came from */
StdOut, /**< Undetermined stderr messages */
StdErr, /**< Undetermined stdout messages */
Launcher, /**< Launcher Messages */
Debug, /**< Debug Messages */
Info, /**< Info Messages */
Message, /**< Standard Messages */
Warning, /**< Warnings */
Error, /**< Errors */
Fatal, /**< Fatal Errors */
Debug, /**< Debug Messages */
Info, /**< Info Messages */
Message, /**< Standard Messages */
Warning, /**< Warnings */
Error, /**< Errors */
Fatal, /**< Fatal Errors */
};
MessageLevel::Enum getLevel(const QString& levelName);
MessageLevel::Enum getLevel(const QString &levelName);
/* Get message level from a line. Line is modified if it was successful. */
MessageLevel::Enum fromLine(QString& line);
} // namespace MessageLevel
MessageLevel::Enum fromLine(QString &line);
}

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