Compare commits
266 Commits
develop
...
fjord-deve
Author | SHA1 | Date | |
---|---|---|---|
|
8555ede5c4 | ||
|
35131193dd | ||
|
ce0a730531 | ||
|
f00226f797 | ||
|
a96a915d79 | ||
|
83f79c93f8 | ||
|
b228800de1 | ||
|
e8bdc72476 | ||
|
da9c359184 | ||
|
b02718ee57 | ||
|
ddcd61c808 | ||
|
760fbdf54f | ||
|
5c4bd3db52 | ||
|
87c5b6cc83 | ||
|
80ff62c96d | ||
|
b5fc23952e | ||
|
5c8ef3a003 | ||
|
b0ee4f6e58 | ||
|
25ce20a877 | ||
|
2952b1d6b6 | ||
|
416495cda7 | ||
|
6de8ed91a3 | ||
|
f9ab98f2cf | ||
|
fcc1e80237 | ||
|
1a56396e2e | ||
|
0f21e01b46 | ||
|
88109eaa71 | ||
|
1425c9801e | ||
|
808277d2f8 | ||
|
3ced5d6546 | ||
|
43cd5853c1 | ||
|
9116d373e7 | ||
|
1a06e34fe4 | ||
|
d0a77df703 | ||
|
57120e0c9d | ||
|
0e943b4bb1 | ||
|
57981a070f | ||
|
be1a1baba4 | ||
|
9686a3c81c | ||
|
b348114dee | ||
|
07604c9271 | ||
|
fde597aa0a | ||
|
84e9606e4f | ||
|
d9ca7d2e74 | ||
|
859ab72ea1 | ||
|
b49e9763bf | ||
|
b9d15a4781 | ||
|
190d17acd3 | ||
|
5de91076ea | ||
|
299cbf8009 | ||
|
70ca7af205 | ||
|
19aaaa9988 | ||
|
fc639c0902 | ||
|
2bb094a90b | ||
|
c206064976 | ||
|
15aff73aa1 | ||
|
d8f51e900a | ||
|
b85e53eaf9 | ||
|
0073d82863 | ||
|
70db5a2f92 | ||
|
07010f226e | ||
|
d75b23c092 | ||
|
748b92571c | ||
|
97180324ee | ||
|
ec0baa6a1e | ||
|
2bf6d66615 | ||
|
abd55c9d38 | ||
|
c45000eac3 | ||
|
162188eaec | ||
|
9842c2f24f | ||
|
86903bfc7f | ||
|
61892f857f | ||
|
63edfdf7ac | ||
|
ec7aaaadf3 | ||
|
7d786987ad | ||
|
1ab6ce42b5 | ||
|
05bf9c3689 | ||
|
f760f08df6 | ||
|
9456140155 | ||
|
159771f283 | ||
|
9616d898d4 | ||
|
e2526c80c8 | ||
|
a37cac6f9b | ||
|
2f889429da | ||
|
c7831fd697 | ||
|
b4c3123957 | ||
|
ae3e1a262e | ||
|
f8dc58665b | ||
|
19247bad14 | ||
|
4371933a84 | ||
|
dd90049b30 | ||
|
18170cc900 | ||
|
71eced3e61 | ||
|
cb49b27513 | ||
|
7b502fe8c9 | ||
|
55578bf949 | ||
|
ee33bf50c2 | ||
|
702db71f61 | ||
|
8e8efc304f | ||
|
2b204c8169 | ||
|
ed2eddc258 | ||
|
855e49bda0 | ||
|
bbde47842b | ||
|
0508bbd05f | ||
|
808978080e | ||
|
ea0d710aaf | ||
|
02e1988779 | ||
|
fdab044c04 | ||
|
e77ac36c3f | ||
|
0d06fae7ae | ||
|
a0c4cc59ac | ||
|
bd7371dbf1 | ||
|
d7756d29dc | ||
|
0a7325ac12 | ||
|
ed2b5d4b0e | ||
|
9f199a470c | ||
|
1a98ef8bb7 | ||
|
d25d0d7ee6 | ||
|
beac093d62 | ||
|
81318d4953 | ||
|
8c6602147b | ||
|
9d8c3f251f | ||
|
c787e65ed4 | ||
|
ae84a78470 | ||
|
820b9e3fd0 | ||
|
c8c3370d36 | ||
|
cffa9a2da0 | ||
|
01397896eb | ||
|
3048e43900 | ||
|
417bb2fce6 | ||
|
e234c66818 | ||
|
589ee73bfd | ||
|
0cb5ac7a06 | ||
|
db35f47105 | ||
|
cf8a82e78a | ||
|
15887c2d92 | ||
|
b7b5630588 | ||
|
43834e2148 | ||
|
feb275a580 | ||
|
759db0c90f | ||
|
b2c75dde95 | ||
|
62ed3f8406 | ||
|
bf48b45ff1 | ||
|
d931e8fa49 | ||
|
a108c8678e | ||
|
5e227ceaae | ||
|
a5fcdd03bf | ||
|
d2d6320e44 | ||
|
8cd807c2d6 | ||
|
ccc886b51f | ||
|
594fa981ea | ||
|
a739db8bdb | ||
|
936252e445 | ||
|
fdfdf2eef1 | ||
|
bbfa5a5ed1 | ||
|
43dc330e55 | ||
|
0c962dff59 | ||
|
ca0dd583ba | ||
|
7bb686c85b | ||
|
f8d3d8399d | ||
|
9cb2a3ab2d | ||
|
072c495ec6 | ||
|
335941fb46 | ||
|
ebeff36da5 | ||
|
1d29f63bec | ||
|
87dc1bb6a6 | ||
|
a85720b8d6 | ||
|
4c28a94de1 | ||
|
344ae87f25 | ||
|
577999bd35 | ||
|
887db29f35 | ||
|
7865b8128e | ||
|
a58f125535 | ||
|
d1a05d312d | ||
|
bbf21e5824 | ||
|
73b4223b61 | ||
|
686e0b7b18 | ||
|
189878e7e3 | ||
|
af74d5f410 | ||
|
cdfd0b1002 | ||
|
9791c306dc | ||
|
e7e9265c40 | ||
|
6879344334 | ||
|
a5bca15d46 | ||
|
7b5a2baa36 | ||
|
70e662ca7d | ||
|
7516b3c70c | ||
|
8f44b6513f | ||
|
e0c323a190 | ||
|
a914747416 | ||
|
e2de54b6b3 | ||
|
d453240a94 | ||
|
4c4017d7ca | ||
|
2a4c4ed8ea | ||
|
947fbf7d64 | ||
|
1c94a3e3e5 | ||
|
eefeb5799d | ||
|
6c6a4625ab | ||
|
6d2a96f6a7 | ||
|
ea9029d7b6 | ||
|
76602391f4 | ||
|
509f8c7307 | ||
|
984daa450b | ||
|
04e1a97d59 | ||
|
1dce1360bd | ||
|
87d5b96760 | ||
|
7f074ca7b9 | ||
|
c0394c52b7 | ||
|
53bc20f13f | ||
|
40af3cf3d2 | ||
|
3977918c5b | ||
|
484d90899f | ||
|
0f5eb03839 | ||
|
d21cda1880 | ||
|
52a321b93b | ||
|
dd8dec8be8 | ||
|
390af2df45 | ||
|
4e154d2b47 | ||
|
23d6f8aaed | ||
|
d105d76f2a | ||
|
698a838b56 | ||
|
1129b4160c | ||
|
63a6f823fd | ||
|
db32b2ad99 | ||
|
c3cf5d31da | ||
|
1e696328bb | ||
|
1d9c97803a | ||
|
3d55236fdf | ||
|
42190819ae | ||
|
130714a9ab | ||
|
bf57d77075 | ||
|
98fe035442 | ||
|
8e4994590b | ||
|
0722dba820 | ||
|
9560447c92 | ||
|
4d89512ca1 | ||
|
42a6bd1151 | ||
|
f06505b138 | ||
|
d854087089 | ||
|
31031ec923 | ||
|
5f06517b62 | ||
|
dde5fc47d3 | ||
|
76d5f632dc | ||
|
27c6596bcb | ||
|
e2f5fb27a6 | ||
|
98819a0d02 | ||
|
2dde2c4bec | ||
|
008d69e5e5 | ||
|
a839258c07 | ||
|
89041531e1 | ||
|
729cec5f45 | ||
|
80d675d2e6 | ||
|
fb7d19941f | ||
|
877ab62c2f | ||
|
b983ae0fb0 | ||
|
85422427b9 | ||
|
51a71d0471 | ||
|
25eaa4eba6 | ||
|
6da14d66bb | ||
|
199c5497d3 | ||
|
740db2db02 | ||
|
577f8074e1 | ||
|
c5e7bb90c5 | ||
|
1cf91fa5d9 | ||
|
3bf4fbf8f4 | ||
|
ff97affa72 |
133
.github/workflows/build.yml
vendored
133
.github/workflows/build.yml
vendored
@ -39,9 +39,6 @@ on:
|
|||||||
APPLE_NOTARIZE_PASSWORD:
|
APPLE_NOTARIZE_PASSWORD:
|
||||||
description: Password used for notarizing macOS builds
|
description: Password used for notarizing macOS builds
|
||||||
required: false
|
required: false
|
||||||
CACHIX_AUTH_TOKEN:
|
|
||||||
description: Private token for authenticating against Cachix cache
|
|
||||||
required: false
|
|
||||||
GPG_PRIVATE_KEY:
|
GPG_PRIVATE_KEY:
|
||||||
description: Private key for AppImage signing
|
description: Private key for AppImage signing
|
||||||
required: false
|
required: false
|
||||||
@ -59,15 +56,18 @@ jobs:
|
|||||||
qt_ver: 5
|
qt_ver: 5
|
||||||
qt_host: linux
|
qt_host: linux
|
||||||
qt_arch: ""
|
qt_arch: ""
|
||||||
qt_version: "5.12.8"
|
qt_version: "5.15.2"
|
||||||
qt_modules: "qtnetworkauth"
|
qt_modules: "qtnetworkauth"
|
||||||
|
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-22.04
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: linux
|
qt_host: linux
|
||||||
qt_arch: ""
|
qt_arch: ""
|
||||||
qt_version: "6.2.4"
|
qt_version: "6.5.3"
|
||||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
|
linuxdeploy_hash: "4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage"
|
||||||
|
linuxdeploy_qt_hash: "15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage"
|
||||||
|
appimageupdate_hash: "f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage"
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MinGW-w64"
|
name: "Windows-MinGW-w64"
|
||||||
@ -80,9 +80,9 @@ jobs:
|
|||||||
architecture: "x64"
|
architecture: "x64"
|
||||||
vcvars_arch: "amd64"
|
vcvars_arch: "amd64"
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: windows
|
qt_host: "windows"
|
||||||
qt_arch: ""
|
qt_arch: "win64_msvc2022_64"
|
||||||
qt_version: "6.7.3"
|
qt_version: "6.8.1"
|
||||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
nscurl_tag: "v24.9.26.122"
|
nscurl_tag: "v24.9.26.122"
|
||||||
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
|
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
|
||||||
@ -93,9 +93,9 @@ jobs:
|
|||||||
architecture: "arm64"
|
architecture: "arm64"
|
||||||
vcvars_arch: "amd64_arm64"
|
vcvars_arch: "amd64_arm64"
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: windows
|
qt_host: "windows"
|
||||||
qt_arch: "win64_msvc2019_arm64"
|
qt_arch: "win64_msvc2022_arm64_cross_compiled"
|
||||||
qt_version: "6.7.3"
|
qt_version: "6.8.1"
|
||||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
nscurl_tag: "v24.9.26.122"
|
nscurl_tag: "v24.9.26.122"
|
||||||
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
|
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
|
||||||
@ -106,7 +106,7 @@ jobs:
|
|||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: mac
|
qt_host: mac
|
||||||
qt_arch: ""
|
qt_arch: ""
|
||||||
qt_version: "6.7.3"
|
qt_version: "6.8.1"
|
||||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
|
|
||||||
- os: macos-14
|
- os: macos-14
|
||||||
@ -173,7 +173,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve ccache cache (Windows MinGW-w64)
|
- name: Retrieve ccache cache (Windows MinGW-w64)
|
||||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||||
uses: actions/cache@v4.1.1
|
uses: actions/cache@v4.2.0
|
||||||
with:
|
with:
|
||||||
path: '${{ github.workspace }}\.ccache'
|
path: '${{ github.workspace }}\.ccache'
|
||||||
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
||||||
@ -206,7 +206,7 @@ jobs:
|
|||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get -y update
|
sudo apt-get -y update
|
||||||
sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream
|
sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream libxcb-cursor-dev
|
||||||
|
|
||||||
- name: Install Dependencies (macOS)
|
- name: Install Dependencies (macOS)
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
@ -216,14 +216,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Install host Qt (Windows MSVC arm64)
|
- name: Install host Qt (Windows MSVC arm64)
|
||||||
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
aqtversion: "==3.1.*"
|
aqtversion: "==3.1.*"
|
||||||
py7zrversion: ">=0.20.2"
|
py7zrversion: ">=0.20.2"
|
||||||
version: ${{ matrix.qt_version }}
|
version: ${{ matrix.qt_version }}
|
||||||
host: "windows"
|
host: "windows"
|
||||||
target: "desktop"
|
target: "desktop"
|
||||||
arch: ""
|
arch: ${{ matrix.qt_arch }}
|
||||||
modules: ${{ matrix.qt_modules }}
|
modules: ${{ matrix.qt_modules }}
|
||||||
cache: ${{ inputs.is_qt_cached }}
|
cache: ${{ inputs.is_qt_cached }}
|
||||||
cache-key-prefix: host-qt-arm64-windows
|
cache-key-prefix: host-qt-arm64-windows
|
||||||
@ -232,7 +232,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install Qt (macOS, Linux & Windows MSVC)
|
- name: Install Qt (macOS, Linux & Windows MSVC)
|
||||||
if: matrix.msystem == ''
|
if: matrix.msystem == ''
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
aqtversion: "==3.1.*"
|
aqtversion: "==3.1.*"
|
||||||
py7zrversion: ">=0.20.2"
|
py7zrversion: ">=0.20.2"
|
||||||
@ -252,19 +252,26 @@ jobs:
|
|||||||
|
|
||||||
- name: Prepare AppImage (Linux)
|
- name: Prepare AppImage (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
|
env:
|
||||||
|
APPIMAGEUPDATE_HASH: ${{ matrix.appimageupdate_hash }}
|
||||||
|
LINUXDEPLOY_HASH: ${{ matrix.linuxdeploy_hash }}
|
||||||
|
LINUXDEPLOY_QT_HASH: ${{ matrix.linuxdeploy_qt_hash }}
|
||||||
run: |
|
run: |
|
||||||
wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
|
wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage"
|
||||||
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage"
|
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage"
|
||||||
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
|
|
||||||
|
|
||||||
wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage"
|
wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage"
|
||||||
|
|
||||||
sudo apt install libopengl0
|
sha256sum -c - <<< "$LINUXDEPLOY_HASH"
|
||||||
|
sha256sum -c - <<< "$LINUXDEPLOY_QT_HASH"
|
||||||
|
sha256sum -c - <<< "$APPIMAGEUPDATE_HASH"
|
||||||
|
|
||||||
|
sudo apt install libopengl0 libfuse2
|
||||||
|
|
||||||
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
|
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
|
||||||
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
||||||
run: |
|
run: |
|
||||||
echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2019_64" >> $env:GITHUB_ENV
|
echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2022_64" >> $env:GITHUB_ENV
|
||||||
|
|
||||||
- name: Setup java (macOS)
|
- name: Setup java (macOS)
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
@ -519,8 +526,8 @@ jobs:
|
|||||||
|
|
||||||
cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
||||||
|
|
||||||
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||||
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||||
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||||
|
|
||||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
|
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
|
||||||
@ -555,9 +562,9 @@ jobs:
|
|||||||
mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
cp /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
cp /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
cp /usr/lib/x86_64-linux-gnu/libffi.so.7 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
cp /usr/lib/x86_64-linux-gnu/libffi.so.*.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
|
||||||
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
||||||
@ -629,71 +636,3 @@ jobs:
|
|||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
run: |
|
run: |
|
||||||
ccache -s
|
ccache -s
|
||||||
|
|
||||||
flatpak:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: bilelmoussaoui/flatpak-github-actions:kde-6.7
|
|
||||||
options: --privileged
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
if: inputs.build_type == 'Debug'
|
|
||||||
with:
|
|
||||||
submodules: "true"
|
|
||||||
- name: Build Flatpak (Linux)
|
|
||||||
if: inputs.build_type == 'Debug'
|
|
||||||
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
|
||||||
with:
|
|
||||||
bundle: "Fjord Launcher.flatpak"
|
|
||||||
manifest-path: flatpak/org.unmojang.FjordLauncher.yml
|
|
||||||
|
|
||||||
nix:
|
|
||||||
name: Nix (${{ matrix.system }})
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: ubuntu-22.04
|
|
||||||
system: x86_64-linux
|
|
||||||
|
|
||||||
- os: macos-13
|
|
||||||
system: x86_64-darwin
|
|
||||||
|
|
||||||
- os: macos-14
|
|
||||||
system: aarch64-darwin
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install Nix
|
|
||||||
uses: cachix/install-nix-action@v30
|
|
||||||
|
|
||||||
# For PRs
|
|
||||||
- name: Setup Nix Magic Cache
|
|
||||||
uses: DeterminateSystems/magic-nix-cache-action@v8
|
|
||||||
|
|
||||||
# For in-tree builds
|
|
||||||
- name: Setup Cachix
|
|
||||||
uses: cachix/cachix-action@v15
|
|
||||||
with:
|
|
||||||
name: unmojang
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
|
|
||||||
- name: Run flake checks
|
|
||||||
run: |
|
|
||||||
nix flake check --print-build-logs --show-trace
|
|
||||||
|
|
||||||
- name: Build debug package
|
|
||||||
if: ${{ inputs.build_type == 'Debug' }}
|
|
||||||
run: |
|
|
||||||
nix build --print-build-logs .#fjordlauncher-debug
|
|
||||||
|
|
||||||
- name: Build release package
|
|
||||||
if: ${{ inputs.build_type != 'Debug' }}
|
|
||||||
run: |
|
|
||||||
nix build --print-build-logs .#fjordlauncher
|
|
||||||
|
62
.github/workflows/flatpak.yml
vendored
Normal file
62
.github/workflows/flatpak.yml
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
name: Flatpak
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- "**.md"
|
||||||
|
- "**/LICENSE"
|
||||||
|
- ".github/ISSUE_TEMPLATE/**"
|
||||||
|
- ".markdownlint**"
|
||||||
|
- "nix/**"
|
||||||
|
# We don't do anything with these artifacts on releases. They go to Flathub
|
||||||
|
tags-ignore:
|
||||||
|
- "*"
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- "**.md"
|
||||||
|
- "**/LICENSE"
|
||||||
|
- ".github/ISSUE_TEMPLATE/**"
|
||||||
|
- ".markdownlint**"
|
||||||
|
- "nix/**"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build (${{ matrix.arch }})
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-22.04
|
||||||
|
arch: x86_64
|
||||||
|
|
||||||
|
- os: ubuntu-22.04-arm
|
||||||
|
arch: aarch64
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
container:
|
||||||
|
image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.8
|
||||||
|
options: --privileged
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Set short version
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "VERSION=${GITHUB_SHA::7}" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Build Flatpak
|
||||||
|
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
||||||
|
with:
|
||||||
|
bundle: FjordLauncher-${{ runner.os }}-${{ env.VERSION }}-Flatpak.flatpak
|
||||||
|
manifest-path: flatpak/org.unmojang.FjordLauncher.yml
|
||||||
|
arch: ${{ matrix.arch }}
|
88
.github/workflows/nix.yml
vendored
Normal file
88
.github/workflows/nix.yml
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
name: Nix
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- "**.md"
|
||||||
|
- "**/LICENSE"
|
||||||
|
- ".github/ISSUE_TEMPLATE/**"
|
||||||
|
- ".markdownlint**"
|
||||||
|
- "flatpak/**"
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
pull_request_target:
|
||||||
|
paths-ignore:
|
||||||
|
- "**.md"
|
||||||
|
- "**/LICENSE"
|
||||||
|
- ".github/ISSUE_TEMPLATE/**"
|
||||||
|
- ".markdownlint**"
|
||||||
|
- "flatpak/**"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
DEBUG: ${{ github.ref_type != 'tag' }}
|
||||||
|
USE_DETERMINATE: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build (${{ matrix.system }})
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-22.04
|
||||||
|
system: x86_64-linux
|
||||||
|
|
||||||
|
- os: ubuntu-22.04-arm
|
||||||
|
system: aarch64-linux
|
||||||
|
|
||||||
|
- os: macos-13
|
||||||
|
system: x86_64-darwin
|
||||||
|
|
||||||
|
- os: macos-14
|
||||||
|
system: aarch64-darwin
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Nix
|
||||||
|
uses: DeterminateSystems/nix-installer-action@v16
|
||||||
|
with:
|
||||||
|
determinate: ${{ env.USE_DETERMINATE }}
|
||||||
|
|
||||||
|
# For PRs
|
||||||
|
- name: Setup Nix Magic Cache
|
||||||
|
if: ${{ env.USE_DETERMINATE }}
|
||||||
|
uses: DeterminateSystems/flakehub-cache-action@v1
|
||||||
|
|
||||||
|
# For in-tree builds
|
||||||
|
- name: Setup Cachix
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
|
||||||
|
uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: unmojang
|
||||||
|
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
|
||||||
|
- name: Run Flake checks
|
||||||
|
run: |
|
||||||
|
nix flake check --print-build-logs --show-trace
|
||||||
|
|
||||||
|
- name: Build debug package
|
||||||
|
if: ${{ env.DEBUG }}
|
||||||
|
run: |
|
||||||
|
nix build --print-build-logs .#fjordlauncher-debug
|
||||||
|
|
||||||
|
- name: Build release package
|
||||||
|
if: ${{ !env.DEBUG }}
|
||||||
|
run: |
|
||||||
|
nix build --print-build-logs .#fjordlauncher
|
45
.github/workflows/publish.yml
vendored
Normal file
45
.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
name: Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [ released ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
flakehub:
|
||||||
|
name: FlakeHub
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.ref }}
|
||||||
|
|
||||||
|
- name: Install Nix
|
||||||
|
uses: cachix/install-nix-action@v30
|
||||||
|
|
||||||
|
- name: Publish on FlakeHub
|
||||||
|
uses: determinatesystems/flakehub-push@v5
|
||||||
|
with:
|
||||||
|
visibility: "public"
|
||||||
|
|
||||||
|
winget:
|
||||||
|
name: Winget
|
||||||
|
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Publish on Winget
|
||||||
|
uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
|
with:
|
||||||
|
identifier: PrismLauncher.PrismLauncher
|
||||||
|
version: ${{ github.event.release.tag_name }}
|
||||||
|
installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$'
|
||||||
|
token: ${{ secrets.WINGET_TOKEN }}
|
1
.github/workflows/trigger_builds.yml
vendored
1
.github/workflows/trigger_builds.yml
vendored
@ -38,6 +38,5 @@ jobs:
|
|||||||
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
||||||
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
||||||
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
||||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||||
|
1
.github/workflows/trigger_release.yml
vendored
1
.github/workflows/trigger_release.yml
vendored
@ -22,7 +22,6 @@ jobs:
|
|||||||
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
||||||
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
||||||
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
||||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||||
|
|
||||||
|
15
.github/workflows/winget.yml
vendored
15
.github/workflows/winget.yml
vendored
@ -1,15 +0,0 @@
|
|||||||
name: Publish to WinGet
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [released]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- uses: vedantmgoyal2009/winget-releaser@v2
|
|
||||||
with:
|
|
||||||
identifier: PrismLauncher.PrismLauncher
|
|
||||||
version: ${{ github.event.release.tag_name }}
|
|
||||||
installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$'
|
|
||||||
token: ${{ secrets.WINGET_TOKEN }}
|
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -47,8 +47,12 @@ run/
|
|||||||
|
|
||||||
# Nix/NixOS
|
# Nix/NixOS
|
||||||
.direnv/
|
.direnv/
|
||||||
.pre-commit-config.yaml
|
## Used when manually invoking stdenv phases
|
||||||
|
outputs/
|
||||||
|
## Regular artifacts
|
||||||
result
|
result
|
||||||
|
result-*
|
||||||
|
repl-result-*
|
||||||
|
|
||||||
# Flatpak
|
# Flatpak
|
||||||
.flatpak-builder
|
.flatpak-builder
|
||||||
|
@ -78,6 +78,13 @@ else()
|
|||||||
# ATL's pack list needs more than the default 1 Mib stack on windows
|
# ATL's pack list needs more than the default 1 Mib stack on windows
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}")
|
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}")
|
||||||
|
|
||||||
|
# -ffunction-sections and -fdata-sections help reduce binary size
|
||||||
|
# -mguard=cf enables Control Flow Guard
|
||||||
|
# TODO: Look into -gc-sections to further reduce binary size
|
||||||
|
foreach(lang C CXX)
|
||||||
|
set("CMAKE_${lang}_FLAGS_RELEASE" "-ffunction-sections -fdata-sections -mguard=cf")
|
||||||
|
endforeach()
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -106,14 +113,14 @@ if ((CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebI
|
|||||||
else()
|
else()
|
||||||
# AppleClang and Clang
|
# AppleClang and Clang
|
||||||
message(STATUS "Address Sanitizer available on Clang")
|
message(STATUS "Address Sanitizer available on Clang")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=null")
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=null")
|
||||||
endif()
|
endif()
|
||||||
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
||||||
# GCC
|
# GCC
|
||||||
message(STATUS "Address Sanitizer available on GCC")
|
message(STATUS "Address Sanitizer available on GCC")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover")
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover")
|
||||||
link_libraries("asan")
|
link_libraries("asan")
|
||||||
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
|
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
|
||||||
message(STATUS "Address Sanitizer available on MSVC")
|
message(STATUS "Address Sanitizer available on MSVC")
|
||||||
@ -181,7 +188,7 @@ set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE S
|
|||||||
|
|
||||||
######## Set version numbers ########
|
######## Set version numbers ########
|
||||||
set(Launcher_VERSION_MAJOR 9)
|
set(Launcher_VERSION_MAJOR 9)
|
||||||
set(Launcher_VERSION_MINOR 1)
|
set(Launcher_VERSION_MINOR 3)
|
||||||
set(Launcher_VERSION_PATCH 0)
|
set(Launcher_VERSION_PATCH 0)
|
||||||
|
|
||||||
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}")
|
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}")
|
||||||
@ -192,7 +199,7 @@ set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_M
|
|||||||
set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
|
set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
|
||||||
|
|
||||||
# Github repo URL with releases for updater
|
# Github repo URL with releases for updater
|
||||||
set(Launcher_UPDATER_GITHUB_REPO "" CACHE STRING "Base github URL for the updater.")
|
set(Launcher_UPDATER_GITHUB_REPO "https://github.com/unmojang/FjordLauncher" CACHE STRING "Base github URL for the updater.")
|
||||||
|
|
||||||
# Name to help updater identify valid artifacts
|
# Name to help updater identify valid artifacts
|
||||||
set(Launcher_BUILD_ARTIFACT "" CACHE STRING "Artifact name to help the updater identify valid artifacts.")
|
set(Launcher_BUILD_ARTIFACT "" CACHE STRING "Artifact name to help the updater identify valid artifacts.")
|
||||||
@ -390,8 +397,8 @@ if(UNIX AND APPLE)
|
|||||||
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "" CACHE STRING "Public key for Sparkle update feed")
|
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "" CACHE STRING "Public key for Sparkle update feed")
|
||||||
set(MACOSX_SPARKLE_UPDATE_FEED_URL "" CACHE STRING "URL for Sparkle update feed")
|
set(MACOSX_SPARKLE_UPDATE_FEED_URL "" CACHE STRING "URL for Sparkle update feed")
|
||||||
|
|
||||||
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.5.2/Sparkle-2.5.2.tar.xz" CACHE STRING "URL to Sparkle release archive")
|
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.6.4/Sparkle-2.6.4.tar.xz" CACHE STRING "URL to Sparkle release archive")
|
||||||
set(MACOSX_SPARKLE_SHA256 "572dd67ae398a466f19f343a449e1890bac1ef74885b4739f68f979a8a89884b" CACHE STRING "SHA256 checksum for Sparkle release archive")
|
set(MACOSX_SPARKLE_SHA256 "50612a06038abc931f16011d7903b8326a362c1074dabccb718404ce8e585f0b" CACHE STRING "SHA256 checksum for Sparkle release archive")
|
||||||
set(MACOSX_SPARKLE_DIR "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
|
set(MACOSX_SPARKLE_DIR "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
|
||||||
|
|
||||||
# directories to look for dependencies
|
# directories to look for dependencies
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
## Fjord Launcher
|
## Fjord Launcher
|
||||||
|
|
||||||
Fjord Launcher - Minecraft Launcher
|
Fjord Launcher - Minecraft Launcher
|
||||||
Copyright (C) 2024-2024 Fjord Launcher Contributors
|
Copyright (C) 2024-2025 Fjord Launcher Contributors
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@ -69,7 +69,7 @@
|
|||||||
## Prism Launcher
|
## Prism Launcher
|
||||||
|
|
||||||
Prism Launcher - Minecraft Launcher
|
Prism Launcher - Minecraft Launcher
|
||||||
Copyright (C) 2022-2024 Prism Launcher Contributors
|
Copyright (C) 2022-2025 Prism Launcher Contributors
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -72,7 +72,6 @@ Install from the MPR with [Mist](https://docs.makedeb.org/using-the-mpr/mist-the
|
|||||||
|
|
||||||
```Shell
|
```Shell
|
||||||
mist install fjordlauncher
|
mist install fjordlauncher
|
||||||
mist install fjordlauncher-bin # binary package
|
|
||||||
mist install fjordlauncher-git # build latest Git commit from source
|
mist install fjordlauncher-git # build latest Git commit from source
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ class Config {
|
|||||||
QString FMLLIBS_BASE_URL;
|
QString FMLLIBS_BASE_URL;
|
||||||
QString TRANSLATION_FILES_URL;
|
QString TRANSLATION_FILES_URL;
|
||||||
|
|
||||||
QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/";
|
QString MODPACKSCH_API_BASE_URL = "https://api.feed-the-beast.com/v1/modpacks/";
|
||||||
|
|
||||||
QString LEGACY_FTB_CDN_BASE_URL = "https://dist.creeper.host/FTB2/";
|
QString LEGACY_FTB_CDN_BASE_URL = "https://dist.creeper.host/FTB2/";
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
<string>A Minecraft mod wants to access your microphone.</string>
|
<string>A Minecraft mod wants to access your microphone.</string>
|
||||||
<key>NSDownloadsFolderUsageDescription</key>
|
<key>NSDownloadsFolderUsageDescription</key>
|
||||||
<string>Fjord uses access to your Downloads folder to help you more quickly add mods that can't be automatically downloaded to your instance. You can change where Fjord scans for downloaded mods in Settings or the prompt that appears.</string>
|
<string>Fjord uses access to your Downloads folder to help you more quickly add mods that can't be automatically downloaded to your instance. You can change where Fjord scans for downloaded mods in Settings or the prompt that appears.</string>
|
||||||
|
<key>NSLocalNetworkUsageDescription</key>
|
||||||
|
<string>Minecraft uses the local network to find and connect to LAN servers.</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
|
13
default.nix
13
default.nix
@ -1,9 +1,4 @@
|
|||||||
(import (
|
(import (fetchTarball {
|
||||||
let
|
url = "https://github.com/edolstra/flake-compat/archive/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec.tar.gz";
|
||||||
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
sha256 = "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=";
|
||||||
in
|
}) { src = ./.; }).defaultNix
|
||||||
fetchTarball {
|
|
||||||
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
|
||||||
sha256 = lock.nodes.flake-compat.locked.narHash;
|
|
||||||
}
|
|
||||||
) { src = ./.; }).defaultNix
|
|
||||||
|
39
flake.lock
generated
39
flake.lock
generated
@ -1,21 +1,5 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-compat": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1696426674,
|
|
||||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"libnbtplusplus": {
|
"libnbtplusplus": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
@ -32,28 +16,13 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nix-filter": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1710156097,
|
|
||||||
"narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "nix-filter",
|
|
||||||
"rev": "3342559a24e85fc164b295c3444e8a139924675b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "nix-filter",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1729256560,
|
"lastModified": 1743827369,
|
||||||
"narHash": "sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c+cHUJwA=",
|
"narHash": "sha256-rpqepOZ8Eo1zg+KJeWoq1HAOgoMCDloqv5r2EAa9TSA=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0",
|
"rev": "42a1c966be226125b48c384171c44c651c236c22",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -65,9 +34,7 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat",
|
|
||||||
"libnbtplusplus": "libnbtplusplus",
|
"libnbtplusplus": "libnbtplusplus",
|
||||||
"nix-filter": "nix-filter",
|
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
139
flake.nix
139
flake.nix
@ -15,28 +15,6 @@
|
|||||||
url = "github:PrismLauncher/libnbtplusplus";
|
url = "github:PrismLauncher/libnbtplusplus";
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
nix-filter.url = "github:numtide/nix-filter";
|
|
||||||
|
|
||||||
/*
|
|
||||||
Inputs below this are optional and can be removed
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
inputs.fjordlauncher = {
|
|
||||||
url = "github:unmojang/FjordLauncher";
|
|
||||||
inputs = {
|
|
||||||
flake-compat.follows = "";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
*/
|
|
||||||
|
|
||||||
flake-compat = {
|
|
||||||
url = "github:edolstra/flake-compat";
|
|
||||||
flake = false;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
@ -44,9 +22,8 @@
|
|||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
libnbtplusplus,
|
libnbtplusplus,
|
||||||
nix-filter,
|
|
||||||
...
|
|
||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (nixpkgs) lib;
|
inherit (nixpkgs) lib;
|
||||||
|
|
||||||
@ -58,45 +35,119 @@
|
|||||||
forAllSystems = lib.genAttrs systems;
|
forAllSystems = lib.genAttrs systems;
|
||||||
nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system});
|
nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system});
|
||||||
in
|
in
|
||||||
|
|
||||||
{
|
{
|
||||||
checks = forAllSystems (
|
checks = forAllSystems (
|
||||||
system:
|
system:
|
||||||
|
|
||||||
let
|
let
|
||||||
checks' = nixpkgsFor.${system}.callPackage ./nix/checks.nix { inherit self; };
|
pkgs = nixpkgsFor.${system};
|
||||||
|
llvm = pkgs.llvmPackages_19;
|
||||||
in
|
in
|
||||||
lib.filterAttrs (_: lib.isDerivation) checks'
|
|
||||||
|
{
|
||||||
|
formatting =
|
||||||
|
pkgs.runCommand "check-formatting"
|
||||||
|
{
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
deadnix
|
||||||
|
llvm.clang-tools
|
||||||
|
markdownlint-cli
|
||||||
|
nixfmt-rfc-style
|
||||||
|
statix
|
||||||
|
];
|
||||||
|
}
|
||||||
|
''
|
||||||
|
cd ${self}
|
||||||
|
|
||||||
|
echo "Running clang-format...."
|
||||||
|
clang-format --dry-run --style='file' --Werror */**.{c,cc,cpp,h,hh,hpp}
|
||||||
|
|
||||||
|
echo "Running deadnix..."
|
||||||
|
deadnix --fail
|
||||||
|
|
||||||
|
echo "Running markdownlint..."
|
||||||
|
markdownlint --dot .
|
||||||
|
|
||||||
|
echo "Running nixfmt..."
|
||||||
|
find -type f -name '*.nix' -exec nixfmt --check {} +
|
||||||
|
|
||||||
|
echo "Running statix"
|
||||||
|
statix check .
|
||||||
|
|
||||||
|
touch $out
|
||||||
|
'';
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
devShells = forAllSystems (
|
devShells = forAllSystems (
|
||||||
system:
|
system:
|
||||||
|
|
||||||
let
|
let
|
||||||
pkgs = nixpkgsFor.${system};
|
pkgs = nixpkgsFor.${system};
|
||||||
|
llvm = pkgs.llvmPackages_19;
|
||||||
|
|
||||||
|
packages' = self.packages.${system};
|
||||||
|
|
||||||
|
# Re-use our package wrapper to wrap our development environment
|
||||||
|
qt-wrapper-env = packages'.fjordlauncher.overrideAttrs (old: {
|
||||||
|
name = "qt-wrapper-env";
|
||||||
|
|
||||||
|
# Required to use script-based makeWrapper below
|
||||||
|
strictDeps = true;
|
||||||
|
|
||||||
|
# We don't need/want the unwrapped Fjord package
|
||||||
|
paths = [ ];
|
||||||
|
|
||||||
|
nativeBuildInputs = old.nativeBuildInputs or [ ] ++ [
|
||||||
|
# Ensure the wrapper is script based so it can be sourced
|
||||||
|
pkgs.makeWrapper
|
||||||
|
];
|
||||||
|
|
||||||
|
# Inspired by https://discourse.nixos.org/t/python-qt-woes/11808/10
|
||||||
|
buildCommand = ''
|
||||||
|
makeQtWrapper ${lib.getExe pkgs.runtimeShellPackage} "$out"
|
||||||
|
sed -i '/^exec/d' "$out"
|
||||||
|
'';
|
||||||
|
});
|
||||||
in
|
in
|
||||||
|
|
||||||
{
|
{
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
inputsFrom = [ self.packages.${system}.fjordlauncher-unwrapped ];
|
inputsFrom = [ packages'.fjordlauncher-unwrapped ];
|
||||||
buildInputs = with pkgs; [
|
|
||||||
|
packages = with pkgs; [
|
||||||
ccache
|
ccache
|
||||||
ninja
|
llvm.clang-tools
|
||||||
];
|
];
|
||||||
|
|
||||||
|
cmakeBuildType = "Debug";
|
||||||
|
cmakeFlags = [ "-GNinja" ] ++ packages'.fjordlauncher.cmakeFlags;
|
||||||
|
dontFixCmake = true;
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
echo "Sourcing ${qt-wrapper-env}"
|
||||||
|
source ${qt-wrapper-env}
|
||||||
|
|
||||||
|
git submodule update --init --force
|
||||||
|
|
||||||
|
if [ ! -f compile_commands.json ]; then
|
||||||
|
cmakeConfigurePhase
|
||||||
|
cd ..
|
||||||
|
ln -s "$cmakeBuildDir"/compile_commands.json compile_commands.json
|
||||||
|
fi
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style);
|
formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style);
|
||||||
|
|
||||||
overlays.default =
|
overlays.default = final: prev: {
|
||||||
final: prev:
|
|
||||||
let
|
|
||||||
version = builtins.substring 0 8 self.lastModifiedDate or "dirty";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
fjordlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
|
fjordlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
|
||||||
inherit
|
inherit
|
||||||
libnbtplusplus
|
libnbtplusplus
|
||||||
nix-filter
|
|
||||||
self
|
self
|
||||||
version
|
|
||||||
;
|
;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -105,6 +156,7 @@
|
|||||||
|
|
||||||
packages = forAllSystems (
|
packages = forAllSystems (
|
||||||
system:
|
system:
|
||||||
|
|
||||||
let
|
let
|
||||||
pkgs = nixpkgsFor.${system};
|
pkgs = nixpkgsFor.${system};
|
||||||
|
|
||||||
@ -117,6 +169,7 @@
|
|||||||
default = fjordPackages.fjordlauncher;
|
default = fjordPackages.fjordlauncher;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|
||||||
# Only output them if they're available on the current system
|
# Only output them if they're available on the current system
|
||||||
lib.filterAttrs (_: lib.meta.availableOn pkgs.stdenv.hostPlatform) packages
|
lib.filterAttrs (_: lib.meta.availableOn pkgs.stdenv.hostPlatform) packages
|
||||||
);
|
);
|
||||||
@ -124,16 +177,18 @@
|
|||||||
# We put these under legacyPackages as they are meant for CI, not end user consumption
|
# We put these under legacyPackages as they are meant for CI, not end user consumption
|
||||||
legacyPackages = forAllSystems (
|
legacyPackages = forAllSystems (
|
||||||
system:
|
system:
|
||||||
|
|
||||||
let
|
let
|
||||||
fjordPackages = self.packages.${system};
|
packages' = self.packages.${system};
|
||||||
legacyPackages = self.legacyPackages.${system};
|
legacyPackages' = self.legacyPackages.${system};
|
||||||
in
|
in
|
||||||
|
|
||||||
{
|
{
|
||||||
fjordlauncher-debug = fjordPackages.fjordlauncher.override {
|
fjordlauncher-debug = packages'.fjordlauncher.override {
|
||||||
fjordlauncher-unwrapped = legacyPackages.fjordlauncher-unwrapped-debug;
|
fjordlauncher-unwrapped = legacyPackages'.fjordlauncher-unwrapped-debug;
|
||||||
};
|
};
|
||||||
|
|
||||||
fjordlauncher-unwrapped-debug = fjordPackages.fjordlauncher-unwrapped.overrideAttrs {
|
fjordlauncher-unwrapped-debug = packages'.fjordlauncher-unwrapped.overrideAttrs {
|
||||||
cmakeBuildType = "Debug";
|
cmakeBuildType = "Debug";
|
||||||
dontStrip = true;
|
dontStrip = true;
|
||||||
};
|
};
|
||||||
|
20
flatpak/flite.json
Normal file
20
flatpak/flite.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "flite",
|
||||||
|
"config-opts": [
|
||||||
|
"--enable-shared",
|
||||||
|
"--with-audio=pulseaudio"
|
||||||
|
],
|
||||||
|
"no-parallel-make": true,
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/festvox/flite.git",
|
||||||
|
"tag": "v2.2",
|
||||||
|
"commit": "e9e2e37c329dbe98bfeb27a1828ef9a71fa84f88",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "git",
|
||||||
|
"tag-pattern": "^v([\\d.]+)$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -8,11 +8,7 @@
|
|||||||
{
|
{
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitlab.freedesktop.org/libdecor/libdecor.git",
|
"url": "https://gitlab.freedesktop.org/libdecor/libdecor.git",
|
||||||
"commit": "73260393a97291c887e1074ab7f318e031be0ac6"
|
"commit": "c2bd8ad6fa42c0cb17553ce77ad8a87d1f543b1f"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patch",
|
|
||||||
"path": "patches/weird_libdecor.patch"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"cleanup": [
|
"cleanup": [
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
id: org.unmojang.FjordLauncher
|
id: org.unmojang.FjordLauncher
|
||||||
runtime: org.kde.Platform
|
runtime: org.kde.Platform
|
||||||
runtime-version: 6.7
|
runtime-version: '6.8'
|
||||||
sdk: org.kde.Sdk
|
sdk: org.kde.Sdk
|
||||||
sdk-extensions:
|
sdk-extensions:
|
||||||
- org.freedesktop.Sdk.Extension.openjdk17
|
- org.freedesktop.Sdk.Extension.openjdk17
|
||||||
@ -19,6 +19,12 @@ finish-args:
|
|||||||
- --filesystem=xdg-download:ro
|
- --filesystem=xdg-download:ro
|
||||||
# FTBApp import
|
# FTBApp import
|
||||||
- --filesystem=~/.ftba:ro
|
- --filesystem=~/.ftba:ro
|
||||||
|
# Userspace visibility for manual hugepages configuration
|
||||||
|
# Required for -XX:+UseLargePages
|
||||||
|
- --filesystem=/sys/kernel/mm/hugepages:ro
|
||||||
|
# Userspace visibility for transparent hugepages configuration
|
||||||
|
# Required for -XX:+UseTransparentHugePages
|
||||||
|
- --filesystem=/sys/kernel/mm/transparent_hugepage:ro
|
||||||
|
|
||||||
modules:
|
modules:
|
||||||
# Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31)
|
# Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31)
|
||||||
@ -27,11 +33,16 @@ modules:
|
|||||||
# Needed for proper Wayland support
|
# Needed for proper Wayland support
|
||||||
- libdecor.json
|
- libdecor.json
|
||||||
|
|
||||||
|
# Text to Speech in the game
|
||||||
|
- flite.json
|
||||||
|
|
||||||
- name: fjordlauncher
|
- name: fjordlauncher
|
||||||
buildsystem: cmake-ninja
|
buildsystem: cmake-ninja
|
||||||
builddir: true
|
builddir: true
|
||||||
config-opts:
|
config-opts:
|
||||||
- -DLauncher_BUILD_PLATFORM=flatpak
|
- -DLauncher_BUILD_PLATFORM=flatpak
|
||||||
|
# This allows us to manage and update Java independently of this Flatpak
|
||||||
|
- -DLauncher_ENABLE_JAVA_DOWNLOADER=ON
|
||||||
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
build-options:
|
build-options:
|
||||||
env:
|
env:
|
||||||
@ -47,18 +58,14 @@ modules:
|
|||||||
config-opts:
|
config-opts:
|
||||||
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
- -DBUILD_SHARED_LIBS:BOOL=ON
|
- -DBUILD_SHARED_LIBS:BOOL=ON
|
||||||
- -DGLFW_USE_WAYLAND:BOOL=ON
|
- -DGLFW_BUILD_WAYLAND:BOOL=ON
|
||||||
- -DGLFW_BUILD_DOCS:BOOL=OFF
|
- -DGLFW_BUILD_DOCS:BOOL=OFF
|
||||||
sources:
|
sources:
|
||||||
- type: git
|
- type: git
|
||||||
url: https://github.com/glfw/glfw.git
|
url: https://github.com/glfw/glfw.git
|
||||||
commit: 3fa2360720eeba1964df3c0ecf4b5df8648a8e52
|
commit: 7b6aead9fb88b3623e3b3725ebb42670cbe4c579 # 3.4
|
||||||
- type: patch
|
- type: patch
|
||||||
path: patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch
|
path: patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch
|
||||||
- type: patch
|
|
||||||
path: patches/0005-Add-warning-about-being-an-unofficial-patch.patch
|
|
||||||
- type: patch
|
|
||||||
path: patches/0007-Platform-Prefer-Wayland-over-X11.patch
|
|
||||||
cleanup:
|
cleanup:
|
||||||
- /include
|
- /include
|
||||||
- /lib/cmake
|
- /lib/cmake
|
||||||
@ -68,8 +75,8 @@ modules:
|
|||||||
buildsystem: autotools
|
buildsystem: autotools
|
||||||
sources:
|
sources:
|
||||||
- type: archive
|
- type: archive
|
||||||
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.2.tar.xz
|
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.3.tar.xz
|
||||||
sha256: c8bee4790d9058bacc4b6246456c58021db58a87ddda1a9d0139bf5f18f1f240
|
sha256: f8dd7566adb74147fab9964680b6bbadee87cf406a7fcff51718a5e6949b841c
|
||||||
x-checker-data:
|
x-checker-data:
|
||||||
type: anitya
|
type: anitya
|
||||||
project-id: 14957
|
project-id: 14957
|
||||||
@ -91,8 +98,8 @@ modules:
|
|||||||
sources:
|
sources:
|
||||||
- type: archive
|
- type: archive
|
||||||
dest-filename: gamemode.tar.gz
|
dest-filename: gamemode.tar.gz
|
||||||
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.1
|
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.2
|
||||||
sha256: 969cf85b5ca3944f3e315cd73a0ee9bea4f9c968cd7d485e9f4745bc1e679c4e
|
sha256: 2886d4ce543c78bd2a364316d5e7fd59ef06b71de63f896b37c6d3dc97658f60
|
||||||
x-checker-data:
|
x-checker-data:
|
||||||
type: json
|
type: json
|
||||||
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest
|
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
diff --git a/src/wl_window.c b/src/wl_window.c
|
|
||||||
index 52d3b9eb..4ac4eb5d 100644
|
|
||||||
--- a/src/wl_window.c
|
|
||||||
+++ b/src/wl_window.c
|
|
||||||
@@ -2117,8 +2117,7 @@ void _glfwSetWindowTitleWayland(_GLFWwindow* window, const char* title)
|
|
||||||
void _glfwSetWindowIconWayland(_GLFWwindow* window,
|
|
||||||
int count, const GLFWimage* images)
|
|
||||||
{
|
|
||||||
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
|
||||||
- "Wayland: The platform does not support setting the window icon");
|
|
||||||
+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the window icon\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void _glfwGetWindowPosWayland(_GLFWwindow* window, int* xpos, int* ypos)
|
|
||||||
@@ -2361,8 +2360,7 @@ void _glfwRequestWindowAttentionWayland(_GLFWwindow* window)
|
|
||||||
|
|
||||||
void _glfwFocusWindowWayland(_GLFWwindow* window)
|
|
||||||
{
|
|
||||||
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
|
||||||
- "Wayland: The platform does not support setting the input focus");
|
|
||||||
+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the input focus\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void _glfwSetWindowMonitorWayland(_GLFWwindow* window,
|
|
@ -1,17 +0,0 @@
|
|||||||
diff --git a/src/init.c b/src/init.c
|
|
||||||
index 06dbb3f2..a7c6da86 100644
|
|
||||||
--- a/src/init.c
|
|
||||||
+++ b/src/init.c
|
|
||||||
@@ -449,6 +449,12 @@ GLFWAPI int glfwInit(void)
|
|
||||||
_glfw.initialized = GLFW_TRUE;
|
|
||||||
|
|
||||||
glfwDefaultWindowHints();
|
|
||||||
+
|
|
||||||
+ fprintf(stderr, "!!! Patched GLFW from https://github.com/Admicos/minecraft-wayland\n"
|
|
||||||
+ "!!! If any issues with the window, or some issues with rendering, occur, "
|
|
||||||
+ "first try with the built-in GLFW, and if that solves the issue, report there first.\n"
|
|
||||||
+ "!!! Use outside Minecraft is untested, and things might break.\n");
|
|
||||||
+
|
|
||||||
return GLFW_TRUE;
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
|||||||
diff --git a/src/platform.c b/src/platform.c
|
|
||||||
index c5966ae7..3e7442f9 100644
|
|
||||||
--- a/src/platform.c
|
|
||||||
+++ b/src/platform.c
|
|
||||||
@@ -49,12 +49,12 @@ static const struct
|
|
||||||
#if defined(_GLFW_COCOA)
|
|
||||||
{ GLFW_PLATFORM_COCOA, _glfwConnectCocoa },
|
|
||||||
#endif
|
|
||||||
-#if defined(_GLFW_X11)
|
|
||||||
- { GLFW_PLATFORM_X11, _glfwConnectX11 },
|
|
||||||
-#endif
|
|
||||||
#if defined(_GLFW_WAYLAND)
|
|
||||||
{ GLFW_PLATFORM_WAYLAND, _glfwConnectWayland },
|
|
||||||
#endif
|
|
||||||
+#if defined(_GLFW_X11)
|
|
||||||
+ { GLFW_PLATFORM_X11, _glfwConnectX11 },
|
|
||||||
+#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
GLFWbool _glfwSelectPlatform(int desiredID, _GLFWplatform* platform)
|
|
@ -0,0 +1,59 @@
|
|||||||
|
From 9997ae55a47de469ea26f8437c30b51483abda5f Mon Sep 17 00:00:00 2001
|
||||||
|
From: Dan Klishch <danilklishch@gmail.com>
|
||||||
|
Date: Sat, 30 Sep 2023 23:38:05 -0400
|
||||||
|
Subject: Defer setting cursor position until the cursor is locked
|
||||||
|
|
||||||
|
---
|
||||||
|
src/wl_platform.h | 3 +++
|
||||||
|
src/wl_window.c | 14 ++++++++++++--
|
||||||
|
2 files changed, 15 insertions(+), 2 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/wl_platform.h b/src/wl_platform.h
|
||||||
|
index ca34f66e..cd1f227f 100644
|
||||||
|
--- a/src/wl_platform.h
|
||||||
|
+++ b/src/wl_platform.h
|
||||||
|
@@ -403,6 +403,9 @@ typedef struct _GLFWwindowWayland
|
||||||
|
int scaleSize;
|
||||||
|
int compositorPreferredScale;
|
||||||
|
|
||||||
|
+ double askedCursorPosX, askedCursorPosY;
|
||||||
|
+ GLFWbool didAskForSetCursorPos;
|
||||||
|
+
|
||||||
|
struct zwp_relative_pointer_v1* relativePointer;
|
||||||
|
struct zwp_locked_pointer_v1* lockedPointer;
|
||||||
|
struct zwp_confined_pointer_v1* confinedPointer;
|
||||||
|
diff --git a/src/wl_window.c b/src/wl_window.c
|
||||||
|
index 1de26558..0df16747 100644
|
||||||
|
--- a/src/wl_window.c
|
||||||
|
+++ b/src/wl_window.c
|
||||||
|
@@ -2586,8 +2586,9 @@ void _glfwGetCursorPosWayland(_GLFWwindow* window, double* xpos, double* ypos)
|
||||||
|
|
||||||
|
void _glfwSetCursorPosWayland(_GLFWwindow* window, double x, double y)
|
||||||
|
{
|
||||||
|
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
||||||
|
- "Wayland: The platform does not support setting the cursor position");
|
||||||
|
+ window->wl.didAskForSetCursorPos = true;
|
||||||
|
+ window->wl.askedCursorPosX = x;
|
||||||
|
+ window->wl.askedCursorPosY = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _glfwSetCursorModeWayland(_GLFWwindow* window, int mode)
|
||||||
|
@@ -2819,6 +2820,15 @@ static const struct zwp_relative_pointer_v1_listener relativePointerListener =
|
||||||
|
static void lockedPointerHandleLocked(void* userData,
|
||||||
|
struct zwp_locked_pointer_v1* lockedPointer)
|
||||||
|
{
|
||||||
|
+ _GLFWwindow* window = userData;
|
||||||
|
+
|
||||||
|
+ if (window->wl.didAskForSetCursorPos)
|
||||||
|
+ {
|
||||||
|
+ window->wl.didAskForSetCursorPos = false;
|
||||||
|
+ zwp_locked_pointer_v1_set_cursor_position_hint(window->wl.lockedPointer,
|
||||||
|
+ wl_fixed_from_double(window->wl.askedCursorPosX),
|
||||||
|
+ wl_fixed_from_double(window->wl.askedCursorPosY));
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lockedPointerHandleUnlocked(void* userData,
|
||||||
|
--
|
||||||
|
2.42.0
|
||||||
|
|
@ -1,40 +0,0 @@
|
|||||||
diff --git a/src/libdecor.c b/src/libdecor.c
|
|
||||||
index a9c1106..1aa38b3 100644
|
|
||||||
--- a/src/libdecor.c
|
|
||||||
+++ b/src/libdecor.c
|
|
||||||
@@ -1391,22 +1391,32 @@ calculate_priority(const struct libdecor_plugin_description *plugin_description)
|
|
||||||
static bool
|
|
||||||
check_symbol_conflicts(const struct libdecor_plugin_description *plugin_description)
|
|
||||||
{
|
|
||||||
+ bool ret = true;
|
|
||||||
char * const *symbol;
|
|
||||||
+ void* main_prog = dlopen(NULL, RTLD_LAZY);
|
|
||||||
+ if (!main_prog) {
|
|
||||||
+ fprintf(stderr, "Plugin \"%s\" couldn't check conflicting symbols: \"%s\".\n",
|
|
||||||
+ plugin_description->description, dlerror());
|
|
||||||
+ return false;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
|
|
||||||
symbol = plugin_description->conflicting_symbols;
|
|
||||||
while (*symbol) {
|
|
||||||
dlerror();
|
|
||||||
- dlsym (RTLD_DEFAULT, *symbol);
|
|
||||||
+ dlsym (main_prog, *symbol);
|
|
||||||
if (!dlerror()) {
|
|
||||||
fprintf(stderr, "Plugin \"%s\" uses conflicting symbol \"%s\".\n",
|
|
||||||
plugin_description->description, *symbol);
|
|
||||||
- return false;
|
|
||||||
+ ret = false;
|
|
||||||
+ break;
|
|
||||||
}
|
|
||||||
|
|
||||||
symbol++;
|
|
||||||
}
|
|
||||||
|
|
||||||
- return true;
|
|
||||||
+ dlclose(main_prog);
|
|
||||||
+ return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct plugin_loader *
|
|
@ -1 +1 @@
|
|||||||
Subproject commit f2b0c16a2a217a1822ce5a6538ba8f755ed1dd32
|
Subproject commit f5d368a31d6ef046eb2955c74ec6f54f32ed5c4e
|
@ -48,6 +48,7 @@
|
|||||||
#include "net/PasteUpload.h"
|
#include "net/PasteUpload.h"
|
||||||
#include "pathmatcher/MultiMatcher.h"
|
#include "pathmatcher/MultiMatcher.h"
|
||||||
#include "pathmatcher/SimplePrefixMatcher.h"
|
#include "pathmatcher/SimplePrefixMatcher.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
#include "tools/GenericProfiler.h"
|
#include "tools/GenericProfiler.h"
|
||||||
#include "ui/InstanceWindow.h"
|
#include "ui/InstanceWindow.h"
|
||||||
#include "ui/MainWindow.h"
|
#include "ui/MainWindow.h"
|
||||||
@ -159,6 +160,7 @@
|
|||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <QStyleHints>
|
||||||
#include "WindowsConsole.h"
|
#include "WindowsConsole.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -230,7 +232,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()));
|
setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()));
|
||||||
setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT);
|
setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT);
|
||||||
setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
|
setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
|
||||||
startTime = QDateTime::currentDateTime();
|
m_startTime = QDateTime::currentDateTime();
|
||||||
|
|
||||||
// Don't quit on hiding the last window
|
// Don't quit on hiding the last window
|
||||||
this->setQuitOnLastWindowClosed(false);
|
this->setQuitOnLastWindowClosed(false);
|
||||||
@ -1092,6 +1094,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
bool Application::createSetupWizard()
|
bool Application::createSetupWizard()
|
||||||
{
|
{
|
||||||
bool javaRequired = [&]() {
|
bool javaRequired = [&]() {
|
||||||
|
if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_settings->get("AutomaticJavaDownload").toBool()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool();
|
bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool();
|
||||||
if (ignoreJavaWizard) {
|
if (ignoreJavaWizard) {
|
||||||
return false;
|
return false;
|
||||||
@ -1104,10 +1109,7 @@ bool Application::createSetupWizard()
|
|||||||
}
|
}
|
||||||
QString currentJavaPath = settings()->get("JavaPath").toString();
|
QString currentJavaPath = settings()->get("JavaPath").toString();
|
||||||
QString actualPath = FS::ResolveExecutable(currentJavaPath);
|
QString actualPath = FS::ResolveExecutable(currentJavaPath);
|
||||||
if (actualPath.isNull()) {
|
return actualPath.isNull();
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}();
|
}();
|
||||||
bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() &&
|
bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() &&
|
||||||
!m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool();
|
!m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool();
|
||||||
@ -1122,8 +1124,16 @@ bool Application::createSetupWizard()
|
|||||||
// set default theme after going into theme wizard
|
// set default theme after going into theme wizard
|
||||||
if (!validIcons)
|
if (!validIcons)
|
||||||
settings()->set("IconTheme", QString("pe_colored"));
|
settings()->set("IconTheme", QString("pe_colored"));
|
||||||
if (!validWidgets)
|
if (!validWidgets) {
|
||||||
settings()->set("ApplicationTheme", QString("system"));
|
#if defined(Q_OS_WIN32) && QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||||
|
const QString style =
|
||||||
|
QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark ? QStringLiteral("dark") : QStringLiteral("bright");
|
||||||
|
#else
|
||||||
|
const QString style = QStringLiteral("system");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
settings()->set("ApplicationTheme", style);
|
||||||
|
}
|
||||||
|
|
||||||
m_themeManager->applyCurrentlySelectedTheme(true);
|
m_themeManager->applyCurrentlySelectedTheme(true);
|
||||||
|
|
||||||
@ -1190,6 +1200,9 @@ bool Application::event(QEvent* event)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (event->type() == QEvent::FileOpen) {
|
if (event->type() == QEvent::FileOpen) {
|
||||||
|
if (!m_mainWindow) {
|
||||||
|
showMainWindow(false);
|
||||||
|
}
|
||||||
auto ev = static_cast<QFileOpenEvent*>(event);
|
auto ev = static_cast<QFileOpenEvent*>(event);
|
||||||
m_mainWindow->processURLs({ ev->url() });
|
m_mainWindow->processURLs({ ev->url() });
|
||||||
}
|
}
|
||||||
@ -1348,6 +1361,9 @@ void Application::messageReceived(const QByteArray& message)
|
|||||||
qWarning() << "Received" << command << "message without a zip path/URL.";
|
qWarning() << "Received" << command << "message without a zip path/URL.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!m_mainWindow) {
|
||||||
|
showMainWindow(false);
|
||||||
|
}
|
||||||
m_mainWindow->processURLs({ normalizeImportUrl(url) });
|
m_mainWindow->processURLs({ normalizeImportUrl(url) });
|
||||||
} else if (command == "launch") {
|
} else if (command == "launch") {
|
||||||
QString id = received.args["id"];
|
QString id = received.args["id"];
|
||||||
@ -1426,6 +1442,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
|
|||||||
if (m_updateRunning) {
|
if (m_updateRunning) {
|
||||||
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
|
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
|
||||||
} else if (instance->canLaunch()) {
|
} else if (instance->canLaunch()) {
|
||||||
|
QMutexLocker locker(&m_instanceExtrasMutex);
|
||||||
auto& extras = m_instanceExtras[instance->id()];
|
auto& extras = m_instanceExtras[instance->id()];
|
||||||
auto window = extras.window;
|
auto window = extras.window;
|
||||||
if (window) {
|
if (window) {
|
||||||
@ -1450,7 +1467,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
|
|||||||
connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed);
|
connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed);
|
||||||
connect(controller.get(), &LaunchController::aborted, this, [this] { controllerFailed(tr("Aborted")); });
|
connect(controller.get(), &LaunchController::aborted, this, [this] { controllerFailed(tr("Aborted")); });
|
||||||
addRunningInstance();
|
addRunningInstance();
|
||||||
controller->start();
|
QMetaObject::invokeMethod(controller.get(), &Task::start, Qt::QueuedConnection);
|
||||||
return true;
|
return true;
|
||||||
} else if (instance->isRunning()) {
|
} else if (instance->isRunning()) {
|
||||||
showInstanceWindow(instance, "console");
|
showInstanceWindow(instance, "console");
|
||||||
@ -1468,9 +1485,11 @@ bool Application::kill(InstancePtr instance)
|
|||||||
qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running.";
|
qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
QMutexLocker locker(&m_instanceExtrasMutex);
|
||||||
auto& extras = m_instanceExtras[instance->id()];
|
auto& extras = m_instanceExtras[instance->id()];
|
||||||
// NOTE: copy of the shared pointer keeps it alive
|
// NOTE: copy of the shared pointer keeps it alive
|
||||||
auto controller = extras.controller;
|
auto controller = extras.controller;
|
||||||
|
locker.unlock();
|
||||||
if (controller) {
|
if (controller) {
|
||||||
return controller->abort();
|
return controller->abort();
|
||||||
}
|
}
|
||||||
@ -1524,12 +1543,14 @@ void Application::controllerSucceeded()
|
|||||||
if (!controller)
|
if (!controller)
|
||||||
return;
|
return;
|
||||||
auto id = controller->id();
|
auto id = controller->id();
|
||||||
|
|
||||||
|
QMutexLocker locker(&m_instanceExtrasMutex);
|
||||||
auto& extras = m_instanceExtras[id];
|
auto& extras = m_instanceExtras[id];
|
||||||
|
|
||||||
// on success, do...
|
// on success, do...
|
||||||
if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) {
|
if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) {
|
||||||
if (extras.window) {
|
if (extras.window) {
|
||||||
extras.window->close();
|
QMetaObject::invokeMethod(extras.window, &QWidget::close, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extras.controller.reset();
|
extras.controller.reset();
|
||||||
@ -1549,6 +1570,7 @@ void Application::controllerFailed(const QString& error)
|
|||||||
if (!controller)
|
if (!controller)
|
||||||
return;
|
return;
|
||||||
auto id = controller->id();
|
auto id = controller->id();
|
||||||
|
QMutexLocker locker(&m_instanceExtrasMutex);
|
||||||
auto& extras = m_instanceExtras[id];
|
auto& extras = m_instanceExtras[id];
|
||||||
|
|
||||||
// on failure, do... nothing
|
// on failure, do... nothing
|
||||||
@ -1606,6 +1628,7 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
|
|||||||
if (!instance)
|
if (!instance)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
auto id = instance->id();
|
auto id = instance->id();
|
||||||
|
QMutexLocker locker(&m_instanceExtrasMutex);
|
||||||
auto& extras = m_instanceExtras[id];
|
auto& extras = m_instanceExtras[id];
|
||||||
auto& window = extras.window;
|
auto& window = extras.window;
|
||||||
|
|
||||||
@ -1643,6 +1666,7 @@ void Application::on_windowClose()
|
|||||||
m_openWindows--;
|
m_openWindows--;
|
||||||
auto instWindow = qobject_cast<InstanceWindow*>(QObject::sender());
|
auto instWindow = qobject_cast<InstanceWindow*>(QObject::sender());
|
||||||
if (instWindow) {
|
if (instWindow) {
|
||||||
|
QMutexLocker locker(&m_instanceExtrasMutex);
|
||||||
auto& extras = m_instanceExtras[instWindow->instanceId()];
|
auto& extras = m_instanceExtras[instWindow->instanceId()];
|
||||||
extras.window = nullptr;
|
extras.window = nullptr;
|
||||||
if (extras.controller) {
|
if (extras.controller) {
|
||||||
@ -1890,7 +1914,7 @@ bool Application::handleDataMigration(const QString& currentData,
|
|||||||
matcher->add(std::make_shared<SimplePrefixMatcher>("themes/"));
|
matcher->add(std::make_shared<SimplePrefixMatcher>("themes/"));
|
||||||
|
|
||||||
ProgressDialog diag;
|
ProgressDialog diag;
|
||||||
DataMigrationTask task(nullptr, oldData, currentData, matcher);
|
DataMigrationTask task(oldData, currentData, matcher);
|
||||||
if (diag.execWithTask(&task)) {
|
if (diag.execWithTask(&task)) {
|
||||||
qDebug() << "<> Migration succeeded";
|
qDebug() << "<> Migration succeeded";
|
||||||
setDoNotMigrate();
|
setDoNotMigrate();
|
||||||
@ -1929,3 +1953,31 @@ const QString Application::javaPath()
|
|||||||
{
|
{
|
||||||
return m_settings->get("JavaDir").toString();
|
return m_settings->get("JavaDir").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::addQSavePath(QString path)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_qsaveResourcesMutex);
|
||||||
|
m_qsaveResources[path] = m_qsaveResources.value(path, 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::removeQSavePath(QString path)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_qsaveResourcesMutex);
|
||||||
|
auto count = m_qsaveResources.value(path, 0) - 1;
|
||||||
|
if (count <= 0) {
|
||||||
|
m_qsaveResources.remove(path);
|
||||||
|
} else {
|
||||||
|
m_qsaveResources[path] = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::checkQSavePath(QString path)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_qsaveResourcesMutex);
|
||||||
|
for (auto partialPath : m_qsaveResources.keys()) {
|
||||||
|
if (path.startsWith(partialPath) && m_qsaveResources.value(partialPath, 0) > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
@ -42,6 +42,7 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFlag>
|
#include <QFlag>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
#include <QMutex>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@ -111,7 +112,7 @@ class Application : public QApplication {
|
|||||||
|
|
||||||
std::shared_ptr<SettingsObject> settings() const { return m_settings; }
|
std::shared_ptr<SettingsObject> settings() const { return m_settings; }
|
||||||
|
|
||||||
qint64 timeSinceStart() const { return startTime.msecsTo(QDateTime::currentDateTime()); }
|
qint64 timeSinceStart() const { return m_startTime.msecsTo(QDateTime::currentDateTime()); }
|
||||||
|
|
||||||
QIcon getThemedIcon(const QString& name);
|
QIcon getThemedIcon(const QString& name);
|
||||||
|
|
||||||
@ -235,7 +236,7 @@ class Application : public QApplication {
|
|||||||
bool shouldExitNow() const;
|
bool shouldExitNow() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QDateTime startTime;
|
QDateTime m_startTime;
|
||||||
|
|
||||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
||||||
|
|
||||||
@ -278,6 +279,7 @@ class Application : public QApplication {
|
|||||||
shared_qobject_ptr<LaunchController> controller;
|
shared_qobject_ptr<LaunchController> controller;
|
||||||
};
|
};
|
||||||
std::map<QString, InstanceXtras> m_instanceExtras;
|
std::map<QString, InstanceXtras> m_instanceExtras;
|
||||||
|
mutable QMutex m_instanceExtrasMutex;
|
||||||
|
|
||||||
// main state variables
|
// main state variables
|
||||||
size_t m_openWindows = 0;
|
size_t m_openWindows = 0;
|
||||||
@ -303,4 +305,13 @@ class Application : public QApplication {
|
|||||||
QList<QUrl> m_urlsToImport;
|
QList<QUrl> m_urlsToImport;
|
||||||
QString m_instanceIdToShowWindowOf;
|
QString m_instanceIdToShowWindowOf;
|
||||||
std::unique_ptr<QFile> logFile;
|
std::unique_ptr<QFile> logFile;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void addQSavePath(QString);
|
||||||
|
void removeQSavePath(QString);
|
||||||
|
bool checkQSavePath(QString);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QHash<QString, int> m_qsaveResources;
|
||||||
|
mutable QMutex m_qsaveResourcesMutex;
|
||||||
};
|
};
|
||||||
|
@ -30,6 +30,7 @@ set(CORE_SOURCES
|
|||||||
StringUtils.cpp
|
StringUtils.cpp
|
||||||
QVariantUtils.h
|
QVariantUtils.h
|
||||||
RuntimeContext.h
|
RuntimeContext.h
|
||||||
|
PSaveFile.h
|
||||||
|
|
||||||
# Basic instance manipulation tasks (derived from InstanceTask)
|
# Basic instance manipulation tasks (derived from InstanceTask)
|
||||||
InstanceCreationTask.h
|
InstanceCreationTask.h
|
||||||
@ -237,6 +238,8 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/auth/AuthFlow.cpp
|
minecraft/auth/AuthFlow.cpp
|
||||||
minecraft/auth/AuthFlow.h
|
minecraft/auth/AuthFlow.h
|
||||||
|
|
||||||
|
minecraft/auth/steps/AuthlibInjectorMetadataStep.cpp
|
||||||
|
minecraft/auth/steps/AuthlibInjectorMetadataStep.h
|
||||||
minecraft/auth/steps/EntitlementsStep.cpp
|
minecraft/auth/steps/EntitlementsStep.cpp
|
||||||
minecraft/auth/steps/EntitlementsStep.h
|
minecraft/auth/steps/EntitlementsStep.h
|
||||||
minecraft/auth/steps/GetSkinStep.cpp
|
minecraft/auth/steps/GetSkinStep.cpp
|
||||||
@ -384,6 +387,10 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/AssetsUtils.cpp
|
minecraft/AssetsUtils.cpp
|
||||||
|
|
||||||
# Minecraft skins
|
# Minecraft skins
|
||||||
|
minecraft/skins/AuthlibInjectorTextureDelete.cpp
|
||||||
|
minecraft/skins/AuthlibInjectorTextureDelete.h
|
||||||
|
minecraft/skins/AuthlibInjectorTextureUpload.cpp
|
||||||
|
minecraft/skins/AuthlibInjectorTextureUpload.h
|
||||||
minecraft/skins/CapeChange.cpp
|
minecraft/skins/CapeChange.cpp
|
||||||
minecraft/skins/CapeChange.h
|
minecraft/skins/CapeChange.h
|
||||||
minecraft/skins/SkinUpload.cpp
|
minecraft/skins/SkinUpload.cpp
|
||||||
@ -1059,8 +1066,6 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/dialogs/CopyInstanceDialog.h
|
ui/dialogs/CopyInstanceDialog.h
|
||||||
ui/dialogs/CustomMessageBox.cpp
|
ui/dialogs/CustomMessageBox.cpp
|
||||||
ui/dialogs/CustomMessageBox.h
|
ui/dialogs/CustomMessageBox.h
|
||||||
ui/dialogs/EditAccountDialog.cpp
|
|
||||||
ui/dialogs/EditAccountDialog.h
|
|
||||||
ui/dialogs/ExportInstanceDialog.cpp
|
ui/dialogs/ExportInstanceDialog.cpp
|
||||||
ui/dialogs/ExportInstanceDialog.h
|
ui/dialogs/ExportInstanceDialog.h
|
||||||
ui/dialogs/ExportPackDialog.cpp
|
ui/dialogs/ExportPackDialog.cpp
|
||||||
@ -1250,7 +1255,6 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/dialogs/OfflineLoginDialog.ui
|
ui/dialogs/OfflineLoginDialog.ui
|
||||||
ui/dialogs/AuthlibInjectorLoginDialog.ui
|
ui/dialogs/AuthlibInjectorLoginDialog.ui
|
||||||
ui/dialogs/AboutDialog.ui
|
ui/dialogs/AboutDialog.ui
|
||||||
ui/dialogs/EditAccountDialog.ui
|
|
||||||
ui/dialogs/ReviewMessageBox.ui
|
ui/dialogs/ReviewMessageBox.ui
|
||||||
ui/dialogs/ScrollMessageBox.ui
|
ui/dialogs/ScrollMessageBox.ui
|
||||||
ui/dialogs/BlockedModsDialog.ui
|
ui/dialogs/BlockedModsDialog.ui
|
||||||
|
@ -12,11 +12,8 @@
|
|||||||
|
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
||||||
DataMigrationTask::DataMigrationTask(QObject* parent,
|
DataMigrationTask::DataMigrationTask(const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathMatcher)
|
||||||
const QString& sourcePath,
|
: Task(), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
|
||||||
const QString& targetPath,
|
|
||||||
const IPathMatcher::Ptr pathMatcher)
|
|
||||||
: Task(parent), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
|
|
||||||
{
|
{
|
||||||
m_copy.matcher(m_pathMatcher.get()).whitelist(true);
|
m_copy.matcher(m_pathMatcher.get()).whitelist(true);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
class DataMigrationTask : public Task {
|
class DataMigrationTask : public Task {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
|
explicit DataMigrationTask(const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
|
||||||
~DataMigrationTask() override = default;
|
~DataMigrationTask() override = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -45,7 +45,6 @@
|
|||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QSaveFile>
|
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QStorageInfo>
|
#include <QStorageInfo>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
@ -54,6 +53,7 @@
|
|||||||
#include <system_error>
|
#include <system_error>
|
||||||
|
|
||||||
#include "DesktopServices.h"
|
#include "DesktopServices.h"
|
||||||
|
#include "PSaveFile.h"
|
||||||
#include "StringUtils.h"
|
#include "StringUtils.h"
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
@ -191,8 +191,8 @@ void ensureExists(const QDir& dir)
|
|||||||
void write(const QString& filename, const QByteArray& data)
|
void write(const QString& filename, const QByteArray& data)
|
||||||
{
|
{
|
||||||
ensureExists(QFileInfo(filename).dir());
|
ensureExists(QFileInfo(filename).dir());
|
||||||
QSaveFile file(filename);
|
PSaveFile file(filename);
|
||||||
if (!file.open(QSaveFile::WriteOnly)) {
|
if (!file.open(PSaveFile::WriteOnly)) {
|
||||||
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
||||||
}
|
}
|
||||||
if (data.size() != file.write(data)) {
|
if (data.size() != file.write(data)) {
|
||||||
@ -213,8 +213,8 @@ void appendSafe(const QString& filename, const QByteArray& data)
|
|||||||
buffer = QByteArray();
|
buffer = QByteArray();
|
||||||
}
|
}
|
||||||
buffer.append(data);
|
buffer.append(data);
|
||||||
QSaveFile file(filename);
|
PSaveFile file(filename);
|
||||||
if (!file.open(QSaveFile::WriteOnly)) {
|
if (!file.open(PSaveFile::WriteOnly)) {
|
||||||
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
||||||
}
|
}
|
||||||
if (buffer.size() != file.write(buffer)) {
|
if (buffer.size() != file.write(buffer)) {
|
||||||
@ -971,8 +971,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
|
|||||||
if (!args.empty())
|
if (!args.empty())
|
||||||
argstring = " \"" + args.join("\" \"") + "\"";
|
argstring = " \"" + args.join("\" \"") + "\"";
|
||||||
|
|
||||||
stream << "#!/bin/bash"
|
stream << "#!/bin/bash" << "\n";
|
||||||
<< "\n";
|
|
||||||
stream << "\"" << target << "\" " << argstring << "\n";
|
stream << "\"" << target << "\" " << argstring << "\n";
|
||||||
|
|
||||||
stream.flush();
|
stream.flush();
|
||||||
@ -1016,12 +1015,9 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
|
|||||||
if (!args.empty())
|
if (!args.empty())
|
||||||
argstring = " '" + args.join("' '") + "'";
|
argstring = " '" + args.join("' '") + "'";
|
||||||
|
|
||||||
stream << "[Desktop Entry]"
|
stream << "[Desktop Entry]" << "\n";
|
||||||
<< "\n";
|
stream << "Type=Application" << "\n";
|
||||||
stream << "Type=Application"
|
stream << "Categories=Game;ActionGame;AdventureGame;Simulation" << "\n";
|
||||||
<< "\n";
|
|
||||||
stream << "Categories=Game;ActionGame;AdventureGame;Simulation"
|
|
||||||
<< "\n";
|
|
||||||
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
|
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
|
||||||
stream << "Name=" << name.toLocal8Bit() << "\n";
|
stream << "Name=" << name.toLocal8Bit() << "\n";
|
||||||
if (!icon.isEmpty()) {
|
if (!icon.isEmpty()) {
|
||||||
|
@ -72,7 +72,8 @@ auto GetAuthlibInjectorApiLocation::Sink::finalize(QNetworkReply& reply) -> Task
|
|||||||
qDebug() << "X-Authlib-Injector-API-Location header not found!";
|
qDebug() << "X-Authlib-Injector-API-Location header not found!";
|
||||||
}
|
}
|
||||||
|
|
||||||
m_outer.m_account.reset(MinecraftAccount::createFromUsernameAuthlibInjector(m_outer.m_username, url.toString()));
|
const auto& encodedUrl = url.toEncoded(QUrl::FullyEncoded);
|
||||||
|
m_outer.m_account.reset(MinecraftAccount::createFromUsernameAuthlibInjector(m_outer.m_username, encodedUrl));
|
||||||
return Task::State::Succeeded;
|
return Task::State::Succeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +61,6 @@ void InstanceCreationTask::executeTask()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!m_abort)
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
@ -69,9 +69,11 @@ bool InstanceImportTask::abort()
|
|||||||
if (!canAbort())
|
if (!canAbort())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (task)
|
bool wasAborted = false;
|
||||||
task->abort();
|
if (m_task)
|
||||||
return Task::abort();
|
wasAborted = m_task->abort();
|
||||||
|
Task::abort();
|
||||||
|
return wasAborted;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::executeTask()
|
void InstanceImportTask::executeTask()
|
||||||
@ -104,7 +106,7 @@ void InstanceImportTask::downloadFromUrl()
|
|||||||
connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
|
connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
|
||||||
connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed);
|
connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed);
|
||||||
connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted);
|
connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted);
|
||||||
task.reset(filesNetJob);
|
m_task.reset(filesNetJob);
|
||||||
filesNetJob->start();
|
filesNetJob->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,7 +195,7 @@ void InstanceImportTask::processZipPack()
|
|||||||
stepProgress(*progressStep);
|
stepProgress(*progressStep);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished);
|
connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished, Qt::QueuedConnection);
|
||||||
connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
|
connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
|
||||||
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
|
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
|
||||||
progressStep->state = TaskStepState::Failed;
|
progressStep->state = TaskStepState::Failed;
|
||||||
@ -210,12 +212,13 @@ void InstanceImportTask::processZipPack()
|
|||||||
progressStep->status = status;
|
progressStep->status = status;
|
||||||
stepProgress(*progressStep);
|
stepProgress(*progressStep);
|
||||||
});
|
});
|
||||||
task.reset(zipTask);
|
m_task.reset(zipTask);
|
||||||
zipTask->start();
|
zipTask->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::extractFinished()
|
void InstanceImportTask::extractFinished()
|
||||||
{
|
{
|
||||||
|
setAbortable(false);
|
||||||
QDir extractDir(m_stagingPath);
|
QDir extractDir(m_stagingPath);
|
||||||
|
|
||||||
qDebug() << "Fixing permissions for extracted pack files...";
|
qDebug() << "Fixing permissions for extracted pack files...";
|
||||||
@ -289,8 +292,11 @@ void InstanceImportTask::processFlame()
|
|||||||
inst_creation_task->setGroup(m_instGroup);
|
inst_creation_task->setGroup(m_instGroup);
|
||||||
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
||||||
|
|
||||||
connect(inst_creation_task.get(), &Task::succeeded, this, [this, inst_creation_task] {
|
auto weak = inst_creation_task.toWeakRef();
|
||||||
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
|
connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
|
||||||
|
if (auto sp = weak.lock()) {
|
||||||
|
setOverride(sp->shouldOverride(), sp->originalInstanceID());
|
||||||
|
}
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
});
|
});
|
||||||
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
|
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||||
@ -299,11 +305,12 @@ void InstanceImportTask::processFlame()
|
|||||||
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
||||||
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
||||||
|
|
||||||
connect(this, &Task::aborted, inst_creation_task.get(), &InstanceCreationTask::abort);
|
|
||||||
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
|
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
|
||||||
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
|
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
|
||||||
|
|
||||||
inst_creation_task->start();
|
m_task.reset(inst_creation_task);
|
||||||
|
setAbortable(true);
|
||||||
|
m_task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::processTechnic()
|
void InstanceImportTask::processTechnic()
|
||||||
@ -350,7 +357,7 @@ void InstanceImportTask::processMultiMC()
|
|||||||
|
|
||||||
void InstanceImportTask::processModrinth()
|
void InstanceImportTask::processModrinth()
|
||||||
{
|
{
|
||||||
ModrinthCreationTask* inst_creation_task = nullptr;
|
shared_qobject_ptr<ModrinthCreationTask> inst_creation_task = nullptr;
|
||||||
if (!m_extra_info.isEmpty()) {
|
if (!m_extra_info.isEmpty()) {
|
||||||
auto pack_id_it = m_extra_info.constFind("pack_id");
|
auto pack_id_it = m_extra_info.constFind("pack_id");
|
||||||
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
|
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
|
||||||
@ -367,7 +374,7 @@ void InstanceImportTask::processModrinth()
|
|||||||
original_instance_id = original_instance_id_it.value();
|
original_instance_id = original_instance_id_it.value();
|
||||||
|
|
||||||
inst_creation_task =
|
inst_creation_task =
|
||||||
new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
|
makeShared<ModrinthCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
|
||||||
} else {
|
} else {
|
||||||
QString pack_id;
|
QString pack_id;
|
||||||
if (!m_sourceUrl.isEmpty()) {
|
if (!m_sourceUrl.isEmpty()) {
|
||||||
@ -376,7 +383,7 @@ void InstanceImportTask::processModrinth()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Find a way to get the ID in directly imported ZIPs
|
// FIXME: Find a way to get the ID in directly imported ZIPs
|
||||||
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id);
|
inst_creation_task = makeShared<ModrinthCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
inst_creation_task->setName(*this);
|
inst_creation_task->setName(*this);
|
||||||
@ -384,20 +391,23 @@ void InstanceImportTask::processModrinth()
|
|||||||
inst_creation_task->setGroup(m_instGroup);
|
inst_creation_task->setGroup(m_instGroup);
|
||||||
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
||||||
|
|
||||||
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
|
auto weak = inst_creation_task.toWeakRef();
|
||||||
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
|
connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
|
||||||
|
if (auto sp = weak.lock()) {
|
||||||
|
setOverride(sp->shouldOverride(), sp->originalInstanceID());
|
||||||
|
}
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
});
|
});
|
||||||
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||||
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
|
connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress);
|
||||||
connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
|
connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
|
||||||
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
|
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
||||||
connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails);
|
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
||||||
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
|
|
||||||
|
|
||||||
connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort);
|
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
|
||||||
connect(inst_creation_task, &Task::aborted, this, &Task::abort);
|
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
|
||||||
connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable);
|
|
||||||
|
|
||||||
inst_creation_task->start();
|
m_task.reset(inst_creation_task);
|
||||||
|
setAbortable(true);
|
||||||
|
m_task->start();
|
||||||
}
|
}
|
||||||
|
@ -40,16 +40,13 @@
|
|||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include "InstanceTask.h"
|
#include "InstanceTask.h"
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
class QuaZip;
|
class QuaZip;
|
||||||
|
|
||||||
class InstanceImportTask : public InstanceTask {
|
class InstanceImportTask : public InstanceTask {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
|
explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
|
||||||
|
virtual ~InstanceImportTask() = default;
|
||||||
bool abort() override;
|
bool abort() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -70,7 +67,7 @@ class InstanceImportTask : public InstanceTask {
|
|||||||
private: /* data */
|
private: /* data */
|
||||||
QUrl m_sourceUrl;
|
QUrl m_sourceUrl;
|
||||||
QString m_archivePath;
|
QString m_archivePath;
|
||||||
Task::Ptr task;
|
Task::Ptr m_task;
|
||||||
enum class ModpackType {
|
enum class ModpackType {
|
||||||
Unknown,
|
Unknown,
|
||||||
MultiMC,
|
MultiMC,
|
||||||
|
@ -116,7 +116,7 @@ void JavaCommon::TestCheck::run()
|
|||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0, this));
|
checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0));
|
||||||
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
|
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
|
||||||
checker->start();
|
checker->start();
|
||||||
}
|
}
|
||||||
@ -128,7 +128,7 @@ void JavaCommon::TestCheck::checkFinished(const JavaChecker::Result& result)
|
|||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0, this));
|
checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0));
|
||||||
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
|
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
|
||||||
checker->start();
|
checker->start();
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@
|
|||||||
#include "launch/steps/TextPrint.h"
|
#include "launch/steps/TextPrint.h"
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
LaunchController::LaunchController(QObject* parent) : Task(parent) {}
|
LaunchController::LaunchController() : Task() {}
|
||||||
|
|
||||||
void LaunchController::executeTask()
|
void LaunchController::executeTask()
|
||||||
{
|
{
|
||||||
|
@ -49,7 +49,7 @@ class LaunchController : public Task {
|
|||||||
public:
|
public:
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
|
|
||||||
LaunchController(QObject* parent = nullptr);
|
LaunchController();
|
||||||
virtual ~LaunchController() = default;
|
virtual ~LaunchController() = default;
|
||||||
|
|
||||||
void setInstance(InstancePtr instance) { m_instance = instance; }
|
void setInstance(InstancePtr instance) { m_instance = instance; }
|
||||||
|
@ -39,8 +39,16 @@ if [ "x$DEPS_LIST" = "x" ]; then
|
|||||||
# Just to be sure...
|
# Just to be sure...
|
||||||
chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}"
|
chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}"
|
||||||
|
|
||||||
|
ARGS=("${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}")
|
||||||
|
|
||||||
|
if [ -f portable.txt ]; then
|
||||||
|
ARGS+=("-d" "${LAUNCHER_DIR}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
ARGS+=("$@")
|
||||||
|
|
||||||
# Run the launcher
|
# Run the launcher
|
||||||
exec -a "${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"
|
exec -a "${ARGS[@]}"
|
||||||
|
|
||||||
# Run the launcher in valgrind
|
# Run the launcher in valgrind
|
||||||
# valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"
|
# valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"
|
||||||
|
@ -378,7 +378,7 @@ std::optional<QStringList> extractDir(QString fileCompressed, QString dir)
|
|||||||
if (fileInfo.size() == 22) {
|
if (fileInfo.size() == 22) {
|
||||||
return QStringList();
|
return QStringList();
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
|
qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
|
||||||
;
|
;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@ -395,7 +395,7 @@ std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QS
|
|||||||
if (fileInfo.size() == 22) {
|
if (fileInfo.size() == 22) {
|
||||||
return QStringList();
|
return QStringList();
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
|
qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
|
||||||
;
|
;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@ -412,7 +412,7 @@ bool extractFile(QString fileCompressed, QString file, QString target)
|
|||||||
if (fileInfo.size() == 22) {
|
if (fileInfo.size() == 22) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
|
qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return extractRelFile(&zip, file, target);
|
return extractRelFile(&zip, file, target);
|
||||||
@ -577,7 +577,7 @@ auto ExtractZipTask::extractZip() -> ZipResult
|
|||||||
|
|
||||||
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size()));
|
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size()));
|
||||||
auto original_name = relative_file_name;
|
auto original_name = relative_file_name;
|
||||||
setStatus("Unziping: " + relative_file_name);
|
setStatus("Unpacking: " + relative_file_name);
|
||||||
|
|
||||||
// Fix subdirs/files ending with a / getting transformed into absolute paths
|
// Fix subdirs/files ending with a / getting transformed into absolute paths
|
||||||
if (relative_file_name.startsWith('/'))
|
if (relative_file_name.startsWith('/'))
|
||||||
|
71
launcher/PSaveFile.h
Normal file
71
launcher/PSaveFile.h
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QSaveFile>
|
||||||
|
#include "Application.h"
|
||||||
|
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
|
|
||||||
|
/* PSaveFile
|
||||||
|
* A class that mimics QSaveFile for Windows.
|
||||||
|
*
|
||||||
|
* When reading resources, we need to avoid accessing temporary files
|
||||||
|
* generated by QSaveFile. If we start reading such a file, we may
|
||||||
|
* inadvertently keep it open while QSaveFile is trying to remove it,
|
||||||
|
* or we might detect the file just before it is removed, leading to
|
||||||
|
* race conditions and errors.
|
||||||
|
*
|
||||||
|
* Unfortunately, QSaveFile doesn't provide a way to retrieve the
|
||||||
|
* temporary file name or to set a specific template for the temporary
|
||||||
|
* file name it uses. By default, QSaveFile appends a `.XXXXXX` suffix
|
||||||
|
* to the original file name, where the `XXXXXX` part is dynamically
|
||||||
|
* generated to ensure uniqueness.
|
||||||
|
*
|
||||||
|
* This class acts like a lock by adding and removing the target file
|
||||||
|
* name into/from a global string set, helping to manage access to
|
||||||
|
* files during critical operations.
|
||||||
|
*
|
||||||
|
* Note: Please do not use the `setFileName` function directly, as it
|
||||||
|
* is not virtual and cannot be overridden.
|
||||||
|
*/
|
||||||
|
class PSaveFile : public QSaveFile {
|
||||||
|
public:
|
||||||
|
PSaveFile(const QString& name) : QSaveFile(name) { addPath(name); }
|
||||||
|
PSaveFile(const QString& name, QObject* parent) : QSaveFile(name, parent) { addPath(name); }
|
||||||
|
virtual ~PSaveFile()
|
||||||
|
{
|
||||||
|
if (auto app = APPLICATION_DYN) {
|
||||||
|
app->removeQSavePath(m_absoluteFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void addPath(const QString& path)
|
||||||
|
{
|
||||||
|
m_absoluteFilePath = QFileInfo(path).absoluteFilePath() + "."; // add dot for tmp files only
|
||||||
|
if (auto app = APPLICATION_DYN) {
|
||||||
|
app->addQSavePath(m_absoluteFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QString m_absoluteFilePath;
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
#define PSaveFile QSaveFile
|
||||||
|
#endif
|
@ -81,9 +81,9 @@ QString getSupportedJavaArchitecture()
|
|||||||
if (arch == "arm64")
|
if (arch == "arm64")
|
||||||
return "mac-os-arm64";
|
return "mac-os-arm64";
|
||||||
if (arch.contains("64"))
|
if (arch.contains("64"))
|
||||||
return "mac-os-64";
|
return "mac-os-x64";
|
||||||
if (arch.contains("86"))
|
if (arch.contains("86"))
|
||||||
return "mac-os-86";
|
return "mac-os-x86";
|
||||||
// Unknown, maybe something new, appending arch
|
// Unknown, maybe something new, appending arch
|
||||||
return "mac-os-" + arch;
|
return "mac-os-" + arch;
|
||||||
} else if (sys == "linux") {
|
} else if (sys == "linux") {
|
||||||
|
@ -307,6 +307,7 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw)
|
|||||||
if (!replacing) {
|
if (!replacing) {
|
||||||
roles.clear();
|
roles.clear();
|
||||||
filterModel->setSourceModel(replacing);
|
filterModel->setSourceModel(replacing);
|
||||||
|
endResetModel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,8 +44,8 @@
|
|||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "java/JavaUtils.h"
|
#include "java/JavaUtils.h"
|
||||||
|
|
||||||
JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id, QObject* parent)
|
JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id)
|
||||||
: Task(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id)
|
: Task(), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void JavaChecker::executeTask()
|
void JavaChecker::executeTask()
|
||||||
@ -171,7 +171,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
|
|||||||
auto os_arch = results["os.arch"];
|
auto os_arch = results["os.arch"];
|
||||||
auto java_version = results["java.version"];
|
auto java_version = results["java.version"];
|
||||||
auto java_vendor = results["java.vendor"];
|
auto java_vendor = results["java.vendor"];
|
||||||
bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64";
|
bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64" || os_arch == "riscv64";
|
||||||
|
|
||||||
result.validity = Result::Validity::Valid;
|
result.validity = Result::Validity::Valid;
|
||||||
result.is_64bit = is_64;
|
result.is_64bit = is_64;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "JavaVersion.h"
|
#include "JavaVersion.h"
|
||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
@ -26,7 +25,7 @@ class JavaChecker : public Task {
|
|||||||
enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
|
enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0, QObject* parent = 0);
|
explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void checkFinished(const Result& result);
|
void checkFinished(const Result& result);
|
||||||
|
@ -163,7 +163,7 @@ void JavaListLoadTask::executeTask()
|
|||||||
JavaUtils ju;
|
JavaUtils ju;
|
||||||
QList<QString> candidate_paths = m_only_managed_versions ? getPrismJavaBundle() : ju.FindJavaPaths();
|
QList<QString> candidate_paths = m_only_managed_versions ? getPrismJavaBundle() : ju.FindJavaPaths();
|
||||||
|
|
||||||
ConcurrentTask::Ptr job(new ConcurrentTask(this, "Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
|
ConcurrentTask::Ptr job(new ConcurrentTask("Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
|
||||||
m_job.reset(job);
|
m_job.reset(job);
|
||||||
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
|
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
|
||||||
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
|
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
|
||||||
@ -171,7 +171,7 @@ void JavaListLoadTask::executeTask()
|
|||||||
qDebug() << "Probing the following Java paths: ";
|
qDebug() << "Probing the following Java paths: ";
|
||||||
int id = 0;
|
int id = 0;
|
||||||
for (QString candidate : candidate_paths) {
|
for (QString candidate : candidate_paths) {
|
||||||
auto checker = new JavaChecker(candidate, "", 0, 0, 0, id, this);
|
auto checker = new JavaChecker(candidate, "", 0, 0, 0, id);
|
||||||
connect(checker, &JavaChecker::checkFinished, [this](const JavaChecker::Result& result) { m_results << result; });
|
connect(checker, &JavaChecker::checkFinished, [this](const JavaChecker::Result& result) { m_results << result; });
|
||||||
job->addTask(Task::Ptr(checker));
|
job->addTask(Task::Ptr(checker));
|
||||||
id++;
|
id++;
|
||||||
|
@ -102,6 +102,8 @@ QProcessEnvironment CleanEnviroment()
|
|||||||
QString newValue = stripVariableEntries(key, value, rawenv.value("LAUNCHER_" + key));
|
QString newValue = stripVariableEntries(key, value, rawenv.value("LAUNCHER_" + key));
|
||||||
|
|
||||||
qDebug() << "Env: stripped" << key << value << "to" << newValue;
|
qDebug() << "Env: stripped" << key << value << "to" << newValue;
|
||||||
|
|
||||||
|
value = newValue;
|
||||||
}
|
}
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
// Strip IBus
|
// Strip IBus
|
||||||
|
@ -86,11 +86,10 @@ void ManifestDownloadTask::downloadJava(const QJsonDocument& doc)
|
|||||||
if (type == "directory") {
|
if (type == "directory") {
|
||||||
FS::ensureFolderPathExists(file);
|
FS::ensureFolderPathExists(file);
|
||||||
} else if (type == "link") {
|
} else if (type == "link") {
|
||||||
// this is linux only !
|
// this is *nix only !
|
||||||
auto path = Json::ensureString(meta, "target");
|
auto path = Json::ensureString(meta, "target");
|
||||||
if (!path.isEmpty()) {
|
if (!path.isEmpty()) {
|
||||||
auto target = FS::PathCombine(file, "../" + path);
|
QFile::link(path, file);
|
||||||
QFile(target).link(file);
|
|
||||||
}
|
}
|
||||||
} else if (type == "file") {
|
} else if (type == "file") {
|
||||||
// TODO download compressed version if it exists ?
|
// TODO download compressed version if it exists ?
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
#include "LaunchStep.h"
|
#include "LaunchStep.h"
|
||||||
#include "LaunchTask.h"
|
#include "LaunchTask.h"
|
||||||
|
|
||||||
LaunchStep::LaunchStep(LaunchTask* parent) : Task(parent), m_parent(parent)
|
LaunchStep::LaunchStep(LaunchTask* parent) : Task(), m_parent(parent)
|
||||||
{
|
{
|
||||||
connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch);
|
connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch);
|
||||||
connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);
|
connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);
|
||||||
|
@ -254,20 +254,60 @@ void LaunchTask::emitFailed(QString reason)
|
|||||||
Task::emitFailed(reason);
|
Task::emitFailed(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LaunchTask::substituteVariables(QStringList& args) const
|
QString expandVariables(const QString& input, QProcessEnvironment dict)
|
||||||
{
|
{
|
||||||
auto env = m_instance->createEnvironment();
|
QString result = input;
|
||||||
|
|
||||||
for (auto key : env.keys()) {
|
enum { base, maybeBrace, variable, brace } state = base;
|
||||||
args.replaceInStrings("$" + key, env.value(key));
|
int startIdx = -1;
|
||||||
|
for (int i = 0; i < result.length();) {
|
||||||
|
QChar c = result.at(i++);
|
||||||
|
switch (state) {
|
||||||
|
case base:
|
||||||
|
if (c == '$')
|
||||||
|
state = maybeBrace;
|
||||||
|
break;
|
||||||
|
case maybeBrace:
|
||||||
|
if (c == '{') {
|
||||||
|
state = brace;
|
||||||
|
startIdx = i;
|
||||||
|
} else if (c.isLetterOrNumber() || c == '_') {
|
||||||
|
state = variable;
|
||||||
|
startIdx = i - 1;
|
||||||
|
} else {
|
||||||
|
state = base;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case brace:
|
||||||
|
if (c == '}') {
|
||||||
|
const auto res = dict.value(result.mid(startIdx, i - 1 - startIdx), "");
|
||||||
|
if (!res.isEmpty()) {
|
||||||
|
result.replace(startIdx - 2, i - startIdx + 2, res);
|
||||||
|
i = startIdx - 2 + res.length();
|
||||||
|
}
|
||||||
|
state = base;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case variable:
|
||||||
|
if (!c.isLetterOrNumber() && c != '_') {
|
||||||
|
const auto res = dict.value(result.mid(startIdx, i - startIdx - 1), "");
|
||||||
|
if (!res.isEmpty()) {
|
||||||
|
result.replace(startIdx - 1, i - startIdx, res);
|
||||||
|
i = startIdx - 1 + res.length();
|
||||||
|
}
|
||||||
|
state = base;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (state == variable) {
|
||||||
|
if (const auto res = dict.value(result.mid(startIdx), ""); !res.isEmpty())
|
||||||
|
result.replace(startIdx - 1, result.length() - startIdx + 1, res);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void LaunchTask::substituteVariables(QString& cmd) const
|
QString LaunchTask::substituteVariables(QString& cmd, bool isLaunch) const
|
||||||
{
|
{
|
||||||
auto env = m_instance->createEnvironment();
|
return expandVariables(cmd, isLaunch ? m_instance->createLaunchEnvironment() : m_instance->createEnvironment());
|
||||||
|
|
||||||
for (auto key : env.keys()) {
|
|
||||||
cmd.replace("$" + key, env.value(key));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -87,8 +87,7 @@ class LaunchTask : public Task {
|
|||||||
shared_qobject_ptr<LogModel> getLogModel();
|
shared_qobject_ptr<LogModel> getLogModel();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void substituteVariables(QStringList& args) const;
|
QString substituteVariables(QString& cmd, bool isLaunch = false) const;
|
||||||
void substituteVariables(QString& cmd) const;
|
|
||||||
QString censorPrivateInfo(QString in);
|
QString censorPrivateInfo(QString in);
|
||||||
|
|
||||||
protected: /* methods */
|
protected: /* methods */
|
||||||
|
@ -32,7 +32,7 @@ class LogModel : public QAbstractListModel {
|
|||||||
|
|
||||||
private /* types */:
|
private /* types */:
|
||||||
struct entry {
|
struct entry {
|
||||||
MessageLevel::Enum level;
|
MessageLevel::Enum level = MessageLevel::Enum::Unknown;
|
||||||
QString line;
|
QString line;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ void CheckJava::executeTask()
|
|||||||
// if timestamps are not the same, or something is missing, check!
|
// if timestamps are not the same, or something is missing, check!
|
||||||
if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 ||
|
if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 ||
|
||||||
storedRealArchitecture.size() == 0 || storedVendor.size() == 0) {
|
storedRealArchitecture.size() == 0 || storedVendor.size() == 0) {
|
||||||
m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0, this));
|
m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0));
|
||||||
emit logLine(QString("Checking Java version..."), MessageLevel::Launcher);
|
emit logLine(QString("Checking Java version..."), MessageLevel::Launcher);
|
||||||
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
|
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
|
||||||
m_JavaChecker->start();
|
m_JavaChecker->start();
|
||||||
|
@ -47,19 +47,15 @@ PostLaunchCommand::PostLaunchCommand(LaunchTask* parent) : LaunchStep(parent)
|
|||||||
|
|
||||||
void PostLaunchCommand::executeTask()
|
void PostLaunchCommand::executeTask()
|
||||||
{
|
{
|
||||||
// FIXME: where to put this?
|
auto cmd = m_parent->substituteVariables(m_command);
|
||||||
|
emit logLine(tr("Running Post-Launch command: %1").arg(cmd), MessageLevel::Launcher);
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
auto args = QProcess::splitCommand(m_command);
|
auto args = QProcess::splitCommand(cmd);
|
||||||
m_parent->substituteVariables(args);
|
|
||||||
|
|
||||||
emit logLine(tr("Running Post-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher);
|
|
||||||
const QString program = args.takeFirst();
|
const QString program = args.takeFirst();
|
||||||
m_process.start(program, args);
|
m_process.start(program, args);
|
||||||
#else
|
#else
|
||||||
m_parent->substituteVariables(m_command);
|
m_process.start(cmd);
|
||||||
|
|
||||||
emit logLine(tr("Running Post-Launch command: %1").arg(m_command), MessageLevel::Launcher);
|
|
||||||
m_process.start(m_command);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,19 +47,14 @@ PreLaunchCommand::PreLaunchCommand(LaunchTask* parent) : LaunchStep(parent)
|
|||||||
|
|
||||||
void PreLaunchCommand::executeTask()
|
void PreLaunchCommand::executeTask()
|
||||||
{
|
{
|
||||||
// FIXME: where to put this?
|
auto cmd = m_parent->substituteVariables(m_command);
|
||||||
|
emit logLine(tr("Running Pre-Launch command: %1").arg(cmd), MessageLevel::Launcher);
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
auto args = QProcess::splitCommand(m_command);
|
auto args = QProcess::splitCommand(cmd);
|
||||||
m_parent->substituteVariables(args);
|
|
||||||
|
|
||||||
emit logLine(tr("Running Pre-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher);
|
|
||||||
const QString program = args.takeFirst();
|
const QString program = args.takeFirst();
|
||||||
m_process.start(program, args);
|
m_process.start(program, args);
|
||||||
#else
|
#else
|
||||||
m_parent->substituteVariables(m_command);
|
m_process.start(cmd);
|
||||||
|
|
||||||
emit logLine(tr("Running Pre-Launch command: %1").arg(m_command), MessageLevel::Launcher);
|
|
||||||
m_process.start(m_command);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,8 +140,8 @@ Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mo
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto versionList = get(uid);
|
auto versionList = get(uid);
|
||||||
auto loadTask = makeShared<SequentialTask>(
|
auto loadTask =
|
||||||
this, tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version));
|
makeShared<SequentialTask>(tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version));
|
||||||
if (status() != BaseEntity::LoadStatus::Remote || force) {
|
if (status() != BaseEntity::LoadStatus::Remote || force) {
|
||||||
loadTask->addTask(this->loadTask(mode));
|
loadTask->addTask(this->loadTask(mode));
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,7 @@ VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList(
|
|||||||
|
|
||||||
Task::Ptr VersionList::getLoadTask()
|
Task::Ptr VersionList::getLoadTask()
|
||||||
{
|
{
|
||||||
auto loadTask =
|
auto loadTask = makeShared<SequentialTask>(tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
|
||||||
makeShared<SequentialTask>(this, tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
|
|
||||||
loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online));
|
loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online));
|
||||||
loadTask->addTask(this->loadTask(Net::Mode::Online));
|
loadTask->addTask(this->loadTask(Net::Mode::Online));
|
||||||
return loadTask;
|
return loadTask;
|
||||||
|
@ -222,10 +222,11 @@ bool Component::isMoveable()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Component::isVersionChangeable()
|
bool Component::isVersionChangeable(bool wait)
|
||||||
{
|
{
|
||||||
auto list = getVersionList();
|
auto list = getVersionList();
|
||||||
if (list) {
|
if (list) {
|
||||||
|
if (wait)
|
||||||
list->waitToLoad();
|
list->waitToLoad();
|
||||||
return list->count() != 0;
|
return list->count() != 0;
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ class Component : public QObject, public ProblemProvider {
|
|||||||
bool isRevertible();
|
bool isRevertible();
|
||||||
bool isRemovable();
|
bool isRemovable();
|
||||||
bool isCustom();
|
bool isCustom();
|
||||||
bool isVersionChangeable();
|
bool isVersionChangeable(bool wait = true);
|
||||||
bool isKnownModloader();
|
bool isKnownModloader();
|
||||||
QStringList knownConflictingComponents();
|
QStringList knownConflictingComponents();
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
* If the component list changes, start over.
|
* If the component list changes, start over.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent) : Task(parent)
|
ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list) : Task()
|
||||||
{
|
{
|
||||||
d.reset(new ComponentUpdateTaskData);
|
d.reset(new ComponentUpdateTaskData);
|
||||||
d->m_profile = list;
|
d->m_profile = list;
|
||||||
|
@ -14,7 +14,7 @@ class ComponentUpdateTask : public Task {
|
|||||||
enum class Mode { Launch, Resolution };
|
enum class Mode { Launch, Resolution };
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent = 0);
|
explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list);
|
||||||
virtual ~ComponentUpdateTask();
|
virtual ~ComponentUpdateTask();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -654,6 +654,7 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
|||||||
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
|
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
|
||||||
appendLib("libMangoHud_dlsym.so");
|
appendLib("libMangoHud_dlsym.so");
|
||||||
appendLib("libMangoHud_opengl.so");
|
appendLib("libMangoHud_opengl.so");
|
||||||
|
appendLib("libMangoHud_shim.so");
|
||||||
preloadList << mangoHudLibString;
|
preloadList << mangoHudLibString;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1131,17 +1132,10 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
|||||||
process->appendStep(step);
|
process->appendStep(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
// run pre-launch command if that's needed
|
|
||||||
if (getPreLaunchCommand().size()) {
|
|
||||||
auto step = makeShared<PreLaunchCommand>(pptr);
|
|
||||||
step->setWorkingDirectory(gameRoot());
|
|
||||||
process->appendStep(step);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load meta
|
// load meta
|
||||||
{
|
{
|
||||||
auto mode = session->status != AuthSession::PlayableOffline ? Net::Mode::Online : Net::Mode::Offline;
|
auto mode = session->status != AuthSession::PlayableOffline ? Net::Mode::Online : Net::Mode::Offline;
|
||||||
process->appendStep(makeShared<TaskStepWrapper>(pptr, makeShared<MinecraftLoadAndCheck>(this, mode, pptr)));
|
process->appendStep(makeShared<TaskStepWrapper>(pptr, makeShared<MinecraftLoadAndCheck>(this, mode)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check java
|
// check java
|
||||||
@ -1150,6 +1144,13 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
|||||||
process->appendStep(makeShared<CheckJava>(pptr));
|
process->appendStep(makeShared<CheckJava>(pptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// run pre-launch command if that's needed
|
||||||
|
if (getPreLaunchCommand().size()) {
|
||||||
|
auto step = makeShared<PreLaunchCommand>(pptr);
|
||||||
|
step->setWorkingDirectory(gameRoot());
|
||||||
|
process->appendStep(step);
|
||||||
|
}
|
||||||
|
|
||||||
// if we aren't in offline mode,.
|
// if we aren't in offline mode,.
|
||||||
if (session->status != AuthSession::PlayableOffline) {
|
if (session->status != AuthSession::PlayableOffline) {
|
||||||
if (!session->demo) {
|
if (!session->demo) {
|
||||||
|
@ -2,15 +2,16 @@
|
|||||||
#include "MinecraftInstance.h"
|
#include "MinecraftInstance.h"
|
||||||
#include "PackProfile.h"
|
#include "PackProfile.h"
|
||||||
|
|
||||||
MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode, QObject* parent)
|
MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode) : m_inst(inst), m_netmode(netmode) {}
|
||||||
: Task(parent), m_inst(inst), m_netmode(netmode)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void MinecraftLoadAndCheck::executeTask()
|
void MinecraftLoadAndCheck::executeTask()
|
||||||
{
|
{
|
||||||
// add offline metadata load task
|
// add offline metadata load task
|
||||||
auto components = m_inst->getPackProfile();
|
auto components = m_inst->getPackProfile();
|
||||||
components->reload(m_netmode);
|
if (auto result = components->reload(m_netmode); !result) {
|
||||||
|
emitFailed(result.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
m_task = components->getCurrentTask();
|
m_task = components->getCurrentTask();
|
||||||
|
|
||||||
if (!m_task) {
|
if (!m_task) {
|
||||||
|
@ -23,7 +23,7 @@ class MinecraftInstance;
|
|||||||
class MinecraftLoadAndCheck : public Task {
|
class MinecraftLoadAndCheck : public Task {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode, QObject* parent = nullptr);
|
explicit MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode);
|
||||||
virtual ~MinecraftLoadAndCheck() = default;
|
virtual ~MinecraftLoadAndCheck() = default;
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
|
|
||||||
|
@ -180,29 +180,32 @@ static bool savePackProfile(const QString& filename, const ComponentContainer& c
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read the given file into component containers
|
// Read the given file into component containers
|
||||||
static bool loadPackProfile(PackProfile* parent,
|
static PackProfile::Result loadPackProfile(PackProfile* parent,
|
||||||
const QString& filename,
|
const QString& filename,
|
||||||
const QString& componentJsonPattern,
|
const QString& componentJsonPattern,
|
||||||
ComponentContainer& container)
|
ComponentContainer& container)
|
||||||
{
|
{
|
||||||
QFile componentsFile(filename);
|
QFile componentsFile(filename);
|
||||||
if (!componentsFile.exists()) {
|
if (!componentsFile.exists()) {
|
||||||
qCWarning(instanceProfileC) << "Components file" << filename << "doesn't exist. This should never happen.";
|
auto message = QObject::tr("Components file %1 doesn't exist. This should never happen.").arg(filename);
|
||||||
return false;
|
qCWarning(instanceProfileC) << message;
|
||||||
|
return PackProfile::Result::Error(message);
|
||||||
}
|
}
|
||||||
if (!componentsFile.open(QFile::ReadOnly)) {
|
if (!componentsFile.open(QFile::ReadOnly)) {
|
||||||
qCCritical(instanceProfileC) << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString();
|
auto message = QObject::tr("Couldn't open %1 for reading: %2").arg(componentsFile.fileName(), componentsFile.errorString());
|
||||||
|
qCCritical(instanceProfileC) << message;
|
||||||
qCWarning(instanceProfileC) << "Ignoring overridden order";
|
qCWarning(instanceProfileC) << "Ignoring overridden order";
|
||||||
return false;
|
return PackProfile::Result::Error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// and it's valid JSON
|
// and it's valid JSON
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error);
|
QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error);
|
||||||
if (error.error != QJsonParseError::NoError) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString();
|
auto message = QObject::tr("Couldn't parse %1 as json: %2").arg(componentsFile.fileName(), error.errorString());
|
||||||
|
qCCritical(instanceProfileC) << message;
|
||||||
qCWarning(instanceProfileC) << "Ignoring overridden order";
|
qCWarning(instanceProfileC) << "Ignoring overridden order";
|
||||||
return false;
|
return PackProfile::Result::Error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// and then read it and process it if all above is true.
|
// and then read it and process it if all above is true.
|
||||||
@ -219,11 +222,13 @@ static bool loadPackProfile(PackProfile* parent,
|
|||||||
container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj));
|
container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj));
|
||||||
}
|
}
|
||||||
} catch ([[maybe_unused]] const JSONValidationError& err) {
|
} catch ([[maybe_unused]] const JSONValidationError& err) {
|
||||||
qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ": bad file format";
|
auto message = QObject::tr("Couldn't parse %1 : bad file format").arg(componentsFile.fileName());
|
||||||
|
qCCritical(instanceProfileC) << message;
|
||||||
|
qCWarning(instanceProfileC) << "error:" << err.what();
|
||||||
container.clear();
|
container.clear();
|
||||||
return false;
|
return PackProfile::Result::Error(message);
|
||||||
}
|
}
|
||||||
return true;
|
return PackProfile::Result::Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
// END: component file format
|
// END: component file format
|
||||||
@ -290,16 +295,16 @@ void PackProfile::save_internal()
|
|||||||
d->dirty = false;
|
d->dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PackProfile::load()
|
PackProfile::Result PackProfile::load()
|
||||||
{
|
{
|
||||||
auto filename = componentsFilePath();
|
auto filename = componentsFilePath();
|
||||||
|
|
||||||
// load the new component list and swap it with the current one...
|
// load the new component list and swap it with the current one...
|
||||||
ComponentContainer newComponents;
|
ComponentContainer newComponents;
|
||||||
if (!loadPackProfile(this, filename, patchesPattern(), newComponents)) {
|
if (auto result = loadPackProfile(this, filename, patchesPattern(), newComponents); !result) {
|
||||||
qCritical() << d->m_instance->name() << "|" << "Failed to load the component config";
|
qCritical() << d->m_instance->name() << "|" << "Failed to load the component config";
|
||||||
return false;
|
return result;
|
||||||
} else {
|
}
|
||||||
// FIXME: actually use fine-grained updates, not this...
|
// FIXME: actually use fine-grained updates, not this...
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
// disconnect all the old components
|
// disconnect all the old components
|
||||||
@ -319,15 +324,14 @@ bool PackProfile::load()
|
|||||||
}
|
}
|
||||||
endResetModel();
|
endResetModel();
|
||||||
d->loaded = true;
|
d->loaded = true;
|
||||||
return true;
|
return Result::Success();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PackProfile::reload(Net::Mode netmode)
|
PackProfile::Result PackProfile::reload(Net::Mode netmode)
|
||||||
{
|
{
|
||||||
// Do not reload when the update/resolve task is running. It is in control.
|
// Do not reload when the update/resolve task is running. It is in control.
|
||||||
if (d->m_updateTask) {
|
if (d->m_updateTask) {
|
||||||
return;
|
return Result::Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush any scheduled saves to not lose state
|
// flush any scheduled saves to not lose state
|
||||||
@ -336,9 +340,11 @@ void PackProfile::reload(Net::Mode netmode)
|
|||||||
// FIXME: differentiate when a reapply is required by propagating state from components
|
// FIXME: differentiate when a reapply is required by propagating state from components
|
||||||
invalidateLaunchProfile();
|
invalidateLaunchProfile();
|
||||||
|
|
||||||
if (load()) {
|
if (auto result = load(); !result) {
|
||||||
resolve(netmode);
|
return result;
|
||||||
}
|
}
|
||||||
|
resolve(netmode);
|
||||||
|
return Result::Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
Task::Ptr PackProfile::getCurrentTask()
|
Task::Ptr PackProfile::getCurrentTask()
|
||||||
|
@ -62,6 +62,19 @@ class PackProfile : public QAbstractListModel {
|
|||||||
public:
|
public:
|
||||||
enum Columns { NameColumn = 0, VersionColumn, NUM_COLUMNS };
|
enum Columns { NameColumn = 0, VersionColumn, NUM_COLUMNS };
|
||||||
|
|
||||||
|
struct Result {
|
||||||
|
bool success;
|
||||||
|
QString error;
|
||||||
|
|
||||||
|
// Implicit conversion to bool
|
||||||
|
operator bool() const { return success; }
|
||||||
|
|
||||||
|
// Factory methods for convenience
|
||||||
|
static Result Success() { return { true, "" }; }
|
||||||
|
|
||||||
|
static Result Error(const QString& errorMessage) { return { false, errorMessage }; }
|
||||||
|
};
|
||||||
|
|
||||||
explicit PackProfile(MinecraftInstance* instance);
|
explicit PackProfile(MinecraftInstance* instance);
|
||||||
virtual ~PackProfile();
|
virtual ~PackProfile();
|
||||||
|
|
||||||
@ -102,7 +115,7 @@ class PackProfile : public QAbstractListModel {
|
|||||||
bool revertToBase(int index);
|
bool revertToBase(int index);
|
||||||
|
|
||||||
/// reload the list, reload all components, resolve dependencies
|
/// reload the list, reload all components, resolve dependencies
|
||||||
void reload(Net::Mode netmode);
|
Result reload(Net::Mode netmode);
|
||||||
|
|
||||||
// reload all components, resolve dependencies
|
// reload all components, resolve dependencies
|
||||||
void resolve(Net::Mode netmode);
|
void resolve(Net::Mode netmode);
|
||||||
@ -169,7 +182,7 @@ class PackProfile : public QAbstractListModel {
|
|||||||
void disableInteraction(bool disable);
|
void disableInteraction(bool disable);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool load();
|
Result load();
|
||||||
bool installJarMods_internal(QStringList filepaths);
|
bool installJarMods_internal(QStringList filepaths);
|
||||||
bool installCustomJar_internal(QString filepath);
|
bool installCustomJar_internal(QString filepath);
|
||||||
bool installAgents_internal(QStringList filepaths);
|
bool installAgents_internal(QStringList filepaths);
|
||||||
|
@ -38,7 +38,6 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QSaveFile>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
@ -57,6 +56,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
#include "PSaveFile.h"
|
||||||
|
|
||||||
using std::nullopt;
|
using std::nullopt;
|
||||||
using std::optional;
|
using std::optional;
|
||||||
@ -183,7 +183,7 @@ bool putLevelDatDataToFS(const QFileInfo& file, QByteArray& data)
|
|||||||
if (fullFilePath.isNull()) {
|
if (fullFilePath.isNull()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QSaveFile f(fullFilePath);
|
PSaveFile f(fullFilePath);
|
||||||
if (!f.open(QIODevice::WriteOnly)) {
|
if (!f.open(QIODevice::WriteOnly)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -133,6 +133,8 @@ void profileToJSONV3(QJsonObject& parent, MinecraftProfile p, const char* tokenN
|
|||||||
out["skin"] = skinObj;
|
out["skin"] = skinObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out["canUploadSkins"] = p.canUploadSkins;
|
||||||
|
|
||||||
QJsonArray capesArray;
|
QJsonArray capesArray;
|
||||||
for (auto& cape : p.capes) {
|
for (auto& cape : p.capes) {
|
||||||
QJsonObject capeObj;
|
QJsonObject capeObj;
|
||||||
@ -195,6 +197,11 @@ MinecraftProfile profileFromJSONV3(const QJsonObject& parent, const char* tokenN
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out.canUploadSkins = true;
|
||||||
|
if (tokenObject.value("canUploadSkins").isBool()) {
|
||||||
|
out.canUploadSkins = tokenObject.value("canUploadskins").toBool();
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto capesV = tokenObject.value("capes");
|
auto capesV = tokenObject.value("capes");
|
||||||
if (!capesV.isArray()) {
|
if (!capesV.isArray()) {
|
||||||
@ -383,9 +390,9 @@ bool AccountData::usesCustomApiServers() const
|
|||||||
return type == AccountType::AuthlibInjector;
|
return type == AccountType::AuthlibInjector;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AccountData::supportsSkinManagement() const
|
bool AccountData::canUploadSkins() const
|
||||||
{
|
{
|
||||||
return type == AccountType::MSA;
|
return minecraftProfile.canUploadSkins;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AccountData::authServerUrl() const
|
QString AccountData::authServerUrl() const
|
||||||
|
@ -82,6 +82,7 @@ struct MinecraftEntitlement {
|
|||||||
struct MinecraftProfile {
|
struct MinecraftProfile {
|
||||||
QString id;
|
QString id;
|
||||||
QString name;
|
QString name;
|
||||||
|
bool canUploadSkins = true;
|
||||||
Skin skin;
|
Skin skin;
|
||||||
QString currentCape;
|
QString currentCape;
|
||||||
QMap<QString, Cape> capes;
|
QMap<QString, Cape> capes;
|
||||||
@ -96,7 +97,7 @@ struct AccountData {
|
|||||||
QJsonObject saveState() const;
|
QJsonObject saveState() const;
|
||||||
bool resumeStateFromV3(QJsonObject data);
|
bool resumeStateFromV3(QJsonObject data);
|
||||||
|
|
||||||
bool supportsSkinManagement() const;
|
bool canUploadSkins() const;
|
||||||
bool usesCustomApiServers() const;
|
bool usesCustomApiServers() const;
|
||||||
QString authServerUrl() const;
|
QString authServerUrl() const;
|
||||||
QString accountServerUrl() const;
|
QString accountServerUrl() const;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
#include "minecraft/auth/AccountData.h"
|
#include "minecraft/auth/AccountData.h"
|
||||||
|
#include "minecraft/auth/steps/AuthlibInjectorMetadataStep.h"
|
||||||
#include "minecraft/auth/steps/EntitlementsStep.h"
|
#include "minecraft/auth/steps/EntitlementsStep.h"
|
||||||
#include "minecraft/auth/steps/GetSkinStep.h"
|
#include "minecraft/auth/steps/GetSkinStep.h"
|
||||||
#include "minecraft/auth/steps/LauncherLoginStep.h"
|
#include "minecraft/auth/steps/LauncherLoginStep.h"
|
||||||
@ -21,7 +22,7 @@
|
|||||||
|
|
||||||
#include <Application.h>
|
#include <Application.h>
|
||||||
|
|
||||||
AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent, const std::optional<QString> password) : Task(parent), m_data(data)
|
AuthFlow::AuthFlow(AccountData* data, Action action, const std::optional<QString> password) : Task(), m_data(data)
|
||||||
{
|
{
|
||||||
if (data->type == AccountType::MSA) {
|
if (data->type == AccountType::MSA) {
|
||||||
if (action == Action::DeviceCode) {
|
if (action == Action::DeviceCode) {
|
||||||
@ -46,6 +47,7 @@ AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent, const std:
|
|||||||
} else if (data->type == AccountType::AuthlibInjector) {
|
} else if (data->type == AccountType::AuthlibInjector) {
|
||||||
m_steps.append(makeShared<YggdrasilStep>(m_data, password));
|
m_steps.append(makeShared<YggdrasilStep>(m_data, password));
|
||||||
m_steps.append(makeShared<YggdrasilMinecraftProfileStep>(m_data));
|
m_steps.append(makeShared<YggdrasilMinecraftProfileStep>(m_data));
|
||||||
|
m_steps.append(makeShared<AuthlibInjectorMetadataStep>(m_data));
|
||||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
m_steps.append(makeShared<GetSkinStep>(m_data));
|
||||||
}
|
}
|
||||||
changeState(AccountTaskState::STATE_CREATED);
|
changeState(AccountTaskState::STATE_CREATED);
|
||||||
|
@ -20,7 +20,6 @@ class AuthFlow : public Task {
|
|||||||
|
|
||||||
explicit AuthFlow(AccountData* data,
|
explicit AuthFlow(AccountData* data,
|
||||||
Action action = Action::Refresh,
|
Action action = Action::Refresh,
|
||||||
QObject* parent = 0,
|
|
||||||
std::optional<QString> password = std::nullopt);
|
std::optional<QString> password = std::nullopt);
|
||||||
virtual ~AuthFlow() = default;
|
virtual ~AuthFlow() = default;
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
|
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
data.internalId = QUuid::createUuid().toString(QUuid::Id128);
|
data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json)
|
MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json)
|
||||||
@ -138,7 +138,7 @@ shared_qobject_ptr<AuthFlow> MinecraftAccount::login(bool useDeviceCode, std::op
|
|||||||
{
|
{
|
||||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||||
|
|
||||||
m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login, this, password));
|
m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login, password));
|
||||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||||
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
||||||
@ -152,7 +152,7 @@ shared_qobject_ptr<AuthFlow> MinecraftAccount::refresh()
|
|||||||
return m_currentTask;
|
return m_currentTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_currentTask.reset(new AuthFlow(&data, AuthFlow::Action::Refresh, this));
|
m_currentTask.reset(new AuthFlow(&data, AuthFlow::Action::Refresh));
|
||||||
|
|
||||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||||
|
@ -119,7 +119,7 @@ class MinecraftAccount : public QObject, public Usable {
|
|||||||
|
|
||||||
bool usesCustomApiServers() const { return data.usesCustomApiServers(); }
|
bool usesCustomApiServers() const { return data.usesCustomApiServers(); }
|
||||||
|
|
||||||
bool supportsSkinManagement() const { return data.supportsSkinManagement(); }
|
bool canUploadSkins() const { return data.canUploadSkins(); }
|
||||||
|
|
||||||
QString accountDisplayString() const { return data.accountDisplayString(); }
|
QString accountDisplayString() const { return data.accountDisplayString(); }
|
||||||
|
|
||||||
|
@ -188,6 +188,9 @@ bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output)
|
|||||||
output.skin = skinOut;
|
output.skin = skinOut;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output.canUploadSkins = true;
|
||||||
|
|
||||||
auto capesArray = obj.value("capes").toArray();
|
auto capesArray = obj.value("capes").toArray();
|
||||||
|
|
||||||
QString currentCape;
|
QString currentCape;
|
||||||
@ -306,13 +309,16 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output)
|
|||||||
|
|
||||||
auto propsArray = obj.value("properties").toArray();
|
auto propsArray = obj.value("properties").toArray();
|
||||||
QByteArray texturePayload;
|
QByteArray texturePayload;
|
||||||
|
bool canUploadSkins = true;
|
||||||
for (auto p : propsArray) {
|
for (auto p : propsArray) {
|
||||||
auto pObj = p.toObject();
|
auto pObj = p.toObject();
|
||||||
auto name = pObj.value("name");
|
auto name = pObj.value("name");
|
||||||
if (!name.isString() || name.toString() != "textures") {
|
if (!name.isString()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto& nameString = name.toString();
|
||||||
|
if (nameString == "textures") {
|
||||||
auto value = pObj.value("value");
|
auto value = pObj.value("value");
|
||||||
if (value.isString()) {
|
if (value.isString()) {
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
@ -321,11 +327,20 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output)
|
|||||||
texturePayload = QByteArray::fromBase64(value.toString().toUtf8());
|
texturePayload = QByteArray::fromBase64(value.toString().toUtf8());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
} else if (nameString == "uploadableTextures") {
|
||||||
if (!texturePayload.isEmpty()) {
|
// https://github.com/yushijinhun/authlib-injector/wiki/Yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#uploadabletextures-%E5%8F%AF%E4%B8%8A%E4%BC%A0%E7%9A%84%E6%9D%90%E8%B4%A8%E7%B1%BB%E5%9E%8B
|
||||||
break;
|
const auto& value = pObj.value("value");
|
||||||
|
if (value.isString()) {
|
||||||
|
canUploadSkins = false;
|
||||||
|
for (const auto& textureType : value.toString().split(",")) {
|
||||||
|
if (textureType == "skin") {
|
||||||
|
canUploadSkins = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output.canUploadSkins = canUploadSkins;
|
||||||
|
|
||||||
if (texturePayload.isNull()) {
|
if (texturePayload.isNull()) {
|
||||||
qWarning() << "No texture payload data";
|
qWarning() << "No texture payload data";
|
||||||
@ -379,7 +394,6 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output)
|
|||||||
// we don't know the cape ID as it is not returned from the session server
|
// we don't know the cape ID as it is not returned from the session server
|
||||||
// so just fake it - changing capes is probably locked anyway :(
|
// so just fake it - changing capes is probably locked anyway :(
|
||||||
capeOut.alias = "cape";
|
capeOut.alias = "cape";
|
||||||
capeOut.id = "00000000-0000-0000-0000-000000000000";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
#include "Application.h"
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
|
||||||
|
|
||||||
AuthlibInjectorMetadataStep::AuthlibInjectorMetadataStep(AccountData* data) : AuthStep(data) {}
|
AuthlibInjectorMetadataStep::AuthlibInjectorMetadataStep(AccountData* data) : AuthStep(data) {}
|
||||||
|
|
||||||
@ -21,29 +19,32 @@ void AuthlibInjectorMetadataStep::perform()
|
|||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Account has no authlib-injector URL."));
|
emit finished(AccountTaskState::STATE_WORKING, tr("Account has no authlib-injector URL."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QNetworkRequest request = QNetworkRequest(m_data->customAuthlibInjectorUrl);
|
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
QUrl url{m_data->customAuthlibInjectorUrl};
|
||||||
connect(requestor, &AuthRequest::finished, this, &AuthlibInjectorMetadataStep::onRequestDone);
|
|
||||||
requestor->get(request);
|
m_response.reset(new QByteArray());
|
||||||
|
m_request = Net::Download::makeByteArray(url, m_response);
|
||||||
|
|
||||||
|
m_task.reset(new NetJob("AuthlibInjectorMetadataStep", APPLICATION->network()));
|
||||||
|
m_task->setAskRetry(false);
|
||||||
|
m_task->setAutoRetryLimit(0);
|
||||||
|
m_task->addNetAction(m_request);
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &AuthlibInjectorMetadataStep::onRequestDone);
|
||||||
|
|
||||||
|
m_task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AuthlibInjectorMetadataStep::rehydrate() {}
|
void AuthlibInjectorMetadataStep::onRequestDone()
|
||||||
|
|
||||||
void AuthlibInjectorMetadataStep::onRequestDone(QNetworkReply::NetworkError error,
|
|
||||||
QByteArray data,
|
|
||||||
QList<QNetworkReply::RawHeaderPair> headers)
|
|
||||||
{
|
{
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
if (m_request->error() == QNetworkReply::NoError && m_response->size() > 0) {
|
||||||
requestor->deleteLater();
|
|
||||||
|
|
||||||
if (error == QNetworkReply::NoError && data.size() > 0) {
|
|
||||||
QJsonParseError jsonError;
|
QJsonParseError jsonError;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
QJsonDocument doc = QJsonDocument::fromJson(*m_response, &jsonError);
|
||||||
if (jsonError.error == QJsonParseError::NoError) {
|
if (jsonError.error == QJsonParseError::NoError) {
|
||||||
m_data->authlibInjectorMetadata = data.toBase64();
|
m_data->authlibInjectorMetadata = m_response->toBase64();
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got authlib-injector metadata."));
|
emit finished(AccountTaskState::STATE_WORKING, tr("Got authlib-injector metadata."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Didn't get authlib-injector metadata, continuing anyway."));
|
emit finished(AccountTaskState::STATE_WORKING, tr("Couldn't get authlib-injector metadata, continuing anyway."));
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
|
#include "net/Download.h"
|
||||||
|
|
||||||
class AuthlibInjectorMetadataStep : public AuthStep {
|
class AuthlibInjectorMetadataStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -12,10 +13,14 @@ class AuthlibInjectorMetadataStep : public AuthStep {
|
|||||||
virtual ~AuthlibInjectorMetadataStep() noexcept;
|
virtual ~AuthlibInjectorMetadataStep() noexcept;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Download::Ptr m_request;
|
||||||
|
NetJob::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
@ -75,12 +75,12 @@ void MSADeviceCodeStep::perform()
|
|||||||
m_task->setAskRetry(false);
|
m_task->setAskRetry(false);
|
||||||
m_task->addNetAction(m_request);
|
m_task->addNetAction(m_request);
|
||||||
|
|
||||||
connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAutorizationFinished);
|
connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAuthorizationFinished);
|
||||||
|
|
||||||
m_task->start();
|
m_task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DeviceAutorizationResponse {
|
struct DeviceAuthorizationResponse {
|
||||||
QString device_code;
|
QString device_code;
|
||||||
QString user_code;
|
QString user_code;
|
||||||
QString verification_uri;
|
QString verification_uri;
|
||||||
@ -91,17 +91,17 @@ struct DeviceAutorizationResponse {
|
|||||||
QString error_description;
|
QString error_description;
|
||||||
};
|
};
|
||||||
|
|
||||||
DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& data)
|
DeviceAuthorizationResponse parseDeviceAuthorizationResponse(const QByteArray& data)
|
||||||
{
|
{
|
||||||
QJsonParseError err;
|
QJsonParseError err;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
|
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
|
||||||
if (err.error != QJsonParseError::NoError) {
|
if (err.error != QJsonParseError::NoError) {
|
||||||
qWarning() << "Failed to parse device autorization response due to err:" << err.errorString();
|
qWarning() << "Failed to parse device authorization response due to err:" << err.errorString();
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!doc.isObject()) {
|
if (!doc.isObject()) {
|
||||||
qWarning() << "Device autorization response is not an object";
|
qWarning() << "Device authorization response is not an object";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
auto obj = doc.object();
|
auto obj = doc.object();
|
||||||
@ -112,9 +112,9 @@ DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& dat
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void MSADeviceCodeStep::deviceAutorizationFinished()
|
void MSADeviceCodeStep::deviceAuthorizationFinished()
|
||||||
{
|
{
|
||||||
auto rsp = parseDeviceAutorizationResponse(*m_response);
|
auto rsp = parseDeviceAuthorizationResponse(*m_response);
|
||||||
if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) {
|
if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) {
|
||||||
qWarning() << "Device authorization failed:" << rsp.error;
|
qWarning() << "Device authorization failed:" << rsp.error;
|
||||||
emit finished(AccountTaskState::STATE_FAILED_HARD,
|
emit finished(AccountTaskState::STATE_FAILED_HARD,
|
||||||
@ -210,12 +210,12 @@ AuthenticationResponse parseAuthenticationResponse(const QByteArray& data)
|
|||||||
QJsonParseError err;
|
QJsonParseError err;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
|
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
|
||||||
if (err.error != QJsonParseError::NoError) {
|
if (err.error != QJsonParseError::NoError) {
|
||||||
qWarning() << "Failed to parse device autorization response due to err:" << err.errorString();
|
qWarning() << "Failed to parse device authorization response due to err:" << err.errorString();
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!doc.isObject()) {
|
if (!doc.isObject()) {
|
||||||
qWarning() << "Device autorization response is not an object";
|
qWarning() << "Device authorization response is not an object";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
auto obj = doc.object();
|
auto obj = doc.object();
|
||||||
|
@ -58,7 +58,7 @@ class MSADeviceCodeStep : public AuthStep {
|
|||||||
void authorizeWithBrowser(QString url, QString code, int expiresIn);
|
void authorizeWithBrowser(QString url, QString code, int expiresIn);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void deviceAutorizationFinished();
|
void deviceAuthorizationFinished();
|
||||||
void startPoolTimer();
|
void startPoolTimer();
|
||||||
void authenticateUser();
|
void authenticateUser();
|
||||||
void authenticationFinished();
|
void authenticationFinished();
|
||||||
|
@ -85,8 +85,7 @@ class CustomOAuthOobReplyHandler : public QOAuthOobReplyHandler {
|
|||||||
MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent)
|
MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent)
|
||||||
{
|
{
|
||||||
m_clientId = APPLICATION->getMSAClientID();
|
m_clientId = APPLICATION->getMSAClientID();
|
||||||
if (QCoreApplication::applicationFilePath().startsWith("/tmp/.mount_") ||
|
if (QCoreApplication::applicationFilePath().startsWith("/tmp/.mount_") || APPLICATION->isPortable() || !isSchemeHandlerRegistered())
|
||||||
QFile::exists(FS::PathCombine(APPLICATION->root(), "portable.txt")) || !isSchemeHandlerRegistered())
|
|
||||||
|
|
||||||
{
|
{
|
||||||
auto replyHandler = new QOAuthHttpServerReplyHandler(this);
|
auto replyHandler = new QOAuthHttpServerReplyHandler(this);
|
||||||
@ -100,44 +99,44 @@ MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(sile
|
|||||||
</script>
|
</script>
|
||||||
)XXX")
|
)XXX")
|
||||||
.arg(BuildConfig.LOGIN_CALLBACK_URL));
|
.arg(BuildConfig.LOGIN_CALLBACK_URL));
|
||||||
oauth2.setReplyHandler(replyHandler);
|
m_oauth2.setReplyHandler(replyHandler);
|
||||||
} else {
|
} else {
|
||||||
oauth2.setReplyHandler(new CustomOAuthOobReplyHandler(this));
|
m_oauth2.setReplyHandler(new CustomOAuthOobReplyHandler(this));
|
||||||
}
|
}
|
||||||
oauth2.setAuthorizationUrl(QUrl("https://login.live.com/oauth20_connect.srf"));
|
m_oauth2.setAuthorizationUrl(QUrl("https://login.live.com/oauth20_connect.srf"));
|
||||||
oauth2.setAccessTokenUrl(QUrl("https://login.live.com/oauth20_token.srf"));
|
m_oauth2.setAccessTokenUrl(QUrl("https://login.live.com/oauth20_token.srf"));
|
||||||
const auto& scope = "service::user.auth.xboxlive.com::MBI_SSL";
|
const auto& scope = "service::user.auth.xboxlive.com::MBI_SSL";
|
||||||
oauth2.setScope(scope);
|
m_oauth2.setScope(scope);
|
||||||
// QOAuth2AuthorizationCodeFlow doesn't pass a "scope" when refreshing access tokens, but Microsoft expects it.
|
// QOAuth2AuthorizationCodeFlow doesn't pass a "scope" when refreshing access tokens, but Microsoft expects it.
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QVariantMap* parameters) {
|
m_oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QVariantMap* parameters) {
|
||||||
if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) {
|
if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) {
|
||||||
(*parameters)["scope"] = scope;
|
(*parameters)["scope"] = scope;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
#else
|
#else
|
||||||
oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* parameters) {
|
m_oauth2.setModifyParametersFunction([&](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* parameters) {
|
||||||
if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) {
|
if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) {
|
||||||
(*parameters).insert("scope", scope);
|
(*parameters).insert("scope", scope);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
oauth2.setClientIdentifier(m_clientId);
|
m_oauth2.setClientIdentifier(m_clientId);
|
||||||
oauth2.setNetworkAccessManager(APPLICATION->network().get());
|
m_oauth2.setNetworkAccessManager(APPLICATION->network().get());
|
||||||
|
|
||||||
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::granted, this, [this] {
|
connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::granted, this, [this] {
|
||||||
m_data->msaClientID = oauth2.clientIdentifier();
|
m_data->msaClientID = m_oauth2.clientIdentifier();
|
||||||
m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc();
|
m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc();
|
||||||
m_data->msaToken.notAfter = oauth2.expirationAt();
|
m_data->msaToken.notAfter = m_oauth2.expirationAt();
|
||||||
m_data->msaToken.extra = oauth2.extraTokens();
|
m_data->msaToken.extra = m_oauth2.extraTokens();
|
||||||
m_data->msaToken.refresh_token = oauth2.refreshToken();
|
m_data->msaToken.refresh_token = m_oauth2.refreshToken();
|
||||||
m_data->msaToken.token = oauth2.token();
|
m_data->msaToken.token = m_oauth2.token();
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
|
emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
|
||||||
});
|
});
|
||||||
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &MSAStep::authorizeWithBrowser);
|
connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &MSAStep::authorizeWithBrowser);
|
||||||
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this, silent](const QAbstractOAuth2::Error err) {
|
connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this, silent](const QAbstractOAuth2::Error err) {
|
||||||
auto state = AccountTaskState::STATE_FAILED_HARD;
|
auto state = AccountTaskState::STATE_FAILED_HARD;
|
||||||
if (oauth2.status() == QAbstractOAuth::Status::Granted || silent) {
|
if (m_oauth2.status() == QAbstractOAuth::Status::Granted || silent) {
|
||||||
if (err == QAbstractOAuth2::Error::NetworkError) {
|
if (err == QAbstractOAuth2::Error::NetworkError) {
|
||||||
state = AccountTaskState::STATE_OFFLINE;
|
state = AccountTaskState::STATE_OFFLINE;
|
||||||
} else {
|
} else {
|
||||||
@ -151,16 +150,16 @@ MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(sile
|
|||||||
qWarning() << message;
|
qWarning() << message;
|
||||||
emit finished(state, message);
|
emit finished(state, message);
|
||||||
});
|
});
|
||||||
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::error, this,
|
connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::error, this,
|
||||||
[this](const QString& error, const QString& errorDescription, const QUrl& uri) {
|
[this](const QString& error, const QString& errorDescription, const QUrl& uri) {
|
||||||
qWarning() << "Failed to login because" << error << errorDescription;
|
qWarning() << "Failed to login because" << error << errorDescription;
|
||||||
emit finished(AccountTaskState::STATE_FAILED_HARD, errorDescription);
|
emit finished(AccountTaskState::STATE_FAILED_HARD, errorDescription);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::extraTokensChanged, this,
|
connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::extraTokensChanged, this,
|
||||||
[this](const QVariantMap& tokens) { m_data->msaToken.extra = tokens; });
|
[this](const QVariantMap& tokens) { m_data->msaToken.extra = tokens; });
|
||||||
|
|
||||||
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::clientIdentifierChanged, this,
|
connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::clientIdentifierChanged, this,
|
||||||
[this](const QString& clientIdentifier) { m_data->msaClientID = clientIdentifier; });
|
[this](const QString& clientIdentifier) { m_data->msaClientID = clientIdentifier; });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,20 +180,20 @@ void MSAStep::perform()
|
|||||||
emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - refresh token is empty."));
|
emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - refresh token is empty."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
oauth2.setRefreshToken(m_data->msaToken.refresh_token);
|
m_oauth2.setRefreshToken(m_data->msaToken.refresh_token);
|
||||||
oauth2.refreshAccessToken();
|
m_oauth2.refreshAccessToken();
|
||||||
} else {
|
} else {
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0
|
||||||
oauth2.setModifyParametersFunction(
|
m_oauth2.setModifyParametersFunction(
|
||||||
[](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* map) { map->insert("prompt", "select_account"); });
|
[](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* map) { map->insert("prompt", "select_account"); });
|
||||||
#else
|
#else
|
||||||
oauth2.setModifyParametersFunction(
|
m_oauth2.setModifyParametersFunction(
|
||||||
[](QAbstractOAuth::Stage stage, QMap<QString, QVariant>* map) { map->insert("prompt", "select_account"); });
|
[](QAbstractOAuth::Stage stage, QMap<QString, QVariant>* map) { map->insert("prompt", "select_account"); });
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
*m_data = AccountData();
|
*m_data = AccountData();
|
||||||
m_data->msaClientID = m_clientId;
|
m_data->msaClientID = m_clientId;
|
||||||
oauth2.grant();
|
m_oauth2.grant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,5 +55,5 @@ class MSAStep : public AuthStep {
|
|||||||
private:
|
private:
|
||||||
bool m_silent;
|
bool m_silent;
|
||||||
QString m_clientId;
|
QString m_clientId;
|
||||||
QOAuth2AuthorizationCodeFlow oauth2;
|
QOAuth2AuthorizationCodeFlow m_oauth2;
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "YggdrasilStep.h"
|
#include "YggdrasilStep.h"
|
||||||
|
#include <QInputDialog>
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
#include "net/RawHeaderProxy.h"
|
#include "net/RawHeaderProxy.h"
|
||||||
@ -48,10 +49,9 @@ void YggdrasilStep::login(QString password)
|
|||||||
req.insert("username", m_data->userName());
|
req.insert("username", m_data->userName());
|
||||||
req.insert("password", password);
|
req.insert("password", password);
|
||||||
req.insert("requestUser", false);
|
req.insert("requestUser", false);
|
||||||
//
|
|
||||||
// If we already have a client token, give it to the server.
|
// If we already have a client token, give it to the server.
|
||||||
// Otherwise, let the server give us one.
|
// Otherwise, let the server give us one.
|
||||||
|
|
||||||
m_data->generateClientTokenIfMissing();
|
m_data->generateClientTokenIfMissing();
|
||||||
req.insert("clientToken", m_data->clientToken());
|
req.insert("clientToken", m_data->clientToken());
|
||||||
|
|
||||||
@ -79,19 +79,26 @@ void YggdrasilStep::refresh()
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* {
|
* {
|
||||||
* "clientToken": "client identifier"
|
* "clientToken": "client identifier",
|
||||||
* "accessToken": "current access token to be refreshed"
|
* "accessToken": "current access token to be refreshed",
|
||||||
* "selectedProfile": // specifying this causes errors
|
* "selectedProfile": {
|
||||||
* {
|
* "id": "profile ID",
|
||||||
* "id": "profile ID"
|
|
||||||
* "name": "profile name"
|
* "name": "profile name"
|
||||||
* }
|
* },
|
||||||
* "requestUser": true/false // request the user structure
|
* "requestUser": true/false // request the user structure
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
QJsonObject req;
|
QJsonObject req;
|
||||||
req.insert("clientToken", m_data->clientToken());
|
req.insert("clientToken", m_data->clientToken());
|
||||||
req.insert("accessToken", m_data->accessToken());
|
req.insert("accessToken", m_data->accessToken());
|
||||||
|
|
||||||
|
if (m_didSelectProfile) {
|
||||||
|
QJsonObject selectedProfile;
|
||||||
|
selectedProfile.insert("id", m_data->profileId());
|
||||||
|
selectedProfile.insert("name", m_data->profileName());
|
||||||
|
req.insert("selectedProfile", selectedProfile);
|
||||||
|
}
|
||||||
req.insert("requestUser", false);
|
req.insert("requestUser", false);
|
||||||
|
|
||||||
QJsonDocument doc(req);
|
QJsonDocument doc(req);
|
||||||
@ -235,20 +242,51 @@ void YggdrasilStep::processResponse(QJsonObject responseData)
|
|||||||
m_data->yggdrasilToken.validity = Validity::Certain;
|
m_data->yggdrasilToken.validity = Validity::Certain;
|
||||||
m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
|
m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
|
||||||
|
|
||||||
// Get UUID here since we need it for later
|
// Select a profile
|
||||||
// FIXME: Here is a simple workaround for now,, which uses the first available profile when selectedProfile is not provided
|
|
||||||
auto profile = responseData.value("selectedProfile");
|
auto profile = responseData.value("selectedProfile");
|
||||||
if (!profile.isObject()) {
|
if (profile.isObject()) {
|
||||||
|
m_didSelectProfile = false;
|
||||||
|
} else {
|
||||||
|
if (m_didSelectProfile) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't save our selected profile."));
|
||||||
|
}
|
||||||
auto profiles = responseData.value("availableProfiles");
|
auto profiles = responseData.value("availableProfiles");
|
||||||
if (!profiles.isArray()) {
|
if (!profiles.isArray()) {
|
||||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send available profiles."));
|
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send available profiles."));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
if (profiles.toArray().isEmpty()) {
|
const auto& profilesArray = profiles.toArray();
|
||||||
|
if (profilesArray.isEmpty()) {
|
||||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Account has no available profile."));
|
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Account has no available profile."));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else if (profilesArray.size() == 1) {
|
||||||
profile = profiles.toArray().first();
|
profile = profiles.toArray().first();
|
||||||
|
} else {
|
||||||
|
std::map<QString, QJsonValue> profileMap;
|
||||||
|
QStringList profileNames;
|
||||||
|
|
||||||
|
const auto& invalidProfileMessage = tr("Authentication server sent an invalid available profile.");
|
||||||
|
for (const auto& profileValue : profilesArray) {
|
||||||
|
if (!profileValue.isObject()) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD, invalidProfileMessage);
|
||||||
|
}
|
||||||
|
const auto& profileNameValue = profileValue.toObject().value("name");
|
||||||
|
if (!profileNameValue.isString()) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD, invalidProfileMessage);
|
||||||
|
}
|
||||||
|
const auto& profileName = profileNameValue.toString();
|
||||||
|
profileMap.insert({ profileName, profileValue });
|
||||||
|
profileNames.append(profileName);
|
||||||
|
}
|
||||||
|
bool ok;
|
||||||
|
const auto& profileName =
|
||||||
|
QInputDialog::getItem(nullptr, "Select a player", "Select a player:", profileNames, 0, false, &ok);
|
||||||
|
|
||||||
|
if (!ok || profileName.isEmpty()) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication cancelled."));
|
||||||
|
}
|
||||||
|
profile = profileMap[profileName];
|
||||||
|
m_didSelectProfile = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -267,6 +305,12 @@ void YggdrasilStep::processResponse(QJsonObject responseData)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_didSelectProfile) {
|
||||||
|
// The authlib-injector specification requires that we refresh immediately after the user has selected a profile:
|
||||||
|
// https://github.com/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#%E8%B4%A6%E6%88%B7%E7%9A%84%E6%B7%BB%E5%8A%A0
|
||||||
|
return refresh();
|
||||||
|
}
|
||||||
|
|
||||||
emit finished(AccountTaskState::STATE_WORKING, "Logged in");
|
emit finished(AccountTaskState::STATE_WORKING, "Logged in");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,4 +33,5 @@ class YggdrasilStep : public AuthStep {
|
|||||||
std::shared_ptr<QByteArray> m_response;
|
std::shared_ptr<QByteArray> m_response;
|
||||||
Net::Upload::Ptr m_request;
|
Net::Upload::Ptr m_request;
|
||||||
NetJob::Ptr m_task;
|
NetJob::Ptr m_task;
|
||||||
|
bool m_didSelectProfile = false;
|
||||||
};
|
};
|
||||||
|
@ -57,9 +57,7 @@
|
|||||||
#include "tasks/SequentialTask.h"
|
#include "tasks/SequentialTask.h"
|
||||||
|
|
||||||
AutoInstallJava::AutoInstallJava(LaunchTask* parent)
|
AutoInstallJava::AutoInstallJava(LaunchTask* parent)
|
||||||
: LaunchStep(parent)
|
: LaunchStep(parent), m_instance(m_parent->instance()), m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {};
|
||||||
, m_instance(m_parent->instance())
|
|
||||||
, m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {};
|
|
||||||
|
|
||||||
void AutoInstallJava::executeTask()
|
void AutoInstallJava::executeTask()
|
||||||
{
|
{
|
||||||
@ -78,7 +76,7 @@ void AutoInstallJava::executeTask()
|
|||||||
auto java = std::dynamic_pointer_cast<JavaInstall>(javas->at(i));
|
auto java = std::dynamic_pointer_cast<JavaInstall>(javas->at(i));
|
||||||
if (java && packProfile->getProfile()->getCompatibleJavaMajors().contains(java->id.major())) {
|
if (java && packProfile->getProfile()->getCompatibleJavaMajors().contains(java->id.major())) {
|
||||||
if (!java->is_64bit) {
|
if (!java->is_64bit) {
|
||||||
emit logLine(tr("The automatic Java mechanism detected a 32-bit installation of Java."), MessageLevel::Info);
|
emit logLine(tr("The automatic Java mechanism detected a 32-bit installation of Java."), MessageLevel::Launcher);
|
||||||
}
|
}
|
||||||
setJavaPath(java->path);
|
setJavaPath(java->path);
|
||||||
return;
|
return;
|
||||||
@ -136,7 +134,7 @@ void AutoInstallJava::setJavaPath(QString path)
|
|||||||
settings->set("OverrideJavaLocation", true);
|
settings->set("OverrideJavaLocation", true);
|
||||||
settings->set("JavaPath", path);
|
settings->set("JavaPath", path);
|
||||||
settings->set("AutomaticJava", true);
|
settings->set("AutomaticJava", true);
|
||||||
emit logLine(tr("Compatible Java found at: %1.").arg(path), MessageLevel::Info);
|
emit logLine(tr("Compatible Java found at: %1.").arg(path), MessageLevel::Launcher);
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +177,7 @@ void AutoInstallJava::downloadJava(Meta::Version::Ptr version, QString javaName)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#if defined(Q_OS_MACOS)
|
#if defined(Q_OS_MACOS)
|
||||||
auto seq = makeShared<SequentialTask>(this, tr("Install Java"));
|
auto seq = makeShared<SequentialTask>(tr("Install Java"));
|
||||||
seq->addTask(m_current_task);
|
seq->addTask(m_current_task);
|
||||||
seq->addTask(makeShared<Java::SymlinkTask>(final_path));
|
seq->addTask(makeShared<Java::SymlinkTask>(final_path));
|
||||||
m_current_task = seq;
|
m_current_task = seq;
|
||||||
|
@ -132,6 +132,7 @@ void LauncherPartLaunch::executeTask()
|
|||||||
|
|
||||||
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
|
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
|
||||||
if (!wrapperCommandStr.isEmpty()) {
|
if (!wrapperCommandStr.isEmpty()) {
|
||||||
|
wrapperCommandStr = m_parent->substituteVariables(wrapperCommandStr);
|
||||||
auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr);
|
auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr);
|
||||||
auto wrapperCommand = wrapperArgs.takeFirst();
|
auto wrapperCommand = wrapperArgs.takeFirst();
|
||||||
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
|
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
|
||||||
@ -171,6 +172,7 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state)
|
|||||||
case LoggedProcess::Aborted:
|
case LoggedProcess::Aborted:
|
||||||
case LoggedProcess::Crashed: {
|
case LoggedProcess::Crashed: {
|
||||||
m_parent->setPid(-1);
|
m_parent->setPid(-1);
|
||||||
|
m_parent->instance()->setMinecraftRunning(false);
|
||||||
emitFailed(tr("Game crashed."));
|
emitFailed(tr("Game crashed."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -261,7 +261,7 @@ bool ResourceFolderModel::update()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResourceFolderModel::resolveResource(Resource* res)
|
void ResourceFolderModel::resolveResource(Resource::Ptr res)
|
||||||
{
|
{
|
||||||
if (!res->shouldResolve()) {
|
if (!res->shouldResolve()) {
|
||||||
return;
|
return;
|
||||||
@ -277,11 +277,14 @@ void ResourceFolderModel::resolveResource(Resource* res)
|
|||||||
m_active_parse_tasks.insert(ticket, task);
|
m_active_parse_tasks.insert(ticket, task);
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
task.get(), &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
task.get(), &Task::succeeded, this, [this, ticket, res] { onParseSucceeded(ticket, res->internal_id()); },
|
||||||
connect(task.get(), &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
Qt::ConnectionType::QueuedConnection);
|
||||||
|
connect(
|
||||||
|
task.get(), &Task::failed, this, [this, ticket, res] { onParseFailed(ticket, res->internal_id()); },
|
||||||
|
Qt::ConnectionType::QueuedConnection);
|
||||||
connect(
|
connect(
|
||||||
task.get(), &Task::finished, this,
|
task.get(), &Task::finished, this,
|
||||||
[=] {
|
[this, ticket] {
|
||||||
m_active_parse_tasks.remove(ticket);
|
m_active_parse_tasks.remove(ticket);
|
||||||
emit parseFinished();
|
emit parseFinished();
|
||||||
},
|
},
|
||||||
@ -317,7 +320,7 @@ void ResourceFolderModel::onUpdateSucceeded()
|
|||||||
void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
|
void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
|
||||||
{
|
{
|
||||||
auto iter = m_active_parse_tasks.constFind(ticket);
|
auto iter = m_active_parse_tasks.constFind(ticket);
|
||||||
if (iter == m_active_parse_tasks.constEnd())
|
if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int row = m_resources_index[resource_id];
|
int row = m_resources_index[resource_id];
|
||||||
@ -629,7 +632,7 @@ QString ResourceFolderModel::instDirPath() const
|
|||||||
void ResourceFolderModel::onParseFailed(int ticket, QString resource_id)
|
void ResourceFolderModel::onParseFailed(int ticket, QString resource_id)
|
||||||
{
|
{
|
||||||
auto iter = m_active_parse_tasks.constFind(ticket);
|
auto iter = m_active_parse_tasks.constFind(ticket);
|
||||||
if (iter == m_active_parse_tasks.constEnd())
|
if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto removed_index = m_resources_index[resource_id];
|
auto removed_index = m_resources_index[resource_id];
|
||||||
|
@ -76,7 +76,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||||||
virtual bool update();
|
virtual bool update();
|
||||||
|
|
||||||
/** Creates a new parse task, if needed, for 'res' and start it.*/
|
/** Creates a new parse task, if needed, for 'res' and start it.*/
|
||||||
virtual void resolveResource(Resource* res);
|
virtual void resolveResource(Resource::Ptr res);
|
||||||
|
|
||||||
[[nodiscard]] qsizetype size() const { return m_resources.size(); }
|
[[nodiscard]] qsizetype size() const { return m_resources.size(); }
|
||||||
[[nodiscard]] bool empty() const { return size() == 0; }
|
[[nodiscard]] bool empty() const { return size() == 0; }
|
||||||
@ -285,7 +285,7 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_resources[row].reset(new_resource);
|
m_resources[row].reset(new_resource);
|
||||||
resolveResource(m_resources.at(row).get());
|
resolveResource(m_resources.at(row));
|
||||||
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,7 +333,7 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>
|
|||||||
for (auto& added : added_set) {
|
for (auto& added : added_set) {
|
||||||
auto res = new_resources[added];
|
auto res = new_resources[added];
|
||||||
m_resources.append(res);
|
m_resources.append(res);
|
||||||
resolveResource(m_resources.last().get());
|
resolveResource(m_resources.last());
|
||||||
}
|
}
|
||||||
|
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "minecraft/mod/Resource.h"
|
#include "minecraft/mod/Resource.h"
|
||||||
|
|
||||||
@ -25,16 +26,12 @@ class BasicFolderLoadTask : public Task {
|
|||||||
[[nodiscard]] ResultPtr result() const { return m_result; }
|
[[nodiscard]] ResultPtr result() const { return m_result; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread())
|
BasicFolderLoadTask(QDir dir) : Task(false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread())
|
||||||
{
|
{
|
||||||
m_create_func = [](QFileInfo const& entry) -> Resource::Ptr { return makeShared<Resource>(entry); };
|
m_create_func = [](QFileInfo const& entry) -> Resource::Ptr { return makeShared<Resource>(entry); };
|
||||||
}
|
}
|
||||||
BasicFolderLoadTask(QDir dir, std::function<Resource::Ptr(QFileInfo const&)> create_function)
|
BasicFolderLoadTask(QDir dir, std::function<Resource::Ptr(QFileInfo const&)> create_function)
|
||||||
: Task(nullptr, false)
|
: Task(false), m_dir(dir), m_result(new Result), m_create_func(std::move(create_function)), m_thread_to_spawn_into(thread())
|
||||||
, m_dir(dir)
|
|
||||||
, m_result(new Result)
|
|
||||||
, m_create_func(std::move(create_function))
|
|
||||||
, m_thread_to_spawn_into(thread())
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
[[nodiscard]] bool canAbort() const override { return true; }
|
[[nodiscard]] bool canAbort() const override { return true; }
|
||||||
@ -52,6 +49,9 @@ class BasicFolderLoadTask : public Task {
|
|||||||
m_dir.refresh();
|
m_dir.refresh();
|
||||||
for (auto entry : m_dir.entryInfoList()) {
|
for (auto entry : m_dir.entryInfoList()) {
|
||||||
auto filePath = entry.absoluteFilePath();
|
auto filePath = entry.absoluteFilePath();
|
||||||
|
if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
auto newFilePath = FS::getUniqueResourceName(filePath);
|
auto newFilePath = FS::getUniqueResourceName(filePath);
|
||||||
if (newFilePath != filePath) {
|
if (newFilePath != filePath) {
|
||||||
FS::move(filePath, newFilePath);
|
FS::move(filePath, newFilePath);
|
||||||
|
@ -52,11 +52,10 @@ static bool checkDependencies(std::shared_ptr<GetModDependenciesTask::PackDepend
|
|||||||
(!loaders || !sel->version.loaders || sel->version.loaders & loaders);
|
(!loaders || !sel->version.loaders || sel->version.loaders & loaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
|
GetModDependenciesTask::GetModDependenciesTask(BaseInstance* instance,
|
||||||
BaseInstance* instance,
|
|
||||||
ModFolderModel* folder,
|
ModFolderModel* folder,
|
||||||
QList<std::shared_ptr<PackDependency>> selected)
|
QList<std::shared_ptr<PackDependency>> selected)
|
||||||
: SequentialTask(parent, tr("Get dependencies"))
|
: SequentialTask(tr("Get dependencies"))
|
||||||
, m_selected(selected)
|
, m_selected(selected)
|
||||||
, m_flame_provider{ ModPlatform::ResourceProvider::FLAME, std::make_shared<ResourceDownload::FlameModModel>(*instance),
|
, m_flame_provider{ ModPlatform::ResourceProvider::FLAME, std::make_shared<ResourceDownload::FlameModModel>(*instance),
|
||||||
std::make_shared<FlameAPI>() }
|
std::make_shared<FlameAPI>() }
|
||||||
@ -185,7 +184,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
|
|||||||
auto provider = providerName == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
|
auto provider = providerName == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
|
||||||
|
|
||||||
auto tasks = makeShared<SequentialTask>(
|
auto tasks = makeShared<SequentialTask>(
|
||||||
this, QString("DependencyInfo: %1").arg(dep.addonId.toString().isEmpty() ? dep.version : dep.addonId.toString()));
|
QString("DependencyInfo: %1").arg(dep.addonId.toString().isEmpty() ? dep.version : dep.addonId.toString()));
|
||||||
|
|
||||||
if (!dep.addonId.toString().isEmpty()) {
|
if (!dep.addonId.toString().isEmpty()) {
|
||||||
tasks->addTask(getProjectInfoTask(pDep));
|
tasks->addTask(getProjectInfoTask(pDep));
|
||||||
|
@ -61,10 +61,7 @@ class GetModDependenciesTask : public SequentialTask {
|
|||||||
std::shared_ptr<ResourceAPI> api;
|
std::shared_ptr<ResourceAPI> api;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit GetModDependenciesTask(QObject* parent,
|
explicit GetModDependenciesTask(BaseInstance* instance, ModFolderModel* folder, QList<std::shared_ptr<PackDependency>> selected);
|
||||||
BaseInstance* instance,
|
|
||||||
ModFolderModel* folder,
|
|
||||||
QList<std::shared_ptr<PackDependency>> selected);
|
|
||||||
|
|
||||||
auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; }
|
auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; }
|
||||||
QHash<QString, PackDependencyExtraInfo> getExtraInfo();
|
QHash<QString, PackDependencyExtraInfo> getExtraInfo();
|
||||||
|
@ -157,7 +157,7 @@ bool validate(QFileInfo file)
|
|||||||
|
|
||||||
} // namespace DataPackUtils
|
} // namespace DataPackUtils
|
||||||
|
|
||||||
LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) : Task(nullptr, false), m_token(token), m_data_pack(dp) {}
|
LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) : Task(false), m_token(token), m_data_pack(dp) {}
|
||||||
|
|
||||||
bool LocalDataPackParseTask::abort()
|
bool LocalDataPackParseTask::abort()
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonValue>
|
#include <QJsonValue>
|
||||||
|
#include <QRegularExpression>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
@ -15,6 +16,8 @@
|
|||||||
#include "minecraft/mod/ModDetails.h"
|
#include "minecraft/mod/ModDetails.h"
|
||||||
#include "settings/INIFile.h"
|
#include "settings/INIFile.h"
|
||||||
|
|
||||||
|
static QRegularExpression newlineRegex("\r\n|\n|\r");
|
||||||
|
|
||||||
namespace ModUtils {
|
namespace ModUtils {
|
||||||
|
|
||||||
// NEW format
|
// NEW format
|
||||||
@ -290,13 +293,13 @@ ModDetails ReadFabricModInfo(QByteArray contents)
|
|||||||
// https://github.com/QuiltMC/rfcs/blob/master/specification/0002-quilt.mod.json.md
|
// https://github.com/QuiltMC/rfcs/blob/master/specification/0002-quilt.mod.json.md
|
||||||
ModDetails ReadQuiltModInfo(QByteArray contents)
|
ModDetails ReadQuiltModInfo(QByteArray contents)
|
||||||
{
|
{
|
||||||
|
ModDetails details;
|
||||||
|
try {
|
||||||
QJsonParseError jsonError;
|
QJsonParseError jsonError;
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
|
||||||
auto object = Json::requireObject(jsonDoc, "quilt.mod.json");
|
auto object = Json::requireObject(jsonDoc, "quilt.mod.json");
|
||||||
auto schemaVersion = Json::ensureInteger(object.value("schema_version"), 0, "Quilt schema_version");
|
auto schemaVersion = Json::ensureInteger(object.value("schema_version"), 0, "Quilt schema_version");
|
||||||
|
|
||||||
ModDetails details;
|
|
||||||
|
|
||||||
// https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md
|
// https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md
|
||||||
if (schemaVersion == 1) {
|
if (schemaVersion == 1) {
|
||||||
auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info");
|
auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info");
|
||||||
@ -339,8 +342,8 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
|
|||||||
details.licenses.append(ModLicense(license.toString()));
|
details.licenses.append(ModLicense(license.toString()));
|
||||||
} else if (license.isObject()) {
|
} else if (license.isObject()) {
|
||||||
auto obj = license.toObject();
|
auto obj = license.toObject();
|
||||||
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), obj.value("url").toString(),
|
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(),
|
||||||
obj.value("description").toString()));
|
obj.value("url").toString(), obj.value("description").toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,6 +374,10 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} catch (const Exception& e) {
|
||||||
|
qWarning() << "Unable to parse mod info:" << e.cause();
|
||||||
|
}
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,11 +494,11 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// quick and dirty line-by-line parser
|
// quick and dirty line-by-line parser
|
||||||
auto manifestLines = file.readAll().split('\n');
|
auto manifestLines = QString(file.readAll()).split(newlineRegex);
|
||||||
QString manifestVersion = "";
|
QString manifestVersion = "";
|
||||||
for (auto& line : manifestLines) {
|
for (auto& line : manifestLines) {
|
||||||
if (QString(line).startsWith("Implementation-Version: ")) {
|
if (line.startsWith("Implementation-Version: ", Qt::CaseInsensitive)) {
|
||||||
manifestVersion = QString(line).remove("Implementation-Version: ");
|
manifestVersion = line.remove("Implementation-Version: ", Qt::CaseInsensitive);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -730,7 +737,7 @@ bool loadIconFile(const Mod& mod, QPixmap* pixmap)
|
|||||||
} // namespace ModUtils
|
} // namespace ModUtils
|
||||||
|
|
||||||
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
|
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
|
||||||
: Task(nullptr, false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result())
|
: Task(false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result())
|
||||||
{}
|
{}
|
||||||
|
|
||||||
bool LocalModParseTask::abort()
|
bool LocalModParseTask::abort()
|
||||||
|
@ -358,9 +358,7 @@ bool validate(QFileInfo file)
|
|||||||
|
|
||||||
} // namespace ResourcePackUtils
|
} // namespace ResourcePackUtils
|
||||||
|
|
||||||
LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp)
|
LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp) : Task(false), m_token(token), m_resource_pack(rp) {}
|
||||||
: Task(nullptr, false), m_token(token), m_resource_pack(rp)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool LocalResourcePackParseTask::abort()
|
bool LocalResourcePackParseTask::abort()
|
||||||
{
|
{
|
||||||
|
@ -93,7 +93,7 @@ bool validate(QFileInfo file)
|
|||||||
|
|
||||||
} // namespace ShaderPackUtils
|
} // namespace ShaderPackUtils
|
||||||
|
|
||||||
LocalShaderPackParseTask::LocalShaderPackParseTask(int token, ShaderPack& sp) : Task(nullptr, false), m_token(token), m_shader_pack(sp) {}
|
LocalShaderPackParseTask::LocalShaderPackParseTask(int token, ShaderPack& sp) : Task(false), m_token(token), m_shader_pack(sp) {}
|
||||||
|
|
||||||
bool LocalShaderPackParseTask::abort()
|
bool LocalShaderPackParseTask::abort()
|
||||||
{
|
{
|
||||||
|
@ -230,8 +230,7 @@ bool validate(QFileInfo file)
|
|||||||
|
|
||||||
} // namespace TexturePackUtils
|
} // namespace TexturePackUtils
|
||||||
|
|
||||||
LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp) : Task(nullptr, false), m_token(token), m_texture_pack(rp)
|
LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp) : Task(false), m_token(token), m_texture_pack(rp) {}
|
||||||
{}
|
|
||||||
|
|
||||||
bool LocalTexturePackParseTask::abort()
|
bool LocalTexturePackParseTask::abort()
|
||||||
{
|
{
|
||||||
|
@ -170,7 +170,7 @@ bool validate(QFileInfo file)
|
|||||||
|
|
||||||
} // namespace WorldSaveUtils
|
} // namespace WorldSaveUtils
|
||||||
|
|
||||||
LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) : Task(nullptr, false), m_token(token), m_save(save) {}
|
LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) : Task(false), m_token(token), m_save(save) {}
|
||||||
|
|
||||||
bool LocalWorldSaveParseTask::abort()
|
bool LocalWorldSaveParseTask::abort()
|
||||||
{
|
{
|
||||||
|
@ -36,13 +36,14 @@
|
|||||||
|
|
||||||
#include "ModFolderLoadTask.h"
|
#include "ModFolderLoadTask.h"
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "minecraft/mod/MetadataHandler.h"
|
#include "minecraft/mod/MetadataHandler.h"
|
||||||
|
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan)
|
ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan)
|
||||||
: Task(nullptr, false)
|
: Task(false)
|
||||||
, m_mods_dir(mods_dir)
|
, m_mods_dir(mods_dir)
|
||||||
, m_index_dir(index_dir)
|
, m_index_dir(index_dir)
|
||||||
, m_is_indexed(is_indexed)
|
, m_is_indexed(is_indexed)
|
||||||
@ -65,6 +66,9 @@ void ModFolderLoadTask::executeTask()
|
|||||||
m_mods_dir.refresh();
|
m_mods_dir.refresh();
|
||||||
for (auto entry : m_mods_dir.entryInfoList()) {
|
for (auto entry : m_mods_dir.entryInfoList()) {
|
||||||
auto filePath = entry.absoluteFilePath();
|
auto filePath = entry.absoluteFilePath();
|
||||||
|
if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
auto newFilePath = FS::getUniqueResourceName(filePath);
|
auto newFilePath = FS::getUniqueResourceName(filePath);
|
||||||
if (newFilePath != filePath) {
|
if (newFilePath != filePath) {
|
||||||
FS::move(filePath, newFilePath);
|
FS::move(filePath, newFilePath);
|
||||||
|
62
launcher/minecraft/skins/AuthlibInjectorTextureDelete.cpp
Normal file
62
launcher/minecraft/skins/AuthlibInjectorTextureDelete.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Fjord Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2024 Evan Goode <mail@evangoo.de>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AuthlibInjectorTextureDelete.h"
|
||||||
|
|
||||||
|
#include "net/ByteArraySink.h"
|
||||||
|
#include "net/RawHeaderProxy.h"
|
||||||
|
|
||||||
|
AuthlibInjectorTextureDelete::AuthlibInjectorTextureDelete(QString textureType) : NetRequest(), m_textureType(textureType)
|
||||||
|
{
|
||||||
|
logCat = taskMCSkinsLogC;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply* AuthlibInjectorTextureDelete::getReply(QNetworkRequest& request)
|
||||||
|
{
|
||||||
|
setStatus(tr("Deleting texture"));
|
||||||
|
return m_network->deleteResource(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthlibInjectorTextureDelete::Ptr AuthlibInjectorTextureDelete::make(MinecraftAccountPtr account, QString textureType)
|
||||||
|
{
|
||||||
|
auto up = makeShared<AuthlibInjectorTextureDelete>(textureType);
|
||||||
|
QString token = account->accessToken();
|
||||||
|
up->m_url = QUrl(account->accountServerUrl() + "/user/profile/" + account->profileId() + "/" + textureType);
|
||||||
|
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
|
||||||
|
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
|
||||||
|
{ "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() },
|
||||||
|
}));
|
||||||
|
return up;
|
||||||
|
}
|
38
launcher/minecraft/skins/AuthlibInjectorTextureDelete.h
Normal file
38
launcher/minecraft/skins/AuthlibInjectorTextureDelete.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Fjord Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2024 Evan Goode <mail@evangoo.de>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <minecraft/auth/MinecraftAccount.h>
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
|
class AuthlibInjectorTextureDelete : public Net::NetRequest {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
using Ptr = shared_qobject_ptr<AuthlibInjectorTextureDelete>;
|
||||||
|
AuthlibInjectorTextureDelete(QString textureType);
|
||||||
|
virtual ~AuthlibInjectorTextureDelete() = default;
|
||||||
|
|
||||||
|
static AuthlibInjectorTextureDelete::Ptr make(MinecraftAccountPtr account, QString textureType);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_textureType;
|
||||||
|
};
|
83
launcher/minecraft/skins/AuthlibInjectorTextureUpload.cpp
Normal file
83
launcher/minecraft/skins/AuthlibInjectorTextureUpload.cpp
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Fjord Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2024 Evan Goode <mail@evangoo.de>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AuthlibInjectorTextureUpload.h"
|
||||||
|
|
||||||
|
#include <QHttpMultiPart>
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "net/ByteArraySink.h"
|
||||||
|
#include "net/RawHeaderProxy.h"
|
||||||
|
|
||||||
|
AuthlibInjectorTextureUpload::AuthlibInjectorTextureUpload(QString path, std::optional<QString> skin_variant) : NetRequest(), m_path(path), m_skin_variant(skin_variant)
|
||||||
|
{
|
||||||
|
logCat = taskMCSkinsLogC;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply* AuthlibInjectorTextureUpload::getReply(QNetworkRequest& request)
|
||||||
|
{
|
||||||
|
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this);
|
||||||
|
|
||||||
|
QHttpPart file;
|
||||||
|
file.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png"));
|
||||||
|
file.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"texture.png\""));
|
||||||
|
file.setBody(FS::read(m_path));
|
||||||
|
multiPart->append(file);
|
||||||
|
|
||||||
|
if (m_skin_variant.has_value()) {
|
||||||
|
QHttpPart model;
|
||||||
|
model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"model\""));
|
||||||
|
model.setBody(m_skin_variant->toUtf8());
|
||||||
|
multiPart->append(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus(tr("Uploading texture"));
|
||||||
|
return m_network->put(request, multiPart);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthlibInjectorTextureUpload::Ptr AuthlibInjectorTextureUpload::make(MinecraftAccountPtr account, QString path, std::optional<QString> skin_variant)
|
||||||
|
{
|
||||||
|
auto up = makeShared<AuthlibInjectorTextureUpload>(path, skin_variant);
|
||||||
|
QString token = account->accessToken();
|
||||||
|
|
||||||
|
QString textureType = skin_variant.has_value() ? "skin" : "cape";
|
||||||
|
up->m_url = QUrl(account->accountServerUrl() + "/user/profile/" + account->profileId() + "/" + textureType);
|
||||||
|
up->setObjectName(QString("BYTES:") + up->m_url.toString());
|
||||||
|
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
|
||||||
|
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
|
||||||
|
{ "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() },
|
||||||
|
}));
|
||||||
|
return up;
|
||||||
|
}
|
41
launcher/minecraft/skins/AuthlibInjectorTextureUpload.h
Normal file
41
launcher/minecraft/skins/AuthlibInjectorTextureUpload.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Fjord Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2024 Evan Goode <mail@evangoo.de>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <minecraft/auth/MinecraftAccount.h>
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
|
class AuthlibInjectorTextureUpload : public Net::NetRequest {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
using Ptr = shared_qobject_ptr<AuthlibInjectorTextureUpload>;
|
||||||
|
|
||||||
|
// Note this class takes ownership of the file.
|
||||||
|
AuthlibInjectorTextureUpload(QString path, std::optional<QString> skin_variant);
|
||||||
|
virtual ~AuthlibInjectorTextureUpload() = default;
|
||||||
|
|
||||||
|
static AuthlibInjectorTextureUpload::Ptr make(MinecraftAccountPtr account, QString path, std::optional<QString> skin_variant);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_path;
|
||||||
|
std::optional<QString> m_skin_variant;
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user