Compare commits

...

95 Commits
develop ... 8.2

Author SHA1 Message Date
Sefa Eyeoglu
284633ff91
Merge pull request #2188 from Scrumplex/bump-8.2 2024-03-03 20:37:21 +01:00
Sefa Eyeoglu
a7bfd08ce3
chore: bump to 8.2
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2024-03-03 20:35:43 +01:00
Sefa Eyeoglu
8c5cc43fc5
Merge pull request #2186 from PrismLauncher/backport-1923-to-release-8.x 2024-03-03 18:40:15 +01:00
Tayou
a8553e7897 add line back to group separator
Signed-off-by: Tayou <git@tayou.org>
(cherry picked from commit 877eb4172a737c44505b3c784e84ab710b291e5e)
2024-03-03 17:38:44 +00:00
Tayou
ca1b68b7ca
Merge pull request #2185 from PrismLauncher/backport-2178-to-release-8.x
[Backport release-8.x] (More) mrpack export fixes
2024-03-03 16:46:48 +01:00
TheKodeToad
1fe710777a Allow multiline description
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 4101fbb63433d1ee90476f55d76a7a7f0ced6ccb)
2024-03-03 15:30:54 +00:00
TheKodeToad
e349e24548 Fix server side mods being marked as unsupported on client
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 138df66355b54151302f0b143ec1c2a5a4eb11c8)
2024-03-03 15:30:54 +00:00
Sefa Eyeoglu
8ad7c37d5e
Merge pull request #2184 from PrismLauncher/backport-2160-to-release-8.x 2024-03-03 15:18:38 +01:00
TheKodeToad
b7f079b006 Fix build on Windows and formatting
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit c0eb80947db57c4b0de376213d53aac4f694e1b0)
2024-03-03 14:07:08 +00:00
TheKodeToad
c39ca8852d Fix a mistake
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit fccf857d8e5040fd64d7bc302463c1c9184f110f)
2024-03-03 14:07:08 +00:00
TheKodeToad
69a10e4b3d Add upper-bound for randomisation
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit bd0cd828269ee259dd10439c84baeaec2f1220df)
2024-03-03 14:07:08 +00:00
TheKodeToad
3b02b6b7d7 Add error when staging folder could not be created
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit df60f5cc9650a111a65ada2ea589bf7ecf9ac506)
2024-03-03 14:07:08 +00:00
TheKodeToad
82584e5d7e Delete staging on abort
Previously instances would get "stuck" in the folder

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 9574ebe480a6a837533fc4339c1f3605300555ca)
2024-03-03 14:07:08 +00:00
TheKodeToad
6c8ee3dfa9 Re-randomise key until it does not collide
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 6a9f5540d385cd2edf7e43f3ad308041571a9f83)
2024-03-03 14:07:08 +00:00
Trial97
f9ba4a746e Updated application manifest
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit aac6fea724e029b9ef4e4ed3e98b04c40149add0)
2024-03-03 14:07:08 +00:00
Trial97
e37cf125c3 Reduced temporary instance folder name
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 726b0ffb3b0488bd289d85feacea31066c464a45)
2024-03-03 14:07:08 +00:00
Sefa Eyeoglu
b7dc0b83b2
Merge pull request #2153 from PrismLauncher/backport-2141-to-release-8.x 2024-02-22 12:37:54 +01:00
crpz1
0113456dea Use empty list label code for welcome screen
Signed-off-by: crpz1 <8588315+crpz1@users.noreply.github.com>
(cherry picked from commit 9aa81cf00508c45ad6fe6b1d53c0933f1494571c)
2024-02-22 11:36:13 +00:00
seth
4add24696b
Merge pull request #2151 from DioEgizio/updates-backport
fix: fix sparkle signing backport
2024-02-22 06:10:30 -05:00
DioEgizio
c6441d231b fix: fix sparkle signing
Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
2024-02-22 11:53:48 +01:00
DioEgizio
438ccc41ef
Merge pull request #2136 from DioEgizio/updates-backport 2024-02-18 15:04:29 +01:00
Alexandru Ionut Tripon
80f863f6b6
Merge pull request #2135 from PrismLauncher/backport-2110-to-release-8.x
[Backport release-8.x] Fixed curseforge neoforge import for 1.20.1 versions
2024-02-18 15:02:54 +02:00
DioEgizio
d8e34e6c39 Merge pull request #2130 from DioEgizio/updates 2024-02-18 12:58:09 +01:00
Sefa Eyeoglu
c4cb36b9d5 Merge pull request #1951 from guihkx/sync-flatpak 2024-02-18 12:58:09 +01:00
Trial97
72c4a52119 Fixed curseforge neoforge export for 1.20.1
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit fdaaee64d193472144dd95c15c8d03f6fc4ed83a)
2024-02-18 11:42:43 +00:00
Alexandru Ionut Tripon
8eba326f62 Update launcher/modplatform/flame/FlameInstanceCreationTask.cpp
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
(cherry picked from commit 29bf6cc68c132c036f8c9ee90a337fb2db7d3cf2)
2024-02-18 11:42:43 +00:00
Trial97
69d675dc76 Fixed curseforge neoforge import for 1.20.1 versions
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 830ce246e17e381807d9394819c175dc0b730ff8)
2024-02-18 11:42:43 +00:00
Sefa Eyeoglu
e9991e99c0
Merge pull request #2129 from PrismLauncher/backport-2126-to-release-8.x
[Backport release-8.x] Use absolute path to load MangoHUD library
2024-02-17 09:22:51 +01:00
Echo J
6da36140eb Use absolute path to load MangoHUD library
This should finally fix the Arch Linux Java symbol issue with MangoHUD
enabled: https://bugs.archlinux.org/task/77183

Signed-off-by: Echo J <tcg96nougat@gmail.com>
(cherry picked from commit 01180c20e19fcdd9a902d9cd51514549eeceb531)
2024-02-17 00:57:29 +00:00
Sefa Eyeoglu
816c900edc
Merge pull request #2122 from PrismLauncher/backport-2112-to-release-8.x 2024-02-13 09:16:10 +01:00
TheKodeToad
4ebce79353 Use QTextFragment for inserting log lines
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 7cac8ad2b42f19dd3f38fe5c5ef5f93116ca5aa9)
2024-02-13 08:16:01 +00:00
Sefa Eyeoglu
256ed2a038
Merge pull request #2121 from PrismLauncher/backport-2116-to-release-8.x 2024-02-13 09:15:05 +01:00
TheKodeToad
3f30e4c691 Fix formatting issues
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 8cf2a04f317c59920e8af90d7e16fea872b2c122)
2024-02-13 08:14:52 +00:00
TheKodeToad
ad9dbb4a68 Fix immediate free in Exception.h
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 7f4c74ffbe413fcf191842606c12056fbc6493c7)
2024-02-13 08:14:52 +00:00
TheKodeToad
fb04b6fa85 Fix ResourceModel error display
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit ff989b4305d5ae4578d2154afe27ce6bf72cadaf)
2024-02-13 08:14:52 +00:00
TheKodeToad
6badd694c5 Fix NetJob use-after-free
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit ded77e61832661557a6985d9aa587f3b5d1e1f87)
2024-02-13 08:14:52 +00:00
TheKodeToad
6f5cddeec8
Merge pull request #2091 from PrismLauncher/revert-2084-backport-2050-to-release-8.x
Revert "[Backport release-8.x] Use `minecraft` instead of `.minecraft` for better accessibility"
2024-02-04 16:09:36 +00:00
TheKodeToad
25ba275230
Revert "[Backport release-8.x] Use minecraft instead of .minecraft for better accessibility" 2024-02-04 16:08:16 +00:00
Tayou
cf6ffc3954
Merge pull request #2084 from PrismLauncher/backport-2050-to-release-8.x
[Backport release-8.x] Use `minecraft` instead of `.minecraft` for better accessibility
2024-02-01 18:29:46 +01:00
TheKodeToad
5e3b7d88ec Use minecraft instead of .minecraft for better accessibility
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit f54ac25614b62b0a43d014d8b47f9faee8112d12)
2024-02-01 17:16:29 +00:00
Alexandru Ionut Tripon
1f5552dd81
Merge pull request #2064 from PrismLauncher/backport-2063-to-release-8.x
[Backport release-8.x] fix: remove alpha notice on modrinth packs
2024-01-24 18:13:58 +02:00
DioEgizio
a2aede0d94 fix: remove alpha notice on modrinth packs
with the modrinth app being released for quite a while now it's weird to still have this (tbh they've never been truly alpha)

Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
(cherry picked from commit 8de1060bfb8eba7ad6efbb2093df61992e25e3cd)
2024-01-24 16:13:32 +00:00
Sefa Eyeoglu
2fd5635b2d
Merge pull request #2057 from PrismLauncher/backport-2053-to-release-8.x 2024-01-23 09:30:59 +01:00
Trial97
bc63e1906f Fixed open path a second time
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 12bf91376411d74432f5e88c85d68cef003fd899)
2024-01-23 08:30:16 +00:00
Alexandru Ionut Tripon
a8b168dc1f
Merge pull request #2043 from PrismLauncher/backport-1913-to-release-8.x
[Backport release-8.x] Fixed askIfShouldUpdate dialog on mac
2024-01-18 22:06:12 +02:00
Trial97
fce2163350 Fixed askIfShouldUpdate dialog on mac
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 577c737d686df43ee777a3b0382b788bb34ec170)
2024-01-18 20:05:30 +00:00
Sefa Eyeoglu
5d0c83dec2
Merge pull request #1768 from Trial97/toggle_deps2 2024-01-18 18:19:17 +01:00
Sefa Eyeoglu
557fce577a
Merge pull request #1825 from Trial97/ftb_import 2024-01-18 18:19:17 +01:00
Sefa Eyeoglu
bcb5b9c9c2
Merge pull request #2034 from Scrumplex/feat/macos-signing 2024-01-18 17:00:00 +01:00
Sefa Eyeoglu
428260b91f
Merge pull request #2035 from PrismLauncher/backport-1954-to-release-8.x 2024-01-17 15:19:48 +01:00
Sefa Eyeoglu
fec1a00dc8 fix: simplify openPath calls
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit 67d088dc53337fc2abeae3ce20c89c9495f5c9be)
2024-01-17 14:19:03 +00:00
Sefa Eyeoglu
8dd898f895 chore: improve param name
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit e5b608447a1c4860343aed15ef1e1bd5940ba17d)
2024-01-17 14:19:03 +00:00
Sefa Eyeoglu
f8585602d9 chore: remove unused methods
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit a8220cd296a5b9540df07bce7be7e1f6cc28ffe0)
2024-01-17 14:19:03 +00:00
Sefa Eyeoglu
0ec3b9ce0e chore: remove maybe_unused
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit 213963257cf2b2f7a265893c3112cd71bbce6199)
2024-01-17 14:19:03 +00:00
Sefa Eyeoglu
6f5d074b4b fix: open paths directly
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit 0ccdcd23e3f3889f3e516d48bb03b27d3ddb62cc)
2024-01-17 14:19:03 +00:00
seth
7d9b95b4ce
nix: add garnix cache to nixConfig
Signed-off-by: seth <getchoo@tuta.io>
(cherry picked from commit 839adfc90bac8204581b930efc7cf2ad55629546)
2024-01-11 09:51:53 +01:00
seth
c08890391c
nix: deduplicate nixpkgs input
Signed-off-by: seth <getchoo@tuta.io>
(cherry picked from commit 26474d13749dd81869e155d64125c4b7d73ff0f2)
2024-01-11 09:51:51 +01:00
Alexandru Ionut Tripon
422135b2ab
Merge pull request #1771 from Trial97/account
Try refreshing account on launch if needed
2024-01-10 16:24:34 +02:00
Tayou
b83b25cbe3
Merge pull request #2015 from PrismLauncher/backport-2012-to-release-8.x
[Backport release-8.x] Fixed openAL placeholder
2024-01-07 00:32:17 +01:00
Trial97
4a17799449 Fixed openAL placeholder
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 219e2862f9d67d6a81ef634846237e1eec177d69)
2024-01-06 23:23:49 +00:00
Alexandru Ionut Tripon
3fe18e0810
Merge pull request #2008 from PrismLauncher/backport-1983-to-release-8.x
[Backport release-8.x] remove legacy processArguments code
2024-01-05 15:24:54 +02:00
tildejustin
5f4e0dfc14 remove legacy processArguments code
Signed-off-by: tildejustin <spacepepper3.14@gmail.com>
(cherry picked from commit eca5d88576900d32edea2000258f37d700af5f06)
2024-01-05 12:27:13 +00:00
Alexandru Ionut Tripon
afc73ffa16
Merge pull request #2007 from PrismLauncher/backport-2005-to-release-8.x
[Backport release-8.x] Format java code
2024-01-04 22:29:21 +02:00
Trial97
7ae3c05f1d Format java code
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 786e03571b529c610ada33d87581e65cefc4cae3)
2024-01-04 20:18:56 +00:00
Alexandru Ionut Tripon
744b81fc30
Merge pull request #2004 from PrismLauncher/backport-1979-to-release-8.x
[Backport release-8.x] Mod downloader page improvements
2024-01-03 22:47:27 +02:00
lumiscosity
f4617d7c90 fix styling
Signed-off-by: lumiscosity <averyrudelphe@gmail.com>
(cherry picked from commit c29af83dc328612d340c54e3ec037b1264d66d51)
2024-01-03 20:46:44 +00:00
lumiscosity
a00966986c move simplify call to drawing
Signed-off-by: lumiscosity <averyrudelphe@gmail.com>
(cherry picked from commit 1e48bf838db3f72fd391ecf9c91aa8e693daf213)
2024-01-03 20:46:44 +00:00
lumiscosity
42ea2ecbc8 Strip newlines in mod descriptions
Mirrors Modrinth page behaviour.

Signed-off-by: lumiscosity <averyrudelphe@gmail.com>
(cherry picked from commit 1bf386603dece42df0815495ae6b43b0e2d665c8)
2024-01-03 20:46:44 +00:00
lumiscosity
568734eb8a add archived project warning
Signed-off-by: lumiscosity <averyrudelphe@gmail.com>
(cherry picked from commit 358cf5b3485cfb23cce226c02fef03523468d4eb)
2024-01-03 20:46:44 +00:00
Alexandru Ionut Tripon
3b578ecfe0
Merge pull request #2003 from PrismLauncher/backport-1978-to-release-8.x
[Backport release-8.x] Fixed openURL cast for resource
2024-01-03 22:44:59 +02:00
Trial97
bb843b86ab Fixed openURL cast for resource
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 9a33bdcf501655dbef2981094ee05662f34cd671)
2024-01-03 20:44:44 +00:00
Sefa Eyeoglu
b12dffe420
Merge pull request #2001 from PrismLauncher/backport-1999-to-release-8.x 2024-01-03 18:26:32 +01:00
TheKodeToad
36d8ffd3c1 Pass proxy
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 345cdf5c9d6417b5c2ce15e8967de3886d1113f1)
2024-01-03 17:25:14 +00:00
TheKodeToad
f31bf5a1f1 Add online mode fix for legacy versions; minor refactors in legacy
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 05e453309629d2c4cc1cf4b1322669cb8aed21ff)
2024-01-03 17:25:14 +00:00
Sefa Eyeoglu
270accf6d7
Merge pull request #2000 from PrismLauncher/backport-1998-to-release-8.x 2024-01-03 18:15:52 +01:00
TheKodeToad
6816364354 Happy new year :D
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 1130fe851012b3d7deff207db495b455501e17fc)
2024-01-03 17:13:30 +00:00
TheKodeToad
2f3715beed
Merge pull request #1968 from PrismLauncher/backport-1964-to-release-8.x
[Backport release-8.x] Don't log Microsoft refresh tokens
2023-12-19 11:30:19 +00:00
Sefa Eyeoglu
6912782f4b fix: don't log katabasis by default
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
(cherry picked from commit a1e3901b3f4e4910e3fe40206879907a1f7ff03c)
2023-12-19 11:25:00 +00:00
Sefa Eyeoglu
d8809197f8
Merge pull request #1962 from PrismLauncher/backport-1960-to-release-8.x
[Backport release-8.x] Minor spelling fixes
2023-12-16 17:28:07 +01:00
lumiscosity
199312dd5a one more
Signed-off-by: lumiscosity <averyrudelphe@gmail.com>
(cherry picked from commit eb8e150fa804f0b34b21d7a95e840516cd119c97)
2023-12-16 16:25:35 +00:00
lumiscosity
a108a01be7 Update ModFolderPage.cpp
Signed-off-by: lumiscosity <averyrudelphe@gmail.com>
(cherry picked from commit 56397446dbe05ea346359f401587383d4a1a84dd)
2023-12-16 16:25:35 +00:00
lumiscosity
7568e90655 Update GetModDependenciesTask.cpp
Signed-off-by: lumiscosity <averyrudelphe@gmail.com>
(cherry picked from commit 8cbdecc454de7ee8f3dadbd00c35b339a8581c45)
2023-12-16 16:25:35 +00:00
Alexandru Ionut Tripon
c216713844
Merge pull request #1952 from PrismLauncher/backport-1947-to-release-8.x
[Backport release-8.x] fix: Minimized windows dont get shown
2023-12-13 20:31:31 +02:00
TheKodeToad
4dfe925cf9 Revert change to .gitignore
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
(cherry picked from commit 2391ad200f80ef6a84ffef246ed236d66928f0e4)
2023-12-13 18:24:40 +00:00
theMackabu
534b4e4aa3 remove: maidfile.toml (local build tool)
Signed-off-by: theMackabu <theMackabu@gmail.com>
(cherry picked from commit 7c33a8e448d238960b89f1e1c2f14a18d12040eb)
2023-12-13 18:24:40 +00:00
theMackabu
9aa8e1d7ad clang-format: fix formatting
Signed-off-by: theMackabu <theMackabu@gmail.com>
(cherry picked from commit 201c9783d56cef2e1bba1fdbffdc377bdf294468)
2023-12-13 18:24:40 +00:00
theMackabu
ff146d5855 #1945 resolve issue on windows
Signed-off-by: theMackabu <theMackabu@gmail.com>
(cherry picked from commit 836f74cd4629748ce8056f9f186284fc55d0d8b4)
2023-12-13 18:24:40 +00:00
theMackabu
d0cb62cb62 #1945 resolve minimized windows pull from dock
Signed-off-by: theMackabu <theMackabu@gmail.com>
(cherry picked from commit 3567369d44c6ba9a3b702caf2db18b126f1b4fc4)
2023-12-13 18:24:40 +00:00
timoreo
743d48744a
Merge pull request #1712 from Trial97/current_time 2023-12-11 16:14:52 +01:00
Sefa Eyeoglu
2837ae8bff
Merge pull request #1738 from Trial97/some_removals 2023-12-11 11:31:55 +01:00
Sefa Eyeoglu
3e54d4ddbb
Merge pull request #1732 from Trial97/update_file2 2023-12-11 11:25:10 +01:00
Sefa Eyeoglu
426deb4454
Merge pull request #1694 from Trial97/concurrent 2023-12-11 11:20:17 +01:00
Sefa Eyeoglu
52ccf3d93b
Merge pull request #1857 from Trial97/copy_time 2023-12-11 11:02:12 +01:00
Sefa Eyeoglu
b6a366ed97
Merge pull request #1944 from PrismLauncher/backport-1908-to-release-8.x 2023-12-11 11:00:57 +01:00
DioEgizio
1ba6ed9fe9 chore: update qt to qt 6.6.1
Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
(cherry picked from commit eb011e6729fe49da867e6ae1ab2524424eedac46)
2023-12-11 10:00:43 +00:00
109 changed files with 917 additions and 654 deletions

View File

@ -21,8 +21,23 @@ on:
WINDOWS_CODESIGN_PASSWORD:
description: Password for signing Windows builds
required: false
CACHIX_AUTH_TOKEN:
description: Private token for authenticating against Cachix cache
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
@ -61,7 +76,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: ''
qt_version: '6.6.0'
qt_version: '6.6.2'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@ -73,7 +88,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: 'win64_msvc2019_arm64'
qt_version: '6.6.0'
qt_version: '6.6.2'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@ -83,7 +98,7 @@ jobs:
qt_ver: 6
qt_host: mac
qt_arch: ''
qt_version: '6.6.0'
qt_version: '6.6.2'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@ -336,6 +351,20 @@ jobs:
# 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: |
@ -343,9 +372,34 @@ jobs:
cd ${{ env.INSTALL_DIR }}
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher"
sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}'
else
APPLE_CODESIGN_ID='-'
fi
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
mv "PrismLauncher.app" "Prism Launcher.app"
tar -czf ../PrismLauncher.tar.gz *
- 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
- name: Make Sparkle signature (macOS)
if: matrix.name == 'macOS'
@ -353,7 +407,7 @@ jobs:
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
brew install openssl@3
echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem
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)
signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
rm ed25519-priv.pem
cat >> $GITHUB_STEP_SUMMARY << EOF
### Artifact Information :information_source:
@ -520,7 +574,7 @@ jobs:
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher.tar.gz
path: PrismLauncher.zip
- name: Upload binary zip (Windows)
if: runner.os == 'Windows'

View File

@ -32,6 +32,11 @@ jobs:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
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 }}

View File

@ -16,7 +16,12 @@ jobs:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
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 }}
@ -46,8 +51,8 @@ jobs:
mv PrismLauncher-Linux-Qt5*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt5-${{ 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.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
mv PrismLauncher-macOS-Legacy*/PrismLauncher.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip
tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
@ -102,6 +107,6 @@ jobs:
PrismLauncher-Windows-MSVC-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe
PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
PrismLauncher-macOS-${{ env.VERSION }}.zip
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
PrismLauncher-${{ env.VERSION }}.tar.gz

View File

@ -179,7 +179,7 @@ set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRIN
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 8)
set(Launcher_VERSION_MINOR 0)
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")
@ -377,12 +377,12 @@ 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 "© 2022-2023 ${Launcher_Copyright_Mac}")
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_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_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.5.2/Sparkle-2.5.2.tar.xz" CACHE STRING "URL to Sparkle release archive")
set(MACOSX_SPARKLE_SHA256 "572dd67ae398a466f19f343a449e1890bac1ef74885b4739f68f979a8a89884b" CACHE STRING "SHA256 checksum for Sparkle release archive")
set(MACOSX_SPARKLE_DIR "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
# directories to look for dependencies
@ -504,11 +504,10 @@ else()
endif()
if(NOT cmark_FOUND)
message(STATUS "Using bundled cmark")
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)
set(BUILD_TESTING 0)
set(BUILD_SHARED_LIBS 0)
add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser
add_library(cmark::cmark ALIAS cmark_static)
add_library(cmark::cmark ALIAS cmark)
else()
message(STATUS "Using system cmark")
endif()

View File

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

22
flake.lock generated
View File

@ -18,7 +18,9 @@
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
"nixpkgs-lib": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1698882062,
@ -120,24 +122,6 @@
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"dir": "lib",
"lastModified": 1698611440,
"narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735",
"type": "github"
},
"original": {
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": [

View File

@ -1,15 +1,25 @@
{
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://cache.garnix.io"];
extra-trusted-public-keys = ["cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g="];
};
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
flake-parts = {
url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs";
};
nix-filter.url = "github:numtide/nix-filter";
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";
inputs = {
nixpkgs.follows = "nixpkgs";
nixpkgs-stable.follows = "nixpkgs";
flake-compat.follows = "flake-compat";
};
};
flake-compat = {
url = "github:edolstra/flake-compat";

View File

@ -1,6 +1,6 @@
id: org.prismlauncher.PrismLauncher
runtime: org.kde.Platform
runtime-version: "5.15-23.08"
runtime-version: 5.15-23.08
sdk: org.kde.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.openjdk17
@ -104,18 +104,15 @@ modules:
- install -Dm755 ../data/gamemoderun -t /app/bin
sources:
- type: archive
archive-type: tar-gzip
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.7
sha256: 57ce73ba605d1cf12f8d13725006a895182308d93eba0f69f285648449641803
dest-filename: gamemode.tar.gz
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.1
sha256: 969cf85b5ca3944f3e315cd73a0ee9bea4f9c968cd7d485e9f4745bc1e679c4e
x-checker-data:
type: json
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest
version-query: .tag_name
url-query: .tarball_url
timestamp-query: .published_at
# from https://github.com/flathub/net.gaijin.WarThunder/blob/7ea6f7a9f84b9c77150c003a7059dc03f8dcbc7f/gamemode.patch
- type: patch
path: patches/gamemode.patch
cleanup:
- /include
- /lib/pkgconfig

View File

@ -1,12 +0,0 @@
diff -ruN a/common/common-pidfds.c b/common/common-pidfds.c
--- a/common/common-pidfds.c 2021-02-18 20:00:12.000000000 +0100
+++ b/common/common-pidfds.c 2023-09-07 08:57:42.954362763 +0200
@@ -58,6 +58,8 @@
{
return (int)syscall(__NR_pidfd_open, pid, flags);
}
+#else
+#include <sys/pidfd.h>
#endif
/* pidfd functions */

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

View File

@ -494,8 +494,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
{
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME) << ", (c) 2022-2023 "
<< qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
qDebug() << "Version : " << BuildConfig.printableVersionString();
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
@ -749,6 +748,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("ModrinthToken", "");
m_settings->registerSetting("UserAgentOverride", "");
// FTBApp instances
m_settings->registerSetting("FTBAppInstancesPath", "");
// Init page provider
{
m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings"));
@ -1511,6 +1513,17 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
auto& window = extras.window;
if (window) {
// If the window is minimized on macOS or Windows, activate and bring it up
#ifdef Q_OS_MACOS
if (window->isMinimized()) {
window->setWindowState(window->windowState() & ~Qt::WindowMinimized);
}
#elif defined(Q_OS_WIN)
if (window->isMinimized()) {
window->showNormal();
}
#endif
window->raise();
window->activateWindow();
} else {
@ -1518,6 +1531,7 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
m_openWindows++;
connect(window, &InstanceWindow::isClosing, this, &Application::on_windowClose);
}
if (!page.isEmpty()) {
window->selectPage(page);
}

View File

@ -64,6 +64,8 @@ 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", "[]");

View File

@ -37,140 +37,33 @@
#include <QDesktopServices>
#include <QDir>
#include <QProcess>
/**
* 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 <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.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
#include "FileSystem.h"
namespace DesktopServices {
bool openDirectory(const QString& path, [[maybe_unused]] bool ensureExists)
bool openPath(const QFileInfo& path, bool ensureFolderPathExists)
{
qDebug() << "Opening directory" << path;
QDir parentPath;
QDir dir(path);
if (ensureExists && !dir.exists()) {
parentPath.mkpath(dir.absolutePath());
qDebug() << "Opening path" << path;
if (ensureFolderPathExists) {
FS::ensureFolderPathExists(path);
}
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
return IndirectOpen(f);
}
#endif
return f();
return openUrl(QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()));
}
bool openFile(const QString& path)
bool openPath(const QString& path, bool ensureFolderPathExists)
{
qDebug() << "Opening file" << path;
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
return IndirectOpen(f);
} else {
return f();
}
#else
return f();
#endif
}
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 (!isSandbox()) {
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
return openPath(QFileInfo(path), ensureFolderPathExists);
}
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 (!isSandbox()) {
// 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)
{
qDebug() << "Opening URL" << url.toString();
auto f = [&]() { return QDesktopServices::openUrl(url); };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
return IndirectOpen(f);
} else {
return f();
}
#else
return f();
#endif
return QDesktopServices::openUrl(url);
}
bool isFlatpak()
@ -191,9 +84,4 @@ bool isSnap()
#endif
}
bool isSandbox()
{
return isSnap() || isFlatpak();
}
} // namespace DesktopServices

View File

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

View File

@ -1,4 +1,37 @@
// Licensed under the Apache-2.0 license. See README.md for details.
// 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.
*/
#pragma once
@ -8,12 +41,12 @@
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()) {}
Exception(const QString& message) : std::exception(), m_message(message.toUtf8()) { qCritical() << "Exception:" << message; }
Exception(const Exception& other) : std::exception(), m_message(other.m_message) {}
virtual ~Exception() noexcept {}
const char* what() const noexcept { return m_message.toLatin1().constData(); }
QString cause() const { return m_message; }
const char* what() const noexcept { return m_message.constData(); }
QString cause() const { return QString::fromUtf8(m_message); }
private:
QString m_message;
QByteArray m_message;
};

View File

@ -272,15 +272,19 @@ bool ensureFilePathExists(QString filenamepath)
return success;
}
bool ensureFolderPathExists(QString foldernamepath)
bool ensureFolderPathExists(const QFileInfo folderPath)
{
QFileInfo a(foldernamepath);
QDir dir;
QString ensuredPath = a.filePath();
QString ensuredPath = folderPath.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

View File

@ -91,7 +91,13 @@ 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(QString filenamepath);
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);
/**
* @brief Copies a directory and it's contents from src to dest

View File

@ -142,9 +142,8 @@ void InstanceCopyTask::copyFinished()
if (!m_keepPlaytime) {
inst->resetTimePlayed();
}
if (m_useLinks)
inst->addLinkedInstanceId(m_origInstance->id());
if (m_useLinks) {
inst->addLinkedInstanceId(m_origInstance->id());
auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt"));
QByteArray allowed_symlinks;

View File

@ -38,6 +38,7 @@
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QFileSystemWatcher>
#include <QJsonArray>
#include <QJsonDocument>
@ -847,14 +848,16 @@ class InstanceStaging : public Task {
const unsigned maxBackoff = 16;
public:
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))
InstanceStaging(InstanceList* parent, InstanceTask* child, SettingsObjectPtr settings)
: m_parent(parent), backoff(minBackoff, maxBackoff)
{
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::failed, this, &InstanceStaging::childFailed);
connect(child, &Task::aborted, this, &InstanceStaging::childAborted);
@ -866,7 +869,7 @@ class InstanceStaging : public Task {
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceeded);
}
virtual ~InstanceStaging(){};
virtual ~InstanceStaging() {}
// FIXME/TODO: add ability to abort during instance commit retries
bool abort() override
@ -881,14 +884,22 @@ class InstanceStaging : public Task {
bool canAbort() const override { return (m_child && m_child->canAbort()); }
protected:
virtual void executeTask() override { m_child->start(); }
virtual void executeTask() override
{
if (m_stagingPath.isNull()) {
emitFailed(tr("Could not create staging folder"));
return;
}
m_child->start();
}
QStringList warnings() const override { return m_child->warnings(); }
private slots:
void childSucceeded()
{
unsigned sleepTime = backoff();
if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, *m_child.get())) {
if (m_parent->commitStagedInstance(m_stagingPath, *m_child.get(), m_child->group(), *m_child.get())) {
emitSucceeded();
return;
}
@ -897,7 +908,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_instance_name.name() << "Initiating backoff:" << sleepTime;
qDebug() << "Failed to commit instance" << m_child->name() << "Initiating backoff:" << sleepTime;
m_backoffTimer.start(sleepTime * 500);
}
void childFailed(const QString& reason)
@ -906,7 +917,11 @@ class InstanceStaging : public Task {
emitFailed(reason);
}
void childAborted() { emitAborted(); }
void childAborted()
{
m_parent->destroyStagingPath(m_stagingPath);
emitAborted();
}
private:
InstanceList* m_parent;
@ -918,34 +933,35 @@ 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)
{
auto stagingPath = getStagedInstancePath();
task->setStagingPath(stagingPath);
task->setParentSettings(m_globalSettings);
return new InstanceStaging(this, task, stagingPath, *task, task->group());
return new InstanceStaging(this, task, m_globalSettings);
}
QString InstanceList::getStagedInstancePath()
{
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();
}
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 {};
#ifdef Q_OS_WIN32
auto tempPath = FS::PathCombine(m_instDir, tempDir);
SetFileAttributesA(tempPath.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
SetFileAttributesA(tempRoot.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
#endif
return path;
return result;
}
bool InstanceList::commitStagedInstance(const QString& path,

View File

@ -2,6 +2,8 @@
#include "ui/dialogs/CustomMessageBox.h"
#include <QPushButton>
InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name)
{
auto dialog =
@ -27,16 +29,15 @@ ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name)
"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::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"));
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);
info->exec();
if (info->clickedButton() == info->button(QMessageBox::Ok))
if (info->clickedButton() == update)
return ShouldUpdate::Update;
if (info->clickedButton() == info->button(QMessageBox::Abort))
if (info->clickedButton() == skip)
return ShouldUpdate::SkipUpdating;
return ShouldUpdate::Cancel;
}

View File

@ -42,7 +42,6 @@
#include "ui/InstanceWindow.h"
#include "ui/MainWindow.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ProfileSelectDialog.h"
#include "ui/dialogs/ProfileSetupDialog.h"
#include "ui/dialogs/ProgressDialog.h"
@ -144,6 +143,12 @@ void LaunchController::login()
bool tryagain = true;
unsigned int tries = 0;
if (m_accountToUse->accountType() != AccountType::Offline && m_accountToUse->accountState() == AccountState::Offline) {
// 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) {
if (tries > 0 && tries % 3 == 0) {
auto result =
@ -250,12 +255,6 @@ void LaunchController::login()
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: {
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,

View File

@ -594,9 +594,6 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
QStringList preloadList;
if (auto value = env.value("LD_PRELOAD"); !value.isEmpty())
preloadList = value.split(QLatin1String(":"));
QStringList libPaths;
if (auto value = env.value("LD_LIBRARY_PATH"); !value.isEmpty())
libPaths = value.split(QLatin1String(":"));
auto mangoHudLibString = MangoHud::getLibraryString();
if (!mangoHudLibString.isEmpty()) {
@ -604,18 +601,16 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
QString libPath = mangoHudLib.absolutePath();
auto appendLib = [libPath, &preloadList](QString fileName) {
if (QFileInfo(FS::PathCombine(libPath, fileName)).exists())
preloadList << fileName;
preloadList << FS::PathCombine(libPath, fileName);
};
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
appendLib("libMangoHud_dlsym.so");
appendLib("libMangoHud_opengl.so");
appendLib(mangoHudLib.fileName());
libPaths << libPath;
}
env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":")));
env.insert("LD_LIBRARY_PATH", libPaths.join(QLatin1String(":")));
env.insert("MANGOHUD", "1");
}

View File

@ -157,20 +157,6 @@ void MojangVersionFormat::readVersionProperties(const QJsonObject& in, VersionFi
Bits::readString(in, "id", out->minecraftVersion);
Bits::readString(in, "mainClass", out->mainClass);
Bits::readString(in, "minecraftArguments", out->minecraftArguments);
if (out->minecraftArguments.isEmpty()) {
QString processArguments;
Bits::readString(in, "processArguments", processArguments);
QString toCompare = processArguments.toLower();
if (toCompare == "legacy") {
out->minecraftArguments = " ${auth_player_name} ${auth_session}";
} else if (toCompare == "username_session") {
out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}";
} else if (toCompare == "username_session_version") {
out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}";
} else if (!toCompare.isEmpty()) {
out->addProblem(ProblemSeverity::Error, QObject::tr("processArguments is set to unknown value '%1'").arg(processArguments));
}
}
Bits::readString(in, "type", out->type);
Bits::readString(in, "assets", out->assets);

View File

@ -52,8 +52,6 @@
#include <FileSystem.h>
#include <QSaveFile>
#include <chrono>
enum AccountListVersion { MojangMSA = 3 };
AccountList::AccountList(QObject* parent) : QAbstractListModel(parent)

View File

@ -306,7 +306,6 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>
auto removed_it = m_resources.begin() + removed_index;
Q_ASSERT(removed_it != m_resources.end());
Q_ASSERT(removed_set.contains(removed_it->get()->internal_id()));
if ((*removed_it)->isResolving()) {
auto ticket = (*removed_it)->resolutionTicket();

View File

@ -182,7 +182,9 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType };
ResourceAPI::DependencySearchCallbacks callbacks;
callbacks.on_fail = [](QString reason, int) {
qCritical() << tr("A network error occurred. Could not load project dependencies:%1").arg(reason);
};
callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, [[maybe_unused]] auto& pack) {
try {
QJsonArray arr;

View File

@ -149,6 +149,7 @@ void EnsureMetadataTask::executeTask()
if (m_current_task)
m_current_task.reset();
});
connect(project_task.get(), &Task::failed, this, &EnsureMetadataTask::emitFailed);
m_current_task = project_task;
project_task->start();

View File

@ -122,6 +122,8 @@ struct ExtraPackData {
QString wikiUrl;
QString discordUrl;
QString status;
QString body;
};

View File

@ -96,6 +96,7 @@ class ResourceAPI {
};
struct VersionSearchCallbacks {
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
std::function<void(QString const& reason, int network_error_code)> on_fail;
};
struct ProjectInfoArgs {
@ -118,6 +119,7 @@ class ResourceAPI {
struct DependencySearchCallbacks {
std::function<void(QJsonDocument&, const ModPlatform::Dependency&)> on_succeed;
std::function<void(QString const& reason, int network_error_code)> on_fail;
};
public:

View File

@ -119,7 +119,6 @@ void Flame::FileResolvingTask::netJobFinished()
connect(m_checkJob.get(), &NetJob::failed, this, [this, step_progress](QString reason) {
step_progress->state = TaskStepState::Failed;
stepProgress(*step_progress);
emitFailed(reason);
});
connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress);
connect(m_checkJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {

View File

@ -24,7 +24,7 @@ bool FlameCheckUpdate::abort()
return true;
}
ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
ModPlatform::IndexedPack FlameCheckUpdate::getProjectInfo(ModPlatform::IndexedVersion& ver_info)
{
ModPlatform::IndexedPack pack;
@ -57,6 +57,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
}
});
connect(get_project_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed);
QObject::connect(get_project_job, &NetJob::finished, [&loop, get_project_job] {
get_project_job->deleteLater();
loop.quit();
@ -68,7 +69,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
return pack;
}
ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileId)
{
ModPlatform::IndexedVersion ver;
@ -100,7 +101,7 @@ ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
qDebug() << doc;
}
});
connect(get_file_info_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed);
QObject::connect(get_file_info_job, &NetJob::finished, [&loop, get_file_info_job] {
get_file_info_job->deleteLater();
loop.quit();

View File

@ -22,6 +22,9 @@ class FlameCheckUpdate : public CheckUpdateTask {
void executeTask() override;
private:
ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info);
ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId);
NetJob* m_net_job = nullptr;
bool m_was_aborted = false;

View File

@ -227,6 +227,7 @@ bool FlameCreationTask::updateInstance()
m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path));
}
});
connect(job.get(), &Task::failed, this, [](QString reason) { qCritical() << "Failed to get files: " << reason; });
connect(job.get(), &Task::finished, &loop, &QEventLoop::quit);
m_process_update_file_info_job = job;
@ -353,6 +354,8 @@ bool FlameCreationTask::createInstance()
auto id = loader.id;
if (id.startsWith("neoforge-")) {
id.remove("neoforge-");
if (id.startsWith("1.20.1-"))
id.remove("1.20.1-"); // this is a mess for curseforge
loaderType = "neoforge";
loaderUid = "net.neoforged";
} else if (id.startsWith("forge-")) {
@ -427,6 +430,9 @@ bool FlameCreationTask::createInstance()
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty())
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
else
instance.setManagedPack("flame", "", name(), "", "");
instance.setName(name());
m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack));

View File

@ -323,6 +323,7 @@ void FlamePackExportTask::getProjectsInfo()
}
buildZip();
});
connect(projTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed);
task.reset(projTask);
task->start();
}
@ -397,8 +398,12 @@ QByteArray FlamePackExportTask::generateIndex()
id = "fabric-" + fabric->getVersion();
else if (forge != nullptr)
id = "forge-" + forge->getVersion();
else if (neoforge != nullptr)
id = "neoforge-" + neoforge->getVersion();
else if (neoforge != nullptr) {
id = "neoforge-";
if (minecraft->m_version == "1.20.1")
id += "1.20.1-";
id += neoforge->getVersion();
}
version["modLoaders"] = QJsonArray();
if (!id.isEmpty()) {
QJsonObject loader;

View File

@ -43,7 +43,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
callbacks.on_succeed(doc);
});
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) {
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
int network_error_code = -1;
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
@ -102,6 +102,13 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
callbacks.on_succeed(doc, args.pack);
});
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
int network_error_code = -1;
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
callbacks.on_fail(reason, network_error_code);
});
return netJob;
}
@ -146,6 +153,12 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
callbacks.on_succeed(doc, args.dependency);
});
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
int network_error_code = -1;
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
callbacks.on_fail(reason, network_error_code);
});
return netJob;
}

View File

@ -72,9 +72,7 @@ void ModrinthCheckUpdate::executeTask()
auto response = std::make_shared<QByteArray>();
auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response);
QEventLoop lock;
connect(job.get(), &Task::succeeded, this, [this, response, &mappings, best_hash_type, job] {
connect(job.get(), &Task::succeeded, this, [this, response, mappings, best_hash_type, job] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
@ -82,7 +80,7 @@ void ModrinthCheckUpdate::executeTask()
<< " reason: " << parse_error.errorString();
qWarning() << *response;
failed(parse_error.errorString());
emitFailed(parse_error.errorString());
return;
}
@ -167,19 +165,17 @@ void ModrinthCheckUpdate::executeTask()
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
}
} catch (Json::JsonException& e) {
failed(e.cause() + " : " + e.what());
emitFailed(e.cause() + " : " + e.what());
return;
}
emitSucceeded();
});
connect(job.get(), &Task::finished, &lock, &QEventLoop::quit);
connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::emitFailed);
setStatus(tr("Waiting for the API response from Modrinth..."));
setProgress(1, 3);
m_net_job = qSharedPointerObjectCast<NetJob, Task>(job);
job->start();
lock.exec();
emitSucceeded();
}

View File

@ -226,6 +226,9 @@ bool ModrinthCreationTask::createInstance()
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty())
instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
else
instance.setManagedPack("modrinth", "", name(), "", "");
instance.setName(name());
instance.saveNow();
@ -289,7 +292,7 @@ bool ModrinthCreationTask::createInstance()
// Only change the name if it didn't use a custom name, so that the previous custom name
// is preserved, but if we're using the original one, we update the version string.
// NOTE: This needs to come before the copyManagedPack call!
if (inst->name().contains(inst->getManagedPackVersionName())) {
if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != instance.name()) {
if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange)
inst->setName(instance.name());
}

View File

@ -287,16 +287,12 @@ QByteArray ModrinthPackExportTask::generateIndex()
env["client"] = "required";
env["server"] = "required";
}
switch (iterator->side) {
case Metadata::ModSide::ClientSide:
// a server side mod does not imply that the mod does not work on the client
// however, if a mrpack mod is marked as server-only it will not install on the client
if (iterator->side == Metadata::ModSide::ClientSide)
env["server"] = "unsupported";
break;
case Metadata::ModSide::ServerSide:
env["client"] = "unsupported";
break;
case Metadata::ModSide::UniversalSide:
break;
}
fileOut["env"] = env;
fileOut["path"] = path;

View File

@ -104,6 +104,8 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
pack.extraData.donate.append(donate);
}
pack.extraData.status = Json::ensureString(obj, "status");
pack.extraData.body = Json::ensureString(obj, "body").remove("<br>");
pack.extraDataLoaded = true;

View File

@ -95,6 +95,8 @@ void loadIndexedInfo(Modpack& pack, QJsonObject& obj)
pack.extra.donate.append(donate);
}
pack.extra.status = Json::ensureString(obj, "status");
pack.extraInfoLoaded = true;
}

View File

@ -77,6 +77,8 @@ struct ModpackExtra {
QString discordUrl;
QList<DonationData> donate;
QString status;
};
struct ModpackVersion {

View File

@ -36,6 +36,7 @@
*/
#include "NetJob.h"
#include "tasks/ConcurrentTask.h"
#if defined(LAUNCHER_APPLICATION)
#include "Application.h"
#endif
@ -56,18 +57,15 @@ auto NetJob::addNetAction(NetAction::Ptr action) -> bool
return true;
}
void NetJob::startNext()
void NetJob::executeNextSubTask()
{
if (m_queue.isEmpty() && m_doing.isEmpty()) {
// We're finished, check for failures and retry if we can (up to 3 times)
if (!m_failed.isEmpty() && m_try < 3) {
if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) {
m_try += 1;
while (!m_failed.isEmpty())
m_queue.enqueue(m_failed.take(*m_failed.keyBegin()));
}
}
ConcurrentTask::startNext();
ConcurrentTask::executeNextSubTask();
}
auto NetJob::size() const -> int

View File

@ -55,8 +55,6 @@ class NetJob : public ConcurrentTask {
explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network);
~NetJob() override = default;
void startNext() override;
auto size() const -> int;
auto canAbort() const -> bool override;
@ -69,6 +67,9 @@ class NetJob : public ConcurrentTask {
// Qt can't handle auto at the start for some reason?
bool abort() override;
protected slots:
void executeNextSubTask() override;
protected:
void updateState() override;

View File

@ -5,6 +5,7 @@
qt.*.debug=false
# don't log credentials by default
launcher.auth.credentials.debug=false
katabasis.*.debug=false
# remove the debug lines, other log levels still get through
launcher.task.net.download.debug=false
# enable or disable whole catageries

View File

@ -58,14 +58,14 @@ void ImgurUpload::init()
QNetworkReply* ImgurUpload::getReply(QNetworkRequest& request)
{
auto file = new QFile(m_fileInfo.absoluteFilePath());
auto file = new QFile(m_fileInfo.absoluteFilePath(), this);
if (!file->open(QFile::ReadOnly)) {
emitFailed();
return nullptr;
}
QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this);
file->setParent(multipart);
QHttpPart filePart;
filePart.setBodyDevice(file);

View File

@ -54,6 +54,7 @@ bool INIFile::saveFile(QString fileName)
insert("ConfigVersion", "1.2");
QSettings _settings_obj{ fileName, QSettings::Format::IniFormat };
_settings_obj.setFallbacksEnabled(false);
_settings_obj.clear();
for (Iterator iter = begin(); iter != end(); iter++)
_settings_obj.setValue(iter.key(), iter.value());

View File

@ -35,7 +35,6 @@
*/
#include "ConcurrentTask.h"
#include <QCoreApplication>
#include <QDebug>
#include "tasks/Task.h"
@ -47,9 +46,9 @@ ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concu
ConcurrentTask::~ConcurrentTask()
{
for (auto task : m_queue) {
for (auto task : m_doing) {
if (task)
task->deleteLater();
task->disconnect(this);
}
}
@ -65,15 +64,13 @@ void ConcurrentTask::addTask(Task::Ptr task)
void ConcurrentTask::executeTask()
{
// Start one task, startNext handles starting the up to the m_total_max_size
// while tracking the number currently being done
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection);
for (auto i = 0; i < m_total_max_size; i++)
QMetaObject::invokeMethod(this, &ConcurrentTask::executeNextSubTask, Qt::QueuedConnection);
}
bool ConcurrentTask::abort()
{
m_queue.clear();
m_aborted = true;
if (m_doing.isEmpty()) {
// Don't call emitAborted() here, we want to bypass the 'is the task running' check
@ -108,29 +105,36 @@ void ConcurrentTask::clear()
m_failed.clear();
m_queue.clear();
m_aborted = false;
m_progress = 0;
m_stepProgress = 0;
}
void ConcurrentTask::startNext()
void ConcurrentTask::executeNextSubTask()
{
if (m_aborted || m_doing.count() > m_total_max_size)
if (!isRunning()) {
return;
if (m_queue.isEmpty() && m_doing.isEmpty() && !wasSuccessful()) {
}
if (m_doing.count() >= m_total_max_size) {
return;
}
if (m_queue.isEmpty()) {
if (m_doing.isEmpty()) {
if (m_failed.isEmpty())
emitSucceeded();
else
emitFailed(tr("One or more subtasks failed"));
}
return;
}
if (m_queue.isEmpty())
return;
Task::Ptr next = m_queue.dequeue();
startSubTask(m_queue.dequeue());
}
void ConcurrentTask::startSubTask(Task::Ptr next)
{
connect(next.get(), &Task::succeeded, this, [this, next]() { subTaskSucceeded(next); });
connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); });
// this should never happen but if it does, it's better to fail the task than get stuck
connect(next.get(), &Task::aborted, this, [this, next] { subTaskFailed(next, "Aborted"); });
connect(next.get(), &Task::status, this, [this, next](QString msg) { subTaskStatus(next, msg); });
@ -140,55 +144,42 @@ void ConcurrentTask::startNext()
connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total) { subTaskProgress(next, current, total); });
m_doing.insert(next.get(), next);
qsizetype num_starts = qMin(m_queue.size(), m_total_max_size - m_doing.size());
auto task_progress = std::make_shared<TaskStepProgress>(next->getUid());
m_task_progress.insert(next->getUid(), task_progress);
updateState();
updateStepProgress(*task_progress.get(), Operation::ADDED);
QCoreApplication::processEvents();
QMetaObject::invokeMethod(next.get(), &Task::start, Qt::QueuedConnection);
}
// Allow going up the number of concurrent tasks in case of tasks being added in the middle of a running task.
for (int i = 0; i < num_starts; i++)
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection);
void ConcurrentTask::subTaskFinished(Task::Ptr task, TaskStepState state)
{
m_done.insert(task.get(), task);
(state == TaskStepState::Succeeded ? m_succeeded : m_failed).insert(task.get(), task);
m_doing.remove(task.get());
auto task_progress = m_task_progress.value(task->getUid());
task_progress->state = state;
disconnect(task.get(), 0, this, 0);
emit stepProgress(*task_progress);
updateState();
updateStepProgress(*task_progress, Operation::REMOVED);
QMetaObject::invokeMethod(this, &ConcurrentTask::executeNextSubTask, Qt::QueuedConnection);
}
void ConcurrentTask::subTaskSucceeded(Task::Ptr task)
{
m_done.insert(task.get(), task);
m_succeeded.insert(task.get(), task);
m_doing.remove(task.get());
auto task_progress = m_task_progress.value(task->getUid());
task_progress->state = TaskStepState::Succeeded;
disconnect(task.get(), 0, this, 0);
emit stepProgress(*task_progress);
updateState();
updateStepProgress(*task_progress, Operation::REMOVED);
startNext();
subTaskFinished(task, TaskStepState::Succeeded);
}
void ConcurrentTask::subTaskFailed(Task::Ptr task, [[maybe_unused]] const QString& msg)
{
m_done.insert(task.get(), task);
m_failed.insert(task.get(), task);
m_doing.remove(task.get());
auto task_progress = m_task_progress.value(task->getUid());
task_progress->state = TaskStepState::Failed;
disconnect(task.get(), 0, this, 0);
emit stepProgress(*task_progress);
updateState();
updateStepProgress(*task_progress, Operation::REMOVED);
startNext();
subTaskFinished(task, TaskStepState::Failed);
}
void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg)

View File

@ -72,10 +72,11 @@ class ConcurrentTask : public Task {
protected slots:
void executeTask() override;
virtual void startNext();
virtual void executeNextSubTask();
void subTaskSucceeded(Task::Ptr);
void subTaskFailed(Task::Ptr, const QString& msg);
virtual void subTaskFailed(Task::Ptr, const QString& msg);
void subTaskFinished(Task::Ptr, TaskStepState);
void subTaskStatus(Task::Ptr task, const QString& msg);
void subTaskDetails(Task::Ptr task, const QString& msg);
void subTaskProgress(Task::Ptr task, qint64 current, qint64 total);
@ -90,6 +91,8 @@ class ConcurrentTask : public Task {
virtual void updateState();
void startSubTask(Task::Ptr task);
protected:
QString m_name;
QString m_step_status;
@ -107,6 +110,4 @@ class ConcurrentTask : public Task {
qint64 m_stepProgress = 0;
qint64 m_stepTotalProgress = 100;
bool m_aborted = false;
};

View File

@ -36,9 +36,9 @@
#include <QDebug>
MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : SequentialTask(parent, task_name) {}
MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : ConcurrentTask(parent, task_name, 1) {}
void MultipleOptionsTask::startNext()
void MultipleOptionsTask::executeNextSubTask()
{
if (m_done.size() != m_failed.size()) {
emitSucceeded();
@ -51,7 +51,7 @@ void MultipleOptionsTask::startNext()
return;
}
ConcurrentTask::startNext();
ConcurrentTask::executeNextSubTask();
}
void MultipleOptionsTask::updateState()

View File

@ -34,18 +34,18 @@
*/
#pragma once
#include "SequentialTask.h"
#include "ConcurrentTask.h"
/* This task type will attempt to do run each of it's subtasks in sequence,
* until one of them succeeds. When that happens, the remaining tasks will not run.
* */
class MultipleOptionsTask : public SequentialTask {
class MultipleOptionsTask : public ConcurrentTask {
Q_OBJECT
public:
explicit MultipleOptionsTask(QObject* parent = nullptr, const QString& task_name = "");
~MultipleOptionsTask() override = default;
private slots:
void startNext() override;
void executeNextSubTask() override;
void updateState() override;
};

View File

@ -36,18 +36,15 @@
#include "SequentialTask.h"
#include <QDebug>
#include "tasks/ConcurrentTask.h"
SequentialTask::SequentialTask(QObject* parent, QString task_name) : ConcurrentTask(parent, task_name, 1) {}
void SequentialTask::startNext()
void SequentialTask::subTaskFailed(Task::Ptr task, const QString& msg)
{
if (m_failed.size() > 0) {
emitFailed(tr("One of the tasks failed!"));
qWarning() << m_failed.constBegin()->get()->failReason();
return;
}
ConcurrentTask::startNext();
emitFailed(msg);
qWarning() << msg;
ConcurrentTask::subTaskFailed(task, msg);
}
void SequentialTask::updateState()

View File

@ -50,7 +50,9 @@ class SequentialTask : public ConcurrentTask {
explicit SequentialTask(QObject* parent = nullptr, QString task_name = "");
~SequentialTask() override = default;
protected slots:
virtual void subTaskFailed(Task::Ptr, const QString& msg) override;
protected:
void startNext() override;
void updateState() override;
};

View File

@ -1182,43 +1182,43 @@ void MainWindow::undoTrashInstance()
void MainWindow::on_actionViewLauncherRootFolder_triggered()
{
DesktopServices::openDirectory(".");
DesktopServices::openPath(".");
}
void MainWindow::on_actionViewInstanceFolder_triggered()
{
QString str = APPLICATION->settings()->get("InstanceDir").toString();
DesktopServices::openDirectory(str);
DesktopServices::openPath(str);
}
void MainWindow::on_actionViewCentralModsFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->settings()->get("CentralModsDir").toString(), true);
DesktopServices::openPath(APPLICATION->settings()->get("CentralModsDir").toString(), true);
}
void MainWindow::on_actionViewIconThemeFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->themeManager()->getIconThemesFolder().path(), true);
DesktopServices::openPath(APPLICATION->themeManager()->getIconThemesFolder().path(), true);
}
void MainWindow::on_actionViewWidgetThemeFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->themeManager()->getApplicationThemesFolder().path(), true);
DesktopServices::openPath(APPLICATION->themeManager()->getApplicationThemesFolder().path(), true);
}
void MainWindow::on_actionViewCatPackFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->themeManager()->getCatPacksFolder().path(), true);
DesktopServices::openPath(APPLICATION->themeManager()->getCatPacksFolder().path(), true);
}
void MainWindow::on_actionViewIconsFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->icons()->getDirectory(), true);
DesktopServices::openPath(APPLICATION->icons()->getDirectory(), true);
}
void MainWindow::on_actionViewLogsFolder_triggered()
{
DesktopServices::openDirectory("logs", true);
DesktopServices::openPath("logs", true);
}
void MainWindow::refreshInstances()
@ -1437,7 +1437,7 @@ void MainWindow::on_actionViewSelectedInstFolder_triggered()
{
if (m_selectedInstance) {
QString str = m_selectedInstance->instanceRoot();
DesktopServices::openDirectory(QDir(str).absolutePath());
DesktopServices::openPath(QFileInfo(str));
}
}

View File

@ -174,8 +174,7 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia
QString urlText("<html><head/><body><p><a href=\"%1\">%1</a></p></body></html>");
ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT));
QString copyText("© 2022-2023 %1");
ui->copyLabel->setText(copyText.arg(BuildConfig.LAUNCHER_COPYRIGHT));
ui->copyLabel->setText(BuildConfig.LAUNCHER_COPYRIGHT);
connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));

View File

@ -15,7 +15,6 @@
#pragma once
#include <net/NetJob.h>
#include <QDialog>
namespace Ui {
@ -31,7 +30,4 @@ class AboutDialog : public QDialog {
private:
Ui::AboutDialog* ui;
NetJob::Ptr netJob;
QByteArray dataSink;
};

View File

@ -47,11 +47,18 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
setWindowTitle(tr("Export Modrinth Pack"));
ui->summary->setText(instance->settings()->get("ExportSummary").toString());
ui->authorLabel->hide();
ui->author->hide();
ui->summary->setPlainText(instance->settings()->get("ExportSummary").toString());
} else {
setWindowTitle(tr("Export CurseForge Pack"));
ui->summaryLabel->setText(tr("&Author"));
ui->summary->setText(instance->settings()->get("ExportAuthor").toString());
ui->summaryLabel->hide();
ui->summary->hide();
ui->author->setText(instance->settings()->get("ExportAuthor").toString());
}
// ensure a valid pack is generated
@ -108,9 +115,13 @@ void ExportPackDialog::done(int result)
auto settings = instance->settings();
settings->set("ExportName", ui->name->text());
settings->set("ExportVersion", ui->version->text());
settings->set(m_provider == ModPlatform::ResourceProvider::FLAME ? "ExportAuthor" : "ExportSummary", ui->summary->text());
settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked());
if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
settings->set("ExportSummary", ui->summary->toPlainText());
else
settings->set("ExportAuthor", ui->author->text());
if (result == Accepted) {
const QString name = ui->name->text().isEmpty() ? instance->name() : ui->name->text();
const QString filename = FS::RemoveInvalidFilenameChars(name);
@ -134,10 +145,10 @@ void ExportPackDialog::done(int result)
Task* task;
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance,
output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->toPlainText(), ui->optionalFiles->isChecked(),
instance, output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
} else {
task = new FlamePackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance, output,
task = new FlamePackExportTask(name, ui->version->text(), ui->author->text(), ui->optionalFiles->isChecked(), instance, output,
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
}

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>650</width>
<height>510</height>
<height>532</height>
</rect>
</property>
<property name="sizeGripEnabled">
@ -19,21 +19,8 @@
<property name="title">
<string>&amp;Description</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QLabel" name="summaryLabel">
<property name="text">
<string>&amp;Summary</string>
</property>
<property name="buddy">
<cstring>summary</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="summary"/>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>&amp;Name</string>
@ -43,7 +30,10 @@
</property>
</widget>
</item>
<item row="1" column="0">
<item>
<widget class="QLineEdit" name="name"/>
</item>
<item>
<widget class="QLabel" name="versionLabel">
<property name="text">
<string>&amp;Version</string>
@ -53,16 +43,43 @@
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="name"/>
</item>
<item row="1" column="1">
<item>
<widget class="QLineEdit" name="version">
<property name="text">
<string>1.0.0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="summaryLabel">
<property name="text">
<string>&amp;Summary</string>
</property>
<property name="buddy">
<cstring>summary</cstring>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="summary">
<property name="tabChangesFocus">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="authorLabel">
<property name="text">
<string>&amp;Author</string>
</property>
<property name="buddy">
<cstring>author</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="author"/>
</item>
</layout>
</widget>
</item>
@ -124,6 +141,7 @@
<tabstop>name</tabstop>
<tabstop>version</tabstop>
<tabstop>summary</tabstop>
<tabstop>author</tabstop>
<tabstop>files</tabstop>
<tabstop>optionalFiles</tabstop>
</tabstops>

View File

@ -159,5 +159,5 @@ IconPickerDialog::~IconPickerDialog()
void IconPickerDialog::openFolder()
{
DesktopServices::openDirectory(APPLICATION->icons()->getDirectory(), true);
DesktopServices::openPath(APPLICATION->icons()->getDirectory(), true);
}

View File

@ -328,6 +328,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool
connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH);
});
connect(modrinth_task.get(), &EnsureMetadataTask::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
if (modrinth_task->getHashingTask())
seq.addTask(modrinth_task->getHashingTask());
@ -341,6 +343,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool
connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME);
});
connect(flame_task.get(), &EnsureMetadataTask::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
if (flame_task->getHashingTask())
seq.addTask(flame_task->getHashingTask());
@ -394,6 +398,8 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::R
auto task = makeShared<EnsureMetadataTask>(mod, index_dir, next(first_choice));
connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); });
connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Mod* candidate) { onMetadataFailed(candidate, false); });
connect(task.get(), &EnsureMetadataTask::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
m_second_try_metadata->addTask(task);
} else {
@ -437,6 +443,9 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri
reqItem->insertChildren(i++, { reqItem });
}
}
ui->toggleDepsButton->show();
m_deps << item_top;
}
auto changelog_item = new QTreeWidgetItem(item_top);

View File

@ -13,6 +13,7 @@ ReviewMessageBox::ReviewMessageBox(QWidget* parent, [[maybe_unused]] QString con
auto back_button = ui->buttonBox->button(QDialogButtonBox::Cancel);
back_button->setText(tr("Back"));
ui->toggleDepsButton->hide();
ui->modTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch);
ui->modTreeWidget->header()->setStretchLastSection(false);
ui->modTreeWidget->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
@ -75,6 +76,8 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
}
itemTop->insertChildren(childIndx++, { requiredByItem });
ui->toggleDepsButton->show();
m_deps << itemTop;
}
auto versionTypeItem = new QTreeWidgetItem(itemTop);
@ -108,3 +111,10 @@ void ReviewMessageBox::retranslateUi(QString resources_name)
ui->explainLabel->setText(tr("You're about to download the following %1:").arg(resources_name));
ui->onlyCheckedLabel->setText(tr("Only %1 with a check will be downloaded!").arg(resources_name));
}
void ReviewMessageBox::on_toggleDepsButton_clicked()
{
m_deps_checked = !m_deps_checked;
auto state = m_deps_checked ? Qt::Checked : Qt::Unchecked;
for (auto dep : m_deps)
dep->setCheckState(0, state);
};

View File

@ -1,6 +1,7 @@
#pragma once
#include <QDialog>
#include <QTreeWidgetItem>
namespace Ui {
class ReviewMessageBox;
@ -28,8 +29,14 @@ class ReviewMessageBox : public QDialog {
~ReviewMessageBox() override;
protected slots:
void on_toggleDepsButton_clicked();
protected:
ReviewMessageBox(QWidget* parent, const QString& title, const QString& icon);
Ui::ReviewMessageBox* ui;
QList<QTreeWidgetItem*> m_deps;
bool m_deps_checked = true;
};

View File

@ -44,15 +44,20 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="explainLabel">
</widget>
<widget class="QLabel" name="explainLabel"/>
</item>
<item row="5" column="0" rowspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="onlyCheckedLabel">
<widget class="QPushButton" name="toggleDepsButton">
<property name="text">
<string>Toggle Dependencies</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="onlyCheckedLabel"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">

View File

@ -480,32 +480,42 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event)
if (model()->rowCount() == 0) {
painter.save();
const QString line1 = tr("Welcome!");
const QString line2 = tr("Click \"Add Instance\" to get started.");
auto rect = this->viewport()->rect();
auto font = option.font;
font.setPointSize(37);
painter.setFont(font);
auto fm = painter.fontMetrics();
QString emptyString = tr("Welcome!") + "\n" + tr("Click \"Add Instance\" to get started.");
if (rect.height() <= (fm.height() * 5) || rect.width() <= fm.horizontalAdvance(line2)) {
auto s = rect.height() / (5. * fm.height());
auto sx = rect.width() * 1. / fm.horizontalAdvance(line2);
if (s >= sx)
s = sx;
auto ps = font.pointSize() * s;
if (ps <= 0)
ps = 1;
font.setPointSize(ps);
// calculate the rect for the overlay
painter.setRenderHint(QPainter::Antialiasing, true);
QFont font("sans", 20);
font.setBold(true);
QRect bounds = viewport()->geometry();
bounds.moveTop(0);
auto innerBounds = bounds;
innerBounds.adjust(10, 10, -10, -10);
QColor background = QApplication::palette().color(QPalette::WindowText);
QColor foreground = QApplication::palette().color(QPalette::Base);
foreground.setAlpha(190);
painter.setFont(font);
fm = painter.fontMetrics();
auto fontMetrics = painter.fontMetrics();
auto textRect = fontMetrics.boundingRect(innerBounds, Qt::AlignHCenter | Qt::TextWordWrap, emptyString);
textRect.moveCenter(bounds.center());
auto wrapRect = textRect;
wrapRect.adjust(-10, -10, 10, 10);
// check if we are allowed to draw in our area
if (!event->rect().intersects(wrapRect)) {
return;
}
// text
rect.setTop(rect.top() + fm.height() * 1.5);
painter.drawText(rect, Qt::AlignHCenter, line1);
rect.setTop(rect.top() + fm.height());
painter.drawText(rect, Qt::AlignHCenter, line2);
painter.setBrush(QBrush(background));
painter.setPen(foreground);
painter.drawRoundedRect(wrapRect, 5.0, 5.0);
painter.setPen(foreground);
painter.setFont(font);
painter.drawText(textRect, Qt::AlignHCenter | Qt::TextWordWrap, emptyString);
painter.restore();
return;
}

View File

@ -158,13 +158,14 @@ void VisualGroup::drawHeader(QPainter* painter, const QStyleOptionViewItem& opti
painter->setRenderHint(QPainter::Antialiasing);
// sizes and offsets, to keep things consistent below
int arrowOffsetLeft = fontMetrics.height() / 2 + 7;
int textOffsetLeft = arrowOffsetLeft * 2;
int arrowSize = 6;
int centerHeight = optRect.top() + fontMetrics.height() / 2;
const int arrowOffsetLeft = fontMetrics.height() / 2 + 7;
const int textOffsetLeft = arrowOffsetLeft * 2;
const int centerHeight = optRect.top() + fontMetrics.height() / 2;
const QString& textToDraw = text.isEmpty() ? QObject::tr("Ungrouped") : text;
// BEGIN: arrow
{
constexpr int arrowSize = 6;
QPolygon arrowPolygon;
if (collapsed) {
arrowPolygon << QPoint(arrowOffsetLeft - arrowSize / 2, centerHeight - arrowSize)
@ -188,9 +189,26 @@ void VisualGroup::drawHeader(QPainter* painter, const QStyleOptionViewItem& opti
textRect.setHeight(fontMetrics.height());
textRect.setRight(textRect.right() - 7);
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, !text.isEmpty() ? text : QObject::tr("Ungrouped"));
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, textToDraw);
}
// END: text
// BEGIN: horizontal line
{
penColor.setAlphaF(0.05);
pen.setColor(penColor);
painter->setPen(pen);
// startPoint is left + arrow + text + space
const int startPoint =
optRect.left() + fontMetrics.height() + fontMetrics.size(Qt::AlignLeft | Qt::AlignVCenter, textToDraw).width() + 20;
painter->setRenderHint(QPainter::Antialiasing, false);
QPolygon polygon;
// for some reason the height (yPos) doesn't look centered, so we are adding 1 to the center height
const int lineHeight = centerHeight + 1;
polygon << QPoint(startPoint, lineHeight) << QPoint(optRect.right() - 3, lineHeight);
painter->drawPolyline(polygon);
}
// END: horizontal line
}
int VisualGroup::totalHeight() const

View File

@ -206,7 +206,7 @@
<item>
<widget class="QCheckBox" name="onlineFixes">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Emulates usages of old online services which are no longer operating.&lt;/p&gt;&lt;p&gt;This currently allows modern skins to be used.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Emulates usages of old online services which are no longer operating.&lt;/p&gt;&lt;p&gt;Current fixes include: skin and online mode support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable online fixes (experimental)</string>

View File

@ -290,12 +290,12 @@ void ExternalResourcesPage::disableItem()
void ExternalResourcesPage::viewConfigs()
{
DesktopServices::openDirectory(m_instance->instanceConfigFolder(), true);
DesktopServices::openPath(m_instance->instanceConfigFolder(), true);
}
void ExternalResourcesPage::viewFolder()
{
DesktopServices::openDirectory(m_model->dir().absolutePath(), true);
DesktopServices::openPath(m_model->dir().absolutePath(), true);
}
bool ExternalResourcesPage::current(const QModelIndex& current, const QModelIndex& previous)

View File

@ -346,7 +346,7 @@ void InstanceSettingsPage::loadSettings()
#ifdef Q_OS_LINUX
ui->lineEditOpenALPath->setPlaceholderText(APPLICATION->m_detectedOpenALPath);
#else
ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME));
ui->lineEditOpenALPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME));
#endif
// Performance

View File

@ -605,7 +605,7 @@
<item>
<widget class="QCheckBox" name="onlineFixes">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Emulates usages of old online services which are no longer operating.&lt;/p&gt;&lt;p&gt;This currently allows modern skins to be used.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Emulates usages of old online services which are no longer operating.&lt;/p&gt;&lt;p&gt;Current fixes include: skin and online mode support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable online fixes (experimental)</string>

View File

@ -131,6 +131,22 @@ ManagedPackPage::~ManagedPackPage()
void ManagedPackPage::openedImpl()
{
if (m_inst->getManagedPackID().isEmpty()) {
ui->packVersion->hide();
ui->packVersionLabel->hide();
ui->packOrigin->hide();
ui->packOriginLabel->hide();
ui->versionsComboBox->hide();
ui->updateButton->hide();
ui->updateToVersionLabel->hide();
ui->updateFromFileButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
ui->packName->setText(m_inst->name());
ui->changelogTextBrowser->setText(tr("This is a local modpack.\n"
"This can be updated only using a file in %1 format\n")
.arg(displayName()));
return;
}
ui->packName->setText(m_inst->getManagedPackName());
ui->packVersion->setText(m_inst->getManagedPackVersionName());
ui->packOrigin->setText(tr("Website: <a href=%1>%2</a> | Pack ID: %3 | Version ID: %4")
@ -355,6 +371,8 @@ void ModrinthManagedPackPage::update()
void ModrinthManagedPackPage::updateFromFile()
{
auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), "Modrinth pack (*.mrpack *.zip)");
if (output.isEmpty())
return;
QMap<QString, QString> extra_info;
extra_info.insert("pack_id", m_inst->getManagedPackID());
extra_info.insert("pack_version_id", QString());
@ -472,7 +490,7 @@ void FlameManagedPackPage::parseManagedPack()
QString FlameManagedPackPage::url() const
{
// FIXME: We should display the websiteUrl field, but this requires doing the API request first :(
return {};
return "https://www.curseforge.com/projects/" + m_inst->getManagedPackID();
}
void FlameManagedPackPage::suggestVersion()
@ -519,6 +537,8 @@ void FlameManagedPackPage::update()
void FlameManagedPackPage::updateFromFile()
{
auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), "CurseForge pack (*.zip)");
if (output.isEmpty())
return;
QMap<QString, QString> extra_info;
extra_info.insert("pack_id", m_inst->getManagedPackID());

View File

@ -242,7 +242,7 @@ void ModFolderPage::updateMods(bool includeDeps)
if (m_instance != nullptr && m_instance->isRunning()) {
auto response =
CustomMessageBox::selectable(this, tr("Confirm Update"),
tr("If you update mods while the game is running may cause mod duplication and game crashes.\n"
tr("Updating mods while the game is running may cause mod duplication and game crashes.\n"
"The old files may not be deleted as they are in use.\n"
"Are you sure you want to do this?"),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)

View File

@ -324,8 +324,7 @@ void ScreenshotsPage::onItemActivated(QModelIndex index)
if (!index.isValid())
return;
auto info = m_model->fileInfo(index);
QString fileName = info.absoluteFilePath();
DesktopServices::openFile(info.absoluteFilePath());
DesktopServices::openPath(info);
}
void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected)
@ -352,7 +351,7 @@ void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected)
void ScreenshotsPage::on_actionView_Folder_triggered()
{
DesktopServices::openDirectory(m_folder, true);
DesktopServices::openPath(m_folder, true);
}
void ScreenshotsPage::on_actionUpload_triggered()

View File

@ -295,13 +295,6 @@ void VersionPage::on_actionRemove_triggered()
m_container->refreshContainer();
}
void VersionPage::on_actionInstall_mods_triggered()
{
if (m_container) {
m_container->selectPage("mods");
}
}
void VersionPage::on_actionAdd_to_Minecraft_jar_triggered()
{
auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"),
@ -454,12 +447,12 @@ void VersionPage::on_actionAdd_Empty_triggered()
void VersionPage::on_actionLibrariesFolder_triggered()
{
DesktopServices::openDirectory(m_inst->getLocalLibraryPath(), true);
DesktopServices::openPath(m_inst->getLocalLibraryPath(), true);
}
void VersionPage::on_actionMinecraftFolder_triggered()
{
DesktopServices::openDirectory(m_inst->gameRoot(), true);
DesktopServices::openPath(m_inst->gameRoot(), true);
}
void VersionPage::versionCurrent(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)

View File

@ -80,7 +80,6 @@ class VersionPage : public QMainWindow, public BasePage {
void on_actionAdd_Agents_triggered();
void on_actionRevert_triggered();
void on_actionEdit_triggered();
void on_actionInstall_mods_triggered();
void on_actionCustomize_triggered();
void on_actionDownload_All_triggered();

View File

@ -207,7 +207,7 @@ void WorldListPage::on_actionRemove_triggered()
void WorldListPage::on_actionView_Folder_triggered()
{
DesktopServices::openDirectory(m_worlds->dir().absolutePath(), true);
DesktopServices::openPath(m_worlds->dir().absolutePath(), true);
}
void WorldListPage::on_actionDatapacks_triggered()
@ -223,7 +223,7 @@ void WorldListPage::on_actionDatapacks_triggered()
auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
DesktopServices::openDirectory(FS::PathCombine(fullPath, "datapacks"), true);
DesktopServices::openPath(FS::PathCombine(fullPath, "datapacks"), true);
}
void WorldListPage::on_actionReset_Icon_triggered()

View File

@ -207,6 +207,11 @@ void ResourceModel::loadEntry(QModelIndex& entry)
return;
versionRequestSucceeded(doc, pack, entry);
};
if (!callbacks.on_fail)
callbacks.on_fail = [](QString reason, int) {
QMessageBox::critical(nullptr, tr("Error"),
tr("A network error occurred. Could not load project versions: %1").arg(reason));
};
if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job)
runInfoJob(job);
@ -228,7 +233,13 @@ void ResourceModel::loadEntry(QModelIndex& entry)
callbacks.on_fail = [this](QString reason) {
if (!s_running_models.constFind(this).value())
return;
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason));
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info: %1").arg(reason));
};
if (!callbacks.on_abort)
callbacks.on_abort = [this] {
if (!s_running_models.constFind(this).value())
return;
qCritical() << tr("The request was aborted for an unknown reason");
};
if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job)

View File

@ -200,6 +200,11 @@ void ResourcePage::updateUi()
}
if (current_pack->extraDataLoaded) {
if (current_pack->extraData.status == "archived") {
text += "<br><br>" + tr("<b>This project has been archived. It will not receive any further updates unless the author decides "
"to unarchive the project.</b>");
}
if (!current_pack->extraData.donate.isEmpty()) {
text += "<br><br>" + tr("Donate information: ");
auto donateToStr = [](ModPlatform::DonationData& donate) -> QString {
@ -404,9 +409,9 @@ void ResourcePage::openUrl(const QUrl& url)
auto jump = [url, slug, model, view] {
for (int row = 0; row < model->rowCount({}); row++) {
const QModelIndex index = model->index(row);
const auto pack = model->data(index, Qt::UserRole).value<ModPlatform::IndexedPack>();
const auto pack = model->data(index, Qt::UserRole).value<ModPlatform::IndexedPack::Ptr>();
if (pack.slug == slug) {
if (pack->slug == slug) {
view->setCurrentIndex(index);
return;
}

View File

@ -170,6 +170,10 @@ void ListModel::performPaginatedSearch()
callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
callbacks.on_abort = [this] {
qCritical() << "Search task aborted by an unknown reason!";
searchRequestFailed("Abborted");
};
static const FlameAPI api;
if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
jobPtr = job;

View File

@ -34,6 +34,7 @@
*/
#include "FlamePage.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui_FlamePage.h"
#include <QKeyEvent>
@ -193,6 +194,8 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde
suggestCurrent();
});
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
connect(netJob, &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
netJob->start();
} else {
for (auto version : current.versions) {

View File

@ -20,6 +20,7 @@
#include "ui/widgets/ProjectItem.h"
#include "ui_ImportFTBPage.h"
#include <QFileDialog>
#include <QWidget>
#include "FileSystem.h"
#include "ListModel.h"
@ -56,6 +57,13 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg
connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch);
connect(ui->browseButton, &QPushButton::clicked, this, [this] {
auto path = listModel->getPath();
QString dir = QFileDialog::getExistingDirectory(this, tr("Select FTBApp instances directory"), path, QFileDialog::ShowDirsOnly);
if (!dir.isEmpty())
listModel->setPath(dir);
});
ui->modpackList->setItemDelegate(new ProjectItemDelegate(this));
ui->modpackList->selectionModel()->reset();
}

View File

@ -11,7 +11,7 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<item row="2" column="1">
<widget class="QTreeView" name="modpackList">
<property name="maximumSize">
<size>
@ -21,28 +21,7 @@
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QComboBox" name="sortByBox">
@ -69,6 +48,54 @@
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="browseButton">
<property name="toolTip">
<string>Select FTBApp instances directory</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="viewfolder">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>Note: If your FTB instances are not in the default location, select it using the button next to search.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>

View File

@ -17,11 +17,13 @@
*/
#include "ListModel.h"
#include <qfileinfo.h>
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QIcon>
#include <QProcessEnvironment>
#include "Application.h"
#include "FileSystem.h"
#include "StringUtils.h"
#include "modplatform/import_ftb/PackHelpers.h"
@ -29,7 +31,7 @@
namespace FTBImportAPP {
QString getPath()
QString getStaticPath()
{
QString partialPath;
#if defined(Q_OS_OSX)
@ -42,14 +44,14 @@ QString getPath()
return FS::PathCombine(partialPath, ".ftba");
}
const QString ListModel::FTB_APP_PATH = getPath();
static const QString FTB_APP_PATH = FS::PathCombine(getStaticPath(), "instances");
void ListModel::update()
{
beginResetModel();
modpacks.clear();
QString instancesPath = FS::PathCombine(FTB_APP_PATH, "instances");
QString instancesPath = getPath();
if (auto instancesInfo = QFileInfo(instancesPath); instancesInfo.exists() && instancesInfo.isDir()) {
QDirIterator directoryIterator(instancesPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden,
QDirIterator::FollowSymlinks);
@ -168,4 +170,17 @@ FilterModel::Sorting FilterModel::getCurrentSorting()
{
return currentSorting;
}
void ListModel::setPath(QString path)
{
APPLICATION->settings()->set("FTBAppInstancesPath", path);
update();
}
QString ListModel::getPath()
{
auto path = APPLICATION->settings()->get("FTBAppInstancesPath").toString();
if (path.isEmpty() || !QFileInfo(path).exists())
path = FTB_APP_PATH;
return path;
}
} // namespace FTBImportAPP

View File

@ -60,7 +60,8 @@ class ListModel : public QAbstractListModel {
void update();
static const QString FTB_APP_PATH;
QString getPath();
void setPath(QString path);
private:
ModpackList modpacks;

View File

@ -140,6 +140,10 @@ void ModpackListModel::performPaginatedSearch()
callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
callbacks.on_abort = [this] {
qCritical() << "Search task aborted by an unknown reason!";
searchRequestFailed("Aborted");
};
static const ModrinthAPI api;
if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
jobPtr = job;

View File

@ -35,6 +35,7 @@
*/
#include "ModrinthPage.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui_ModrinthPage.h"
#include "ModrinthModel.h"
@ -182,6 +183,8 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
suggestCurrent();
});
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
connect(netJob, &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
netJob->start();
} else
updateUI();
@ -235,6 +238,8 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
suggestCurrent();
});
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
connect(netJob, &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
netJob->start();
} else {
@ -262,6 +267,11 @@ void ModrinthPage::updateUI()
text += "<br>" + tr(" by ") + QString("<a href=%1>%2</a>").arg(std::get<1>(current.author).toString(), std::get<0>(current.author));
if (current.extraInfoLoaded) {
if (current.extra.status == "archived") {
text += "<br><br>" + tr("<b>This project has been archived. It will not receive any further updates unless the author decides "
"to unarchive the project.</b>");
}
if (!current.extra.donate.isEmpty()) {
text += "<br><br>" + tr("Donate information: ");
auto donateToStr = [](Modrinth::DonationData& donate) -> QString {

View File

@ -11,43 +11,7 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>Note: Modrinth modpacks are still in alpha phase. Some things may be rough on the edges, or not working at all! Use it with caution.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter ...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="searchButton">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<item row="2" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QListView" name="packView">
@ -77,7 +41,7 @@
</item>
</layout>
</item>
<item row="4" column="0">
<item row="3" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QComboBox" name="sortByBox"/>
@ -97,6 +61,24 @@
</item>
</layout>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter ...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="searchButton">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>

View File

@ -34,6 +34,7 @@
*/
#include "TechnicPage.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/widgets/ProjectItem.h"
#include "ui_TechnicPage.h"
@ -208,6 +209,8 @@ void TechnicPage::suggestCurrent()
metadataLoaded();
});
connect(jobPtr.get(), &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
jobPtr = netJob;
jobPtr->start();
@ -258,6 +261,8 @@ void TechnicPage::metadataLoaded()
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded);
connect(jobPtr.get(), &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
jobPtr = netJob;
jobPtr->start();

View File

@ -36,6 +36,7 @@
#include "LogView.h"
#include <QScrollBar>
#include <QTextBlock>
#include <QTextDocumentFragment>
LogView::LogView(QWidget* parent) : QPlainTextEdit(parent)
{
@ -117,6 +118,9 @@ void LogView::rowsAboutToBeInserted(const QModelIndex& parent, int first, int la
void LogView::rowsInserted(const QModelIndex& parent, int first, int last)
{
QTextDocument document;
QTextCursor cursor(&document);
for (int i = first; i <= last; i++) {
auto idx = m_model->index(i, 0, parent);
auto text = m_model->data(idx, Qt::DisplayRole).toString();
@ -133,11 +137,16 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last)
if (bg.isValid()) {
format.setBackground(bg.value<QColor>());
}
auto workCursor = textCursor();
workCursor.movePosition(QTextCursor::End);
workCursor.insertText(text, format);
workCursor.insertBlock();
cursor.movePosition(QTextCursor::End);
cursor.insertText(text, format);
cursor.insertBlock();
}
QTextDocumentFragment fragment(&document);
QTextCursor workCursor = textCursor();
workCursor.movePosition(QTextCursor::End);
workCursor.insertFragment(fragment);
if (m_scroll && !m_scrolling) {
m_scrolling = true;
QMetaObject::invokeMethod(this, "scrollToBottom", Qt::QueuedConnection);

View File

@ -88,7 +88,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o
}
{ // Description painting
auto description = index.data(UserDataTypes::DESCRIPTION).toString();
auto description = index.data(UserDataTypes::DESCRIPTION).toString().simplified();
QTextLayout text_layout(description, opt.font);

View File

@ -34,11 +34,11 @@ ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget* parent) : QWidget(pa
connect(ui->backgroundCatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyCatTheme);
connect(ui->iconsFolder, &QPushButton::clicked, this,
[] { DesktopServices::openDirectory(APPLICATION->themeManager()->getIconThemesFolder().path()); });
[] { DesktopServices::openPath(APPLICATION->themeManager()->getIconThemesFolder().path()); });
connect(ui->widgetStyleFolder, &QPushButton::clicked, this,
[] { DesktopServices::openDirectory(APPLICATION->themeManager()->getApplicationThemesFolder().path()); });
[] { DesktopServices::openPath(APPLICATION->themeManager()->getApplicationThemesFolder().path()); });
connect(ui->catPackFolder, &QPushButton::clicked, this,
[] { DesktopServices::openDirectory(APPLICATION->themeManager()->getCatPacksFolder().path()); });
[] { DesktopServices::openPath(APPLICATION->themeManager()->getCatPacksFolder().path()); });
}
ThemeCustomizationWidget::~ThemeCustomizationWidget()

View File

@ -102,14 +102,7 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source,
auto full_entry_path = entry->getFullPath();
auto source_url = source;
connect(job, &NetJob::succeeded, this, [this, doc, full_entry_path, source_url, posInDocument] {
qDebug() << "Loaded resource at" << full_entry_path;
// If we flushed, don't proceed.
if (!m_fetching_images.contains(source_url))
return;
QImage image(full_entry_path);
auto loadImage = [this, doc, full_entry_path, source_url, posInDocument](const QImage& image) {
doc->addResource(QTextDocument::ImageResource, source_url, image);
parseImage(doc, image, posInDocument);
@ -121,6 +114,23 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source,
doc->setPageSize(size);
m_fetching_images.remove(source_url);
};
connect(job, &NetJob::succeeded, this, [this, full_entry_path, source_url, loadImage] {
qDebug() << "Loaded resource at:" << full_entry_path;
// If we flushed, don't proceed.
if (!m_fetching_images.contains(source_url))
return;
QImage image(full_entry_path);
loadImage(image);
});
connect(job, &NetJob::failed, this, [this, full_entry_path, source_url, loadImage](QString reason) {
qWarning() << "Failed resource at:" << full_entry_path << " because:" << reason;
// If we flushed, don't proceed.
if (!m_fetching_images.contains(source_url))
return;
loadImage(QImage());
});
connect(job, &NetJob::finished, job, &NetJob::deleteLater);

View File

@ -436,8 +436,8 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
}
{ // log debug program info
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME) << "Updater"
<< ", (c) 2022-2023 " << qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + " Updater, " +
QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
qDebug() << "Version : " << BuildConfig.printableVersionString();
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;

@ -1 +1 @@
Subproject commit 5ba25ff40eba44c811f79ab6a792baf945b8307c
Subproject commit 8fbf029685482827828b5858444157052f1b0a5f

@ -1 +1 @@
Subproject commit 8a2edd6d92ed820521d42c94d179462bf06b5ed3
Subproject commit 2fc4b463759e043476fc0036da094e5877e3dd50

View File

@ -25,13 +25,14 @@ set(LEGACY_SRC
legacy/org/prismlauncher/legacy/LegacyLauncher.java
legacy/org/prismlauncher/legacy/fix/online/Handler.java
legacy/org/prismlauncher/legacy/fix/online/OnlineFixes.java
legacy/org/prismlauncher/legacy/fix/online/OnlineModeFix.java
legacy/org/prismlauncher/legacy/fix/online/SkinFix.java
legacy/org/prismlauncher/legacy/utils/Base64.java
legacy/org/prismlauncher/legacy/utils/api/MojangApi.java
legacy/org/prismlauncher/legacy/utils/api/Texture.java
legacy/org/prismlauncher/legacy/utils/json/JsonParseException.java
legacy/org/prismlauncher/legacy/utils/json/JsonParser.java
legacy/org/prismlauncher/legacy/utils/url/CustomUrlConnection.java
legacy/org/prismlauncher/legacy/utils/url/ByteArrayUrlConnection.java
legacy/org/prismlauncher/legacy/utils/url/UrlUtils.java
legacy/net/minecraft/Launcher.java
legacy/org/prismlauncher/legacy/LegacyProxy.java

View File

@ -53,11 +53,16 @@ final class Handler extends URLStreamHandler {
protected URLConnection openConnection(URL address, Proxy proxy) throws IOException {
URLConnection result;
// try skin fix
// try various fixes...
result = SkinFix.openConnection(address, proxy);
if (result != null)
return result;
result = OnlineModeFix.openConnection(address, proxy);
if (result != null)
return result;
// ...then give up and make the request directly
return UrlUtils.openConnection(address, proxy);
}
}

View File

@ -46,6 +46,8 @@ import java.net.URLStreamHandlerFactory;
/**
* Fixes skins by redirecting to other URLs.
* Thanks to MineOnline for the implementation from which this was inspired!
* See https://github.com/ahnewark/MineOnline/tree/main/src/main/java/gg/codie/mineonline/protocol.
*
* @see {@link Handler}
* @see {@link UrlUtils}

View File

@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* 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
* 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.
*
* Linking this library statically or dynamically with other modules is
* making a combined work based on this library. Thus, the terms and
* conditions of the GNU General Public License cover the whole
* combination.
*
* As a special exception, the copyright holders of this library give
* you permission to link this library with independent modules to
* produce an executable, regardless of the license terms of these
* independent modules, and to copy and distribute the resulting
* executable under terms of your choice, provided that you also meet,
* for each linked independent module, the terms and conditions of the
* license of that module. An independent module is a module which is
* not derived from or based on this library. If you modify this
* library, you may extend this exception to your version of the
* library, but you are not obliged to do so. If you do not wish to do
* so, delete this exception statement from your version.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.prismlauncher.legacy.fix.online;
import org.prismlauncher.legacy.utils.url.UrlUtils;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
public final class OnlineModeFix {
public static URLConnection openConnection(URL address, Proxy proxy) throws IOException {
// we start with "http://www.minecraft.net/game/joinserver.jsp?user=..."
if (!(address.getHost().equals("www.minecraft.net") && address.getPath().equals("/game/joinserver.jsp")))
return null;
// change it to "https://session.minecraft.net/game/joinserver.jsp?user=..."
// this seems to be the modern version of the same endpoint...
// maybe Mojang planned to patch old versions of the game to use it
// if it ever disappears this should be changed to use sessionserver.mojang.com/session/minecraft/join
// which of course has a different usage requiring JSON serialisation...
URL url;
try {
url = new URL("https", "session.minecraft.net", address.getPort(), address.getFile());
} catch (MalformedURLException e) {
throw new AssertionError("url should be valid", e);
}
return UrlUtils.openConnection(url, proxy);
}
}

View File

@ -54,7 +54,7 @@ package org.prismlauncher.legacy.fix.online;
import org.prismlauncher.legacy.utils.api.MojangApi;
import org.prismlauncher.legacy.utils.api.Texture;
import org.prismlauncher.legacy.utils.url.CustomUrlConnection;
import org.prismlauncher.legacy.utils.url.ByteArrayUrlConnection;
import org.prismlauncher.legacy.utils.url.UrlUtils;
import java.awt.AlphaComposite;
@ -97,9 +97,9 @@ final class SkinFix {
URLConnection connection = UrlUtils.openConnection(texture.getUrl(), proxy);
try (InputStream in = connection.getInputStream()) {
// thank you craftycodie!
// thank you ahnewark!
// this is heavily based on
// https://github.com/craftycodie/MineOnline/blob/4f4f86f9d051e0a6fd7ff0b95b2a05f7437683d7/src/main/java/gg/codie/mineonline/gui/textures/TextureHelper.java#L17
// https://github.com/ahnewark/MineOnline/blob/4f4f86f9d051e0a6fd7ff0b95b2a05f7437683d7/src/main/java/gg/codie/mineonline/gui/textures/TextureHelper.java#L17
BufferedImage image = ImageIO.read(in);
Graphics2D graphics = image.createGraphics();
graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
@ -131,7 +131,7 @@ final class SkinFix {
image = image.getSubimage(0, 0, 64, 32);
ImageIO.write(image, "png", out);
return new CustomUrlConnection(out.toByteArray());
return new ByteArrayUrlConnection(out.toByteArray());
}
}

View File

@ -40,16 +40,12 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
public final class CustomUrlConnection extends HttpURLConnection {
public final class ByteArrayUrlConnection extends HttpURLConnection {
private final InputStream in;
public CustomUrlConnection(byte[] data) {
this(new ByteArrayInputStream(data));
}
public CustomUrlConnection(InputStream in) {
public ByteArrayUrlConnection(byte[] data) {
super(null);
this.in = in;
this.in = new ByteArrayInputStream(data);
}
@Override
@ -58,12 +54,7 @@ public final class CustomUrlConnection extends HttpURLConnection {
}
@Override
public void disconnect() {
try {
in.close();
} catch (IOException e) {
}
}
public void disconnect() {}
@Override
public InputStream getInputStream() throws IOException {

View File

@ -101,7 +101,7 @@ public final class UrlUtils {
} catch (IOException | Error | RuntimeException e) {
throw e; // rethrow if possible
} catch (Throwable e) {
throw new AssertionError(e); // oh dear! this isn't meant to happen
throw new AssertionError("openConnection should not throw", e); // oh dear! this isn't meant to happen
}
}
}

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