Compare commits
452 Commits
Author | SHA1 | Date | |
---|---|---|---|
d8044ababe | |||
32b526b729 | |||
7f6515dbe4 | |||
a39390b8b4 | |||
63a3dd1919 | |||
7a5a4de6ea | |||
664d4e701e | |||
a4ba8d8288 | |||
392bf7a97b | |||
34687049b1 | |||
9337ec6706 | |||
5bcb6962c4 | |||
0617b43190 | |||
ed28234cfb | |||
9c4455ca03 | |||
9ec7837275 | |||
549b5a6488 | |||
2652f37453 | |||
0eaff22145 | |||
2f5393b9d0 | |||
e28480a8e4 | |||
fcef6321fc | |||
35e792c5de | |||
c08b632b51 | |||
cac800bfd8 | |||
2eb8173951 | |||
75abf2c124 | |||
d40a18d6c5 | |||
a74fdc588c | |||
25b0ec6eff | |||
04e8982d33 | |||
58bd449db8 | |||
c984e9b5d6 | |||
ddd319369a | |||
de3c336213 | |||
6e94e9bff1 | |||
93b8d9e454 | |||
6d46081864 | |||
41032aaac2 | |||
4e08f28246 | |||
46c57e120f | |||
6b52ee61ae | |||
61fbc5a791 | |||
22365205f9 | |||
c6515c1dad | |||
8201d1df02 | |||
ddf168c536 | |||
c9f81f56e9 | |||
570264e1e9 | |||
325e58d98c | |||
23a2960aa3 | |||
888a87463e | |||
2ebaf46095 | |||
3a95a3b7c1 | |||
c4edffb388 | |||
5eec7cc788 | |||
fb4cf0b75d | |||
5584884fe6 | |||
3bb1068ef0 | |||
b1b85313ae | |||
aec3e7b0fc | |||
243600b75b | |||
3d89d126d0 | |||
54281e53a1 | |||
6befd2be81 | |||
1f0ca9ed92 | |||
f3db9c3920 | |||
83ceb26151 | |||
32cdfb871c | |||
801e7da5ee | |||
2cf1ab7ec5 | |||
d049c1afaf | |||
9f45389bc1 | |||
d875481969 | |||
17a1e1245c | |||
06d28c3eec | |||
684982c25c | |||
421522a61a | |||
f3b29d67f4 | |||
2ee5c6b2a1 | |||
5083772c6f | |||
fd51e5df47 | |||
3405fd91c6 | |||
194822f11e | |||
98963d4cdf | |||
804ef36b20 | |||
3c6ff8fddf | |||
511893d535 | |||
e05fe77bfe | |||
2b7b9a2abb | |||
6db3f68ae1 | |||
3773f2e7a5 | |||
1f8bffc15a | |||
afaef4e83b | |||
b746f723cb | |||
cfaf44f57b | |||
eed541201e | |||
a8bcb85f7b | |||
981e9cf290 | |||
3ac398ac49 | |||
2d0728395f | |||
722194405a | |||
0868a5e534 | |||
a86b9c82ce | |||
f8642548c8 | |||
27873f7ed4 | |||
b3eda6bcbb | |||
1c7799e292 | |||
aecd158d3c | |||
87d35f0d16 | |||
c089f9b59f | |||
82d7f9f5a4 | |||
8bc529be3d | |||
924c1634d3 | |||
03d077e915 | |||
303628bb05 | |||
3b92ec8e82 | |||
545944cb0d | |||
c90a88b6b6 | |||
89e45a61b3 | |||
2aff7bac4a | |||
124097d3a5 | |||
c520faed6d | |||
5bdb3703ed | |||
2901039a48 | |||
dfa220ef02 | |||
f26be00571 | |||
83654a193e | |||
b2a5d8daf4 | |||
19ee736e1d | |||
fda3f1352e | |||
d194b02e28 | |||
aaba99dc10 | |||
93a2e0f777 | |||
71f3c6b461 | |||
aabcca5059 | |||
7e67fd8c79 | |||
fecf1ffcb9 | |||
fafc9cf9ca | |||
3111e6a721 | |||
e436f471a0 | |||
28f84902f6 | |||
ebee50eedc | |||
e0ef09dfe1 | |||
d7992ab29d | |||
60f19f305e | |||
d99976f5d7 | |||
db158a5735 | |||
ea3be17220 | |||
f26049009e | |||
787234a53a | |||
fc3a64056c | |||
6ebc9abb80 | |||
80e9eed35a | |||
23b3990f99 | |||
41276403df | |||
e3e9e39498 | |||
f315025a8d | |||
8e43d97133 | |||
c97a47dc62 | |||
7ccafdc993 | |||
0dca9cb6b8 | |||
be3d780720 | |||
0f59a1dde1 | |||
7c6bb80cee | |||
62841c5b23 | |||
aad4f8d1f7 | |||
11c44c676c | |||
370c3aa598 | |||
1cdadafdf8 | |||
dd6f670dec | |||
9ff364b0d3 | |||
58a5331f7b | |||
ed261f0af9 | |||
c01b475cbf | |||
3d4feeec8d | |||
a1800ec23f | |||
8a4f1c66f8 | |||
b187231b0e | |||
60b38de69f | |||
600c49f7f0 | |||
e7380e70a3 | |||
3df8594f19 | |||
ee4a829293 | |||
1862f3c124 | |||
777ab3416f | |||
ecf5ab75e7 | |||
4f6d964217 | |||
06019f01e3 | |||
ddde885084 | |||
be8c6f218c | |||
9eb35ea7c8 | |||
d2fdbec41d | |||
2dd372600c | |||
eda6cf11ef | |||
68facd6b93 | |||
87002fb8f8 | |||
6a50fa35ec | |||
6541570969 | |||
4b0ceea894 | |||
8c0816c166 | |||
be769d07f1 | |||
3a9d58e31c | |||
7024acac06 | |||
6131346e2f | |||
eed73c9078 | |||
795d6f35ee | |||
ebd46705d5 | |||
72d2ca234e | |||
2246c3359b | |||
242fb156a2 | |||
208ed73e59 | |||
4441b37338 | |||
941d75824a | |||
ec9ddc4f22 | |||
98b6f90172 | |||
7e280de361 | |||
7bd8bd13fe | |||
09e85e948c | |||
9ec1c00887 | |||
c6bcb6228b | |||
a24d589845 | |||
ebbcc9f6da | |||
23fc453fae | |||
aad6f74db6 | |||
07dcefabcb | |||
40c68595d7 | |||
fe9a4fece4 | |||
098327f128 | |||
0873b8d304 | |||
c9eb584ac8 | |||
10493bd44a | |||
9e35230467 | |||
81e326571b | |||
1b2a7de4e2 | |||
11216d200c | |||
777be6a48d | |||
5765a1fdf1 | |||
29dcb9d274 | |||
8674ac4f68 | |||
684b8f24f3 | |||
1ca2be0039 | |||
8c41ff68f7 | |||
931d6c280a | |||
ee0fb2d0e0 | |||
c496ad1237 | |||
277fa21f5f | |||
1cf949226e | |||
be3fae6511 | |||
5932f36285 | |||
30abb65368 | |||
64a06b5ed6 | |||
69d18f17a5 | |||
4c7d3a103c | |||
10320bdeb4 | |||
4261dcff39 | |||
a091245793 | |||
ca282f9fb3 | |||
7cf2c3be0f | |||
f65d506f26 | |||
333dbca01e | |||
42eb265624 | |||
8a65726e9d | |||
1b0ca47682 | |||
8e3f5c3305 | |||
3c0a6987cd | |||
e37f70b9f7 | |||
03c148ce50 | |||
4817f0312d | |||
b70a82c609 | |||
25d1e0c4e6 | |||
8e3356f11a | |||
1b559c7776 | |||
d5583f0f02 | |||
bedd3c50b6 | |||
43a7af3f44 | |||
9db27c6acc | |||
42c81395b3 | |||
3b13e692d2 | |||
0331f5a1eb | |||
8a7e117f6b | |||
9b984cedac | |||
dd9e30b24a | |||
6a93688b2e | |||
3ab17a97a8 | |||
f21ae66265 | |||
afa1a5e932 | |||
050768c266 | |||
cda2bfc240 | |||
2f167b1512 | |||
ba3ac85356 | |||
ec29cedeb7 | |||
064ae49d2b | |||
247f99ce2f | |||
7b6d269904 | |||
87a0482b8b | |||
e899699918 | |||
bdf464e792 | |||
c410bb4ecb | |||
a720bcc637 | |||
369a8cdc74 | |||
3f4e55be4f | |||
9171f471ab | |||
2186b134a4 | |||
f371ec210c | |||
afcd669d2f | |||
fbf542d205 | |||
13184eb8e9 | |||
ddf1e1ccee | |||
5ac4e73697 | |||
6be59b53f1 | |||
bb54fec907 | |||
0b81b283bf | |||
e2ab2aea32 | |||
c3ceefbafb | |||
e7cf9932a9 | |||
0c9d03f5df | |||
92aa24ae34 | |||
97a74d5c1f | |||
256f8094f5 | |||
1e2f0ab308 | |||
af2cf2734d | |||
ec62d8e973 | |||
3225f514f6 | |||
2d63c86022 | |||
2dcff83be7 | |||
afb9ebcd99 | |||
92d7e44525 | |||
a517f442ea | |||
311758233b | |||
6e086eb808 | |||
7e8644430c | |||
70a8f6743a | |||
69eafd0e11 | |||
6b6a095b91 | |||
d5a2185030 | |||
26f31e9288 | |||
e654e66839 | |||
bb4861cf0d | |||
01505910f4 | |||
ab766a0598 | |||
51c664a678 | |||
3c4b45c9e7 | |||
93507a263b | |||
1175461030 | |||
2f5e55bea0 | |||
c375e7b4df | |||
5d188c69ed | |||
6bff7751d0 | |||
1a5986abe0 | |||
cebac3c10e | |||
a5da3db966 | |||
cd30f75173 | |||
94df4ceb36 | |||
a14476c5fb | |||
d82bb29919 | |||
33af0c6a7c | |||
68f3f98bc3 | |||
f873cd5b1a | |||
fa8df286d9 | |||
76aa0c434a | |||
78dc0cfdf3 | |||
9654728bfb | |||
123d1864f4 | |||
96a91e988d | |||
19ecb1701e | |||
8085b2728d | |||
56cad31a36 | |||
3275bc4e93 | |||
8963378039 | |||
c7d435bb7a | |||
06cc7e4ea0 | |||
fba20e2cfb | |||
358f080c76 | |||
8cea57ff0f | |||
7b27f200b1 | |||
f4b207220c | |||
d835e1d14e | |||
75f92de8f8 | |||
293c1deee5 | |||
4dee05a967 | |||
8d6414d74a | |||
362ecdb583 | |||
058b9f9272 | |||
355762aa30 | |||
be4fb65470 | |||
4ed296bad4 | |||
a8aa862919 | |||
31ba1de53b | |||
bca1fef2b2 | |||
f33b31e048 | |||
0d10ebb7ca | |||
4a8abc948e | |||
6fd3672618 | |||
abd090bd48 | |||
bd9140f1f3 | |||
9d78b2d259 | |||
9c9528838a | |||
77b640b76b | |||
a8dfe98b1a | |||
cee41b87f7 | |||
7a95314e42 | |||
6aaf1f4f21 | |||
368a0ddd44 | |||
0808a10b7b | |||
6f052baa94 | |||
5bc67d3f6b | |||
74c6c5cfbc | |||
158b7fd166 | |||
c3f647dc96 | |||
5936c7b65c | |||
a8bcd85c93 | |||
127b558f95 | |||
6e9a27f40f | |||
4a13dbe3bb | |||
0f61f5ba03 | |||
5f1efbeb67 | |||
9c105914f0 | |||
579582740e | |||
b15544c163 | |||
94a63e3859 | |||
842b7e6c39 | |||
22f5011451 | |||
b4e8abd0ad | |||
a9e8ed5087 | |||
00520b6a0e | |||
e6f2a3893a | |||
24c034ff6a | |||
631a93bcd8 | |||
b1763353ea | |||
f95bcf45ad | |||
15ec1abb6a | |||
cfda8dbb2b | |||
ab6e1b112b | |||
c666c3e251 | |||
20b1723e78 | |||
be78afeee5 | |||
6a1d611fd1 | |||
c8a72c876d | |||
ec87a8ddfc | |||
33e34ebb83 | |||
509f7bd018 | |||
10f27250ee | |||
aed7963d11 | |||
2810413112 | |||
31fd92e071 | |||
bf560f4594 | |||
a7fc23dd96 | |||
9e69b8fe1b | |||
e0ae631d59 | |||
273cf3d565 | |||
f432cfd73a |
9
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
9
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -27,7 +27,14 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Version of PolyMC
|
label: Version of PolyMC
|
||||||
description: The version of PolyMC used in the bug report.
|
description: The version of PolyMC used in the bug report.
|
||||||
placeholder: PolyMC 1.3.2
|
placeholder: PolyMC 1.4.1
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Version of Qt
|
||||||
|
description: The version of Qt used in the bug report. You can find it in Help -> About PolyMC -> About Qt.
|
||||||
|
placeholder: Qt 6.3.0
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
3
.github/codeql/codeql-config.yml
vendored
Normal file
3
.github/codeql/codeql-config.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
query-filters:
|
||||||
|
- exclude:
|
||||||
|
id: cpp/fixme-comment
|
131
.github/workflows/build.yml
vendored
131
.github/workflows/build.yml
vendored
@ -27,25 +27,32 @@ jobs:
|
|||||||
qt_host: linux
|
qt_host: linux
|
||||||
qt_version: '6.2.4'
|
qt_version: '6.2.4'
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_path: /home/runner/work/PolyMC/Qt
|
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-Legacy"
|
name: "Windows-Legacy"
|
||||||
msystem: mingw32
|
msystem: clang32
|
||||||
qt_ver: 5
|
qt_ver: 5
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows"
|
name: "Windows"
|
||||||
msystem: mingw32
|
msystem: clang64
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
|
|
||||||
- os: macos-12
|
- os: macos-12
|
||||||
macosx_deployment_target: 10.14
|
name: macOS
|
||||||
|
macosx_deployment_target: 10.15
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: mac
|
qt_host: mac
|
||||||
qt_version: '6.3.1'
|
qt_version: '6.3.0'
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_path: /Users/runner/work/PolyMC/Qt
|
|
||||||
|
- os: macos-12
|
||||||
|
name: macOS-Legacy
|
||||||
|
macosx_deployment_target: 10.13
|
||||||
|
qt_ver: 5
|
||||||
|
qt_host: mac
|
||||||
|
qt_version: '5.15.2'
|
||||||
|
qt_modules: ''
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
@ -66,6 +73,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
|
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
if: runner.os == 'Linux' && matrix.qt_ver == 6
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
config-file: ./.github/codeql/codeql-config.yml
|
||||||
|
queries: security-and-quality
|
||||||
|
languages: cpp, java
|
||||||
|
|
||||||
- name: 'Setup MSYS2'
|
- name: 'Setup MSYS2'
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
uses: msys2/setup-msys2@v2
|
uses: msys2/setup-msys2@v2
|
||||||
@ -74,6 +89,7 @@ jobs:
|
|||||||
update: true
|
update: true
|
||||||
install: >-
|
install: >-
|
||||||
git
|
git
|
||||||
|
mingw-w64-x86_64-binutils
|
||||||
pacboy: >-
|
pacboy: >-
|
||||||
toolchain:p
|
toolchain:p
|
||||||
cmake:p
|
cmake:p
|
||||||
@ -84,12 +100,11 @@ jobs:
|
|||||||
qt${{ matrix.qt_ver }}-imageformats:p
|
qt${{ matrix.qt_ver }}-imageformats:p
|
||||||
quazip-qt${{ matrix.qt_ver }}:p
|
quazip-qt${{ matrix.qt_ver }}:p
|
||||||
ccache:p
|
ccache:p
|
||||||
nsis:p
|
|
||||||
${{ matrix.qt_ver == 6 && 'qt6-5compat:p' || '' }}
|
${{ matrix.qt_ver == 6 && 'qt6-5compat:p' || '' }}
|
||||||
|
|
||||||
- name: Setup ccache
|
- name: Setup ccache
|
||||||
if: runner.os != 'Windows' && inputs.build_type == 'Debug'
|
if: runner.os != 'Windows' && inputs.build_type == 'Debug'
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.1
|
uses: hendrikmuhs/ccache-action@v1.2.3
|
||||||
with:
|
with:
|
||||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
|
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
|
||||||
|
|
||||||
@ -111,7 +126,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve ccache cache (Windows)
|
- name: Retrieve ccache cache (Windows)
|
||||||
if: runner.os == 'Windows' && inputs.build_type == 'Debug'
|
if: runner.os == 'Windows' && inputs.build_type == 'Debug'
|
||||||
uses: actions/cache@v3.0.2
|
uses: actions/cache@v3.0.11
|
||||||
with:
|
with:
|
||||||
path: '${{ github.workspace }}\.ccache'
|
path: '${{ github.workspace }}\.ccache'
|
||||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
|
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
|
||||||
@ -141,24 +156,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
|
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
|
||||||
|
|
||||||
- name: Cache Qt (macOS and AppImage)
|
|
||||||
id: cache-qt
|
|
||||||
if: matrix.qt_ver == 6 && runner.os != 'Windows'
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: '${{ matrix.qt_path }}/${{ matrix.qt_version }}'
|
|
||||||
key: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache
|
|
||||||
|
|
||||||
- name: Install Qt (macOS and AppImage)
|
- name: Install Qt (macOS and AppImage)
|
||||||
if: matrix.qt_ver == 6 && runner.os != 'Windows'
|
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS'
|
||||||
uses: jurplel/install-qt-action@v2
|
uses: jurplel/install-qt-action@v3
|
||||||
with:
|
with:
|
||||||
version: ${{ matrix.qt_version }}
|
version: ${{ matrix.qt_version }}
|
||||||
host: ${{ matrix.qt_host }}
|
host: ${{ matrix.qt_host }}
|
||||||
target: 'desktop'
|
target: 'desktop'
|
||||||
modules: ${{ matrix.qt_modules }}
|
modules: ${{ matrix.qt_modules }}
|
||||||
cached: ${{ steps.cache-qt.outputs.cache-hit }}
|
cache: true
|
||||||
aqtversion: ==2.1.*
|
cache-key-prefix: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache
|
||||||
|
|
||||||
- name: Prepare AppImage (Linux)
|
- name: Prepare AppImage (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
@ -174,15 +181,20 @@ jobs:
|
|||||||
##
|
##
|
||||||
|
|
||||||
- name: Configure CMake (macOS)
|
- name: Configure CMake (macOS)
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS' && matrix.qt_ver == 6
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DLauncher_BUILD_PLATFORM=macOS -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja
|
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja
|
||||||
|
|
||||||
|
- name: Configure CMake (macOS-Legacy)
|
||||||
|
if: runner.os == 'macOS' && matrix.qt_ver == 5
|
||||||
|
run: |
|
||||||
|
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja
|
||||||
|
|
||||||
- name: Configure CMake (Windows)
|
- name: Configure CMake (Windows)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja
|
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -G Ninja
|
||||||
|
|
||||||
- name: Configure CMake (Linux)
|
- name: Configure CMake (Linux)
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
@ -219,6 +231,14 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
ctest --test-dir build --output-on-failure
|
ctest --test-dir build --output-on-failure
|
||||||
|
|
||||||
|
##
|
||||||
|
# CODE SCAN
|
||||||
|
##
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
if: runner.os == 'Linux' && matrix.qt_ver == 6
|
||||||
|
uses: github/codeql-action/analyze@v2
|
||||||
|
|
||||||
##
|
##
|
||||||
# PACKAGE BUILDS
|
# PACKAGE BUILDS
|
||||||
##
|
##
|
||||||
@ -229,17 +249,18 @@ jobs:
|
|||||||
cmake --install ${{ env.BUILD_DIR }}
|
cmake --install ${{ env.BUILD_DIR }}
|
||||||
|
|
||||||
cd ${{ env.INSTALL_DIR }}
|
cd ${{ env.INSTALL_DIR }}
|
||||||
chmod +x "PolyMC.app/Contents/MacOS/polymc"
|
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||||
sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PolyMC.app/Contents/MacOS/polymc"
|
sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||||
tar -czf ../PolyMC.tar.gz *
|
mv "PrismLauncher.app" "Prism Launcher.app"
|
||||||
|
tar -czf ../PrismLauncher.tar.gz *
|
||||||
|
|
||||||
- name: Make Sparkle signature (macOS)
|
- name: Make Sparkle signature (macOS)
|
||||||
if: runner.os == 'macOS'
|
if: matrix.name == 'macOS'
|
||||||
run: |
|
run: |
|
||||||
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
|
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
|
||||||
brew install openssl@3
|
brew install openssl@3
|
||||||
echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem
|
echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem
|
||||||
signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PolyMC.tar.gz -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
|
signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.tar.gz -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
|
||||||
rm ed25519-priv.pem
|
rm ed25519-priv.pem
|
||||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||||
### Artifact Information :information_source:
|
### Artifact Information :information_source:
|
||||||
@ -259,10 +280,8 @@ jobs:
|
|||||||
cmake --install ${{ env.BUILD_DIR }}
|
cmake --install ${{ env.BUILD_DIR }}
|
||||||
|
|
||||||
cd ${{ env.INSTALL_DIR }}
|
cd ${{ env.INSTALL_DIR }}
|
||||||
if [ "${{ matrix.msystem }}" == "mingw32" ]; then
|
if [ "${{ matrix.qt_ver }}" == "5" ]; then
|
||||||
cp /mingw32/bin/libcrypto-1_1.dll /mingw32/bin/libssl-1_1.dll ./
|
cp /clang32/bin/libcrypto-1_1.dll /clang32/bin/libssl-1_1.dll ./
|
||||||
elif [ "${{ matrix.msystem }}" == "mingw64" ]; then
|
|
||||||
cp /mingw64/bin/libcrypto-1_1-x64.dll /mingw64/bin/libssl-1_1-x64.dll ./
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Package (Windows, portable)
|
- name: Package (Windows, portable)
|
||||||
@ -274,7 +293,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Package (Windows, installer)
|
- name: Package (Windows, installer)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
shell: msys2 {0}
|
|
||||||
run: |
|
run: |
|
||||||
cd ${{ env.INSTALL_DIR }}
|
cd ${{ env.INSTALL_DIR }}
|
||||||
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
|
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
|
||||||
@ -285,7 +303,7 @@ jobs:
|
|||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}
|
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}
|
||||||
|
|
||||||
cd ${{ env.INSTALL_DIR }}
|
cd ${{ env.INSTALL_DIR }}
|
||||||
tar --owner root --group root -czf ../PolyMC.tar.gz *
|
tar --owner root --group root -czf ../PrismLauncher.tar.gz *
|
||||||
|
|
||||||
- name: Package (Linux, portable)
|
- name: Package (Linux, portable)
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
@ -294,7 +312,7 @@ jobs:
|
|||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
||||||
|
|
||||||
cd ${{ env.INSTALL_PORTABLE_DIR }}
|
cd ${{ env.INSTALL_PORTABLE_DIR }}
|
||||||
tar -czf ../PolyMC-portable.tar.gz *
|
tar -czf ../PrismLauncher-portable.tar.gz *
|
||||||
|
|
||||||
- name: Package AppImage (Linux)
|
- name: Package AppImage (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
@ -302,7 +320,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
|
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
|
||||||
|
|
||||||
export OUTPUT="PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
|
export OUTPUT="PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
|
||||||
|
|
||||||
chmod +x linuxdeploy-*.AppImage
|
chmod +x linuxdeploy-*.AppImage
|
||||||
|
|
||||||
@ -313,7 +331,10 @@ jobs:
|
|||||||
|
|
||||||
cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk
|
cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk
|
||||||
|
|
||||||
cp -r /home/runner/work/PolyMC/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
cp -r /home/runner/work/PrismLauncher/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
||||||
|
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}//usr/lib/
|
||||||
|
|
||||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
|
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
|
||||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server"
|
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server"
|
||||||
@ -322,7 +343,7 @@ jobs:
|
|||||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib"
|
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib"
|
||||||
export LD_LIBRARY_PATH
|
export LD_LIBRARY_PATH
|
||||||
|
|
||||||
./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.polymc.PolyMC.svg
|
./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg
|
||||||
|
|
||||||
##
|
##
|
||||||
# UPLOAD BUILDS
|
# UPLOAD BUILDS
|
||||||
@ -332,63 +353,63 @@ jobs:
|
|||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PolyMC.tar.gz
|
path: PrismLauncher.tar.gz
|
||||||
|
|
||||||
- name: Upload binary zip (Windows)
|
- name: Upload binary zip (Windows)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: PolyMC-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: ${{ env.INSTALL_DIR }}/**
|
path: ${{ env.INSTALL_DIR }}/**
|
||||||
|
|
||||||
- name: Upload binary zip (Windows, portable)
|
- name: Upload binary zip (Windows, portable)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: PolyMC-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: ${{ env.INSTALL_PORTABLE_DIR }}/**
|
path: ${{ env.INSTALL_PORTABLE_DIR }}/**
|
||||||
|
|
||||||
- name: Upload installer (Windows)
|
- name: Upload installer (Windows)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: PolyMC-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PolyMC-Setup.exe
|
path: PrismLauncher-Setup.exe
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux, Qt 5)
|
- name: Upload binary tarball (Linux, Qt 5)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PolyMC.tar.gz
|
path: PrismLauncher.tar.gz
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux, portable, Qt 5)
|
- name: Upload binary tarball (Linux, portable, Qt 5)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: PolyMC-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PolyMC-portable.tar.gz
|
path: PrismLauncher-portable.tar.gz
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux, Qt 6)
|
- name: Upload binary tarball (Linux, Qt 6)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver !=5
|
if: runner.os == 'Linux' && matrix.qt_ver !=5
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: PolyMC-${{ runner.os }}-Qt6-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ runner.os }}-Qt6-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PolyMC.tar.gz
|
path: PrismLauncher.tar.gz
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux, portable, Qt 6)
|
- name: Upload binary tarball (Linux, portable, Qt 6)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: PolyMC-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PolyMC-portable.tar.gz
|
path: PrismLauncher-portable.tar.gz
|
||||||
|
|
||||||
- name: Upload AppImage (Linux)
|
- name: Upload AppImage (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
||||||
path: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
||||||
|
|
||||||
|
|
||||||
|
2
.github/workflows/trigger_builds.yml
vendored
2
.github/workflows/trigger_builds.yml
vendored
@ -11,6 +11,7 @@ on:
|
|||||||
- '**.nix'
|
- '**.nix'
|
||||||
- 'packages/**'
|
- 'packages/**'
|
||||||
- '.github/ISSUE_TEMPLATE/**'
|
- '.github/ISSUE_TEMPLATE/**'
|
||||||
|
- '.markdownlint**'
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
@ -19,6 +20,7 @@ on:
|
|||||||
- '**.nix'
|
- '**.nix'
|
||||||
- 'packages/**'
|
- 'packages/**'
|
||||||
- '.github/ISSUE_TEMPLATE/**'
|
- '.github/ISSUE_TEMPLATE/**'
|
||||||
|
- '.markdownlint**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
54
.github/workflows/trigger_release.yml
vendored
54
.github/workflows/trigger_release.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
path: 'PolyMC-source'
|
path: 'PrismLauncher-source'
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
- name: Grab and store version
|
- name: Grab and store version
|
||||||
@ -34,25 +34,26 @@ jobs:
|
|||||||
echo "VERSION=$tag_name" >> $GITHUB_ENV
|
echo "VERSION=$tag_name" >> $GITHUB_ENV
|
||||||
- name: Package artifacts properly
|
- name: Package artifacts properly
|
||||||
run: |
|
run: |
|
||||||
mv ${{ github.workspace }}/PolyMC-source PolyMC-${{ env.VERSION }}
|
mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }}
|
||||||
mv PolyMC-Linux-Qt6-Portable*/PolyMC-portable.tar.gz PolyMC-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||||
mv PolyMC-Linux-Qt6*/PolyMC.tar.gz PolyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||||
mv PolyMC-Linux-Portable*/PolyMC-portable.tar.gz PolyMC-Linux-Portable-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-Linux-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
|
||||||
mv PolyMC-Linux*/PolyMC.tar.gz PolyMC-Linux-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-Linux*/PrismLauncher.tar.gz PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
|
||||||
mv PolyMC-*.AppImage/PolyMC-*.AppImage PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
|
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage
|
||||||
mv PolyMC-macOS*/PolyMC.tar.gz PolyMC-macOS-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
||||||
|
mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
|
||||||
|
|
||||||
tar -czf PolyMC-${{ env.VERSION }}.tar.gz PolyMC-${{ env.VERSION }}
|
tar -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
|
||||||
|
|
||||||
for d in PolyMC-Windows-*; do
|
for d in PrismLauncher-Windows-*; do
|
||||||
cd "${d}" || continue
|
cd "${d}" || continue
|
||||||
LEGACY="$(echo -n ${d} | grep -o Legacy || true)"
|
LEGACY="$(echo -n ${d} | grep -o Legacy || true)"
|
||||||
INST="$(echo -n ${d} | grep -o Setup || true)"
|
INST="$(echo -n ${d} | grep -o Setup || true)"
|
||||||
PORT="$(echo -n ${d} | grep -o Portable || true)"
|
PORT="$(echo -n ${d} | grep -o Portable || true)"
|
||||||
NAME="PolyMC-Windows"
|
NAME="PrismLauncher-Windows"
|
||||||
test -z "${LEGACY}" || NAME="${NAME}-Legacy"
|
test -z "${LEGACY}" || NAME="${NAME}-Legacy"
|
||||||
test -z "${PORT}" || NAME="${NAME}-Portable"
|
test -z "${PORT}" || NAME="${NAME}-Portable"
|
||||||
test -z "${INST}" || mv PolyMC-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
|
test -z "${INST}" || mv PrismLauncher-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
|
||||||
test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
|
test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
|
||||||
cd ..
|
cd ..
|
||||||
done
|
done
|
||||||
@ -64,20 +65,21 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ github.ref }}
|
tag_name: ${{ github.ref }}
|
||||||
name: PolyMC ${{ env.VERSION }}
|
name: Prism Launcher ${{ env.VERSION }}
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: false
|
prerelease: false
|
||||||
files: |
|
files: |
|
||||||
PolyMC-Linux-${{ env.VERSION }}.tar.gz
|
PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
|
||||||
PolyMC-Linux-Portable-${{ env.VERSION }}.tar.gz
|
PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
|
||||||
PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
|
PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage
|
||||||
PolyMC-Windows-Legacy-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-Legacy-${{ env.VERSION }}.zip
|
||||||
PolyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||||
PolyMC-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||||
PolyMC-Windows-Legacy-Portable-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-Legacy-Portable-${{ env.VERSION }}.zip
|
||||||
PolyMC-Windows-Legacy-Setup-${{ env.VERSION }}.exe
|
PrismLauncher-Windows-Legacy-Setup-${{ env.VERSION }}.exe
|
||||||
PolyMC-Windows-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-${{ env.VERSION }}.zip
|
||||||
PolyMC-Windows-Portable-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-Portable-${{ env.VERSION }}.zip
|
||||||
PolyMC-Windows-Setup-${{ env.VERSION }}.exe
|
PrismLauncher-Windows-Setup-${{ env.VERSION }}.exe
|
||||||
PolyMC-macOS-${{ env.VERSION }}.tar.gz
|
PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
|
||||||
PolyMC-${{ env.VERSION }}.tar.gz
|
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
||||||
|
PrismLauncher-${{ env.VERSION }}.tar.gz
|
||||||
|
7
.github/workflows/winget.yml
vendored
7
.github/workflows/winget.yml
vendored
@ -7,8 +7,9 @@ jobs:
|
|||||||
publish:
|
publish:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: vedantmgoyal2009/winget-releaser@latest
|
- uses: vedantmgoyal2009/winget-releaser@v1
|
||||||
with:
|
with:
|
||||||
identifier: PolyMC.PolyMC
|
identifier: PrismLauncher.PrismLauncher
|
||||||
installers-regex: '\.exe$'
|
version: ${{ github.event.release.tag_name }}
|
||||||
|
installers-regex: 'PrismLauncher-Windows-Setup-.+\.exe$'
|
||||||
token: ${{ secrets.WINGET_TOKEN }}
|
token: ${{ secrets.WINGET_TOKEN }}
|
||||||
|
14
.gitmodules
vendored
14
.gitmodules
vendored
@ -1,8 +1,12 @@
|
|||||||
[submodule "depends/libnbtplusplus"]
|
|
||||||
path = libraries/libnbtplusplus
|
|
||||||
url = https://github.com/PolyMC/libnbtplusplus.git
|
|
||||||
pushurl = git@github.com:PolyMC/libnbtplusplus.git
|
|
||||||
|
|
||||||
[submodule "libraries/quazip"]
|
[submodule "libraries/quazip"]
|
||||||
path = libraries/quazip
|
path = libraries/quazip
|
||||||
url = https://github.com/stachenov/quazip.git
|
url = https://github.com/stachenov/quazip.git
|
||||||
|
[submodule "libraries/tomlplusplus"]
|
||||||
|
path = libraries/tomlplusplus
|
||||||
|
url = https://github.com/marzer/tomlplusplus.git
|
||||||
|
[submodule "libraries/filesystem"]
|
||||||
|
path = libraries/filesystem
|
||||||
|
url = https://github.com/gulrak/filesystem
|
||||||
|
[submodule "libraries/libnbtplusplus"]
|
||||||
|
path = libraries/libnbtplusplus
|
||||||
|
url = https://github.com/PrismLauncher/libnbtplusplus.git
|
||||||
|
12
.markdownlint.yaml
Normal file
12
.markdownlint.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# MD013/line-length - Line length
|
||||||
|
MD013: false
|
||||||
|
|
||||||
|
# MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content
|
||||||
|
MD024:
|
||||||
|
siblings-only: true
|
||||||
|
|
||||||
|
# MD033/no-inline-html Inline HTML
|
||||||
|
MD033: false
|
||||||
|
|
||||||
|
# MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading
|
||||||
|
MD041: false
|
2
.markdownlintignore
Normal file
2
.markdownlintignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
libraries/nbtplusplus
|
||||||
|
libraries/quazip
|
52
BUILD.md
52
BUILD.md
@ -1,5 +1,53 @@
|
|||||||
# Build Instructions
|
# Build Instructions
|
||||||
|
|
||||||
Build instructions are available on [the website](https://polymc.org/wiki/development/build-instructions/).
|
Full build instructions will be available on [the website](https://prismlauncher.org/wiki/development/build-instructions/).
|
||||||
|
|
||||||
|
If you would like to contribute or fix an issue with the Build instructions you will be able to do so [here](https://github.com/PrismLauncher/website/blob/master/src/wiki/development/build-instructions.md).
|
||||||
|
|
||||||
|
## Getting the source
|
||||||
|
|
||||||
|
Clone the source code using git, and grab all the submodules. This is generic for all platforms you want to build on.
|
||||||
|
```
|
||||||
|
git clone --recursive https://github.com/PrismLauncher/PrismLauncher
|
||||||
|
cd PrismLauncher
|
||||||
|
```
|
||||||
|
|
||||||
|
## Linux
|
||||||
|
|
||||||
|
This guide will mostly mention dependant packages by their Debian naming and commands are done by a user in the sudoers file.
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- A C++ compiler capable of building C++17 code (can be found in the package `build-essential`).
|
||||||
|
- Qt Development tools 5.12 or newer (on Debian 11 or Debian-based distributions, `qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5`).
|
||||||
|
- `cmake` 3.15 or newer.
|
||||||
|
- `extra-cmake-modules`.
|
||||||
|
- zlib (`zlib1g-dev` on Debian 11 or Debian-based distributions).
|
||||||
|
- Java Development Kit (Java JDK) (`openjdk-17-jdk` on Debian 11 or Debian-based distributions).
|
||||||
|
- Mesa GL headers (`libgl1-mesa-dev` on Debian 11 or Debian-based distributions).
|
||||||
|
- (Optional) `scdoc` to generate man pages.
|
||||||
|
|
||||||
|
In conclusion, to check if all you need is installed (including optional):
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo apt install build-essential qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 cmake extra-cmake-modules zlib1g-dev openjdk-17-jdk libgl1-mesa-dev scdoc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiling
|
||||||
|
#### Building and installing on the system
|
||||||
|
This is usually the suggested way to build the client.
|
||||||
|
|
||||||
|
```
|
||||||
|
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="/usr" -DENABLE_LTO=ON
|
||||||
|
cmake --build build -j$(nproc)
|
||||||
|
sudo cmake --install build
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Building a portable binary
|
||||||
|
|
||||||
|
```
|
||||||
|
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=install
|
||||||
|
cmake --build build -j$(nproc)
|
||||||
|
cmake --install build
|
||||||
|
cmake --install build --component portable
|
||||||
|
```
|
||||||
|
|
||||||
If you would like to contribute or fix an issue with the Build instructions you can do so [here](https://github.com/PolyMC/polymc.github.io/blob/master/src/wiki/development/build-instructions.md).
|
|
||||||
|
102
CMakeLists.txt
102
CMakeLists.txt
@ -29,13 +29,10 @@ set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/jars)
|
|||||||
######## Set compiler flags ########
|
######## Set compiler flags ########
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED true)
|
set(CMAKE_CXX_STANDARD_REQUIRED true)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED true)
|
set(CMAKE_C_STANDARD_REQUIRED true)
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
include(GenerateExportHeader)
|
include(GenerateExportHeader)
|
||||||
set(CMAKE_CXX_FLAGS "-Wall -pedantic -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
|
set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
|
||||||
if(UNIX AND APPLE)
|
|
||||||
set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Fix build with Qt 5.13
|
# Fix build with Qt 5.13
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
|
||||||
@ -67,24 +64,24 @@ find_package(ECM REQUIRED NO_MODULE)
|
|||||||
set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}")
|
set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}")
|
||||||
include(CTest)
|
include(CTest)
|
||||||
include(ECMAddTests)
|
include(ECMAddTests)
|
||||||
if (BUILD_TESTING)
|
if(BUILD_TESTING)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
##################################### Set Application options #####################################
|
##################################### Set Application options #####################################
|
||||||
|
|
||||||
######## Set URLs ########
|
######## Set URLs ########
|
||||||
set(Launcher_NEWS_RSS_URL "https://polymc.org/feed/feed.xml" CACHE STRING "URL to fetch PolyMC's news RSS feed from.")
|
set(Launcher_NEWS_RSS_URL "https://prismlauncher.org/feed/feed.xml" CACHE STRING "URL to fetch Prism Launcher's news RSS feed from.")
|
||||||
set(Launcher_NEWS_OPEN_URL "https://polymc.org/news" CACHE STRING "URL that gets opened when the user clicks 'More News'")
|
set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL that gets opened when the user clicks 'More News'")
|
||||||
set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
|
set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
|
||||||
|
|
||||||
######## Set version numbers ########
|
######## Set version numbers ########
|
||||||
set(Launcher_VERSION_MAJOR 1)
|
set(Launcher_VERSION_MAJOR 5)
|
||||||
set(Launcher_VERSION_MINOR 4)
|
set(Launcher_VERSION_MINOR 1)
|
||||||
set(Launcher_VERSION_HOTFIX 0)
|
|
||||||
|
|
||||||
# Build number
|
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
|
||||||
set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
|
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0")
|
||||||
|
set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},0,0")
|
||||||
|
|
||||||
# Build platform.
|
# Build platform.
|
||||||
set(Launcher_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
|
set(Launcher_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
|
||||||
@ -93,25 +90,25 @@ set(Launcher_BUILD_PLATFORM "" CACHE STRING "A short string identifying the plat
|
|||||||
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
|
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
|
||||||
|
|
||||||
# The metadata server
|
# The metadata server
|
||||||
set(Launcher_META_URL "https://meta.polymc.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.")
|
set(Launcher_META_URL "https://meta.prismlauncher.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.")
|
||||||
|
|
||||||
# Imgur API Client ID
|
# Imgur API Client ID
|
||||||
set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application")
|
set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application")
|
||||||
|
|
||||||
# Bug tracker URL
|
# Bug tracker URL
|
||||||
set(Launcher_BUG_TRACKER_URL "https://github.com/PolyMC/PolyMC/issues" CACHE STRING "URL for the bug tracker.")
|
set(Launcher_BUG_TRACKER_URL "https://github.com/PrismLauncher/PrismLauncher/issues" CACHE STRING "URL for the bug tracker.")
|
||||||
|
|
||||||
# Translations Platform URL
|
# Translations Platform URL
|
||||||
set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/polymc/polymc/" CACHE STRING "URL for the translations platform.")
|
set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/prismlauncher/launcher/" CACHE STRING "URL for the translations platform.")
|
||||||
|
|
||||||
# Matrix Space
|
# Matrix Space
|
||||||
set(Launcher_MATRIX_URL "https://matrix.to/#/#polymc:matrix.org" CACHE STRING "URL to the Matrix Space")
|
set(Launcher_MATRIX_URL "https://matrix.to/#/#prismlauncher:matrix.org" CACHE STRING "URL to the Matrix Space")
|
||||||
|
|
||||||
# Discord URL
|
# Discord URL
|
||||||
set(Launcher_DISCORD_URL "https://discord.gg/Z52pwxWCHP" CACHE STRING "URL for the Discord guild.")
|
set(Launcher_DISCORD_URL "https://discord.gg/prismlauncher" CACHE STRING "URL for the Discord guild.")
|
||||||
|
|
||||||
# Subreddit URL
|
# Subreddit URL
|
||||||
set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PolyMCLauncher/" CACHE STRING "URL for the subreddit.")
|
set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PrismLauncher/" CACHE STRING "URL for the subreddit.")
|
||||||
|
|
||||||
# Builds
|
# Builds
|
||||||
set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
|
set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
|
||||||
@ -126,12 +123,13 @@ set(Launcher_QT_VERSION_MAJOR "5" CACHE STRING "Major Qt version to build agains
|
|||||||
|
|
||||||
# By using this key in your builds you accept the terms of use laid down in
|
# By using this key in your builds you accept the terms of use laid down in
|
||||||
# https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use
|
# https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use
|
||||||
set(Launcher_MSA_CLIENT_ID "549033b2-1532-4d4e-ae77-1bbaa46f9d74" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application")
|
set(Launcher_MSA_CLIENT_ID "c36a9fb6-4f2a-41ff-90bd-ae7cc92031eb" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application")
|
||||||
|
|
||||||
# By using this key in your builds you accept the terms and conditions laid down in
|
# By using this key in your builds you accept the terms and conditions laid down in
|
||||||
# https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions
|
# https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions
|
||||||
# NOTE: CurseForge requires you to change this if you make any kind of derivative work.
|
# NOTE: CurseForge requires you to change this if you make any kind of derivative work.
|
||||||
set(Launcher_CURSEFORGE_API_KEY "$2a$10$1Oqr2MX3O4n/ilhFGc597u8tfI3L2Hyr9/rtWDAMRjghSQV2QUuxq" CACHE STRING "API key for the CurseForge platform")
|
# This key was issued specifically for Prism Launcher
|
||||||
|
set(Launcher_CURSEFORGE_API_KEY "$2a$10$wuAJuNZuted3NORVmpgUC.m8sI.pv1tOPKZyBgLFGjxFp/br0lZCC" CACHE STRING "API key for the CurseForge platform")
|
||||||
|
|
||||||
|
|
||||||
#### Check the current Git commit and branch
|
#### Check the current Git commit and branch
|
||||||
@ -143,15 +141,8 @@ message(STATUS "Git commit: ${Launcher_GIT_COMMIT}")
|
|||||||
message(STATUS "Git tag: ${Launcher_GIT_TAG}")
|
message(STATUS "Git tag: ${Launcher_GIT_TAG}")
|
||||||
message(STATUS "Git refspec: ${Launcher_GIT_REFSPEC}")
|
message(STATUS "Git refspec: ${Launcher_GIT_REFSPEC}")
|
||||||
|
|
||||||
set(Launcher_RELEASE_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}")
|
|
||||||
set(Launcher_RELEASE_VERSION_NAME4 "${Launcher_RELEASE_VERSION_NAME}.0")
|
|
||||||
set(Launcher_RELEASE_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},${Launcher_VERSION_HOTFIX},0")
|
|
||||||
string(TIMESTAMP TODAY "%Y-%m-%d")
|
string(TIMESTAMP TODAY "%Y-%m-%d")
|
||||||
set(Launcher_RELEASE_TIMESTAMP "${TODAY}")
|
set(Launcher_BUILD_TIMESTAMP "${TODAY}")
|
||||||
|
|
||||||
#### Custom target to just print the version.
|
|
||||||
add_custom_target(version echo "Version: ${Launcher_RELEASE_VERSION_NAME}")
|
|
||||||
add_custom_target(tcversion echo "\\#\\#teamcity[setParameter name=\\'env.LAUNCHER_VERSION\\' value=\\'${Launcher_RELEASE_VERSION_NAME}\\']")
|
|
||||||
|
|
||||||
################################ 3rd Party Libs ################################
|
################################ 3rd Party Libs ################################
|
||||||
|
|
||||||
@ -199,9 +190,17 @@ if (Qt5_POSITION_INDEPENDENT_CODE)
|
|||||||
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||||
|
# Find toml++
|
||||||
|
find_package(tomlplusplus 3.2.0 QUIET)
|
||||||
|
|
||||||
|
# Find ghc_filesystem
|
||||||
|
find_package(ghc_filesystem QUIET)
|
||||||
|
endif()
|
||||||
|
|
||||||
####################################### Program Info #######################################
|
####################################### Program Info #######################################
|
||||||
|
|
||||||
set(Launcher_APP_BINARY_NAME "polymc" CACHE STRING "Name of the Launcher binary")
|
set(Launcher_APP_BINARY_NAME "prismlauncher" CACHE STRING "Name of the Launcher binary")
|
||||||
add_subdirectory(program_info)
|
add_subdirectory(program_info)
|
||||||
|
|
||||||
####################################### Install layout #######################################
|
####################################### Install layout #######################################
|
||||||
@ -223,16 +222,16 @@ if(UNIX AND APPLE)
|
|||||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app")
|
set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app")
|
||||||
|
|
||||||
# Mac bundle settings
|
# Mac bundle settings
|
||||||
set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_Name}")
|
set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_DisplayName}")
|
||||||
set(MACOSX_BUNDLE_INFO_STRING "${Launcher_Name}: A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.")
|
set(MACOSX_BUNDLE_INFO_STRING "${Launcher_DisplayName}: A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.")
|
||||||
set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.polymc.${Launcher_Name}")
|
set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.prismlauncher.${Launcher_Name}")
|
||||||
set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}")
|
set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_NAME}")
|
||||||
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}")
|
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
|
||||||
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}")
|
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
|
||||||
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
|
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
|
||||||
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2021-2022 ${Launcher_Copyright}")
|
set(MACOSX_BUNDLE_COPYRIGHT "© 2022 ${Launcher_Copyright_Mac}")
|
||||||
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "idALcUIazingvKSSsEa9U7coDVxZVx/ORpOEE/QtJfg=")
|
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=")
|
||||||
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://polymc.org/feed/appcast.xml")
|
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml")
|
||||||
|
|
||||||
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.1.0/Sparkle-2.1.0.tar.xz" CACHE STRING "URL to Sparkle release archive")
|
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.1.0/Sparkle-2.1.0.tar.xz" CACHE STRING "URL to Sparkle release archive")
|
||||||
set(MACOSX_SPARKLE_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive")
|
set(MACOSX_SPARKLE_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive")
|
||||||
@ -250,7 +249,7 @@ if(UNIX AND APPLE)
|
|||||||
elseif(UNIX)
|
elseif(UNIX)
|
||||||
set(BINARY_DEST_DIR "bin")
|
set(BINARY_DEST_DIR "bin")
|
||||||
set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}")
|
set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}")
|
||||||
set(JARS_DEST_DIR "share/jars")
|
set(JARS_DEST_DIR "share/${Launcher_APP_BINARY_NAME}")
|
||||||
set(LAUNCHER_DESKTOP_DEST_DIR "share/applications" CACHE STRING "Path to the desktop file directory")
|
set(LAUNCHER_DESKTOP_DEST_DIR "share/applications" CACHE STRING "Path to the desktop file directory")
|
||||||
set(LAUNCHER_METAINFO_DEST_DIR "share/metainfo" CACHE STRING "Path to the metainfo directory")
|
set(LAUNCHER_METAINFO_DEST_DIR "share/metainfo" CACHE STRING "Path to the metainfo directory")
|
||||||
set(LAUNCHER_ICON_DEST_DIR "share/icons/hicolor/scalable/apps" CACHE STRING "Path to the scalable icon directory")
|
set(LAUNCHER_ICON_DEST_DIR "share/icons/hicolor/scalable/apps" CACHE STRING "Path to the scalable icon directory")
|
||||||
@ -307,7 +306,6 @@ add_subdirectory(libraries/systeminfo) # system information library
|
|||||||
add_subdirectory(libraries/hoedown) # markdown parser
|
add_subdirectory(libraries/hoedown) # markdown parser
|
||||||
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
|
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
|
||||||
add_subdirectory(libraries/javacheck) # java compatibility checker
|
add_subdirectory(libraries/javacheck) # java compatibility checker
|
||||||
add_subdirectory(libraries/xz-embedded) # xz compression
|
|
||||||
if (FORCE_BUNDLED_QUAZIP)
|
if (FORCE_BUNDLED_QUAZIP)
|
||||||
message(STATUS "Using bundled QuaZip")
|
message(STATUS "Using bundled QuaZip")
|
||||||
set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts.
|
set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts.
|
||||||
@ -318,16 +316,30 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
add_subdirectory(libraries/rainbow) # Qt extension for colors
|
add_subdirectory(libraries/rainbow) # Qt extension for colors
|
||||||
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
|
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
|
||||||
add_subdirectory(libraries/classparser) # class parser library
|
if(NOT tomlplusplus_FOUND)
|
||||||
add_subdirectory(libraries/optional-bare)
|
message(STATUS "Using bundled tomlplusplus")
|
||||||
add_subdirectory(libraries/tomlc99) # toml parser
|
add_subdirectory(libraries/tomlplusplus) # toml parser
|
||||||
|
else()
|
||||||
|
message(STATUS "Using system tomlplusplus")
|
||||||
|
endif()
|
||||||
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
|
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
|
||||||
add_subdirectory(libraries/gamemode)
|
add_subdirectory(libraries/gamemode)
|
||||||
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
|
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
|
||||||
|
if (NOT ghc_filesystem_FOUND)
|
||||||
|
message(STATUS "Using bundled ghc_filesystem")
|
||||||
|
set(GHC_FILESYSTEM_WITH_INSTALL OFF) # Workaround ghc::filesystem bug
|
||||||
|
add_subdirectory(libraries/filesystem) # Implementation of std::filesystem for old C++, for usage in old macOS
|
||||||
|
add_library(ghcFilesystem::ghc_filesystem ALIAS ghc_filesystem)
|
||||||
|
else()
|
||||||
|
message(STATUS "Using system ghc_filesystem")
|
||||||
|
endif()
|
||||||
|
|
||||||
############################### Built Artifacts ###############################
|
############################### Built Artifacts ###############################
|
||||||
|
|
||||||
add_subdirectory(buildconfig)
|
add_subdirectory(buildconfig)
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
add_subdirectory(tests)
|
||||||
|
endif()
|
||||||
# NOTE: this must always be last to appease the CMake deity of quirky install command evaluation order.
|
# NOTE: this must always be last to appease the CMake deity of quirky install command evaluation order.
|
||||||
add_subdirectory(launcher)
|
add_subdirectory(launcher)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
This is a modified version of the Contributor Covenant.
|
This is a modified version of the Contributor Covenant.
|
||||||
See commit history to see our changes.
|
See commit history to see our changes.
|
||||||
|
|
||||||
@ -62,7 +63,7 @@ representative at an online or offline event.
|
|||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community leaders responsible for enforcement via email at
|
reported to the community leaders responsible for enforcement via email at
|
||||||
[polymc-enforcement@scrumplex.net](mailto:polymc-enforcement@scrumplex.net) (Email
|
[coc@scrumplex.net](mailto:coc@scrumplex.net) (Email
|
||||||
address subject to change).
|
address subject to change).
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
@ -133,4 +134,3 @@ For answers to common questions about this code of conduct, see the FAQ at
|
|||||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
[FAQ]: https://www.contributor-covenant.org/faq
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
[translations]: https://www.contributor-covenant.org/translations
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ Try to follow the existing formatting.
|
|||||||
If there is no existing formatting, you may use `clang-format` with our included `.clang-format` configuration.
|
If there is no existing formatting, you may use `clang-format` with our included `.clang-format` configuration.
|
||||||
|
|
||||||
In general, in order of importance:
|
In general, in order of importance:
|
||||||
|
|
||||||
- Make sure your IDE is not messing up line endings or whitespace and avoid using linters.
|
- Make sure your IDE is not messing up line endings or whitespace and avoid using linters.
|
||||||
- Prefer readability over dogma.
|
- Prefer readability over dogma.
|
||||||
- Keep to the existing formatting.
|
- Keep to the existing formatting.
|
||||||
@ -26,37 +27,37 @@ Signed-off-by: Author name <Author email>
|
|||||||
|
|
||||||
By signing off your work, you agree to the terms below:
|
By signing off your work, you agree to the terms below:
|
||||||
|
|
||||||
Developer's Certificate of Origin 1.1
|
```
|
||||||
|
Developer's Certificate of Origin 1.1
|
||||||
|
|
||||||
By making a contribution to this project, I certify that:
|
By making a contribution to this project, I certify that:
|
||||||
|
|
||||||
(a) The contribution was created in whole or in part by me and I
|
(a) The contribution was created in whole or in part by me and I
|
||||||
have the right to submit it under the open source license
|
have the right to submit it under the open source license
|
||||||
indicated in the file; or
|
indicated in the file; or
|
||||||
|
|
||||||
(b) The contribution is based upon previous work that, to the best
|
(b) The contribution is based upon previous work that, to the best
|
||||||
of my knowledge, is covered under an appropriate open source
|
of my knowledge, is covered under an appropriate open source
|
||||||
license and I have the right under that license to submit that
|
license and I have the right under that license to submit that
|
||||||
work with modifications, whether created in whole or in part
|
work with modifications, whether created in whole or in part
|
||||||
by me, under the same open source license (unless I am
|
by me, under the same open source license (unless I am
|
||||||
permitted to submit under a different license), as indicated
|
permitted to submit under a different license), as indicated
|
||||||
in the file; or
|
in the file; or
|
||||||
|
|
||||||
(c) The contribution was provided directly to me by some other
|
(c) The contribution was provided directly to me by some other
|
||||||
person who certified (a), (b) or (c) and I have not modified
|
person who certified (a), (b) or (c) and I have not modified
|
||||||
it.
|
it.
|
||||||
|
|
||||||
(d) I understand and agree that this project and the contribution
|
(d) I understand and agree that this project and the contribution
|
||||||
are public and that a record of the contribution (including all
|
are public and that a record of the contribution (including all
|
||||||
personal information I submit with it, including my sign-off) is
|
personal information I submit with it, including my sign-off) is
|
||||||
maintained indefinitely and may be redistributed consistent with
|
maintained indefinitely and may be redistributed consistent with
|
||||||
this project or the open source license(s) involved.
|
this project or the open source license(s) involved.
|
||||||
|
```
|
||||||
|
|
||||||
These terms will be enforced once you create a pull request, and you will be informed automatically if any of your commits aren't signed-off by you.
|
These terms will be enforced once you create a pull request, and you will be informed automatically if any of your commits aren't signed-off by you.
|
||||||
|
|
||||||
As a bonus, you can also [cryptographically sign your commits][gh-signing-commits] and enable [vigilant mode][gh-vigilant-mode] on GitHub.
|
As a bonus, you can also [cryptographically sign your commits][gh-signing-commits] and enable [vigilant mode][gh-vigilant-mode] on GitHub.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[gh-signing-commits]: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits
|
[gh-signing-commits]: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits
|
||||||
[gh-vigilant-mode]: https://docs.github.com/en/authentication/managing-commit-signature-verification/displaying-verification-statuses-for-all-of-your-commits
|
[gh-vigilant-mode]: https://docs.github.com/en/authentication/managing-commit-signature-verification/displaying-verification-statuses-for-all-of-your-commits
|
||||||
|
265
COPYING.md
265
COPYING.md
@ -1,4 +1,38 @@
|
|||||||
# PolyMC
|
## Prism Launcher
|
||||||
|
|
||||||
|
Prism Launcher - Minecraft Launcher
|
||||||
|
Copyright (C) 2022 Prism Launcher Contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
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.
|
||||||
|
|
||||||
|
## PolyMC
|
||||||
|
|
||||||
PolyMC - Minecraft Launcher
|
PolyMC - Minecraft Launcher
|
||||||
Copyright (C) 2021-2022 PolyMC Contributors
|
Copyright (C) 2021-2022 PolyMC Contributors
|
||||||
@ -32,36 +66,56 @@
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
# MinGW runtime (Windows)
|
## MinGW-w64 runtime (Windows)
|
||||||
|
|
||||||
Copyright (c) 2012 MinGW.org project
|
Copyright (c) 2009, 2010, 2011, 2012, 2013 by the mingw-w64 project
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
This license has been certified as open source. It has also been designated
|
||||||
copy of this software and associated documentation files (the "Software"),
|
as GPL compatible by the Free Software Foundation (FSF).
|
||||||
to deal in the Software without restriction, including without limitation
|
|
||||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice, this permission notice and the below disclaimer
|
Redistribution and use in source and binary forms, with or without
|
||||||
shall be included in all copies or substantial portions of the Software.
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
1. Redistributions in source code must retain the accompanying copyright
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
notice, this list of conditions, and the following disclaimer.
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
2. Redistributions in binary form must reproduce the accompanying
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
copyright notice, this list of conditions, and the following disclaimer
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
in the documentation and/or other materials provided with the
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
distribution.
|
||||||
DEALINGS IN THE SOFTWARE.
|
3. Names of the copyright holders must not be used to endorse or promote
|
||||||
|
products derived from this software without prior written permission
|
||||||
|
from the copyright holders.
|
||||||
|
4. The right to distribute this software or to use it for any purpose does
|
||||||
|
not give you the right to use Servicemarks (sm) or Trademarks (tm) of
|
||||||
|
the copyright holders. Use of them is covered by separate agreement
|
||||||
|
with the copyright holders.
|
||||||
|
5. If any files are modified, you must cause the modified files to carry
|
||||||
|
prominent notices stating that you changed the files and the date of
|
||||||
|
any change.
|
||||||
|
|
||||||
# Qt 5/6
|
Disclaimer
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED
|
||||||
|
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||||
|
EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||||
|
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||||
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
Information on third party licenses used in MinGW-w64 can be found in its COPYING.MinGW-w64-runtime.txt.
|
||||||
|
|
||||||
|
## Qt 5/6
|
||||||
|
|
||||||
Copyright (C) 2022 The Qt Company Ltd and other contributors.
|
Copyright (C) 2022 The Qt Company Ltd and other contributors.
|
||||||
Contact: https://www.qt.io/licensing
|
Contact: https://www.qt.io/licensing
|
||||||
|
|
||||||
Licensed under LGPL v3
|
Licensed under LGPL v3
|
||||||
|
|
||||||
# libnbt++
|
## libnbt++
|
||||||
|
|
||||||
libnbt++ - A library for the Minecraft Named Binary Tag format.
|
libnbt++ - A library for the Minecraft Named Binary Tag format.
|
||||||
Copyright (C) 2013, 2015 ljfa-ag
|
Copyright (C) 2013, 2015 ljfa-ag
|
||||||
@ -79,7 +133,7 @@
|
|||||||
You should have received a copy of the GNU Lesser General Public License
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
along with libnbt++. If not, see <http://www.gnu.org/licenses/>.
|
along with libnbt++. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# rainbow (KGuiAddons)
|
## rainbow (KGuiAddons)
|
||||||
|
|
||||||
Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
|
Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
|
||||||
Copyright (C) 2007 Olaf Schmidt <ojschmidt@kde.org>
|
Copyright (C) 2007 Olaf Schmidt <ojschmidt@kde.org>
|
||||||
@ -102,7 +156,7 @@
|
|||||||
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
Boston, MA 02110-1301, USA.
|
Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
# Hoedown
|
## Hoedown
|
||||||
|
|
||||||
Copyright (c) 2008, Natacha Porté
|
Copyright (c) 2008, Natacha Porté
|
||||||
Copyright (c) 2011, Vicent Martí
|
Copyright (c) 2011, Vicent Martí
|
||||||
@ -120,7 +174,7 @@
|
|||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
# Batch icon set
|
## Batch icon set
|
||||||
|
|
||||||
You are free to use Batch (the "icon set") or any part thereof (the "icons")
|
You are free to use Batch (the "icon set") or any part thereof (the "icons")
|
||||||
in any personal, open-source or commercial work without obligation of payment
|
in any personal, open-source or commercial work without obligation of payment
|
||||||
@ -136,7 +190,7 @@
|
|||||||
PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THE USE OF THE ICONS,
|
PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THE USE OF THE ICONS,
|
||||||
EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
# Material Design Icons
|
## Material Design Icons
|
||||||
|
|
||||||
Copyright (c) 2014, Austin Andrews (http://materialdesignicons.com/),
|
Copyright (c) 2014, Austin Andrews (http://materialdesignicons.com/),
|
||||||
with Reserved Font Name Material Design Icons.
|
with Reserved Font Name Material Design Icons.
|
||||||
@ -147,7 +201,7 @@
|
|||||||
This license is copied below, and is also available with a FAQ at:
|
This license is copied below, and is also available with a FAQ at:
|
||||||
http://scripts.sil.org/OFL
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
# Quazip
|
## Quazip
|
||||||
|
|
||||||
Copyright (C) 2005-2021 Sergey A. Tachenov
|
Copyright (C) 2005-2021 Sergey A. Tachenov
|
||||||
|
|
||||||
@ -171,53 +225,7 @@
|
|||||||
|
|
||||||
See COPYING file for the full LGPL text.
|
See COPYING file for the full LGPL text.
|
||||||
|
|
||||||
# xz-minidec
|
## launcher (`libraries/launcher`)
|
||||||
|
|
||||||
XZ decompressor
|
|
||||||
|
|
||||||
Authors: Lasse Collin <lasse.collin@tukaani.org>
|
|
||||||
Igor Pavlov <http://7-zip.org/>
|
|
||||||
|
|
||||||
This file has been put into the public domain.
|
|
||||||
You can do whatever you want with this file.
|
|
||||||
|
|
||||||
# ColumnResizer
|
|
||||||
|
|
||||||
Copyright (c) 2011-2016 Aurélien Gâteau and contributors.
|
|
||||||
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted (subject to the limitations in the
|
|
||||||
disclaimer below) provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
|
|
||||||
* The name of the contributors may not be used to endorse or
|
|
||||||
promote products derived from this software without specific prior
|
|
||||||
written permission.
|
|
||||||
|
|
||||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
|
|
||||||
GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
|
|
||||||
HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
|
||||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
||||||
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
||||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
||||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
|
||||||
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
# launcher (`libraries/launcher`)
|
|
||||||
|
|
||||||
PolyMC - Minecraft Launcher
|
PolyMC - Minecraft Launcher
|
||||||
Copyright (C) 2021-2022 PolyMC Contributors
|
Copyright (C) 2021-2022 PolyMC Contributors
|
||||||
@ -268,7 +276,7 @@
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
# lionshead
|
## lionshead
|
||||||
|
|
||||||
Code has been taken from https://github.com/natefoo/lionshead and loosely
|
Code has been taken from https://github.com/natefoo/lionshead and loosely
|
||||||
translated to C++ laced with Qt.
|
translated to C++ laced with Qt.
|
||||||
@ -295,60 +303,26 @@
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
# optional-bare
|
## tomlplusplus
|
||||||
|
|
||||||
Code from https://github.com/martinmoene/optional-bare/
|
|
||||||
|
|
||||||
Boost Software License - Version 1.0 - August 17th, 2003
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
obtaining a copy of the software and accompanying documentation covered by
|
|
||||||
this license (the "Software") to use, reproduce, display, distribute,
|
|
||||||
execute, and transmit the Software, and to prepare derivative works of the
|
|
||||||
Software, and to permit third-parties to whom the Software is furnished to
|
|
||||||
do so, all subject to the following:
|
|
||||||
|
|
||||||
The copyright notices in the Software and this entire statement, including
|
|
||||||
the above license grant, this restriction and the following disclaimer,
|
|
||||||
must be included in all copies of the Software, in whole or in part, and
|
|
||||||
all derivative works of the Software, unless such copies or derivative
|
|
||||||
works are solely in the form of machine-executable object code generated by
|
|
||||||
a source language processor.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
|
||||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
|
||||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
|
||||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
# tomlc99
|
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2017 CK Tan
|
Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
|
||||||
https://github.com/cktan/tomlc99
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
||||||
in the Software without restriction, including without limitation the rights
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
||||||
copies or substantial portions of the Software.
|
Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
# O2 (Katabasis fork)
|
## O2 (Katabasis fork)
|
||||||
|
|
||||||
Copyright (c) 2012, Akos Polster
|
Copyright (c) 2012, Akos Polster
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
@ -373,3 +347,54 @@
|
|||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
## Gamemode
|
||||||
|
|
||||||
|
Copyright (c) 2017-2022, Feral Interactive
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of Feral Interactive nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
## gulrak/filesystem
|
||||||
|
|
||||||
|
Copyright (c) 2018, Steffen Schümann <s.schuemann@pobox.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
95
README.md
95
README.md
@ -1,98 +1,29 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="./program_info/polymc-header-black.svg#gh-light-mode-only" alt="PolyMC logo" width="50%"/>
|
<img src="./program_info/org.prismlauncher.PrismLauncher.logo.svg#gh-light-mode-only" alt="Prism Launcher logo" width="50%"/>
|
||||||
<img src="./program_info/polymc-header.svg#gh-dark-mode-only" alt="PolyMC logo" width="50%"/>
|
<img src="./program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg#gh-dark-mode-only" alt="Prism Launcher logo" width="50%"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity.
|
Prism Launcher is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.
|
||||||
|
|
||||||
This is a **fork** of the MultiMC Launcher and not endorsed by MultiMC.
|
We are working on a website and other media, for more info we have a [Discord server](https://discord.gg/prismlauncher).
|
||||||
If you want to read about why this fork was created, check out [our FAQ page](https://polymc.org/wiki/overview/faq/).
|
|
||||||
<br>
|
|
||||||
|
|
||||||
# Installation
|
## Installation
|
||||||
|
|
||||||
- All downloads and instructions for PolyMC can be found [here](https://polymc.org/download/)
|
- All downloads and instructions for Prism Launcher will soon be available.
|
||||||
- Last build status: https://github.com/PolyMC/PolyMC/actions
|
- Last build status can be found [here](https://github.com/PrismLauncher/PrismLauncher/actions).
|
||||||
|
|
||||||
|
### Development Builds
|
||||||
|
|
||||||
## Development Builds
|
There are development builds available [here](https://github.com/PrismLauncher/PrismLauncher/actions). These have debug information in the binaries, so their file sizes are relatively larger.
|
||||||
|
|
||||||
There are per-commit development builds available [here](https://github.com/PolyMC/PolyMC/actions). These have debug information in the binaries, so their file sizes are relatively larger.
|
|
||||||
Portable builds are provided for AppImage on Linux, Windows, and macOS.
|
Portable builds are provided for AppImage on Linux, Windows, and macOS.
|
||||||
|
|
||||||
For Debian and Arch, you can use these packages for the latest development versions:
|
## Help & Support
|
||||||
[](https://aur.archlinux.org/packages/polymc-git/)
|
|
||||||
[](https://mpr.makedeb.org/packages/polymc-git)
|
|
||||||
For flatpak, you can use [flathub-beta](https://discourse.flathub.org/t/how-to-use-flathub-beta/2111)
|
|
||||||
|
|
||||||
# Help & Support
|
|
||||||
|
|
||||||
Feel free to create an issue if you need help. However, you might find it easier to ask in the Discord server.
|
[](https://discord.gg/hX4g537UNE)
|
||||||
|
|
||||||
[](https://discord.gg/xq7fxrgtMP)
|
|
||||||
|
|
||||||
For people who don't want to use Discord, we have a Matrix Space which is bridged to the Discord server:
|
## License
|
||||||
|
|
||||||
[](https://matrix.to/#/#polymc:matrix.org)
|
|
||||||
|
|
||||||
If there are any issues with the space or you are using a client that does not support the feature here are the individual rooms:
|
|
||||||
|
|
||||||
[](https://matrix.to/#/#polymc-development:matrix.org)
|
|
||||||
[](https://matrix.to/#/#polymc-discussion:matrix.org)
|
|
||||||
[](https://matrix.to/#/#polymc-github:matrix.org)
|
|
||||||
[](https://matrix.to/#/#polymc-maintainers:matrix.org)
|
|
||||||
[](https://matrix.to/#/#polymc-news:matrix.org)
|
|
||||||
[](https://matrix.to/#/#polymc-offtopic:matrix.org)
|
|
||||||
[](https://matrix.to/#/#polymc-support:matrix.org)
|
|
||||||
[](https://matrix.to/#/#polymc-voice:matrix.org)
|
|
||||||
|
|
||||||
We also have a subreddit you can post your issues and suggestions on:
|
|
||||||
|
|
||||||
[r/PolyMCLauncher](https://www.reddit.com/r/PolyMCLauncher/)
|
|
||||||
|
|
||||||
# Development
|
|
||||||
|
|
||||||
If you want to contribute to PolyMC you might find it useful to join our Discord Server or Matrix Space.
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
If you want to build PolyMC yourself, check [Build Instructions](https://polymc.org/wiki/development/build-instructions/) for build instructions.
|
|
||||||
|
|
||||||
## Translations
|
|
||||||
|
|
||||||
The translation effort for PolyMC is hosted on [Weblate](https://hosted.weblate.org/projects/polymc/polymc/) and information about translating PolyMC is available at https://github.com/PolyMC/Translations
|
|
||||||
|
|
||||||
## Download information
|
|
||||||
|
|
||||||
To modify download information or change packaging information send a pull request or issue to the website [here](https://github.com/PolyMC/polymc.github.io/tree/master/src/download).
|
|
||||||
|
|
||||||
## Forking/Redistributing/Custom builds policy
|
|
||||||
|
|
||||||
We don't care what you do with your fork/custom build as long as you follow the terms of the [license](LICENSE) (this is a legal responsibility), and if you made code changes rather than just packaging a custom build, please do the following as a basic courtesy:
|
|
||||||
- Make it clear that your fork is not PolyMC and is not endorsed by or affiliated with the PolyMC project (https://polymc.org).
|
|
||||||
- Go through [CMakeLists.txt](CMakeLists.txt) and change PolyMC's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled).
|
|
||||||
|
|
||||||
If you have any questions or want any clarification on the above conditions please make an issue and ask us.
|
|
||||||
|
|
||||||
Be aware that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions:
|
|
||||||
- [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use)
|
|
||||||
- [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions)
|
|
||||||
|
|
||||||
If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file by setting them to an empty string (`""`).
|
|
||||||
|
|
||||||
All launcher code is available under the GPL-3.0-only license.
|
All launcher code is available under the GPL-3.0-only license.
|
||||||
|
|
||||||
The logo and related assets are under the CC BY-SA 4.0 license.
|
|
||||||
|
|
||||||
## Sponsors
|
|
||||||
Thank you to all our generous backers over at Open Collective! Support PolyMC by [becoming a backer](https://opencollective.com/polymc).
|
|
||||||
|
|
||||||
[](https://opencollective.com/polymc#backers)
|
|
||||||
|
|
||||||
Also, thanks to JetBrains for providing us a few licenses for all their products, as part of their [Open Source program](https://www.jetbrains.com/opensource/).
|
|
||||||
|
|
||||||
[](https://www.jetbrains.com/opensource/)
|
|
||||||
|
|
||||||
Additionally, thanks to the awesome people over at [MacStadium](https://www.macstadium.com/), for providing M1-Macs for development purposes!
|
|
||||||
|
|
||||||
<a href="https://www.macstadium.com"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="Powered by MacStadium" width="300"></a>
|
|
||||||
|
@ -42,12 +42,14 @@ Config::Config()
|
|||||||
{
|
{
|
||||||
// Name and copyright
|
// Name and copyright
|
||||||
LAUNCHER_NAME = "@Launcher_Name@";
|
LAUNCHER_NAME = "@Launcher_Name@";
|
||||||
|
LAUNCHER_APP_BINARY_NAME = "@Launcher_APP_BINARY_NAME@";
|
||||||
LAUNCHER_DISPLAYNAME = "@Launcher_DisplayName@";
|
LAUNCHER_DISPLAYNAME = "@Launcher_DisplayName@";
|
||||||
LAUNCHER_COPYRIGHT = "@Launcher_Copyright@";
|
LAUNCHER_COPYRIGHT = "@Launcher_Copyright@";
|
||||||
LAUNCHER_DOMAIN = "@Launcher_Domain@";
|
LAUNCHER_DOMAIN = "@Launcher_Domain@";
|
||||||
LAUNCHER_CONFIGFILE = "@Launcher_ConfigFile@";
|
LAUNCHER_CONFIGFILE = "@Launcher_ConfigFile@";
|
||||||
LAUNCHER_GIT = "@Launcher_Git@";
|
LAUNCHER_GIT = "@Launcher_Git@";
|
||||||
LAUNCHER_DESKTOPFILENAME = "@Launcher_DesktopFileName@";
|
LAUNCHER_DESKTOPFILENAME = "@Launcher_DesktopFileName@";
|
||||||
|
LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@";
|
||||||
|
|
||||||
USER_AGENT = "@Launcher_UserAgent@";
|
USER_AGENT = "@Launcher_UserAgent@";
|
||||||
USER_AGENT_UNCACHED = USER_AGENT + " (Uncached)";
|
USER_AGENT_UNCACHED = USER_AGENT + " (Uncached)";
|
||||||
@ -55,10 +57,9 @@ Config::Config()
|
|||||||
// Version information
|
// Version information
|
||||||
VERSION_MAJOR = @Launcher_VERSION_MAJOR@;
|
VERSION_MAJOR = @Launcher_VERSION_MAJOR@;
|
||||||
VERSION_MINOR = @Launcher_VERSION_MINOR@;
|
VERSION_MINOR = @Launcher_VERSION_MINOR@;
|
||||||
VERSION_HOTFIX = @Launcher_VERSION_HOTFIX@;
|
|
||||||
VERSION_BUILD = @Launcher_VERSION_BUILD@;
|
|
||||||
|
|
||||||
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
|
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
|
||||||
|
BUILD_DATE = "@Launcher_BUILD_TIMESTAMP@";
|
||||||
UPDATER_BASE = "@Launcher_UPDATER_BASE@";
|
UPDATER_BASE = "@Launcher_UPDATER_BASE@";
|
||||||
|
|
||||||
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
|
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
|
||||||
@ -85,7 +86,7 @@ Config::Config()
|
|||||||
{
|
{
|
||||||
VERSION_CHANNEL = GIT_REFSPEC;
|
VERSION_CHANNEL = GIT_REFSPEC;
|
||||||
VERSION_CHANNEL.remove("refs/heads/");
|
VERSION_CHANNEL.remove("refs/heads/");
|
||||||
if(!UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty() && VERSION_BUILD >= 0) {
|
if(!UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty()) {
|
||||||
UPDATER_ENABLED = true;
|
UPDATER_ENABLED = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,7 +99,6 @@ Config::Config()
|
|||||||
VERSION_CHANNEL = "unknown";
|
VERSION_CHANNEL = "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
VERSION_STR = "@Launcher_VERSION_STRING@";
|
|
||||||
NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@";
|
NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@";
|
||||||
NEWS_OPEN_URL = "@Launcher_NEWS_OPEN_URL@";
|
NEWS_OPEN_URL = "@Launcher_NEWS_OPEN_URL@";
|
||||||
HELP_URL = "@Launcher_HELP_URL@";
|
HELP_URL = "@Launcher_HELP_URL@";
|
||||||
@ -116,7 +116,7 @@ Config::Config()
|
|||||||
|
|
||||||
QString Config::versionString() const
|
QString Config::versionString() const
|
||||||
{
|
{
|
||||||
return QString("%1.%2.%3").arg(VERSION_MAJOR).arg(VERSION_MINOR).arg(VERSION_HOTFIX);
|
return QString("%1.%2").arg(VERSION_MAJOR).arg(VERSION_MINOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Config::printableVersionString() const
|
QString Config::printableVersionString() const
|
||||||
@ -128,11 +128,5 @@ QString Config::printableVersionString() const
|
|||||||
{
|
{
|
||||||
vstr += "-" + VERSION_CHANNEL;
|
vstr += "-" + VERSION_CHANNEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if a build number is set, also add it to the end
|
|
||||||
if(VERSION_BUILD >= 0)
|
|
||||||
{
|
|
||||||
vstr += "+build." + QString::number(VERSION_BUILD);
|
|
||||||
}
|
|
||||||
return vstr;
|
return vstr;
|
||||||
}
|
}
|
||||||
|
@ -44,21 +44,19 @@ class Config {
|
|||||||
public:
|
public:
|
||||||
Config();
|
Config();
|
||||||
QString LAUNCHER_NAME;
|
QString LAUNCHER_NAME;
|
||||||
|
QString LAUNCHER_APP_BINARY_NAME;
|
||||||
QString LAUNCHER_DISPLAYNAME;
|
QString LAUNCHER_DISPLAYNAME;
|
||||||
QString LAUNCHER_COPYRIGHT;
|
QString LAUNCHER_COPYRIGHT;
|
||||||
QString LAUNCHER_DOMAIN;
|
QString LAUNCHER_DOMAIN;
|
||||||
QString LAUNCHER_CONFIGFILE;
|
QString LAUNCHER_CONFIGFILE;
|
||||||
QString LAUNCHER_GIT;
|
QString LAUNCHER_GIT;
|
||||||
QString LAUNCHER_DESKTOPFILENAME;
|
QString LAUNCHER_DESKTOPFILENAME;
|
||||||
|
QString LAUNCHER_SVGFILENAME;
|
||||||
|
|
||||||
/// The major version number.
|
/// The major version number.
|
||||||
int VERSION_MAJOR;
|
int VERSION_MAJOR;
|
||||||
/// The minor version number.
|
/// The minor version number.
|
||||||
int VERSION_MINOR;
|
int VERSION_MINOR;
|
||||||
/// The hotfix number.
|
|
||||||
int VERSION_HOTFIX;
|
|
||||||
/// The build number.
|
|
||||||
int VERSION_BUILD;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version channel
|
* The version channel
|
||||||
@ -71,6 +69,9 @@ class Config {
|
|||||||
/// A short string identifying this build's platform. For example, "lin64" or "win32".
|
/// A short string identifying this build's platform. For example, "lin64" or "win32".
|
||||||
QString BUILD_PLATFORM;
|
QString BUILD_PLATFORM;
|
||||||
|
|
||||||
|
/// A string containing the build timestamp
|
||||||
|
QString BUILD_DATE;
|
||||||
|
|
||||||
/// URL for the updater's channel
|
/// URL for the updater's channel
|
||||||
QString UPDATER_BASE;
|
QString UPDATER_BASE;
|
||||||
|
|
||||||
@ -95,9 +96,6 @@ class Config {
|
|||||||
/// The git refspec of this build
|
/// The git refspec of this build
|
||||||
QString GIT_REFSPEC;
|
QString GIT_REFSPEC;
|
||||||
|
|
||||||
/// This is printed on start to standard output
|
|
||||||
QString VERSION_STR;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is used to fetch the news RSS feed.
|
* This is used to fetch the news RSS feed.
|
||||||
* It defaults in CMakeLists.txt to "https://multimc.org/rss.xml"
|
* It defaults in CMakeLists.txt to "https://multimc.org/rss.xml"
|
||||||
@ -144,8 +142,8 @@ class Config {
|
|||||||
QString LIBRARY_BASE = "https://libraries.minecraft.net/";
|
QString LIBRARY_BASE = "https://libraries.minecraft.net/";
|
||||||
QString AUTH_BASE = "https://authserver.mojang.com/";
|
QString AUTH_BASE = "https://authserver.mojang.com/";
|
||||||
QString IMGUR_BASE_URL = "https://api.imgur.com/3/";
|
QString IMGUR_BASE_URL = "https://api.imgur.com/3/";
|
||||||
QString FMLLIBS_BASE_URL = "https://files.polymc.org/fmllibs/";
|
QString FMLLIBS_BASE_URL = "https://files.prismlauncher.org/fmllibs/"; // FIXME: move into CMakeLists
|
||||||
QString TRANSLATIONS_BASE_URL = "https://i18n.polymc.org/";
|
QString TRANSLATIONS_BASE_URL = "https://i18n.prismlauncher.org/"; // FIXME: move into CMakeLists
|
||||||
|
|
||||||
QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/";
|
QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/";
|
||||||
|
|
||||||
|
29
flake.lock
generated
29
flake.lock
generated
@ -21,24 +21,24 @@
|
|||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1650031308,
|
"lastModified": 1650031308,
|
||||||
"narHash": "sha256-TvVOjkUobYJD9itQYueELJX3wmecvEdCbJ0FinW2mL4=",
|
"narHash": "sha256-TvVOjkUobYJD9itQYueELJX3wmecvEdCbJ0FinW2mL4=",
|
||||||
"owner": "PolyMC",
|
"owner": "PrismLauncher",
|
||||||
"repo": "libnbtplusplus",
|
"repo": "libnbtplusplus",
|
||||||
"rev": "2203af7eeb48c45398139b583615134efd8d407f",
|
"rev": "2203af7eeb48c45398139b583615134efd8d407f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "PolyMC",
|
"owner": "PrismLauncher",
|
||||||
"repo": "libnbtplusplus",
|
"repo": "libnbtplusplus",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1658119717,
|
"lastModified": 1666057921,
|
||||||
"narHash": "sha256-4upOZIQQ7Bc4CprqnHsKnqYfw+arJeAuU+QcpjYBXW0=",
|
"narHash": "sha256-VpQqtXdj6G7cH//SvoprjR7XT3KS7p+tCVebGK1N6tE=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "9eb60f25aff0d2218c848dd4574a0ab5e296cabe",
|
"rev": "88eab1e431cabd0ed621428d8b40d425a07af39f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -52,7 +52,24 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
"libnbtplusplus": "libnbtplusplus",
|
"libnbtplusplus": "libnbtplusplus",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs",
|
||||||
|
"tomlplusplus": "tomlplusplus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tomlplusplus": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1666091090,
|
||||||
|
"narHash": "sha256-djpMCFPvkJcfynV8WnsYdtwLq+J7jpV1iM4C6TojiyM=",
|
||||||
|
"owner": "marzer",
|
||||||
|
"repo": "tomlplusplus",
|
||||||
|
"rev": "1e4a3833d013aee08f58c5b31c69f709afc69f73",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "marzer",
|
||||||
|
"repo": "tomlplusplus",
|
||||||
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
11
flake.nix
11
flake.nix
@ -4,10 +4,11 @@
|
|||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||||
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
|
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
|
||||||
libnbtplusplus = { url = "github:PolyMC/libnbtplusplus"; flake = false; };
|
libnbtplusplus = { url = "github:PrismLauncher/libnbtplusplus"; flake = false; };
|
||||||
|
tomlplusplus = { url = "github:marzer/tomlplusplus"; flake = false; };
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, libnbtplusplus, ... }:
|
outputs = { self, nixpkgs, libnbtplusplus, tomlplusplus, ... }:
|
||||||
let
|
let
|
||||||
# User-friendly version number.
|
# User-friendly version number.
|
||||||
version = builtins.substring 0 8 self.lastModifiedDate;
|
version = builtins.substring 0 8 self.lastModifiedDate;
|
||||||
@ -22,14 +23,14 @@
|
|||||||
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
|
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
|
||||||
|
|
||||||
packagesFn = pkgs: rec {
|
packagesFn = pkgs: rec {
|
||||||
polymc = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; };
|
prismlauncher = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
|
||||||
polymc-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; };
|
prismlauncher-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages = forAllSystems (system:
|
packages = forAllSystems (system:
|
||||||
let packages = packagesFn pkgs.${system}; in
|
let packages = packagesFn pkgs.${system}; in
|
||||||
packages // { default = packages.polymc; }
|
packages // { default = packages.prismlauncher; }
|
||||||
);
|
);
|
||||||
|
|
||||||
overlay = final: packagesFn;
|
overlay = final: packagesFn;
|
||||||
|
@ -60,6 +60,11 @@
|
|||||||
#include "ui/themes/BrightTheme.h"
|
#include "ui/themes/BrightTheme.h"
|
||||||
#include "ui/themes/CustomTheme.h"
|
#include "ui/themes/CustomTheme.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include "ui/WinDarkmode.h"
|
||||||
|
#include <versionhelpers.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "ui/setupwizard/SetupWizard.h"
|
#include "ui/setupwizard/SetupWizard.h"
|
||||||
#include "ui/setupwizard/LanguageWizardPage.h"
|
#include "ui/setupwizard/LanguageWizardPage.h"
|
||||||
#include "ui/setupwizard/JavaWizardPage.h"
|
#include "ui/setupwizard/JavaWizardPage.h"
|
||||||
@ -74,6 +79,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <QAccessible>
|
#include <QAccessible>
|
||||||
|
#include <QCommandLineParser>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
@ -106,13 +112,17 @@
|
|||||||
#include "translations/TranslationsModel.h"
|
#include "translations/TranslationsModel.h"
|
||||||
#include "meta/Index.h"
|
#include "meta/Index.h"
|
||||||
|
|
||||||
#include <Commandline.h>
|
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
#include <DesktopServices.h>
|
#include <DesktopServices.h>
|
||||||
#include <LocalPeer.h>
|
#include <LocalPeer.h>
|
||||||
|
|
||||||
#include <sys.h>
|
#include <sys.h>
|
||||||
|
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include "gamemode_client.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
@ -127,12 +137,6 @@
|
|||||||
|
|
||||||
static const QLatin1String liveCheckFile("live.check");
|
static const QLatin1String liveCheckFile("live.check");
|
||||||
|
|
||||||
using namespace Commandline;
|
|
||||||
|
|
||||||
#define MACOS_HINT "If you are on macOS Sierra, you might have to move the app to your /Applications or ~/Applications folder. "\
|
|
||||||
"This usually fixes the problem and you can move the application elsewhere afterwards.\n"\
|
|
||||||
"\n"
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||||
{
|
{
|
||||||
@ -233,80 +237,27 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
this->setQuitOnLastWindowClosed(false);
|
this->setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
// Commandline parsing
|
// Commandline parsing
|
||||||
QHash<QString, QVariant> args;
|
QCommandLineParser parser;
|
||||||
{
|
parser.setApplicationDescription(BuildConfig.LAUNCHER_DISPLAYNAME);
|
||||||
Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals);
|
|
||||||
|
|
||||||
// --help
|
parser.addOptions({
|
||||||
parser.addSwitch("help");
|
{{"d", "dir"}, "Use a custom path as application root (use '.' for current directory)", "directory"},
|
||||||
parser.addShortOpt("help", 'h');
|
{{"l", "launch"}, "Launch the specified instance (by instance ID)", "instance"},
|
||||||
parser.addDocumentation("help", "Display this help and exit.");
|
{{"s", "server"}, "Join the specified server on launch (only valid in combination with --launch)", "address"},
|
||||||
// --version
|
{{"a", "profile"}, "Use the account specified by its profile name (only valid in combination with --launch)", "profile"},
|
||||||
parser.addSwitch("version");
|
{"alive", "Write a small '" + liveCheckFile + "' file after the launcher starts"},
|
||||||
parser.addShortOpt("version", 'V');
|
{{"I", "import"}, "Import instance from specified zip (local path or URL)", "file"}
|
||||||
parser.addDocumentation("version", "Display program version and exit.");
|
});
|
||||||
// --dir
|
parser.addHelpOption();
|
||||||
parser.addOption("dir");
|
parser.addVersionOption();
|
||||||
parser.addShortOpt("dir", 'd');
|
|
||||||
parser.addDocumentation("dir", "Use the supplied folder as application root instead of the binary location (use '.' for current)");
|
|
||||||
// --launch
|
|
||||||
parser.addOption("launch");
|
|
||||||
parser.addShortOpt("launch", 'l');
|
|
||||||
parser.addDocumentation("launch", "Launch the specified instance (by instance ID)");
|
|
||||||
// --server
|
|
||||||
parser.addOption("server");
|
|
||||||
parser.addShortOpt("server", 's');
|
|
||||||
parser.addDocumentation("server", "Join the specified server on launch (only valid in combination with --launch)");
|
|
||||||
// --profile
|
|
||||||
parser.addOption("profile");
|
|
||||||
parser.addShortOpt("profile", 'a');
|
|
||||||
parser.addDocumentation("profile", "Use the account specified by its profile name (only valid in combination with --launch)");
|
|
||||||
// --alive
|
|
||||||
parser.addSwitch("alive");
|
|
||||||
parser.addDocumentation("alive", "Write a small '" + liveCheckFile + "' file after the launcher starts");
|
|
||||||
// --import
|
|
||||||
parser.addOption("import");
|
|
||||||
parser.addShortOpt("import", 'I');
|
|
||||||
parser.addDocumentation("import", "Import instance from specified zip (local path or URL)");
|
|
||||||
|
|
||||||
// parse the arguments
|
parser.process(arguments());
|
||||||
try
|
|
||||||
{
|
|
||||||
args = parser.parse(arguments());
|
|
||||||
}
|
|
||||||
catch (const ParsingError &e)
|
|
||||||
{
|
|
||||||
std::cerr << "CommandLineError: " << e.what() << std::endl;
|
|
||||||
if(argc > 0)
|
|
||||||
std::cerr << "Try '" << argv[0] << " -h' to get help on command line parameters."
|
|
||||||
<< std::endl;
|
|
||||||
m_status = Application::Failed;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// display help and exit
|
m_instanceIdToLaunch = parser.value("launch");
|
||||||
if (args["help"].toBool())
|
m_serverToJoin = parser.value("server");
|
||||||
{
|
m_profileToUse = parser.value("profile");
|
||||||
std::cout << qPrintable(parser.compileHelp(arguments()[0]));
|
m_liveCheck = parser.isSet("alive");
|
||||||
m_status = Application::Succeeded;
|
m_zipToImport = parser.value("import");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// display version and exit
|
|
||||||
if (args["version"].toBool())
|
|
||||||
{
|
|
||||||
std::cout << "Version " << BuildConfig.printableVersionString().toStdString() << std::endl;
|
|
||||||
std::cout << "Git " << BuildConfig.GIT_COMMIT.toStdString() << std::endl;
|
|
||||||
m_status = Application::Succeeded;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_instanceIdToLaunch = args["launch"].toString();
|
|
||||||
m_serverToJoin = args["server"].toString();
|
|
||||||
m_profileToUse = args["profile"].toString();
|
|
||||||
m_liveCheck = args["alive"].toBool();
|
|
||||||
m_zipToImport = args["import"].toUrl();
|
|
||||||
|
|
||||||
// error if --launch is missing with --server or --profile
|
// error if --launch is missing with --server or --profile
|
||||||
if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty())
|
if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty())
|
||||||
@ -321,7 +272,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
{
|
{
|
||||||
// Root path is used for updates and portable data
|
// Root path is used for updates and portable data
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
QDir foo(FS::PathCombine(binPath, "..")); // typically portable-root or /usr
|
QDir foo(FS::PathCombine(binPath, "..")); // typically portable-root or /usr
|
||||||
m_rootPath = foo.absolutePath();
|
m_rootPath = foo.absolutePath();
|
||||||
#elif defined(Q_OS_WIN32)
|
#elif defined(Q_OS_WIN32)
|
||||||
@ -337,7 +288,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
QString adjustedBy;
|
QString adjustedBy;
|
||||||
QString dataPath;
|
QString dataPath;
|
||||||
// change folder
|
// change folder
|
||||||
QString dirParam = args["dir"].toString();
|
QString dirParam = parser.value("dir");
|
||||||
if (!dirParam.isEmpty())
|
if (!dirParam.isEmpty())
|
||||||
{
|
{
|
||||||
// the dir param. it makes multimc data path point to whatever the user specified
|
// the dir param. it makes multimc data path point to whatever the user specified
|
||||||
@ -351,6 +302,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
dataPath = foo.absolutePath();
|
dataPath = foo.absolutePath();
|
||||||
adjustedBy = "Persistent data path";
|
adjustedBy = "Persistent data path";
|
||||||
|
|
||||||
|
QDir polymcData(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "PolyMC"));
|
||||||
|
if (polymcData.exists()) {
|
||||||
|
dataPath = polymcData.absolutePath();
|
||||||
|
adjustedBy = "PolyMC data path";
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
// TODO: this should be removed in a future version
|
// TODO: this should be removed in a future version
|
||||||
// TODO: provide a migration path similar to macOS migration
|
// TODO: provide a migration path similar to macOS migration
|
||||||
@ -376,9 +333,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
QString(
|
QString(
|
||||||
"The launcher data folder could not be created.\n"
|
"The launcher data folder could not be created.\n"
|
||||||
"\n"
|
"\n"
|
||||||
#if defined(Q_OS_MAC)
|
|
||||||
MACOS_HINT
|
|
||||||
#endif
|
|
||||||
"Make sure you have the right permissions to the launcher data folder and any folder needed to access it.\n"
|
"Make sure you have the right permissions to the launcher data folder and any folder needed to access it.\n"
|
||||||
"(%1)\n"
|
"(%1)\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -394,9 +348,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
QString(
|
QString(
|
||||||
"The launcher data folder could not be opened.\n"
|
"The launcher data folder could not be opened.\n"
|
||||||
"\n"
|
"\n"
|
||||||
#if defined(Q_OS_MAC)
|
|
||||||
MACOS_HINT
|
|
||||||
#endif
|
|
||||||
"Make sure you have the right permissions to the launcher data folder.\n"
|
"Make sure you have the right permissions to the launcher data folder.\n"
|
||||||
"(%1)\n"
|
"(%1)\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -477,9 +428,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
QString(
|
QString(
|
||||||
"The launcher couldn't create a log file - the data folder is not writable.\n"
|
"The launcher couldn't create a log file - the data folder is not writable.\n"
|
||||||
"\n"
|
"\n"
|
||||||
#if defined(Q_OS_MAC)
|
|
||||||
MACOS_HINT
|
|
||||||
#endif
|
|
||||||
"Make sure you have write permissions to the data folder.\n"
|
"Make sure you have write permissions to the data folder.\n"
|
||||||
"(%1)\n"
|
"(%1)\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -541,7 +489,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
// Initialize application settings
|
// Initialize application settings
|
||||||
{
|
{
|
||||||
m_settings.reset(new INISettingsObject(BuildConfig.LAUNCHER_CONFIGFILE, this));
|
// Provide a fallback for migration from PolyMC
|
||||||
|
m_settings.reset(new INISettingsObject({ BuildConfig.LAUNCHER_CONFIGFILE, "polymc.cfg", "multimc.cfg" }, this));
|
||||||
// Updates
|
// Updates
|
||||||
// Multiple channels are separated by spaces
|
// Multiple channels are separated by spaces
|
||||||
m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL);
|
m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL);
|
||||||
@ -680,6 +629,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
m_settings->registerSetting("UpdateDialogGeometry", "");
|
m_settings->registerSetting("UpdateDialogGeometry", "");
|
||||||
|
|
||||||
|
m_settings->registerSetting("ModDownloadGeometry", "");
|
||||||
|
|
||||||
// HACK: This code feels so stupid is there a less stupid way of doing this?
|
// HACK: This code feels so stupid is there a less stupid way of doing this?
|
||||||
{
|
{
|
||||||
m_settings->registerSetting("PastebinURL", "");
|
m_settings->registerSetting("PastebinURL", "");
|
||||||
@ -774,7 +725,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
auto platform = getIdealPlatform(BuildConfig.BUILD_PLATFORM);
|
auto platform = getIdealPlatform(BuildConfig.BUILD_PLATFORM);
|
||||||
auto channelUrl = BuildConfig.UPDATER_BASE + platform + "/channels.json";
|
auto channelUrl = BuildConfig.UPDATER_BASE + platform + "/channels.json";
|
||||||
qDebug() << "Initializing updater with platform: " << platform << " -- " << channelUrl;
|
qDebug() << "Initializing updater with platform: " << platform << " -- " << channelUrl;
|
||||||
m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD));
|
m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL));
|
||||||
qDebug() << "<> Updater started.";
|
qDebug() << "<> Updater started.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -864,7 +815,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
|
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
|
||||||
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
|
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
|
||||||
m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
|
m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
|
||||||
|
m_metacache->addBase("FlameMods", QDir("cache/FlameMods").absolutePath());
|
||||||
m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath());
|
m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath());
|
||||||
|
m_metacache->addBase("ModrinthModpacks", QDir("cache/ModrinthModpacks").absolutePath());
|
||||||
m_metacache->addBase("root", QDir::currentPath());
|
m_metacache->addBase("root", QDir::currentPath());
|
||||||
m_metacache->addBase("translations", QDir("translations").absolutePath());
|
m_metacache->addBase("translations", QDir("translations").absolutePath());
|
||||||
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
|
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
|
||||||
@ -915,10 +868,13 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
qDebug() << "<> Application theme set.";
|
qDebug() << "<> Application theme set.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateCapabilities();
|
||||||
|
|
||||||
if(createSetupWizard())
|
if(createSetupWizard())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
performMainStartupAction();
|
performMainStartupAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1027,7 +983,7 @@ void Application::performMainStartupAction()
|
|||||||
qDebug() << " Launching with account" << m_profileToUse;
|
qDebug() << " Launching with account" << m_profileToUse;
|
||||||
}
|
}
|
||||||
|
|
||||||
launch(inst, true, nullptr, serverToJoin, accountToUse);
|
launch(inst, true, false, nullptr, serverToJoin, accountToUse);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1131,6 +1087,7 @@ void Application::messageReceived(const QByteArray& message)
|
|||||||
launch(
|
launch(
|
||||||
instance,
|
instance,
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
nullptr,
|
nullptr,
|
||||||
serverObject,
|
serverObject,
|
||||||
accountObject
|
accountObject
|
||||||
@ -1168,15 +1125,6 @@ std::vector<ITheme *> Application::getValidApplicationThemes()
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::isFlatpak()
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_LINUX
|
|
||||||
return QFile::exists("/.flatpak-info");
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::setApplicationTheme(const QString& name, bool initial)
|
void Application::setApplicationTheme(const QString& name, bool initial)
|
||||||
{
|
{
|
||||||
auto systemPalette = qApp->palette();
|
auto systemPalette = qApp->palette();
|
||||||
@ -1185,6 +1133,15 @@ void Application::setApplicationTheme(const QString& name, bool initial)
|
|||||||
{
|
{
|
||||||
auto & theme = (*themeIter).second;
|
auto & theme = (*themeIter).second;
|
||||||
theme->apply(initial);
|
theme->apply(initial);
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
if (m_mainWindow && IsWindows10OrGreater()) {
|
||||||
|
if (QString::compare(theme->id(), "dark") == 0) {
|
||||||
|
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
|
||||||
|
} else {
|
||||||
|
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1200,7 +1157,7 @@ void Application::setIconTheme(const QString& name)
|
|||||||
QIcon Application::getThemedIcon(const QString& name)
|
QIcon Application::getThemedIcon(const QString& name)
|
||||||
{
|
{
|
||||||
if(name == "logo") {
|
if(name == "logo") {
|
||||||
return QIcon(":/org.polymc.PolyMC.svg");
|
return QIcon(":/" + BuildConfig.LAUNCHER_SVGFILENAME);
|
||||||
}
|
}
|
||||||
return QIcon::fromTheme(name);
|
return QIcon::fromTheme(name);
|
||||||
}
|
}
|
||||||
@ -1222,6 +1179,7 @@ bool Application::openJsonEditor(const QString &filename)
|
|||||||
bool Application::launch(
|
bool Application::launch(
|
||||||
InstancePtr instance,
|
InstancePtr instance,
|
||||||
bool online,
|
bool online,
|
||||||
|
bool demo,
|
||||||
BaseProfilerFactory *profiler,
|
BaseProfilerFactory *profiler,
|
||||||
MinecraftServerTargetPtr serverToJoin,
|
MinecraftServerTargetPtr serverToJoin,
|
||||||
MinecraftAccountPtr accountToUse
|
MinecraftAccountPtr accountToUse
|
||||||
@ -1245,6 +1203,7 @@ bool Application::launch(
|
|||||||
controller.reset(new LaunchController());
|
controller.reset(new LaunchController());
|
||||||
controller->setInstance(instance);
|
controller->setInstance(instance);
|
||||||
controller->setOnline(online);
|
controller->setOnline(online);
|
||||||
|
controller->setDemo(demo);
|
||||||
controller->setProfiler(profiler);
|
controller->setProfiler(profiler);
|
||||||
controller->setServerToJoin(serverToJoin);
|
controller->setServerToJoin(serverToJoin);
|
||||||
controller->setAccountToUse(accountToUse);
|
controller->setAccountToUse(accountToUse);
|
||||||
@ -1258,6 +1217,9 @@ bool Application::launch(
|
|||||||
}
|
}
|
||||||
connect(controller.get(), &LaunchController::succeeded, this, &Application::controllerSucceeded);
|
connect(controller.get(), &LaunchController::succeeded, this, &Application::controllerSucceeded);
|
||||||
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"));
|
||||||
|
});
|
||||||
addRunningInstance();
|
addRunningInstance();
|
||||||
controller->start();
|
controller->start();
|
||||||
return true;
|
return true;
|
||||||
@ -1412,6 +1374,16 @@ MainWindow* Application::showMainWindow(bool minimized)
|
|||||||
m_mainWindow = new MainWindow();
|
m_mainWindow = new MainWindow();
|
||||||
m_mainWindow->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").toByteArray()));
|
m_mainWindow->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").toByteArray()));
|
||||||
m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").toByteArray()));
|
m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").toByteArray()));
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
if (IsWindows10OrGreater())
|
||||||
|
{
|
||||||
|
if (QString::compare(settings()->get("ApplicationTheme").toString(), "dark") == 0) {
|
||||||
|
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
|
||||||
|
} else {
|
||||||
|
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if(minimized)
|
if(minimized)
|
||||||
{
|
{
|
||||||
m_mainWindow->showMinimized();
|
m_mainWindow->showMinimized();
|
||||||
@ -1564,21 +1536,37 @@ shared_qobject_ptr<Meta::Index> Application::metadataIndex()
|
|||||||
return m_metadataIndex;
|
return m_metadataIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
Application::Capabilities Application::currentCapabilities()
|
void Application::updateCapabilities()
|
||||||
{
|
{
|
||||||
Capabilities c;
|
m_capabilities = None;
|
||||||
if (!getMSAClientID().isEmpty())
|
if (!getMSAClientID().isEmpty())
|
||||||
c |= SupportsMSA;
|
m_capabilities |= SupportsMSA;
|
||||||
if (!getFlameAPIKey().isEmpty())
|
if (!getFlameAPIKey().isEmpty())
|
||||||
c |= SupportsFlame;
|
m_capabilities |= SupportsFlame;
|
||||||
return c;
|
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
if (gamemode_query_status() >= 0)
|
||||||
|
m_capabilities |= SupportsGameMode;
|
||||||
|
|
||||||
|
{
|
||||||
|
void *dummy = dlopen("libMangoHud_dlsym.so", RTLD_LAZY);
|
||||||
|
// try normal variant as well
|
||||||
|
if (dummy == NULL)
|
||||||
|
dummy = dlopen("libMangoHud.so", RTLD_LAZY);
|
||||||
|
|
||||||
|
if (dummy != NULL) {
|
||||||
|
dlclose(dummy);
|
||||||
|
m_capabilities |= SupportsMangoHud;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Application::getJarPath(QString jarFile)
|
QString Application::getJarPath(QString jarFile)
|
||||||
{
|
{
|
||||||
QStringList potentialPaths = {
|
QStringList potentialPaths = {
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
FS::PathCombine(m_rootPath, "share/jars"),
|
FS::PathCombine(m_rootPath, "share/" + BuildConfig.LAUNCHER_APP_BINARY_NAME),
|
||||||
#endif
|
#endif
|
||||||
FS::PathCombine(m_rootPath, "jars"),
|
FS::PathCombine(m_rootPath, "jars"),
|
||||||
FS::PathCombine(applicationDirPath(), "jars")
|
FS::PathCombine(applicationDirPath(), "jars")
|
||||||
|
@ -95,6 +95,8 @@ public:
|
|||||||
|
|
||||||
SupportsMSA = 1 << 0,
|
SupportsMSA = 1 << 0,
|
||||||
SupportsFlame = 1 << 1,
|
SupportsFlame = 1 << 1,
|
||||||
|
SupportsGameMode = 1 << 2,
|
||||||
|
SupportsMangoHud = 1 << 3,
|
||||||
};
|
};
|
||||||
Q_DECLARE_FLAGS(Capabilities, Capability)
|
Q_DECLARE_FLAGS(Capabilities, Capability)
|
||||||
|
|
||||||
@ -114,8 +116,6 @@ public:
|
|||||||
|
|
||||||
QIcon getThemedIcon(const QString& name);
|
QIcon getThemedIcon(const QString& name);
|
||||||
|
|
||||||
bool isFlatpak();
|
|
||||||
|
|
||||||
void setIconTheme(const QString& name);
|
void setIconTheme(const QString& name);
|
||||||
|
|
||||||
std::vector<ITheme *> getValidApplicationThemes();
|
std::vector<ITheme *> getValidApplicationThemes();
|
||||||
@ -162,7 +162,7 @@ public:
|
|||||||
|
|
||||||
shared_qobject_ptr<Meta::Index> metadataIndex();
|
shared_qobject_ptr<Meta::Index> metadataIndex();
|
||||||
|
|
||||||
Capabilities currentCapabilities();
|
void updateCapabilities();
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Finds and returns the full path to a jar file.
|
* Finds and returns the full path to a jar file.
|
||||||
@ -180,6 +180,10 @@ public:
|
|||||||
return m_rootPath;
|
return m_rootPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Capabilities capabilities() {
|
||||||
|
return m_capabilities;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Opens a json file using either a system default editor, or, if not empty, the editor
|
* Opens a json file using either a system default editor, or, if not empty, the editor
|
||||||
* specified in the settings
|
* specified in the settings
|
||||||
@ -207,6 +211,7 @@ public slots:
|
|||||||
bool launch(
|
bool launch(
|
||||||
InstancePtr instance,
|
InstancePtr instance,
|
||||||
bool online = true,
|
bool online = true,
|
||||||
|
bool demo = false,
|
||||||
BaseProfilerFactory *profiler = nullptr,
|
BaseProfilerFactory *profiler = nullptr,
|
||||||
MinecraftServerTargetPtr serverToJoin = nullptr,
|
MinecraftServerTargetPtr serverToJoin = nullptr,
|
||||||
MinecraftAccountPtr accountToUse = nullptr
|
MinecraftAccountPtr accountToUse = nullptr
|
||||||
@ -258,6 +263,7 @@ private:
|
|||||||
|
|
||||||
QString m_rootPath;
|
QString m_rootPath;
|
||||||
Status m_status = Application::StartingUp;
|
Status m_status = Application::StartingUp;
|
||||||
|
Capabilities m_capabilities;
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;
|
Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;
|
||||||
|
@ -53,15 +53,22 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
|
|||||||
: QObject()
|
: QObject()
|
||||||
{
|
{
|
||||||
m_settings = settings;
|
m_settings = settings;
|
||||||
|
m_global_settings = globalSettings;
|
||||||
m_rootDir = rootDir;
|
m_rootDir = rootDir;
|
||||||
|
|
||||||
m_settings->registerSetting("name", "Unnamed Instance");
|
m_settings->registerSetting("name", "Unnamed Instance");
|
||||||
m_settings->registerSetting("iconKey", "default");
|
m_settings->registerSetting("iconKey", "default");
|
||||||
m_settings->registerSetting("notes", "");
|
m_settings->registerSetting("notes", "");
|
||||||
|
|
||||||
m_settings->registerSetting("lastLaunchTime", 0);
|
m_settings->registerSetting("lastLaunchTime", 0);
|
||||||
m_settings->registerSetting("totalTimePlayed", 0);
|
m_settings->registerSetting("totalTimePlayed", 0);
|
||||||
m_settings->registerSetting("lastTimePlayed", 0);
|
m_settings->registerSetting("lastTimePlayed", 0);
|
||||||
|
|
||||||
|
// Game time override
|
||||||
|
auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false);
|
||||||
|
m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride);
|
||||||
|
m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride);
|
||||||
|
|
||||||
// NOTE: Sometimees InstanceType is already registered, as it was used to identify the type of
|
// NOTE: Sometimees InstanceType is already registered, as it was used to identify the type of
|
||||||
// a locally stored instance
|
// a locally stored instance
|
||||||
if (!m_settings->getSetting("InstanceType"))
|
if (!m_settings->getSetting("InstanceType"))
|
||||||
@ -107,49 +114,59 @@ QString BaseInstance::getPostExitCommand()
|
|||||||
return settings()->get("PostExitCommand").toString();
|
return settings()->get("PostExitCommand").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseInstance::isManagedPack()
|
bool BaseInstance::isManagedPack() const
|
||||||
{
|
{
|
||||||
return settings()->get("ManagedPack").toBool();
|
return m_settings->get("ManagedPack").toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString BaseInstance::getManagedPackType()
|
QString BaseInstance::getManagedPackType() const
|
||||||
{
|
{
|
||||||
return settings()->get("ManagedPackType").toString();
|
return m_settings->get("ManagedPackType").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString BaseInstance::getManagedPackID()
|
QString BaseInstance::getManagedPackID() const
|
||||||
{
|
{
|
||||||
return settings()->get("ManagedPackID").toString();
|
return m_settings->get("ManagedPackID").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString BaseInstance::getManagedPackName()
|
QString BaseInstance::getManagedPackName() const
|
||||||
{
|
{
|
||||||
return settings()->get("ManagedPackName").toString();
|
return m_settings->get("ManagedPackName").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString BaseInstance::getManagedPackVersionID()
|
QString BaseInstance::getManagedPackVersionID() const
|
||||||
{
|
{
|
||||||
return settings()->get("ManagedPackVersionID").toString();
|
return m_settings->get("ManagedPackVersionID").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString BaseInstance::getManagedPackVersionName()
|
QString BaseInstance::getManagedPackVersionName() const
|
||||||
{
|
{
|
||||||
return settings()->get("ManagedPackVersionName").toString();
|
return m_settings->get("ManagedPackVersionName").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseInstance::setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version)
|
void BaseInstance::setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version)
|
||||||
{
|
{
|
||||||
settings()->set("ManagedPack", true);
|
m_settings->set("ManagedPack", true);
|
||||||
settings()->set("ManagedPackType", type);
|
m_settings->set("ManagedPackType", type);
|
||||||
settings()->set("ManagedPackID", id);
|
m_settings->set("ManagedPackID", id);
|
||||||
settings()->set("ManagedPackName", name);
|
m_settings->set("ManagedPackName", name);
|
||||||
settings()->set("ManagedPackVersionID", versionId);
|
m_settings->set("ManagedPackVersionID", versionId);
|
||||||
settings()->set("ManagedPackVersionName", version);
|
m_settings->set("ManagedPackVersionName", version);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseInstance::copyManagedPack(BaseInstance& other)
|
||||||
|
{
|
||||||
|
m_settings->set("ManagedPack", other.isManagedPack());
|
||||||
|
m_settings->set("ManagedPackType", other.getManagedPackType());
|
||||||
|
m_settings->set("ManagedPackID", other.getManagedPackID());
|
||||||
|
m_settings->set("ManagedPackName", other.getManagedPackName());
|
||||||
|
m_settings->set("ManagedPackVersionID", other.getManagedPackVersionID());
|
||||||
|
m_settings->set("ManagedPackVersionName", other.getManagedPackVersionName());
|
||||||
}
|
}
|
||||||
|
|
||||||
int BaseInstance::getConsoleMaxLines() const
|
int BaseInstance::getConsoleMaxLines() const
|
||||||
{
|
{
|
||||||
auto lineSetting = settings()->getSetting("ConsoleMaxLines");
|
auto lineSetting = m_settings->getSetting("ConsoleMaxLines");
|
||||||
bool conversionOk = false;
|
bool conversionOk = false;
|
||||||
int maxLines = lineSetting->get().toInt(&conversionOk);
|
int maxLines = lineSetting->get().toInt(&conversionOk);
|
||||||
if(!conversionOk)
|
if(!conversionOk)
|
||||||
@ -162,7 +179,7 @@ int BaseInstance::getConsoleMaxLines() const
|
|||||||
|
|
||||||
bool BaseInstance::shouldStopOnConsoleOverflow() const
|
bool BaseInstance::shouldStopOnConsoleOverflow() const
|
||||||
{
|
{
|
||||||
return settings()->get("ConsoleOverflowStop").toBool();
|
return m_settings->get("ConsoleOverflowStop").toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseInstance::iconUpdated(QString key)
|
void BaseInstance::iconUpdated(QString key)
|
||||||
@ -237,7 +254,7 @@ void BaseInstance::setRunning(bool running)
|
|||||||
|
|
||||||
int64_t BaseInstance::totalTimePlayed() const
|
int64_t BaseInstance::totalTimePlayed() const
|
||||||
{
|
{
|
||||||
qint64 current = settings()->get("totalTimePlayed").toLongLong();
|
qint64 current = m_settings->get("totalTimePlayed").toLongLong();
|
||||||
if(m_isRunning)
|
if(m_isRunning)
|
||||||
{
|
{
|
||||||
QDateTime timeNow = QDateTime::currentDateTime();
|
QDateTime timeNow = QDateTime::currentDateTime();
|
||||||
@ -253,7 +270,7 @@ int64_t BaseInstance::lastTimePlayed() const
|
|||||||
QDateTime timeNow = QDateTime::currentDateTime();
|
QDateTime timeNow = QDateTime::currentDateTime();
|
||||||
return m_timeStarted.secsTo(timeNow);
|
return m_timeStarted.secsTo(timeNow);
|
||||||
}
|
}
|
||||||
return settings()->get("lastTimePlayed").toLongLong();
|
return m_settings->get("lastTimePlayed").toLongLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseInstance::resetTimePlayed()
|
void BaseInstance::resetTimePlayed()
|
||||||
@ -272,8 +289,10 @@ QString BaseInstance::instanceRoot() const
|
|||||||
return m_rootDir;
|
return m_rootDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsObjectPtr BaseInstance::settings() const
|
SettingsObjectPtr BaseInstance::settings()
|
||||||
{
|
{
|
||||||
|
loadSpecificSettings();
|
||||||
|
|
||||||
return m_settings;
|
return m_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,11 +355,11 @@ QString BaseInstance::name() const
|
|||||||
|
|
||||||
QString BaseInstance::windowTitle() const
|
QString BaseInstance::windowTitle() const
|
||||||
{
|
{
|
||||||
return BuildConfig.LAUNCHER_NAME + ": " + name().replace(QRegularExpression("\\s+"), " ");
|
return BuildConfig.LAUNCHER_DISPLAYNAME + ": " + name().replace(QRegularExpression("\\s+"), " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: why is this here? move it to MinecraftInstance!!!
|
// FIXME: why is this here? move it to MinecraftInstance!!!
|
||||||
QStringList BaseInstance::extraArguments() const
|
QStringList BaseInstance::extraArguments()
|
||||||
{
|
{
|
||||||
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
|
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
|
||||||
}
|
}
|
||||||
@ -349,3 +368,8 @@ shared_qobject_ptr<LaunchTask> BaseInstance::getLaunchTask()
|
|||||||
{
|
{
|
||||||
return m_launchProcess;
|
return m_launchProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BaseInstance::updateRuntimeContext()
|
||||||
|
{
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
@ -54,6 +54,7 @@
|
|||||||
#include "net/Mode.h"
|
#include "net/Mode.h"
|
||||||
|
|
||||||
#include "minecraft/launch/MinecraftServerTarget.h"
|
#include "minecraft/launch/MinecraftServerTarget.h"
|
||||||
|
#include "RuntimeContext.h"
|
||||||
|
|
||||||
class QDir;
|
class QDir;
|
||||||
class Task;
|
class Task;
|
||||||
@ -140,13 +141,14 @@ public:
|
|||||||
QString getPostExitCommand();
|
QString getPostExitCommand();
|
||||||
QString getWrapperCommand();
|
QString getWrapperCommand();
|
||||||
|
|
||||||
bool isManagedPack();
|
bool isManagedPack() const;
|
||||||
QString getManagedPackType();
|
QString getManagedPackType() const;
|
||||||
QString getManagedPackID();
|
QString getManagedPackID() const;
|
||||||
QString getManagedPackName();
|
QString getManagedPackName() const;
|
||||||
QString getManagedPackVersionID();
|
QString getManagedPackVersionID() const;
|
||||||
QString getManagedPackVersionName();
|
QString getManagedPackVersionName() const;
|
||||||
void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version);
|
void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version);
|
||||||
|
void copyManagedPack(BaseInstance& other);
|
||||||
|
|
||||||
/// guess log level from a line of game log
|
/// guess log level from a line of game log
|
||||||
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
|
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
|
||||||
@ -154,7 +156,7 @@ public:
|
|||||||
return level;
|
return level;
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual QStringList extraArguments() const;
|
virtual QStringList extraArguments();
|
||||||
|
|
||||||
/// Traits. Normally inside the version, depends on instance implementation.
|
/// Traits. Normally inside the version, depends on instance implementation.
|
||||||
virtual QSet <QString> traits() const = 0;
|
virtual QSet <QString> traits() const = 0;
|
||||||
@ -170,9 +172,18 @@ public:
|
|||||||
/*!
|
/*!
|
||||||
* \brief Gets this instance's settings object.
|
* \brief Gets this instance's settings object.
|
||||||
* This settings object stores instance-specific settings.
|
* This settings object stores instance-specific settings.
|
||||||
|
*
|
||||||
|
* Note that this method is not const.
|
||||||
|
* It may call loadSpecificSettings() to ensure those are loaded.
|
||||||
|
*
|
||||||
* \return A pointer to this instance's settings object.
|
* \return A pointer to this instance's settings object.
|
||||||
*/
|
*/
|
||||||
virtual SettingsObjectPtr settings() const;
|
virtual SettingsObjectPtr settings();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Loads settings specific to an instance type if they're not already loaded.
|
||||||
|
*/
|
||||||
|
virtual void loadSpecificSettings() = 0;
|
||||||
|
|
||||||
/// returns a valid update task
|
/// returns a valid update task
|
||||||
virtual Task::Ptr createUpdateTask(Net::Mode mode) = 0;
|
virtual Task::Ptr createUpdateTask(Net::Mode mode) = 0;
|
||||||
@ -206,10 +217,16 @@ public:
|
|||||||
virtual QString instanceConfigFolder() const = 0;
|
virtual QString instanceConfigFolder() const = 0;
|
||||||
|
|
||||||
/// get variables this instance exports
|
/// get variables this instance exports
|
||||||
virtual QMap<QString, QString> getVariables() const = 0;
|
virtual QMap<QString, QString> getVariables() = 0;
|
||||||
|
|
||||||
virtual QString typeName() const = 0;
|
virtual QString typeName() const = 0;
|
||||||
|
|
||||||
|
void updateRuntimeContext();
|
||||||
|
RuntimeContext runtimeContext() const
|
||||||
|
{
|
||||||
|
return m_runtimeContext;
|
||||||
|
}
|
||||||
|
|
||||||
bool hasVersionBroken() const
|
bool hasVersionBroken() const
|
||||||
{
|
{
|
||||||
return m_hasBrokenVersion;
|
return m_hasBrokenVersion;
|
||||||
@ -268,6 +285,11 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
void changeStatus(Status newStatus);
|
void changeStatus(Status newStatus);
|
||||||
|
|
||||||
|
SettingsObjectPtr globalSettings() const { return m_global_settings.lock(); };
|
||||||
|
|
||||||
|
bool isSpecificSettingsLoaded() const { return m_specific_settings_loaded; }
|
||||||
|
void setSpecificSettingsLoaded(bool loaded) { m_specific_settings_loaded = loaded; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
/*!
|
/*!
|
||||||
* \brief Signal emitted when properties relevant to the instance view change
|
* \brief Signal emitted when properties relevant to the instance view change
|
||||||
@ -290,12 +312,17 @@ protected: /* data */
|
|||||||
bool m_isRunning = false;
|
bool m_isRunning = false;
|
||||||
shared_qobject_ptr<LaunchTask> m_launchProcess;
|
shared_qobject_ptr<LaunchTask> m_launchProcess;
|
||||||
QDateTime m_timeStarted;
|
QDateTime m_timeStarted;
|
||||||
|
RuntimeContext m_runtimeContext;
|
||||||
|
|
||||||
private: /* data */
|
private: /* data */
|
||||||
Status m_status = Status::Present;
|
Status m_status = Status::Present;
|
||||||
bool m_crashed = false;
|
bool m_crashed = false;
|
||||||
bool m_hasUpdate = false;
|
bool m_hasUpdate = false;
|
||||||
bool m_hasBrokenVersion = false;
|
bool m_hasBrokenVersion = false;
|
||||||
|
|
||||||
|
SettingsObjectWeakPtr m_global_settings;
|
||||||
|
bool m_specific_settings_loaded = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(shared_qobject_ptr<BaseInstance>)
|
Q_DECLARE_METATYPE(shared_qobject_ptr<BaseInstance>)
|
||||||
|
@ -26,6 +26,7 @@ set(CORE_SOURCES
|
|||||||
MMCZip.cpp
|
MMCZip.cpp
|
||||||
MMCStrings.h
|
MMCStrings.h
|
||||||
MMCStrings.cpp
|
MMCStrings.cpp
|
||||||
|
RuntimeContext.h
|
||||||
|
|
||||||
# Basic instance manipulation tasks (derived from InstanceTask)
|
# Basic instance manipulation tasks (derived from InstanceTask)
|
||||||
InstanceCreationTask.h
|
InstanceCreationTask.h
|
||||||
@ -88,12 +89,6 @@ set(CORE_SOURCES
|
|||||||
MMCTime.cpp
|
MMCTime.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
ecm_add_test(FileSystem_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
|
|
||||||
TEST_NAME FileSystem) # TODO: needs testdata
|
|
||||||
|
|
||||||
ecm_add_test(GZip_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
|
|
||||||
TEST_NAME GZip)
|
|
||||||
|
|
||||||
set(PATHMATCHER_SOURCES
|
set(PATHMATCHER_SOURCES
|
||||||
# Path matchers
|
# Path matchers
|
||||||
pathmatcher/FSTreeMatcher.h
|
pathmatcher/FSTreeMatcher.h
|
||||||
@ -294,8 +289,6 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/Rule.h
|
minecraft/Rule.h
|
||||||
minecraft/OneSixVersionFormat.cpp
|
minecraft/OneSixVersionFormat.cpp
|
||||||
minecraft/OneSixVersionFormat.h
|
minecraft/OneSixVersionFormat.h
|
||||||
minecraft/OpSys.cpp
|
|
||||||
minecraft/OpSys.h
|
|
||||||
minecraft/ParseUtils.cpp
|
minecraft/ParseUtils.cpp
|
||||||
minecraft/ParseUtils.h
|
minecraft/ParseUtils.h
|
||||||
minecraft/ProfileUtils.cpp
|
minecraft/ProfileUtils.cpp
|
||||||
@ -303,6 +296,8 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/Library.cpp
|
minecraft/Library.cpp
|
||||||
minecraft/Library.h
|
minecraft/Library.h
|
||||||
minecraft/MojangDownloadInfo.h
|
minecraft/MojangDownloadInfo.h
|
||||||
|
minecraft/VanillaInstanceCreationTask.cpp
|
||||||
|
minecraft/VanillaInstanceCreationTask.h
|
||||||
minecraft/VersionFile.cpp
|
minecraft/VersionFile.cpp
|
||||||
minecraft/VersionFile.h
|
minecraft/VersionFile.h
|
||||||
minecraft/VersionFilterData.h
|
minecraft/VersionFilterData.h
|
||||||
@ -318,16 +313,30 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/mod/ModDetails.h
|
minecraft/mod/ModDetails.h
|
||||||
minecraft/mod/ModFolderModel.h
|
minecraft/mod/ModFolderModel.h
|
||||||
minecraft/mod/ModFolderModel.cpp
|
minecraft/mod/ModFolderModel.cpp
|
||||||
|
minecraft/mod/Resource.h
|
||||||
|
minecraft/mod/Resource.cpp
|
||||||
|
minecraft/mod/ResourceFolderModel.h
|
||||||
|
minecraft/mod/ResourceFolderModel.cpp
|
||||||
|
minecraft/mod/ResourcePack.h
|
||||||
|
minecraft/mod/ResourcePack.cpp
|
||||||
minecraft/mod/ResourcePackFolderModel.h
|
minecraft/mod/ResourcePackFolderModel.h
|
||||||
minecraft/mod/ResourcePackFolderModel.cpp
|
minecraft/mod/ResourcePackFolderModel.cpp
|
||||||
|
minecraft/mod/TexturePack.h
|
||||||
|
minecraft/mod/TexturePack.cpp
|
||||||
minecraft/mod/TexturePackFolderModel.h
|
minecraft/mod/TexturePackFolderModel.h
|
||||||
minecraft/mod/TexturePackFolderModel.cpp
|
minecraft/mod/TexturePackFolderModel.cpp
|
||||||
|
minecraft/mod/ShaderPackFolderModel.h
|
||||||
|
minecraft/mod/tasks/BasicFolderLoadTask.h
|
||||||
minecraft/mod/tasks/ModFolderLoadTask.h
|
minecraft/mod/tasks/ModFolderLoadTask.h
|
||||||
minecraft/mod/tasks/ModFolderLoadTask.cpp
|
minecraft/mod/tasks/ModFolderLoadTask.cpp
|
||||||
minecraft/mod/tasks/LocalModParseTask.h
|
minecraft/mod/tasks/LocalModParseTask.h
|
||||||
minecraft/mod/tasks/LocalModParseTask.cpp
|
minecraft/mod/tasks/LocalModParseTask.cpp
|
||||||
minecraft/mod/tasks/LocalModUpdateTask.h
|
minecraft/mod/tasks/LocalModUpdateTask.h
|
||||||
minecraft/mod/tasks/LocalModUpdateTask.cpp
|
minecraft/mod/tasks/LocalModUpdateTask.cpp
|
||||||
|
minecraft/mod/tasks/LocalResourcePackParseTask.h
|
||||||
|
minecraft/mod/tasks/LocalResourcePackParseTask.cpp
|
||||||
|
minecraft/mod/tasks/LocalTexturePackParseTask.h
|
||||||
|
minecraft/mod/tasks/LocalTexturePackParseTask.cpp
|
||||||
|
|
||||||
# Assets
|
# Assets
|
||||||
minecraft/AssetsUtils.h
|
minecraft/AssetsUtils.h
|
||||||
@ -345,42 +354,6 @@ set(MINECRAFT_SOURCES
|
|||||||
mojang/PackageManifest.cpp
|
mojang/PackageManifest.cpp
|
||||||
minecraft/Agent.h)
|
minecraft/Agent.h)
|
||||||
|
|
||||||
ecm_add_test(minecraft/GradleSpecifier_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
|
|
||||||
TEST_NAME GradleSpecifier)
|
|
||||||
|
|
||||||
if(BUILD_TESTING)
|
|
||||||
add_executable(PackageManifest
|
|
||||||
mojang/PackageManifest_test.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(PackageManifest
|
|
||||||
Launcher_logic
|
|
||||||
Qt${QT_VERSION_MAJOR}::Test
|
|
||||||
)
|
|
||||||
target_include_directories(PackageManifest
|
|
||||||
PRIVATE ../cmake/UnitTest/
|
|
||||||
)
|
|
||||||
add_test(
|
|
||||||
NAME PackageManifest
|
|
||||||
COMMAND PackageManifest
|
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# TODO: needs minecraft/testdata
|
|
||||||
ecm_add_test(minecraft/MojangVersionFormat_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
|
|
||||||
TEST_NAME MojangVersionFormat)
|
|
||||||
|
|
||||||
ecm_add_test(minecraft/Library_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
|
|
||||||
TEST_NAME Library)
|
|
||||||
|
|
||||||
# FIXME: shares data with FileSystem test
|
|
||||||
# TODO: needs testdata
|
|
||||||
ecm_add_test(minecraft/mod/ModFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
|
|
||||||
TEST_NAME ModFolderModel)
|
|
||||||
|
|
||||||
ecm_add_test(minecraft/ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
|
|
||||||
TEST_NAME ParseUtils)
|
|
||||||
|
|
||||||
# the screenshots feature
|
# the screenshots feature
|
||||||
set(SCREENSHOTS_SOURCES
|
set(SCREENSHOTS_SOURCES
|
||||||
screenshots/Screenshot.h
|
screenshots/Screenshot.h
|
||||||
@ -402,9 +375,6 @@ set(TASKS_SOURCES
|
|||||||
tasks/MultipleOptionsTask.cpp
|
tasks/MultipleOptionsTask.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
ecm_add_test(tasks/Task_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
|
|
||||||
TEST_NAME Task)
|
|
||||||
|
|
||||||
set(SETTINGS_SOURCES
|
set(SETTINGS_SOURCES
|
||||||
# Settings
|
# Settings
|
||||||
settings/INIFile.cpp
|
settings/INIFile.cpp
|
||||||
@ -421,9 +391,6 @@ set(SETTINGS_SOURCES
|
|||||||
settings/SettingsObject.h
|
settings/SettingsObject.h
|
||||||
)
|
)
|
||||||
|
|
||||||
ecm_add_test(settings/INIFile_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
|
|
||||||
TEST_NAME INIFile)
|
|
||||||
|
|
||||||
set(JAVA_SOURCES
|
set(JAVA_SOURCES
|
||||||
java/JavaChecker.h
|
java/JavaChecker.h
|
||||||
java/JavaChecker.cpp
|
java/JavaChecker.cpp
|
||||||
@ -439,9 +406,6 @@ set(JAVA_SOURCES
|
|||||||
java/JavaVersion.cpp
|
java/JavaVersion.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
ecm_add_test(java/JavaVersion_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
|
|
||||||
TEST_NAME JavaVersion)
|
|
||||||
|
|
||||||
set(TRANSLATIONS_SOURCES
|
set(TRANSLATIONS_SOURCES
|
||||||
translations/TranslationsModel.h
|
translations/TranslationsModel.h
|
||||||
translations/TranslationsModel.cpp
|
translations/TranslationsModel.cpp
|
||||||
@ -494,6 +458,10 @@ set(API_SOURCES
|
|||||||
modplatform/modrinth/ModrinthAPI.cpp
|
modplatform/modrinth/ModrinthAPI.cpp
|
||||||
modplatform/helpers/NetworkModAPI.h
|
modplatform/helpers/NetworkModAPI.h
|
||||||
modplatform/helpers/NetworkModAPI.cpp
|
modplatform/helpers/NetworkModAPI.cpp
|
||||||
|
modplatform/helpers/HashUtils.h
|
||||||
|
modplatform/helpers/HashUtils.cpp
|
||||||
|
modplatform/helpers/OverrideUtils.h
|
||||||
|
modplatform/helpers/OverrideUtils.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(FTB_SOURCES
|
set(FTB_SOURCES
|
||||||
@ -519,6 +487,8 @@ set(FLAME_SOURCES
|
|||||||
modplatform/flame/FileResolvingTask.cpp
|
modplatform/flame/FileResolvingTask.cpp
|
||||||
modplatform/flame/FlameCheckUpdate.cpp
|
modplatform/flame/FlameCheckUpdate.cpp
|
||||||
modplatform/flame/FlameCheckUpdate.h
|
modplatform/flame/FlameCheckUpdate.h
|
||||||
|
modplatform/flame/FlameInstanceCreationTask.h
|
||||||
|
modplatform/flame/FlameInstanceCreationTask.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(MODRINTH_SOURCES
|
set(MODRINTH_SOURCES
|
||||||
@ -528,6 +498,8 @@ set(MODRINTH_SOURCES
|
|||||||
modplatform/modrinth/ModrinthPackManifest.h
|
modplatform/modrinth/ModrinthPackManifest.h
|
||||||
modplatform/modrinth/ModrinthCheckUpdate.cpp
|
modplatform/modrinth/ModrinthCheckUpdate.cpp
|
||||||
modplatform/modrinth/ModrinthCheckUpdate.h
|
modplatform/modrinth/ModrinthCheckUpdate.h
|
||||||
|
modplatform/modrinth/ModrinthInstanceCreationTask.cpp
|
||||||
|
modplatform/modrinth/ModrinthInstanceCreationTask.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(MODPACKSCH_SOURCES
|
set(MODPACKSCH_SOURCES
|
||||||
@ -542,9 +514,6 @@ set(PACKWIZ_SOURCES
|
|||||||
modplatform/packwiz/Packwiz.cpp
|
modplatform/packwiz/Packwiz.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: needs modplatform/packwiz/testdata
|
|
||||||
ecm_add_test(modplatform/packwiz/Packwiz_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
|
|
||||||
TEST_NAME Packwiz)
|
|
||||||
|
|
||||||
set(TECHNIC_SOURCES
|
set(TECHNIC_SOURCES
|
||||||
modplatform/technic/SingleZipPackInstallTask.h
|
modplatform/technic/SingleZipPackInstallTask.h
|
||||||
@ -568,9 +537,6 @@ set(ATLAUNCHER_SOURCES
|
|||||||
modplatform/atlauncher/ATLShareCode.h
|
modplatform/atlauncher/ATLShareCode.h
|
||||||
)
|
)
|
||||||
|
|
||||||
ecm_add_test(meta/Index_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
|
|
||||||
TEST_NAME Index)
|
|
||||||
|
|
||||||
################################ COMPILE ################################
|
################################ COMPILE ################################
|
||||||
|
|
||||||
# we need zlib
|
# we need zlib
|
||||||
@ -764,6 +730,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
|
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
|
||||||
ui/pages/modplatform/atlauncher/AtlPage.cpp
|
ui/pages/modplatform/atlauncher/AtlPage.cpp
|
||||||
ui/pages/modplatform/atlauncher/AtlPage.h
|
ui/pages/modplatform/atlauncher/AtlPage.h
|
||||||
|
ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp
|
||||||
|
ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h
|
||||||
|
|
||||||
ui/pages/modplatform/ftb/FtbFilterModel.cpp
|
ui/pages/modplatform/ftb/FtbFilterModel.cpp
|
||||||
ui/pages/modplatform/ftb/FtbFilterModel.h
|
ui/pages/modplatform/ftb/FtbFilterModel.h
|
||||||
@ -849,6 +817,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/dialogs/ModDownloadDialog.h
|
ui/dialogs/ModDownloadDialog.h
|
||||||
ui/dialogs/ScrollMessageBox.cpp
|
ui/dialogs/ScrollMessageBox.cpp
|
||||||
ui/dialogs/ScrollMessageBox.h
|
ui/dialogs/ScrollMessageBox.h
|
||||||
|
ui/dialogs/BlockedModsDialog.cpp
|
||||||
|
ui/dialogs/BlockedModsDialog.h
|
||||||
ui/dialogs/ChooseProviderDialog.h
|
ui/dialogs/ChooseProviderDialog.h
|
||||||
ui/dialogs/ChooseProviderDialog.cpp
|
ui/dialogs/ChooseProviderDialog.cpp
|
||||||
ui/dialogs/ModUpdateDialog.cpp
|
ui/dialogs/ModUpdateDialog.cpp
|
||||||
@ -875,8 +845,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/widgets/LineSeparator.h
|
ui/widgets/LineSeparator.h
|
||||||
ui/widgets/LogView.cpp
|
ui/widgets/LogView.cpp
|
||||||
ui/widgets/LogView.h
|
ui/widgets/LogView.h
|
||||||
ui/widgets/MCModInfoFrame.cpp
|
ui/widgets/InfoFrame.cpp
|
||||||
ui/widgets/MCModInfoFrame.h
|
ui/widgets/InfoFrame.h
|
||||||
ui/widgets/ModFilterWidget.cpp
|
ui/widgets/ModFilterWidget.cpp
|
||||||
ui/widgets/ModFilterWidget.h
|
ui/widgets/ModFilterWidget.h
|
||||||
ui/widgets/ModListView.cpp
|
ui/widgets/ModListView.cpp
|
||||||
@ -884,6 +854,12 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/widgets/PageContainer.cpp
|
ui/widgets/PageContainer.cpp
|
||||||
ui/widgets/PageContainer.h
|
ui/widgets/PageContainer.h
|
||||||
ui/widgets/PageContainer_p.h
|
ui/widgets/PageContainer_p.h
|
||||||
|
ui/widgets/ProjectDescriptionPage.h
|
||||||
|
ui/widgets/ProjectDescriptionPage.cpp
|
||||||
|
ui/widgets/VariableSizedImageObject.h
|
||||||
|
ui/widgets/VariableSizedImageObject.cpp
|
||||||
|
ui/widgets/ProjectItem.h
|
||||||
|
ui/widgets/ProjectItem.cpp
|
||||||
ui/widgets/VersionListView.cpp
|
ui/widgets/VersionListView.cpp
|
||||||
ui/widgets/VersionListView.h
|
ui/widgets/VersionListView.h
|
||||||
ui/widgets/VersionSelectWidget.cpp
|
ui/widgets/VersionSelectWidget.cpp
|
||||||
@ -907,6 +883,16 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/instanceview/VisualGroup.h
|
ui/instanceview/VisualGroup.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
set(LAUNCHER_SOURCES
|
||||||
|
${LAUNCHER_SOURCES}
|
||||||
|
|
||||||
|
# GUI - dark titlebar for Windows 10/11
|
||||||
|
ui/WinDarkmode.h
|
||||||
|
ui/WinDarkmode.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
qt_wrap_ui(LAUNCHER_UI
|
qt_wrap_ui(LAUNCHER_UI
|
||||||
ui/setupwizard/PasteWizardPage.ui
|
ui/setupwizard/PasteWizardPage.ui
|
||||||
ui/pages/global/AccountListPage.ui
|
ui/pages/global/AccountListPage.ui
|
||||||
@ -938,7 +924,7 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/pages/modplatform/technic/TechnicPage.ui
|
ui/pages/modplatform/technic/TechnicPage.ui
|
||||||
ui/widgets/InstanceCardWidget.ui
|
ui/widgets/InstanceCardWidget.ui
|
||||||
ui/widgets/CustomCommands.ui
|
ui/widgets/CustomCommands.ui
|
||||||
ui/widgets/MCModInfoFrame.ui
|
ui/widgets/InfoFrame.ui
|
||||||
ui/widgets/ModFilterWidget.ui
|
ui/widgets/ModFilterWidget.ui
|
||||||
ui/dialogs/CopyInstanceDialog.ui
|
ui/dialogs/CopyInstanceDialog.ui
|
||||||
ui/dialogs/ProfileSetupDialog.ui
|
ui/dialogs/ProfileSetupDialog.ui
|
||||||
@ -958,6 +944,7 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/dialogs/EditAccountDialog.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/ChooseProviderDialog.ui
|
ui/dialogs/ChooseProviderDialog.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -982,17 +969,17 @@ endif()
|
|||||||
|
|
||||||
# Add executable
|
# Add executable
|
||||||
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
|
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
|
||||||
|
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
target_link_libraries(Launcher_logic
|
target_link_libraries(Launcher_logic
|
||||||
systeminfo
|
systeminfo
|
||||||
Launcher_classparser
|
|
||||||
Launcher_murmur2
|
Launcher_murmur2
|
||||||
nbt++
|
nbt++
|
||||||
${ZLIB_LIBRARIES}
|
${ZLIB_LIBRARIES}
|
||||||
optional-bare
|
tomlplusplus::tomlplusplus
|
||||||
tomlc99
|
|
||||||
BuildConfig
|
BuildConfig
|
||||||
Katabasis
|
Katabasis
|
||||||
Qt${QT_VERSION_MAJOR}::Widgets
|
Qt${QT_VERSION_MAJOR}::Widgets
|
||||||
|
ghcFilesystem::ghc_filesystem
|
||||||
)
|
)
|
||||||
|
|
||||||
if (UNIX AND NOT CYGWIN AND NOT APPLE)
|
if (UNIX AND NOT CYGWIN AND NOT APPLE)
|
||||||
|
@ -92,412 +92,4 @@ QStringList splitArgs(QString args)
|
|||||||
argv << current;
|
argv << current;
|
||||||
return argv;
|
return argv;
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser::Parser(FlagStyle::Enum flagStyle, ArgumentStyle::Enum argStyle)
|
|
||||||
{
|
|
||||||
m_flagStyle = flagStyle;
|
|
||||||
m_argStyle = argStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
// styles setter/getter
|
|
||||||
void Parser::setArgumentStyle(ArgumentStyle::Enum style)
|
|
||||||
{
|
|
||||||
m_argStyle = style;
|
|
||||||
}
|
|
||||||
ArgumentStyle::Enum Parser::argumentStyle()
|
|
||||||
{
|
|
||||||
return m_argStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Parser::setFlagStyle(FlagStyle::Enum style)
|
|
||||||
{
|
|
||||||
m_flagStyle = style;
|
|
||||||
}
|
|
||||||
FlagStyle::Enum Parser::flagStyle()
|
|
||||||
{
|
|
||||||
return m_flagStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup methods
|
|
||||||
void Parser::addSwitch(QString name, bool def)
|
|
||||||
{
|
|
||||||
if (m_params.contains(name))
|
|
||||||
throw "Name not unique";
|
|
||||||
|
|
||||||
OptionDef *param = new OptionDef;
|
|
||||||
param->type = otSwitch;
|
|
||||||
param->name = name;
|
|
||||||
param->metavar = QString("<%1>").arg(name);
|
|
||||||
param->def = def;
|
|
||||||
|
|
||||||
m_options[name] = param;
|
|
||||||
m_params[name] = (CommonDef *)param;
|
|
||||||
m_optionList.append(param);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Parser::addOption(QString name, QVariant def)
|
|
||||||
{
|
|
||||||
if (m_params.contains(name))
|
|
||||||
throw "Name not unique";
|
|
||||||
|
|
||||||
OptionDef *param = new OptionDef;
|
|
||||||
param->type = otOption;
|
|
||||||
param->name = name;
|
|
||||||
param->metavar = QString("<%1>").arg(name);
|
|
||||||
param->def = def;
|
|
||||||
|
|
||||||
m_options[name] = param;
|
|
||||||
m_params[name] = (CommonDef *)param;
|
|
||||||
m_optionList.append(param);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Parser::addArgument(QString name, bool required, QVariant def)
|
|
||||||
{
|
|
||||||
if (m_params.contains(name))
|
|
||||||
throw "Name not unique";
|
|
||||||
|
|
||||||
PositionalDef *param = new PositionalDef;
|
|
||||||
param->name = name;
|
|
||||||
param->def = def;
|
|
||||||
param->required = required;
|
|
||||||
param->metavar = name;
|
|
||||||
|
|
||||||
m_positionals.append(param);
|
|
||||||
m_params[name] = (CommonDef *)param;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Parser::addDocumentation(QString name, QString doc, QString metavar)
|
|
||||||
{
|
|
||||||
if (!m_params.contains(name))
|
|
||||||
throw "Name does not exist";
|
|
||||||
|
|
||||||
CommonDef *param = m_params[name];
|
|
||||||
param->doc = doc;
|
|
||||||
if (!metavar.isNull())
|
|
||||||
param->metavar = metavar;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Parser::addShortOpt(QString name, QChar flag)
|
|
||||||
{
|
|
||||||
if (!m_params.contains(name))
|
|
||||||
throw "Name does not exist";
|
|
||||||
if (!m_options.contains(name))
|
|
||||||
throw "Name is not an Option or Swtich";
|
|
||||||
|
|
||||||
OptionDef *param = m_options[name];
|
|
||||||
m_flags[flag] = param;
|
|
||||||
param->flag = flag;
|
|
||||||
}
|
|
||||||
|
|
||||||
// help methods
|
|
||||||
QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags)
|
|
||||||
{
|
|
||||||
QStringList help;
|
|
||||||
help << compileUsage(progName, useFlags) << "\r\n";
|
|
||||||
|
|
||||||
// positionals
|
|
||||||
if (!m_positionals.isEmpty())
|
|
||||||
{
|
|
||||||
help << "\r\n";
|
|
||||||
help << "Positional arguments:\r\n";
|
|
||||||
QListIterator<PositionalDef *> it2(m_positionals);
|
|
||||||
while (it2.hasNext())
|
|
||||||
{
|
|
||||||
PositionalDef *param = it2.next();
|
|
||||||
help << " " << param->metavar;
|
|
||||||
help << " " << QString(helpIndent - param->metavar.length() - 1, ' ');
|
|
||||||
help << param->doc << "\r\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options
|
|
||||||
if (!m_optionList.isEmpty())
|
|
||||||
{
|
|
||||||
help << "\r\n";
|
|
||||||
QString optPrefix, flagPrefix;
|
|
||||||
getPrefix(optPrefix, flagPrefix);
|
|
||||||
|
|
||||||
help << "Options & Switches:\r\n";
|
|
||||||
QListIterator<OptionDef *> it(m_optionList);
|
|
||||||
while (it.hasNext())
|
|
||||||
{
|
|
||||||
OptionDef *option = it.next();
|
|
||||||
help << " ";
|
|
||||||
int nameLength = optPrefix.length() + option->name.length();
|
|
||||||
if (!option->flag.isNull())
|
|
||||||
{
|
|
||||||
nameLength += 3 + flagPrefix.length();
|
|
||||||
help << flagPrefix << option->flag << ", ";
|
|
||||||
}
|
|
||||||
help << optPrefix << option->name;
|
|
||||||
if (option->type == otOption)
|
|
||||||
{
|
|
||||||
QString arg = QString("%1%2").arg(
|
|
||||||
((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar);
|
|
||||||
nameLength += arg.length();
|
|
||||||
help << arg;
|
|
||||||
}
|
|
||||||
help << " " << QString(helpIndent - nameLength - 1, ' ');
|
|
||||||
help << option->doc << "\r\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return help.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Parser::compileUsage(QString progName, bool useFlags)
|
|
||||||
{
|
|
||||||
QStringList usage;
|
|
||||||
usage << "Usage: " << progName;
|
|
||||||
|
|
||||||
QString optPrefix, flagPrefix;
|
|
||||||
getPrefix(optPrefix, flagPrefix);
|
|
||||||
|
|
||||||
// options
|
|
||||||
QListIterator<OptionDef *> it(m_optionList);
|
|
||||||
while (it.hasNext())
|
|
||||||
{
|
|
||||||
OptionDef *option = it.next();
|
|
||||||
usage << " [";
|
|
||||||
if (!option->flag.isNull() && useFlags)
|
|
||||||
usage << flagPrefix << option->flag;
|
|
||||||
else
|
|
||||||
usage << optPrefix << option->name;
|
|
||||||
if (option->type == otOption)
|
|
||||||
usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar;
|
|
||||||
usage << "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
// arguments
|
|
||||||
QListIterator<PositionalDef *> it2(m_positionals);
|
|
||||||
while (it2.hasNext())
|
|
||||||
{
|
|
||||||
PositionalDef *param = it2.next();
|
|
||||||
usage << " " << (param->required ? "<" : "[");
|
|
||||||
usage << param->metavar;
|
|
||||||
usage << (param->required ? ">" : "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
return usage.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
// parsing
|
|
||||||
QHash<QString, QVariant> Parser::parse(QStringList argv)
|
|
||||||
{
|
|
||||||
QHash<QString, QVariant> map;
|
|
||||||
|
|
||||||
QStringListIterator it(argv);
|
|
||||||
QString programName = it.next();
|
|
||||||
|
|
||||||
QString optionPrefix;
|
|
||||||
QString flagPrefix;
|
|
||||||
QListIterator<PositionalDef *> positionals(m_positionals);
|
|
||||||
QStringList expecting;
|
|
||||||
|
|
||||||
getPrefix(optionPrefix, flagPrefix);
|
|
||||||
|
|
||||||
while (it.hasNext())
|
|
||||||
{
|
|
||||||
QString arg = it.next();
|
|
||||||
|
|
||||||
if (!expecting.isEmpty())
|
|
||||||
// we were expecting an argument
|
|
||||||
{
|
|
||||||
QString name = expecting.first();
|
|
||||||
/*
|
|
||||||
if (map.contains(name))
|
|
||||||
throw ParsingError(
|
|
||||||
QString("Option %2%1 was given multiple times").arg(name, optionPrefix));
|
|
||||||
*/
|
|
||||||
map[name] = QVariant(arg);
|
|
||||||
|
|
||||||
expecting.removeFirst();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg.startsWith(optionPrefix))
|
|
||||||
// we have an option
|
|
||||||
{
|
|
||||||
// qDebug("Found option %s", qPrintable(arg));
|
|
||||||
|
|
||||||
QString name = arg.mid(optionPrefix.length());
|
|
||||||
QString equals;
|
|
||||||
|
|
||||||
if ((m_argStyle == ArgumentStyle::Equals ||
|
|
||||||
m_argStyle == ArgumentStyle::SpaceAndEquals) &&
|
|
||||||
name.contains("="))
|
|
||||||
{
|
|
||||||
int i = name.indexOf("=");
|
|
||||||
equals = name.mid(i + 1);
|
|
||||||
name = name.left(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_options.contains(name))
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
if (map.contains(name))
|
|
||||||
throw ParsingError(QString("Option %2%1 was given multiple times")
|
|
||||||
.arg(name, optionPrefix));
|
|
||||||
*/
|
|
||||||
OptionDef *option = m_options[name];
|
|
||||||
if (option->type == otSwitch)
|
|
||||||
map[name] = true;
|
|
||||||
else // if (option->type == otOption)
|
|
||||||
{
|
|
||||||
if (m_argStyle == ArgumentStyle::Space)
|
|
||||||
expecting.append(name);
|
|
||||||
else if (!equals.isNull())
|
|
||||||
map[name] = equals;
|
|
||||||
else if (m_argStyle == ArgumentStyle::SpaceAndEquals)
|
|
||||||
expecting.append(name);
|
|
||||||
else
|
|
||||||
throw ParsingError(QString("Option %2%1 reqires an argument.")
|
|
||||||
.arg(name, optionPrefix));
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg.startsWith(flagPrefix))
|
|
||||||
// we have (a) flag(s)
|
|
||||||
{
|
|
||||||
// qDebug("Found flags %s", qPrintable(arg));
|
|
||||||
|
|
||||||
QString flags = arg.mid(flagPrefix.length());
|
|
||||||
QString equals;
|
|
||||||
|
|
||||||
if ((m_argStyle == ArgumentStyle::Equals ||
|
|
||||||
m_argStyle == ArgumentStyle::SpaceAndEquals) &&
|
|
||||||
flags.contains("="))
|
|
||||||
{
|
|
||||||
int i = flags.indexOf("=");
|
|
||||||
equals = flags.mid(i + 1);
|
|
||||||
flags = flags.left(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < flags.length(); i++)
|
|
||||||
{
|
|
||||||
QChar flag = flags.at(i);
|
|
||||||
|
|
||||||
if (!m_flags.contains(flag))
|
|
||||||
throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix));
|
|
||||||
|
|
||||||
OptionDef *option = m_flags[flag];
|
|
||||||
/*
|
|
||||||
if (map.contains(option->name))
|
|
||||||
throw ParsingError(QString("Option %2%1 was given multiple times")
|
|
||||||
.arg(option->name, optionPrefix));
|
|
||||||
*/
|
|
||||||
if (option->type == otSwitch)
|
|
||||||
map[option->name] = true;
|
|
||||||
else // if (option->type == otOption)
|
|
||||||
{
|
|
||||||
if (m_argStyle == ArgumentStyle::Space)
|
|
||||||
expecting.append(option->name);
|
|
||||||
else if (!equals.isNull())
|
|
||||||
if (i == flags.length() - 1)
|
|
||||||
map[option->name] = equals;
|
|
||||||
else
|
|
||||||
throw ParsingError(QString("Flag %4%2 of Argument-requiring Option "
|
|
||||||
"%1 not last flag in %4%3")
|
|
||||||
.arg(option->name, flag, flags, flagPrefix));
|
|
||||||
else if (m_argStyle == ArgumentStyle::SpaceAndEquals)
|
|
||||||
expecting.append(option->name);
|
|
||||||
else
|
|
||||||
throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)")
|
|
||||||
.arg(option->name, flag, flagPrefix));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// must be a positional argument
|
|
||||||
if (!positionals.hasNext())
|
|
||||||
throw ParsingError(QString("Don't know what to do with '%1'").arg(arg));
|
|
||||||
|
|
||||||
PositionalDef *param = positionals.next();
|
|
||||||
|
|
||||||
map[param->name] = arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if we're missing something
|
|
||||||
if (!expecting.isEmpty())
|
|
||||||
throw ParsingError(QString("Was still expecting arguments for %2%1").arg(
|
|
||||||
expecting.join(QString(", ") + optionPrefix), optionPrefix));
|
|
||||||
|
|
||||||
while (positionals.hasNext())
|
|
||||||
{
|
|
||||||
PositionalDef *param = positionals.next();
|
|
||||||
if (param->required)
|
|
||||||
throw ParsingError(
|
|
||||||
QString("Missing required positional argument '%1'").arg(param->name));
|
|
||||||
else
|
|
||||||
map[param->name] = param->def;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill out gaps
|
|
||||||
QListIterator<OptionDef *> iter(m_optionList);
|
|
||||||
while (iter.hasNext())
|
|
||||||
{
|
|
||||||
OptionDef *option = iter.next();
|
|
||||||
if (!map.contains(option->name))
|
|
||||||
map[option->name] = option->def;
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear defs
|
|
||||||
void Parser::clear()
|
|
||||||
{
|
|
||||||
m_flags.clear();
|
|
||||||
m_params.clear();
|
|
||||||
m_options.clear();
|
|
||||||
|
|
||||||
QMutableListIterator<OptionDef *> it(m_optionList);
|
|
||||||
while (it.hasNext())
|
|
||||||
{
|
|
||||||
OptionDef *option = it.next();
|
|
||||||
it.remove();
|
|
||||||
delete option;
|
|
||||||
}
|
|
||||||
|
|
||||||
QMutableListIterator<PositionalDef *> it2(m_positionals);
|
|
||||||
while (it2.hasNext())
|
|
||||||
{
|
|
||||||
PositionalDef *arg = it2.next();
|
|
||||||
it2.remove();
|
|
||||||
delete arg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destructor
|
|
||||||
Parser::~Parser()
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPrefix
|
|
||||||
void Parser::getPrefix(QString &opt, QString &flag)
|
|
||||||
{
|
|
||||||
if (m_flagStyle == FlagStyle::Windows)
|
|
||||||
opt = flag = "/";
|
|
||||||
else if (m_flagStyle == FlagStyle::Unix)
|
|
||||||
opt = flag = "-";
|
|
||||||
// else if (m_flagStyle == FlagStyle::GNU)
|
|
||||||
else
|
|
||||||
{
|
|
||||||
opt = "--";
|
|
||||||
flag = "-";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParsingError
|
|
||||||
ParsingError::ParsingError(const QString &what) : std::runtime_error(what.toStdString())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <exception>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVariant>
|
|
||||||
#include <QHash>
|
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,212 +34,4 @@ namespace Commandline
|
|||||||
* @return a QStringList containing all arguments
|
* @return a QStringList containing all arguments
|
||||||
*/
|
*/
|
||||||
QStringList splitArgs(QString args);
|
QStringList splitArgs(QString args);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The FlagStyle enum
|
|
||||||
* Specifies how flags are decorated
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace FlagStyle
|
|
||||||
{
|
|
||||||
enum Enum
|
|
||||||
{
|
|
||||||
GNU, /**< --option and -o (GNU Style) */
|
|
||||||
Unix, /**< -option and -o (Unix Style) */
|
|
||||||
Windows, /**< /option and /o (Windows Style) */
|
|
||||||
#ifdef Q_OS_WIN32
|
|
||||||
Default = Windows
|
|
||||||
#else
|
|
||||||
Default = GNU
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The ArgumentStyle enum
|
|
||||||
*/
|
|
||||||
namespace ArgumentStyle
|
|
||||||
{
|
|
||||||
enum Enum
|
|
||||||
{
|
|
||||||
Space, /**< --option value */
|
|
||||||
Equals, /**< --option=value */
|
|
||||||
SpaceAndEquals, /**< --option[= ]value */
|
|
||||||
#ifdef Q_OS_WIN32
|
|
||||||
Default = Equals
|
|
||||||
#else
|
|
||||||
Default = SpaceAndEquals
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The ParsingError class
|
|
||||||
*/
|
|
||||||
class ParsingError : public std::runtime_error
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ParsingError(const QString &what);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The Parser class
|
|
||||||
*/
|
|
||||||
class Parser
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Parser constructor
|
|
||||||
* @param flagStyle the FlagStyle to use in this Parser
|
|
||||||
* @param argStyle the ArgumentStyle to use in this Parser
|
|
||||||
*/
|
|
||||||
Parser(FlagStyle::Enum flagStyle = FlagStyle::Default,
|
|
||||||
ArgumentStyle::Enum argStyle = ArgumentStyle::Default);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief set the flag style
|
|
||||||
* @param style
|
|
||||||
*/
|
|
||||||
void setFlagStyle(FlagStyle::Enum style);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief get the flag style
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
FlagStyle::Enum flagStyle();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief set the argument style
|
|
||||||
* @param style
|
|
||||||
*/
|
|
||||||
void setArgumentStyle(ArgumentStyle::Enum style);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief get the argument style
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
ArgumentStyle::Enum argumentStyle();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief define a boolean switch
|
|
||||||
* @param name the parameter name
|
|
||||||
* @param def the default value
|
|
||||||
*/
|
|
||||||
void addSwitch(QString name, bool def = false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief define an option that takes an additional argument
|
|
||||||
* @param name the parameter name
|
|
||||||
* @param def the default value
|
|
||||||
*/
|
|
||||||
void addOption(QString name, QVariant def = QVariant());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief define a positional argument
|
|
||||||
* @param name the parameter name
|
|
||||||
* @param required wether this argument is required
|
|
||||||
* @param def the default value
|
|
||||||
*/
|
|
||||||
void addArgument(QString name, bool required = true, QVariant def = QVariant());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief adds a flag to an existing parameter
|
|
||||||
* @param name the (existing) parameter name
|
|
||||||
* @param flag the flag character
|
|
||||||
* @see addSwitch addArgument addOption
|
|
||||||
* Note: any one parameter can only have one flag
|
|
||||||
*/
|
|
||||||
void addShortOpt(QString name, QChar flag);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief adds documentation to a Parameter
|
|
||||||
* @param name the parameter name
|
|
||||||
* @param metavar a string to be displayed as placeholder for the value
|
|
||||||
* @param doc a QString containing the documentation
|
|
||||||
* Note: on positional arguments, metavar replaces the name as displayed.
|
|
||||||
* on options , metavar replaces the value placeholder
|
|
||||||
*/
|
|
||||||
void addDocumentation(QString name, QString doc, QString metavar = QString());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief generate a help message
|
|
||||||
* @param progName the program name to use in the help message
|
|
||||||
* @param helpIndent how much the parameter documentation should be indented
|
|
||||||
* @param flagsInUsage whether we should use flags instead of options in the usage
|
|
||||||
* @return a help message
|
|
||||||
*/
|
|
||||||
QString compileHelp(QString progName, int helpIndent = 22, bool flagsInUsage = true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief generate a short usage message
|
|
||||||
* @param progName the program name to use in the usage message
|
|
||||||
* @param useFlags whether we should use flags instead of options
|
|
||||||
* @return a usage message
|
|
||||||
*/
|
|
||||||
QString compileUsage(QString progName, bool useFlags = true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief parse
|
|
||||||
* @param argv a QStringList containing the program ARGV
|
|
||||||
* @return a QHash mapping argument names to their values
|
|
||||||
*/
|
|
||||||
QHash<QString, QVariant> parse(QStringList argv);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief clear all definitions
|
|
||||||
*/
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
~Parser();
|
|
||||||
|
|
||||||
private:
|
|
||||||
FlagStyle::Enum m_flagStyle;
|
|
||||||
ArgumentStyle::Enum m_argStyle;
|
|
||||||
|
|
||||||
enum OptionType
|
|
||||||
{
|
|
||||||
otSwitch,
|
|
||||||
otOption
|
|
||||||
};
|
|
||||||
|
|
||||||
// Important: the common part MUST BE COMMON ON ALL THREE structs
|
|
||||||
struct CommonDef
|
|
||||||
{
|
|
||||||
QString name;
|
|
||||||
QString doc;
|
|
||||||
QString metavar;
|
|
||||||
QVariant def;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OptionDef
|
|
||||||
{
|
|
||||||
// common
|
|
||||||
QString name;
|
|
||||||
QString doc;
|
|
||||||
QString metavar;
|
|
||||||
QVariant def;
|
|
||||||
// option
|
|
||||||
OptionType type;
|
|
||||||
QChar flag;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PositionalDef
|
|
||||||
{
|
|
||||||
// common
|
|
||||||
QString name;
|
|
||||||
QString doc;
|
|
||||||
QString metavar;
|
|
||||||
QVariant def;
|
|
||||||
// positional
|
|
||||||
bool required;
|
|
||||||
};
|
|
||||||
|
|
||||||
QHash<QString, OptionDef *> m_options;
|
|
||||||
QHash<QChar, OptionDef *> m_flags;
|
|
||||||
QHash<QString, CommonDef *> m_params;
|
|
||||||
QList<PositionalDef *> m_positionals;
|
|
||||||
QList<OptionDef *> m_optionList;
|
|
||||||
|
|
||||||
void getPrefix(QString &opt, QString &flag);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ bool openDirectory(const QString &path, bool ensureExists)
|
|||||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
|
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
|
||||||
};
|
};
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||||
if(!APPLICATION->isFlatpak())
|
if(!isFlatpak())
|
||||||
{
|
{
|
||||||
return IndirectOpen(f);
|
return IndirectOpen(f);
|
||||||
}
|
}
|
||||||
@ -140,7 +140,7 @@ bool openFile(const QString &path)
|
|||||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||||
};
|
};
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||||
if(!APPLICATION->isFlatpak())
|
if(!isFlatpak())
|
||||||
{
|
{
|
||||||
return IndirectOpen(f);
|
return IndirectOpen(f);
|
||||||
}
|
}
|
||||||
@ -158,7 +158,7 @@ bool openFile(const QString &application, const QString &path, const QString &wo
|
|||||||
qDebug() << "Opening file" << path << "using" << application;
|
qDebug() << "Opening file" << path << "using" << application;
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||||
if(!APPLICATION->isFlatpak())
|
if(!isFlatpak())
|
||||||
{
|
{
|
||||||
return IndirectOpen([&]()
|
return IndirectOpen([&]()
|
||||||
{
|
{
|
||||||
@ -178,7 +178,7 @@ bool run(const QString &application, const QStringList &args, const QString &wor
|
|||||||
{
|
{
|
||||||
qDebug() << "Running" << application << "with args" << args.join(' ');
|
qDebug() << "Running" << application << "with args" << args.join(' ');
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||||
if(!APPLICATION->isFlatpak())
|
if(!isFlatpak())
|
||||||
{
|
{
|
||||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||||
return IndirectOpen([&]()
|
return IndirectOpen([&]()
|
||||||
@ -203,7 +203,7 @@ bool openUrl(const QUrl &url)
|
|||||||
return QDesktopServices::openUrl(url);
|
return QDesktopServices::openUrl(url);
|
||||||
};
|
};
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||||
if(!APPLICATION->isFlatpak())
|
if(!isFlatpak())
|
||||||
{
|
{
|
||||||
return IndirectOpen(f);
|
return IndirectOpen(f);
|
||||||
}
|
}
|
||||||
@ -216,4 +216,13 @@ bool openUrl(const QUrl &url)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isFlatpak()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
return QFile::exists("/.flatpak-info");
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,4 +33,6 @@ namespace DesktopServices
|
|||||||
* Open the URL, most likely in a browser. Maybe.
|
* Open the URL, most likely in a browser. Maybe.
|
||||||
*/
|
*/
|
||||||
bool openUrl(const QUrl &url);
|
bool openUrl(const QUrl &url);
|
||||||
|
|
||||||
|
bool isFlatpak();
|
||||||
}
|
}
|
||||||
|
@ -35,76 +35,101 @@
|
|||||||
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
|
||||||
#include <QDir>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QSaveFile>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QUrl>
|
#include <QDir>
|
||||||
|
#include <QDirIterator>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QSaveFile>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
#include <QUrl>
|
||||||
|
#include "DesktopServices.h"
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
#include <windows.h>
|
#include <objbase.h>
|
||||||
#include <string>
|
#include <objidl.h>
|
||||||
#include <sys/utime.h>
|
#include <shlguid.h>
|
||||||
#include <winnls.h>
|
#include <shlobj.h>
|
||||||
#include <shobjidl.h>
|
#include <shobjidl.h>
|
||||||
#include <objbase.h>
|
#include <sys/utime.h>
|
||||||
#include <objidl.h>
|
#include <windows.h>
|
||||||
#include <shlguid.h>
|
#include <winnls.h>
|
||||||
#include <shlobj.h>
|
#include <string>
|
||||||
#else
|
#else
|
||||||
#include <utime.h>
|
#include <utime.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <Availability.h> // for deployment target to support pre-catalina targets without std::fs
|
||||||
|
#endif // __APPLE__
|
||||||
|
|
||||||
|
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
|
||||||
|
#if __has_include(<filesystem>) && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
|
||||||
|
#define GHC_USE_STD_FS
|
||||||
|
#include <filesystem>
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
#endif // MacOS min version check
|
||||||
|
#endif // Other OSes version check
|
||||||
|
|
||||||
|
#ifndef GHC_USE_STD_FS
|
||||||
|
#include <ghc/filesystem.hpp>
|
||||||
|
namespace fs = ghc::filesystem;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined Q_OS_WIN32
|
||||||
|
|
||||||
|
std::wstring toStdString(QString s)
|
||||||
|
{
|
||||||
|
return s.toStdWString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
std::string toStdString(QString s)
|
||||||
|
{
|
||||||
|
return s.toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace FS {
|
namespace FS {
|
||||||
|
|
||||||
void ensureExists(const QDir &dir)
|
void ensureExists(const QDir& dir)
|
||||||
{
|
{
|
||||||
if (!QDir().mkpath(dir.absolutePath()))
|
if (!QDir().mkpath(dir.absolutePath())) {
|
||||||
{
|
throw FileSystemException("Unable to create folder " + dir.dirName() + " (" + dir.absolutePath() + ")");
|
||||||
throw FileSystemException("Unable to create folder " + dir.dirName() + " (" +
|
|
||||||
dir.absolutePath() + ")");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
QSaveFile file(filename);
|
||||||
if (!file.open(QSaveFile::WriteOnly))
|
if (!file.open(QSaveFile::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)) {
|
||||||
{
|
throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
|
||||||
throw FileSystemException("Error writing data to " + filename + ": " +
|
|
||||||
file.errorString());
|
|
||||||
}
|
}
|
||||||
if (!file.commit())
|
if (!file.commit()) {
|
||||||
{
|
throw FileSystemException("Error while committing data to " + filename + ": " + file.errorString());
|
||||||
throw FileSystemException("Error while committing data to " + filename + ": " +
|
|
||||||
file.errorString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray read(const QString &filename)
|
QByteArray read(const QString& filename)
|
||||||
{
|
{
|
||||||
QFile file(filename);
|
QFile file(filename);
|
||||||
if (!file.open(QFile::ReadOnly))
|
if (!file.open(QFile::ReadOnly)) {
|
||||||
{
|
throw FileSystemException("Unable to open " + filename + " for reading: " + file.errorString());
|
||||||
throw FileSystemException("Unable to open " + filename + " for reading: " +
|
|
||||||
file.errorString());
|
|
||||||
}
|
}
|
||||||
const qint64 size = file.size();
|
const qint64 size = file.size();
|
||||||
QByteArray data(int(size), 0);
|
QByteArray data(int(size), 0);
|
||||||
const qint64 ret = file.read(data.data(), size);
|
const qint64 ret = file.read(data.data(), size);
|
||||||
if (ret == -1 || ret != size)
|
if (ret == -1 || ret != size) {
|
||||||
{
|
throw FileSystemException("Error reading data from " + filename + ": " + file.errorString());
|
||||||
throw FileSystemException("Error reading data from " + filename + ": " +
|
|
||||||
file.errorString());
|
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -138,149 +163,102 @@ bool ensureFolderPathExists(QString foldernamepath)
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool copy::operator()(const QString &offset)
|
bool copy::operator()(const QString& offset)
|
||||||
{
|
{
|
||||||
//NOTE always deep copy on windows. the alternatives are too messy.
|
using copy_opts = fs::copy_options;
|
||||||
#if defined Q_OS_WIN32
|
|
||||||
|
// NOTE always deep copy on windows. the alternatives are too messy.
|
||||||
|
#if defined Q_OS_WIN32
|
||||||
m_followSymlinks = true;
|
m_followSymlinks = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto src = PathCombine(m_src.absolutePath(), offset);
|
auto src = PathCombine(m_src.absolutePath(), offset);
|
||||||
auto dst = PathCombine(m_dst.absolutePath(), offset);
|
auto dst = PathCombine(m_dst.absolutePath(), offset);
|
||||||
|
|
||||||
QFileInfo currentSrc(src);
|
std::error_code err;
|
||||||
if (!currentSrc.exists())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if(!m_followSymlinks && currentSrc.isSymLink())
|
fs::copy_options opt = copy_opts::none;
|
||||||
{
|
|
||||||
qDebug() << "creating symlink" << src << " - " << dst;
|
// The default behavior is to follow symlinks
|
||||||
if (!ensureFilePathExists(dst))
|
if (!m_followSymlinks)
|
||||||
{
|
opt |= copy_opts::copy_symlinks;
|
||||||
qWarning() << "Cannot create path!";
|
|
||||||
return false;
|
// Function that'll do the actual copying
|
||||||
|
auto copy_file = [&](QString src_path, QString relative_dst_path) {
|
||||||
|
if (m_blacklist && m_blacklist->matches(relative_dst_path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto dst_path = PathCombine(dst, relative_dst_path);
|
||||||
|
ensureFilePathExists(dst_path);
|
||||||
|
|
||||||
|
fs::copy(toStdString(src_path), toStdString(dst_path), opt, err);
|
||||||
|
if (err) {
|
||||||
|
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
|
||||||
|
qDebug() << "Source file:" << src_path;
|
||||||
|
qDebug() << "Destination file:" << dst_path;
|
||||||
}
|
}
|
||||||
return QFile::link(currentSrc.symLinkTarget(), dst);
|
};
|
||||||
|
|
||||||
|
// We can't use copy_opts::recursive because we need to take into account the
|
||||||
|
// blacklisted paths, so we iterate over the source directory, and if there's no blacklist
|
||||||
|
// match, we copy the file.
|
||||||
|
QDir src_dir(src);
|
||||||
|
QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories);
|
||||||
|
|
||||||
|
while (source_it.hasNext()) {
|
||||||
|
auto src_path = source_it.next();
|
||||||
|
auto relative_path = src_dir.relativeFilePath(src_path);
|
||||||
|
|
||||||
|
copy_file(src_path, relative_path);
|
||||||
}
|
}
|
||||||
else if(currentSrc.isFile())
|
|
||||||
{
|
// If the root src is not a directory, the previous iterator won't run.
|
||||||
qDebug() << "copying file" << src << " - " << dst;
|
if (!fs::is_directory(toStdString(src)))
|
||||||
if (!ensureFilePathExists(dst))
|
copy_file(src, "");
|
||||||
{
|
|
||||||
qWarning() << "Cannot create path!";
|
return err.value() == 0;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return QFile::copy(src, dst);
|
|
||||||
}
|
|
||||||
else if(currentSrc.isDir())
|
|
||||||
{
|
|
||||||
qDebug() << "recursing" << offset;
|
|
||||||
if (!ensureFolderPathExists(dst))
|
|
||||||
{
|
|
||||||
qWarning() << "Cannot create path!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
QDir currentDir(src);
|
|
||||||
for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System))
|
|
||||||
{
|
|
||||||
auto inner_offset = PathCombine(offset, f);
|
|
||||||
// ignore and skip stuff that matches the blacklist.
|
|
||||||
if(m_blacklist && m_blacklist->matches(inner_offset))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(!operator()(inner_offset))
|
|
||||||
{
|
|
||||||
qWarning() << "Failed to copy" << inner_offset;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qCritical() << "Copy ERROR: Unknown filesystem object:" << src;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool deletePath(QString path)
|
bool deletePath(QString path)
|
||||||
{
|
{
|
||||||
bool OK = true;
|
std::error_code err;
|
||||||
QFileInfo finfo(path);
|
|
||||||
if(finfo.isFile()) {
|
fs::remove_all(toStdString(path), err);
|
||||||
return QFile::remove(path);
|
|
||||||
|
if (err) {
|
||||||
|
qWarning() << "Failed to remove files:" << QString::fromStdString(err.message());
|
||||||
}
|
}
|
||||||
|
|
||||||
QDir dir(path);
|
return err.value() == 0;
|
||||||
|
|
||||||
if (!dir.exists())
|
|
||||||
{
|
|
||||||
return OK;
|
|
||||||
}
|
|
||||||
auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
|
|
||||||
QDir::AllDirs | QDir::Files,
|
|
||||||
QDir::DirsFirst);
|
|
||||||
|
|
||||||
for(auto & info: allEntries)
|
|
||||||
{
|
|
||||||
#if defined Q_OS_WIN32
|
|
||||||
QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath());
|
|
||||||
auto wString = nativePath.toStdWString();
|
|
||||||
DWORD dwAttrs = GetFileAttributesW(wString.c_str());
|
|
||||||
// Windows: check for junctions, reparse points and other nasty things of that sort
|
|
||||||
if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT)
|
|
||||||
{
|
|
||||||
if (info.isFile())
|
|
||||||
{
|
|
||||||
OK &= QFile::remove(info.absoluteFilePath());
|
|
||||||
}
|
|
||||||
else if (info.isDir())
|
|
||||||
{
|
|
||||||
OK &= dir.rmdir(info.absoluteFilePath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// We do not trust Qt with reparse points, but do trust it with unix symlinks.
|
|
||||||
if(info.isSymLink())
|
|
||||||
{
|
|
||||||
OK &= QFile::remove(info.absoluteFilePath());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
else if (info.isDir())
|
|
||||||
{
|
|
||||||
OK &= deletePath(info.absoluteFilePath());
|
|
||||||
}
|
|
||||||
else if (info.isFile())
|
|
||||||
{
|
|
||||||
OK &= QFile::remove(info.absoluteFilePath());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
OK = false;
|
|
||||||
qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OK &= dir.rmdir(dir.absolutePath());
|
|
||||||
return OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool trash(QString path, QString *pathInTrash = nullptr)
|
||||||
QString PathCombine(const QString & path1, const QString & path2)
|
|
||||||
{
|
{
|
||||||
if(!path1.size())
|
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
// FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal
|
||||||
|
if (DesktopServices::isFlatpak())
|
||||||
|
return false;
|
||||||
|
return QFile::moveToTrash(path, pathInTrash);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PathCombine(const QString& path1, const QString& path2)
|
||||||
|
{
|
||||||
|
if (!path1.size())
|
||||||
return path2;
|
return path2;
|
||||||
if(!path2.size())
|
if (!path2.size())
|
||||||
return path1;
|
return path1;
|
||||||
return QDir::cleanPath(path1 + QDir::separator() + path2);
|
return QDir::cleanPath(path1 + QDir::separator() + path2);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PathCombine(const QString & path1, const QString & path2, const QString & path3)
|
QString PathCombine(const QString& path1, const QString& path2, const QString& path3)
|
||||||
{
|
{
|
||||||
return PathCombine(PathCombine(path1, path2), path3);
|
return PathCombine(PathCombine(path1, path2), path3);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PathCombine(const QString & path1, const QString & path2, const QString & path3, const QString & path4)
|
QString PathCombine(const QString& path1, const QString& path2, const QString& path3, const QString& path4)
|
||||||
{
|
{
|
||||||
return PathCombine(PathCombine(path1, path2, path3), path4);
|
return PathCombine(PathCombine(path1, path2, path3), path4);
|
||||||
}
|
}
|
||||||
@ -292,17 +270,14 @@ QString AbsolutePath(QString path)
|
|||||||
|
|
||||||
QString ResolveExecutable(QString path)
|
QString ResolveExecutable(QString path)
|
||||||
{
|
{
|
||||||
if (path.isEmpty())
|
if (path.isEmpty()) {
|
||||||
{
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if(!path.contains('/'))
|
if (!path.contains('/')) {
|
||||||
{
|
|
||||||
path = QStandardPaths::findExecutable(path);
|
path = QStandardPaths::findExecutable(path);
|
||||||
}
|
}
|
||||||
QFileInfo pathInfo(path);
|
QFileInfo pathInfo(path);
|
||||||
if(!pathInfo.exists() || !pathInfo.isExecutable())
|
if (!pathInfo.exists() || !pathInfo.isExecutable()) {
|
||||||
{
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
return pathInfo.absoluteFilePath();
|
return pathInfo.absoluteFilePath();
|
||||||
@ -322,12 +297,9 @@ QString NormalizePath(QString path)
|
|||||||
QDir b(path);
|
QDir b(path);
|
||||||
QString newAbsolute = b.absolutePath();
|
QString newAbsolute = b.absolutePath();
|
||||||
|
|
||||||
if (newAbsolute.startsWith(currentAbsolute))
|
if (newAbsolute.startsWith(currentAbsolute)) {
|
||||||
{
|
|
||||||
return a.relativeFilePath(newAbsolute);
|
return a.relativeFilePath(newAbsolute);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return newAbsolute;
|
return newAbsolute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,10 +308,8 @@ QString badFilenameChars = "\"\\/?<>:;*|!+\r\n";
|
|||||||
|
|
||||||
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
|
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < string.length(); i++)
|
for (int i = 0; i < string.length(); i++) {
|
||||||
{
|
if (badFilenameChars.contains(string[i])) {
|
||||||
if (badFilenameChars.contains(string[i]))
|
|
||||||
{
|
|
||||||
string[i] = replaceWith;
|
string[i] = replaceWith;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -351,15 +321,11 @@ QString DirNameFromString(QString string, QString inDir)
|
|||||||
int num = 0;
|
int num = 0;
|
||||||
QString baseName = RemoveInvalidFilenameChars(string, '-');
|
QString baseName = RemoveInvalidFilenameChars(string, '-');
|
||||||
QString dirName;
|
QString dirName;
|
||||||
do
|
do {
|
||||||
{
|
if (num == 0) {
|
||||||
if(num == 0)
|
|
||||||
{
|
|
||||||
dirName = baseName;
|
dirName = baseName;
|
||||||
}
|
} else {
|
||||||
else
|
dirName = baseName + "(" + QString::number(num) + ")";
|
||||||
{
|
|
||||||
dirName = baseName + QString::number(num);;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's over 9000
|
// If it's over 9000
|
||||||
@ -378,63 +344,13 @@ bool checkProblemticPathJava(QDir folder)
|
|||||||
return pathfoldername.contains("!", Qt::CaseInsensitive);
|
return pathfoldername.contains("!", Qt::CaseInsensitive);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Win32 crap
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
|
|
||||||
bool called_coinit = false;
|
|
||||||
|
|
||||||
HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args)
|
|
||||||
{
|
|
||||||
HRESULT hres;
|
|
||||||
|
|
||||||
if (!called_coinit)
|
|
||||||
{
|
|
||||||
hres = CoInitialize(NULL);
|
|
||||||
called_coinit = true;
|
|
||||||
|
|
||||||
if (!SUCCEEDED(hres))
|
|
||||||
{
|
|
||||||
qWarning("Failed to initialize COM. Error 0x%08lX", hres);
|
|
||||||
return hres;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IShellLinkA *link;
|
|
||||||
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
|
|
||||||
(LPVOID *)&link);
|
|
||||||
|
|
||||||
if (SUCCEEDED(hres))
|
|
||||||
{
|
|
||||||
IPersistFile *persistFile;
|
|
||||||
|
|
||||||
link->SetPath(targetPath);
|
|
||||||
link->SetArguments(args);
|
|
||||||
|
|
||||||
hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile);
|
|
||||||
if (SUCCEEDED(hres))
|
|
||||||
{
|
|
||||||
WCHAR wstr[MAX_PATH];
|
|
||||||
|
|
||||||
MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH);
|
|
||||||
|
|
||||||
hres = persistFile->Save(wstr, TRUE);
|
|
||||||
persistFile->Release();
|
|
||||||
}
|
|
||||||
link->Release();
|
|
||||||
}
|
|
||||||
return hres;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QString getDesktopDir()
|
QString getDesktopDir()
|
||||||
{
|
{
|
||||||
return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cross-platform Shortcut creation
|
// Cross-platform Shortcut creation
|
||||||
bool createShortCut(QString location, QString dest, QStringList args, QString name,
|
bool createShortCut(QString location, QString dest, QStringList args, QString name, QString icon)
|
||||||
QString icon)
|
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||||
location = PathCombine(location, name + ".desktop");
|
location = PathCombine(location, name + ".desktop");
|
||||||
@ -459,8 +375,7 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
|
|||||||
stream.flush();
|
stream.flush();
|
||||||
f.close();
|
f.close();
|
||||||
|
|
||||||
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup |
|
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
|
||||||
QFileDevice::ExeOther);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
#elif defined Q_OS_WIN
|
#elif defined Q_OS_WIN
|
||||||
@ -488,46 +403,25 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList listFolderPaths(QDir root)
|
|
||||||
{
|
|
||||||
auto createAbsPath = [](QFileInfo const& entry) { return FS::PathCombine(entry.path(), entry.fileName()); };
|
|
||||||
|
|
||||||
QStringList entries;
|
|
||||||
|
|
||||||
root.refresh();
|
|
||||||
for (auto entry : root.entryInfoList(QDir::Filter::Files)) {
|
|
||||||
entries.append(createAbsPath(entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto entry : root.entryInfoList(QDir::Filter::AllDirs | QDir::Filter::NoDotAndDotDot)) {
|
|
||||||
entries.append(listFolderPaths(createAbsPath(entry)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool overrideFolder(QString overwritten_path, QString override_path)
|
bool overrideFolder(QString overwritten_path, QString override_path)
|
||||||
{
|
{
|
||||||
|
using copy_opts = fs::copy_options;
|
||||||
|
|
||||||
if (!FS::ensureFolderPathExists(overwritten_path))
|
if (!FS::ensureFolderPathExists(overwritten_path))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
QStringList paths_to_override;
|
std::error_code err;
|
||||||
QDir root_override (override_path);
|
fs::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing;
|
||||||
for (auto file : listFolderPaths(root_override)) {
|
|
||||||
QString destination = file;
|
|
||||||
destination.replace(override_path, overwritten_path);
|
|
||||||
|
|
||||||
qDebug() << QString("Applying override %1 in %2").arg(file, destination);
|
// FIXME: hello traveller! Apparently std::copy does NOT overwrite existing files on GNU libstdc++ on Windows?
|
||||||
|
fs::copy(toStdString(override_path), toStdString(overwritten_path), opt, err);
|
||||||
|
|
||||||
if (QFile::exists(destination))
|
if (err) {
|
||||||
QFile::remove(destination);
|
qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path);
|
||||||
if (!QFile::rename(file, destination)) {
|
qCritical() << "Reason:" << QString::fromStdString(err.message());
|
||||||
qCritical() << QString("Failed to apply override from %1 to %2").arg(file, destination);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return err.value() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -41,29 +41,27 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFlags>
|
#include <QFlags>
|
||||||
|
|
||||||
namespace FS
|
namespace FS {
|
||||||
{
|
|
||||||
|
|
||||||
class FileSystemException : public ::Exception
|
class FileSystemException : public ::Exception {
|
||||||
{
|
public:
|
||||||
public:
|
FileSystemException(const QString& message) : Exception(message) {}
|
||||||
FileSystemException(const QString &message) : Exception(message) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* write data to a file safely
|
* write data to a file safely
|
||||||
*/
|
*/
|
||||||
void write(const QString &filename, const QByteArray &data);
|
void write(const QString& filename, const QByteArray& data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* read data from a file safely\
|
* read data from a file safely\
|
||||||
*/
|
*/
|
||||||
QByteArray read(const QString &filename);
|
QByteArray read(const QString& filename);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the last changed timestamp of an existing file
|
* Update the last changed timestamp of an existing file
|
||||||
*/
|
*/
|
||||||
bool updateTimestamp(const QString & filename);
|
bool updateTimestamp(const QString& filename);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates all the folders in a path for the specified path
|
* Creates all the folders in a path for the specified path
|
||||||
@ -77,35 +75,31 @@ bool ensureFilePathExists(QString filenamepath);
|
|||||||
*/
|
*/
|
||||||
bool ensureFolderPathExists(QString filenamepath);
|
bool ensureFolderPathExists(QString filenamepath);
|
||||||
|
|
||||||
class copy
|
class copy {
|
||||||
{
|
public:
|
||||||
public:
|
copy(const QString& src, const QString& dst)
|
||||||
copy(const QString & src, const QString & dst)
|
|
||||||
{
|
{
|
||||||
m_src.setPath(src);
|
m_src.setPath(src);
|
||||||
m_dst.setPath(dst);
|
m_dst.setPath(dst);
|
||||||
}
|
}
|
||||||
copy & followSymlinks(const bool follow)
|
copy& followSymlinks(const bool follow)
|
||||||
{
|
{
|
||||||
m_followSymlinks = follow;
|
m_followSymlinks = follow;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
copy & blacklist(const IPathMatcher * filter)
|
copy& blacklist(const IPathMatcher* filter)
|
||||||
{
|
{
|
||||||
m_blacklist = filter;
|
m_blacklist = filter;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
bool operator()()
|
bool operator()() { return operator()(QString()); }
|
||||||
{
|
|
||||||
return operator()(QString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool operator()(const QString &offset);
|
bool operator()(const QString& offset);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_followSymlinks = true;
|
bool m_followSymlinks = true;
|
||||||
const IPathMatcher * m_blacklist = nullptr;
|
const IPathMatcher* m_blacklist = nullptr;
|
||||||
QDir m_src;
|
QDir m_src;
|
||||||
QDir m_dst;
|
QDir m_dst;
|
||||||
};
|
};
|
||||||
@ -115,9 +109,14 @@ private:
|
|||||||
*/
|
*/
|
||||||
bool deletePath(QString path);
|
bool deletePath(QString path);
|
||||||
|
|
||||||
QString PathCombine(const QString &path1, const QString &path2);
|
/**
|
||||||
QString PathCombine(const QString &path1, const QString &path2, const QString &path3);
|
* Trash a folder / file
|
||||||
QString PathCombine(const QString &path1, const QString &path2, const QString &path3, const QString &path4);
|
*/
|
||||||
|
bool trash(QString path, QString *pathInTrash);
|
||||||
|
|
||||||
|
QString PathCombine(const QString& path1, const QString& path2);
|
||||||
|
QString PathCombine(const QString& path1, const QString& path2, const QString& path3);
|
||||||
|
QString PathCombine(const QString& path1, const QString& path2, const QString& path3, const QString& path4);
|
||||||
|
|
||||||
QString AbsolutePath(QString path);
|
QString AbsolutePath(QString path);
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ bool GZip::unzip(const QByteArray &compressedBytes, QByteArray &uncompressedByte
|
|||||||
uncompLength *= 2;
|
uncompLength *= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
strm.next_out = (Bytef *)(uncompressedBytes.data() + strm.total_out);
|
strm.next_out = reinterpret_cast<Bytef *>((uncompressedBytes.data() + strm.total_out));
|
||||||
strm.avail_out = uncompLength - strm.total_out;
|
strm.avail_out = uncompLength - strm.total_out;
|
||||||
|
|
||||||
// Inflate another chunk.
|
// Inflate another chunk.
|
||||||
@ -129,7 +129,7 @@ bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes)
|
|||||||
{
|
{
|
||||||
compressedBytes.resize(compressedBytes.size() * 2);
|
compressedBytes.resize(compressedBytes.size() * 2);
|
||||||
}
|
}
|
||||||
zs.next_out = (Bytef *) (compressedBytes.data() + offset);
|
zs.next_out = reinterpret_cast<Bytef*>((compressedBytes.data() + offset));
|
||||||
temp = zs.avail_out = compressedBytes.size() - offset;
|
temp = zs.avail_out = compressedBytes.size() - offset;
|
||||||
ret = deflate(&zs, Z_FINISH);
|
ret = deflate(&zs, Z_FINISH);
|
||||||
offset += temp - zs.avail_out;
|
offset += temp - zs.avail_out;
|
||||||
|
@ -42,7 +42,7 @@ public:
|
|||||||
}
|
}
|
||||||
void put(QByteArray input)
|
void put(QByteArray input)
|
||||||
{
|
{
|
||||||
hoedown_buffer_put(buf, (uint8_t *) input.data(), input.size());
|
hoedown_buffer_put(buf, reinterpret_cast<uint8_t *>(input.data()), input.size());
|
||||||
}
|
}
|
||||||
const uint8_t * data() const
|
const uint8_t * data() const
|
||||||
{
|
{
|
||||||
|
@ -44,7 +44,7 @@ void InstanceCopyTask::copyFinished()
|
|||||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
|
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
|
||||||
|
|
||||||
InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath));
|
InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath));
|
||||||
inst->setName(m_instName);
|
inst->setName(name());
|
||||||
inst->setIconKey(m_instIcon);
|
inst->setIconKey(m_instIcon);
|
||||||
if(!m_keepPlaytime) {
|
if(!m_keepPlaytime) {
|
||||||
inst->resetTimePlayed();
|
inst->resetTimePlayed();
|
||||||
|
@ -1,40 +1,56 @@
|
|||||||
#include "InstanceCreationTask.h"
|
#include "InstanceCreationTask.h"
|
||||||
#include "settings/INISettingsObject.h"
|
|
||||||
#include "FileSystem.h"
|
|
||||||
|
|
||||||
//FIXME: remove this
|
#include <QDebug>
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include <QFile>
|
||||||
#include "minecraft/PackProfile.h"
|
|
||||||
|
|
||||||
InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version)
|
InstanceCreationTask::InstanceCreationTask() = default;
|
||||||
{
|
|
||||||
m_version = version;
|
|
||||||
m_usingLoader = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loaderVersion)
|
|
||||||
{
|
|
||||||
m_version = version;
|
|
||||||
m_usingLoader = true;
|
|
||||||
m_loader = loader;
|
|
||||||
m_loaderVersion = loaderVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InstanceCreationTask::executeTask()
|
void InstanceCreationTask::executeTask()
|
||||||
{
|
{
|
||||||
setStatus(tr("Creating instance from version %1").arg(m_version->name()));
|
setAbortable(true);
|
||||||
{
|
|
||||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
|
if (updateInstance()) {
|
||||||
instanceSettings->suspendSave();
|
emitSucceeded();
|
||||||
MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
|
return;
|
||||||
auto components = inst.getPackProfile();
|
|
||||||
components->buildingFromScratch();
|
|
||||||
components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
|
|
||||||
if(m_usingLoader)
|
|
||||||
components->setComponentVersion(m_loader, m_loaderVersion->descriptor());
|
|
||||||
inst.setName(m_instName);
|
|
||||||
inst.setIconKey(m_instIcon);
|
|
||||||
instanceSettings->resumeSave();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When the user aborted in the update stage.
|
||||||
|
if (m_abort) {
|
||||||
|
emitAborted();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!createInstance()) {
|
||||||
|
if (m_abort)
|
||||||
|
return;
|
||||||
|
|
||||||
|
qWarning() << "Instance creation failed!";
|
||||||
|
if (!m_error_message.isEmpty())
|
||||||
|
qWarning() << "Reason: " << m_error_message;
|
||||||
|
emitFailed(tr("Error while creating new instance."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is set, it means we're updating an instance. So, we now need to remove the
|
||||||
|
// files scheduled to, and we'd better not let the user abort in the middle of it, since it'd
|
||||||
|
// put the instance in an invalid state.
|
||||||
|
if (shouldOverride()) {
|
||||||
|
setAbortable(false);
|
||||||
|
setStatus(tr("Removing old conflicting files..."));
|
||||||
|
qDebug() << "Removing old files";
|
||||||
|
|
||||||
|
for (auto path : m_files_to_remove) {
|
||||||
|
if (!QFile::exists(path))
|
||||||
|
continue;
|
||||||
|
qDebug() << "Removing" << path;
|
||||||
|
if (!QFile::remove(path)) {
|
||||||
|
qCritical() << "Couldn't remove the old conflicting files.";
|
||||||
|
emitFailed(tr("Failed to remove old conflicting files."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,46 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
#include "net/NetJob.h"
|
|
||||||
#include <QUrl>
|
|
||||||
#include "settings/SettingsObject.h"
|
|
||||||
#include "BaseVersion.h"
|
#include "BaseVersion.h"
|
||||||
#include "InstanceTask.h"
|
#include "InstanceTask.h"
|
||||||
|
|
||||||
class InstanceCreationTask : public InstanceTask
|
class InstanceCreationTask : public InstanceTask {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit InstanceCreationTask(BaseVersionPtr version);
|
InstanceCreationTask();
|
||||||
explicit InstanceCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loaderVersion);
|
virtual ~InstanceCreationTask() = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
//! Entry point for tasks.
|
void executeTask() final override;
|
||||||
virtual void executeTask() override;
|
|
||||||
|
|
||||||
private: /* data */
|
/**
|
||||||
BaseVersionPtr m_version;
|
* Tries to update an already existing instance.
|
||||||
bool m_usingLoader;
|
*
|
||||||
QString m_loader;
|
* This can be implemented by subclasses to provide a way of updating an already existing
|
||||||
BaseVersionPtr m_loaderVersion;
|
* instance, according to that implementation's concept of 'identity' (i.e. instances that
|
||||||
|
* are updates / downgrades of one another).
|
||||||
|
*
|
||||||
|
* If this returns true, createInstance() will not run, so you should do all update steps in here.
|
||||||
|
* Otherwise, createInstance() is run as normal.
|
||||||
|
*/
|
||||||
|
virtual bool updateInstance() { return false; };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* Returns whether the instance creation was successful (true) or not (false).
|
||||||
|
*/
|
||||||
|
virtual bool createInstance() { return false; };
|
||||||
|
|
||||||
|
QString getError() const { return m_error_message; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void setError(QString message) { m_error_message = message; };
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool m_abort = false;
|
||||||
|
|
||||||
|
QStringList m_files_to_remove;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_error_message;
|
||||||
};
|
};
|
||||||
|
@ -35,35 +35,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "InstanceImportTask.h"
|
#include "InstanceImportTask.h"
|
||||||
#include <QtConcurrentRun>
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "BaseInstance.h"
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "MMCZip.h"
|
#include "MMCZip.h"
|
||||||
#include "NullInstance.h"
|
#include "NullInstance.h"
|
||||||
|
|
||||||
#include "icons/IconList.h"
|
#include "icons/IconList.h"
|
||||||
#include "icons/IconUtils.h"
|
#include "icons/IconUtils.h"
|
||||||
|
|
||||||
|
#include "modplatform/technic/TechnicPackProcessor.h"
|
||||||
|
#include "modplatform/modrinth/ModrinthInstanceCreationTask.h"
|
||||||
|
#include "modplatform/flame/FlameInstanceCreationTask.h"
|
||||||
|
|
||||||
#include "settings/INISettingsObject.h"
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
// FIXME: this does not belong here, it's Minecraft/Flame specific
|
#include <QtConcurrentRun>
|
||||||
#include <quazip/quazipdir.h>
|
|
||||||
#include "Json.h"
|
|
||||||
#include "minecraft/MinecraftInstance.h"
|
|
||||||
#include "minecraft/PackProfile.h"
|
|
||||||
#include "modplatform/flame/FileResolvingTask.h"
|
|
||||||
#include "modplatform/flame/PackManifest.h"
|
|
||||||
#include "modplatform/modrinth/ModrinthPackManifest.h"
|
|
||||||
#include "modplatform/technic/TechnicPackProcessor.h"
|
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
#include "icons/IconList.h"
|
|
||||||
#include "net/ChecksumValidator.h"
|
|
||||||
|
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
|
||||||
#include "ui/dialogs/ScrollMessageBox.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <quazip/quazipdir.h>
|
||||||
|
|
||||||
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
|
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
|
||||||
{
|
{
|
||||||
m_sourceUrl = sourceUrl;
|
m_sourceUrl = sourceUrl;
|
||||||
@ -72,35 +63,41 @@ InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
|
|||||||
|
|
||||||
bool InstanceImportTask::abort()
|
bool InstanceImportTask::abort()
|
||||||
{
|
{
|
||||||
|
if (!canAbort())
|
||||||
|
return false;
|
||||||
|
|
||||||
if (m_filesNetJob)
|
if (m_filesNetJob)
|
||||||
m_filesNetJob->abort();
|
m_filesNetJob->abort();
|
||||||
m_extractFuture.cancel();
|
m_extractFuture.cancel();
|
||||||
|
|
||||||
return false;
|
return Task::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::executeTask()
|
void InstanceImportTask::executeTask()
|
||||||
{
|
{
|
||||||
if (m_sourceUrl.isLocalFile())
|
setAbortable(true);
|
||||||
{
|
|
||||||
|
if (m_sourceUrl.isLocalFile()) {
|
||||||
m_archivePath = m_sourceUrl.toLocalFile();
|
m_archivePath = m_sourceUrl.toLocalFile();
|
||||||
processZipPack();
|
processZipPack();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
|
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
|
||||||
m_downloadRequired = true;
|
m_downloadRequired = true;
|
||||||
|
|
||||||
const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
|
const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path());
|
||||||
|
|
||||||
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
|
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
|
||||||
entry->setStale(true);
|
entry->setStale(true);
|
||||||
|
m_archivePath = entry->getFullPath();
|
||||||
|
|
||||||
m_filesNetJob = new NetJob(tr("Modpack download"), APPLICATION->network());
|
m_filesNetJob = new NetJob(tr("Modpack download"), APPLICATION->network());
|
||||||
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
|
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
|
||||||
m_archivePath = entry->getFullPath();
|
|
||||||
auto job = m_filesNetJob.get();
|
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
|
||||||
connect(job, &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
|
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
|
||||||
connect(job, &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
|
connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
|
||||||
connect(job, &NetJob::failed, this, &InstanceImportTask::downloadFailed);
|
connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
|
||||||
|
|
||||||
m_filesNetJob->start();
|
m_filesNetJob->start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,7 +116,13 @@ void InstanceImportTask::downloadFailed(QString reason)
|
|||||||
|
|
||||||
void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
|
void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
|
||||||
{
|
{
|
||||||
setProgress(current / 2, total);
|
setProgress(current, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceImportTask::downloadAborted()
|
||||||
|
{
|
||||||
|
emitAborted();
|
||||||
|
m_filesNetJob.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::processZipPack()
|
void InstanceImportTask::processZipPack()
|
||||||
@ -255,290 +258,31 @@ void InstanceImportTask::extractFinished()
|
|||||||
|
|
||||||
void InstanceImportTask::extractAborted()
|
void InstanceImportTask::extractAborted()
|
||||||
{
|
{
|
||||||
emitFailed(tr("Instance import has been aborted."));
|
emitAborted();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::processFlame()
|
void InstanceImportTask::processFlame()
|
||||||
{
|
{
|
||||||
const static QMap<QString,QString> forgemap = {
|
auto* inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent);
|
||||||
{"1.2.5", "3.4.9.171"},
|
|
||||||
{"1.4.2", "6.0.1.355"},
|
|
||||||
{"1.4.7", "6.6.2.534"},
|
|
||||||
{"1.5.2", "7.8.1.737"}
|
|
||||||
};
|
|
||||||
Flame::Manifest pack;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
QString configPath = FS::PathCombine(m_stagingPath, "manifest.json");
|
|
||||||
Flame::loadManifest(pack, configPath);
|
|
||||||
QFile::remove(configPath);
|
|
||||||
}
|
|
||||||
catch (const JSONValidationError &e)
|
|
||||||
{
|
|
||||||
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(!pack.overrides.isEmpty())
|
|
||||||
{
|
|
||||||
QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides);
|
|
||||||
if (QFile::exists(overridePath))
|
|
||||||
{
|
|
||||||
QString mcPath = FS::PathCombine(m_stagingPath, "minecraft");
|
|
||||||
if (!QFile::rename(overridePath, mcPath))
|
|
||||||
{
|
|
||||||
emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logWarning(tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString forgeVersion;
|
inst_creation_task->setName(*this);
|
||||||
QString fabricVersion;
|
inst_creation_task->setIcon(m_instIcon);
|
||||||
// TODO: is Quilt relevant here?
|
inst_creation_task->setGroup(m_instGroup);
|
||||||
for(auto &loader: pack.minecraft.modLoaders)
|
|
||||||
{
|
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
|
||||||
auto id = loader.id;
|
setOverride(inst_creation_task->shouldOverride());
|
||||||
if(id.startsWith("forge-"))
|
emitSucceeded();
|
||||||
{
|
|
||||||
id.remove("forge-");
|
|
||||||
forgeVersion = id;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(id.startsWith("fabric-"))
|
|
||||||
{
|
|
||||||
id.remove("fabric-");
|
|
||||||
fabricVersion = id;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
logWarning(tr("Unknown mod loader in manifest: %1").arg(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
|
||||||
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
|
|
||||||
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
|
||||||
auto mcVersion = pack.minecraft.version;
|
|
||||||
// Hack to correct some 'special sauce'...
|
|
||||||
if(mcVersion.endsWith('.'))
|
|
||||||
{
|
|
||||||
mcVersion.remove(QRegularExpression("[.]+$"));
|
|
||||||
logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack."));
|
|
||||||
}
|
|
||||||
auto components = instance.getPackProfile();
|
|
||||||
components->buildingFromScratch();
|
|
||||||
components->setComponentVersion("net.minecraft", mcVersion, true);
|
|
||||||
if(!forgeVersion.isEmpty())
|
|
||||||
{
|
|
||||||
// FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata.
|
|
||||||
if(forgeVersion == "recommended")
|
|
||||||
{
|
|
||||||
if(forgemap.contains(mcVersion))
|
|
||||||
{
|
|
||||||
forgeVersion = forgemap[mcVersion];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logWarning(tr("Could not map recommended Forge version for Minecraft %1").arg(mcVersion));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
components->setComponentVersion("net.minecraftforge", forgeVersion);
|
|
||||||
}
|
|
||||||
if(!fabricVersion.isEmpty())
|
|
||||||
{
|
|
||||||
components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);
|
|
||||||
}
|
|
||||||
if (m_instIcon != "default")
|
|
||||||
{
|
|
||||||
instance.setIconKey(m_instIcon);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(pack.name.contains("Direwolf20"))
|
|
||||||
{
|
|
||||||
instance.setIconKey("steve");
|
|
||||||
}
|
|
||||||
else if(pack.name.contains("FTB") || pack.name.contains("Feed The Beast"))
|
|
||||||
{
|
|
||||||
instance.setIconKey("ftb_logo");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// default to something other than the MultiMC default to distinguish these
|
|
||||||
instance.setIconKey("flame");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods");
|
|
||||||
QFileInfo jarmodsInfo(jarmodsPath);
|
|
||||||
if(jarmodsInfo.isDir())
|
|
||||||
{
|
|
||||||
// install all the jar mods
|
|
||||||
qDebug() << "Found jarmods:";
|
|
||||||
QDir jarmodsDir(jarmodsPath);
|
|
||||||
QStringList jarMods;
|
|
||||||
for (auto info: jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files))
|
|
||||||
{
|
|
||||||
qDebug() << info.fileName();
|
|
||||||
jarMods.push_back(info.absoluteFilePath());
|
|
||||||
}
|
|
||||||
auto profile = instance.getPackProfile();
|
|
||||||
profile->installJarMods(jarMods);
|
|
||||||
// nuke the original files
|
|
||||||
FS::deletePath(jarmodsPath);
|
|
||||||
}
|
|
||||||
instance.setName(m_instName);
|
|
||||||
m_modIdResolver = new Flame::FileResolvingTask(APPLICATION->network(), pack);
|
|
||||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]()
|
|
||||||
{
|
|
||||||
auto results = m_modIdResolver->getResults();
|
|
||||||
//first check for blocked mods
|
|
||||||
QString text;
|
|
||||||
auto anyBlocked = false;
|
|
||||||
for(const auto& result: results.files.values()) {
|
|
||||||
if (!result.resolved || result.url.isEmpty()) {
|
|
||||||
text += QString("%1: <a href='%2'>%2</a><br/>").arg(result.fileName, result.websiteUrl);
|
|
||||||
anyBlocked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(anyBlocked) {
|
|
||||||
qWarning() << "Blocked mods found, displaying mod list";
|
|
||||||
|
|
||||||
auto message_dialog = new ScrollMessageBox(m_parent,
|
|
||||||
tr("Blocked mods found"),
|
|
||||||
tr("The following mods were blocked on third party launchers.<br/>"
|
|
||||||
"You will need to manually download them and add them to the modpack"),
|
|
||||||
text);
|
|
||||||
message_dialog->setModal(true);
|
|
||||||
|
|
||||||
if (message_dialog->exec()) {
|
|
||||||
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
|
|
||||||
for (const auto &result: m_modIdResolver->getResults().files) {
|
|
||||||
QString filename = result.fileName;
|
|
||||||
if (!result.required) {
|
|
||||||
filename += ".disabled";
|
|
||||||
}
|
|
||||||
|
|
||||||
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
|
|
||||||
auto path = FS::PathCombine(m_stagingPath, relpath);
|
|
||||||
|
|
||||||
switch (result.type) {
|
|
||||||
case Flame::File::Type::Folder: {
|
|
||||||
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
|
|
||||||
// fall-through intentional, we treat these as plain old mods and dump them wherever.
|
|
||||||
}
|
|
||||||
case Flame::File::Type::SingleFile:
|
|
||||||
case Flame::File::Type::Mod: {
|
|
||||||
if (!result.url.isEmpty()) {
|
|
||||||
qDebug() << "Will download" << result.url << "to" << path;
|
|
||||||
auto dl = Net::Download::makeFile(result.url, path);
|
|
||||||
m_filesNetJob->addNetAction(dl);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Flame::File::Type::Modpack:
|
|
||||||
logWarning(
|
|
||||||
tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(
|
|
||||||
relpath));
|
|
||||||
break;
|
|
||||||
case Flame::File::Type::Cmod2:
|
|
||||||
case Flame::File::Type::Ctoc:
|
|
||||||
case Flame::File::Type::Unknown:
|
|
||||||
logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_modIdResolver.reset();
|
|
||||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() {
|
|
||||||
m_filesNetJob.reset();
|
|
||||||
emitSucceeded();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) {
|
|
||||||
m_filesNetJob.reset();
|
|
||||||
emitFailed(reason);
|
|
||||||
});
|
|
||||||
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
|
|
||||||
setProgress(current, total);
|
|
||||||
});
|
|
||||||
setStatus(tr("Downloading mods..."));
|
|
||||||
m_filesNetJob->start();
|
|
||||||
} else {
|
|
||||||
m_modIdResolver.reset();
|
|
||||||
emitFailed("Canceled");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//TODO extract to function ?
|
|
||||||
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
|
|
||||||
for (const auto &result: m_modIdResolver->getResults().files) {
|
|
||||||
QString filename = result.fileName;
|
|
||||||
if (!result.required) {
|
|
||||||
filename += ".disabled";
|
|
||||||
}
|
|
||||||
|
|
||||||
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
|
|
||||||
auto path = FS::PathCombine(m_stagingPath, relpath);
|
|
||||||
|
|
||||||
switch (result.type) {
|
|
||||||
case Flame::File::Type::Folder: {
|
|
||||||
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
|
|
||||||
// fall-through intentional, we treat these as plain old mods and dump them wherever.
|
|
||||||
}
|
|
||||||
case Flame::File::Type::SingleFile:
|
|
||||||
case Flame::File::Type::Mod: {
|
|
||||||
if (!result.url.isEmpty()) {
|
|
||||||
qDebug() << "Will download" << result.url << "to" << path;
|
|
||||||
auto dl = Net::Download::makeFile(result.url, path);
|
|
||||||
m_filesNetJob->addNetAction(dl);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Flame::File::Type::Modpack:
|
|
||||||
logWarning(
|
|
||||||
tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(
|
|
||||||
relpath));
|
|
||||||
break;
|
|
||||||
case Flame::File::Type::Cmod2:
|
|
||||||
case Flame::File::Type::Ctoc:
|
|
||||||
case Flame::File::Type::Unknown:
|
|
||||||
logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_modIdResolver.reset();
|
|
||||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() {
|
|
||||||
m_filesNetJob.reset();
|
|
||||||
emitSucceeded();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) {
|
|
||||||
m_filesNetJob.reset();
|
|
||||||
emitFailed(reason);
|
|
||||||
});
|
|
||||||
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
|
|
||||||
setProgress(current, total);
|
|
||||||
});
|
|
||||||
setStatus(tr("Downloading mods..."));
|
|
||||||
m_filesNetJob->start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason)
|
|
||||||
{
|
|
||||||
m_modIdResolver.reset();
|
|
||||||
emitFailed(tr("Unable to resolve mod IDs:\n") + reason);
|
|
||||||
});
|
});
|
||||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, [&](qint64 current, qint64 total)
|
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||||
{
|
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
|
||||||
setProgress(current, total);
|
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
|
||||||
});
|
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
|
||||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, [&](QString status)
|
|
||||||
{
|
connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort);
|
||||||
setStatus(status);
|
connect(inst_creation_task, &Task::aborted, this, &Task::abort);
|
||||||
});
|
connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable);
|
||||||
m_modIdResolver->start();
|
|
||||||
|
inst_creation_task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::processTechnic()
|
void InstanceImportTask::processTechnic()
|
||||||
@ -546,7 +290,7 @@ void InstanceImportTask::processTechnic()
|
|||||||
shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor();
|
shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor();
|
||||||
connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &InstanceImportTask::emitSucceeded);
|
connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &InstanceImportTask::emitSucceeded);
|
||||||
connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &InstanceImportTask::emitFailed);
|
connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &InstanceImportTask::emitFailed);
|
||||||
packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath);
|
packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::processMultiMC()
|
void InstanceImportTask::processMultiMC()
|
||||||
@ -560,7 +304,7 @@ void InstanceImportTask::processMultiMC()
|
|||||||
instance.resetTimePlayed();
|
instance.resetTimePlayed();
|
||||||
|
|
||||||
// set a new nice name
|
// set a new nice name
|
||||||
instance.setName(m_instName);
|
instance.setName(name());
|
||||||
|
|
||||||
// if the icon was specified by user, use that. otherwise pull icon from the pack
|
// if the icon was specified by user, use that. otherwise pull icon from the pack
|
||||||
if (m_instIcon != "default") {
|
if (m_instIcon != "default") {
|
||||||
@ -581,198 +325,26 @@ void InstanceImportTask::processMultiMC()
|
|||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://docs.modrinth.com/docs/modpacks/format_definition/
|
|
||||||
void InstanceImportTask::processModrinth()
|
void InstanceImportTask::processModrinth()
|
||||||
{
|
{
|
||||||
std::vector<Modrinth::File> files;
|
auto* inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, m_sourceUrl.toString());
|
||||||
QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;
|
|
||||||
try {
|
|
||||||
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
|
|
||||||
auto doc = Json::requireDocument(indexPath);
|
|
||||||
auto obj = Json::requireObject(doc, "modrinth.index.json");
|
|
||||||
int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json");
|
|
||||||
if (formatVersion == 1) {
|
|
||||||
auto game = Json::requireString(obj, "game", "modrinth.index.json");
|
|
||||||
if (game != "minecraft") {
|
|
||||||
throw JSONValidationError("Unknown game: " + game);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
|
inst_creation_task->setName(*this);
|
||||||
bool had_optional = false;
|
inst_creation_task->setIcon(m_instIcon);
|
||||||
for (auto modInfo : jsonFiles) {
|
inst_creation_task->setGroup(m_instGroup);
|
||||||
Modrinth::File file;
|
|
||||||
file.path = Json::requireString(modInfo, "path");
|
|
||||||
|
|
||||||
auto env = Json::ensureObject(modInfo, "env");
|
|
||||||
// 'env' field is optional
|
|
||||||
if (!env.isEmpty()) {
|
|
||||||
QString support = Json::ensureString(env, "client", "unsupported");
|
|
||||||
if (support == "unsupported") {
|
|
||||||
continue;
|
|
||||||
} else if (support == "optional") {
|
|
||||||
// TODO: Make a review dialog for choosing which ones the user wants!
|
|
||||||
if (!had_optional) {
|
|
||||||
had_optional = true;
|
|
||||||
auto info = CustomMessageBox::selectable(
|
|
||||||
m_parent, tr("Optional mod detected!"),
|
|
||||||
tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"),
|
|
||||||
QMessageBox::Information);
|
|
||||||
info->exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file.path.endsWith(".jar"))
|
|
||||||
file.path += ".disabled";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject hashes = Json::requireObject(modInfo, "hashes");
|
|
||||||
QString hash;
|
|
||||||
QCryptographicHash::Algorithm hashAlgorithm;
|
|
||||||
hash = Json::ensureString(hashes, "sha1");
|
|
||||||
hashAlgorithm = QCryptographicHash::Sha1;
|
|
||||||
if (hash.isEmpty()) {
|
|
||||||
hash = Json::ensureString(hashes, "sha512");
|
|
||||||
hashAlgorithm = QCryptographicHash::Sha512;
|
|
||||||
if (hash.isEmpty()) {
|
|
||||||
hash = Json::ensureString(hashes, "sha256");
|
|
||||||
hashAlgorithm = QCryptographicHash::Sha256;
|
|
||||||
if (hash.isEmpty()) {
|
|
||||||
throw JSONValidationError("No hash found for: " + file.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file.hash = QByteArray::fromHex(hash.toLatin1());
|
|
||||||
file.hashAlgorithm = hashAlgorithm;
|
|
||||||
|
|
||||||
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
|
|
||||||
// (as Modrinth seems to incorrectly handle spaces)
|
|
||||||
|
|
||||||
auto download_arr = Json::ensureArray(modInfo, "downloads");
|
|
||||||
for(auto download : download_arr) {
|
|
||||||
qWarning() << download.toString();
|
|
||||||
bool is_last = download.toString() == download_arr.last().toString();
|
|
||||||
|
|
||||||
auto download_url = QUrl(download.toString());
|
|
||||||
|
|
||||||
if (!download_url.isValid()) {
|
|
||||||
qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL")
|
|
||||||
.arg(download_url.toString(), file.path);
|
|
||||||
if(is_last && file.downloads.isEmpty())
|
|
||||||
throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
file.downloads.push_back(download_url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
files.push_back(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
|
||||||
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
|
|
||||||
QString name = it.key();
|
|
||||||
if (name == "minecraft") {
|
|
||||||
minecraftVersion = Json::requireString(*it, "Minecraft version");
|
|
||||||
}
|
|
||||||
else if (name == "fabric-loader") {
|
|
||||||
fabricVersion = Json::requireString(*it, "Fabric Loader version");
|
|
||||||
}
|
|
||||||
else if (name == "quilt-loader") {
|
|
||||||
quiltVersion = Json::requireString(*it, "Quilt Loader version");
|
|
||||||
}
|
|
||||||
else if (name == "forge") {
|
|
||||||
forgeVersion = Json::requireString(*it, "Forge version");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw JSONValidationError("Unknown dependency type: " + name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion));
|
|
||||||
}
|
|
||||||
QFile::remove(indexPath);
|
|
||||||
} catch (const JSONValidationError& e) {
|
|
||||||
emitFailed(tr("Could not understand pack index:\n") + e.cause());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
|
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
|
||||||
|
setOverride(inst_creation_task->shouldOverride());
|
||||||
auto override_path = FS::PathCombine(m_stagingPath, "overrides");
|
emitSucceeded();
|
||||||
if (QFile::exists(override_path)) {
|
|
||||||
if (!QFile::rename(override_path, mcPath)) {
|
|
||||||
emitFailed(tr("Could not rename the overrides folder:\n") + "overrides");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do client overrides
|
|
||||||
auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides");
|
|
||||||
if (QFile::exists(client_override_path)) {
|
|
||||||
if (!FS::overrideFolder(mcPath, client_override_path)) {
|
|
||||||
emitFailed(tr("Could not rename the client overrides folder:\n") + "client overrides");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
|
||||||
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
|
|
||||||
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
|
||||||
auto components = instance.getPackProfile();
|
|
||||||
components->buildingFromScratch();
|
|
||||||
components->setComponentVersion("net.minecraft", minecraftVersion, true);
|
|
||||||
if (!fabricVersion.isEmpty())
|
|
||||||
components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);
|
|
||||||
if (!quiltVersion.isEmpty())
|
|
||||||
components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion);
|
|
||||||
if (!forgeVersion.isEmpty())
|
|
||||||
components->setComponentVersion("net.minecraftforge", forgeVersion);
|
|
||||||
if (m_instIcon != "default")
|
|
||||||
{
|
|
||||||
instance.setIconKey(m_instIcon);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
instance.setIconKey("modrinth");
|
|
||||||
}
|
|
||||||
instance.setName(m_instName);
|
|
||||||
instance.saveNow();
|
|
||||||
|
|
||||||
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
|
|
||||||
for (auto file : files)
|
|
||||||
{
|
|
||||||
auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path);
|
|
||||||
qDebug() << "Will try to download" << file.downloads.front() << "to" << path;
|
|
||||||
auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
|
|
||||||
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
|
||||||
m_filesNetJob->addNetAction(dl);
|
|
||||||
|
|
||||||
if (file.downloads.size() > 0) {
|
|
||||||
// FIXME: This really needs to be put into a ConcurrentTask of
|
|
||||||
// MultipleOptionsTask's , once those exist :)
|
|
||||||
connect(dl.get(), &NetAction::failed, [this, &file, path, dl]{
|
|
||||||
auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
|
|
||||||
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
|
||||||
m_filesNetJob->addNetAction(dl);
|
|
||||||
dl->succeeded();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
|
|
||||||
{
|
|
||||||
m_filesNetJob.reset();
|
|
||||||
emitSucceeded();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
connect(m_filesNetJob.get(), &NetJob::failed, [&](const QString &reason)
|
|
||||||
{
|
|
||||||
m_filesNetJob.reset();
|
|
||||||
emitFailed(reason);
|
|
||||||
});
|
});
|
||||||
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total)
|
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||||
{
|
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
|
||||||
setProgress(current, total);
|
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
|
||||||
});
|
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
|
||||||
setStatus(tr("Downloading mods..."));
|
|
||||||
m_filesNetJob->start();
|
connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort);
|
||||||
|
connect(inst_creation_task, &Task::aborted, this, &Task::abort);
|
||||||
|
connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable);
|
||||||
|
|
||||||
|
inst_creation_task->start();
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
#include "modplatform/flame/PackManifest.h"
|
#include "modplatform/flame/PackManifest.h"
|
||||||
|
|
||||||
#include <nonstd/optional>
|
#include <optional>
|
||||||
|
|
||||||
class QuaZip;
|
class QuaZip;
|
||||||
namespace Flame
|
namespace Flame
|
||||||
@ -58,7 +58,6 @@ class InstanceImportTask : public InstanceTask
|
|||||||
public:
|
public:
|
||||||
explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr);
|
explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr);
|
||||||
|
|
||||||
bool canAbort() const override { return true; }
|
|
||||||
bool abort() override;
|
bool abort() override;
|
||||||
const QVector<Flame::File> &getBlockedFiles() const
|
const QVector<Flame::File> &getBlockedFiles() const
|
||||||
{
|
{
|
||||||
@ -80,6 +79,7 @@ private slots:
|
|||||||
void downloadSucceeded();
|
void downloadSucceeded();
|
||||||
void downloadFailed(QString reason);
|
void downloadFailed(QString reason);
|
||||||
void downloadProgressChanged(qint64 current, qint64 total);
|
void downloadProgressChanged(qint64 current, qint64 total);
|
||||||
|
void downloadAborted();
|
||||||
void extractFinished();
|
void extractFinished();
|
||||||
void extractAborted();
|
void extractAborted();
|
||||||
|
|
||||||
@ -90,8 +90,8 @@ private: /* data */
|
|||||||
QString m_archivePath;
|
QString m_archivePath;
|
||||||
bool m_downloadRequired = false;
|
bool m_downloadRequired = false;
|
||||||
std::unique_ptr<QuaZip> m_packZip;
|
std::unique_ptr<QuaZip> m_packZip;
|
||||||
QFuture<nonstd::optional<QStringList>> m_extractFuture;
|
QFuture<std::optional<QStringList>> m_extractFuture;
|
||||||
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
|
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
||||||
QVector<Flame::File> m_blockedMods;
|
QVector<Flame::File> m_blockedMods;
|
||||||
enum class ModpackType{
|
enum class ModpackType{
|
||||||
Unknown,
|
Unknown,
|
||||||
|
@ -33,30 +33,32 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QSet>
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QThread>
|
|
||||||
#include <QTextStream>
|
|
||||||
#include <QXmlStreamReader>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
#include <QUuid>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QStack>
|
||||||
|
#include <QPair>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QUuid>
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
|
||||||
#include "InstanceList.h"
|
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include "InstanceTask.h"
|
|
||||||
#include "settings/INISettingsObject.h"
|
|
||||||
#include "NullInstance.h"
|
|
||||||
#include "minecraft/MinecraftInstance.h"
|
|
||||||
#include "FileSystem.h"
|
|
||||||
#include "ExponentialSeries.h"
|
#include "ExponentialSeries.h"
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "InstanceList.h"
|
||||||
|
#include "InstanceTask.h"
|
||||||
|
#include "NullInstance.h"
|
||||||
#include "WatchLock.h"
|
#include "WatchLock.h"
|
||||||
|
#include "minecraft/MinecraftInstance.h"
|
||||||
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
@ -64,13 +66,12 @@
|
|||||||
|
|
||||||
const static int GROUP_FILE_FORMAT_VERSION = 1;
|
const static int GROUP_FILE_FORMAT_VERSION = 1;
|
||||||
|
|
||||||
InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent)
|
InstanceList::InstanceList(SettingsObjectPtr settings, const QString& instDir, QObject* parent)
|
||||||
: QAbstractListModel(parent), m_globalSettings(settings)
|
: QAbstractListModel(parent), m_globalSettings(settings)
|
||||||
{
|
{
|
||||||
resumeWatch();
|
resumeWatch();
|
||||||
// Create aand normalize path
|
// Create aand normalize path
|
||||||
if (!QDir::current().exists(instDir))
|
if (!QDir::current().exists(instDir)) {
|
||||||
{
|
|
||||||
QDir::current().mkpath(instDir);
|
QDir::current().mkpath(instDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,9 +84,7 @@ InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir,
|
|||||||
m_watcher->addPath(m_instDir);
|
m_watcher->addPath(m_instDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
InstanceList::~InstanceList()
|
InstanceList::~InstanceList() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Qt::DropActions InstanceList::supportedDragActions() const
|
Qt::DropActions InstanceList::supportedDragActions() const
|
||||||
{
|
{
|
||||||
@ -99,7 +98,7 @@ Qt::DropActions InstanceList::supportedDropActions() const
|
|||||||
|
|
||||||
bool InstanceList::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
|
bool InstanceList::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
|
||||||
{
|
{
|
||||||
if(data && data->hasFormat("application/x-instanceid")) {
|
if (data && data->hasFormat("application/x-instanceid")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -107,7 +106,7 @@ bool InstanceList::canDropMimeData(const QMimeData* data, Qt::DropAction action,
|
|||||||
|
|
||||||
bool InstanceList::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
|
bool InstanceList::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
|
||||||
{
|
{
|
||||||
if(data && data->hasFormat("application/x-instanceid")) {
|
if (data && data->hasFormat("application/x-instanceid")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -120,35 +119,33 @@ QStringList InstanceList::mimeTypes() const
|
|||||||
return types;
|
return types;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMimeData * InstanceList::mimeData(const QModelIndexList& indexes) const
|
QMimeData* InstanceList::mimeData(const QModelIndexList& indexes) const
|
||||||
{
|
{
|
||||||
auto mimeData = QAbstractListModel::mimeData(indexes);
|
auto mimeData = QAbstractListModel::mimeData(indexes);
|
||||||
if(indexes.size() == 1) {
|
if (indexes.size() == 1) {
|
||||||
auto instanceId = data(indexes[0], InstanceIDRole).toString();
|
auto instanceId = data(indexes[0], InstanceIDRole).toString();
|
||||||
mimeData->setData("application/x-instanceid", instanceId.toUtf8());
|
mimeData->setData("application/x-instanceid", instanceId.toUtf8());
|
||||||
}
|
}
|
||||||
return mimeData;
|
return mimeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int InstanceList::rowCount(const QModelIndex& parent) const
|
||||||
int InstanceList::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
{
|
||||||
Q_UNUSED(parent);
|
Q_UNUSED(parent);
|
||||||
return m_instances.count();
|
return m_instances.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex InstanceList::index(int row, int column, const QModelIndex &parent) const
|
QModelIndex InstanceList::index(int row, int column, const QModelIndex& parent) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(parent);
|
Q_UNUSED(parent);
|
||||||
if (row < 0 || row >= m_instances.size())
|
if (row < 0 || row >= m_instances.size())
|
||||||
return QModelIndex();
|
return QModelIndex();
|
||||||
return createIndex(row, column, (void *)m_instances.at(row).get());
|
return createIndex(row, column, (void*)m_instances.at(row).get());
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant InstanceList::data(const QModelIndex &index, int role) const
|
QVariant InstanceList::data(const QModelIndex& index, int role) const
|
||||||
{
|
{
|
||||||
if (!index.isValid())
|
if (!index.isValid()) {
|
||||||
{
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
|
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
|
||||||
@ -193,29 +190,25 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
|
|||||||
|
|
||||||
bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role)
|
bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||||
{
|
{
|
||||||
if (!index.isValid())
|
if (!index.isValid()) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(role != Qt::EditRole)
|
if (role != Qt::EditRole) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
|
BaseInstance* pdata = static_cast<BaseInstance*>(index.internalPointer());
|
||||||
auto newName = value.toString();
|
auto newName = value.toString();
|
||||||
if(pdata->name() == newName)
|
if (pdata->name() == newName) {
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
pdata->setName(newName);
|
pdata->setName(newName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
|
Qt::ItemFlags InstanceList::flags(const QModelIndex& index) const
|
||||||
{
|
{
|
||||||
Qt::ItemFlags f;
|
Qt::ItemFlags f;
|
||||||
if (index.isValid())
|
if (index.isValid()) {
|
||||||
{
|
|
||||||
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
||||||
}
|
}
|
||||||
return f;
|
return f;
|
||||||
@ -224,13 +217,11 @@ Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
|
|||||||
GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
|
GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
|
||||||
{
|
{
|
||||||
auto inst = getInstanceById(id);
|
auto inst = getInstanceById(id);
|
||||||
if(!inst)
|
if (!inst) {
|
||||||
{
|
|
||||||
return GroupId();
|
return GroupId();
|
||||||
}
|
}
|
||||||
auto iter = m_instanceGroupIndex.find(inst->id());
|
auto iter = m_instanceGroupIndex.find(inst->id());
|
||||||
if(iter != m_instanceGroupIndex.end())
|
if (iter != m_instanceGroupIndex.end()) {
|
||||||
{
|
|
||||||
return *iter;
|
return *iter;
|
||||||
}
|
}
|
||||||
return GroupId();
|
return GroupId();
|
||||||
@ -239,33 +230,27 @@ GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
|
|||||||
void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||||
{
|
{
|
||||||
auto inst = getInstanceById(id);
|
auto inst = getInstanceById(id);
|
||||||
if(!inst)
|
if (!inst) {
|
||||||
{
|
|
||||||
qDebug() << "Attempt to set a null instance's group";
|
qDebug() << "Attempt to set a null instance's group";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
auto iter = m_instanceGroupIndex.find(inst->id());
|
auto iter = m_instanceGroupIndex.find(inst->id());
|
||||||
if(iter != m_instanceGroupIndex.end())
|
if (iter != m_instanceGroupIndex.end()) {
|
||||||
{
|
if (*iter != name) {
|
||||||
if(*iter != name)
|
|
||||||
{
|
|
||||||
*iter = name;
|
*iter = name;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
changed = true;
|
changed = true;
|
||||||
m_instanceGroupIndex[id] = name;
|
m_instanceGroupIndex[id] = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(changed)
|
if (changed) {
|
||||||
{
|
|
||||||
m_groupNameCache.insert(name);
|
m_groupNameCache.insert(name);
|
||||||
auto idx = getInstIndex(inst.get());
|
auto idx = getInstIndex(inst.get());
|
||||||
emit dataChanged(index(idx), index(idx), {GroupRole});
|
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -279,24 +264,20 @@ void InstanceList::deleteGroup(const QString& name)
|
|||||||
{
|
{
|
||||||
bool removed = false;
|
bool removed = false;
|
||||||
qDebug() << "Delete group" << name;
|
qDebug() << "Delete group" << name;
|
||||||
for(auto & instance: m_instances)
|
for (auto& instance : m_instances) {
|
||||||
{
|
const auto& instID = instance->id();
|
||||||
const auto & instID = instance->id();
|
|
||||||
auto instGroupName = getInstanceGroup(instID);
|
auto instGroupName = getInstanceGroup(instID);
|
||||||
if(instGroupName == name)
|
if (instGroupName == name) {
|
||||||
{
|
|
||||||
m_instanceGroupIndex.remove(instID);
|
m_instanceGroupIndex.remove(instID);
|
||||||
qDebug() << "Remove" << instID << "from group" << name;
|
qDebug() << "Remove" << instID << "from group" << name;
|
||||||
removed = true;
|
removed = true;
|
||||||
auto idx = getInstIndex(instance.get());
|
auto idx = getInstIndex(instance.get());
|
||||||
if(idx > 0)
|
if (idx > 0) {
|
||||||
{
|
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||||
emit dataChanged(index(idx), index(idx), {GroupRole});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(removed)
|
if (removed) {
|
||||||
{
|
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -306,23 +287,75 @@ bool InstanceList::isGroupCollapsed(const QString& group)
|
|||||||
return m_collapsedGroups.contains(group);
|
return m_collapsedGroups.contains(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InstanceList::trashInstance(const InstanceId& id)
|
||||||
|
{
|
||||||
|
auto inst = getInstanceById(id);
|
||||||
|
if (!inst) {
|
||||||
|
qDebug() << "Cannot trash instance" << id << ". No such instance is present (deleted externally?).";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cachedGroupId = m_instanceGroupIndex[id];
|
||||||
|
|
||||||
|
qDebug() << "Will trash instance" << id;
|
||||||
|
QString trashedLoc;
|
||||||
|
|
||||||
|
if (m_instanceGroupIndex.remove(id)) {
|
||||||
|
saveGroupList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FS::trash(inst->instanceRoot(), &trashedLoc)) {
|
||||||
|
qDebug() << "Trash of instance" << id << "has not been completely successfully...";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Instance" << id << "has been trashed by the launcher.";
|
||||||
|
m_trashHistory.push({id, inst->instanceRoot(), trashedLoc, cachedGroupId});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InstanceList::trashedSomething() {
|
||||||
|
return !m_trashHistory.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceList::undoTrashInstance() {
|
||||||
|
if (m_trashHistory.empty()) {
|
||||||
|
qWarning() << "Nothing to recover from trash.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto top = m_trashHistory.pop();
|
||||||
|
|
||||||
|
while (QDir(top.polyPath).exists()) {
|
||||||
|
top.id += "1";
|
||||||
|
top.polyPath += "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Moving" << top.trashPath << "back to" << top.polyPath;
|
||||||
|
QFile(top.trashPath).rename(top.polyPath);
|
||||||
|
|
||||||
|
m_instanceGroupIndex[top.id] = top.groupName;
|
||||||
|
m_groupNameCache.insert(top.groupName);
|
||||||
|
|
||||||
|
saveGroupList();
|
||||||
|
emit instancesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
void InstanceList::deleteInstance(const InstanceId& id)
|
void InstanceList::deleteInstance(const InstanceId& id)
|
||||||
{
|
{
|
||||||
auto inst = getInstanceById(id);
|
auto inst = getInstanceById(id);
|
||||||
if(!inst)
|
if (!inst) {
|
||||||
{
|
|
||||||
qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
|
qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(m_instanceGroupIndex.remove(id))
|
if (m_instanceGroupIndex.remove(id)) {
|
||||||
{
|
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Will delete instance" << id;
|
qDebug() << "Will delete instance" << id;
|
||||||
if(!FS::deletePath(inst->instanceRoot()))
|
if (!FS::deletePath(inst->instanceRoot())) {
|
||||||
{
|
|
||||||
qWarning() << "Deletion of instance" << id << "has not been completely successful ...";
|
qWarning() << "Deletion of instance" << id << "has not been completely successful ...";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -330,15 +363,13 @@ void InstanceList::deleteInstance(const InstanceId& id)
|
|||||||
qDebug() << "Instance" << id << "has been deleted by the launcher.";
|
qDebug() << "Instance" << id << "has been deleted by the launcher.";
|
||||||
}
|
}
|
||||||
|
|
||||||
static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &list)
|
static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr>& list)
|
||||||
{
|
{
|
||||||
QMap<InstanceId, InstanceLocator> out;
|
QMap<InstanceId, InstanceLocator> out;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for(auto & item: list)
|
for (auto& item : list) {
|
||||||
{
|
|
||||||
auto id = item->id();
|
auto id = item->id();
|
||||||
if(out.contains(id))
|
if (out.contains(id)) {
|
||||||
{
|
|
||||||
qWarning() << "Duplicate ID" << id << "in instance list";
|
qWarning() << "Duplicate ID" << id << "in instance list";
|
||||||
}
|
}
|
||||||
out[id] = std::make_pair(item, i);
|
out[id] = std::make_pair(item, i);
|
||||||
@ -347,24 +378,21 @@ static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList< InstanceId > InstanceList::discoverInstances()
|
QList<InstanceId> InstanceList::discoverInstances()
|
||||||
{
|
{
|
||||||
qDebug() << "Discovering instances in" << m_instDir;
|
qDebug() << "Discovering instances in" << m_instDir;
|
||||||
QList<InstanceId> out;
|
QList<InstanceId> out;
|
||||||
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks);
|
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks);
|
||||||
while (iter.hasNext())
|
while (iter.hasNext()) {
|
||||||
{
|
|
||||||
QString subDir = iter.next();
|
QString subDir = iter.next();
|
||||||
QFileInfo dirInfo(subDir);
|
QFileInfo dirInfo(subDir);
|
||||||
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
|
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
|
||||||
continue;
|
continue;
|
||||||
// if it is a symlink, ignore it if it goes to the instance folder
|
// if it is a symlink, ignore it if it goes to the instance folder
|
||||||
if(dirInfo.isSymLink())
|
if (dirInfo.isSymLink()) {
|
||||||
{
|
|
||||||
QFileInfo targetInfo(dirInfo.symLinkTarget());
|
QFileInfo targetInfo(dirInfo.symLinkTarget());
|
||||||
QFileInfo instDirInfo(m_instDir);
|
QFileInfo instDirInfo(m_instDir);
|
||||||
if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath())
|
if (targetInfo.canonicalPath() == instDirInfo.canonicalFilePath()) {
|
||||||
{
|
|
||||||
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
|
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -388,74 +416,56 @@ InstanceList::InstListError InstanceList::loadList()
|
|||||||
|
|
||||||
QList<InstancePtr> newList;
|
QList<InstancePtr> newList;
|
||||||
|
|
||||||
for(auto & id: discoverInstances())
|
for (auto& id : discoverInstances()) {
|
||||||
{
|
if (existingIds.contains(id)) {
|
||||||
if(existingIds.contains(id))
|
|
||||||
{
|
|
||||||
auto instPair = existingIds[id];
|
auto instPair = existingIds[id];
|
||||||
existingIds.remove(id);
|
existingIds.remove(id);
|
||||||
qDebug() << "Should keep and soft-reload" << id;
|
qDebug() << "Should keep and soft-reload" << id;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
InstancePtr instPtr = loadInstance(id);
|
InstancePtr instPtr = loadInstance(id);
|
||||||
if(instPtr)
|
if (instPtr) {
|
||||||
{
|
|
||||||
newList.append(instPtr);
|
newList.append(instPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
|
// TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
|
||||||
if(!existingIds.isEmpty())
|
if (!existingIds.isEmpty()) {
|
||||||
{
|
|
||||||
// get the list of removed instances and sort it by their original index, from last to first
|
// get the list of removed instances and sort it by their original index, from last to first
|
||||||
auto deadList = existingIds.values();
|
auto deadList = existingIds.values();
|
||||||
auto orderSortPredicate = [](const InstanceLocator & a, const InstanceLocator & b) -> bool
|
auto orderSortPredicate = [](const InstanceLocator& a, const InstanceLocator& b) -> bool { return a.second > b.second; };
|
||||||
{
|
|
||||||
return a.second > b.second;
|
|
||||||
};
|
|
||||||
std::sort(deadList.begin(), deadList.end(), orderSortPredicate);
|
std::sort(deadList.begin(), deadList.end(), orderSortPredicate);
|
||||||
// remove the contiguous ranges of rows
|
// remove the contiguous ranges of rows
|
||||||
int front_bookmark = -1;
|
int front_bookmark = -1;
|
||||||
int back_bookmark = -1;
|
int back_bookmark = -1;
|
||||||
int currentItem = -1;
|
int currentItem = -1;
|
||||||
auto removeNow = [&]()
|
auto removeNow = [&]() {
|
||||||
{
|
|
||||||
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
|
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
|
||||||
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
|
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
front_bookmark = -1;
|
front_bookmark = -1;
|
||||||
back_bookmark = currentItem;
|
back_bookmark = currentItem;
|
||||||
};
|
};
|
||||||
for(auto & removedItem: deadList)
|
for (auto& removedItem : deadList) {
|
||||||
{
|
|
||||||
auto instPtr = removedItem.first;
|
auto instPtr = removedItem.first;
|
||||||
instPtr->invalidate();
|
instPtr->invalidate();
|
||||||
currentItem = removedItem.second;
|
currentItem = removedItem.second;
|
||||||
if(back_bookmark == -1)
|
if (back_bookmark == -1) {
|
||||||
{
|
|
||||||
// no bookmark yet
|
// no bookmark yet
|
||||||
back_bookmark = currentItem;
|
back_bookmark = currentItem;
|
||||||
}
|
} else if (currentItem == front_bookmark - 1) {
|
||||||
else if(currentItem == front_bookmark - 1)
|
|
||||||
{
|
|
||||||
// part of contiguous sequence, continue
|
// part of contiguous sequence, continue
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// seam between previous and current item
|
// seam between previous and current item
|
||||||
removeNow();
|
removeNow();
|
||||||
}
|
}
|
||||||
front_bookmark = currentItem;
|
front_bookmark = currentItem;
|
||||||
}
|
}
|
||||||
if(back_bookmark != -1)
|
if (back_bookmark != -1) {
|
||||||
{
|
|
||||||
removeNow();
|
removeNow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(newList.size())
|
if (newList.size()) {
|
||||||
{
|
|
||||||
add(newList);
|
add(newList);
|
||||||
}
|
}
|
||||||
m_dirty = false;
|
m_dirty = false;
|
||||||
@ -466,26 +476,23 @@ InstanceList::InstListError InstanceList::loadList()
|
|||||||
void InstanceList::updateTotalPlayTime()
|
void InstanceList::updateTotalPlayTime()
|
||||||
{
|
{
|
||||||
totalPlayTime = 0;
|
totalPlayTime = 0;
|
||||||
for(auto const& itr : m_instances)
|
for (auto const& itr : m_instances) {
|
||||||
{
|
|
||||||
totalPlayTime += itr.get()->totalTimePlayed();
|
totalPlayTime += itr.get()->totalTimePlayed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceList::saveNow()
|
void InstanceList::saveNow()
|
||||||
{
|
{
|
||||||
for(auto & item: m_instances)
|
for (auto& item : m_instances) {
|
||||||
{
|
|
||||||
item->saveNow();
|
item->saveNow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceList::add(const QList<InstancePtr> &t)
|
void InstanceList::add(const QList<InstancePtr>& t)
|
||||||
{
|
{
|
||||||
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
|
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
|
||||||
m_instances.append(t);
|
m_instances.append(t);
|
||||||
for(auto & ptr : t)
|
for (auto& ptr : t) {
|
||||||
{
|
|
||||||
connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged);
|
connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged);
|
||||||
}
|
}
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
@ -493,69 +500,74 @@ void InstanceList::add(const QList<InstancePtr> &t)
|
|||||||
|
|
||||||
void InstanceList::resumeWatch()
|
void InstanceList::resumeWatch()
|
||||||
{
|
{
|
||||||
if(m_watchLevel > 0)
|
if (m_watchLevel > 0) {
|
||||||
{
|
|
||||||
qWarning() << "Bad suspend level resume in instance list";
|
qWarning() << "Bad suspend level resume in instance list";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_watchLevel++;
|
m_watchLevel++;
|
||||||
if(m_watchLevel > 0 && m_dirty)
|
if (m_watchLevel > 0 && m_dirty) {
|
||||||
{
|
|
||||||
loadList();
|
loadList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceList::suspendWatch()
|
void InstanceList::suspendWatch()
|
||||||
{
|
{
|
||||||
m_watchLevel --;
|
m_watchLevel--;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceList::providerUpdated()
|
void InstanceList::providerUpdated()
|
||||||
{
|
{
|
||||||
m_dirty = true;
|
m_dirty = true;
|
||||||
if(m_watchLevel == 1)
|
if (m_watchLevel == 1) {
|
||||||
{
|
|
||||||
loadList();
|
loadList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InstancePtr InstanceList::getInstanceById(QString instId) const
|
InstancePtr InstanceList::getInstanceById(QString instId) const
|
||||||
{
|
{
|
||||||
if(instId.isEmpty())
|
if (instId.isEmpty())
|
||||||
return InstancePtr();
|
return InstancePtr();
|
||||||
for(auto & inst: m_instances)
|
for (auto& inst : m_instances) {
|
||||||
{
|
if (inst->id() == instId) {
|
||||||
if (inst->id() == instId)
|
|
||||||
{
|
|
||||||
return inst;
|
return inst;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return InstancePtr();
|
return InstancePtr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InstancePtr InstanceList::getInstanceByManagedName(const QString& managed_name) const
|
||||||
|
{
|
||||||
|
if (managed_name.isEmpty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
for (auto instance : m_instances) {
|
||||||
|
if (instance->getManagedPackName() == managed_name)
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
QModelIndex InstanceList::getInstanceIndexById(const QString &id) const
|
QModelIndex InstanceList::getInstanceIndexById(const QString &id) const
|
||||||
{
|
{
|
||||||
return index(getInstIndex(getInstanceById(id).get()));
|
return index(getInstIndex(getInstanceById(id).get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
int InstanceList::getInstIndex(BaseInstance *inst) const
|
int InstanceList::getInstIndex(BaseInstance* inst) const
|
||||||
{
|
{
|
||||||
int count = m_instances.count();
|
int count = m_instances.count();
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++) {
|
||||||
{
|
if (inst == m_instances[i].get()) {
|
||||||
if (inst == m_instances[i].get())
|
|
||||||
{
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceList::propertiesChanged(BaseInstance *inst)
|
void InstanceList::propertiesChanged(BaseInstance* inst)
|
||||||
{
|
{
|
||||||
int i = getInstIndex(inst);
|
int i = getInstIndex(inst);
|
||||||
if (i != -1)
|
if (i != -1) {
|
||||||
{
|
|
||||||
emit dataChanged(index(i), index(i));
|
emit dataChanged(index(i), index(i));
|
||||||
updateTotalPlayTime();
|
updateTotalPlayTime();
|
||||||
}
|
}
|
||||||
@ -563,8 +575,7 @@ void InstanceList::propertiesChanged(BaseInstance *inst)
|
|||||||
|
|
||||||
InstancePtr InstanceList::loadInstance(const InstanceId& id)
|
InstancePtr InstanceList::loadInstance(const InstanceId& id)
|
||||||
{
|
{
|
||||||
if(!m_groupsLoaded)
|
if (!m_groupsLoaded) {
|
||||||
{
|
|
||||||
loadGroupList();
|
loadGroupList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -592,50 +603,42 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
|
|||||||
void InstanceList::saveGroupList()
|
void InstanceList::saveGroupList()
|
||||||
{
|
{
|
||||||
qDebug() << "Will save group list now.";
|
qDebug() << "Will save group list now.";
|
||||||
if(!m_instancesProbed)
|
if (!m_instancesProbed) {
|
||||||
{
|
|
||||||
qDebug() << "Group saving prevented because we don't know the full list of instances yet.";
|
qDebug() << "Group saving prevented because we don't know the full list of instances yet.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
WatchLock foo(m_watcher, m_instDir);
|
WatchLock foo(m_watcher, m_instDir);
|
||||||
QString groupFileName = m_instDir + "/instgroups.json";
|
QString groupFileName = m_instDir + "/instgroups.json";
|
||||||
QMap<QString, QSet<QString>> reverseGroupMap;
|
QMap<QString, QSet<QString>> reverseGroupMap;
|
||||||
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++)
|
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) {
|
||||||
{
|
|
||||||
QString id = iter.key();
|
QString id = iter.key();
|
||||||
QString group = iter.value();
|
QString group = iter.value();
|
||||||
if (group.isEmpty())
|
if (group.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
if(!instanceSet.contains(id))
|
if (!instanceSet.contains(id)) {
|
||||||
{
|
|
||||||
qDebug() << "Skipping saving missing instance" << id << "to groups list.";
|
qDebug() << "Skipping saving missing instance" << id << "to groups list.";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reverseGroupMap.count(group))
|
if (!reverseGroupMap.count(group)) {
|
||||||
{
|
|
||||||
QSet<QString> set;
|
QSet<QString> set;
|
||||||
set.insert(id);
|
set.insert(id);
|
||||||
reverseGroupMap[group] = set;
|
reverseGroupMap[group] = set;
|
||||||
}
|
} else {
|
||||||
else
|
QSet<QString>& set = reverseGroupMap[group];
|
||||||
{
|
|
||||||
QSet<QString> &set = reverseGroupMap[group];
|
|
||||||
set.insert(id);
|
set.insert(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QJsonObject toplevel;
|
QJsonObject toplevel;
|
||||||
toplevel.insert("formatVersion", QJsonValue(QString("1")));
|
toplevel.insert("formatVersion", QJsonValue(QString("1")));
|
||||||
QJsonObject groupsArr;
|
QJsonObject groupsArr;
|
||||||
for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++)
|
for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++) {
|
||||||
{
|
|
||||||
auto list = iter.value();
|
auto list = iter.value();
|
||||||
auto name = iter.key();
|
auto name = iter.key();
|
||||||
QJsonObject groupObj;
|
QJsonObject groupObj;
|
||||||
QJsonArray instanceArr;
|
QJsonArray instanceArr;
|
||||||
groupObj.insert("hidden", QJsonValue(m_collapsedGroups.contains(name)));
|
groupObj.insert("hidden", QJsonValue(m_collapsedGroups.contains(name)));
|
||||||
for (auto item : list)
|
for (auto item : list) {
|
||||||
{
|
|
||||||
instanceArr.append(QJsonValue(item));
|
instanceArr.append(QJsonValue(item));
|
||||||
}
|
}
|
||||||
groupObj.insert("instances", instanceArr);
|
groupObj.insert("instances", instanceArr);
|
||||||
@ -643,13 +646,10 @@ void InstanceList::saveGroupList()
|
|||||||
}
|
}
|
||||||
toplevel.insert("groups", groupsArr);
|
toplevel.insert("groups", groupsArr);
|
||||||
QJsonDocument doc(toplevel);
|
QJsonDocument doc(toplevel);
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
FS::write(groupFileName, doc.toJson());
|
FS::write(groupFileName, doc.toJson());
|
||||||
qDebug() << "Group list saved.";
|
qDebug() << "Group list saved.";
|
||||||
}
|
} catch (const FS::FileSystemException& e) {
|
||||||
catch (const FS::FileSystemException &e)
|
|
||||||
{
|
|
||||||
qCritical() << "Failed to write instance group file :" << e.cause();
|
qCritical() << "Failed to write instance group file :" << e.cause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -665,12 +665,9 @@ void InstanceList::loadGroupList()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
QByteArray jsonData;
|
QByteArray jsonData;
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
jsonData = FS::read(groupFileName);
|
jsonData = FS::read(groupFileName);
|
||||||
}
|
} catch (const FS::FileSystemException& e) {
|
||||||
catch (const FS::FileSystemException &e)
|
|
||||||
{
|
|
||||||
qCritical() << "Failed to read instance group file :" << e.cause();
|
qCritical() << "Failed to read instance group file :" << e.cause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -679,17 +676,15 @@ void InstanceList::loadGroupList()
|
|||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
|
||||||
|
|
||||||
// if the json was bad, fail
|
// if the json was bad, fail
|
||||||
if (error.error != QJsonParseError::NoError)
|
if (error.error != QJsonParseError::NoError) {
|
||||||
{
|
|
||||||
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
|
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
|
||||||
.arg(error.errorString(), QString::number(error.offset))
|
.arg(error.errorString(), QString::number(error.offset))
|
||||||
.toUtf8();
|
.toUtf8();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the root of the json wasn't an object, fail
|
// if the root of the json wasn't an object, fail
|
||||||
if (!jsonDoc.isObject())
|
if (!jsonDoc.isObject()) {
|
||||||
{
|
|
||||||
qWarning() << "Invalid group file. Root entry should be an object.";
|
qWarning() << "Invalid group file. Root entry should be an object.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -701,8 +696,7 @@ void InstanceList::loadGroupList()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Get the groups. if it's not an object, fail
|
// Get the groups. if it's not an object, fail
|
||||||
if (!rootObj.value("groups").isObject())
|
if (!rootObj.value("groups").isObject()) {
|
||||||
{
|
|
||||||
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
|
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -712,21 +706,20 @@ void InstanceList::loadGroupList()
|
|||||||
|
|
||||||
// Iterate through all the groups.
|
// Iterate through all the groups.
|
||||||
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
||||||
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
|
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) {
|
||||||
{
|
|
||||||
QString groupName = iter.key();
|
QString groupName = iter.key();
|
||||||
|
|
||||||
// If not an object, complain and skip to the next one.
|
// If not an object, complain and skip to the next one.
|
||||||
if (!iter.value().isObject())
|
if (!iter.value().isObject()) {
|
||||||
{
|
|
||||||
qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
|
qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject groupObj = iter.value().toObject();
|
QJsonObject groupObj = iter.value().toObject();
|
||||||
if (!groupObj.value("instances").isArray())
|
if (!groupObj.value("instances").isArray()) {
|
||||||
{
|
qWarning() << QString("Group '%1' in the group list is invalid. It should contain an array called 'instances'.")
|
||||||
qWarning() << QString("Group '%1' in the group list is invalid. It should contain an array called 'instances'.").arg(groupName).toUtf8();
|
.arg(groupName)
|
||||||
|
.toUtf8();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -734,15 +727,14 @@ void InstanceList::loadGroupList()
|
|||||||
groupSet.insert(groupName);
|
groupSet.insert(groupName);
|
||||||
|
|
||||||
auto hidden = groupObj.value("hidden").toBool(false);
|
auto hidden = groupObj.value("hidden").toBool(false);
|
||||||
if(hidden) {
|
if (hidden) {
|
||||||
m_collapsedGroups.insert(groupName);
|
m_collapsedGroups.insert(groupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate through the list of instances in the group.
|
// Iterate through the list of instances in the group.
|
||||||
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
||||||
|
|
||||||
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++)
|
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++) {
|
||||||
{
|
|
||||||
m_instanceGroupIndex[(*iter2).toString()] = groupName;
|
m_instanceGroupIndex[(*iter2).toString()] = groupName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -757,13 +749,11 @@ void InstanceList::instanceDirContentsChanged(const QString& path)
|
|||||||
emit instancesChanged();
|
emit instancesChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value)
|
void InstanceList::on_InstFolderChanged(const Setting& setting, QVariant value)
|
||||||
{
|
{
|
||||||
QString newInstDir = QDir(value.toString()).canonicalPath();
|
QString newInstDir = QDir(value.toString()).canonicalPath();
|
||||||
if(newInstDir != m_instDir)
|
if (newInstDir != m_instDir) {
|
||||||
{
|
if (m_groupsLoaded) {
|
||||||
if(m_groupsLoaded)
|
|
||||||
{
|
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
m_instDir = newInstDir;
|
m_instDir = newInstDir;
|
||||||
@ -775,7 +765,7 @@ void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value)
|
|||||||
void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed)
|
void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed)
|
||||||
{
|
{
|
||||||
qDebug() << "Group" << group << (collapsed ? "collapsed" : "expanded");
|
qDebug() << "Group" << group << (collapsed ? "collapsed" : "expanded");
|
||||||
if(collapsed) {
|
if (collapsed) {
|
||||||
m_collapsedGroups.insert(group);
|
m_collapsedGroups.insert(group);
|
||||||
} else {
|
} else {
|
||||||
m_collapsedGroups.remove(group);
|
m_collapsedGroups.remove(group);
|
||||||
@ -783,89 +773,75 @@ void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed)
|
|||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
|
|
||||||
class InstanceStaging : public Task
|
class InstanceStaging : public Task {
|
||||||
{
|
Q_OBJECT
|
||||||
Q_OBJECT
|
|
||||||
const unsigned minBackoff = 1;
|
const unsigned minBackoff = 1;
|
||||||
const unsigned maxBackoff = 16;
|
const unsigned maxBackoff = 16;
|
||||||
public:
|
public:
|
||||||
InstanceStaging (
|
InstanceStaging(InstanceList* parent, InstanceTask* child, QString stagingPath, InstanceName const& instanceName, QString groupName)
|
||||||
InstanceList * parent,
|
: m_parent(parent), backoff(minBackoff, maxBackoff), m_stagingPath(std::move(stagingPath)), m_instance_name(std::move(instanceName)), m_groupName(std::move(groupName))
|
||||||
Task * child,
|
|
||||||
const QString & stagingPath,
|
|
||||||
const QString& instanceName,
|
|
||||||
const QString& groupName )
|
|
||||||
: backoff(minBackoff, maxBackoff)
|
|
||||||
{
|
{
|
||||||
m_parent = parent;
|
|
||||||
m_child.reset(child);
|
m_child.reset(child);
|
||||||
connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded);
|
connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded);
|
||||||
connect(child, &Task::failed, this, &InstanceStaging::childFailed);
|
connect(child, &Task::failed, this, &InstanceStaging::childFailed);
|
||||||
|
connect(child, &Task::aborted, this, &InstanceStaging::childAborted);
|
||||||
|
connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable);
|
||||||
connect(child, &Task::status, this, &InstanceStaging::setStatus);
|
connect(child, &Task::status, this, &InstanceStaging::setStatus);
|
||||||
connect(child, &Task::progress, this, &InstanceStaging::setProgress);
|
connect(child, &Task::progress, this, &InstanceStaging::setProgress);
|
||||||
m_instanceName = instanceName;
|
|
||||||
m_groupName = groupName;
|
|
||||||
m_stagingPath = stagingPath;
|
|
||||||
m_backoffTimer.setSingleShot(true);
|
|
||||||
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
|
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~InstanceStaging() {};
|
virtual ~InstanceStaging(){};
|
||||||
|
|
||||||
|
|
||||||
// FIXME/TODO: add ability to abort during instance commit retries
|
// FIXME/TODO: add ability to abort during instance commit retries
|
||||||
bool abort() override
|
bool abort() override
|
||||||
{
|
{
|
||||||
if(m_child && m_child->canAbort())
|
if (!canAbort())
|
||||||
{
|
return false;
|
||||||
return m_child->abort();
|
|
||||||
}
|
m_child->abort();
|
||||||
return false;
|
|
||||||
|
return Task::abort();
|
||||||
}
|
}
|
||||||
bool canAbort() const override
|
bool canAbort() const override
|
||||||
{
|
{
|
||||||
if(m_child && m_child->canAbort())
|
return (m_child && m_child->canAbort());
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void executeTask() override
|
virtual void executeTask() override { m_child->start(); }
|
||||||
{
|
QStringList warnings() const override { return m_child->warnings(); }
|
||||||
m_child->start();
|
|
||||||
}
|
|
||||||
QStringList warnings() const override
|
|
||||||
{
|
|
||||||
return m_child->warnings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void childSucceded()
|
void childSucceded()
|
||||||
{
|
{
|
||||||
unsigned sleepTime = backoff();
|
unsigned sleepTime = backoff();
|
||||||
if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName))
|
if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, m_child->shouldOverride()))
|
||||||
{
|
{
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// we actually failed, retry?
|
// we actually failed, retry?
|
||||||
if(sleepTime == maxBackoff)
|
if (sleepTime == maxBackoff) {
|
||||||
{
|
|
||||||
emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something."));
|
emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime;
|
qDebug() << "Failed to commit instance" << m_instance_name.name() << "Initiating backoff:" << sleepTime;
|
||||||
m_backoffTimer.start(sleepTime * 500);
|
m_backoffTimer.start(sleepTime * 500);
|
||||||
}
|
}
|
||||||
void childFailed(const QString & reason)
|
void childFailed(const QString& reason)
|
||||||
{
|
{
|
||||||
m_parent->destroyStagingPath(m_stagingPath);
|
m_parent->destroyStagingPath(m_stagingPath);
|
||||||
emitFailed(reason);
|
emitFailed(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void childAborted()
|
||||||
|
{
|
||||||
|
emitAborted();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
InstanceList * m_parent;
|
||||||
/*
|
/*
|
||||||
* WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows.
|
* WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows.
|
||||||
* Basically, it starts messing things up while the launcher is extracting/creating instances
|
* Basically, it starts messing things up while the launcher is extracting/creating instances
|
||||||
@ -873,19 +849,18 @@ private:
|
|||||||
*/
|
*/
|
||||||
ExponentialSeries backoff;
|
ExponentialSeries backoff;
|
||||||
QString m_stagingPath;
|
QString m_stagingPath;
|
||||||
InstanceList * m_parent;
|
unique_qobject_ptr<InstanceTask> m_child;
|
||||||
unique_qobject_ptr<Task> m_child;
|
InstanceName m_instance_name;
|
||||||
QString m_instanceName;
|
|
||||||
QString m_groupName;
|
QString m_groupName;
|
||||||
QTimer m_backoffTimer;
|
QTimer m_backoffTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
Task * InstanceList::wrapInstanceTask(InstanceTask * task)
|
Task* InstanceList::wrapInstanceTask(InstanceTask* task)
|
||||||
{
|
{
|
||||||
auto stagingPath = getStagedInstancePath();
|
auto stagingPath = getStagedInstancePath();
|
||||||
task->setStagingPath(stagingPath);
|
task->setStagingPath(stagingPath);
|
||||||
task->setParentSettings(m_globalSettings);
|
task->setParentSettings(m_globalSettings);
|
||||||
return new InstanceStaging(this, task, stagingPath, task->name(), task->group());
|
return new InstanceStaging(this, task, stagingPath, *task, task->group());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString InstanceList::getStagedInstancePath()
|
QString InstanceList::getStagedInstancePath()
|
||||||
@ -895,8 +870,7 @@ QString InstanceList::getStagedInstancePath()
|
|||||||
QString relPath = FS::PathCombine(tempDir, key);
|
QString relPath = FS::PathCombine(tempDir, key);
|
||||||
QDir rootPath(m_instDir);
|
QDir rootPath(m_instDir);
|
||||||
auto path = FS::PathCombine(m_instDir, relPath);
|
auto path = FS::PathCombine(m_instDir, relPath);
|
||||||
if(!rootPath.mkpath(relPath))
|
if (!rootPath.mkpath(relPath)) {
|
||||||
{
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
@ -906,24 +880,50 @@ QString InstanceList::getStagedInstancePath()
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName)
|
bool InstanceList::commitStagedInstance(const QString& path, InstanceName const& instanceName, const QString& groupName, bool should_override)
|
||||||
{
|
{
|
||||||
QDir dir;
|
QDir dir;
|
||||||
QString instID = FS::DirNameFromString(instanceName, m_instDir);
|
QString instID;
|
||||||
|
InstancePtr inst;
|
||||||
|
|
||||||
|
if (should_override) {
|
||||||
|
// This is to avoid problems when the instance folder gets manually renamed
|
||||||
|
if ((inst = getInstanceByManagedName(instanceName.originalName()))) {
|
||||||
|
instID = QFileInfo(inst->instanceRoot()).fileName();
|
||||||
|
} else if ((inst = getInstanceByManagedName(instanceName.modifiedName()))) {
|
||||||
|
instID = QFileInfo(inst->instanceRoot()).fileName();
|
||||||
|
} else {
|
||||||
|
instID = FS::RemoveInvalidFilenameChars(instanceName.modifiedName(), '-');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instID = FS::DirNameFromString(instanceName.modifiedName(), m_instDir);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
WatchLock lock(m_watcher, m_instDir);
|
WatchLock lock(m_watcher, m_instDir);
|
||||||
QString destination = FS::PathCombine(m_instDir, instID);
|
QString destination = FS::PathCombine(m_instDir, instID);
|
||||||
if(!dir.rename(path, destination))
|
|
||||||
{
|
if (should_override) {
|
||||||
qWarning() << "Failed to move" << path << "to" << destination;
|
if (!FS::overrideFolder(destination, path)) {
|
||||||
return false;
|
qWarning() << "Failed to override" << path << "to" << destination;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!dir.rename(path, destination)) {
|
||||||
|
qWarning() << "Failed to move" << path << "to" << destination;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_instanceGroupIndex[instID] = groupName;
|
||||||
|
m_groupNameCache.insert(groupName);
|
||||||
}
|
}
|
||||||
m_instanceGroupIndex[instID] = groupName;
|
|
||||||
instanceSet.insert(instID);
|
instanceSet.insert(instID);
|
||||||
m_groupNameCache.insert(groupName);
|
|
||||||
emit instancesChanged();
|
emit instancesChanged();
|
||||||
emit instanceSelectRequest(instID);
|
emit instanceSelectRequest(instID);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -933,7 +933,8 @@ bool InstanceList::destroyStagingPath(const QString& keyPath)
|
|||||||
return FS::deletePath(keyPath);
|
return FS::deletePath(keyPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
int InstanceList::getTotalPlayTime() {
|
int InstanceList::getTotalPlayTime()
|
||||||
|
{
|
||||||
updateTotalPlayTime();
|
updateTotalPlayTime();
|
||||||
return totalPlayTime;
|
return totalPlayTime;
|
||||||
}
|
}
|
||||||
|
@ -19,13 +19,15 @@
|
|||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QStack>
|
||||||
|
#include <QPair>
|
||||||
|
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
|
|
||||||
class QFileSystemWatcher;
|
class QFileSystemWatcher;
|
||||||
class InstanceTask;
|
class InstanceTask;
|
||||||
|
struct InstanceName;
|
||||||
|
|
||||||
using InstanceId = QString;
|
using InstanceId = QString;
|
||||||
using GroupId = QString;
|
using GroupId = QString;
|
||||||
using InstanceLocator = std::pair<InstancePtr, int>;
|
using InstanceLocator = std::pair<InstancePtr, int>;
|
||||||
@ -46,6 +48,12 @@ enum class GroupsState
|
|||||||
Dirty
|
Dirty
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TrashHistoryItem {
|
||||||
|
QString id;
|
||||||
|
QString polyPath;
|
||||||
|
QString trashPath;
|
||||||
|
QString groupName;
|
||||||
|
};
|
||||||
|
|
||||||
class InstanceList : public QAbstractListModel
|
class InstanceList : public QAbstractListModel
|
||||||
{
|
{
|
||||||
@ -93,7 +101,10 @@ public:
|
|||||||
InstListError loadList();
|
InstListError loadList();
|
||||||
void saveNow();
|
void saveNow();
|
||||||
|
|
||||||
|
/* O(n) */
|
||||||
InstancePtr getInstanceById(QString id) const;
|
InstancePtr getInstanceById(QString id) const;
|
||||||
|
/* O(n) */
|
||||||
|
InstancePtr getInstanceByManagedName(const QString& managed_name) const;
|
||||||
QModelIndex getInstanceIndexById(const QString &id) const;
|
QModelIndex getInstanceIndexById(const QString &id) const;
|
||||||
QStringList getGroups();
|
QStringList getGroups();
|
||||||
bool isGroupCollapsed(const QString &groupName);
|
bool isGroupCollapsed(const QString &groupName);
|
||||||
@ -102,6 +113,9 @@ public:
|
|||||||
void setInstanceGroup(const InstanceId & id, const GroupId& name);
|
void setInstanceGroup(const InstanceId & id, const GroupId& name);
|
||||||
|
|
||||||
void deleteGroup(const GroupId & name);
|
void deleteGroup(const GroupId & name);
|
||||||
|
bool trashInstance(const InstanceId &id);
|
||||||
|
bool trashedSomething();
|
||||||
|
void undoTrashInstance();
|
||||||
void deleteInstance(const InstanceId & id);
|
void deleteInstance(const InstanceId & id);
|
||||||
|
|
||||||
// Wrap an instance creation task in some more task machinery and make it ready to be used
|
// Wrap an instance creation task in some more task machinery and make it ready to be used
|
||||||
@ -116,8 +130,10 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
|
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
|
||||||
* Used by instance manipulation tasks.
|
* Used by instance manipulation tasks.
|
||||||
|
* should_override is used when another similar instance already exists, and we want to override it
|
||||||
|
* - for instance, when updating it.
|
||||||
*/
|
*/
|
||||||
bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName);
|
bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, const QString& groupName, bool should_override);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy a previously created staging area given by @keyPath - used when creation fails.
|
* Destroy a previously created staging area given by @keyPath - used when creation fails.
|
||||||
@ -180,4 +196,6 @@ private:
|
|||||||
QSet<InstanceId> instanceSet;
|
QSet<InstanceId> instanceSet;
|
||||||
bool m_groupsLoaded = false;
|
bool m_groupsLoaded = false;
|
||||||
bool m_instancesProbed = false;
|
bool m_instancesProbed = false;
|
||||||
|
|
||||||
|
QStack<TrashHistoryItem> m_trashHistory;
|
||||||
};
|
};
|
||||||
|
@ -37,9 +37,9 @@ public:
|
|||||||
modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
|
modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
|
||||||
values.append(modsPage);
|
values.append(modsPage);
|
||||||
values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList()));
|
values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList()));
|
||||||
values.append(new ResourcePackPage(onesix.get()));
|
values.append(new ResourcePackPage(onesix.get(), onesix->resourcePackList()));
|
||||||
values.append(new TexturePackPage(onesix.get()));
|
values.append(new TexturePackPage(onesix.get(), onesix->texturePackList()));
|
||||||
values.append(new ShaderPackPage(onesix.get()));
|
values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList()));
|
||||||
values.append(new NotesPage(onesix.get()));
|
values.append(new NotesPage(onesix.get()));
|
||||||
values.append(new WorldListPage(onesix.get(), onesix->worldList()));
|
values.append(new WorldListPage(onesix.get(), onesix->worldList()));
|
||||||
values.append(new ServersPage(onesix));
|
values.append(new ServersPage(onesix));
|
||||||
|
@ -1,9 +1,52 @@
|
|||||||
#include "InstanceTask.h"
|
#include "InstanceTask.h"
|
||||||
|
|
||||||
InstanceTask::InstanceTask()
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
|
|
||||||
|
InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name)
|
||||||
{
|
{
|
||||||
|
auto dialog =
|
||||||
|
CustomMessageBox::selectable(parent, QObject::tr("Change instance name"),
|
||||||
|
QObject::tr("The instance's name seems to include the old version. Would you like to update it?\n\n"
|
||||||
|
"Old name: %1\n"
|
||||||
|
"New name: %2")
|
||||||
|
.arg(old_name, new_name),
|
||||||
|
QMessageBox::Question, QMessageBox::No | QMessageBox::Yes);
|
||||||
|
auto result = dialog->exec();
|
||||||
|
|
||||||
|
if (result == QMessageBox::Yes)
|
||||||
|
return InstanceNameChange::ShouldChange;
|
||||||
|
return InstanceNameChange::ShouldKeep;
|
||||||
}
|
}
|
||||||
|
|
||||||
InstanceTask::~InstanceTask()
|
QString InstanceName::name() const
|
||||||
{
|
{
|
||||||
|
if (!m_modified_name.isEmpty())
|
||||||
|
return modifiedName();
|
||||||
|
return QString("%1 %2").arg(m_original_name, m_original_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString InstanceName::originalName() const
|
||||||
|
{
|
||||||
|
return m_original_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString InstanceName::modifiedName() const
|
||||||
|
{
|
||||||
|
if (!m_modified_name.isEmpty())
|
||||||
|
return m_modified_name;
|
||||||
|
return m_original_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString InstanceName::version() const
|
||||||
|
{
|
||||||
|
return m_original_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceName::setName(InstanceName& other)
|
||||||
|
{
|
||||||
|
m_original_name = other.m_original_name;
|
||||||
|
m_original_version = other.m_original_version;
|
||||||
|
m_modified_name = other.m_modified_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
InstanceTask::InstanceTask() : Task(), InstanceName() {}
|
||||||
|
@ -1,52 +1,57 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
#include "settings/SettingsObject.h"
|
#include "settings/SettingsObject.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
class InstanceTask : public Task
|
/* Helpers */
|
||||||
{
|
enum class InstanceNameChange { ShouldChange, ShouldKeep };
|
||||||
|
[[nodiscard]] InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name);
|
||||||
|
|
||||||
|
struct InstanceName {
|
||||||
|
public:
|
||||||
|
InstanceName() = default;
|
||||||
|
InstanceName(QString name, QString version) : m_original_name(std::move(name)), m_original_version(std::move(version)) {}
|
||||||
|
|
||||||
|
[[nodiscard]] QString modifiedName() const;
|
||||||
|
[[nodiscard]] QString originalName() const;
|
||||||
|
[[nodiscard]] QString name() const;
|
||||||
|
[[nodiscard]] QString version() const;
|
||||||
|
|
||||||
|
void setName(QString name) { m_modified_name = name; }
|
||||||
|
void setName(InstanceName& other);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QString m_original_name;
|
||||||
|
QString m_original_version;
|
||||||
|
|
||||||
|
QString m_modified_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InstanceTask : public Task, public InstanceName {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit InstanceTask();
|
InstanceTask();
|
||||||
virtual ~InstanceTask();
|
~InstanceTask() override = default;
|
||||||
|
|
||||||
void setParentSettings(SettingsObjectPtr settings)
|
void setParentSettings(SettingsObjectPtr settings) { m_globalSettings = settings; }
|
||||||
{
|
|
||||||
m_globalSettings = settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStagingPath(const QString &stagingPath)
|
void setStagingPath(const QString& stagingPath) { m_stagingPath = stagingPath; }
|
||||||
{
|
|
||||||
m_stagingPath = stagingPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setName(const QString &name)
|
void setIcon(const QString& icon) { m_instIcon = icon; }
|
||||||
{
|
|
||||||
m_instName = name;
|
|
||||||
}
|
|
||||||
QString name() const
|
|
||||||
{
|
|
||||||
return m_instName;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setIcon(const QString &icon)
|
void setGroup(const QString& group) { m_instGroup = group; }
|
||||||
{
|
QString group() const { return m_instGroup; }
|
||||||
m_instIcon = icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setGroup(const QString &group)
|
bool shouldOverride() const { return m_override_existing; }
|
||||||
{
|
|
||||||
m_instGroup = group;
|
|
||||||
}
|
|
||||||
QString group() const
|
|
||||||
{
|
|
||||||
return m_instGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected: /* data */
|
protected:
|
||||||
|
void setOverride(bool override) { m_override_existing = override; }
|
||||||
|
|
||||||
|
protected: /* data */
|
||||||
SettingsObjectPtr m_globalSettings;
|
SettingsObjectPtr m_globalSettings;
|
||||||
QString m_instName;
|
|
||||||
QString m_instIcon;
|
QString m_instIcon;
|
||||||
QString m_instGroup;
|
QString m_instGroup;
|
||||||
QString m_stagingPath;
|
QString m_stagingPath;
|
||||||
|
|
||||||
|
bool m_override_existing = false;
|
||||||
};
|
};
|
||||||
|
@ -93,8 +93,8 @@ void LaunchController::decideAccount()
|
|||||||
auto reply = CustomMessageBox::selectable(
|
auto reply = CustomMessageBox::selectable(
|
||||||
m_parentWidget,
|
m_parentWidget,
|
||||||
tr("No Accounts"),
|
tr("No Accounts"),
|
||||||
tr("In order to play Minecraft, you must have at least one Mojang or Microsoft "
|
tr("In order to play Minecraft, you must have at least one Microsoft or Mojang "
|
||||||
"account logged in. "
|
"account logged in. Mojang accounts can only be used offline. "
|
||||||
"Would you like to open the account manager to add an account now?"),
|
"Would you like to open the account manager to add an account now?"),
|
||||||
QMessageBox::Information,
|
QMessageBox::Information,
|
||||||
QMessageBox::Yes | QMessageBox::No
|
QMessageBox::Yes | QMessageBox::No
|
||||||
@ -145,18 +145,29 @@ void LaunchController::login() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we try empty password first :)
|
|
||||||
QString password;
|
|
||||||
// we loop until the user succeeds in logging in or gives up
|
// we loop until the user succeeds in logging in or gives up
|
||||||
bool tryagain = true;
|
bool tryagain = true;
|
||||||
// the failure. the default failure.
|
unsigned int tries = 0;
|
||||||
const QString needLoginAgain = tr("Your account is currently not logged in. Please enter your password to log in again. <br /> <br /> This could be caused by a password change.");
|
|
||||||
QString failReason = needLoginAgain;
|
|
||||||
|
|
||||||
while (tryagain)
|
while (tryagain)
|
||||||
{
|
{
|
||||||
|
if (tries > 0 && tries % 3 == 0) {
|
||||||
|
auto result = QMessageBox::question(
|
||||||
|
m_parentWidget,
|
||||||
|
tr("Continue launch?"),
|
||||||
|
tr("It looks like we couldn't launch after %1 tries. Do you want to continue trying?")
|
||||||
|
.arg(tries)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result == QMessageBox::No) {
|
||||||
|
emitAborted();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tries++;
|
||||||
m_session = std::make_shared<AuthSession>();
|
m_session = std::make_shared<AuthSession>();
|
||||||
m_session->wants_online = m_online;
|
m_session->wants_online = m_online;
|
||||||
|
m_session->demo = m_demo;
|
||||||
m_accountToUse->fillSession(m_session);
|
m_accountToUse->fillSession(m_session);
|
||||||
|
|
||||||
// Launch immediately in true offline mode
|
// Launch immediately in true offline mode
|
||||||
@ -174,12 +185,18 @@ void LaunchController::login() {
|
|||||||
if(!m_session->wants_online) {
|
if(!m_session->wants_online) {
|
||||||
// we ask the user for a player name
|
// we ask the user for a player name
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
|
|
||||||
|
QString message = tr("Choose your offline mode player name.");
|
||||||
|
if(m_session->demo) {
|
||||||
|
message = tr("Choose your demo mode player name.");
|
||||||
|
}
|
||||||
|
|
||||||
QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString();
|
QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString();
|
||||||
QString usedname = lastOfflinePlayerName.isEmpty() ? m_session->player_name : lastOfflinePlayerName;
|
QString usedname = lastOfflinePlayerName.isEmpty() ? m_session->player_name : lastOfflinePlayerName;
|
||||||
QString name = QInputDialog::getText(
|
QString name = QInputDialog::getText(
|
||||||
m_parentWidget,
|
m_parentWidget,
|
||||||
tr("Player name"),
|
tr("Player name"),
|
||||||
tr("Choose your offline mode player name."),
|
message,
|
||||||
QLineEdit::Normal,
|
QLineEdit::Normal,
|
||||||
usedname,
|
usedname,
|
||||||
&ok
|
&ok
|
||||||
@ -359,13 +376,13 @@ void LaunchController::launchInstance()
|
|||||||
}
|
}
|
||||||
m_launcher->prependStep(new TextPrint(m_launcher.get(), resolved_servers, MessageLevel::Launcher));
|
m_launcher->prependStep(new TextPrint(m_launcher.get(), resolved_servers, MessageLevel::Launcher));
|
||||||
} else {
|
} else {
|
||||||
online_mode = "offline";
|
online_mode = m_demo ? "demo" : "offline";
|
||||||
}
|
}
|
||||||
|
|
||||||
m_launcher->prependStep(new TextPrint(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher));
|
m_launcher->prependStep(new TextPrint(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher));
|
||||||
|
|
||||||
// Prepend Version
|
// Prepend Version
|
||||||
m_launcher->prependStep(new TextPrint(m_launcher.get(), BuildConfig.LAUNCHER_NAME + " version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::Launcher));
|
m_launcher->prependStep(new TextPrint(m_launcher.get(), BuildConfig.LAUNCHER_DISPLAYNAME + " version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::Launcher));
|
||||||
m_launcher->start();
|
m_launcher->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +63,10 @@ public:
|
|||||||
m_online = online;
|
m_online = online;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setDemo(bool demo) {
|
||||||
|
m_demo = demo;
|
||||||
|
}
|
||||||
|
|
||||||
void setProfiler(BaseProfilerFactory *profiler) {
|
void setProfiler(BaseProfilerFactory *profiler) {
|
||||||
m_profiler = profiler;
|
m_profiler = profiler;
|
||||||
}
|
}
|
||||||
@ -101,6 +105,7 @@ private slots:
|
|||||||
private:
|
private:
|
||||||
BaseProfilerFactory *m_profiler = nullptr;
|
BaseProfilerFactory *m_profiler = nullptr;
|
||||||
bool m_online = true;
|
bool m_online = true;
|
||||||
|
bool m_demo = false;
|
||||||
InstancePtr m_instance;
|
InstancePtr m_instance;
|
||||||
QWidget * m_parentWidget = nullptr;
|
QWidget * m_parentWidget = nullptr;
|
||||||
InstanceWindow *m_console = nullptr;
|
InstanceWindow *m_console = nullptr;
|
||||||
|
@ -34,8 +34,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "LoggedProcess.h"
|
#include "LoggedProcess.h"
|
||||||
#include "MessageLevel.h"
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QTextDecoder>
|
||||||
|
#include "MessageLevel.h"
|
||||||
|
|
||||||
LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
|
LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
|
||||||
{
|
{
|
||||||
@ -59,25 +60,26 @@ LoggedProcess::~LoggedProcess()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList reprocess(const QByteArray & data, QString & leftover)
|
QStringList reprocess(const QByteArray& data, QTextDecoder& decoder)
|
||||||
{
|
{
|
||||||
QString str = leftover + QString::fromLocal8Bit(data);
|
auto str = decoder.toUnicode(data);
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||||
str.remove('\r');
|
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, QString::SkipEmptyParts);
|
||||||
QStringList lines = str.split("\n");
|
#else
|
||||||
leftover = lines.takeLast();
|
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, Qt::SkipEmptyParts);
|
||||||
|
#endif
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoggedProcess::on_stdErr()
|
void LoggedProcess::on_stdErr()
|
||||||
{
|
{
|
||||||
auto lines = reprocess(readAllStandardError(), m_err_leftover);
|
auto lines = reprocess(readAllStandardError(), m_err_decoder);
|
||||||
emit log(lines, MessageLevel::StdErr);
|
emit log(lines, MessageLevel::StdErr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoggedProcess::on_stdOut()
|
void LoggedProcess::on_stdOut()
|
||||||
{
|
{
|
||||||
auto lines = reprocess(readAllStandardOutput(), m_out_leftover);
|
auto lines = reprocess(readAllStandardOutput(), m_out_decoder);
|
||||||
emit log(lines, MessageLevel::StdOut);
|
emit log(lines, MessageLevel::StdOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,18 +88,6 @@ void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status)
|
|||||||
// save the exit code
|
// save the exit code
|
||||||
m_exit_code = exit_code;
|
m_exit_code = exit_code;
|
||||||
|
|
||||||
// Flush console window
|
|
||||||
if (!m_err_leftover.isEmpty())
|
|
||||||
{
|
|
||||||
emit log({m_err_leftover}, MessageLevel::StdErr);
|
|
||||||
m_err_leftover.clear();
|
|
||||||
}
|
|
||||||
if (!m_out_leftover.isEmpty())
|
|
||||||
{
|
|
||||||
emit log({m_err_leftover}, MessageLevel::StdOut);
|
|
||||||
m_out_leftover.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// based on state, send signals
|
// based on state, send signals
|
||||||
if (!m_is_aborting)
|
if (!m_is_aborting)
|
||||||
{
|
{
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
|
#include <QTextDecoder>
|
||||||
#include "MessageLevel.h"
|
#include "MessageLevel.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -88,8 +89,8 @@ private:
|
|||||||
void changeState(LoggedProcess::State state);
|
void changeState(LoggedProcess::State state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_err_leftover;
|
QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale());
|
||||||
QString m_out_leftover;
|
QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale());
|
||||||
bool m_killed = false;
|
bool m_killed = false;
|
||||||
State m_state = NotRunning;
|
State m_state = NotRunning;
|
||||||
int m_exit_code = 0;
|
int m_exit_code = 0;
|
||||||
|
@ -148,7 +148,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
|||||||
// do not merge disabled mods.
|
// do not merge disabled mods.
|
||||||
if (!mod->enabled())
|
if (!mod->enabled())
|
||||||
continue;
|
continue;
|
||||||
if (mod->type() == Mod::MOD_ZIPFILE)
|
if (mod->type() == ResourceType::ZIPFILE)
|
||||||
{
|
{
|
||||||
if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles))
|
if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles))
|
||||||
{
|
{
|
||||||
@ -158,7 +158,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mod->type() == Mod::MOD_SINGLEFILE)
|
else if (mod->type() == ResourceType::SINGLEFILE)
|
||||||
{
|
{
|
||||||
// FIXME: buggy - does not work with addedFiles
|
// FIXME: buggy - does not work with addedFiles
|
||||||
auto filename = mod->fileinfo();
|
auto filename = mod->fileinfo();
|
||||||
@ -171,7 +171,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
|||||||
}
|
}
|
||||||
addedFiles.insert(filename.fileName());
|
addedFiles.insert(filename.fileName());
|
||||||
}
|
}
|
||||||
else if (mod->type() == Mod::MOD_FOLDER)
|
else if (mod->type() == ResourceType::FOLDER)
|
||||||
{
|
{
|
||||||
// untested, but seems to be unused / not possible to reach
|
// untested, but seems to be unused / not possible to reach
|
||||||
// FIXME: buggy - does not work with addedFiles
|
// FIXME: buggy - does not work with addedFiles
|
||||||
@ -268,7 +268,7 @@ bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & re
|
|||||||
|
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
|
std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
|
||||||
{
|
{
|
||||||
QDir directory(target);
|
QDir directory(target);
|
||||||
QStringList extracted;
|
QStringList extracted;
|
||||||
@ -277,7 +277,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
|
|||||||
auto numEntries = zip->getEntriesCount();
|
auto numEntries = zip->getEntriesCount();
|
||||||
if(numEntries < 0) {
|
if(numEntries < 0) {
|
||||||
qWarning() << "Failed to enumerate files in archive";
|
qWarning() << "Failed to enumerate files in archive";
|
||||||
return nonstd::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
else if(numEntries == 0) {
|
else if(numEntries == 0) {
|
||||||
qDebug() << "Extracting empty archives seems odd...";
|
qDebug() << "Extracting empty archives seems odd...";
|
||||||
@ -286,7 +286,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
|
|||||||
else if (!zip->goToFirstFile())
|
else if (!zip->goToFirstFile())
|
||||||
{
|
{
|
||||||
qWarning() << "Failed to seek to first file in zip";
|
qWarning() << "Failed to seek to first file in zip";
|
||||||
return nonstd::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
do
|
do
|
||||||
@ -323,7 +323,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
|
|||||||
{
|
{
|
||||||
qWarning() << "Failed to extract file" << original_name << "to" << absFilePath;
|
qWarning() << "Failed to extract file" << original_name << "to" << absFilePath;
|
||||||
JlCompress::removeFile(extracted);
|
JlCompress::removeFile(extracted);
|
||||||
return nonstd::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
extracted.append(absFilePath);
|
extracted.append(absFilePath);
|
||||||
@ -341,7 +341,7 @@ bool MMCZip::extractRelFile(QuaZip *zip, const QString &file, const QString &tar
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir)
|
std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir)
|
||||||
{
|
{
|
||||||
QuaZip zip(fileCompressed);
|
QuaZip zip(fileCompressed);
|
||||||
if (!zip.open(QuaZip::mdUnzip))
|
if (!zip.open(QuaZip::mdUnzip))
|
||||||
@ -352,13 +352,13 @@ nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString
|
|||||||
return QStringList();
|
return QStringList();
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
|
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
|
||||||
return nonstd::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return MMCZip::extractSubDir(&zip, "", dir);
|
return MMCZip::extractSubDir(&zip, "", dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir)
|
std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir)
|
||||||
{
|
{
|
||||||
QuaZip zip(fileCompressed);
|
QuaZip zip(fileCompressed);
|
||||||
if (!zip.open(QuaZip::mdUnzip))
|
if (!zip.open(QuaZip::mdUnzip))
|
||||||
@ -369,7 +369,7 @@ nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString
|
|||||||
return QStringList();
|
return QStringList();
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
|
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
|
||||||
return nonstd::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return MMCZip::extractSubDir(&zip, subdir, dir);
|
return MMCZip::extractSubDir(&zip, subdir, dir);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include <quazip/JlCompress.h>
|
#include <quazip/JlCompress.h>
|
||||||
#include <nonstd/optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace MMCZip
|
namespace MMCZip
|
||||||
{
|
{
|
||||||
@ -95,7 +95,7 @@ namespace MMCZip
|
|||||||
/**
|
/**
|
||||||
* Extract a subdirectory from an archive
|
* Extract a subdirectory from an archive
|
||||||
*/
|
*/
|
||||||
nonstd::optional<QStringList> extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
|
std::optional<QStringList> extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
|
||||||
|
|
||||||
bool extractRelFile(QuaZip *zip, const QString & file, const QString &target);
|
bool extractRelFile(QuaZip *zip, const QString & file, const QString &target);
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ namespace MMCZip
|
|||||||
* \param dir The directory to extract to, the current directory if left empty.
|
* \param dir The directory to extract to, the current directory if left empty.
|
||||||
* \return The list of the full paths of the files extracted, empty on failure.
|
* \return The list of the full paths of the files extracted, empty on failure.
|
||||||
*/
|
*/
|
||||||
nonstd::optional<QStringList> extractDir(QString fileCompressed, QString dir);
|
std::optional<QStringList> extractDir(QString fileCompressed, QString dir);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract a subdirectory from an archive
|
* Extract a subdirectory from an archive
|
||||||
@ -116,7 +116,7 @@ namespace MMCZip
|
|||||||
* \param dir The directory to extract to, the current directory if left empty.
|
* \param dir The directory to extract to, the current directory if left empty.
|
||||||
* \return The list of the full paths of the files extracted, empty on failure.
|
* \return The list of the full paths of the files extracted, empty on failure.
|
||||||
*/
|
*/
|
||||||
nonstd::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir);
|
std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract a single file from an archive into a directory
|
* Extract a single file from an archive into a directory
|
||||||
|
@ -1,3 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include "launch/LaunchTask.h"
|
#include "launch/LaunchTask.h"
|
||||||
@ -15,6 +50,10 @@ public:
|
|||||||
void saveNow() override
|
void saveNow() override
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
void loadSpecificSettings() override
|
||||||
|
{
|
||||||
|
setSpecificSettingsLoaded(true);
|
||||||
|
}
|
||||||
QString getStatusbarDescription() override
|
QString getStatusbarDescription() override
|
||||||
{
|
{
|
||||||
return tr("Unknown instance type");
|
return tr("Unknown instance type");
|
||||||
@ -43,7 +82,7 @@ public:
|
|||||||
{
|
{
|
||||||
return QProcessEnvironment();
|
return QProcessEnvironment();
|
||||||
}
|
}
|
||||||
QMap<QString, QString> getVariables() const override
|
QMap<QString, QString> getVariables() override
|
||||||
{
|
{
|
||||||
return QMap<QString, QString>();
|
return QMap<QString, QString>();
|
||||||
}
|
}
|
||||||
@ -80,4 +119,8 @@ public:
|
|||||||
QString modsRoot() const override {
|
QString modsRoot() const override {
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
void updateRuntimeContext()
|
||||||
|
{
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,91 +1,37 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
namespace details
|
|
||||||
{
|
|
||||||
struct DeleteQObjectLater
|
|
||||||
{
|
|
||||||
void operator()(QObject *obj) const
|
|
||||||
{
|
|
||||||
obj->deleteLater();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* A unique pointer class with unique pointer semantics intended for derivates of QObject
|
* A unique pointer class with unique pointer semantics intended for derivates of QObject
|
||||||
* Calls deleteLater() instead of destroying the contained object immediately
|
* Calls deleteLater() instead of destroying the contained object immediately
|
||||||
*/
|
*/
|
||||||
template<typename T> using unique_qobject_ptr = std::unique_ptr<T, details::DeleteQObjectLater>;
|
template <typename T>
|
||||||
|
using unique_qobject_ptr = QScopedPointer<T, QScopedPointerDeleteLater>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A shared pointer class with shared pointer semantics intended for derivates of QObject
|
* A shared pointer class with shared pointer semantics intended for derivates of QObject
|
||||||
* Calls deleteLater() instead of destroying the contained object immediately
|
* Calls deleteLater() instead of destroying the contained object immediately
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class shared_qobject_ptr
|
class shared_qobject_ptr : public QSharedPointer<T> {
|
||||||
{
|
public:
|
||||||
public:
|
constexpr shared_qobject_ptr() : QSharedPointer<T>() {}
|
||||||
shared_qobject_ptr(){}
|
constexpr shared_qobject_ptr(T* ptr) : QSharedPointer<T>(ptr, &QObject::deleteLater) {}
|
||||||
shared_qobject_ptr(T * wrap)
|
constexpr shared_qobject_ptr(std::nullptr_t null_ptr) : QSharedPointer<T>(null_ptr, &QObject::deleteLater) {}
|
||||||
{
|
|
||||||
reset(wrap);
|
|
||||||
}
|
|
||||||
shared_qobject_ptr(const shared_qobject_ptr<T>& other)
|
|
||||||
{
|
|
||||||
m_ptr = other.m_ptr;
|
|
||||||
}
|
|
||||||
template<typename Derived>
|
|
||||||
shared_qobject_ptr(const shared_qobject_ptr<Derived> &other)
|
|
||||||
{
|
|
||||||
m_ptr = other.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
template <typename Derived>
|
||||||
void reset(T * wrap)
|
constexpr shared_qobject_ptr(const shared_qobject_ptr<Derived>& other) : QSharedPointer<T>(other)
|
||||||
{
|
{}
|
||||||
using namespace std::placeholders;
|
|
||||||
m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1));
|
|
||||||
}
|
|
||||||
void reset(const shared_qobject_ptr<T> &other)
|
|
||||||
{
|
|
||||||
m_ptr = other.m_ptr;
|
|
||||||
}
|
|
||||||
void reset()
|
|
||||||
{
|
|
||||||
m_ptr.reset();
|
|
||||||
}
|
|
||||||
T * get() const
|
|
||||||
{
|
|
||||||
return m_ptr.get();
|
|
||||||
}
|
|
||||||
T * operator->() const
|
|
||||||
{
|
|
||||||
return m_ptr.get();
|
|
||||||
}
|
|
||||||
T & operator*() const
|
|
||||||
{
|
|
||||||
return *m_ptr.get();
|
|
||||||
}
|
|
||||||
operator bool() const
|
|
||||||
{
|
|
||||||
return m_ptr.get() != nullptr;
|
|
||||||
}
|
|
||||||
const std::shared_ptr <T> unwrap() const
|
|
||||||
{
|
|
||||||
return m_ptr;
|
|
||||||
}
|
|
||||||
template<typename U>
|
|
||||||
bool operator==(const shared_qobject_ptr<U>& other) const {
|
|
||||||
return m_ptr == other.m_ptr;
|
|
||||||
}
|
|
||||||
template<typename U>
|
|
||||||
bool operator!=(const shared_qobject_ptr<U>& other) const {
|
|
||||||
return m_ptr != other.m_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
void reset() { QSharedPointer<T>::reset(); }
|
||||||
std::shared_ptr <T> m_ptr;
|
void reset(const shared_qobject_ptr<T>& other)
|
||||||
|
{
|
||||||
|
shared_qobject_ptr<T> t(other);
|
||||||
|
this->swap(t);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
88
launcher/RuntimeContext.h
Normal file
88
launcher/RuntimeContext.h
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
*
|
||||||
|
* 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 <QSet>
|
||||||
|
#include <QString>
|
||||||
|
#include "settings/SettingsObject.h"
|
||||||
|
|
||||||
|
struct RuntimeContext {
|
||||||
|
QString javaArchitecture;
|
||||||
|
QString javaRealArchitecture;
|
||||||
|
QString javaPath;
|
||||||
|
QString system;
|
||||||
|
|
||||||
|
QString mappedJavaRealArchitecture() const
|
||||||
|
{
|
||||||
|
if (javaRealArchitecture == "amd64")
|
||||||
|
return "x86_64";
|
||||||
|
if (javaRealArchitecture == "i386" || javaRealArchitecture == "i686")
|
||||||
|
return "x86";
|
||||||
|
if (javaRealArchitecture == "aarch64")
|
||||||
|
return "arm64";
|
||||||
|
if (javaRealArchitecture == "arm" || javaRealArchitecture == "armhf")
|
||||||
|
return "arm32";
|
||||||
|
return javaRealArchitecture;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateFromInstanceSettings(SettingsObjectPtr instanceSettings)
|
||||||
|
{
|
||||||
|
javaArchitecture = instanceSettings->get("JavaArchitecture").toString();
|
||||||
|
javaRealArchitecture = instanceSettings->get("JavaRealArchitecture").toString();
|
||||||
|
javaPath = instanceSettings->get("JavaPath").toString();
|
||||||
|
system = currentSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getClassifier() const { return system + "-" + mappedJavaRealArchitecture(); }
|
||||||
|
|
||||||
|
// "Legacy" refers to the fact that Mojang assumed that these are the only two architectures
|
||||||
|
bool isLegacyArch() const
|
||||||
|
{
|
||||||
|
const QString mapped = mappedJavaRealArchitecture();
|
||||||
|
return mapped == "x86_64" || mapped == "x86";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool classifierMatches(QString target) const
|
||||||
|
{
|
||||||
|
// try to match precise classifier "[os]-[arch]"
|
||||||
|
bool x = target == getClassifier();
|
||||||
|
// try to match imprecise classifier on legacy architectures "[os]"
|
||||||
|
if (!x && isLegacyArch())
|
||||||
|
x = target == system;
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString currentSystem()
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
|
return "linux";
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
return "osx";
|
||||||
|
#elif defined(Q_OS_WINDOWS)
|
||||||
|
return "windows";
|
||||||
|
#elif defined(Q_OS_FREEBSD)
|
||||||
|
return "freebsd";
|
||||||
|
#elif defined(Q_OS_OPENBSD)
|
||||||
|
return "openbsd";
|
||||||
|
#else
|
||||||
|
return "unknown";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
@ -358,7 +358,7 @@ void UpdateController::fail()
|
|||||||
msg = QObject::tr(
|
msg = QObject::tr(
|
||||||
"Couldn't replace file %1. Changes will be reverted.\n"
|
"Couldn't replace file %1. Changes will be reverted.\n"
|
||||||
"See the %2 log file for details."
|
"See the %2 log file for details."
|
||||||
).arg(m_failedFile, BuildConfig.LAUNCHER_NAME);
|
).arg(m_failedFile, BuildConfig.LAUNCHER_DISPLAYNAME);
|
||||||
doRollback = true;
|
doRollback = true;
|
||||||
QMessageBox::critical(m_parent, failTitle, msg);
|
QMessageBox::critical(m_parent, failTitle, msg);
|
||||||
break;
|
break;
|
||||||
@ -368,7 +368,7 @@ void UpdateController::fail()
|
|||||||
msg = QObject::tr(
|
msg = QObject::tr(
|
||||||
"Couldn't remove file %1. Changes will be reverted.\n"
|
"Couldn't remove file %1. Changes will be reverted.\n"
|
||||||
"See the %2 log file for details."
|
"See the %2 log file for details."
|
||||||
).arg(m_failedFile, BuildConfig.LAUNCHER_NAME);
|
).arg(m_failedFile, BuildConfig.LAUNCHER_DISPLAYNAME);
|
||||||
doRollback = true;
|
doRollback = true;
|
||||||
QMessageBox::critical(m_parent, failTitle, msg);
|
QMessageBox::critical(m_parent, failTitle, msg);
|
||||||
break;
|
break;
|
||||||
@ -399,7 +399,7 @@ void UpdateController::fail()
|
|||||||
{
|
{
|
||||||
msg = QObject::tr("The rollback failed too.\n"
|
msg = QObject::tr("The rollback failed too.\n"
|
||||||
"You will have to repair %1 manually.\n"
|
"You will have to repair %1 manually.\n"
|
||||||
"Please let us know why and how this happened.").arg(BuildConfig.LAUNCHER_NAME);
|
"Please let us know why and how this happened.").arg(BuildConfig.LAUNCHER_DISPLAYNAME);
|
||||||
QMessageBox::critical(m_parent, rollFailTitle, msg);
|
QMessageBox::critical(m_parent, rollFailTitle, msg);
|
||||||
qApp->quit();
|
qApp->quit();
|
||||||
}
|
}
|
||||||
|
@ -174,11 +174,17 @@ JavaInstallPtr JavaUtils::GetDefaultJava()
|
|||||||
|
|
||||||
QStringList addJavasFromEnv(QList<QString> javas)
|
QStringList addJavasFromEnv(QList<QString> javas)
|
||||||
{
|
{
|
||||||
QByteArray env = qgetenv("POLYMC_JAVA_PATHS");
|
auto env = qEnvironmentVariable("PRISMLAUNCHER_JAVA_PATHS"); // FIXME: use launcher name from buildconfig
|
||||||
#if defined(Q_OS_WIN32)
|
#if defined(Q_OS_WIN32)
|
||||||
QList<QString> javaPaths = QString::fromLocal8Bit(env).replace("\\", "/").split(QLatin1String(";"));
|
QList<QString> javaPaths = env.replace("\\", "/").split(QLatin1String(";"));
|
||||||
|
|
||||||
|
auto envPath = qEnvironmentVariable("PATH");
|
||||||
|
QList<QString> javaPathsfromPath = envPath.replace("\\", "/").split(QLatin1String(";"));
|
||||||
|
for (QString string : javaPathsfromPath) {
|
||||||
|
javaPaths.append(string + "/javaw.exe");
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
QList<QString> javaPaths = QString::fromLocal8Bit(env).split(QLatin1String(":"));
|
QList<QString> javaPaths = env.split(QLatin1String(":"));
|
||||||
#endif
|
#endif
|
||||||
for(QString i : javaPaths)
|
for(QString i : javaPaths)
|
||||||
{
|
{
|
||||||
@ -373,7 +379,9 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return addJavasFromEnv(candidates);
|
candidates = addJavasFromEnv(candidates);
|
||||||
|
candidates.removeDuplicates();
|
||||||
|
return candidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif defined(Q_OS_MAC)
|
#elif defined(Q_OS_MAC)
|
||||||
@ -396,7 +404,9 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
||||||
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
|
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
|
||||||
}
|
}
|
||||||
return addJavasFromEnv(javas);
|
javas = addJavasFromEnv(javas);
|
||||||
|
javas.removeDuplicates();
|
||||||
|
return javas;
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif defined(Q_OS_LINUX)
|
#elif defined(Q_OS_LINUX)
|
||||||
@ -435,14 +445,16 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
scanJavaDir("/usr/lib/jvm");
|
scanJavaDir("/usr/lib/jvm");
|
||||||
scanJavaDir("/usr/lib64/jvm");
|
scanJavaDir("/usr/lib64/jvm");
|
||||||
scanJavaDir("/usr/lib32/jvm");
|
scanJavaDir("/usr/lib32/jvm");
|
||||||
// javas stored in PolyMC's folder
|
// javas stored in Prism Launcher's folder
|
||||||
scanJavaDir("java");
|
scanJavaDir("java");
|
||||||
// manually installed JDKs in /opt
|
// manually installed JDKs in /opt
|
||||||
scanJavaDir("/opt/jdk");
|
scanJavaDir("/opt/jdk");
|
||||||
scanJavaDir("/opt/jdks");
|
scanJavaDir("/opt/jdks");
|
||||||
// flatpak
|
// flatpak
|
||||||
scanJavaDir("/app/jdk");
|
scanJavaDir("/app/jdk");
|
||||||
return addJavasFromEnv(javas);
|
javas = addJavasFromEnv(javas);
|
||||||
|
javas.removeDuplicates();
|
||||||
|
return javas;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
QList<QString> JavaUtils::FindJavaPaths()
|
QList<QString> JavaUtils::FindJavaPaths()
|
||||||
|
@ -121,7 +121,6 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
|
|||||||
emit logLine(QString("Could not start java:"), MessageLevel::Error);
|
emit logLine(QString("Could not start java:"), MessageLevel::Error);
|
||||||
emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
|
emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
|
||||||
emit logLine(QString("\nCheck your Java settings."), MessageLevel::Launcher);
|
emit logLine(QString("\nCheck your Java settings."), MessageLevel::Launcher);
|
||||||
printSystemInfo(false, false);
|
|
||||||
emitFailed(QString("Could not start java!"));
|
emitFailed(QString("Could not start java!"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -130,7 +129,6 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
|
|||||||
emit logLine(QString("Java checker returned some invalid data we don't understand:"), MessageLevel::Error);
|
emit logLine(QString("Java checker returned some invalid data we don't understand:"), MessageLevel::Error);
|
||||||
emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
|
emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
|
||||||
emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher);
|
emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher);
|
||||||
printSystemInfo(false, false);
|
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -138,7 +136,6 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
|
|||||||
{
|
{
|
||||||
auto instance = m_parent->instance();
|
auto instance = m_parent->instance();
|
||||||
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor);
|
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor);
|
||||||
printSystemInfo(true, result.is_64bit);
|
|
||||||
instance->settings()->set("JavaVersion", result.javaVersion.toString());
|
instance->settings()->set("JavaVersion", result.javaVersion.toString());
|
||||||
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
|
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
|
||||||
instance->settings()->set("JavaRealArchitecture", result.realPlatform);
|
instance->settings()->set("JavaRealArchitecture", result.realPlatform);
|
||||||
@ -155,20 +152,3 @@ void CheckJava::printJavaInfo(const QString& version, const QString& architectur
|
|||||||
emit logLine(QString("Java is version %1, using %2 (%3) architecture, from %4.\n\n")
|
emit logLine(QString("Java is version %1, using %2 (%3) architecture, from %4.\n\n")
|
||||||
.arg(version, architecture, realArchitecture, vendor), MessageLevel::Launcher);
|
.arg(version, architecture, realArchitecture, vendor), MessageLevel::Launcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit)
|
|
||||||
{
|
|
||||||
auto cpu64 = Sys::isCPU64bit();
|
|
||||||
auto system64 = Sys::isSystem64bit();
|
|
||||||
if(cpu64 != system64)
|
|
||||||
{
|
|
||||||
emit logLine(QString("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error);
|
|
||||||
}
|
|
||||||
if(javaIsKnown)
|
|
||||||
{
|
|
||||||
if(javaIs64bit != system64)
|
|
||||||
{
|
|
||||||
emit logLine(QString("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -75,7 +75,7 @@ int main(int argc, char *argv[])
|
|||||||
Q_INIT_RESOURCE(multimc);
|
Q_INIT_RESOURCE(multimc);
|
||||||
Q_INIT_RESOURCE(backgrounds);
|
Q_INIT_RESOURCE(backgrounds);
|
||||||
Q_INIT_RESOURCE(documents);
|
Q_INIT_RESOURCE(documents);
|
||||||
Q_INIT_RESOURCE(polymc);
|
Q_INIT_RESOURCE(prismlauncher);
|
||||||
|
|
||||||
Q_INIT_RESOURCE(pe_dark);
|
Q_INIT_RESOURCE(pe_dark);
|
||||||
Q_INIT_RESOURCE(pe_light);
|
Q_INIT_RESOURCE(pe_light);
|
||||||
|
@ -140,6 +140,13 @@ VersionPtr VersionList::getVersion(const QString &version)
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VersionList::hasVersion(QString version) const
|
||||||
|
{
|
||||||
|
auto ver = std::find_if(m_versions.constBegin(), m_versions.constEnd(),
|
||||||
|
[&](Meta::VersionPtr const& a){ return a->version() == version; });
|
||||||
|
return (ver != m_versions.constEnd());
|
||||||
|
}
|
||||||
|
|
||||||
void VersionList::setName(const QString &name)
|
void VersionList::setName(const QString &name)
|
||||||
{
|
{
|
||||||
m_name = name;
|
m_name = name;
|
||||||
|
@ -66,6 +66,7 @@ public:
|
|||||||
QString humanReadable() const;
|
QString humanReadable() const;
|
||||||
|
|
||||||
VersionPtr getVersion(const QString &version);
|
VersionPtr getVersion(const QString &version);
|
||||||
|
bool hasVersion(QString version) const;
|
||||||
|
|
||||||
QVector<VersionPtr> versions() const
|
QVector<VersionPtr> versions() const
|
||||||
{
|
{
|
||||||
|
@ -1,3 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
*
|
||||||
|
* 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 <meta/VersionList.h>
|
#include <meta/VersionList.h>
|
||||||
#include <meta/Index.h>
|
#include <meta/Index.h>
|
||||||
#include "Component.h"
|
#include "Component.h"
|
||||||
@ -60,7 +95,7 @@ void Component::applyTo(LaunchProfile* profile)
|
|||||||
auto vfile = getVersionFile();
|
auto vfile = getVersionFile();
|
||||||
if(vfile)
|
if(vfile)
|
||||||
{
|
{
|
||||||
vfile->applyTo(profile);
|
vfile->applyTo(profile, m_parent->runtimeContext());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -173,9 +173,9 @@ void LaunchProfile::applyCompatibleJavaMajors(QList<int>& javaMajor)
|
|||||||
m_compatibleJavaMajors.append(javaMajor);
|
m_compatibleJavaMajors.append(javaMajor);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LaunchProfile::applyLibrary(LibraryPtr library)
|
void LaunchProfile::applyLibrary(LibraryPtr library, const RuntimeContext & runtimeContext)
|
||||||
{
|
{
|
||||||
if(!library->isActive())
|
if(!library->isActive(runtimeContext))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -205,9 +205,9 @@ void LaunchProfile::applyLibrary(LibraryPtr library)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LaunchProfile::applyMavenFile(LibraryPtr mavenFile)
|
void LaunchProfile::applyMavenFile(LibraryPtr mavenFile, const RuntimeContext & runtimeContext)
|
||||||
{
|
{
|
||||||
if(!mavenFile->isActive())
|
if(!mavenFile->isActive(runtimeContext))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -221,10 +221,10 @@ void LaunchProfile::applyMavenFile(LibraryPtr mavenFile)
|
|||||||
m_mavenFiles.append(Library::limitedCopy(mavenFile));
|
m_mavenFiles.append(Library::limitedCopy(mavenFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
void LaunchProfile::applyAgent(AgentPtr agent)
|
void LaunchProfile::applyAgent(AgentPtr agent, const RuntimeContext & runtimeContext)
|
||||||
{
|
{
|
||||||
auto lib = agent->library();
|
auto lib = agent->library();
|
||||||
if(!lib->isActive())
|
if(!lib->isActive(runtimeContext))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -354,7 +354,7 @@ const QList<int> & LaunchProfile::getCompatibleJavaMajors() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LaunchProfile::getLibraryFiles(
|
void LaunchProfile::getLibraryFiles(
|
||||||
const QString& architecture,
|
const RuntimeContext & runtimeContext,
|
||||||
QStringList& jars,
|
QStringList& jars,
|
||||||
QStringList& nativeJars,
|
QStringList& nativeJars,
|
||||||
const QString& overridePath,
|
const QString& overridePath,
|
||||||
@ -366,7 +366,7 @@ void LaunchProfile::getLibraryFiles(
|
|||||||
nativeJars.clear();
|
nativeJars.clear();
|
||||||
for (auto lib : getLibraries())
|
for (auto lib : getLibraries())
|
||||||
{
|
{
|
||||||
lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
|
lib->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath);
|
||||||
}
|
}
|
||||||
// NOTE: order is important here, add main jar last to the lists
|
// NOTE: order is important here, add main jar last to the lists
|
||||||
if(m_mainJar)
|
if(m_mainJar)
|
||||||
@ -379,18 +379,18 @@ void LaunchProfile::getLibraryFiles(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
|
m_mainJar->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto lib : getNativeLibraries())
|
for (auto lib : getNativeLibraries())
|
||||||
{
|
{
|
||||||
lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
|
lib->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath);
|
||||||
}
|
}
|
||||||
if(architecture == "32")
|
if(runtimeContext.javaArchitecture == "32")
|
||||||
{
|
{
|
||||||
nativeJars.append(native32);
|
nativeJars.append(native32);
|
||||||
}
|
}
|
||||||
else if(architecture == "64")
|
else if(runtimeContext.javaArchitecture == "64")
|
||||||
{
|
{
|
||||||
nativeJars.append(native64);
|
nativeJars.append(native64);
|
||||||
}
|
}
|
||||||
|
@ -56,9 +56,9 @@ public: /* application of profile variables from patches */
|
|||||||
void applyTweakers(const QStringList &tweakers);
|
void applyTweakers(const QStringList &tweakers);
|
||||||
void applyJarMods(const QList<LibraryPtr> &jarMods);
|
void applyJarMods(const QList<LibraryPtr> &jarMods);
|
||||||
void applyMods(const QList<LibraryPtr> &jarMods);
|
void applyMods(const QList<LibraryPtr> &jarMods);
|
||||||
void applyLibrary(LibraryPtr library);
|
void applyLibrary(LibraryPtr library, const RuntimeContext & runtimeContext);
|
||||||
void applyMavenFile(LibraryPtr library);
|
void applyMavenFile(LibraryPtr library, const RuntimeContext & runtimeContext);
|
||||||
void applyAgent(AgentPtr agent);
|
void applyAgent(AgentPtr agent, const RuntimeContext & runtimeContext);
|
||||||
void applyCompatibleJavaMajors(QList<int>& javaMajor);
|
void applyCompatibleJavaMajors(QList<int>& javaMajor);
|
||||||
void applyMainJar(LibraryPtr jar);
|
void applyMainJar(LibraryPtr jar);
|
||||||
void applyProblemSeverity(ProblemSeverity severity);
|
void applyProblemSeverity(ProblemSeverity severity);
|
||||||
@ -83,7 +83,7 @@ public: /* getters for profile variables */
|
|||||||
const QList<int> & getCompatibleJavaMajors() const;
|
const QList<int> & getCompatibleJavaMajors() const;
|
||||||
const LibraryPtr getMainJar() const;
|
const LibraryPtr getMainJar() const;
|
||||||
void getLibraryFiles(
|
void getLibraryFiles(
|
||||||
const QString & architecture,
|
const RuntimeContext & runtimeContext,
|
||||||
QStringList & jars,
|
QStringList & jars,
|
||||||
QStringList & nativeJars,
|
QStringList & nativeJars,
|
||||||
const QString & overridePath,
|
const QString & overridePath,
|
||||||
|
@ -1,3 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
*
|
||||||
|
* 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 "Library.h"
|
#include "Library.h"
|
||||||
#include "MinecraftInstance.h"
|
#include "MinecraftInstance.h"
|
||||||
|
|
||||||
@ -7,7 +42,7 @@
|
|||||||
#include <BuildConfig.h>
|
#include <BuildConfig.h>
|
||||||
|
|
||||||
|
|
||||||
void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& native, QStringList& native32,
|
void Library::getApplicableFiles(const RuntimeContext & runtimeContext, QStringList& jar, QStringList& native, QStringList& native32,
|
||||||
QStringList& native64, const QString &overridePath) const
|
QStringList& native64, const QString &overridePath) const
|
||||||
{
|
{
|
||||||
bool local = isLocal();
|
bool local = isLocal();
|
||||||
@ -21,7 +56,7 @@ void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& na
|
|||||||
}
|
}
|
||||||
return out.absoluteFilePath();
|
return out.absoluteFilePath();
|
||||||
};
|
};
|
||||||
QString raw_storage = storageSuffix(system);
|
QString raw_storage = storageSuffix(runtimeContext);
|
||||||
if(isNative())
|
if(isNative())
|
||||||
{
|
{
|
||||||
if (raw_storage.contains("${arch}"))
|
if (raw_storage.contains("${arch}"))
|
||||||
@ -45,7 +80,7 @@ void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& na
|
|||||||
}
|
}
|
||||||
|
|
||||||
QList<NetAction::Ptr> Library::getDownloads(
|
QList<NetAction::Ptr> Library::getDownloads(
|
||||||
OpSys system,
|
const RuntimeContext & runtimeContext,
|
||||||
class HttpMetaCache* cache,
|
class HttpMetaCache* cache,
|
||||||
QStringList& failedLocalFiles,
|
QStringList& failedLocalFiles,
|
||||||
const QString & overridePath
|
const QString & overridePath
|
||||||
@ -88,6 +123,9 @@ QList<NetAction::Ptr> Library::getDownloads(
|
|||||||
options |= Net::Download::Option::AcceptLocalFiles;
|
options |= Net::Download::Option::AcceptLocalFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't add a time limit for the libraries cache entry validity
|
||||||
|
options |= Net::Download::Option::MakeEternal;
|
||||||
|
|
||||||
if(sha1.size())
|
if(sha1.size())
|
||||||
{
|
{
|
||||||
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
|
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
|
||||||
@ -104,14 +142,14 @@ QList<NetAction::Ptr> Library::getDownloads(
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
QString raw_storage = storageSuffix(system);
|
QString raw_storage = storageSuffix(runtimeContext);
|
||||||
if(m_mojangDownloads)
|
if(m_mojangDownloads)
|
||||||
{
|
{
|
||||||
if(isNative())
|
if(isNative())
|
||||||
{
|
{
|
||||||
if(m_nativeClassifiers.contains(system))
|
auto nativeClassifier = getCompatibleNative(runtimeContext);
|
||||||
|
if(!nativeClassifier.isNull())
|
||||||
{
|
{
|
||||||
auto nativeClassifier = m_nativeClassifiers[system];
|
|
||||||
if(nativeClassifier.contains("${arch}"))
|
if(nativeClassifier.contains("${arch}"))
|
||||||
{
|
{
|
||||||
auto nat32Classifier = nativeClassifier;
|
auto nat32Classifier = nativeClassifier;
|
||||||
@ -200,7 +238,7 @@ QList<NetAction::Ptr> Library::getDownloads(
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Library::isActive() const
|
bool Library::isActive(const RuntimeContext & runtimeContext) const
|
||||||
{
|
{
|
||||||
bool result = true;
|
bool result = true;
|
||||||
if (m_rules.empty())
|
if (m_rules.empty())
|
||||||
@ -212,7 +250,7 @@ bool Library::isActive() const
|
|||||||
RuleAction ruleResult = Disallow;
|
RuleAction ruleResult = Disallow;
|
||||||
for (auto rule : m_rules)
|
for (auto rule : m_rules)
|
||||||
{
|
{
|
||||||
RuleAction temp = rule->apply(this);
|
RuleAction temp = rule->apply(this, runtimeContext);
|
||||||
if (temp != Defer)
|
if (temp != Defer)
|
||||||
ruleResult = temp;
|
ruleResult = temp;
|
||||||
}
|
}
|
||||||
@ -220,7 +258,7 @@ bool Library::isActive() const
|
|||||||
}
|
}
|
||||||
if (isNative())
|
if (isNative())
|
||||||
{
|
{
|
||||||
result = result && m_nativeClassifiers.contains(currentSystem);
|
result = result && !getCompatibleNative(runtimeContext).isNull();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -235,6 +273,19 @@ bool Library::isAlwaysStale() const
|
|||||||
return m_hint == "always-stale";
|
return m_hint == "always-stale";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Library::getCompatibleNative(const RuntimeContext & runtimeContext) const {
|
||||||
|
// try to match precise classifier "[os]-[arch]"
|
||||||
|
auto entry = m_nativeClassifiers.constFind(runtimeContext.getClassifier());
|
||||||
|
// try to match imprecise classifier on legacy architectures "[os]"
|
||||||
|
if (entry == m_nativeClassifiers.constEnd() && runtimeContext.isLegacyArch())
|
||||||
|
entry = m_nativeClassifiers.constFind(runtimeContext.system);
|
||||||
|
|
||||||
|
if (entry == m_nativeClassifiers.constEnd())
|
||||||
|
return QString();
|
||||||
|
|
||||||
|
return entry.value();
|
||||||
|
}
|
||||||
|
|
||||||
void Library::setStoragePrefix(QString prefix)
|
void Library::setStoragePrefix(QString prefix)
|
||||||
{
|
{
|
||||||
m_storagePrefix = prefix;
|
m_storagePrefix = prefix;
|
||||||
@ -254,7 +305,7 @@ QString Library::storagePrefix() const
|
|||||||
return m_storagePrefix;
|
return m_storagePrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Library::filename(OpSys system) const
|
QString Library::filename(const RuntimeContext & runtimeContext) const
|
||||||
{
|
{
|
||||||
if(!m_filename.isEmpty())
|
if(!m_filename.isEmpty())
|
||||||
{
|
{
|
||||||
@ -268,9 +319,10 @@ QString Library::filename(OpSys system) const
|
|||||||
|
|
||||||
// otherwise native, override classifiers. Mojang HACK!
|
// otherwise native, override classifiers. Mojang HACK!
|
||||||
GradleSpecifier nativeSpec = m_name;
|
GradleSpecifier nativeSpec = m_name;
|
||||||
if (m_nativeClassifiers.contains(system))
|
QString nativeClassifier = getCompatibleNative(runtimeContext);
|
||||||
|
if (!nativeClassifier.isNull())
|
||||||
{
|
{
|
||||||
nativeSpec.setClassifier(m_nativeClassifiers[system]);
|
nativeSpec.setClassifier(nativeClassifier);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -279,14 +331,14 @@ QString Library::filename(OpSys system) const
|
|||||||
return nativeSpec.getFileName();
|
return nativeSpec.getFileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Library::displayName(OpSys system) const
|
QString Library::displayName(const RuntimeContext & runtimeContext) const
|
||||||
{
|
{
|
||||||
if(!m_displayname.isEmpty())
|
if(!m_displayname.isEmpty())
|
||||||
return m_displayname;
|
return m_displayname;
|
||||||
return filename(system);
|
return filename(runtimeContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Library::storageSuffix(OpSys system) const
|
QString Library::storageSuffix(const RuntimeContext & runtimeContext) const
|
||||||
{
|
{
|
||||||
// non-native? use only the gradle specifier
|
// non-native? use only the gradle specifier
|
||||||
if (!isNative())
|
if (!isNative())
|
||||||
@ -296,9 +348,10 @@ QString Library::storageSuffix(OpSys system) const
|
|||||||
|
|
||||||
// otherwise native, override classifiers. Mojang HACK!
|
// otherwise native, override classifiers. Mojang HACK!
|
||||||
GradleSpecifier nativeSpec = m_name;
|
GradleSpecifier nativeSpec = m_name;
|
||||||
if (m_nativeClassifiers.contains(system))
|
QString nativeClassifier = getCompatibleNative(runtimeContext);
|
||||||
|
if (!nativeClassifier.isNull())
|
||||||
{
|
{
|
||||||
nativeSpec.setClassifier(m_nativeClassifiers[system]);
|
nativeSpec.setClassifier(nativeClassifier);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,44 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <net/NetAction.h>
|
#include <net/NetAction.h>
|
||||||
#include <QPair>
|
#include <QPair>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
@ -10,9 +46,9 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "Rule.h"
|
#include "Rule.h"
|
||||||
#include "minecraft/OpSys.h"
|
|
||||||
#include "GradleSpecifier.h"
|
#include "GradleSpecifier.h"
|
||||||
#include "MojangDownloadInfo.h"
|
#include "MojangDownloadInfo.h"
|
||||||
|
#include "RuntimeContext.h"
|
||||||
|
|
||||||
class Library;
|
class Library;
|
||||||
class MinecraftInstance;
|
class MinecraftInstance;
|
||||||
@ -98,7 +134,7 @@ public: /* methods */
|
|||||||
m_repositoryURL = base_url;
|
m_repositoryURL = base_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
void getApplicableFiles(OpSys system, QStringList & jar, QStringList & native,
|
void getApplicableFiles(const RuntimeContext & runtimeContext, QStringList & jar, QStringList & native,
|
||||||
QStringList & native32, QStringList & native64, const QString & overridePath) const;
|
QStringList & native32, QStringList & native64, const QString & overridePath) const;
|
||||||
|
|
||||||
void setAbsoluteUrl(const QString &absolute_url)
|
void setAbsoluteUrl(const QString &absolute_url)
|
||||||
@ -112,7 +148,7 @@ public: /* methods */
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the file name of the library
|
/// Get the file name of the library
|
||||||
QString filename(OpSys system) const;
|
QString filename(const RuntimeContext & runtimeContext) const;
|
||||||
|
|
||||||
// DEPRECATED: set a display name, used by jar mods only
|
// DEPRECATED: set a display name, used by jar mods only
|
||||||
void setDisplayName(const QString & displayName)
|
void setDisplayName(const QString & displayName)
|
||||||
@ -121,7 +157,7 @@ public: /* methods */
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the file name of the library
|
/// Get the file name of the library
|
||||||
QString displayName(OpSys system) const;
|
QString displayName(const RuntimeContext & runtimeContext) const;
|
||||||
|
|
||||||
void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info)
|
void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info)
|
||||||
{
|
{
|
||||||
@ -140,7 +176,7 @@ public: /* methods */
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the library should be loaded (or extracted, in case of natives)
|
/// Returns true if the library should be loaded (or extracted, in case of natives)
|
||||||
bool isActive() const;
|
bool isActive(const RuntimeContext & runtimeContext) const;
|
||||||
|
|
||||||
/// Returns true if the library is contained in an instance and false if it is shared
|
/// Returns true if the library is contained in an instance and false if it is shared
|
||||||
bool isLocal() const;
|
bool isLocal() const;
|
||||||
@ -152,18 +188,20 @@ public: /* methods */
|
|||||||
bool isForge() const;
|
bool isForge() const;
|
||||||
|
|
||||||
// Get a list of downloads for this library
|
// Get a list of downloads for this library
|
||||||
QList<NetAction::Ptr> getDownloads(OpSys system, class HttpMetaCache * cache,
|
QList<NetAction::Ptr> getDownloads(const RuntimeContext & runtimeContext, class HttpMetaCache * cache,
|
||||||
QStringList & failedLocalFiles, const QString & overridePath) const;
|
QStringList & failedLocalFiles, const QString & overridePath) const;
|
||||||
|
|
||||||
|
QString getCompatibleNative(const RuntimeContext & runtimeContext) const;
|
||||||
|
|
||||||
private: /* methods */
|
private: /* methods */
|
||||||
/// the default storage prefix used by PolyMC
|
/// the default storage prefix used by Prism Launcher
|
||||||
static QString defaultStoragePrefix();
|
static QString defaultStoragePrefix();
|
||||||
|
|
||||||
/// Get the prefix - root of the storage to be used
|
/// Get the prefix - root of the storage to be used
|
||||||
QString storagePrefix() const;
|
QString storagePrefix() const;
|
||||||
|
|
||||||
/// Get the relative file path where the library should be saved
|
/// Get the relative file path where the library should be saved
|
||||||
QString storageSuffix(OpSys system) const;
|
QString storageSuffix(const RuntimeContext & runtimeContext) const;
|
||||||
|
|
||||||
QString hint() const
|
QString hint() const
|
||||||
{
|
{
|
||||||
@ -177,23 +215,23 @@ protected: /* data */
|
|||||||
/// DEPRECATED URL prefix of the maven repo where the file can be downloaded
|
/// DEPRECATED URL prefix of the maven repo where the file can be downloaded
|
||||||
QString m_repositoryURL;
|
QString m_repositoryURL;
|
||||||
|
|
||||||
/// DEPRECATED: PolyMC-specific absolute URL. takes precedence over the implicit maven repo URL, if defined
|
/// DEPRECATED: Prism Launcher-specific absolute URL. takes precedence over the implicit maven repo URL, if defined
|
||||||
QString m_absoluteURL;
|
QString m_absoluteURL;
|
||||||
|
|
||||||
/// PolyMC extension - filename override
|
/// Prism Launcher extension - filename override
|
||||||
QString m_filename;
|
QString m_filename;
|
||||||
|
|
||||||
/// DEPRECATED PolyMC extension - display name
|
/// DEPRECATED Prism Launcher extension - display name
|
||||||
QString m_displayname;
|
QString m_displayname;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PolyMC-specific type hint - modifies how the library is treated
|
* Prism Launcher-specific type hint - modifies how the library is treated
|
||||||
*/
|
*/
|
||||||
QString m_hint;
|
QString m_hint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* storage - by default the local libraries folder in polymc, but could be elsewhere
|
* storage - by default the local libraries folder in Prism Launcher, but could be elsewhere
|
||||||
* PolyMC specific, because of FTB.
|
* Prism Launcher specific, because of FTB.
|
||||||
*/
|
*/
|
||||||
QString m_storagePrefix;
|
QString m_storagePrefix;
|
||||||
|
|
||||||
@ -204,7 +242,7 @@ protected: /* data */
|
|||||||
QStringList m_extractExcludes;
|
QStringList m_extractExcludes;
|
||||||
|
|
||||||
/// native suffixes per OS
|
/// native suffixes per OS
|
||||||
QMap<OpSys, QString> m_nativeClassifiers;
|
QMap<QString, QString> m_nativeClassifiers;
|
||||||
|
|
||||||
/// true if the library had a rules section (even empty)
|
/// true if the library had a rules section (even empty)
|
||||||
bool applyRules = false;
|
bool applyRules = false;
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
|
|
||||||
#include "mod/ModFolderModel.h"
|
#include "mod/ModFolderModel.h"
|
||||||
#include "mod/ResourcePackFolderModel.h"
|
#include "mod/ResourcePackFolderModel.h"
|
||||||
|
#include "mod/ShaderPackFolderModel.h"
|
||||||
#include "mod/TexturePackFolderModel.h"
|
#include "mod/TexturePackFolderModel.h"
|
||||||
|
|
||||||
#include "WorldList.h"
|
#include "WorldList.h"
|
||||||
@ -115,6 +116,19 @@ private:
|
|||||||
MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
|
MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
|
||||||
: BaseInstance(globalSettings, settings, rootDir)
|
: BaseInstance(globalSettings, settings, rootDir)
|
||||||
{
|
{
|
||||||
|
m_components.reset(new PackProfile(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinecraftInstance::saveNow()
|
||||||
|
{
|
||||||
|
m_components->saveNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinecraftInstance::loadSpecificSettings()
|
||||||
|
{
|
||||||
|
if (isSpecificSettingsLoaded())
|
||||||
|
return;
|
||||||
|
|
||||||
// Java Settings
|
// Java Settings
|
||||||
auto javaOverride = m_settings->registerSetting("OverrideJava", false);
|
auto javaOverride = m_settings->registerSetting("OverrideJava", false);
|
||||||
auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false);
|
auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false);
|
||||||
@ -124,64 +138,66 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
|
|||||||
auto javaOrLocation = std::make_shared<OrSetting>("JavaOrLocationOverride", javaOverride, locationOverride);
|
auto javaOrLocation = std::make_shared<OrSetting>("JavaOrLocationOverride", javaOverride, locationOverride);
|
||||||
auto javaOrArgs = std::make_shared<OrSetting>("JavaOrArgsOverride", javaOverride, argsOverride);
|
auto javaOrArgs = std::make_shared<OrSetting>("JavaOrArgsOverride", javaOverride, argsOverride);
|
||||||
|
|
||||||
m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation);
|
if (auto global_settings = globalSettings()) {
|
||||||
m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs);
|
m_settings->registerOverride(global_settings->getSetting("JavaPath"), javaOrLocation);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
|
m_settings->registerOverride(global_settings->getSetting("JvmArgs"), javaOrArgs);
|
||||||
|
m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
|
||||||
|
|
||||||
// special!
|
// special!
|
||||||
m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation);
|
m_settings->registerPassthrough(global_settings->getSetting("JavaTimestamp"), javaOrLocation);
|
||||||
m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation);
|
m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation);
|
||||||
m_settings->registerPassthrough(globalSettings->getSetting("JavaArchitecture"), javaOrLocation);
|
m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation);
|
||||||
|
m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), javaOrLocation);
|
||||||
|
|
||||||
// Window Size
|
// Window Size
|
||||||
auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
|
auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowSetting);
|
m_settings->registerOverride(global_settings->getSetting("LaunchMaximized"), windowSetting);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowSetting);
|
m_settings->registerOverride(global_settings->getSetting("MinecraftWinWidth"), windowSetting);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowSetting);
|
m_settings->registerOverride(global_settings->getSetting("MinecraftWinHeight"), windowSetting);
|
||||||
|
|
||||||
// Memory
|
// Memory
|
||||||
auto memorySetting = m_settings->registerSetting("OverrideMemory", false);
|
auto memorySetting = m_settings->registerSetting("OverrideMemory", false);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting);
|
m_settings->registerOverride(global_settings->getSetting("MinMemAlloc"), memorySetting);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting);
|
m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting);
|
m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting);
|
||||||
|
|
||||||
// Minecraft launch method
|
// Minecraft launch method
|
||||||
auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
|
auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride);
|
m_settings->registerOverride(global_settings->getSetting("MCLaunchMethod"), launchMethodOverride);
|
||||||
|
|
||||||
// Native library workarounds
|
// Native library workarounds
|
||||||
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
|
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
|
m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
|
m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
|
||||||
|
|
||||||
// Peformance related options
|
// Peformance related options
|
||||||
auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
|
auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("EnableFeralGamemode"), performanceOverride);
|
m_settings->registerOverride(global_settings->getSetting("EnableFeralGamemode"), performanceOverride);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("EnableMangoHud"), performanceOverride);
|
m_settings->registerOverride(global_settings->getSetting("EnableMangoHud"), performanceOverride);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("UseDiscreteGpu"), performanceOverride);
|
m_settings->registerOverride(global_settings->getSetting("UseDiscreteGpu"), performanceOverride);
|
||||||
|
|
||||||
// Game time
|
// Miscellaneous
|
||||||
auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false);
|
auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride);
|
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
|
||||||
m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride);
|
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
|
||||||
|
|
||||||
|
m_settings->set("InstanceType", "OneSix");
|
||||||
|
}
|
||||||
|
|
||||||
// Join server on launch, this does not have a global override
|
// Join server on launch, this does not have a global override
|
||||||
m_settings->registerSetting("JoinServerOnLaunch", false);
|
m_settings->registerSetting("JoinServerOnLaunch", false);
|
||||||
m_settings->registerSetting("JoinServerOnLaunchAddress", "");
|
m_settings->registerSetting("JoinServerOnLaunchAddress", "");
|
||||||
|
|
||||||
// Miscellaneous
|
qDebug() << "Instance-type specific settings were loaded!";
|
||||||
auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false);
|
|
||||||
m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
|
|
||||||
m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
|
|
||||||
|
|
||||||
m_settings->set("InstanceType", "OneSix");
|
setSpecificSettingsLoaded(true);
|
||||||
|
|
||||||
m_components.reset(new PackProfile(this));
|
updateRuntimeContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MinecraftInstance::saveNow()
|
void MinecraftInstance::updateRuntimeContext()
|
||||||
{
|
{
|
||||||
m_components->saveNow();
|
m_runtimeContext.updateFromInstanceSettings(m_settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MinecraftInstance::typeName() const
|
QString MinecraftInstance::typeName() const
|
||||||
@ -237,6 +253,14 @@ QString MinecraftInstance::getLocalLibraryPath() const
|
|||||||
return libraries_dir.absolutePath();
|
return libraries_dir.absolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MinecraftInstance::supportsDemo() const
|
||||||
|
{
|
||||||
|
Version instance_ver { getPackProfile()->getComponentVersion("net.minecraft") };
|
||||||
|
// Demo mode was introduced in 1.3.1: https://minecraft.fandom.com/wiki/Demo_mode#History
|
||||||
|
// FIXME: Due to Version constraints atm, this can't handle well non-release versions
|
||||||
|
return instance_ver >= Version("1.3.1");
|
||||||
|
}
|
||||||
|
|
||||||
QString MinecraftInstance::jarModsDir() const
|
QString MinecraftInstance::jarModsDir() const
|
||||||
{
|
{
|
||||||
QDir jarmods_dir(FS::PathCombine(instanceRoot(), "jarmods/"));
|
QDir jarmods_dir(FS::PathCombine(instanceRoot(), "jarmods/"));
|
||||||
@ -308,12 +332,11 @@ QDir MinecraftInstance::versionsPath() const
|
|||||||
return QDir::current().absoluteFilePath("versions");
|
return QDir::current().absoluteFilePath("versions");
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList MinecraftInstance::getClassPath() const
|
QStringList MinecraftInstance::getClassPath()
|
||||||
{
|
{
|
||||||
QStringList jars, nativeJars;
|
QStringList jars, nativeJars;
|
||||||
auto javaArchitecture = settings()->get("JavaArchitecture").toString();
|
|
||||||
auto profile = m_components->getProfile();
|
auto profile = m_components->getProfile();
|
||||||
profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
|
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
|
||||||
return jars;
|
return jars;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,16 +346,15 @@ QString MinecraftInstance::getMainClass() const
|
|||||||
return profile->getMainClass();
|
return profile->getMainClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList MinecraftInstance::getNativeJars() const
|
QStringList MinecraftInstance::getNativeJars()
|
||||||
{
|
{
|
||||||
QStringList jars, nativeJars;
|
QStringList jars, nativeJars;
|
||||||
auto javaArchitecture = settings()->get("JavaArchitecture").toString();
|
|
||||||
auto profile = m_components->getProfile();
|
auto profile = m_components->getProfile();
|
||||||
profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
|
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
|
||||||
return nativeJars;
|
return nativeJars;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList MinecraftInstance::extraArguments() const
|
QStringList MinecraftInstance::extraArguments()
|
||||||
{
|
{
|
||||||
auto list = BaseInstance::extraArguments();
|
auto list = BaseInstance::extraArguments();
|
||||||
auto version = getPackProfile();
|
auto version = getPackProfile();
|
||||||
@ -352,13 +374,13 @@ QStringList MinecraftInstance::extraArguments() const
|
|||||||
for (auto agent : agents)
|
for (auto agent : agents)
|
||||||
{
|
{
|
||||||
QStringList jar, temp1, temp2, temp3;
|
QStringList jar, temp1, temp2, temp3;
|
||||||
agent->library()->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, getLocalLibraryPath());
|
agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath());
|
||||||
list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument()));
|
list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument()));
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList MinecraftInstance::javaArguments() const
|
QStringList MinecraftInstance::javaArguments()
|
||||||
{
|
{
|
||||||
QStringList args;
|
QStringList args;
|
||||||
|
|
||||||
@ -415,7 +437,7 @@ QStringList MinecraftInstance::javaArguments() const
|
|||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMap<QString, QString> MinecraftInstance::getVariables() const
|
QMap<QString, QString> MinecraftInstance::getVariables()
|
||||||
{
|
{
|
||||||
QMap<QString, QString> out;
|
QMap<QString, QString> out;
|
||||||
out.insert("INST_NAME", name());
|
out.insert("INST_NAME", name());
|
||||||
@ -447,13 +469,11 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
|||||||
QProcessEnvironment env = createEnvironment();
|
QProcessEnvironment env = createEnvironment();
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
if (settings()->get("EnableMangoHud").toBool())
|
if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud)
|
||||||
{
|
{
|
||||||
auto preload = env.value("LD_PRELOAD", "") + ":libMangoHud_dlsym.so:libMangoHud.so";
|
auto preload = env.value("LD_PRELOAD", "") + ":libMangoHud_dlsym.so:libMangoHud.so";
|
||||||
auto lib_path = env.value("LD_LIBRARY_PATH", "") + ":/usr/local/$LIB/mangohud/:/usr/$LIB/mangohud/";
|
|
||||||
|
|
||||||
env.insert("LD_PRELOAD", preload);
|
env.insert("LD_PRELOAD", preload);
|
||||||
env.insert("LD_LIBRARY_PATH", lib_path);
|
|
||||||
env.insert("MANGOHUD", "1");
|
env.insert("MANGOHUD", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -611,8 +631,7 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
|
|||||||
// libraries and class path.
|
// libraries and class path.
|
||||||
{
|
{
|
||||||
QStringList jars, nativeJars;
|
QStringList jars, nativeJars;
|
||||||
auto javaArchitecture = settings()->get("JavaArchitecture").toString();
|
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
|
||||||
profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
|
|
||||||
for(auto file: jars)
|
for(auto file: jars)
|
||||||
{
|
{
|
||||||
launchScript += "cp " + file + "\n";
|
launchScript += "cp " + file + "\n";
|
||||||
@ -668,8 +687,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
|||||||
{
|
{
|
||||||
out << "Libraries:";
|
out << "Libraries:";
|
||||||
QStringList jars, nativeJars;
|
QStringList jars, nativeJars;
|
||||||
auto javaArchitecture = settings->get("JavaArchitecture").toString();
|
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
|
||||||
profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
|
|
||||||
auto printLibFile = [&](const QString & path)
|
auto printLibFile = [&](const QString & path)
|
||||||
{
|
{
|
||||||
QFileInfo info(path);
|
QFileInfo info(path);
|
||||||
@ -700,14 +718,14 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
|||||||
{
|
{
|
||||||
out << QString("%1:").arg(label);
|
out << QString("%1:").arg(label);
|
||||||
auto modList = model.allMods();
|
auto modList = model.allMods();
|
||||||
std::sort(modList.begin(), modList.end(), [](Mod::Ptr a, Mod::Ptr b) {
|
std::sort(modList.begin(), modList.end(), [](auto a, auto b) {
|
||||||
auto aName = a->fileinfo().completeBaseName();
|
auto aName = a->fileinfo().completeBaseName();
|
||||||
auto bName = b->fileinfo().completeBaseName();
|
auto bName = b->fileinfo().completeBaseName();
|
||||||
return aName.localeAwareCompare(bName) < 0;
|
return aName.localeAwareCompare(bName) < 0;
|
||||||
});
|
});
|
||||||
for(auto mod: modList)
|
for(auto mod: modList)
|
||||||
{
|
{
|
||||||
if(mod->type() == Mod::MOD_FOLDER)
|
if(mod->type() == ResourceType::FOLDER)
|
||||||
{
|
{
|
||||||
out << u8" [🖿] " + mod->fileinfo().completeBaseName() + " (folder)";
|
out << u8" [🖿] " + mod->fileinfo().completeBaseName() + " (folder)";
|
||||||
continue;
|
continue;
|
||||||
@ -734,8 +752,8 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
|||||||
out << "Jar Mods:";
|
out << "Jar Mods:";
|
||||||
for(auto & jarmod: jarMods)
|
for(auto & jarmod: jarMods)
|
||||||
{
|
{
|
||||||
auto displayname = jarmod->displayName(currentSystem);
|
auto displayname = jarmod->displayName(runtimeContext());
|
||||||
auto realname = jarmod->filename(currentSystem);
|
auto realname = jarmod->filename(runtimeContext());
|
||||||
if(displayname != realname)
|
if(displayname != realname)
|
||||||
{
|
{
|
||||||
out << " " + displayname + " (" + realname + ")";
|
out << " " + displayname + " (" + realname + ")";
|
||||||
@ -897,6 +915,7 @@ QString MinecraftInstance::getStatusbarDescription()
|
|||||||
|
|
||||||
Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode)
|
Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode)
|
||||||
{
|
{
|
||||||
|
updateRuntimeContext();
|
||||||
switch (mode)
|
switch (mode)
|
||||||
{
|
{
|
||||||
case Net::Mode::Offline:
|
case Net::Mode::Offline:
|
||||||
@ -913,6 +932,7 @@ Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode)
|
|||||||
|
|
||||||
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
|
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
|
||||||
{
|
{
|
||||||
|
updateRuntimeContext();
|
||||||
// FIXME: get rid of shared_from_this ...
|
// FIXME: get rid of shared_from_this ...
|
||||||
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this()));
|
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this()));
|
||||||
auto pptr = process.get();
|
auto pptr = process.get();
|
||||||
@ -943,9 +963,9 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
|||||||
process->appendStep(new CreateGameFolders(pptr));
|
process->appendStep(new CreateGameFolders(pptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!serverToJoin && m_settings->get("JoinServerOnLaunch").toBool())
|
if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool())
|
||||||
{
|
{
|
||||||
QString fullAddress = m_settings->get("JoinServerOnLaunchAddress").toString();
|
QString fullAddress = settings()->get("JoinServerOnLaunchAddress").toString();
|
||||||
serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress)));
|
serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1053,10 +1073,10 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
|||||||
|
|
||||||
QString MinecraftInstance::launchMethod()
|
QString MinecraftInstance::launchMethod()
|
||||||
{
|
{
|
||||||
return m_settings->get("MCLaunchMethod").toString();
|
return settings()->get("MCLaunchMethod").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
JavaVersion MinecraftInstance::getJavaVersion() const
|
JavaVersion MinecraftInstance::getJavaVersion()
|
||||||
{
|
{
|
||||||
return JavaVersion(settings()->get("JavaVersion").toString());
|
return JavaVersion(settings()->get("JavaVersion").toString());
|
||||||
}
|
}
|
||||||
@ -1085,18 +1105,18 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const
|
|||||||
return m_core_mod_list;
|
return m_core_mod_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<ModFolderModel> MinecraftInstance::resourcePackList() const
|
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList() const
|
||||||
{
|
{
|
||||||
if (!m_resource_pack_list)
|
if (!m_resource_pack_list)
|
||||||
{
|
{
|
||||||
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir()));
|
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir()));
|
||||||
m_resource_pack_list->disableInteraction(isRunning());
|
m_resource_pack_list->enableInteraction(!isRunning());
|
||||||
connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ModFolderModel::disableInteraction);
|
connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ResourcePackFolderModel::disableInteraction);
|
||||||
}
|
}
|
||||||
return m_resource_pack_list;
|
return m_resource_pack_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const
|
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList() const
|
||||||
{
|
{
|
||||||
if (!m_texture_pack_list)
|
if (!m_texture_pack_list)
|
||||||
{
|
{
|
||||||
@ -1107,11 +1127,11 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const
|
|||||||
return m_texture_pack_list;
|
return m_texture_pack_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<ModFolderModel> MinecraftInstance::shaderPackList() const
|
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList() const
|
||||||
{
|
{
|
||||||
if (!m_shader_pack_list)
|
if (!m_shader_pack_list)
|
||||||
{
|
{
|
||||||
m_shader_pack_list.reset(new ResourcePackFolderModel(shaderPacksDir()));
|
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir()));
|
||||||
m_shader_pack_list->disableInteraction(isRunning());
|
m_shader_pack_list->disableInteraction(isRunning());
|
||||||
connect(this, &BaseInstance::runningStatusChanged, m_shader_pack_list.get(), &ModFolderModel::disableInteraction);
|
connect(this, &BaseInstance::runningStatusChanged, m_shader_pack_list.get(), &ModFolderModel::disableInteraction);
|
||||||
}
|
}
|
||||||
@ -1143,7 +1163,7 @@ QList<Mod*> MinecraftInstance::getJarMods() const
|
|||||||
for (auto jarmod : profile->getJarMods())
|
for (auto jarmod : profile->getJarMods())
|
||||||
{
|
{
|
||||||
QStringList jar, temp1, temp2, temp3;
|
QStringList jar, temp1, temp2, temp3;
|
||||||
jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath());
|
jarmod->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, jarmodsPath().absolutePath());
|
||||||
// QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem));
|
// QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem));
|
||||||
mods.push_back(new Mod(QFileInfo(jar[0])));
|
mods.push_back(new Mod(QFileInfo(jar[0])));
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include <java/JavaVersion.h>
|
#include <java/JavaVersion.h>
|
||||||
@ -7,6 +42,10 @@
|
|||||||
#include "minecraft/launch/MinecraftServerTarget.h"
|
#include "minecraft/launch/MinecraftServerTarget.h"
|
||||||
|
|
||||||
class ModFolderModel;
|
class ModFolderModel;
|
||||||
|
class ResourceFolderModel;
|
||||||
|
class ResourcePackFolderModel;
|
||||||
|
class ShaderPackFolderModel;
|
||||||
|
class TexturePackFolderModel;
|
||||||
class WorldList;
|
class WorldList;
|
||||||
class GameOptions;
|
class GameOptions;
|
||||||
class LaunchStep;
|
class LaunchStep;
|
||||||
@ -20,6 +59,8 @@ public:
|
|||||||
virtual ~MinecraftInstance() {};
|
virtual ~MinecraftInstance() {};
|
||||||
virtual void saveNow() override;
|
virtual void saveNow() override;
|
||||||
|
|
||||||
|
void loadSpecificSettings() override;
|
||||||
|
|
||||||
// FIXME: remove
|
// FIXME: remove
|
||||||
QString typeName() const override;
|
QString typeName() const override;
|
||||||
// FIXME: remove
|
// FIXME: remove
|
||||||
@ -63,6 +104,10 @@ public:
|
|||||||
// where the instance-local libraries should be
|
// where the instance-local libraries should be
|
||||||
QString getLocalLibraryPath() const;
|
QString getLocalLibraryPath() const;
|
||||||
|
|
||||||
|
/** Returns whether the instance, with its version, has support for demo mode. */
|
||||||
|
[[nodiscard]] bool supportsDemo() const;
|
||||||
|
|
||||||
|
void updateRuntimeContext();
|
||||||
|
|
||||||
////// Profile management //////
|
////// Profile management //////
|
||||||
std::shared_ptr<PackProfile> getPackProfile() const;
|
std::shared_ptr<PackProfile> getPackProfile() const;
|
||||||
@ -70,24 +115,24 @@ public:
|
|||||||
////// Mod Lists //////
|
////// Mod Lists //////
|
||||||
std::shared_ptr<ModFolderModel> loaderModList() const;
|
std::shared_ptr<ModFolderModel> loaderModList() const;
|
||||||
std::shared_ptr<ModFolderModel> coreModList() const;
|
std::shared_ptr<ModFolderModel> coreModList() const;
|
||||||
std::shared_ptr<ModFolderModel> resourcePackList() const;
|
std::shared_ptr<ResourcePackFolderModel> resourcePackList() const;
|
||||||
std::shared_ptr<ModFolderModel> texturePackList() const;
|
std::shared_ptr<TexturePackFolderModel> texturePackList() const;
|
||||||
std::shared_ptr<ModFolderModel> shaderPackList() const;
|
std::shared_ptr<ShaderPackFolderModel> shaderPackList() const;
|
||||||
std::shared_ptr<WorldList> worldList() const;
|
std::shared_ptr<WorldList> worldList() const;
|
||||||
std::shared_ptr<GameOptions> gameOptionsModel() const;
|
std::shared_ptr<GameOptions> gameOptionsModel() const;
|
||||||
|
|
||||||
////// Launch stuff //////
|
////// Launch stuff //////
|
||||||
Task::Ptr createUpdateTask(Net::Mode mode) override;
|
Task::Ptr createUpdateTask(Net::Mode mode) override;
|
||||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override;
|
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override;
|
||||||
QStringList extraArguments() const override;
|
QStringList extraArguments() override;
|
||||||
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override;
|
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override;
|
||||||
QList<Mod*> getJarMods() const;
|
QList<Mod*> getJarMods() const;
|
||||||
QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin);
|
QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin);
|
||||||
/// get arguments passed to java
|
/// get arguments passed to java
|
||||||
QStringList javaArguments() const;
|
QStringList javaArguments();
|
||||||
|
|
||||||
/// get variables for launch command variable substitution/environment
|
/// get variables for launch command variable substitution/environment
|
||||||
QMap<QString, QString> getVariables() const override;
|
QMap<QString, QString> getVariables() override;
|
||||||
|
|
||||||
/// create an environment for launching processes
|
/// create an environment for launching processes
|
||||||
QProcessEnvironment createEnvironment() override;
|
QProcessEnvironment createEnvironment() override;
|
||||||
@ -103,16 +148,16 @@ public:
|
|||||||
QString getStatusbarDescription() override;
|
QString getStatusbarDescription() override;
|
||||||
|
|
||||||
// FIXME: remove
|
// FIXME: remove
|
||||||
virtual QStringList getClassPath() const;
|
virtual QStringList getClassPath();
|
||||||
// FIXME: remove
|
// FIXME: remove
|
||||||
virtual QStringList getNativeJars() const;
|
virtual QStringList getNativeJars();
|
||||||
// FIXME: remove
|
// FIXME: remove
|
||||||
virtual QString getMainClass() const;
|
virtual QString getMainClass() const;
|
||||||
|
|
||||||
// FIXME: remove
|
// FIXME: remove
|
||||||
virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) const;
|
virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) const;
|
||||||
|
|
||||||
virtual JavaVersion getJavaVersion() const;
|
virtual JavaVersion getJavaVersion();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
|
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
|
||||||
@ -123,9 +168,9 @@ protected: // data
|
|||||||
std::shared_ptr<PackProfile> m_components;
|
std::shared_ptr<PackProfile> m_components;
|
||||||
mutable std::shared_ptr<ModFolderModel> m_loader_mod_list;
|
mutable std::shared_ptr<ModFolderModel> m_loader_mod_list;
|
||||||
mutable std::shared_ptr<ModFolderModel> m_core_mod_list;
|
mutable std::shared_ptr<ModFolderModel> m_core_mod_list;
|
||||||
mutable std::shared_ptr<ModFolderModel> m_resource_pack_list;
|
mutable std::shared_ptr<ResourcePackFolderModel> m_resource_pack_list;
|
||||||
mutable std::shared_ptr<ModFolderModel> m_shader_pack_list;
|
mutable std::shared_ptr<ShaderPackFolderModel> m_shader_pack_list;
|
||||||
mutable std::shared_ptr<ModFolderModel> m_texture_pack_list;
|
mutable std::shared_ptr<TexturePackFolderModel> m_texture_pack_list;
|
||||||
mutable std::shared_ptr<WorldList> m_world_list;
|
mutable std::shared_ptr<WorldList> m_world_list;
|
||||||
mutable std::shared_ptr<GameOptions> m_game_options;
|
mutable std::shared_ptr<GameOptions> m_game_options;
|
||||||
};
|
};
|
||||||
|
@ -43,7 +43,7 @@ void MinecraftUpdate::executeTask()
|
|||||||
m_tasks.clear();
|
m_tasks.clear();
|
||||||
// create folders
|
// create folders
|
||||||
{
|
{
|
||||||
m_tasks.append(std::make_shared<FoldersTask>(m_inst));
|
m_tasks.append(new FoldersTask(m_inst));
|
||||||
}
|
}
|
||||||
|
|
||||||
// add metadata update task if necessary
|
// add metadata update task if necessary
|
||||||
@ -53,23 +53,23 @@ void MinecraftUpdate::executeTask()
|
|||||||
auto task = components->getCurrentTask();
|
auto task = components->getCurrentTask();
|
||||||
if(task)
|
if(task)
|
||||||
{
|
{
|
||||||
m_tasks.append(task.unwrap());
|
m_tasks.append(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// libraries download
|
// libraries download
|
||||||
{
|
{
|
||||||
m_tasks.append(std::make_shared<LibrariesTask>(m_inst));
|
m_tasks.append(new LibrariesTask(m_inst));
|
||||||
}
|
}
|
||||||
|
|
||||||
// FML libraries download and copy into the instance
|
// FML libraries download and copy into the instance
|
||||||
{
|
{
|
||||||
m_tasks.append(std::make_shared<FMLLibrariesTask>(m_inst));
|
m_tasks.append(new FMLLibrariesTask(m_inst));
|
||||||
}
|
}
|
||||||
|
|
||||||
// assets update
|
// assets update
|
||||||
{
|
{
|
||||||
m_tasks.append(std::make_shared<AssetUpdateTask>(m_inst));
|
m_tasks.append(new AssetUpdateTask(m_inst));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!m_preFailure.isEmpty())
|
if(!m_preFailure.isEmpty())
|
||||||
|
@ -50,7 +50,7 @@ private:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
MinecraftInstance *m_inst = nullptr;
|
MinecraftInstance *m_inst = nullptr;
|
||||||
QList<std::shared_ptr<Task>> m_tasks;
|
QList<Task::Ptr> m_tasks;
|
||||||
QString m_preFailure;
|
QString m_preFailure;
|
||||||
int m_currentTask = -1;
|
int m_currentTask = -1;
|
||||||
bool m_abort = false;
|
bool m_abort = false;
|
||||||
|
@ -214,7 +214,7 @@ void MojangVersionFormat::readVersionProperties(const QJsonObject &in, VersionFi
|
|||||||
QObject::tr("The 'minimumLauncherVersion' value of this version (%1) is higher than supported by %3 (%2). It might not work properly!")
|
QObject::tr("The 'minimumLauncherVersion' value of this version (%1) is higher than supported by %3 (%2). It might not work properly!")
|
||||||
.arg(out->minimumLauncherVersion)
|
.arg(out->minimumLauncherVersion)
|
||||||
.arg(CURRENT_MINIMUM_LAUNCHER_VERSION)
|
.arg(CURRENT_MINIMUM_LAUNCHER_VERSION)
|
||||||
.arg(BuildConfig.LAUNCHER_NAME)
|
.arg(BuildConfig.LAUNCHER_DISPLAYNAME)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -362,11 +362,8 @@ LibraryPtr MojangVersionFormat::libraryFromJson(ProblemContainer & problems, con
|
|||||||
{
|
{
|
||||||
qWarning() << filename << "contains an invalid native (skipping)";
|
qWarning() << filename << "contains an invalid native (skipping)";
|
||||||
}
|
}
|
||||||
OpSys opSys = OpSys_fromString(it.key());
|
// FIXME: Skip unknown platforms
|
||||||
if (opSys != Os_Other)
|
out->m_nativeClassifiers[it.key()] = it.value().toString();
|
||||||
{
|
|
||||||
out->m_nativeClassifiers[opSys] = it.value().toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (libObj.contains("rules"))
|
if (libObj.contains("rules"))
|
||||||
@ -395,7 +392,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
|
|||||||
auto iter = library->m_nativeClassifiers.begin();
|
auto iter = library->m_nativeClassifiers.begin();
|
||||||
while (iter != library->m_nativeClassifiers.end())
|
while (iter != library->m_nativeClassifiers.end())
|
||||||
{
|
{
|
||||||
nativeList.insert(OpSys_toString(iter.key()), iter.value());
|
nativeList.insert(iter.key(), iter.value());
|
||||||
iter++;
|
iter++;
|
||||||
}
|
}
|
||||||
libRoot.insert("natives", nativeList);
|
libRoot.insert("natives", nativeList);
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
/* 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 "OpSys.h"
|
|
||||||
|
|
||||||
OpSys OpSys_fromString(QString name)
|
|
||||||
{
|
|
||||||
if (name == "freebsd")
|
|
||||||
return Os_FreeBSD;
|
|
||||||
if (name == "linux")
|
|
||||||
return Os_Linux;
|
|
||||||
if (name == "windows")
|
|
||||||
return Os_Windows;
|
|
||||||
if (name == "osx")
|
|
||||||
return Os_OSX;
|
|
||||||
return Os_Other;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString OpSys_toString(OpSys name)
|
|
||||||
{
|
|
||||||
switch (name)
|
|
||||||
{
|
|
||||||
case Os_FreeBSD:
|
|
||||||
return "freebsd";
|
|
||||||
case Os_Linux:
|
|
||||||
return "linux";
|
|
||||||
case Os_OSX:
|
|
||||||
return "osx";
|
|
||||||
case Os_Windows:
|
|
||||||
return "windows";
|
|
||||||
default:
|
|
||||||
return "other";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <QString>
|
|
||||||
enum OpSys
|
|
||||||
{
|
|
||||||
Os_Windows,
|
|
||||||
Os_FreeBSD,
|
|
||||||
Os_Linux,
|
|
||||||
Os_OSX,
|
|
||||||
Os_Other
|
|
||||||
};
|
|
||||||
|
|
||||||
OpSys OpSys_fromString(QString);
|
|
||||||
QString OpSys_toString(OpSys);
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
|
||||||
#define currentSystem Os_Windows
|
|
||||||
#elif defined Q_OS_MAC
|
|
||||||
#define currentSystem Os_OSX
|
|
||||||
#elif defined Q_OS_FREEBSD
|
|
||||||
#define currentSystem Os_FreeBSD
|
|
||||||
#else
|
|
||||||
#define currentSystem Os_Linux
|
|
||||||
#endif
|
|
@ -273,6 +273,11 @@ void PackProfile::scheduleSave()
|
|||||||
d->m_saveTimer.start();
|
d->m_saveTimer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RuntimeContext PackProfile::runtimeContext()
|
||||||
|
{
|
||||||
|
return d->m_instance->runtimeContext();
|
||||||
|
}
|
||||||
|
|
||||||
QString PackProfile::componentsFilePath() const
|
QString PackProfile::componentsFilePath() const
|
||||||
{
|
{
|
||||||
return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json");
|
return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json");
|
||||||
@ -784,7 +789,7 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
QStringList jar, temp1, temp2, temp3;
|
QStringList jar, temp1, temp2, temp3;
|
||||||
jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath());
|
jarMod->getApplicableFiles(d->m_instance->runtimeContext(), jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath());
|
||||||
QFileInfo finfo (jar[0]);
|
QFileInfo finfo (jar[0]);
|
||||||
if(finfo.exists())
|
if(finfo.exists())
|
||||||
{
|
{
|
||||||
|
@ -1,16 +1,36 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* you may not use this file except in compliance with the License.
|
* it under the terms of the GNU General Public License as published by
|
||||||
* You may obtain a copy of the License at
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* You should have received a copy of the GNU General Public License
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
*
|
||||||
* See the License for the specific language governing permissions and
|
* This file incorporates work covered by the following copyright and
|
||||||
* limitations under the License.
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@ -104,6 +124,9 @@ public:
|
|||||||
/// if there is a save scheduled, do it now.
|
/// if there is a save scheduled, do it now.
|
||||||
void saveNow();
|
void saveNow();
|
||||||
|
|
||||||
|
/// helper method, returns RuntimeContext of instance
|
||||||
|
RuntimeContext runtimeContext();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void minecraftChanged();
|
void minecraftChanged();
|
||||||
|
|
||||||
|
@ -1,16 +1,36 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* you may not use this file except in compliance with the License.
|
* it under the terms of the GNU General Public License as published by
|
||||||
* You may obtain a copy of the License at
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* You should have received a copy of the GNU General Public License
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
*
|
||||||
* See the License for the specific language governing permissions and
|
* This file incorporates work covered by the following copyright and
|
||||||
* limitations under the License.
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
@ -60,10 +80,10 @@ QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules)
|
|||||||
auto osNameVal = osObj.value("name");
|
auto osNameVal = osObj.value("name");
|
||||||
if (!osNameVal.isString())
|
if (!osNameVal.isString())
|
||||||
continue;
|
continue;
|
||||||
OpSys requiredOs = OpSys_fromString(osNameVal.toString());
|
QString osName = osNameVal.toString();
|
||||||
QString versionRegex = osObj.value("version").toString();
|
QString versionRegex = osObj.value("version").toString();
|
||||||
// add a new OS rule
|
// add a new OS rule
|
||||||
rules.append(OsRule::create(action, requiredOs, versionRegex));
|
rules.append(OsRule::create(action, osName, versionRegex));
|
||||||
}
|
}
|
||||||
return rules;
|
return rules;
|
||||||
}
|
}
|
||||||
@ -81,7 +101,7 @@ QJsonObject OsRule::toJson()
|
|||||||
ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow"));
|
ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow"));
|
||||||
QJsonObject osObj;
|
QJsonObject osObj;
|
||||||
{
|
{
|
||||||
osObj.insert("name", OpSys_toString(m_system));
|
osObj.insert("name", m_system);
|
||||||
if(!m_version_regexp.isEmpty())
|
if(!m_version_regexp.isEmpty())
|
||||||
{
|
{
|
||||||
osObj.insert("version", m_version_regexp);
|
osObj.insert("version", m_version_regexp);
|
||||||
|
@ -1,16 +1,36 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* you may not use this file except in compliance with the License.
|
* it under the terms of the GNU General Public License as published by
|
||||||
* You may obtain a copy of the License at
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* You should have received a copy of the GNU General Public License
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
*
|
||||||
* See the License for the specific language governing permissions and
|
* This file incorporates work covered by the following copyright and
|
||||||
* limitations under the License.
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@ -19,7 +39,7 @@
|
|||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "OpSys.h"
|
#include "RuntimeContext.h"
|
||||||
|
|
||||||
class Library;
|
class Library;
|
||||||
class Rule;
|
class Rule;
|
||||||
@ -37,7 +57,7 @@ class Rule
|
|||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
RuleAction m_result;
|
RuleAction m_result;
|
||||||
virtual bool applies(const Library *parent) = 0;
|
virtual bool applies(const Library *parent, const RuntimeContext & runtimeContext) = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Rule(RuleAction result) : m_result(result)
|
Rule(RuleAction result) : m_result(result)
|
||||||
@ -45,9 +65,9 @@ public:
|
|||||||
}
|
}
|
||||||
virtual ~Rule() {};
|
virtual ~Rule() {};
|
||||||
virtual QJsonObject toJson() = 0;
|
virtual QJsonObject toJson() = 0;
|
||||||
RuleAction apply(const Library *parent)
|
RuleAction apply(const Library *parent, const RuntimeContext & runtimeContext)
|
||||||
{
|
{
|
||||||
if (applies(parent))
|
if (applies(parent, runtimeContext))
|
||||||
return m_result;
|
return m_result;
|
||||||
else
|
else
|
||||||
return Defer;
|
return Defer;
|
||||||
@ -58,23 +78,23 @@ class OsRule : public Rule
|
|||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
// the OS
|
// the OS
|
||||||
OpSys m_system;
|
QString m_system;
|
||||||
// the OS version regexp
|
// the OS version regexp
|
||||||
QString m_version_regexp;
|
QString m_version_regexp;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool applies(const Library *)
|
virtual bool applies(const Library *, const RuntimeContext & runtimeContext)
|
||||||
{
|
{
|
||||||
return (m_system == currentSystem);
|
return runtimeContext.classifierMatches(m_system);
|
||||||
}
|
}
|
||||||
OsRule(RuleAction result, OpSys system, QString version_regexp)
|
OsRule(RuleAction result, QString system, QString version_regexp)
|
||||||
: Rule(result), m_system(system), m_version_regexp(version_regexp)
|
: Rule(result), m_system(system), m_version_regexp(version_regexp)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual QJsonObject toJson();
|
virtual QJsonObject toJson();
|
||||||
static std::shared_ptr<OsRule> create(RuleAction result, OpSys system,
|
static std::shared_ptr<OsRule> create(RuleAction result, QString system,
|
||||||
QString version_regexp)
|
QString version_regexp)
|
||||||
{
|
{
|
||||||
return std::shared_ptr<OsRule>(new OsRule(result, system, version_regexp));
|
return std::shared_ptr<OsRule>(new OsRule(result, system, version_regexp));
|
||||||
@ -84,7 +104,7 @@ public:
|
|||||||
class ImplicitRule : public Rule
|
class ImplicitRule : public Rule
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
virtual bool applies(const Library *)
|
virtual bool applies(const Library *, const RuntimeContext & runtimeContext)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
34
launcher/minecraft/VanillaInstanceCreationTask.cpp
Normal file
34
launcher/minecraft/VanillaInstanceCreationTask.cpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#include "VanillaInstanceCreationTask.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "minecraft/MinecraftInstance.h"
|
||||||
|
#include "minecraft/PackProfile.h"
|
||||||
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
|
VanillaCreationTask::VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version)
|
||||||
|
: InstanceCreationTask(), m_version(std::move(version)), m_using_loader(true), m_loader(std::move(loader)), m_loader_version(std::move(loader_version))
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool VanillaCreationTask::createInstance()
|
||||||
|
{
|
||||||
|
setStatus(tr("Creating instance from version %1").arg(m_version->name()));
|
||||||
|
|
||||||
|
auto instance_settings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
|
||||||
|
instance_settings->suspendSave();
|
||||||
|
{
|
||||||
|
MinecraftInstance inst(m_globalSettings, instance_settings, m_stagingPath);
|
||||||
|
auto components = inst.getPackProfile();
|
||||||
|
components->buildingFromScratch();
|
||||||
|
components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
|
||||||
|
if(m_using_loader)
|
||||||
|
components->setComponentVersion(m_loader, m_loader_version->descriptor());
|
||||||
|
|
||||||
|
inst.setName(name());
|
||||||
|
inst.setIconKey(m_instIcon);
|
||||||
|
}
|
||||||
|
instance_settings->resumeSave();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
22
launcher/minecraft/VanillaInstanceCreationTask.h
Normal file
22
launcher/minecraft/VanillaInstanceCreationTask.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "InstanceCreationTask.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
class VanillaCreationTask final : public InstanceCreationTask {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
VanillaCreationTask(BaseVersionPtr version) : InstanceCreationTask(), m_version(std::move(version)) {}
|
||||||
|
VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version);
|
||||||
|
|
||||||
|
bool createInstance() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Version to update to / create of the instance.
|
||||||
|
BaseVersionPtr m_version;
|
||||||
|
|
||||||
|
bool m_using_loader = false;
|
||||||
|
QString m_loader;
|
||||||
|
BaseVersionPtr m_loader_version;
|
||||||
|
};
|
@ -51,7 +51,7 @@ static bool isMinecraftVersion(const QString &uid)
|
|||||||
return uid == "net.minecraft";
|
return uid == "net.minecraft";
|
||||||
}
|
}
|
||||||
|
|
||||||
void VersionFile::applyTo(LaunchProfile *profile)
|
void VersionFile::applyTo(LaunchProfile *profile, const RuntimeContext & runtimeContext)
|
||||||
{
|
{
|
||||||
// Only real Minecraft can set those. Don't let anything override them.
|
// Only real Minecraft can set those. Don't let anything override them.
|
||||||
if (isMinecraftVersion(uid))
|
if (isMinecraftVersion(uid))
|
||||||
@ -77,15 +77,15 @@ void VersionFile::applyTo(LaunchProfile *profile)
|
|||||||
|
|
||||||
for (auto library : libraries)
|
for (auto library : libraries)
|
||||||
{
|
{
|
||||||
profile->applyLibrary(library);
|
profile->applyLibrary(library, runtimeContext);
|
||||||
}
|
}
|
||||||
for (auto mavenFile : mavenFiles)
|
for (auto mavenFile : mavenFiles)
|
||||||
{
|
{
|
||||||
profile->applyMavenFile(mavenFile);
|
profile->applyMavenFile(mavenFile, runtimeContext);
|
||||||
}
|
}
|
||||||
for (auto agent : agents)
|
for (auto agent : agents)
|
||||||
{
|
{
|
||||||
profile->applyAgent(agent);
|
profile->applyAgent(agent, runtimeContext);
|
||||||
}
|
}
|
||||||
profile->applyProblemSeverity(getProblemSeverity());
|
profile->applyProblemSeverity(getProblemSeverity());
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,6 @@
|
|||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "minecraft/OpSys.h"
|
|
||||||
#include "minecraft/Rule.h"
|
#include "minecraft/Rule.h"
|
||||||
#include "ProblemProvider.h"
|
#include "ProblemProvider.h"
|
||||||
#include "Library.h"
|
#include "Library.h"
|
||||||
@ -60,22 +59,22 @@ class VersionFile : public ProblemContainer
|
|||||||
friend class MojangVersionFormat;
|
friend class MojangVersionFormat;
|
||||||
friend class OneSixVersionFormat;
|
friend class OneSixVersionFormat;
|
||||||
public: /* methods */
|
public: /* methods */
|
||||||
void applyTo(LaunchProfile* profile);
|
void applyTo(LaunchProfile* profile, const RuntimeContext & runtimeContext);
|
||||||
|
|
||||||
public: /* data */
|
public: /* data */
|
||||||
/// PolyMC: order hint for this version file if no explicit order is set
|
/// Prism Launcher: order hint for this version file if no explicit order is set
|
||||||
int order = 0;
|
int order = 0;
|
||||||
|
|
||||||
/// PolyMC: human readable name of this package
|
/// Prism Launcher: human readable name of this package
|
||||||
QString name;
|
QString name;
|
||||||
|
|
||||||
/// PolyMC: package ID of this package
|
/// Prism Launcher: package ID of this package
|
||||||
QString uid;
|
QString uid;
|
||||||
|
|
||||||
/// PolyMC: version of this package
|
/// Prism Launcher: version of this package
|
||||||
QString version;
|
QString version;
|
||||||
|
|
||||||
/// PolyMC: DEPRECATED dependency on a Minecraft version
|
/// Prism Launcher: DEPRECATED dependency on a Minecraft version
|
||||||
QString dependsOnMinecraftVersion;
|
QString dependsOnMinecraftVersion;
|
||||||
|
|
||||||
/// Mojang: DEPRECATED used to version the Mojang version format
|
/// Mojang: DEPRECATED used to version the Mojang version format
|
||||||
@ -87,13 +86,13 @@ public: /* data */
|
|||||||
/// Mojang: class to launch Minecraft with
|
/// Mojang: class to launch Minecraft with
|
||||||
QString mainClass;
|
QString mainClass;
|
||||||
|
|
||||||
/// PolyMC: class to launch legacy Minecraft with (embed in a custom window)
|
/// Prism Launcher: class to launch legacy Minecraft with (embed in a custom window)
|
||||||
QString appletClass;
|
QString appletClass;
|
||||||
|
|
||||||
/// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution)
|
/// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution)
|
||||||
QString minecraftArguments;
|
QString minecraftArguments;
|
||||||
|
|
||||||
/// PolyMC: Additional JVM launch arguments
|
/// Prism Launcher: Additional JVM launch arguments
|
||||||
QStringList addnJvmArguments;
|
QStringList addnJvmArguments;
|
||||||
|
|
||||||
/// Mojang: list of compatible java majors
|
/// Mojang: list of compatible java majors
|
||||||
@ -111,38 +110,38 @@ public: /* data */
|
|||||||
/// Mojang: DEPRECATED asset group to be used with Minecraft
|
/// Mojang: DEPRECATED asset group to be used with Minecraft
|
||||||
QString assets;
|
QString assets;
|
||||||
|
|
||||||
/// PolyMC: list of tweaker mod arguments for launchwrapper
|
/// Prism Launcher: list of tweaker mod arguments for launchwrapper
|
||||||
QStringList addTweakers;
|
QStringList addTweakers;
|
||||||
|
|
||||||
/// Mojang: list of libraries to add to the version
|
/// Mojang: list of libraries to add to the version
|
||||||
QList<LibraryPtr> libraries;
|
QList<LibraryPtr> libraries;
|
||||||
|
|
||||||
/// PolyMC: list of maven files to put in the libraries folder, but not in classpath
|
/// Prism Launcher: list of maven files to put in the libraries folder, but not in classpath
|
||||||
QList<LibraryPtr> mavenFiles;
|
QList<LibraryPtr> mavenFiles;
|
||||||
|
|
||||||
/// PolyMC: list of agents to add to JVM arguments
|
/// Prism Launcher: list of agents to add to JVM arguments
|
||||||
QList<AgentPtr> agents;
|
QList<AgentPtr> agents;
|
||||||
|
|
||||||
/// The main jar (Minecraft version library, normally)
|
/// The main jar (Minecraft version library, normally)
|
||||||
LibraryPtr mainJar;
|
LibraryPtr mainJar;
|
||||||
|
|
||||||
/// PolyMC: list of attached traits of this version file - used to enable features
|
/// Prism Launcher: list of attached traits of this version file - used to enable features
|
||||||
QSet<QString> traits;
|
QSet<QString> traits;
|
||||||
|
|
||||||
/// PolyMC: list of jar mods added to this version
|
/// Prism Launcher: list of jar mods added to this version
|
||||||
QList<LibraryPtr> jarMods;
|
QList<LibraryPtr> jarMods;
|
||||||
|
|
||||||
/// PolyMC: list of mods added to this version
|
/// Prism Launcher: list of mods added to this version
|
||||||
QList<LibraryPtr> mods;
|
QList<LibraryPtr> mods;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PolyMC: set of packages this depends on
|
* Prism Launcher: set of packages this depends on
|
||||||
* NOTE: this is shared with the meta format!!!
|
* NOTE: this is shared with the meta format!!!
|
||||||
*/
|
*/
|
||||||
Meta::RequireSet requires;
|
Meta::RequireSet requires;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PolyMC: set of packages this conflicts with
|
* Prism Launcher: set of packages this conflicts with
|
||||||
* NOTE: this is shared with the meta format!!!
|
* NOTE: this is shared with the meta format!!!
|
||||||
*/
|
*/
|
||||||
Meta::RequireSet conflicts;
|
Meta::RequireSet conflicts;
|
||||||
|
@ -53,12 +53,12 @@
|
|||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
|
||||||
#include <nonstd/optional>
|
#include <optional>
|
||||||
|
|
||||||
using nonstd::optional;
|
using std::optional;
|
||||||
using nonstd::nullopt;
|
using std::nullopt;
|
||||||
|
|
||||||
GameType::GameType(nonstd::optional<int> original):
|
GameType::GameType(std::optional<int> original):
|
||||||
original(original)
|
original(original)
|
||||||
{
|
{
|
||||||
if(!original) {
|
if(!original) {
|
||||||
|
@ -16,11 +16,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <nonstd/optional>
|
#include <optional>
|
||||||
|
|
||||||
struct GameType {
|
struct GameType {
|
||||||
GameType() = default;
|
GameType() = default;
|
||||||
GameType (nonstd::optional<int> original);
|
GameType (std::optional<int> original);
|
||||||
|
|
||||||
QString toTranslatedString() const;
|
QString toTranslatedString() const;
|
||||||
QString toLogString() const;
|
QString toLogString() const;
|
||||||
@ -33,7 +33,7 @@ struct GameType {
|
|||||||
Adventure,
|
Adventure,
|
||||||
Spectator
|
Spectator
|
||||||
} type = Unknown;
|
} type = Unknown;
|
||||||
nonstd::optional<int> original;
|
std::optional<int> original;
|
||||||
};
|
};
|
||||||
|
|
||||||
class World
|
class World
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
* List of available Mojang accounts.
|
* List of available Mojang accounts.
|
||||||
* This should be loaded in the background by PolyMC on startup.
|
* This should be loaded in the background by Prism Launcher on startup.
|
||||||
*/
|
*/
|
||||||
class AccountList : public QAbstractListModel
|
class AccountList : public QAbstractListModel
|
||||||
{
|
{
|
||||||
|
@ -238,7 +238,7 @@ void MinecraftAccount::authFailed(QString reason)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool MinecraftAccount::isActive() const {
|
bool MinecraftAccount::isActive() const {
|
||||||
return m_currentTask;
|
return !m_currentTask.isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MinecraftAccount::shouldRefresh() const {
|
bool MinecraftAccount::shouldRefresh() const {
|
||||||
|
@ -61,7 +61,7 @@ Q_DECLARE_METATYPE(MinecraftAccountPtr)
|
|||||||
* A profile within someone's Mojang account.
|
* A profile within someone's Mojang account.
|
||||||
*
|
*
|
||||||
* Currently, the profile system has not been implemented by Mojang yet,
|
* Currently, the profile system has not been implemented by Mojang yet,
|
||||||
* but we might as well add some things for it in PolyMC right now so
|
* but we might as well add some things for it in Prism Launcher right now so
|
||||||
* we don't have to rip the code to pieces to add it later.
|
* we don't have to rip the code to pieces to add it later.
|
||||||
*/
|
*/
|
||||||
struct AccountProfile
|
struct AccountProfile
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
#include <Commandline.h>
|
#include <Commandline.h>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
#include "gamemode_client.h"
|
#include "gamemode_client.h"
|
||||||
#endif
|
#endif
|
||||||
@ -86,7 +88,7 @@ void DirectJavaLaunch::executeTask()
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
if (instance->settings()->get("EnableFeralGamemode").toBool())
|
if (instance->settings()->get("EnableFeralGamemode").toBool() && APPLICATION->capabilities() & Application::SupportsGameMode)
|
||||||
{
|
{
|
||||||
auto pid = m_process.processId();
|
auto pid = m_process.processId();
|
||||||
if (pid)
|
if (pid)
|
||||||
|
@ -154,7 +154,7 @@ void LauncherPartLaunch::executeTask()
|
|||||||
#else
|
#else
|
||||||
args << classPath.join(':');
|
args << classPath.join(':');
|
||||||
#endif
|
#endif
|
||||||
args << "org.polymc.EntryPoint";
|
args << "org.prismlauncher.EntryPoint";
|
||||||
|
|
||||||
qDebug() << args.join(' ');
|
qDebug() << args.join(' ');
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ void LauncherPartLaunch::executeTask()
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
if (instance->settings()->get("EnableFeralGamemode").toBool())
|
if (instance->settings()->get("EnableFeralGamemode").toBool() && APPLICATION->capabilities() & Application::SupportsGameMode)
|
||||||
{
|
{
|
||||||
auto pid = m_process.processId();
|
auto pid = m_process.processId();
|
||||||
if (pid)
|
if (pid)
|
||||||
|
@ -23,7 +23,7 @@ MinecraftServerTarget MinecraftServerTarget::parse(const QString &fullAddress) {
|
|||||||
|
|
||||||
// The logic below replicates the exact logic minecraft uses for parsing server addresses.
|
// The logic below replicates the exact logic minecraft uses for parsing server addresses.
|
||||||
// While the conversion is not lossless and eats errors, it ensures the same behavior
|
// While the conversion is not lossless and eats errors, it ensures the same behavior
|
||||||
// within Minecraft and PolyMC when entering server addresses.
|
// within Minecraft and Prism Launcher when entering server addresses.
|
||||||
if (fullAddress.startsWith("["))
|
if (fullAddress.startsWith("["))
|
||||||
{
|
{
|
||||||
int bracket = fullAddress.indexOf("]");
|
int bracket = fullAddress.indexOf("]");
|
||||||
|
@ -1,22 +1,41 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* you may not use this file except in compliance with the License.
|
* it under the terms of the GNU General Public License as published by
|
||||||
* You may obtain a copy of the License at
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* You should have received a copy of the GNU General Public License
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
*
|
||||||
* See the License for the specific language governing permissions and
|
* This file incorporates work covered by the following copyright and
|
||||||
* limitations under the License.
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ModMinecraftJar.h"
|
#include "ModMinecraftJar.h"
|
||||||
#include "launch/LaunchTask.h"
|
#include "launch/LaunchTask.h"
|
||||||
#include "MMCZip.h"
|
#include "MMCZip.h"
|
||||||
#include "minecraft/OpSys.h"
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
#include "minecraft/PackProfile.h"
|
#include "minecraft/PackProfile.h"
|
||||||
@ -50,7 +69,7 @@ void ModMinecraftJar::executeTask()
|
|||||||
{
|
{
|
||||||
auto mainJar = profile->getMainJar();
|
auto mainJar = profile->getMainJar();
|
||||||
QStringList jars, temp1, temp2, temp3, temp4;
|
QStringList jars, temp1, temp2, temp3, temp4;
|
||||||
mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath());
|
mainJar->getApplicableFiles(m_inst->runtimeContext(), jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath());
|
||||||
auto sourceJarPath = jars[0];
|
auto sourceJarPath = jars[0];
|
||||||
if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods))
|
if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods))
|
||||||
{
|
{
|
||||||
|
@ -1,22 +1,41 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* you may not use this file except in compliance with the License.
|
* it under the terms of the GNU General Public License as published by
|
||||||
* You may obtain a copy of the License at
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* You should have received a copy of the GNU General Public License
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
*
|
||||||
* See the License for the specific language governing permissions and
|
* This file incorporates work covered by the following copyright and
|
||||||
* limitations under the License.
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ScanModFolders.h"
|
#include "ScanModFolders.h"
|
||||||
#include "launch/LaunchTask.h"
|
#include "launch/LaunchTask.h"
|
||||||
#include "MMCZip.h"
|
#include "MMCZip.h"
|
||||||
#include "minecraft/OpSys.h"
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
#include "minecraft/mod/ModFolderModel.h"
|
#include "minecraft/mod/ModFolderModel.h"
|
||||||
|
@ -36,130 +36,77 @@
|
|||||||
|
|
||||||
#include "Mod.h"
|
#include "Mod.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
#include <FileSystem.h>
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
#include "MetadataHandler.h"
|
#include "MetadataHandler.h"
|
||||||
|
#include "Version.h"
|
||||||
|
|
||||||
namespace {
|
Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details()
|
||||||
|
|
||||||
ModDetails invalidDetails;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Mod::Mod(const QFileInfo& file)
|
|
||||||
{
|
{
|
||||||
repath(file);
|
m_enabled = (file.suffix() != "disabled");
|
||||||
m_changedDateTime = file.lastModified();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata)
|
Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata)
|
||||||
: m_file(mods_dir.absoluteFilePath(metadata.filename))
|
: Mod(mods_dir.absoluteFilePath(metadata.filename))
|
||||||
, m_internal_id(metadata.filename)
|
|
||||||
, m_name(metadata.name)
|
|
||||||
{
|
{
|
||||||
if (m_file.isDir()) {
|
m_name = metadata.name;
|
||||||
m_type = MOD_FOLDER;
|
m_local_details.metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
|
||||||
} else {
|
|
||||||
if (metadata.filename.endsWith(".zip") || metadata.filename.endsWith(".jar"))
|
|
||||||
m_type = MOD_ZIPFILE;
|
|
||||||
else if (metadata.filename.endsWith(".litemod"))
|
|
||||||
m_type = MOD_LITEMOD;
|
|
||||||
else
|
|
||||||
m_type = MOD_SINGLEFILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_enabled = true;
|
|
||||||
m_changedDateTime = m_file.lastModified();
|
|
||||||
|
|
||||||
m_temp_metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mod::repath(const QFileInfo& file)
|
|
||||||
{
|
|
||||||
m_file = file;
|
|
||||||
QString name_base = file.fileName();
|
|
||||||
|
|
||||||
m_type = Mod::MOD_UNKNOWN;
|
|
||||||
|
|
||||||
m_internal_id = name_base;
|
|
||||||
|
|
||||||
if (m_file.isDir()) {
|
|
||||||
m_type = MOD_FOLDER;
|
|
||||||
m_name = name_base;
|
|
||||||
} else if (m_file.isFile()) {
|
|
||||||
if (name_base.endsWith(".disabled")) {
|
|
||||||
m_enabled = false;
|
|
||||||
name_base.chop(9);
|
|
||||||
} else {
|
|
||||||
m_enabled = true;
|
|
||||||
}
|
|
||||||
if (name_base.endsWith(".zip") || name_base.endsWith(".jar")) {
|
|
||||||
m_type = MOD_ZIPFILE;
|
|
||||||
name_base.chop(4);
|
|
||||||
} else if (name_base.endsWith(".litemod")) {
|
|
||||||
m_type = MOD_LITEMOD;
|
|
||||||
name_base.chop(8);
|
|
||||||
} else {
|
|
||||||
m_type = MOD_SINGLEFILE;
|
|
||||||
}
|
|
||||||
m_name = name_base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Mod::enable(bool value) -> bool
|
|
||||||
{
|
|
||||||
if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (m_enabled == value)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
QString path = m_file.absoluteFilePath();
|
|
||||||
QFile file(path);
|
|
||||||
if (value) {
|
|
||||||
if (!path.endsWith(".disabled"))
|
|
||||||
return false;
|
|
||||||
path.chop(9);
|
|
||||||
|
|
||||||
if (!file.rename(path))
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
path += ".disabled";
|
|
||||||
|
|
||||||
if (!file.rename(path))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status() == ModStatus::NoMetadata)
|
|
||||||
repath(QFileInfo(path));
|
|
||||||
|
|
||||||
m_enabled = value;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mod::setStatus(ModStatus status)
|
void Mod::setStatus(ModStatus status)
|
||||||
{
|
{
|
||||||
if (m_localDetails) {
|
m_local_details.status = status;
|
||||||
m_localDetails->status = status;
|
|
||||||
} else {
|
|
||||||
m_temp_status = status;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
void Mod::setMetadata(const Metadata::ModStruct& metadata)
|
void Mod::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
|
||||||
{
|
{
|
||||||
if (status() == ModStatus::NoMetadata)
|
if (status() == ModStatus::NoMetadata)
|
||||||
setStatus(ModStatus::Installed);
|
setStatus(ModStatus::Installed);
|
||||||
|
|
||||||
if (m_localDetails) {
|
m_local_details.metadata = metadata;
|
||||||
m_localDetails->metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
|
}
|
||||||
} else {
|
|
||||||
m_temp_metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
|
std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
|
||||||
|
{
|
||||||
|
auto cast_other = dynamic_cast<Mod const*>(&other);
|
||||||
|
if (!cast_other)
|
||||||
|
return Resource::compare(other, type);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
default:
|
||||||
|
case SortType::ENABLED:
|
||||||
|
case SortType::NAME:
|
||||||
|
case SortType::DATE: {
|
||||||
|
auto res = Resource::compare(other, type);
|
||||||
|
if (res.first != 0)
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
case SortType::VERSION: {
|
||||||
|
auto this_ver = Version(version());
|
||||||
|
auto other_ver = Version(cast_other->version());
|
||||||
|
if (this_ver > other_ver)
|
||||||
|
return { 1, type == SortType::VERSION };
|
||||||
|
if (this_ver < other_ver)
|
||||||
|
return { -1, type == SortType::VERSION };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return { 0, false };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Mod::applyFilter(QRegularExpression filter) const
|
||||||
|
{
|
||||||
|
if (filter.match(description()).hasMatch())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto& author : authors()) {
|
||||||
|
if (filter.match(author).hasMatch()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resource::applyFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool
|
auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool
|
||||||
@ -175,13 +122,12 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_type = MOD_UNKNOWN;
|
return Resource::destroy();
|
||||||
return FS::deletePath(m_file.filePath());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mod::details() const -> const ModDetails&
|
auto Mod::details() const -> const ModDetails&
|
||||||
{
|
{
|
||||||
return m_localDetails ? *m_localDetails : invalidDetails;
|
return m_local_details;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mod::name() const -> QString
|
auto Mod::name() const -> QString
|
||||||
@ -218,35 +164,29 @@ auto Mod::authors() const -> QStringList
|
|||||||
|
|
||||||
auto Mod::status() const -> ModStatus
|
auto Mod::status() const -> ModStatus
|
||||||
{
|
{
|
||||||
if (!m_localDetails)
|
|
||||||
return m_temp_status;
|
|
||||||
return details().status;
|
return details().status;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mod::metadata() -> std::shared_ptr<Metadata::ModStruct>
|
auto Mod::metadata() -> std::shared_ptr<Metadata::ModStruct>
|
||||||
{
|
{
|
||||||
if (m_localDetails)
|
return m_local_details.metadata;
|
||||||
return m_localDetails->metadata;
|
|
||||||
return m_temp_metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mod::metadata() const -> const std::shared_ptr<Metadata::ModStruct>
|
auto Mod::metadata() const -> const std::shared_ptr<Metadata::ModStruct>
|
||||||
{
|
{
|
||||||
if (m_localDetails)
|
return m_local_details.metadata;
|
||||||
return m_localDetails->metadata;
|
|
||||||
return m_temp_metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mod::finishResolvingWithDetails(std::shared_ptr<ModDetails> details)
|
void Mod::finishResolvingWithDetails(ModDetails&& details)
|
||||||
{
|
{
|
||||||
m_resolving = false;
|
m_is_resolving = false;
|
||||||
m_resolved = true;
|
m_is_resolved = true;
|
||||||
m_localDetails = details;
|
|
||||||
|
|
||||||
setStatus(m_temp_status);
|
std::shared_ptr<Metadata::ModStruct> metadata = details.metadata;
|
||||||
|
if (details.status == ModStatus::Unknown)
|
||||||
|
details.status = m_local_details.status;
|
||||||
|
|
||||||
if (m_localDetails && m_temp_metadata && m_temp_metadata->isValid()) {
|
m_local_details = std::move(details);
|
||||||
setMetadata(*m_temp_metadata);
|
if (metadata)
|
||||||
m_temp_metadata.reset();
|
setMetadata(std::move(metadata));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -39,38 +39,23 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
#include "Resource.h"
|
||||||
#include "ModDetails.h"
|
#include "ModDetails.h"
|
||||||
|
|
||||||
class Mod : public QObject
|
class Mod : public Resource
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum ModType
|
|
||||||
{
|
|
||||||
MOD_UNKNOWN, //!< Indicates an unspecified mod type.
|
|
||||||
MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files.
|
|
||||||
MOD_SINGLEFILE, //!< The mod is a single file (not a zip file).
|
|
||||||
MOD_FOLDER, //!< The mod is in a folder on the filesystem.
|
|
||||||
MOD_LITEMOD, //!< The mod is a litemod
|
|
||||||
};
|
|
||||||
|
|
||||||
using Ptr = shared_qobject_ptr<Mod>;
|
using Ptr = shared_qobject_ptr<Mod>;
|
||||||
|
using WeakPtr = QPointer<Mod>;
|
||||||
|
|
||||||
Mod() = default;
|
Mod() = default;
|
||||||
Mod(const QFileInfo &file);
|
Mod(const QFileInfo &file);
|
||||||
explicit Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata);
|
Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata);
|
||||||
|
Mod(QString file_path) : Mod(QFileInfo(file_path)) {}
|
||||||
auto fileinfo() const -> QFileInfo { return m_file; }
|
|
||||||
auto dateTimeChanged() const -> QDateTime { return m_changedDateTime; }
|
|
||||||
auto internal_id() const -> QString { return m_internal_id; }
|
|
||||||
auto type() const -> ModType { return m_type; }
|
|
||||||
auto enabled() const -> bool { return m_enabled; }
|
|
||||||
|
|
||||||
auto valid() const -> bool { return m_type != MOD_UNKNOWN; }
|
|
||||||
|
|
||||||
auto details() const -> const ModDetails&;
|
auto details() const -> const ModDetails&;
|
||||||
auto name() const -> QString;
|
auto name() const -> QString override;
|
||||||
auto version() const -> QString;
|
auto version() const -> QString;
|
||||||
auto homeurl() const -> QString;
|
auto homeurl() const -> QString;
|
||||||
auto description() const -> QString;
|
auto description() const -> QString;
|
||||||
@ -81,46 +66,17 @@ public:
|
|||||||
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
|
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
|
||||||
|
|
||||||
void setStatus(ModStatus status);
|
void setStatus(ModStatus status);
|
||||||
void setMetadata(const Metadata::ModStruct& metadata);
|
void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
|
||||||
|
void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
|
||||||
|
|
||||||
auto enable(bool value) -> bool;
|
[[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
|
||||||
|
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||||
|
|
||||||
// delete all the files of this mod
|
// Delete all the files of this mod
|
||||||
auto destroy(QDir& index_dir, bool preserve_metadata = false) -> bool;
|
auto destroy(QDir& index_dir, bool preserve_metadata = false) -> bool;
|
||||||
|
|
||||||
// change the mod's filesystem path (used by mod lists for *MAGIC* purposes)
|
void finishResolvingWithDetails(ModDetails&& details);
|
||||||
void repath(const QFileInfo &file);
|
|
||||||
|
|
||||||
auto shouldResolve() const -> bool { return !m_resolving && !m_resolved; }
|
|
||||||
auto isResolving() const -> bool { return m_resolving; }
|
|
||||||
auto resolutionTicket() const -> int { return m_resolutionTicket; }
|
|
||||||
|
|
||||||
void setResolving(bool resolving, int resolutionTicket) {
|
|
||||||
m_resolving = resolving;
|
|
||||||
m_resolutionTicket = resolutionTicket;
|
|
||||||
}
|
|
||||||
void finishResolvingWithDetails(std::shared_ptr<ModDetails> details);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QFileInfo m_file;
|
ModDetails m_local_details;
|
||||||
QDateTime m_changedDateTime;
|
|
||||||
|
|
||||||
QString m_internal_id;
|
|
||||||
/* Name as reported via the file name */
|
|
||||||
QString m_name;
|
|
||||||
ModType m_type = MOD_UNKNOWN;
|
|
||||||
|
|
||||||
/* If the mod has metadata, this will be filled in the constructor, and passed to
|
|
||||||
* the ModDetails when calling finishResolvingWithDetails */
|
|
||||||
std::shared_ptr<Metadata::ModStruct> m_temp_metadata;
|
|
||||||
|
|
||||||
/* Set the mod status while it doesn't have local details just yet */
|
|
||||||
ModStatus m_temp_status = ModStatus::NoMetadata;
|
|
||||||
|
|
||||||
std::shared_ptr<ModDetails> m_localDetails;
|
|
||||||
|
|
||||||
bool m_enabled = true;
|
|
||||||
bool m_resolving = false;
|
|
||||||
bool m_resolved = false;
|
|
||||||
int m_resolutionTicket = 0;
|
|
||||||
};
|
};
|
||||||
|
@ -46,34 +46,77 @@ enum class ModStatus {
|
|||||||
Installed, // Both JAR and Metadata are present
|
Installed, // Both JAR and Metadata are present
|
||||||
NotInstalled, // Only the Metadata is present
|
NotInstalled, // Only the Metadata is present
|
||||||
NoMetadata, // Only the JAR is present
|
NoMetadata, // Only the JAR is present
|
||||||
|
Unknown, // Default status
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ModDetails
|
struct ModDetails
|
||||||
{
|
{
|
||||||
/* Mod ID as defined in the ModLoader-specific metadata */
|
/* Mod ID as defined in the ModLoader-specific metadata */
|
||||||
QString mod_id;
|
QString mod_id = {};
|
||||||
|
|
||||||
/* Human-readable name */
|
/* Human-readable name */
|
||||||
QString name;
|
QString name = {};
|
||||||
|
|
||||||
/* Human-readable mod version */
|
/* Human-readable mod version */
|
||||||
QString version;
|
QString version = {};
|
||||||
|
|
||||||
/* Human-readable minecraft version */
|
/* Human-readable minecraft version */
|
||||||
QString mcversion;
|
QString mcversion = {};
|
||||||
|
|
||||||
/* URL for mod's home page */
|
/* URL for mod's home page */
|
||||||
QString homeurl;
|
QString homeurl = {};
|
||||||
|
|
||||||
/* Human-readable description */
|
/* Human-readable description */
|
||||||
QString description;
|
QString description = {};
|
||||||
|
|
||||||
/* List of the author's names */
|
/* List of the author's names */
|
||||||
QStringList authors;
|
QStringList authors = {};
|
||||||
|
|
||||||
/* Installation status of the mod */
|
/* Installation status of the mod */
|
||||||
ModStatus status;
|
ModStatus status = ModStatus::Unknown;
|
||||||
|
|
||||||
/* Metadata information, if any */
|
/* Metadata information, if any */
|
||||||
std::shared_ptr<Metadata::ModStruct> metadata;
|
std::shared_ptr<Metadata::ModStruct> metadata = nullptr;
|
||||||
|
|
||||||
|
ModDetails() = default;
|
||||||
|
|
||||||
|
/** Metadata should be handled manually to properly set the mod status. */
|
||||||
|
ModDetails(ModDetails& other)
|
||||||
|
: mod_id(other.mod_id)
|
||||||
|
, name(other.name)
|
||||||
|
, version(other.version)
|
||||||
|
, mcversion(other.mcversion)
|
||||||
|
, homeurl(other.homeurl)
|
||||||
|
, description(other.description)
|
||||||
|
, authors(other.authors)
|
||||||
|
, status(other.status)
|
||||||
|
{}
|
||||||
|
|
||||||
|
ModDetails& operator=(ModDetails& other)
|
||||||
|
{
|
||||||
|
this->mod_id = other.mod_id;
|
||||||
|
this->name = other.name;
|
||||||
|
this->version = other.version;
|
||||||
|
this->mcversion = other.mcversion;
|
||||||
|
this->homeurl = other.homeurl;
|
||||||
|
this->description = other.description;
|
||||||
|
this->authors = other.authors;
|
||||||
|
this->status = other.status;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModDetails& operator=(ModDetails&& other)
|
||||||
|
{
|
||||||
|
this->mod_id = other.mod_id;
|
||||||
|
this->name = other.name;
|
||||||
|
this->version = other.version;
|
||||||
|
this->mcversion = other.mcversion;
|
||||||
|
this->homeurl = other.homeurl;
|
||||||
|
this->description = other.description;
|
||||||
|
this->authors = other.authors;
|
||||||
|
this->status = other.status;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -49,428 +49,52 @@
|
|||||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||||
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
||||||
|
|
||||||
ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : QAbstractListModel(), m_dir(dir), m_is_indexed(is_indexed)
|
ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFolderModel(QDir(dir)), m_is_indexed(is_indexed)
|
||||||
{
|
{
|
||||||
FS::ensureFolderPathExists(m_dir.absolutePath());
|
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE };
|
||||||
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
|
|
||||||
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
|
|
||||||
m_watcher = new QFileSystemWatcher(this);
|
|
||||||
connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModFolderModel::startWatching()
|
|
||||||
{
|
|
||||||
if(is_watching)
|
|
||||||
return;
|
|
||||||
|
|
||||||
update();
|
|
||||||
|
|
||||||
// Watch the mods folder
|
|
||||||
is_watching = m_watcher->addPath(m_dir.absolutePath());
|
|
||||||
if (is_watching) {
|
|
||||||
qDebug() << "Started watching " << m_dir.absolutePath();
|
|
||||||
} else {
|
|
||||||
qDebug() << "Failed to start watching " << m_dir.absolutePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch the mods index folder
|
|
||||||
is_watching = m_watcher->addPath(indexDir().absolutePath());
|
|
||||||
if (is_watching) {
|
|
||||||
qDebug() << "Started watching " << indexDir().absolutePath();
|
|
||||||
} else {
|
|
||||||
qDebug() << "Failed to start watching " << indexDir().absolutePath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModFolderModel::stopWatching()
|
|
||||||
{
|
|
||||||
if(!is_watching)
|
|
||||||
return;
|
|
||||||
|
|
||||||
is_watching = !m_watcher->removePath(m_dir.absolutePath());
|
|
||||||
if (!is_watching) {
|
|
||||||
qDebug() << "Stopped watching " << m_dir.absolutePath();
|
|
||||||
} else {
|
|
||||||
qDebug() << "Failed to stop watching " << m_dir.absolutePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
is_watching = !m_watcher->removePath(indexDir().absolutePath());
|
|
||||||
if (!is_watching) {
|
|
||||||
qDebug() << "Stopped watching " << indexDir().absolutePath();
|
|
||||||
} else {
|
|
||||||
qDebug() << "Failed to stop watching " << indexDir().absolutePath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModFolderModel::update()
|
|
||||||
{
|
|
||||||
if (!isValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(m_update) {
|
|
||||||
scheduled_update = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto index_dir = indexDir();
|
|
||||||
auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed);
|
|
||||||
|
|
||||||
m_update = task->result();
|
|
||||||
|
|
||||||
QThreadPool *threadPool = QThreadPool::globalInstance();
|
|
||||||
connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate);
|
|
||||||
|
|
||||||
threadPool->start(task);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModFolderModel::finishUpdate()
|
|
||||||
{
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
||||||
auto currentList = modsIndex.keys();
|
|
||||||
QSet<QString> currentSet(currentList.begin(), currentList.end());
|
|
||||||
auto & newMods = m_update->mods;
|
|
||||||
auto newList = newMods.keys();
|
|
||||||
QSet<QString> newSet(newList.begin(), newList.end());
|
|
||||||
#else
|
|
||||||
QSet<QString> currentSet = modsIndex.keys().toSet();
|
|
||||||
auto& newMods = m_update->mods;
|
|
||||||
QSet<QString> newSet = newMods.keys().toSet();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// see if the kept mods changed in some way
|
|
||||||
{
|
|
||||||
QSet<QString> kept = currentSet;
|
|
||||||
kept.intersect(newSet);
|
|
||||||
for(auto& keptMod : kept) {
|
|
||||||
auto newMod = newMods[keptMod];
|
|
||||||
auto row = modsIndex[keptMod];
|
|
||||||
auto currentMod = mods[row];
|
|
||||||
if(newMod->dateTimeChanged() == currentMod->dateTimeChanged()) {
|
|
||||||
// no significant change, ignore...
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto oldMod = mods[row];
|
|
||||||
if(oldMod->isResolving()) {
|
|
||||||
activeTickets.remove(oldMod->resolutionTicket());
|
|
||||||
}
|
|
||||||
|
|
||||||
mods[row] = newMod;
|
|
||||||
resolveMod(mods[row]);
|
|
||||||
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove mods no longer present
|
|
||||||
{
|
|
||||||
QSet<QString> removed = currentSet;
|
|
||||||
QList<int> removedRows;
|
|
||||||
removed.subtract(newSet);
|
|
||||||
for(auto & removedMod: removed) {
|
|
||||||
removedRows.append(modsIndex[removedMod]);
|
|
||||||
}
|
|
||||||
std::sort(removedRows.begin(), removedRows.end(), std::greater<int>());
|
|
||||||
for(auto iter = removedRows.begin(); iter != removedRows.end(); iter++) {
|
|
||||||
int removedIndex = *iter;
|
|
||||||
beginRemoveRows(QModelIndex(), removedIndex, removedIndex);
|
|
||||||
auto removedIter = mods.begin() + removedIndex;
|
|
||||||
if((*removedIter)->isResolving()) {
|
|
||||||
activeTickets.remove((*removedIter)->resolutionTicket());
|
|
||||||
}
|
|
||||||
|
|
||||||
mods.erase(removedIter);
|
|
||||||
endRemoveRows();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add new mods to the end
|
|
||||||
{
|
|
||||||
QSet<QString> added = newSet;
|
|
||||||
added.subtract(currentSet);
|
|
||||||
|
|
||||||
// When you have a Qt build with assertions turned on, proceeding here will abort the application
|
|
||||||
if (added.size() > 0) {
|
|
||||||
beginInsertRows(QModelIndex(), mods.size(), mods.size() + added.size() - 1);
|
|
||||||
for (auto& addedMod : added) {
|
|
||||||
mods.append(newMods[addedMod]);
|
|
||||||
resolveMod(mods.last());
|
|
||||||
}
|
|
||||||
endInsertRows();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update index
|
|
||||||
{
|
|
||||||
modsIndex.clear();
|
|
||||||
int idx = 0;
|
|
||||||
for(auto mod: mods) {
|
|
||||||
modsIndex[mod->internal_id()] = idx;
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_update.reset();
|
|
||||||
|
|
||||||
emit updateFinished();
|
|
||||||
|
|
||||||
if(scheduled_update) {
|
|
||||||
scheduled_update = false;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModFolderModel::resolveMod(Mod::Ptr m)
|
|
||||||
{
|
|
||||||
if(!m->shouldResolve()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto task = new LocalModParseTask(nextResolutionTicket, m->type(), m->fileinfo());
|
|
||||||
auto result = task->result();
|
|
||||||
result->id = m->internal_id();
|
|
||||||
activeTickets.insert(nextResolutionTicket, result);
|
|
||||||
m->setResolving(true, nextResolutionTicket);
|
|
||||||
nextResolutionTicket++;
|
|
||||||
QThreadPool *threadPool = QThreadPool::globalInstance();
|
|
||||||
connect(task, &LocalModParseTask::finished, this, &ModFolderModel::finishModParse);
|
|
||||||
threadPool->start(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModFolderModel::finishModParse(int token)
|
|
||||||
{
|
|
||||||
auto iter = activeTickets.find(token);
|
|
||||||
if(iter == activeTickets.end()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto result = *iter;
|
|
||||||
activeTickets.remove(token);
|
|
||||||
int row = modsIndex[result->id];
|
|
||||||
auto mod = mods[row];
|
|
||||||
mod->finishResolvingWithDetails(result->details);
|
|
||||||
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModFolderModel::disableInteraction(bool disabled)
|
|
||||||
{
|
|
||||||
if (interaction_disabled == disabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
interaction_disabled = disabled;
|
|
||||||
if(size()) {
|
|
||||||
emit dataChanged(index(0), index(size() - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModFolderModel::directoryChanged(QString path)
|
|
||||||
{
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModFolderModel::isValid()
|
|
||||||
{
|
|
||||||
return m_dir.exists() && m_dir.isReadable();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList<Mod::Ptr>
|
|
||||||
{
|
|
||||||
QList<Mod::Ptr> selected_mods;
|
|
||||||
for (auto i : indexes) {
|
|
||||||
if(i.column() != 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
selected_mods.push_back(mods[i.row()]);
|
|
||||||
}
|
|
||||||
return selected_mods;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: this does not take disabled mod (with extra .disable extension) into account...
|
|
||||||
bool ModFolderModel::installMod(const QString &filename)
|
|
||||||
{
|
|
||||||
if(interaction_disabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
|
|
||||||
auto originalPath = FS::NormalizePath(filename);
|
|
||||||
QFileInfo fileinfo(originalPath);
|
|
||||||
|
|
||||||
if (!fileinfo.exists() || !fileinfo.isReadable())
|
|
||||||
{
|
|
||||||
qWarning() << "Caught attempt to install non-existing file or file-like object:" << originalPath;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
qDebug() << "installing: " << fileinfo.absoluteFilePath();
|
|
||||||
|
|
||||||
Mod installedMod(fileinfo);
|
|
||||||
if (!installedMod.valid())
|
|
||||||
{
|
|
||||||
qDebug() << originalPath << "is not a valid mod. Ignoring it.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto type = installedMod.type();
|
|
||||||
if (type == Mod::MOD_UNKNOWN)
|
|
||||||
{
|
|
||||||
qDebug() << "Cannot recognize mod type of" << originalPath << ", ignoring it.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto newpath = FS::NormalizePath(FS::PathCombine(m_dir.path(), fileinfo.fileName()));
|
|
||||||
if(originalPath == newpath)
|
|
||||||
{
|
|
||||||
qDebug() << "Overwriting the mod (" << originalPath << ") with itself makes no sense...";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
|
|
||||||
{
|
|
||||||
if(QFile::exists(newpath) || QFile::exists(newpath + QString(".disabled")))
|
|
||||||
{
|
|
||||||
if(!QFile::remove(newpath))
|
|
||||||
{
|
|
||||||
// FIXME: report error in a user-visible way
|
|
||||||
qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
qDebug() << newpath << "has been deleted.";
|
|
||||||
}
|
|
||||||
if (!QFile::copy(fileinfo.filePath(), newpath))
|
|
||||||
{
|
|
||||||
qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed.";
|
|
||||||
// FIXME: report error in a user-visible way
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
FS::updateTimestamp(newpath);
|
|
||||||
QFileInfo newpathInfo(newpath);
|
|
||||||
installedMod.repath(newpathInfo);
|
|
||||||
update();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (type == Mod::MOD_FOLDER)
|
|
||||||
{
|
|
||||||
QString from = fileinfo.filePath();
|
|
||||||
if(QFile::exists(newpath))
|
|
||||||
{
|
|
||||||
qDebug() << "Ignoring folder " << from << ", it would merge with " << newpath;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!FS::copy(from, newpath)())
|
|
||||||
{
|
|
||||||
qWarning() << "Copy of folder from" << originalPath << "to" << newpath << "has (potentially partially) failed.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
QFileInfo newpathInfo(newpath);
|
|
||||||
installedMod.repath(newpathInfo);
|
|
||||||
update();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
|
|
||||||
{
|
|
||||||
|
|
||||||
for(auto mod : allMods()){
|
|
||||||
if(mod->fileinfo().fileName() == filename){
|
|
||||||
auto index_dir = indexDir();
|
|
||||||
mod->destroy(index_dir, preserve_metadata);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable)
|
|
||||||
{
|
|
||||||
if(interaction_disabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(indexes.isEmpty())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
for (auto index: indexes)
|
|
||||||
{
|
|
||||||
if(index.column() != 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
setModStatus(index.row(), enable);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
|
|
||||||
{
|
|
||||||
if(interaction_disabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(indexes.isEmpty())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
for (auto i: indexes)
|
|
||||||
{
|
|
||||||
if(i.column() != 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto m = mods[i.row()];
|
|
||||||
auto index_dir = indexDir();
|
|
||||||
m->destroy(index_dir);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ModFolderModel::columnCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
return NUM_COLUMNS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant ModFolderModel::data(const QModelIndex &index, int role) const
|
QVariant ModFolderModel::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
if (!index.isValid())
|
if (!validateIndex(index))
|
||||||
return QVariant();
|
return {};
|
||||||
|
|
||||||
int row = index.row();
|
int row = index.row();
|
||||||
int column = index.column();
|
int column = index.column();
|
||||||
|
|
||||||
if (row < 0 || row >= mods.size())
|
|
||||||
return QVariant();
|
|
||||||
|
|
||||||
switch (role)
|
switch (role)
|
||||||
{
|
{
|
||||||
case Qt::DisplayRole:
|
case Qt::DisplayRole:
|
||||||
switch (column)
|
switch (column)
|
||||||
{
|
{
|
||||||
case NameColumn:
|
case NameColumn:
|
||||||
return mods[row]->name();
|
return m_resources[row]->name();
|
||||||
case VersionColumn: {
|
case VersionColumn: {
|
||||||
switch(mods[row]->type()) {
|
switch(m_resources[row]->type()) {
|
||||||
case Mod::MOD_FOLDER:
|
case ResourceType::FOLDER:
|
||||||
return tr("Folder");
|
return tr("Folder");
|
||||||
case Mod::MOD_SINGLEFILE:
|
case ResourceType::SINGLEFILE:
|
||||||
return tr("File");
|
return tr("File");
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return mods[row]->version();
|
return at(row)->version();
|
||||||
}
|
}
|
||||||
case DateColumn:
|
case DateColumn:
|
||||||
return mods[row]->dateTimeChanged();
|
return m_resources[row]->dateTimeChanged();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
case Qt::ToolTipRole:
|
case Qt::ToolTipRole:
|
||||||
return mods[row]->internal_id();
|
return m_resources[row]->internal_id();
|
||||||
|
|
||||||
case Qt::CheckStateRole:
|
case Qt::CheckStateRole:
|
||||||
switch (column)
|
switch (column)
|
||||||
{
|
{
|
||||||
case ActiveColumn:
|
case ActiveColumn:
|
||||||
return mods[row]->enabled() ? Qt::Checked : Qt::Unchecked;
|
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
@ -479,61 +103,6 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
||||||
{
|
|
||||||
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == Qt::CheckStateRole)
|
|
||||||
{
|
|
||||||
return setModStatus(index.row(), Toggle);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction action)
|
|
||||||
{
|
|
||||||
if(row < 0 || row >= mods.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto &mod = mods[row];
|
|
||||||
bool desiredStatus;
|
|
||||||
switch(action) {
|
|
||||||
case Enable:
|
|
||||||
desiredStatus = true;
|
|
||||||
break;
|
|
||||||
case Disable:
|
|
||||||
desiredStatus = false;
|
|
||||||
break;
|
|
||||||
case Toggle:
|
|
||||||
default:
|
|
||||||
desiredStatus = !mod->enabled();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(desiredStatus == mod->enabled()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// preserve the row, but change its ID
|
|
||||||
auto oldId = mod->internal_id();
|
|
||||||
if(!mod->enable(!mod->enabled())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
auto newId = mod->internal_id();
|
|
||||||
if(modsIndex.contains(newId)) {
|
|
||||||
// NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled
|
|
||||||
// But is it necessary?
|
|
||||||
}
|
|
||||||
modsIndex.remove(oldId);
|
|
||||||
modsIndex[newId] = row;
|
|
||||||
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
|
QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
{
|
{
|
||||||
switch (role)
|
switch (role)
|
||||||
@ -573,65 +142,142 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
|
|||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
Qt::ItemFlags ModFolderModel::flags(const QModelIndex &index) const
|
int ModFolderModel::columnCount(const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
return NUM_COLUMNS;
|
||||||
auto flags = defaultFlags;
|
}
|
||||||
if(interaction_disabled) {
|
|
||||||
flags &= ~Qt::ItemIsDropEnabled;
|
Task* ModFolderModel::createUpdateTask()
|
||||||
}
|
{
|
||||||
else
|
auto index_dir = indexDir();
|
||||||
{
|
auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load);
|
||||||
flags |= Qt::ItemIsDropEnabled;
|
m_first_folder_load = false;
|
||||||
if(index.isValid()) {
|
return task;
|
||||||
flags |= Qt::ItemIsUserCheckable;
|
}
|
||||||
|
|
||||||
|
Task* ModFolderModel::createParseTask(Resource& resource)
|
||||||
|
{
|
||||||
|
return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
|
||||||
|
{
|
||||||
|
for(auto mod : allMods()){
|
||||||
|
if(mod->fileinfo().fileName() == filename){
|
||||||
|
auto index_dir = indexDir();
|
||||||
|
mod->destroy(index_dir, preserve_metadata);
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return flags;
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Qt::DropActions ModFolderModel::supportedDropActions() const
|
bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
|
||||||
{
|
{
|
||||||
// copy from outside, move from within and other mod lists
|
if(!m_can_interact) {
|
||||||
return Qt::CopyAction | Qt::MoveAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList ModFolderModel::mimeTypes() const
|
|
||||||
{
|
|
||||||
QStringList types;
|
|
||||||
types << "text/uri-list";
|
|
||||||
return types;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
|
|
||||||
{
|
|
||||||
if (action == Qt::IgnoreAction)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if the action is supported
|
|
||||||
if (!data || !(action & supportedDropActions()))
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// files dropped from outside?
|
if(indexes.isEmpty())
|
||||||
if (data->hasUrls())
|
|
||||||
{
|
|
||||||
auto urls = data->urls();
|
|
||||||
for (auto url : urls)
|
|
||||||
{
|
|
||||||
// only local files may be dropped...
|
|
||||||
if (!url.isLocalFile())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// TODO: implement not only copy, but also move
|
|
||||||
// FIXME: handle errors here
|
|
||||||
installMod(url.toLocalFile());
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
for (auto i: indexes)
|
||||||
|
{
|
||||||
|
if(i.column() != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto m = at(i.row());
|
||||||
|
auto index_dir = indexDir();
|
||||||
|
m->destroy(index_dir);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModFolderModel::isValid()
|
||||||
|
{
|
||||||
|
return m_dir.exists() && m_dir.isReadable();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModFolderModel::startWatching()
|
||||||
|
{
|
||||||
|
// Remove orphaned metadata next time
|
||||||
|
m_first_folder_load = true;
|
||||||
|
return ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModFolderModel::stopWatching()
|
||||||
|
{
|
||||||
|
return ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList<Mod*>
|
||||||
|
{
|
||||||
|
QList<Mod*> selected_resources;
|
||||||
|
for (auto i : indexes) {
|
||||||
|
if(i.column() != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
selected_resources.push_back(at(i.row()));
|
||||||
|
}
|
||||||
|
return selected_resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ModFolderModel::allMods() -> QList<Mod*>
|
||||||
|
{
|
||||||
|
QList<Mod*> mods;
|
||||||
|
|
||||||
|
for (auto& res : qAsConst(m_resources)) {
|
||||||
|
mods.append(static_cast<Mod*>(res.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModFolderModel::onUpdateSucceeded()
|
||||||
|
{
|
||||||
|
auto update_results = static_cast<ModFolderLoadTask*>(m_current_update_task.get())->result();
|
||||||
|
|
||||||
|
auto& new_mods = update_results->mods;
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||||
|
auto current_list = m_resources_index.keys();
|
||||||
|
QSet<QString> current_set(current_list.begin(), current_list.end());
|
||||||
|
|
||||||
|
auto new_list = new_mods.keys();
|
||||||
|
QSet<QString> new_set(new_list.begin(), new_list.end());
|
||||||
|
#else
|
||||||
|
QSet<QString> current_set(m_resources_index.keys().toSet());
|
||||||
|
QSet<QString> new_set(new_mods.keys().toSet());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
applyUpdates(current_set, new_set, new_mods);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
|
||||||
|
{
|
||||||
|
auto iter = m_active_parse_tasks.constFind(ticket);
|
||||||
|
if (iter == m_active_parse_tasks.constEnd())
|
||||||
|
return;
|
||||||
|
|
||||||
|
int row = m_resources_index[mod_id];
|
||||||
|
|
||||||
|
auto parse_task = *iter;
|
||||||
|
auto cast_task = static_cast<LocalModParseTask*>(parse_task.get());
|
||||||
|
|
||||||
|
Q_ASSERT(cast_task->token() == ticket);
|
||||||
|
|
||||||
|
auto resource = find(mod_id);
|
||||||
|
|
||||||
|
auto result = cast_task->result();
|
||||||
|
if (result && resource)
|
||||||
|
resource->finishResolvingWithDetails(std::move(result->details));
|
||||||
|
|
||||||
|
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
|
||||||
#include "Mod.h"
|
#include "Mod.h"
|
||||||
|
#include "ResourceFolderModel.h"
|
||||||
|
|
||||||
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
||||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||||
@ -56,7 +57,7 @@ class QFileSystemWatcher;
|
|||||||
* A legacy mod list.
|
* A legacy mod list.
|
||||||
* Backed by a folder.
|
* Backed by a folder.
|
||||||
*/
|
*/
|
||||||
class ModFolderModel : public QAbstractListModel
|
class ModFolderModel : public ResourceFolderModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
@ -75,105 +76,38 @@ public:
|
|||||||
};
|
};
|
||||||
ModFolderModel(const QString &dir, bool is_indexed = false);
|
ModFolderModel(const QString &dir, bool is_indexed = false);
|
||||||
|
|
||||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
|
|
||||||
Qt::DropActions supportedDropActions() const override;
|
|
||||||
|
|
||||||
/// flags, mostly to support drag&drop
|
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
int columnCount(const QModelIndex &parent) const override;
|
||||||
QStringList mimeTypes() const override;
|
|
||||||
bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override;
|
|
||||||
|
|
||||||
virtual int rowCount(const QModelIndex &) const override
|
[[nodiscard]] Task* createUpdateTask() override;
|
||||||
{
|
[[nodiscard]] Task* createParseTask(Resource&) override;
|
||||||
return size();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
|
||||||
virtual int columnCount(const QModelIndex &parent) const override;
|
|
||||||
|
|
||||||
size_t size() const
|
|
||||||
{
|
|
||||||
return mods.size();
|
|
||||||
}
|
|
||||||
;
|
|
||||||
bool empty() const
|
|
||||||
{
|
|
||||||
return size() == 0;
|
|
||||||
}
|
|
||||||
Mod& operator[](size_t index)
|
|
||||||
{
|
|
||||||
return *mods[index];
|
|
||||||
}
|
|
||||||
const Mod& at(size_t index) const
|
|
||||||
{
|
|
||||||
return *mods.at(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reloads the mod list and returns true if the list changed.
|
|
||||||
bool update();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the given mod to the list at the given index - if the list supports custom ordering
|
|
||||||
*/
|
|
||||||
bool installMod(const QString& filename);
|
|
||||||
|
|
||||||
|
bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
|
||||||
bool uninstallMod(const QString& filename, bool preserve_metadata = false);
|
bool uninstallMod(const QString& filename, bool preserve_metadata = false);
|
||||||
|
|
||||||
/// Deletes all the selected mods
|
/// Deletes all the selected mods
|
||||||
bool deleteMods(const QModelIndexList &indexes);
|
bool deleteMods(const QModelIndexList &indexes);
|
||||||
|
|
||||||
/// Enable or disable listed mods
|
|
||||||
bool setModStatus(const QModelIndexList &indexes, ModStatusAction action);
|
|
||||||
|
|
||||||
void startWatching();
|
|
||||||
void stopWatching();
|
|
||||||
|
|
||||||
bool isValid();
|
bool isValid();
|
||||||
|
|
||||||
QDir& dir()
|
bool startWatching() override;
|
||||||
{
|
bool stopWatching() override;
|
||||||
return m_dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDir indexDir()
|
QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; }
|
||||||
{
|
|
||||||
return { QString("%1/.index").arg(dir().absolutePath()) };
|
|
||||||
}
|
|
||||||
|
|
||||||
const QList<Mod::Ptr>& allMods()
|
auto selectedMods(QModelIndexList& indexes) -> QList<Mod*>;
|
||||||
{
|
auto allMods() -> QList<Mod*>;
|
||||||
return mods;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto selectedMods(QModelIndexList& indexes) -> QList<Mod::Ptr>;
|
RESOURCE_HELPERS(Mod)
|
||||||
|
|
||||||
public slots:
|
|
||||||
void disableInteraction(bool disabled);
|
|
||||||
|
|
||||||
private
|
private
|
||||||
slots:
|
slots:
|
||||||
void directoryChanged(QString path);
|
void onUpdateSucceeded() override;
|
||||||
void finishUpdate();
|
void onParseSucceeded(int ticket, QString resource_id) override;
|
||||||
void finishModParse(int token);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void updateFinished();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void resolveMod(Mod::Ptr m);
|
|
||||||
bool setModStatus(int index, ModStatusAction action);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QFileSystemWatcher *m_watcher;
|
|
||||||
bool is_watching = false;
|
|
||||||
ModFolderLoadTask::ResultPtr m_update;
|
|
||||||
bool scheduled_update = false;
|
|
||||||
bool interaction_disabled = false;
|
|
||||||
QDir m_dir;
|
|
||||||
bool m_is_indexed;
|
bool m_is_indexed;
|
||||||
QMap<QString, int> modsIndex;
|
bool m_first_folder_load = true;
|
||||||
QMap<int, LocalModParseTask::ResultPtr> activeTickets;
|
|
||||||
int nextResolutionTicket = 0;
|
|
||||||
QList<Mod::Ptr> mods;
|
|
||||||
};
|
};
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* PolyMC - Minecraft Launcher
|
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
||||||
*
|
|
||||||
* 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 <QTest>
|
|
||||||
#include <QTemporaryDir>
|
|
||||||
|
|
||||||
#include "FileSystem.h"
|
|
||||||
#include "minecraft/mod/ModFolderModel.h"
|
|
||||||
|
|
||||||
class ModFolderModelTest : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private
|
|
||||||
slots:
|
|
||||||
// test for GH-1178 - install a folder with files to a mod list
|
|
||||||
void test_1178()
|
|
||||||
{
|
|
||||||
// source
|
|
||||||
QString source = QFINDTESTDATA("testdata/test_folder");
|
|
||||||
|
|
||||||
// sanity check
|
|
||||||
QVERIFY(!source.endsWith('/'));
|
|
||||||
|
|
||||||
auto verify = [](QString path)
|
|
||||||
{
|
|
||||||
QDir target_dir(FS::PathCombine(path, "test_folder"));
|
|
||||||
QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
|
|
||||||
QVERIFY(target_dir.entryList().contains("assets"));
|
|
||||||
};
|
|
||||||
|
|
||||||
// 1. test with no trailing /
|
|
||||||
{
|
|
||||||
QString folder = source;
|
|
||||||
QTemporaryDir tempDir;
|
|
||||||
QEventLoop loop;
|
|
||||||
ModFolderModel m(tempDir.path(), true);
|
|
||||||
connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
|
|
||||||
m.installMod(folder);
|
|
||||||
loop.exec();
|
|
||||||
verify(tempDir.path());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. test with trailing /
|
|
||||||
{
|
|
||||||
QString folder = source + '/';
|
|
||||||
QTemporaryDir tempDir;
|
|
||||||
QEventLoop loop;
|
|
||||||
ModFolderModel m(tempDir.path(), true);
|
|
||||||
connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
|
|
||||||
m.installMod(folder);
|
|
||||||
loop.exec();
|
|
||||||
verify(tempDir.path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
QTEST_GUILESS_MAIN(ModFolderModelTest)
|
|
||||||
|
|
||||||
#include "ModFolderModel_test.moc"
|
|
147
launcher/minecraft/mod/Resource.cpp
Normal file
147
launcher/minecraft/mod/Resource.cpp
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
#include "Resource.h"
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
|
|
||||||
|
Resource::Resource(QObject* parent) : QObject(parent) {}
|
||||||
|
|
||||||
|
Resource::Resource(QFileInfo file_info) : QObject()
|
||||||
|
{
|
||||||
|
setFile(file_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resource::setFile(QFileInfo file_info)
|
||||||
|
{
|
||||||
|
m_file_info = file_info;
|
||||||
|
parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Resource::parseFile()
|
||||||
|
{
|
||||||
|
QString file_name{ m_file_info.fileName() };
|
||||||
|
|
||||||
|
m_type = ResourceType::UNKNOWN;
|
||||||
|
|
||||||
|
m_internal_id = file_name;
|
||||||
|
|
||||||
|
if (m_file_info.isDir()) {
|
||||||
|
m_type = ResourceType::FOLDER;
|
||||||
|
m_name = file_name;
|
||||||
|
} else if (m_file_info.isFile()) {
|
||||||
|
if (file_name.endsWith(".disabled")) {
|
||||||
|
file_name.chop(9);
|
||||||
|
m_enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_name.endsWith(".zip") || file_name.endsWith(".jar")) {
|
||||||
|
m_type = ResourceType::ZIPFILE;
|
||||||
|
file_name.chop(4);
|
||||||
|
} else if (file_name.endsWith(".litemod")) {
|
||||||
|
m_type = ResourceType::LITEMOD;
|
||||||
|
file_name.chop(8);
|
||||||
|
} else {
|
||||||
|
m_type = ResourceType::SINGLEFILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_name = file_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_changed_date_time = m_file_info.lastModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void removeThePrefix(QString& string)
|
||||||
|
{
|
||||||
|
QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption);
|
||||||
|
string.remove(regex);
|
||||||
|
string = string.trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, bool> Resource::compare(const Resource& other, SortType type) const
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
default:
|
||||||
|
case SortType::ENABLED:
|
||||||
|
if (enabled() && !other.enabled())
|
||||||
|
return { 1, type == SortType::ENABLED };
|
||||||
|
if (!enabled() && other.enabled())
|
||||||
|
return { -1, type == SortType::ENABLED };
|
||||||
|
case SortType::NAME: {
|
||||||
|
QString this_name{ name() };
|
||||||
|
QString other_name{ other.name() };
|
||||||
|
|
||||||
|
removeThePrefix(this_name);
|
||||||
|
removeThePrefix(other_name);
|
||||||
|
|
||||||
|
auto compare_result = QString::compare(this_name, other_name, Qt::CaseInsensitive);
|
||||||
|
if (compare_result != 0)
|
||||||
|
return { compare_result, type == SortType::NAME };
|
||||||
|
}
|
||||||
|
case SortType::DATE:
|
||||||
|
if (dateTimeChanged() > other.dateTimeChanged())
|
||||||
|
return { 1, type == SortType::DATE };
|
||||||
|
if (dateTimeChanged() < other.dateTimeChanged())
|
||||||
|
return { -1, type == SortType::DATE };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { 0, false };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Resource::applyFilter(QRegularExpression filter) const
|
||||||
|
{
|
||||||
|
return filter.match(name()).hasMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Resource::enable(EnableAction action)
|
||||||
|
{
|
||||||
|
if (m_type == ResourceType::UNKNOWN || m_type == ResourceType::FOLDER)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
|
||||||
|
QString path = m_file_info.absoluteFilePath();
|
||||||
|
QFile file(path);
|
||||||
|
|
||||||
|
bool enable = true;
|
||||||
|
switch (action) {
|
||||||
|
case EnableAction::ENABLE:
|
||||||
|
enable = true;
|
||||||
|
break;
|
||||||
|
case EnableAction::DISABLE:
|
||||||
|
enable = false;
|
||||||
|
break;
|
||||||
|
case EnableAction::TOGGLE:
|
||||||
|
default:
|
||||||
|
enable = !enabled();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_enabled == enable)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
// m_enabled is false, but there's no '.disabled' suffix.
|
||||||
|
// TODO: Report error?
|
||||||
|
if (!path.endsWith(".disabled"))
|
||||||
|
return false;
|
||||||
|
path.chop(9);
|
||||||
|
|
||||||
|
if (!file.rename(path))
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
path += ".disabled";
|
||||||
|
|
||||||
|
if (!file.rename(path))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFile(QFileInfo(path));
|
||||||
|
|
||||||
|
m_enabled = enable;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Resource::destroy()
|
||||||
|
{
|
||||||
|
m_type = ResourceType::UNKNOWN;
|
||||||
|
return FS::deletePath(m_file_info.filePath());
|
||||||
|
}
|
117
launcher/minecraft/mod/Resource.h
Normal file
117
launcher/minecraft/mod/Resource.h
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
|
#include "QObjectPtr.h"
|
||||||
|
|
||||||
|
enum class ResourceType {
|
||||||
|
UNKNOWN, //!< Indicates an unspecified resource type.
|
||||||
|
ZIPFILE, //!< The resource is a zip file containing the resource's class files.
|
||||||
|
SINGLEFILE, //!< The resource is a single file (not a zip file).
|
||||||
|
FOLDER, //!< The resource is in a folder on the filesystem.
|
||||||
|
LITEMOD, //!< The resource is a litemod
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SortType {
|
||||||
|
NAME,
|
||||||
|
DATE,
|
||||||
|
VERSION,
|
||||||
|
ENABLED,
|
||||||
|
PACK_FORMAT
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class EnableAction {
|
||||||
|
ENABLE,
|
||||||
|
DISABLE,
|
||||||
|
TOGGLE
|
||||||
|
};
|
||||||
|
|
||||||
|
/** General class for managed resources. It mirrors a file in disk, with some more info
|
||||||
|
* for display and house-keeping purposes.
|
||||||
|
*
|
||||||
|
* Subclass it to add additional data / behavior, such as Mods or Resource packs.
|
||||||
|
*/
|
||||||
|
class Resource : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(Resource)
|
||||||
|
public:
|
||||||
|
using Ptr = shared_qobject_ptr<Resource>;
|
||||||
|
using WeakPtr = QPointer<Resource>;
|
||||||
|
|
||||||
|
Resource(QObject* parent = nullptr);
|
||||||
|
Resource(QFileInfo file_info);
|
||||||
|
Resource(QString file_path) : Resource(QFileInfo(file_path)) {}
|
||||||
|
|
||||||
|
~Resource() override = default;
|
||||||
|
|
||||||
|
void setFile(QFileInfo file_info);
|
||||||
|
void parseFile();
|
||||||
|
|
||||||
|
[[nodiscard]] auto fileinfo() const -> QFileInfo { return m_file_info; }
|
||||||
|
[[nodiscard]] auto dateTimeChanged() const -> QDateTime { return m_changed_date_time; }
|
||||||
|
[[nodiscard]] auto internal_id() const -> QString { return m_internal_id; }
|
||||||
|
[[nodiscard]] auto type() const -> ResourceType { return m_type; }
|
||||||
|
[[nodiscard]] bool enabled() const { return m_enabled; }
|
||||||
|
|
||||||
|
[[nodiscard]] virtual auto name() const -> QString { return m_name; }
|
||||||
|
[[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; }
|
||||||
|
|
||||||
|
/** Compares two Resources, for sorting purposes, considering a ascending order, returning:
|
||||||
|
* > 0: 'this' comes after 'other'
|
||||||
|
* = 0: 'this' is equal to 'other'
|
||||||
|
* < 0: 'this' comes before 'other'
|
||||||
|
*
|
||||||
|
* The second argument in the pair is true if the sorting type that decided which one is greater was 'type'.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual auto compare(Resource const& other, SortType type = SortType::NAME) const -> std::pair<int, bool>;
|
||||||
|
|
||||||
|
/** Returns whether the given filter should filter out 'this' (false),
|
||||||
|
* or if such filter includes the Resource (true).
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual bool applyFilter(QRegularExpression filter) const;
|
||||||
|
|
||||||
|
/** Changes the enabled property, according to 'action'.
|
||||||
|
*
|
||||||
|
* Returns whether a change was applied to the Resource's properties.
|
||||||
|
*/
|
||||||
|
bool enable(EnableAction action);
|
||||||
|
|
||||||
|
[[nodiscard]] auto shouldResolve() const -> bool { return !m_is_resolving && !m_is_resolved; }
|
||||||
|
[[nodiscard]] auto isResolving() const -> bool { return m_is_resolving; }
|
||||||
|
[[nodiscard]] auto isResolved() const -> bool { return m_is_resolved; }
|
||||||
|
[[nodiscard]] auto resolutionTicket() const -> int { return m_resolution_ticket; }
|
||||||
|
|
||||||
|
void setResolving(bool resolving, int resolutionTicket)
|
||||||
|
{
|
||||||
|
m_is_resolving = resolving;
|
||||||
|
m_resolution_ticket = resolutionTicket;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all files of this resource.
|
||||||
|
bool destroy();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/* The file corresponding to this resource. */
|
||||||
|
QFileInfo m_file_info;
|
||||||
|
/* The cached date when this file was last changed. */
|
||||||
|
QDateTime m_changed_date_time;
|
||||||
|
|
||||||
|
/* Internal ID for internal purposes. Properties such as human-readability should not be assumed. */
|
||||||
|
QString m_internal_id;
|
||||||
|
/* Name as reported via the file name. In the absence of a better name, this is shown to the user. */
|
||||||
|
QString m_name;
|
||||||
|
|
||||||
|
/* The type of file we're dealing with. */
|
||||||
|
ResourceType m_type = ResourceType::UNKNOWN;
|
||||||
|
|
||||||
|
/* Whether the resource is enabled (e.g. shows up in the game) or not. */
|
||||||
|
bool m_enabled = true;
|
||||||
|
|
||||||
|
/* Used to keep trach of pending / concluded actions on the resource. */
|
||||||
|
bool m_is_resolving = false;
|
||||||
|
bool m_is_resolved = false;
|
||||||
|
int m_resolution_ticket = 0;
|
||||||
|
};
|
526
launcher/minecraft/mod/ResourceFolderModel.cpp
Normal file
526
launcher/minecraft/mod/ResourceFolderModel.cpp
Normal file
@ -0,0 +1,526 @@
|
|||||||
|
#include "ResourceFolderModel.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QMimeData>
|
||||||
|
#include <QThreadPool>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
|
|
||||||
|
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||||
|
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent) : QAbstractListModel(parent), m_dir(dir), m_watcher(this)
|
||||||
|
{
|
||||||
|
FS::ensureFolderPathExists(m_dir.absolutePath());
|
||||||
|
|
||||||
|
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
|
||||||
|
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
|
||||||
|
|
||||||
|
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceFolderModel::~ResourceFolderModel()
|
||||||
|
{
|
||||||
|
while (!QThreadPool::globalInstance()->waitForDone(100))
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceFolderModel::startWatching(const QStringList paths)
|
||||||
|
{
|
||||||
|
if (m_is_watching)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto couldnt_be_watched = m_watcher.addPaths(paths);
|
||||||
|
for (auto path : paths) {
|
||||||
|
if (couldnt_be_watched.contains(path))
|
||||||
|
qDebug() << "Failed to start watching " << path;
|
||||||
|
else
|
||||||
|
qDebug() << "Started watching " << path;
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
m_is_watching = !m_is_watching;
|
||||||
|
return m_is_watching;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceFolderModel::stopWatching(const QStringList paths)
|
||||||
|
{
|
||||||
|
if (!m_is_watching)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto couldnt_be_stopped = m_watcher.removePaths(paths);
|
||||||
|
for (auto path : paths) {
|
||||||
|
if (couldnt_be_stopped.contains(path))
|
||||||
|
qDebug() << "Failed to stop watching " << path;
|
||||||
|
else
|
||||||
|
qDebug() << "Stopped watching " << path;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_is_watching = !m_is_watching;
|
||||||
|
return !m_is_watching;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceFolderModel::installResource(QString original_path)
|
||||||
|
{
|
||||||
|
if (!m_can_interact) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
|
||||||
|
original_path = FS::NormalizePath(original_path);
|
||||||
|
QFileInfo file_info(original_path);
|
||||||
|
|
||||||
|
if (!file_info.exists() || !file_info.isReadable()) {
|
||||||
|
qWarning() << "Caught attempt to install non-existing file or file-like object:" << original_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
qDebug() << "Installing: " << file_info.absoluteFilePath();
|
||||||
|
|
||||||
|
Resource resource(file_info);
|
||||||
|
if (!resource.valid()) {
|
||||||
|
qWarning() << original_path << "is not a valid resource. Ignoring it.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto new_path = FS::NormalizePath(m_dir.filePath(file_info.fileName()));
|
||||||
|
if (original_path == new_path) {
|
||||||
|
qWarning() << "Overwriting the mod (" << original_path << ") with itself makes no sense...";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (resource.type()) {
|
||||||
|
case ResourceType::SINGLEFILE:
|
||||||
|
case ResourceType::ZIPFILE:
|
||||||
|
case ResourceType::LITEMOD: {
|
||||||
|
if (QFile::exists(new_path) || QFile::exists(new_path + QString(".disabled"))) {
|
||||||
|
if (!QFile::remove(new_path)) {
|
||||||
|
qCritical() << "Cleaning up new location (" << new_path << ") was unsuccessful!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
qDebug() << new_path << "has been deleted.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QFile::copy(original_path, new_path)) {
|
||||||
|
qCritical() << "Copy from" << original_path << "to" << new_path << "has failed.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FS::updateTimestamp(new_path);
|
||||||
|
|
||||||
|
QFileInfo new_path_file_info(new_path);
|
||||||
|
resource.setFile(new_path_file_info);
|
||||||
|
|
||||||
|
if (!m_is_watching)
|
||||||
|
return update();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case ResourceType::FOLDER: {
|
||||||
|
if (QFile::exists(new_path)) {
|
||||||
|
qDebug() << "Ignoring folder '" << original_path << "', it would merge with" << new_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FS::copy(original_path, new_path)()) {
|
||||||
|
qWarning() << "Copy of folder from" << original_path << "to" << new_path << "has (potentially partially) failed.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo newpathInfo(new_path);
|
||||||
|
resource.setFile(newpathInfo);
|
||||||
|
|
||||||
|
if (!m_is_watching)
|
||||||
|
return update();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceFolderModel::uninstallResource(QString file_name)
|
||||||
|
{
|
||||||
|
for (auto& resource : m_resources) {
|
||||||
|
if (resource->fileinfo().fileName() == file_name) {
|
||||||
|
auto res = resource->destroy();
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
|
||||||
|
{
|
||||||
|
if (!m_can_interact)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (indexes.isEmpty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto i : indexes) {
|
||||||
|
if (i.column() != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& resource = m_resources.at(i.row());
|
||||||
|
|
||||||
|
resource->destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceFolderModel::setResourceEnabled(const QModelIndexList &indexes, EnableAction action)
|
||||||
|
{
|
||||||
|
if (!m_can_interact)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (indexes.isEmpty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
bool succeeded = true;
|
||||||
|
for (auto const& idx : indexes) {
|
||||||
|
if (!validateIndex(idx) || idx.column() != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int row = idx.row();
|
||||||
|
|
||||||
|
auto& resource = m_resources[row];
|
||||||
|
|
||||||
|
// Preserve the row, but change its ID
|
||||||
|
auto old_id = resource->internal_id();
|
||||||
|
if (!resource->enable(action)) {
|
||||||
|
succeeded = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto new_id = resource->internal_id();
|
||||||
|
if (m_resources_index.contains(new_id)) {
|
||||||
|
// FIXME: https://github.com/PolyMC/PolyMC/issues/550
|
||||||
|
}
|
||||||
|
|
||||||
|
m_resources_index.remove(old_id);
|
||||||
|
m_resources_index[new_id] = row;
|
||||||
|
|
||||||
|
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QMutex s_update_task_mutex;
|
||||||
|
bool ResourceFolderModel::update()
|
||||||
|
{
|
||||||
|
// We hold a lock here to prevent race conditions on the m_current_update_task reset.
|
||||||
|
QMutexLocker lock(&s_update_task_mutex);
|
||||||
|
|
||||||
|
// Already updating, so we schedule a future update and return.
|
||||||
|
if (m_current_update_task) {
|
||||||
|
m_scheduled_update = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_current_update_task.reset(createUpdateTask());
|
||||||
|
if (!m_current_update_task)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded,
|
||||||
|
Qt::ConnectionType::QueuedConnection);
|
||||||
|
connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection);
|
||||||
|
connect(m_current_update_task.get(), &Task::finished, this, [=] {
|
||||||
|
m_current_update_task.reset();
|
||||||
|
if (m_scheduled_update) {
|
||||||
|
m_scheduled_update = false;
|
||||||
|
update();
|
||||||
|
} else {
|
||||||
|
emit updateFinished();
|
||||||
|
}
|
||||||
|
}, Qt::ConnectionType::QueuedConnection);
|
||||||
|
|
||||||
|
QThreadPool::globalInstance()->start(m_current_update_task.get());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceFolderModel::resolveResource(Resource* res)
|
||||||
|
{
|
||||||
|
if (!res->shouldResolve()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto task = createParseTask(*res);
|
||||||
|
if (!task)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int ticket = m_next_resolution_ticket.fetch_add(1);
|
||||||
|
|
||||||
|
res->setResolving(true, ticket);
|
||||||
|
m_active_parse_tasks.insert(ticket, task);
|
||||||
|
|
||||||
|
connect(
|
||||||
|
task, &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
||||||
|
connect(
|
||||||
|
task, &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
||||||
|
connect(
|
||||||
|
task, &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection);
|
||||||
|
|
||||||
|
QThreadPool::globalInstance()->start(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceFolderModel::onUpdateSucceeded()
|
||||||
|
{
|
||||||
|
auto update_results = static_cast<BasicFolderLoadTask*>(m_current_update_task.get())->result();
|
||||||
|
|
||||||
|
auto& new_resources = update_results->resources;
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||||
|
auto current_list = m_resources_index.keys();
|
||||||
|
QSet<QString> current_set(current_list.begin(), current_list.end());
|
||||||
|
|
||||||
|
auto new_list = new_resources.keys();
|
||||||
|
QSet<QString> new_set(new_list.begin(), new_list.end());
|
||||||
|
#else
|
||||||
|
QSet<QString> current_set(m_resources_index.keys().toSet());
|
||||||
|
QSet<QString> new_set(new_resources.keys().toSet());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
applyUpdates(current_set, new_set, new_resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
|
||||||
|
{
|
||||||
|
auto iter = m_active_parse_tasks.constFind(ticket);
|
||||||
|
if (iter == m_active_parse_tasks.constEnd())
|
||||||
|
return;
|
||||||
|
|
||||||
|
int row = m_resources_index[resource_id];
|
||||||
|
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
Task* ResourceFolderModel::createUpdateTask()
|
||||||
|
{
|
||||||
|
return new BasicFolderLoadTask(m_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceFolderModel::hasPendingParseTasks() const
|
||||||
|
{
|
||||||
|
return !m_active_parse_tasks.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceFolderModel::directoryChanged(QString path)
|
||||||
|
{
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::DropActions ResourceFolderModel::supportedDropActions() const
|
||||||
|
{
|
||||||
|
// copy from outside, move from within and other resource lists
|
||||||
|
return Qt::CopyAction | Qt::MoveAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::ItemFlags ResourceFolderModel::flags(const QModelIndex& index) const
|
||||||
|
{
|
||||||
|
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
||||||
|
auto flags = defaultFlags;
|
||||||
|
if (!m_can_interact) {
|
||||||
|
flags &= ~Qt::ItemIsDropEnabled;
|
||||||
|
} else {
|
||||||
|
flags |= Qt::ItemIsDropEnabled;
|
||||||
|
if (index.isValid()) {
|
||||||
|
flags |= Qt::ItemIsUserCheckable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ResourceFolderModel::mimeTypes() const
|
||||||
|
{
|
||||||
|
QStringList types;
|
||||||
|
types << "text/uri-list";
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
|
||||||
|
{
|
||||||
|
if (action == Qt::IgnoreAction) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the action is supported
|
||||||
|
if (!data || !(action & supportedDropActions())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// files dropped from outside?
|
||||||
|
if (data->hasUrls()) {
|
||||||
|
auto urls = data->urls();
|
||||||
|
for (auto url : urls) {
|
||||||
|
// only local files may be dropped...
|
||||||
|
if (!url.isLocalFile()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// TODO: implement not only copy, but also move
|
||||||
|
// FIXME: handle errors here
|
||||||
|
installResource(url.toLocalFile());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceFolderModel::validateIndex(const QModelIndex& index) const
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int row = index.row();
|
||||||
|
if (row < 0 || row >= m_resources.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
|
||||||
|
{
|
||||||
|
if (!validateIndex(index))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
int row = index.row();
|
||||||
|
int column = index.column();
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
switch (column) {
|
||||||
|
case NAME_COLUMN:
|
||||||
|
return m_resources[row]->name();
|
||||||
|
case DATE_COLUMN:
|
||||||
|
return m_resources[row]->dateTimeChanged();
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case Qt::ToolTipRole:
|
||||||
|
return m_resources[row]->internal_id();
|
||||||
|
case Qt::CheckStateRole:
|
||||||
|
switch (column) {
|
||||||
|
case ACTIVE_COLUMN:
|
||||||
|
return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||||
|
{
|
||||||
|
int row = index.row();
|
||||||
|
if (row < 0 || row >= rowCount(index) || !index.isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (role == Qt::CheckStateRole)
|
||||||
|
return setResourceEnabled({ index }, EnableAction::TOGGLE);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
|
{
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
switch (section) {
|
||||||
|
case NAME_COLUMN:
|
||||||
|
return tr("Name");
|
||||||
|
case DATE_COLUMN:
|
||||||
|
return tr("Last modified");
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case Qt::ToolTipRole: {
|
||||||
|
switch (section) {
|
||||||
|
case ACTIVE_COLUMN:
|
||||||
|
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||||
|
return tr("Is the resource enabled?");
|
||||||
|
case NAME_COLUMN:
|
||||||
|
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||||
|
return tr("The name of the resource.");
|
||||||
|
case DATE_COLUMN:
|
||||||
|
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||||
|
return tr("The date and time this resource was last changed (or added).");
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* parent)
|
||||||
|
{
|
||||||
|
return new ProxyModel(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
SortType ResourceFolderModel::columnToSortKey(size_t column) const
|
||||||
|
{
|
||||||
|
Q_ASSERT(m_column_sort_keys.size() == columnCount());
|
||||||
|
return m_column_sort_keys.at(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceFolderModel::enableInteraction(bool enabled)
|
||||||
|
{
|
||||||
|
if (m_can_interact == enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_can_interact = enabled;
|
||||||
|
if (size())
|
||||||
|
emit dataChanged(index(0), index(size() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Standard Proxy Model for createFilterProxyModel */
|
||||||
|
[[nodiscard]] bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
|
||||||
|
{
|
||||||
|
auto* model = qobject_cast<ResourceFolderModel*>(sourceModel());
|
||||||
|
if (!model)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const auto& resource = model->at(source_row);
|
||||||
|
|
||||||
|
return resource.applyFilter(filterRegularExpression());
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool ResourceFolderModel::ProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const
|
||||||
|
{
|
||||||
|
auto* model = qobject_cast<ResourceFolderModel*>(sourceModel());
|
||||||
|
if (!model || !source_left.isValid() || !source_right.isValid() || source_left.column() != source_right.column()) {
|
||||||
|
return QSortFilterProxyModel::lessThan(source_left, source_right);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and
|
||||||
|
// proceed.
|
||||||
|
|
||||||
|
auto column_sort_key = model->columnToSortKey(source_left.column());
|
||||||
|
auto const& resource_left = model->at(source_left.row());
|
||||||
|
auto const& resource_right = model->at(source_right.row());
|
||||||
|
|
||||||
|
auto compare_result = resource_left.compare(resource_right, column_sort_key);
|
||||||
|
if (compare_result.first == 0)
|
||||||
|
return QSortFilterProxyModel::lessThan(source_left, source_right);
|
||||||
|
|
||||||
|
if (compare_result.second || sortOrder() != Qt::DescendingOrder)
|
||||||
|
return (compare_result.first < 0);
|
||||||
|
return (compare_result.first > 0);
|
||||||
|
}
|
332
launcher/minecraft/mod/ResourceFolderModel.h
Normal file
332
launcher/minecraft/mod/ResourceFolderModel.h
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileSystemWatcher>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
|
#include "Resource.h"
|
||||||
|
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
class QSortFilterProxyModel;
|
||||||
|
|
||||||
|
/** A basic model for external resources.
|
||||||
|
*
|
||||||
|
* This model manages a list of resources. As such, external users of such resources do not own them,
|
||||||
|
* and the resource's lifetime is contingent on the model's lifetime.
|
||||||
|
*
|
||||||
|
* TODO: Make the resources unique pointers accessible through weak pointers.
|
||||||
|
*/
|
||||||
|
class ResourceFolderModel : public QAbstractListModel {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ResourceFolderModel(QDir, QObject* parent = nullptr);
|
||||||
|
~ResourceFolderModel() override;
|
||||||
|
|
||||||
|
/** Starts watching the paths for changes.
|
||||||
|
*
|
||||||
|
* Returns whether starting to watch all the paths was successful.
|
||||||
|
* If one or more fails, it returns false.
|
||||||
|
*/
|
||||||
|
bool startWatching(const QStringList paths);
|
||||||
|
|
||||||
|
/** Stops watching the paths for changes.
|
||||||
|
*
|
||||||
|
* Returns whether stopping to watch all the paths was successful.
|
||||||
|
* If one or more fails, it returns false.
|
||||||
|
*/
|
||||||
|
bool stopWatching(const QStringList paths);
|
||||||
|
|
||||||
|
/* Helper methods for subclasses, using a predetermined list of paths. */
|
||||||
|
virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); };
|
||||||
|
virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); };
|
||||||
|
|
||||||
|
/** Given a path in the system, install that resource, moving it to its place in the
|
||||||
|
* instance file hierarchy.
|
||||||
|
*
|
||||||
|
* Returns whether the installation was succcessful.
|
||||||
|
*/
|
||||||
|
virtual bool installResource(QString path);
|
||||||
|
|
||||||
|
/** Uninstall (i.e. remove all data about it) a resource, given its file name.
|
||||||
|
*
|
||||||
|
* Returns whether the removal was successful.
|
||||||
|
*/
|
||||||
|
virtual bool uninstallResource(QString file_name);
|
||||||
|
virtual bool deleteResources(const QModelIndexList&);
|
||||||
|
|
||||||
|
/** Applies the given 'action' to the resources in 'indexes'.
|
||||||
|
*
|
||||||
|
* Returns whether the action was successfully applied to all resources.
|
||||||
|
*/
|
||||||
|
virtual bool setResourceEnabled(const QModelIndexList& indexes, EnableAction action);
|
||||||
|
|
||||||
|
/** Creates a new update task and start it. Returns false if no update was done, like when an update is already underway. */
|
||||||
|
virtual bool update();
|
||||||
|
|
||||||
|
/** Creates a new parse task, if needed, for 'res' and start it.*/
|
||||||
|
virtual void resolveResource(Resource* res);
|
||||||
|
|
||||||
|
[[nodiscard]] size_t size() const { return m_resources.size(); };
|
||||||
|
[[nodiscard]] bool empty() const { return size() == 0; }
|
||||||
|
[[nodiscard]] Resource& at(int index) { return *m_resources.at(index); }
|
||||||
|
[[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); }
|
||||||
|
[[nodiscard]] QList<Resource::Ptr> const& all() const { return m_resources; }
|
||||||
|
|
||||||
|
[[nodiscard]] QDir const& dir() const { return m_dir; }
|
||||||
|
|
||||||
|
/** Checks whether there's any parse tasks being done.
|
||||||
|
*
|
||||||
|
* Since they can be quite expensive, and are usually done in a separate thread, if we were to destroy the model while having
|
||||||
|
* such tasks would introduce an undefined behavior, most likely resulting in a crash.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool hasPendingParseTasks() const;
|
||||||
|
|
||||||
|
/* Qt behavior */
|
||||||
|
|
||||||
|
/* Basic columns */
|
||||||
|
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
|
||||||
|
|
||||||
|
[[nodiscard]] int rowCount(const QModelIndex& = {}) const override { return size(); }
|
||||||
|
[[nodiscard]] int columnCount(const QModelIndex& = {}) const override { return NUM_COLUMNS; };
|
||||||
|
|
||||||
|
[[nodiscard]] Qt::DropActions supportedDropActions() const override;
|
||||||
|
|
||||||
|
/// flags, mostly to support drag&drop
|
||||||
|
[[nodiscard]] Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||||
|
[[nodiscard]] QStringList mimeTypes() const override;
|
||||||
|
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override;
|
||||||
|
|
||||||
|
[[nodiscard]] bool validateIndex(const QModelIndex& index) const;
|
||||||
|
|
||||||
|
[[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
|
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
|
||||||
|
|
||||||
|
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
/** This creates a proxy model to filter / sort the model for a UI.
|
||||||
|
*
|
||||||
|
* The actual comparisons and filtering are done directly by the Resource, so to modify behavior go there instead!
|
||||||
|
*/
|
||||||
|
QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] SortType columnToSortKey(size_t column) const;
|
||||||
|
|
||||||
|
class ProxyModel : public QSortFilterProxyModel {
|
||||||
|
public:
|
||||||
|
explicit ProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
|
||||||
|
[[nodiscard]] bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void enableInteraction(bool enabled);
|
||||||
|
void disableInteraction(bool disabled) { enableInteraction(!disabled); }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void updateFinished();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** This creates a new update task to be executed by update().
|
||||||
|
*
|
||||||
|
* The task should load and parse all resources necessary, and provide a way of accessing such results.
|
||||||
|
*
|
||||||
|
* This Task is normally executed when opening a page, so it shouldn't contain much heavy work.
|
||||||
|
* If such work is needed, try using it in the Task create by createParseTask() instead!
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual Task* createUpdateTask();
|
||||||
|
|
||||||
|
/** This creates a new parse task to be executed by onUpdateSucceeded().
|
||||||
|
*
|
||||||
|
* This task should load and parse all heavy info needed by a resource, such as parsing a manifest. It gets executed
|
||||||
|
* in the background, so it slowly updates the UI as tasks get done.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual Task* createParseTask(Resource&) { return nullptr; };
|
||||||
|
|
||||||
|
/** Standard implementation of the model update logic.
|
||||||
|
*
|
||||||
|
* It uses set operations to find differences between the current state and the updated state,
|
||||||
|
* to act only on those disparities.
|
||||||
|
*
|
||||||
|
* The implementation is at the end of this header.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
void applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources);
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void directoryChanged(QString);
|
||||||
|
|
||||||
|
/** Called when the update task is successful.
|
||||||
|
*
|
||||||
|
* This usually calls static_cast on the specific Task type returned by createUpdateTask,
|
||||||
|
* so care must be taken in such cases.
|
||||||
|
* TODO: Figure out a way to express this relationship better without templated classes (Q_OBJECT macro disallows that).
|
||||||
|
*/
|
||||||
|
virtual void onUpdateSucceeded();
|
||||||
|
virtual void onUpdateFailed() {}
|
||||||
|
|
||||||
|
/** Called when the parse task with the given ticket is successful.
|
||||||
|
*
|
||||||
|
* This is just a simple reference implementation. You probably want to override it with your own logic in a subclass
|
||||||
|
* if the resource is complex and has more stuff to parse.
|
||||||
|
*/
|
||||||
|
virtual void onParseSucceeded(int ticket, QString resource_id);
|
||||||
|
virtual void onParseFailed(int ticket, QString resource_id) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
|
||||||
|
// As such, the order in with they appear is very important!
|
||||||
|
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE };
|
||||||
|
|
||||||
|
bool m_can_interact = true;
|
||||||
|
|
||||||
|
QDir m_dir;
|
||||||
|
QFileSystemWatcher m_watcher;
|
||||||
|
bool m_is_watching = false;
|
||||||
|
|
||||||
|
Task::Ptr m_current_update_task = nullptr;
|
||||||
|
bool m_scheduled_update = false;
|
||||||
|
|
||||||
|
QList<Resource::Ptr> m_resources;
|
||||||
|
|
||||||
|
// Represents the relationship between a resource's internal ID and it's row position on the model.
|
||||||
|
QMap<QString, int> m_resources_index;
|
||||||
|
|
||||||
|
QMap<int, Task::Ptr> m_active_parse_tasks;
|
||||||
|
std::atomic<int> m_next_resolution_ticket = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
|
||||||
|
#define RESOURCE_HELPERS(T) \
|
||||||
|
[[nodiscard]] T* operator[](size_t index) \
|
||||||
|
{ \
|
||||||
|
return static_cast<T*>(m_resources[index].get()); \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] T* at(size_t index) \
|
||||||
|
{ \
|
||||||
|
return static_cast<T*>(m_resources[index].get()); \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] const T* at(size_t index) const \
|
||||||
|
{ \
|
||||||
|
return static_cast<const T*>(m_resources.at(index).get()); \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] T* first() \
|
||||||
|
{ \
|
||||||
|
return static_cast<T*>(m_resources.first().get()); \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] T* last() \
|
||||||
|
{ \
|
||||||
|
return static_cast<T*>(m_resources.last().get()); \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] T* find(QString id) \
|
||||||
|
{ \
|
||||||
|
auto iter = std::find_if(m_resources.constBegin(), m_resources.constEnd(), \
|
||||||
|
[&](Resource::Ptr const& r) { return r->internal_id() == id; }); \
|
||||||
|
if (iter == m_resources.constEnd()) \
|
||||||
|
return nullptr; \
|
||||||
|
return static_cast<T*>((*iter).get()); \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Template definition to avoid some code duplication */
|
||||||
|
template <typename T>
|
||||||
|
void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources)
|
||||||
|
{
|
||||||
|
// see if the kept resources changed in some way
|
||||||
|
{
|
||||||
|
QSet<QString> kept_set = current_set;
|
||||||
|
kept_set.intersect(new_set);
|
||||||
|
|
||||||
|
for (auto const& kept : kept_set) {
|
||||||
|
auto row_it = m_resources_index.constFind(kept);
|
||||||
|
Q_ASSERT(row_it != m_resources_index.constEnd());
|
||||||
|
auto row = row_it.value();
|
||||||
|
|
||||||
|
auto& new_resource = new_resources[kept];
|
||||||
|
auto const& current_resource = m_resources.at(row);
|
||||||
|
|
||||||
|
if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
|
||||||
|
// no significant change, ignore...
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the resource is resolving, but something about it changed, we don't want to
|
||||||
|
// continue the resolving.
|
||||||
|
if (current_resource->isResolving()) {
|
||||||
|
auto ticket = current_resource->resolutionTicket();
|
||||||
|
if (m_active_parse_tasks.contains(ticket)) {
|
||||||
|
auto task = (*m_active_parse_tasks.find(ticket)).get();
|
||||||
|
task->abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_resources[row].reset(new_resource);
|
||||||
|
resolveResource(m_resources.at(row).get());
|
||||||
|
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove resources no longer present
|
||||||
|
{
|
||||||
|
QSet<QString> removed_set = current_set;
|
||||||
|
removed_set.subtract(new_set);
|
||||||
|
|
||||||
|
QList<int> removed_rows;
|
||||||
|
for (auto& removed : removed_set)
|
||||||
|
removed_rows.append(m_resources_index[removed]);
|
||||||
|
|
||||||
|
std::sort(removed_rows.begin(), removed_rows.end(), std::greater<int>());
|
||||||
|
|
||||||
|
for (auto& removed_index : removed_rows) {
|
||||||
|
auto removed_it = m_resources.begin() + removed_index;
|
||||||
|
|
||||||
|
Q_ASSERT(removed_it != m_resources.end());
|
||||||
|
Q_ASSERT(removed_set.contains(removed_it->get()->internal_id()));
|
||||||
|
|
||||||
|
if ((*removed_it)->isResolving()) {
|
||||||
|
auto ticket = (*removed_it)->resolutionTicket();
|
||||||
|
if (m_active_parse_tasks.contains(ticket)) {
|
||||||
|
auto task = (*m_active_parse_tasks.find(ticket)).get();
|
||||||
|
task->abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beginRemoveRows(QModelIndex(), removed_index, removed_index);
|
||||||
|
m_resources.erase(removed_it);
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new resources to the end
|
||||||
|
{
|
||||||
|
QSet<QString> added_set = new_set;
|
||||||
|
added_set.subtract(current_set);
|
||||||
|
|
||||||
|
// When you have a Qt build with assertions turned on, proceeding here will abort the application
|
||||||
|
if (added_set.size() > 0) {
|
||||||
|
beginInsertRows(QModelIndex(), m_resources.size(), m_resources.size() + added_set.size() - 1);
|
||||||
|
|
||||||
|
for (auto& added : added_set) {
|
||||||
|
auto res = new_resources[added];
|
||||||
|
m_resources.append(res);
|
||||||
|
resolveResource(m_resources.last().get());
|
||||||
|
}
|
||||||
|
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update index
|
||||||
|
{
|
||||||
|
m_resources_index.clear();
|
||||||
|
int idx = 0;
|
||||||
|
for (auto const& mod : qAsConst(m_resources)) {
|
||||||
|
m_resources_index[mod->internal_id()] = idx;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
116
launcher/minecraft/mod/ResourcePack.cpp
Normal file
116
launcher/minecraft/mod/ResourcePack.cpp
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
#include "ResourcePack.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
#include "Version.h"
|
||||||
|
|
||||||
|
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
||||||
|
|
||||||
|
// Values taken from:
|
||||||
|
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||||
|
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
|
||||||
|
{ 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } },
|
||||||
|
{ 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } },
|
||||||
|
{ 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } },
|
||||||
|
{ 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } },
|
||||||
|
{ 9, { Version("1.19"), Version("1.19.2") } },
|
||||||
|
};
|
||||||
|
|
||||||
|
void ResourcePack::setPackFormat(int new_format_id)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_data_lock);
|
||||||
|
|
||||||
|
if (!s_pack_format_versions.contains(new_format_id)) {
|
||||||
|
qWarning() << "Pack format '%1' is not a recognized resource pack id!";
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pack_format = new_format_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePack::setDescription(QString new_description)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_data_lock);
|
||||||
|
|
||||||
|
m_description = new_description;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcePack::setImage(QImage new_image)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_data_lock);
|
||||||
|
|
||||||
|
Q_ASSERT(!new_image.isNull());
|
||||||
|
|
||||||
|
if (m_pack_image_cache_key.key.isValid())
|
||||||
|
QPixmapCache::remove(m_pack_image_cache_key.key);
|
||||||
|
|
||||||
|
m_pack_image_cache_key.key = QPixmapCache::insert(QPixmap::fromImage(new_image));
|
||||||
|
m_pack_image_cache_key.was_ever_used = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap ResourcePack::image(QSize size)
|
||||||
|
{
|
||||||
|
QPixmap cached_image;
|
||||||
|
if (QPixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
|
||||||
|
if (size.isNull())
|
||||||
|
return cached_image;
|
||||||
|
return cached_image.scaled(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No valid image we can get
|
||||||
|
if (!m_pack_image_cache_key.was_ever_used)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// Imaged got evicted from the cache. Re-process it and retry.
|
||||||
|
ResourcePackUtils::process(*this);
|
||||||
|
return image(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<Version, Version> ResourcePack::compatibleVersions() const
|
||||||
|
{
|
||||||
|
if (!s_pack_format_versions.contains(m_pack_format)) {
|
||||||
|
return { {}, {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
return s_pack_format_versions.constFind(m_pack_format).value();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, bool> ResourcePack::compare(const Resource& other, SortType type) const
|
||||||
|
{
|
||||||
|
auto const& cast_other = static_cast<ResourcePack const&>(other);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
default: {
|
||||||
|
auto res = Resource::compare(other, type);
|
||||||
|
if (res.first != 0)
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
case SortType::PACK_FORMAT: {
|
||||||
|
auto this_ver = packFormat();
|
||||||
|
auto other_ver = cast_other.packFormat();
|
||||||
|
|
||||||
|
if (this_ver > other_ver)
|
||||||
|
return { 1, type == SortType::PACK_FORMAT };
|
||||||
|
if (this_ver < other_ver)
|
||||||
|
return { -1, type == SortType::PACK_FORMAT };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { 0, false };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourcePack::applyFilter(QRegularExpression filter) const
|
||||||
|
{
|
||||||
|
if (filter.match(description()).hasMatch())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (filter.match(QString::number(packFormat())).hasMatch())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (filter.match(compatibleVersions().first.toString()).hasMatch())
|
||||||
|
return true;
|
||||||
|
if (filter.match(compatibleVersions().second.toString()).hasMatch())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return Resource::applyFilter(filter);
|
||||||
|
}
|
69
launcher/minecraft/mod/ResourcePack.h
Normal file
69
launcher/minecraft/mod/ResourcePack.h
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Resource.h"
|
||||||
|
|
||||||
|
#include <QImage>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QPixmapCache>
|
||||||
|
|
||||||
|
class Version;
|
||||||
|
|
||||||
|
/* TODO:
|
||||||
|
*
|
||||||
|
* Store localized descriptions
|
||||||
|
* */
|
||||||
|
|
||||||
|
class ResourcePack : public Resource {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
using Ptr = shared_qobject_ptr<Resource>;
|
||||||
|
|
||||||
|
ResourcePack(QObject* parent = nullptr) : Resource(parent) {}
|
||||||
|
ResourcePack(QFileInfo file_info) : Resource(file_info) {}
|
||||||
|
|
||||||
|
/** Gets the numerical ID of the pack format. */
|
||||||
|
[[nodiscard]] int packFormat() const { return m_pack_format; }
|
||||||
|
/** Gets, respectively, the lower and upper versions supported by the set pack format. */
|
||||||
|
[[nodiscard]] std::pair<Version, Version> compatibleVersions() const;
|
||||||
|
|
||||||
|
/** Gets the description of the resource pack. */
|
||||||
|
[[nodiscard]] QString description() const { return m_description; }
|
||||||
|
|
||||||
|
/** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */
|
||||||
|
[[nodiscard]] QPixmap image(QSize size);
|
||||||
|
|
||||||
|
/** Thread-safe. */
|
||||||
|
void setPackFormat(int new_format_id);
|
||||||
|
|
||||||
|
/** Thread-safe. */
|
||||||
|
void setDescription(QString new_description);
|
||||||
|
|
||||||
|
/** Thread-safe. */
|
||||||
|
void setImage(QImage new_image);
|
||||||
|
|
||||||
|
[[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
|
||||||
|
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
mutable QMutex m_data_lock;
|
||||||
|
|
||||||
|
/* The 'version' of a resource pack, as defined in the pack.mcmeta file.
|
||||||
|
* See https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||||
|
*/
|
||||||
|
int m_pack_format = 0;
|
||||||
|
|
||||||
|
/** The resource pack's description, as defined in the pack.mcmeta file.
|
||||||
|
*/
|
||||||
|
QString m_description;
|
||||||
|
|
||||||
|
/** The resource pack's image file cache key, for access in the QPixmapCache global instance.
|
||||||
|
*
|
||||||
|
* The 'was_ever_used' state simply identifies whether the key was never inserted on the cache (true),
|
||||||
|
* so as to tell whether a cache entry is inexistent or if it was just evicted from the cache.
|
||||||
|
*/
|
||||||
|
struct {
|
||||||
|
QPixmapCache::Key key;
|
||||||
|
bool was_ever_used = false;
|
||||||
|
} m_pack_image_cache_key;
|
||||||
|
};
|
@ -1,58 +1,151 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* PolyMC - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
*
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* the Free Software Foundation, version 3.
|
* 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
|
* This program is distributed in the hope that it will be useful,
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* GNU General Public License for more details.
|
* 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/>.
|
* 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:
|
* This file incorporates work covered by the following copyright and
|
||||||
*
|
* permission notice:
|
||||||
* Copyright 2013-2021 MultiMC Contributors
|
*
|
||||||
*
|
* 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.
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* You may obtain a copy of the License at
|
* 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
|
*
|
||||||
*
|
* 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,
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* See the License for the specific language governing permissions and
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* limitations under the License.
|
* See the License for the specific language governing permissions and
|
||||||
*/
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "ResourcePackFolderModel.h"
|
#include "ResourcePackFolderModel.h"
|
||||||
|
|
||||||
ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) {
|
#include "Version.h"
|
||||||
|
|
||||||
|
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||||
|
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
||||||
|
|
||||||
|
ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir) : ResourceFolderModel(QDir(dir))
|
||||||
|
{
|
||||||
|
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||||
if (role == Qt::ToolTipRole) {
|
{
|
||||||
switch (section) {
|
if (!validateIndex(index))
|
||||||
case ActiveColumn:
|
return {};
|
||||||
return tr("Is the resource pack enabled?");
|
|
||||||
case NameColumn:
|
int row = index.row();
|
||||||
return tr("The name of the resource pack.");
|
int column = index.column();
|
||||||
case VersionColumn:
|
|
||||||
return tr("The version of the resource pack.");
|
switch (role) {
|
||||||
case DateColumn:
|
case Qt::DisplayRole:
|
||||||
return tr("The date and time this resource pack was last changed (or added).");
|
switch (column) {
|
||||||
default:
|
case NameColumn:
|
||||||
return QVariant();
|
return m_resources[row]->name();
|
||||||
|
case PackFormatColumn: {
|
||||||
|
auto resource = at(row);
|
||||||
|
auto pack_format = resource->packFormat();
|
||||||
|
if (pack_format == 0)
|
||||||
|
return tr("Unrecognized");
|
||||||
|
|
||||||
|
auto version_bounds = resource->compatibleVersions();
|
||||||
|
if (version_bounds.first.toString().isEmpty())
|
||||||
|
return QString::number(pack_format);
|
||||||
|
|
||||||
|
return QString("%1 (%2 - %3)")
|
||||||
|
.arg(QString::number(pack_format), version_bounds.first.toString(), version_bounds.second.toString());
|
||||||
|
}
|
||||||
|
case DateColumn:
|
||||||
|
return m_resources[row]->dateTimeChanged();
|
||||||
|
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
case Qt::ToolTipRole: {
|
||||||
|
if (column == PackFormatColumn) {
|
||||||
|
//: The string being explained by this is in the format: ID (Lower version - Upper version)
|
||||||
|
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
|
||||||
|
}
|
||||||
|
return m_resources[row]->internal_id();
|
||||||
}
|
}
|
||||||
|
case Qt::CheckStateRole:
|
||||||
|
switch (column) {
|
||||||
|
case ActiveColumn:
|
||||||
|
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return ModFolderModel::headerData(section, orientation, role);
|
|
||||||
|
QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
|
{
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
switch (section) {
|
||||||
|
case ActiveColumn:
|
||||||
|
return QString();
|
||||||
|
case NameColumn:
|
||||||
|
return tr("Name");
|
||||||
|
case PackFormatColumn:
|
||||||
|
return tr("Pack Format");
|
||||||
|
case DateColumn:
|
||||||
|
return tr("Last changed");
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
case Qt::ToolTipRole:
|
||||||
|
switch (section) {
|
||||||
|
case ActiveColumn:
|
||||||
|
return tr("Is the resource pack enabled? (Only valid for ZIPs)");
|
||||||
|
case NameColumn:
|
||||||
|
return tr("The name of the resource pack.");
|
||||||
|
case PackFormatColumn:
|
||||||
|
//: The string being explained by this is in the format: ID (Lower version - Upper version)
|
||||||
|
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
|
||||||
|
case DateColumn:
|
||||||
|
return tr("The date and time this resource pack was last changed (or added).");
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
|
||||||
|
{
|
||||||
|
return NUM_COLUMNS;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task* ResourcePackFolderModel::createUpdateTask()
|
||||||
|
{
|
||||||
|
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return new ResourcePack(entry); });
|
||||||
|
}
|
||||||
|
|
||||||
|
Task* ResourcePackFolderModel::createParseTask(Resource& resource)
|
||||||
|
{
|
||||||
|
return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast<ResourcePack&>(resource));
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user