Compare commits

..

26 Commits

Author SHA1 Message Date
6e65cd4405 Merge pull request #1118 from flowln/backport_major_version_filter_fix 2022-09-08 18:54:56 +02:00
98fbb3613d refactor: create mod pages and filter widget by factory methods
This takes most expensive operations out of the constructors.

Signed-off-by: flow <flowlnlnln@gmail.com>
2022-09-08 12:47:37 -03:00
2666beafb1 fix: use more robust method of finding matches for major version
This uses the proper version list to find all MC versions matching the
major number (_don't say anything about SemVer_ 🔫).

Signed-off-by: flow <flowlnlnln@gmail.com>
2022-09-08 12:37:11 -03:00
cf8a76be6b chore: bump version
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2022-09-08 15:24:48 +02:00
e96f482544 Merge pull request #965 from flowln/fat_files_in_memory
Refactor a bit EnsureMetadataTask and calculate hashes in a incremental manner
2022-09-08 14:35:59 +02:00
dc32f0671b Merge pull request #941 from Scrumplex/bump-cxx-standard
Bump to C++17
2022-09-08 14:35:30 +02:00
d6d7794cd0 Merge pull request #1107 from DioEgizio/smaller-about 2022-09-08 13:11:06 +02:00
94d95d45d4 Merge pull request #1095 from flowln/ensure_file_path_in_override 2022-09-08 13:11:06 +02:00
5e767a91d9 Merge pull request #1080 from flowln/eternal_cache
Never invalidate libraries cache entries by time elapsed
2022-09-08 13:11:06 +02:00
c89f8b4657 Merge pull request #1087 from DioEgizio/fix-ftblegacy-url
fix: fix urls on ftb legacy
2022-09-08 13:11:06 +02:00
d7fc0f53d3 Merge pull request #1073 from DioEgizio/update-copying
fix(COPYING): fix COPYING.md by adding some missing copyright notices
2022-09-08 13:11:04 +02:00
e81cf8a845 Merge pull request #1035 from Scrumplex/fix-coremods
Make Coremods / Mods seperation more clear
2022-09-08 13:10:37 +02:00
c116885ab1 Merge pull request #1044 from flowln/better_orphan_fix 2022-09-08 13:10:37 +02:00
d68d5ca23f Merge pull request #1007 from Gingeh/disable-update-button 2022-09-08 13:10:37 +02:00
6d94338a56 Merge pull request #1058 from DioEgizio/fix-update-org.polymc.PolyMC.metainfo.xml.in 2022-09-08 13:10:37 +02:00
1e1a1cef05 Merge pull request #1049 from flowln/waiting_for_news_-_- 2022-09-08 13:10:37 +02:00
34bab3e1b2 Merge pull request #968 from magneticflux-/utf8-logging
Decode process lines as UTF-8
2022-09-08 13:10:37 +02:00
be6d6501e8 Merge pull request #1039 from budak7273/fix-world-safety-nag-title-text 2022-09-08 13:10:37 +02:00
10a70732ce Merge pull request #920 from flowln/metacache_fix 2022-09-08 13:10:37 +02:00
a725dc82a7 Merge pull request #1018 from Scrumplex/fix-infinite-auth-loop 2022-09-08 13:10:37 +02:00
97ce8a94e9 Merge pull request #1017 from flowln/kill_orphan_metadata
Remove orphaned metadata to avoid problems with auto-updating instances
2022-09-08 13:10:37 +02:00
85f0904872 Merge pull request #1014 from DioEgizio/downgrade-qt-macos
chore: downgrade to Qt 6.3.0 on macos
2022-09-08 13:10:37 +02:00
cb0a5e42df Merge pull request #1006 from DioEgizio/appimage-ubuntu-moment
fix: work around ubuntu 22.04 openssl appimage issues by copying openssl libs
2022-09-08 13:10:36 +02:00
a0c7fa30c0 Merge pull request #1019 from Scrumplex/fix-openbsd-root
Add root path detection on OpenBSD
2022-09-08 13:10:36 +02:00
b7490b479c Merge pull request #970 from PolyMC/Bump-1.4.1
bump version to 1.4.1
2022-07-28 09:42:56 +01:00
0d35edbbf3 bump version 2022-07-25 06:44:34 +01:00
440 changed files with 14533 additions and 12094 deletions

View File

@ -27,14 +27,7 @@ body:
attributes:
label: Version of PolyMC
description: The version of PolyMC used in the bug report.
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
placeholder: PolyMC 1.3.2
validations:
required: true
- type: textarea

View File

@ -1,3 +0,0 @@
query-filters:
- exclude:
id: cpp/fixme-comment

View File

@ -27,32 +27,25 @@ jobs:
qt_host: linux
qt_version: '6.2.4'
qt_modules: 'qt5compat qtimageformats'
qt_path: /home/runner/work/PolyMC/Qt
- os: windows-2022
name: "Windows-Legacy"
msystem: clang32
msystem: mingw32
qt_ver: 5
- os: windows-2022
name: "Windows"
msystem: clang64
msystem: mingw32
qt_ver: 6
- os: macos-12
name: macOS
macosx_deployment_target: 10.15
macosx_deployment_target: 10.14
qt_ver: 6
qt_host: mac
qt_version: '6.3.0'
qt_modules: 'qt5compat qtimageformats'
- os: macos-12
name: macOS-Legacy
macosx_deployment_target: 10.13
qt_ver: 5
qt_host: mac
qt_version: '5.15.2'
qt_modules: ''
qt_path: /Users/runner/work/PolyMC/Qt
runs-on: ${{ matrix.os }}
@ -73,14 +66,6 @@ jobs:
with:
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'
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
@ -89,7 +74,6 @@ jobs:
update: true
install: >-
git
mingw-w64-x86_64-binutils
pacboy: >-
toolchain:p
cmake:p
@ -100,11 +84,12 @@ jobs:
qt${{ matrix.qt_ver }}-imageformats:p
quazip-qt${{ matrix.qt_ver }}:p
ccache:p
nsis:p
${{ matrix.qt_ver == 6 && 'qt6-5compat:p' || '' }}
- name: Setup ccache
if: runner.os != 'Windows' && inputs.build_type == 'Debug'
uses: hendrikmuhs/ccache-action@v1.2.3
uses: hendrikmuhs/ccache-action@v1.2.1
with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
@ -126,7 +111,7 @@ jobs:
- name: Retrieve ccache cache (Windows)
if: runner.os == 'Windows' && inputs.build_type == 'Debug'
uses: actions/cache@v3.0.11
uses: actions/cache@v3.0.2
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
@ -156,16 +141,24 @@ jobs:
run: |
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)
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS'
uses: jurplel/install-qt-action@v3
if: matrix.qt_ver == 6 && runner.os != 'Windows'
uses: jurplel/install-qt-action@v2
with:
version: ${{ matrix.qt_version }}
host: ${{ matrix.qt_host }}
target: 'desktop'
modules: ${{ matrix.qt_modules }}
cache: true
cache-key-prefix: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache
cached: ${{ steps.cache-qt.outputs.cache-hit }}
aqtversion: ==2.1.*
- name: Prepare AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
@ -181,20 +174,15 @@ jobs:
##
- name: Configure CMake (macOS)
if: runner.os == 'macOS' && matrix.qt_ver == 6
if: runner.os == 'macOS'
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 }} -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
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
- name: Configure CMake (Windows)
if: runner.os == 'Windows'
shell: msys2 {0}
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_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
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
- name: Configure CMake (Linux)
if: runner.os == 'Linux'
@ -231,14 +219,6 @@ jobs:
run: |
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
##
@ -249,18 +229,17 @@ jobs:
cmake --install ${{ env.BUILD_DIR }}
cd ${{ env.INSTALL_DIR }}
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher"
sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
mv "PrismLauncher.app" "Prism Launcher.app"
tar -czf ../PrismLauncher.tar.gz *
chmod +x "PolyMC.app/Contents/MacOS/polymc"
sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PolyMC.app/Contents/MacOS/polymc"
tar -czf ../PolyMC.tar.gz *
- name: Make Sparkle signature (macOS)
if: matrix.name == 'macOS'
if: runner.os == 'macOS'
run: |
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
brew install openssl@3
echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem
signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.tar.gz -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PolyMC.tar.gz -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
rm ed25519-priv.pem
cat >> $GITHUB_STEP_SUMMARY << EOF
### Artifact Information :information_source:
@ -280,8 +259,10 @@ jobs:
cmake --install ${{ env.BUILD_DIR }}
cd ${{ env.INSTALL_DIR }}
if [ "${{ matrix.qt_ver }}" == "5" ]; then
cp /clang32/bin/libcrypto-1_1.dll /clang32/bin/libssl-1_1.dll ./
if [ "${{ matrix.msystem }}" == "mingw32" ]; then
cp /mingw32/bin/libcrypto-1_1.dll /mingw32/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
- name: Package (Windows, portable)
@ -293,6 +274,7 @@ jobs:
- name: Package (Windows, installer)
if: runner.os == 'Windows'
shell: msys2 {0}
run: |
cd ${{ env.INSTALL_DIR }}
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
@ -303,7 +285,7 @@ jobs:
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}
cd ${{ env.INSTALL_DIR }}
tar --owner root --group root -czf ../PrismLauncher.tar.gz *
tar --owner root --group root -czf ../PolyMC.tar.gz *
- name: Package (Linux, portable)
if: runner.os == 'Linux'
@ -312,7 +294,7 @@ jobs:
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
cd ${{ env.INSTALL_PORTABLE_DIR }}
tar -czf ../PrismLauncher-portable.tar.gz *
tar -czf ../PolyMC-portable.tar.gz *
- name: Package AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
@ -320,7 +302,7 @@ jobs:
run: |
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
export OUTPUT="PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
export OUTPUT="PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
chmod +x linuxdeploy-*.AppImage
@ -331,7 +313,7 @@ jobs:
cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk
cp -r /home/runner/work/PrismLauncher/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
cp -r /home/runner/work/PolyMC/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/
@ -343,7 +325,7 @@ jobs:
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib"
export LD_LIBRARY_PATH
./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg
./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
##
# UPLOAD BUILDS
@ -353,63 +335,63 @@ jobs:
if: runner.os == 'macOS'
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher.tar.gz
name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: PolyMC.tar.gz
- name: Upload binary zip (Windows)
if: runner.os == 'Windows'
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
name: PolyMC-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: ${{ env.INSTALL_DIR }}/**
- name: Upload binary zip (Windows, portable)
if: runner.os == 'Windows'
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
name: PolyMC-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: ${{ env.INSTALL_PORTABLE_DIR }}/**
- name: Upload installer (Windows)
if: runner.os == 'Windows'
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-Setup.exe
name: PolyMC-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
path: PolyMC-Setup.exe
- name: Upload binary tarball (Linux, Qt 5)
if: runner.os == 'Linux' && matrix.qt_ver != 6
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher.tar.gz
name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: PolyMC.tar.gz
- name: Upload binary tarball (Linux, portable, Qt 5)
if: runner.os == 'Linux' && matrix.qt_ver != 6
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-portable.tar.gz
name: PolyMC-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: PolyMC-portable.tar.gz
- name: Upload binary tarball (Linux, Qt 6)
if: runner.os == 'Linux' && matrix.qt_ver !=5
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ runner.os }}-Qt6-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher.tar.gz
name: PolyMC-${{ runner.os }}-Qt6-${{ env.VERSION }}-${{ inputs.build_type }}
path: PolyMC.tar.gz
- name: Upload binary tarball (Linux, portable, Qt 6)
if: runner.os == 'Linux' && matrix.qt_ver != 5
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-portable.tar.gz
name: PolyMC-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: PolyMC-portable.tar.gz
- name: Upload AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
path: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage

View File

@ -11,7 +11,6 @@ on:
- '**.nix'
- 'packages/**'
- '.github/ISSUE_TEMPLATE/**'
- '.markdownlint**'
pull_request:
paths-ignore:
- '**.md'
@ -20,7 +19,6 @@ on:
- '**.nix'
- 'packages/**'
- '.github/ISSUE_TEMPLATE/**'
- '.markdownlint**'
workflow_dispatch:
jobs:

View File

@ -25,7 +25,7 @@ jobs:
uses: actions/checkout@v3
with:
submodules: 'true'
path: 'PrismLauncher-source'
path: 'PolyMC-source'
- name: Download artifacts
uses: actions/download-artifact@v3
- name: Grab and store version
@ -34,26 +34,25 @@ jobs:
echo "VERSION=$tag_name" >> $GITHUB_ENV
- name: Package artifacts properly
run: |
mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }}
mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
mv PrismLauncher-Linux-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
mv PrismLauncher-Linux*/PrismLauncher.tar.gz PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage
mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
mv ${{ github.workspace }}/PolyMC-source PolyMC-${{ env.VERSION }}
mv PolyMC-Linux-Qt6-Portable*/PolyMC-portable.tar.gz PolyMC-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
mv PolyMC-Linux-Qt6*/PolyMC.tar.gz PolyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz
mv PolyMC-Linux-Portable*/PolyMC-portable.tar.gz PolyMC-Linux-Portable-${{ env.VERSION }}.tar.gz
mv PolyMC-Linux*/PolyMC.tar.gz PolyMC-Linux-${{ env.VERSION }}.tar.gz
mv PolyMC-*.AppImage/PolyMC-*.AppImage PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
mv PolyMC-macOS*/PolyMC.tar.gz PolyMC-macOS-${{ env.VERSION }}.tar.gz
tar -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
tar -czf PolyMC-${{ env.VERSION }}.tar.gz PolyMC-${{ env.VERSION }}
for d in PrismLauncher-Windows-*; do
for d in PolyMC-Windows-*; do
cd "${d}" || continue
LEGACY="$(echo -n ${d} | grep -o Legacy || true)"
INST="$(echo -n ${d} | grep -o Setup || true)"
PORT="$(echo -n ${d} | grep -o Portable || true)"
NAME="PrismLauncher-Windows"
NAME="PolyMC-Windows"
test -z "${LEGACY}" || NAME="${NAME}-Legacy"
test -z "${PORT}" || NAME="${NAME}-Portable"
test -z "${INST}" || mv PrismLauncher-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
test -z "${INST}" || mv PolyMC-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
cd ..
done
@ -65,21 +64,20 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
name: Prism Launcher ${{ env.VERSION }}
name: PolyMC ${{ env.VERSION }}
draft: true
prerelease: false
files: |
PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage
PrismLauncher-Windows-Legacy-${{ env.VERSION }}.zip
PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Windows-Legacy-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-Legacy-Setup-${{ env.VERSION }}.exe
PrismLauncher-Windows-${{ env.VERSION }}.zip
PrismLauncher-Windows-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-Setup-${{ env.VERSION }}.exe
PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
PrismLauncher-${{ env.VERSION }}.tar.gz
PolyMC-Linux-${{ env.VERSION }}.tar.gz
PolyMC-Linux-Portable-${{ env.VERSION }}.tar.gz
PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
PolyMC-Windows-Legacy-${{ env.VERSION }}.zip
PolyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz
PolyMC-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
PolyMC-Windows-Legacy-Portable-${{ env.VERSION }}.zip
PolyMC-Windows-Legacy-Setup-${{ env.VERSION }}.exe
PolyMC-Windows-${{ env.VERSION }}.zip
PolyMC-Windows-Portable-${{ env.VERSION }}.zip
PolyMC-Windows-Setup-${{ env.VERSION }}.exe
PolyMC-macOS-${{ env.VERSION }}.tar.gz
PolyMC-${{ env.VERSION }}.tar.gz

View File

@ -7,9 +7,8 @@ jobs:
publish:
runs-on: windows-latest
steps:
- uses: vedantmgoyal2009/winget-releaser@v1
- uses: vedantmgoyal2009/winget-releaser@latest
with:
identifier: PrismLauncher.PrismLauncher
version: ${{ github.event.release.tag_name }}
installers-regex: 'PrismLauncher-Windows-Setup-.+\.exe$'
identifier: PolyMC.PolyMC
installers-regex: '\.exe$'
token: ${{ secrets.WINGET_TOKEN }}

14
.gitmodules vendored
View File

@ -1,12 +1,8 @@
[submodule "depends/libnbtplusplus"]
path = libraries/libnbtplusplus
url = https://github.com/PolyMC/libnbtplusplus.git
pushurl = git@github.com:PolyMC/libnbtplusplus.git
[submodule "libraries/quazip"]
path = libraries/quazip
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

View File

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

View File

@ -1,2 +0,0 @@
libraries/nbtplusplus
libraries/quazip

View File

@ -1,53 +1,5 @@
# 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
```
Build instructions are available on [the website](https://polymc.org/wiki/development/build-instructions/).
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).

View File

@ -33,10 +33,8 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11)
include(GenerateExportHeader)
set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
# ATL's packlist needs more than the default 1 Mib stack on windows
if(WIN32)
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}")
if(UNIX AND APPLE)
set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}")
endif()
# Fix build with Qt 5.13
@ -69,24 +67,24 @@ find_package(ECM REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}")
include(CTest)
include(ECMAddTests)
if(BUILD_TESTING)
if (BUILD_TESTING)
enable_testing()
endif()
##################################### Set Application options #####################################
######## Set URLs ########
set(Launcher_NEWS_RSS_URL "https://prismlauncher.org/feed/feed.xml" CACHE STRING "URL to fetch Prism Launcher's news RSS feed from.")
set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL that gets opened when the user clicks 'More News'")
set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
set(Launcher_NEWS_RSS_URL "https://polymc.org/feed/feed.xml" CACHE STRING "URL to fetch PolyMC'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_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 version numbers ########
set(Launcher_VERSION_MAJOR 5)
set(Launcher_VERSION_MINOR 2)
set(Launcher_VERSION_MAJOR 1)
set(Launcher_VERSION_MINOR 4)
set(Launcher_VERSION_HOTFIX 2)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
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 number
set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
# 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.")
@ -95,25 +93,25 @@ set(Launcher_BUILD_PLATFORM "" CACHE STRING "A short string identifying the plat
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
# The metadata server
set(Launcher_META_URL "https://meta.prismlauncher.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.")
set(Launcher_META_URL "https://meta.polymc.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.")
# Imgur API Client ID
set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application")
# Bug tracker URL
set(Launcher_BUG_TRACKER_URL "https://github.com/PrismLauncher/PrismLauncher/issues" CACHE STRING "URL for the bug tracker.")
set(Launcher_BUG_TRACKER_URL "https://github.com/PolyMC/PolyMC/issues" CACHE STRING "URL for the bug tracker.")
# Translations Platform URL
set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/prismlauncher/launcher/" CACHE STRING "URL for the translations platform.")
set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/polymc/polymc/" CACHE STRING "URL for the translations platform.")
# Matrix Space
set(Launcher_MATRIX_URL "https://matrix.to/#/#prismlauncher:matrix.org" CACHE STRING "URL to the Matrix Space")
set(Launcher_MATRIX_URL "https://matrix.to/#/#polymc:matrix.org" CACHE STRING "URL to the Matrix Space")
# Discord URL
set(Launcher_DISCORD_URL "https://discord.gg/prismlauncher" CACHE STRING "URL for the Discord guild.")
set(Launcher_DISCORD_URL "https://discord.gg/Z52pwxWCHP" CACHE STRING "URL for the Discord guild.")
# Subreddit URL
set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PrismLauncher/" CACHE STRING "URL for the subreddit.")
set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PolyMCLauncher/" CACHE STRING "URL for the subreddit.")
# Builds
set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
@ -128,13 +126,12 @@ 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
# https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use
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")
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")
# 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
# NOTE: CurseForge requires you to change this if you make any kind of derivative work.
# This key was issued specifically for Prism Launcher
set(Launcher_CURSEFORGE_API_KEY "$2a$10$wuAJuNZuted3NORVmpgUC.m8sI.pv1tOPKZyBgLFGjxFp/br0lZCC" CACHE STRING "API key for the CurseForge platform")
set(Launcher_CURSEFORGE_API_KEY "$2a$10$1Oqr2MX3O4n/ilhFGc597u8tfI3L2Hyr9/rtWDAMRjghSQV2QUuxq" CACHE STRING "API key for the CurseForge platform")
#### Check the current Git commit and branch
@ -146,8 +143,15 @@ message(STATUS "Git commit: ${Launcher_GIT_COMMIT}")
message(STATUS "Git tag: ${Launcher_GIT_TAG}")
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")
set(Launcher_BUILD_TIMESTAMP "${TODAY}")
set(Launcher_RELEASE_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 ################################
@ -195,17 +199,9 @@ if (Qt5_POSITION_INDEPENDENT_CODE)
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
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 #######################################
set(Launcher_APP_BINARY_NAME "prismlauncher" CACHE STRING "Name of the Launcher binary")
set(Launcher_APP_BINARY_NAME "polymc" CACHE STRING "Name of the Launcher binary")
add_subdirectory(program_info)
####################################### Install layout #######################################
@ -227,16 +223,16 @@ if(UNIX AND APPLE)
set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app")
# Mac bundle settings
set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_DisplayName}")
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.prismlauncher.${Launcher_Name}")
set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_Name}")
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_GUI_IDENTIFIER "org.polymc.${Launcher_Name}")
set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}")
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}")
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
set(MACOSX_BUNDLE_COPYRIGHT "© 2022 ${Launcher_Copyright_Mac}")
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=")
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml")
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2021-2022 ${Launcher_Copyright}")
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "idALcUIazingvKSSsEa9U7coDVxZVx/ORpOEE/QtJfg=")
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://polymc.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_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive")
@ -254,7 +250,7 @@ if(UNIX AND APPLE)
elseif(UNIX)
set(BINARY_DEST_DIR "bin")
set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}")
set(JARS_DEST_DIR "share/${Launcher_APP_BINARY_NAME}")
set(JARS_DEST_DIR "share/jars")
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_ICON_DEST_DIR "share/icons/hicolor/scalable/apps" CACHE STRING "Path to the scalable icon directory")
@ -311,6 +307,7 @@ add_subdirectory(libraries/systeminfo) # system information library
add_subdirectory(libraries/hoedown) # markdown parser
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
add_subdirectory(libraries/javacheck) # java compatibility checker
add_subdirectory(libraries/xz-embedded) # xz compression
if (FORCE_BUNDLED_QUAZIP)
message(STATUS "Using bundled QuaZip")
set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts.
@ -321,30 +318,15 @@ else()
endif()
add_subdirectory(libraries/rainbow) # Qt extension for colors
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
if(NOT tomlplusplus_FOUND)
message(STATUS "Using bundled tomlplusplus")
add_subdirectory(libraries/tomlplusplus) # toml parser
else()
message(STATUS "Using system tomlplusplus")
endif()
add_subdirectory(libraries/classparser) # class parser library
add_subdirectory(libraries/tomlc99) # toml parser
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
add_subdirectory(libraries/gamemode)
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
if (NOT ghc_filesystem_FOUND)
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 ###############################
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.
add_subdirectory(launcher)

View File

@ -1,5 +1,4 @@
# Contributor Covenant Code of Conduct
This is a modified version of the Contributor Covenant.
See commit history to see our changes.
@ -63,7 +62,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement via email at
[coc@scrumplex.net](mailto:coc@scrumplex.net) (Email
[polymc-enforcement@scrumplex.net](mailto:polymc-enforcement@scrumplex.net) (Email
address subject to change).
All complaints will be reviewed and investigated promptly and fairly.
@ -134,3 +133,4 @@ For answers to common questions about this code of conduct, see the FAQ at
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@ -6,7 +6,6 @@ Try to follow the existing formatting.
If there is no existing formatting, you may use `clang-format` with our included `.clang-format` configuration.
In general, in order of importance:
- Make sure your IDE is not messing up line endings or whitespace and avoid using linters.
- Prefer readability over dogma.
- Keep to the existing formatting.
@ -27,37 +26,37 @@ Signed-off-by: Author name <Author email>
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
have the right to submit it under the open source license
indicated in the file; or
(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
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
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.
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-vigilant-mode]: https://docs.github.com/en/authentication/managing-commit-signature-verification/displaying-verification-statuses-for-all-of-your-commits

View File

@ -1,38 +1,4 @@
## 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
PolyMC - Minecraft Launcher
Copyright (C) 2021-2022 PolyMC Contributors
@ -66,7 +32,7 @@
See the License for the specific language governing permissions and
limitations under the License.
## MinGW-w64 runtime (Windows)
# MinGW-w64 runtime (Windows)
Copyright (c) 2009, 2010, 2011, 2012, 2013 by the mingw-w64 project
@ -108,14 +74,14 @@
Information on third party licenses used in MinGW-w64 can be found in its COPYING.MinGW-w64-runtime.txt.
## Qt 5/6
# Qt 5/6
Copyright (C) 2022 The Qt Company Ltd and other contributors.
Contact: https://www.qt.io/licensing
Licensed under LGPL v3
## libnbt++
# libnbt++
libnbt++ - A library for the Minecraft Named Binary Tag format.
Copyright (C) 2013, 2015 ljfa-ag
@ -133,7 +99,7 @@
You should have received a copy of the GNU Lesser General Public License
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 Olaf Schmidt <ojschmidt@kde.org>
@ -156,7 +122,7 @@
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
## Hoedown
# Hoedown
Copyright (c) 2008, Natacha Porté
Copyright (c) 2011, Vicent Martí
@ -174,7 +140,7 @@
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
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")
in any personal, open-source or commercial work without obligation of payment
@ -190,7 +156,7 @@
PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THE USE OF THE ICONS,
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/),
with Reserved Font Name Material Design Icons.
@ -201,7 +167,7 @@
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
## Quazip
# Quazip
Copyright (C) 2005-2021 Sergey A. Tachenov
@ -225,7 +191,53 @@
See COPYING file for the full LGPL text.
## launcher (`libraries/launcher`)
# xz-minidec
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
Copyright (C) 2021-2022 PolyMC Contributors
@ -276,7 +288,7 @@
See the License for the specific language governing permissions and
limitations under the License.
## lionshead
# lionshead
Code has been taken from https://github.com/natefoo/lionshead and loosely
translated to C++ laced with Qt.
@ -303,26 +315,32 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## tomlplusplus
# tomlc99
MIT License
Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
Copyright (c) 2017 CK Tan
https://github.com/cktan/tomlc99
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:
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 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.
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.
## O2 (Katabasis fork)
# O2 (Katabasis fork)
Copyright (c) 2012, Akos Polster
All rights reserved.
@ -376,25 +394,3 @@
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.

View File

@ -1,29 +1,98 @@
<p align="center">
<img src="./program_info/org.prismlauncher.PrismLauncher.logo.svg#gh-light-mode-only" alt="Prism Launcher logo" width="50%"/>
<img src="./program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg#gh-dark-mode-only" alt="Prism Launcher logo" width="50%"/>
<p align="center">
<img src="./program_info/polymc-header-black.svg#gh-light-mode-only" alt="PolyMC logo" width="50%"/>
<img src="./program_info/polymc-header.svg#gh-dark-mode-only" alt="PolyMC logo" width="50%"/>
</p>
Prism Launcher is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.
PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity.
We are working on a website and other media, for more info we have a [Discord server](https://discord.gg/prismlauncher).
This is a **fork** of the MultiMC Launcher and not endorsed by MultiMC.
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 Prism Launcher will soon be available.
- Last build status can be found [here](https://github.com/PrismLauncher/PrismLauncher/actions).
- All downloads and instructions for PolyMC can be found [here](https://polymc.org/download/)
- Last build status: https://github.com/PolyMC/PolyMC/actions
### 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.
## Development Builds
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.
## Help & Support
For Debian and Arch, you can use these packages for the latest development versions:
[![polymc-git](https://img.shields.io/badge/aur-polymc--git-blue)](https://aur.archlinux.org/packages/polymc-git/)
[![polymc-git](https://img.shields.io/badge/mpr-polymc--git-orange)](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
[![Join the Discord Server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner3)](https://discord.gg/hX4g537UNE)
Feel free to create an issue if you need help. However, you might find it easier to ask in the Discord server.
[![PolyMC Discord](https://img.shields.io/discord/923671181020766230?label=PolyMC%20Discord)](https://discord.gg/xq7fxrgtMP)
## License
For people who don't want to use Discord, we have a Matrix Space which is bridged to the Discord server:
[![PolyMC Space](https://img.shields.io/matrix/polymc:matrix.org?label=PolyMC%20space)](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:
[![Development](https://img.shields.io/matrix/polymc-development:matrix.org?label=PolyMC%20Development)](https://matrix.to/#/#polymc-development:matrix.org)
[![Discussion](https://img.shields.io/matrix/polymc-discussion:matrix.org?label=PolyMC%20Discussion)](https://matrix.to/#/#polymc-discussion:matrix.org)
[![Github](https://img.shields.io/matrix/polymc-github:matrix.org?label=PolyMC%20Github)](https://matrix.to/#/#polymc-github:matrix.org)
[![Maintainers](https://img.shields.io/matrix/polymc-maintainers:matrix.org?label=PolyMC%20Maintainers)](https://matrix.to/#/#polymc-maintainers:matrix.org)
[![News](https://img.shields.io/matrix/polymc-news:matrix.org?label=PolyMC%20News)](https://matrix.to/#/#polymc-news:matrix.org)
[![Offtopic](https://img.shields.io/matrix/polymc-offtopic:matrix.org?label=PolyMC%20Offtopic)](https://matrix.to/#/#polymc-offtopic:matrix.org)
[![Support](https://img.shields.io/matrix/polymc-support:matrix.org?label=PolyMC%20Support)](https://matrix.to/#/#polymc-support:matrix.org)
[![Voice](https://img.shields.io/matrix/polymc-voice:matrix.org?label=PolyMC%20Voice)](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.
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).
[![OpenCollective Backers](https://opencollective.com/polymc/backers.svg?width=890&limit=1000)](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/).
[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](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>

View File

@ -42,14 +42,12 @@ Config::Config()
{
// Name and copyright
LAUNCHER_NAME = "@Launcher_Name@";
LAUNCHER_APP_BINARY_NAME = "@Launcher_APP_BINARY_NAME@";
LAUNCHER_DISPLAYNAME = "@Launcher_DisplayName@";
LAUNCHER_COPYRIGHT = "@Launcher_Copyright@";
LAUNCHER_DOMAIN = "@Launcher_Domain@";
LAUNCHER_CONFIGFILE = "@Launcher_ConfigFile@";
LAUNCHER_GIT = "@Launcher_Git@";
LAUNCHER_DESKTOPFILENAME = "@Launcher_DesktopFileName@";
LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@";
USER_AGENT = "@Launcher_UserAgent@";
USER_AGENT_UNCACHED = USER_AGENT + " (Uncached)";
@ -57,9 +55,10 @@ Config::Config()
// Version information
VERSION_MAJOR = @Launcher_VERSION_MAJOR@;
VERSION_MINOR = @Launcher_VERSION_MINOR@;
VERSION_HOTFIX = @Launcher_VERSION_HOTFIX@;
VERSION_BUILD = @Launcher_VERSION_BUILD@;
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
BUILD_DATE = "@Launcher_BUILD_TIMESTAMP@";
UPDATER_BASE = "@Launcher_UPDATER_BASE@";
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
@ -86,7 +85,7 @@ Config::Config()
{
VERSION_CHANNEL = GIT_REFSPEC;
VERSION_CHANNEL.remove("refs/heads/");
if(!UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty()) {
if(!UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty() && VERSION_BUILD >= 0) {
UPDATER_ENABLED = true;
}
}
@ -99,6 +98,7 @@ Config::Config()
VERSION_CHANNEL = "unknown";
}
VERSION_STR = "@Launcher_VERSION_STRING@";
NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@";
NEWS_OPEN_URL = "@Launcher_NEWS_OPEN_URL@";
HELP_URL = "@Launcher_HELP_URL@";
@ -116,7 +116,7 @@ Config::Config()
QString Config::versionString() const
{
return QString("%1.%2").arg(VERSION_MAJOR).arg(VERSION_MINOR);
return QString("%1.%2.%3").arg(VERSION_MAJOR).arg(VERSION_MINOR).arg(VERSION_HOTFIX);
}
QString Config::printableVersionString() const
@ -128,5 +128,11 @@ QString Config::printableVersionString() const
{
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;
}

View File

@ -44,19 +44,21 @@ class Config {
public:
Config();
QString LAUNCHER_NAME;
QString LAUNCHER_APP_BINARY_NAME;
QString LAUNCHER_DISPLAYNAME;
QString LAUNCHER_COPYRIGHT;
QString LAUNCHER_DOMAIN;
QString LAUNCHER_CONFIGFILE;
QString LAUNCHER_GIT;
QString LAUNCHER_DESKTOPFILENAME;
QString LAUNCHER_SVGFILENAME;
/// The major version number.
int VERSION_MAJOR;
/// The minor version number.
int VERSION_MINOR;
/// The hotfix number.
int VERSION_HOTFIX;
/// The build number.
int VERSION_BUILD;
/**
* The version channel
@ -69,9 +71,6 @@ class Config {
/// A short string identifying this build's platform. For example, "lin64" or "win32".
QString BUILD_PLATFORM;
/// A string containing the build timestamp
QString BUILD_DATE;
/// URL for the updater's channel
QString UPDATER_BASE;
@ -96,6 +95,9 @@ class Config {
/// The git refspec of this build
QString GIT_REFSPEC;
/// This is printed on start to standard output
QString VERSION_STR;
/**
* This is used to fetch the news RSS feed.
* It defaults in CMakeLists.txt to "https://multimc.org/rss.xml"
@ -142,8 +144,8 @@ class Config {
QString LIBRARY_BASE = "https://libraries.minecraft.net/";
QString AUTH_BASE = "https://authserver.mojang.com/";
QString IMGUR_BASE_URL = "https://api.imgur.com/3/";
QString FMLLIBS_BASE_URL = "https://files.prismlauncher.org/fmllibs/"; // FIXME: move into CMakeLists
QString TRANSLATIONS_BASE_URL = "https://i18n.prismlauncher.org/"; // FIXME: move into CMakeLists
QString FMLLIBS_BASE_URL = "https://files.polymc.org/fmllibs/";
QString TRANSLATIONS_BASE_URL = "https://i18n.polymc.org/";
QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/";

29
flake.lock generated
View File

@ -21,24 +21,24 @@
"locked": {
"lastModified": 1650031308,
"narHash": "sha256-TvVOjkUobYJD9itQYueELJX3wmecvEdCbJ0FinW2mL4=",
"owner": "PrismLauncher",
"owner": "PolyMC",
"repo": "libnbtplusplus",
"rev": "2203af7eeb48c45398139b583615134efd8d407f",
"type": "github"
},
"original": {
"owner": "PrismLauncher",
"owner": "PolyMC",
"repo": "libnbtplusplus",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1666057921,
"narHash": "sha256-VpQqtXdj6G7cH//SvoprjR7XT3KS7p+tCVebGK1N6tE=",
"lastModified": 1658119717,
"narHash": "sha256-4upOZIQQ7Bc4CprqnHsKnqYfw+arJeAuU+QcpjYBXW0=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "88eab1e431cabd0ed621428d8b40d425a07af39f",
"rev": "9eb60f25aff0d2218c848dd4574a0ab5e296cabe",
"type": "github"
},
"original": {
@ -52,24 +52,7 @@
"inputs": {
"flake-compat": "flake-compat",
"libnbtplusplus": "libnbtplusplus",
"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"
"nixpkgs": "nixpkgs"
}
}
},

View File

@ -4,11 +4,10 @@
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
libnbtplusplus = { url = "github:PrismLauncher/libnbtplusplus"; flake = false; };
tomlplusplus = { url = "github:marzer/tomlplusplus"; flake = false; };
libnbtplusplus = { url = "github:PolyMC/libnbtplusplus"; flake = false; };
};
outputs = { self, nixpkgs, libnbtplusplus, tomlplusplus, ... }:
outputs = { self, nixpkgs, libnbtplusplus, ... }:
let
# User-friendly version number.
version = builtins.substring 0 8 self.lastModifiedDate;
@ -23,14 +22,14 @@
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
packagesFn = pkgs: rec {
prismlauncher = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
prismlauncher-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
polymc = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; };
polymc-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; };
};
in
{
packages = forAllSystems (system:
let packages = packagesFn pkgs.${system}; in
packages // { default = packages.prismlauncher; }
packages // { default = packages.polymc; }
);
overlay = final: packagesFn;

View File

@ -60,11 +60,6 @@
#include "ui/themes/BrightTheme.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/LanguageWizardPage.h"
#include "ui/setupwizard/JavaWizardPage.h"
@ -79,7 +74,6 @@
#include <iostream>
#include <QAccessible>
#include <QCommandLineParser>
#include <QDir>
#include <QFileInfo>
#include <QNetworkAccessManager>
@ -112,17 +106,13 @@
#include "translations/TranslationsModel.h"
#include "meta/Index.h"
#include <Commandline.h>
#include <FileSystem.h>
#include <DesktopServices.h>
#include <LocalPeer.h>
#include <sys.h>
#ifdef Q_OS_LINUX
#include <dlfcn.h>
#include "gamemode_client.h"
#endif
#if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN
@ -137,6 +127,12 @@
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 {
void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
@ -237,27 +233,80 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
this->setQuitOnLastWindowClosed(false);
// Commandline parsing
QCommandLineParser parser;
parser.setApplicationDescription(BuildConfig.LAUNCHER_DISPLAYNAME);
QHash<QString, QVariant> args;
{
Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals);
parser.addOptions({
{{"d", "dir"}, "Use a custom path as application root (use '.' for current directory)", "directory"},
{{"l", "launch"}, "Launch the specified instance (by instance ID)", "instance"},
{{"s", "server"}, "Join the specified server on launch (only valid in combination with --launch)", "address"},
{{"a", "profile"}, "Use the account specified by its profile name (only valid in combination with --launch)", "profile"},
{"alive", "Write a small '" + liveCheckFile + "' file after the launcher starts"},
{{"I", "import"}, "Import instance from specified zip (local path or URL)", "file"}
});
parser.addHelpOption();
parser.addVersionOption();
// --help
parser.addSwitch("help");
parser.addShortOpt("help", 'h');
parser.addDocumentation("help", "Display this help and exit.");
// --version
parser.addSwitch("version");
parser.addShortOpt("version", 'V');
parser.addDocumentation("version", "Display program version and exit.");
// --dir
parser.addOption("dir");
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)");
parser.process(arguments());
// parse the 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;
}
m_instanceIdToLaunch = parser.value("launch");
m_serverToJoin = parser.value("server");
m_profileToUse = parser.value("profile");
m_liveCheck = parser.isSet("alive");
m_zipToImport = parser.value("import");
// display help and exit
if (args["help"].toBool())
{
std::cout << qPrintable(parser.compileHelp(arguments()[0]));
m_status = Application::Succeeded;
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
if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty())
@ -288,7 +337,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
QString adjustedBy;
QString dataPath;
// change folder
QString dirParam = parser.value("dir");
QString dirParam = args["dir"].toString();
if (!dirParam.isEmpty())
{
// the dir param. it makes multimc data path point to whatever the user specified
@ -302,12 +351,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
dataPath = foo.absolutePath();
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
// TODO: this should be removed in a future version
// TODO: provide a migration path similar to macOS migration
@ -333,6 +376,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
QString(
"The launcher data folder could not be created.\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"
"(%1)\n"
"\n"
@ -348,6 +394,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
QString(
"The launcher data folder could not be opened.\n"
"\n"
#if defined(Q_OS_MAC)
MACOS_HINT
#endif
"Make sure you have the right permissions to the launcher data folder.\n"
"(%1)\n"
"\n"
@ -428,6 +477,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
QString(
"The launcher couldn't create a log file - the data folder is not writable.\n"
"\n"
#if defined(Q_OS_MAC)
MACOS_HINT
#endif
"Make sure you have write permissions to the data folder.\n"
"(%1)\n"
"\n"
@ -489,8 +541,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Initialize application settings
{
// Provide a fallback for migration from PolyMC
m_settings.reset(new INISettingsObject({ BuildConfig.LAUNCHER_CONFIGFILE, "polymc.cfg", "multimc.cfg" }, this));
m_settings.reset(new INISettingsObject(BuildConfig.LAUNCHER_CONFIGFILE, this));
// Updates
// Multiple channels are separated by spaces
m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL);
@ -629,8 +680,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("UpdateDialogGeometry", "");
m_settings->registerSetting("ModDownloadGeometry", "");
// HACK: This code feels so stupid is there a less stupid way of doing this?
{
m_settings->registerSetting("PastebinURL", "");
@ -725,7 +774,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
auto platform = getIdealPlatform(BuildConfig.BUILD_PLATFORM);
auto channelUrl = BuildConfig.UPDATER_BASE + platform + "/channels.json";
qDebug() << "Initializing updater with platform: " << platform << " -- " << channelUrl;
m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL));
m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD));
qDebug() << "<> Updater started.";
}
@ -817,7 +866,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
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("ModrinthModpacks", QDir("cache/ModrinthModpacks").absolutePath());
m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
@ -868,13 +916,10 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Application theme set.";
}
updateCapabilities();
if(createSetupWizard())
{
return;
}
performMainStartupAction();
}
@ -983,7 +1028,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse;
}
launch(inst, true, false, nullptr, serverToJoin, accountToUse);
launch(inst, true, nullptr, serverToJoin, accountToUse);
return;
}
}
@ -1087,7 +1132,6 @@ void Application::messageReceived(const QByteArray& message)
launch(
instance,
true,
false,
nullptr,
serverObject,
accountObject
@ -1125,6 +1169,15 @@ std::vector<ITheme *> Application::getValidApplicationThemes()
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)
{
auto systemPalette = qApp->palette();
@ -1133,15 +1186,6 @@ void Application::setApplicationTheme(const QString& name, bool initial)
{
auto & theme = (*themeIter).second;
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
{
@ -1157,7 +1201,7 @@ void Application::setIconTheme(const QString& name)
QIcon Application::getThemedIcon(const QString& name)
{
if(name == "logo") {
return QIcon(":/" + BuildConfig.LAUNCHER_SVGFILENAME);
return QIcon(":/org.polymc.PolyMC.svg");
}
return QIcon::fromTheme(name);
}
@ -1179,7 +1223,6 @@ bool Application::openJsonEditor(const QString &filename)
bool Application::launch(
InstancePtr instance,
bool online,
bool demo,
BaseProfilerFactory *profiler,
MinecraftServerTargetPtr serverToJoin,
MinecraftAccountPtr accountToUse
@ -1203,7 +1246,6 @@ bool Application::launch(
controller.reset(new LaunchController());
controller->setInstance(instance);
controller->setOnline(online);
controller->setDemo(demo);
controller->setProfiler(profiler);
controller->setServerToJoin(serverToJoin);
controller->setAccountToUse(accountToUse);
@ -1374,16 +1416,6 @@ MainWindow* Application::showMainWindow(bool minimized)
m_mainWindow = new MainWindow();
m_mainWindow->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").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)
{
m_mainWindow->showMinimized();
@ -1536,37 +1568,21 @@ shared_qobject_ptr<Meta::Index> Application::metadataIndex()
return m_metadataIndex;
}
void Application::updateCapabilities()
Application::Capabilities Application::currentCapabilities()
{
m_capabilities = None;
Capabilities c;
if (!getMSAClientID().isEmpty())
m_capabilities |= SupportsMSA;
c |= SupportsMSA;
if (!getFlameAPIKey().isEmpty())
m_capabilities |= SupportsFlame;
#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
c |= SupportsFlame;
return c;
}
QString Application::getJarPath(QString jarFile)
{
QStringList potentialPaths = {
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
FS::PathCombine(m_rootPath, "share/" + BuildConfig.LAUNCHER_APP_BINARY_NAME),
FS::PathCombine(m_rootPath, "share/jars"),
#endif
FS::PathCombine(m_rootPath, "jars"),
FS::PathCombine(applicationDirPath(), "jars")

View File

@ -95,8 +95,6 @@ public:
SupportsMSA = 1 << 0,
SupportsFlame = 1 << 1,
SupportsGameMode = 1 << 2,
SupportsMangoHud = 1 << 3,
};
Q_DECLARE_FLAGS(Capabilities, Capability)
@ -116,6 +114,8 @@ public:
QIcon getThemedIcon(const QString& name);
bool isFlatpak();
void setIconTheme(const QString& name);
std::vector<ITheme *> getValidApplicationThemes();
@ -162,7 +162,7 @@ public:
shared_qobject_ptr<Meta::Index> metadataIndex();
void updateCapabilities();
Capabilities currentCapabilities();
/*!
* Finds and returns the full path to a jar file.
@ -180,10 +180,6 @@ public:
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
* specified in the settings
@ -211,7 +207,6 @@ public slots:
bool launch(
InstancePtr instance,
bool online = true,
bool demo = false,
BaseProfilerFactory *profiler = nullptr,
MinecraftServerTargetPtr serverToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr
@ -263,7 +258,6 @@ private:
QString m_rootPath;
Status m_status = Application::StartingUp;
Capabilities m_capabilities;
#ifdef Q_OS_MACOS
Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;

View File

@ -17,14 +17,13 @@
#include <memory>
#include "BaseVersion.h"
class MinecraftInstance;
class QDir;
class QString;
class QObject;
class Task;
class BaseVersion;
typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
class BaseInstaller
{
@ -36,7 +35,7 @@ public:
virtual bool add(MinecraftInstance *to);
virtual bool remove(MinecraftInstance *from);
virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersion::Ptr version, QObject *parent) = 0;
virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersionPtr version, QObject *parent) = 0;
protected:
virtual QString id() const = 0;

View File

@ -53,22 +53,15 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
: QObject()
{
m_settings = settings;
m_global_settings = globalSettings;
m_rootDir = rootDir;
m_settings->registerSetting("name", "Unnamed Instance");
m_settings->registerSetting("iconKey", "default");
m_settings->registerSetting("notes", "");
m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 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
// a locally stored instance
if (!m_settings->getSetting("InstanceType"))
@ -114,59 +107,49 @@ QString BaseInstance::getPostExitCommand()
return settings()->get("PostExitCommand").toString();
}
bool BaseInstance::isManagedPack() const
bool BaseInstance::isManagedPack()
{
return m_settings->get("ManagedPack").toBool();
return settings()->get("ManagedPack").toBool();
}
QString BaseInstance::getManagedPackType() const
QString BaseInstance::getManagedPackType()
{
return m_settings->get("ManagedPackType").toString();
return settings()->get("ManagedPackType").toString();
}
QString BaseInstance::getManagedPackID() const
QString BaseInstance::getManagedPackID()
{
return m_settings->get("ManagedPackID").toString();
return settings()->get("ManagedPackID").toString();
}
QString BaseInstance::getManagedPackName() const
QString BaseInstance::getManagedPackName()
{
return m_settings->get("ManagedPackName").toString();
return settings()->get("ManagedPackName").toString();
}
QString BaseInstance::getManagedPackVersionID() const
QString BaseInstance::getManagedPackVersionID()
{
return m_settings->get("ManagedPackVersionID").toString();
return settings()->get("ManagedPackVersionID").toString();
}
QString BaseInstance::getManagedPackVersionName() const
QString BaseInstance::getManagedPackVersionName()
{
return m_settings->get("ManagedPackVersionName").toString();
return settings()->get("ManagedPackVersionName").toString();
}
void BaseInstance::setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version)
{
m_settings->set("ManagedPack", true);
m_settings->set("ManagedPackType", type);
m_settings->set("ManagedPackID", id);
m_settings->set("ManagedPackName", name);
m_settings->set("ManagedPackVersionID", versionId);
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());
settings()->set("ManagedPack", true);
settings()->set("ManagedPackType", type);
settings()->set("ManagedPackID", id);
settings()->set("ManagedPackName", name);
settings()->set("ManagedPackVersionID", versionId);
settings()->set("ManagedPackVersionName", version);
}
int BaseInstance::getConsoleMaxLines() const
{
auto lineSetting = m_settings->getSetting("ConsoleMaxLines");
auto lineSetting = settings()->getSetting("ConsoleMaxLines");
bool conversionOk = false;
int maxLines = lineSetting->get().toInt(&conversionOk);
if(!conversionOk)
@ -179,7 +162,7 @@ int BaseInstance::getConsoleMaxLines() const
bool BaseInstance::shouldStopOnConsoleOverflow() const
{
return m_settings->get("ConsoleOverflowStop").toBool();
return settings()->get("ConsoleOverflowStop").toBool();
}
void BaseInstance::iconUpdated(QString key)
@ -254,7 +237,7 @@ void BaseInstance::setRunning(bool running)
int64_t BaseInstance::totalTimePlayed() const
{
qint64 current = m_settings->get("totalTimePlayed").toLongLong();
qint64 current = settings()->get("totalTimePlayed").toLongLong();
if(m_isRunning)
{
QDateTime timeNow = QDateTime::currentDateTime();
@ -270,7 +253,7 @@ int64_t BaseInstance::lastTimePlayed() const
QDateTime timeNow = QDateTime::currentDateTime();
return m_timeStarted.secsTo(timeNow);
}
return m_settings->get("lastTimePlayed").toLongLong();
return settings()->get("lastTimePlayed").toLongLong();
}
void BaseInstance::resetTimePlayed()
@ -289,10 +272,8 @@ QString BaseInstance::instanceRoot() const
return m_rootDir;
}
SettingsObjectPtr BaseInstance::settings()
SettingsObjectPtr BaseInstance::settings() const
{
loadSpecificSettings();
return m_settings;
}
@ -355,11 +336,11 @@ QString BaseInstance::name() const
QString BaseInstance::windowTitle() const
{
return BuildConfig.LAUNCHER_DISPLAYNAME + ": " + name().replace(QRegularExpression("\\s+"), " ");
return BuildConfig.LAUNCHER_NAME + ": " + name().replace(QRegularExpression("\\s+"), " ");
}
// FIXME: why is this here? move it to MinecraftInstance!!!
QStringList BaseInstance::extraArguments()
QStringList BaseInstance::extraArguments() const
{
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
}
@ -368,8 +349,3 @@ shared_qobject_ptr<LaunchTask> BaseInstance::getLaunchTask()
{
return m_launchProcess;
}
void BaseInstance::updateRuntimeContext()
{
// NOOP
}

View File

@ -54,7 +54,6 @@
#include "net/Mode.h"
#include "minecraft/launch/MinecraftServerTarget.h"
#include "RuntimeContext.h"
class QDir;
class Task;
@ -141,14 +140,13 @@ public:
QString getPostExitCommand();
QString getWrapperCommand();
bool isManagedPack() const;
QString getManagedPackType() const;
QString getManagedPackID() const;
QString getManagedPackName() const;
QString getManagedPackVersionID() const;
QString getManagedPackVersionName() const;
bool isManagedPack();
QString getManagedPackType();
QString getManagedPackID();
QString getManagedPackName();
QString getManagedPackVersionID();
QString getManagedPackVersionName();
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
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
@ -156,7 +154,7 @@ public:
return level;
};
virtual QStringList extraArguments();
virtual QStringList extraArguments() const;
/// Traits. Normally inside the version, depends on instance implementation.
virtual QSet <QString> traits() const = 0;
@ -172,18 +170,9 @@ public:
/*!
* \brief Gets this instance's settings object.
* 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.
*/
virtual SettingsObjectPtr settings();
/*!
* \brief Loads settings specific to an instance type if they're not already loaded.
*/
virtual void loadSpecificSettings() = 0;
virtual SettingsObjectPtr settings() const;
/// returns a valid update task
virtual Task::Ptr createUpdateTask(Net::Mode mode) = 0;
@ -217,16 +206,10 @@ public:
virtual QString instanceConfigFolder() const = 0;
/// get variables this instance exports
virtual QMap<QString, QString> getVariables() = 0;
virtual QMap<QString, QString> getVariables() const = 0;
virtual QString typeName() const = 0;
void updateRuntimeContext();
RuntimeContext runtimeContext() const
{
return m_runtimeContext;
}
bool hasVersionBroken() const
{
return m_hasBrokenVersion;
@ -285,11 +268,6 @@ public:
protected:
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:
/*!
* \brief Signal emitted when properties relevant to the instance view change
@ -312,17 +290,12 @@ protected: /* data */
bool m_isRunning = false;
shared_qobject_ptr<LaunchTask> m_launchProcess;
QDateTime m_timeStarted;
RuntimeContext m_runtimeContext;
private: /* data */
Status m_status = Status::Present;
bool m_crashed = false;
bool m_hasUpdate = false;
bool m_hasBrokenVersion = false;
SettingsObjectWeakPtr m_global_settings;
bool m_specific_settings_loaded = false;
};
Q_DECLARE_METATYPE(shared_qobject_ptr<BaseInstance>)

View File

@ -25,7 +25,6 @@
class BaseVersion
{
public:
using Ptr = std::shared_ptr<BaseVersion>;
virtual ~BaseVersion() {}
/*!
* A string used to identify this version in config files.
@ -55,4 +54,6 @@ public:
};
};
Q_DECLARE_METATYPE(BaseVersion::Ptr)
typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
Q_DECLARE_METATYPE(BaseVersionPtr)

View File

@ -40,20 +40,20 @@ BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent)
{
}
BaseVersion::Ptr BaseVersionList::findVersion(const QString &descriptor)
BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor)
{
for (int i = 0; i < count(); i++)
{
if (at(i)->descriptor() == descriptor)
return at(i);
}
return nullptr;
return BaseVersionPtr();
}
BaseVersion::Ptr BaseVersionList::getRecommended() const
BaseVersionPtr BaseVersionList::getRecommended() const
{
if (count() <= 0)
return nullptr;
return BaseVersionPtr();
else
return at(0);
}
@ -66,7 +66,7 @@ QVariant BaseVersionList::data(const QModelIndex &index, int role) const
if (index.row() > count())
return QVariant();
BaseVersion::Ptr version = at(index.row());
BaseVersionPtr version = at(index.row());
switch (role)
{

View File

@ -70,7 +70,7 @@ public:
virtual bool isLoaded() = 0;
//! Gets the version at the given index.
virtual const BaseVersion::Ptr at(int i) const = 0;
virtual const BaseVersionPtr at(int i) const = 0;
//! Returns the number of versions in the list.
virtual int count() const = 0;
@ -90,13 +90,13 @@ public:
* \return A const pointer to the version with the given descriptor. NULL if
* one doesn't exist.
*/
virtual BaseVersion::Ptr findVersion(const QString &descriptor);
virtual BaseVersionPtr findVersion(const QString &descriptor);
/*!
* \brief Gets the recommended version from this list
* If the list doesn't support recommended versions, this works exactly as getLatestStable
*/
virtual BaseVersion::Ptr getRecommended() const;
virtual BaseVersionPtr getRecommended() const;
/*!
* Sorts the version list.
@ -117,5 +117,5 @@ slots:
* then copies the versions and sets their parents correctly.
* \param versions List of versions whose parents should be set.
*/
virtual void updateListData(QList<BaseVersion::Ptr> versions) = 0;
virtual void updateListData(QList<BaseVersionPtr> versions) = 0;
};

View File

@ -24,9 +24,8 @@ set(CORE_SOURCES
NullInstance.h
MMCZip.h
MMCZip.cpp
StringUtils.h
StringUtils.cpp
RuntimeContext.h
MMCStrings.h
MMCStrings.cpp
# Basic instance manipulation tasks (derived from InstanceTask)
InstanceCreationTask.h
@ -89,6 +88,12 @@ set(CORE_SOURCES
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
# Path matchers
pathmatcher/FSTreeMatcher.h
@ -289,6 +294,8 @@ set(MINECRAFT_SOURCES
minecraft/Rule.h
minecraft/OneSixVersionFormat.cpp
minecraft/OneSixVersionFormat.h
minecraft/OpSys.cpp
minecraft/OpSys.h
minecraft/ParseUtils.cpp
minecraft/ParseUtils.h
minecraft/ProfileUtils.cpp
@ -296,8 +303,6 @@ set(MINECRAFT_SOURCES
minecraft/Library.cpp
minecraft/Library.h
minecraft/MojangDownloadInfo.h
minecraft/VanillaInstanceCreationTask.cpp
minecraft/VanillaInstanceCreationTask.h
minecraft/VersionFile.cpp
minecraft/VersionFile.h
minecraft/VersionFilterData.h
@ -313,30 +318,16 @@ set(MINECRAFT_SOURCES
minecraft/mod/ModDetails.h
minecraft/mod/ModFolderModel.h
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.cpp
minecraft/mod/TexturePack.h
minecraft/mod/TexturePack.cpp
minecraft/mod/TexturePackFolderModel.h
minecraft/mod/TexturePackFolderModel.cpp
minecraft/mod/ShaderPackFolderModel.h
minecraft/mod/tasks/BasicFolderLoadTask.h
minecraft/mod/tasks/ModFolderLoadTask.h
minecraft/mod/tasks/ModFolderLoadTask.cpp
minecraft/mod/tasks/LocalModParseTask.h
minecraft/mod/tasks/LocalModParseTask.cpp
minecraft/mod/tasks/LocalModUpdateTask.h
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
minecraft/AssetsUtils.h
@ -354,6 +345,42 @@ set(MINECRAFT_SOURCES
mojang/PackageManifest.cpp
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
set(SCREENSHOTS_SOURCES
screenshots/Screenshot.h
@ -375,6 +402,9 @@ set(TASKS_SOURCES
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
# Settings
settings/INIFile.cpp
@ -391,6 +421,9 @@ set(SETTINGS_SOURCES
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
java/JavaChecker.h
java/JavaChecker.cpp
@ -406,6 +439,9 @@ set(JAVA_SOURCES
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
translations/TranslationsModel.h
translations/TranslationsModel.cpp
@ -460,8 +496,6 @@ set(API_SOURCES
modplatform/helpers/NetworkModAPI.cpp
modplatform/helpers/HashUtils.h
modplatform/helpers/HashUtils.cpp
modplatform/helpers/OverrideUtils.h
modplatform/helpers/OverrideUtils.cpp
)
set(FTB_SOURCES
@ -487,8 +521,6 @@ set(FLAME_SOURCES
modplatform/flame/FileResolvingTask.cpp
modplatform/flame/FlameCheckUpdate.cpp
modplatform/flame/FlameCheckUpdate.h
modplatform/flame/FlameInstanceCreationTask.h
modplatform/flame/FlameInstanceCreationTask.cpp
)
set(MODRINTH_SOURCES
@ -498,8 +530,6 @@ set(MODRINTH_SOURCES
modplatform/modrinth/ModrinthPackManifest.h
modplatform/modrinth/ModrinthCheckUpdate.cpp
modplatform/modrinth/ModrinthCheckUpdate.h
modplatform/modrinth/ModrinthInstanceCreationTask.cpp
modplatform/modrinth/ModrinthInstanceCreationTask.h
)
set(MODPACKSCH_SOURCES
@ -514,6 +544,9 @@ set(PACKWIZ_SOURCES
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
modplatform/technic/SingleZipPackInstallTask.h
@ -537,6 +570,9 @@ set(ATLAUNCHER_SOURCES
modplatform/atlauncher/ATLShareCode.h
)
ecm_add_test(meta/Index_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME Index)
################################ COMPILE ################################
# we need zlib
@ -730,8 +766,6 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
ui/pages/modplatform/atlauncher/AtlPage.cpp
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.h
@ -817,8 +851,6 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ModDownloadDialog.h
ui/dialogs/ScrollMessageBox.cpp
ui/dialogs/ScrollMessageBox.h
ui/dialogs/BlockedModsDialog.cpp
ui/dialogs/BlockedModsDialog.h
ui/dialogs/ChooseProviderDialog.h
ui/dialogs/ChooseProviderDialog.cpp
ui/dialogs/ModUpdateDialog.cpp
@ -845,8 +877,8 @@ SET(LAUNCHER_SOURCES
ui/widgets/LineSeparator.h
ui/widgets/LogView.cpp
ui/widgets/LogView.h
ui/widgets/InfoFrame.cpp
ui/widgets/InfoFrame.h
ui/widgets/MCModInfoFrame.cpp
ui/widgets/MCModInfoFrame.h
ui/widgets/ModFilterWidget.cpp
ui/widgets/ModFilterWidget.h
ui/widgets/ModListView.cpp
@ -854,12 +886,6 @@ SET(LAUNCHER_SOURCES
ui/widgets/PageContainer.cpp
ui/widgets/PageContainer.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.h
ui/widgets/VersionSelectWidget.cpp
@ -883,16 +909,6 @@ SET(LAUNCHER_SOURCES
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
ui/setupwizard/PasteWizardPage.ui
ui/pages/global/AccountListPage.ui
@ -924,7 +940,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/modplatform/technic/TechnicPage.ui
ui/widgets/InstanceCardWidget.ui
ui/widgets/CustomCommands.ui
ui/widgets/InfoFrame.ui
ui/widgets/MCModInfoFrame.ui
ui/widgets/ModFilterWidget.ui
ui/dialogs/CopyInstanceDialog.ui
ui/dialogs/ProfileSetupDialog.ui
@ -944,7 +960,6 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/EditAccountDialog.ui
ui/dialogs/ReviewMessageBox.ui
ui/dialogs/ScrollMessageBox.ui
ui/dialogs/BlockedModsDialog.ui
ui/dialogs/ChooseProviderDialog.ui
)
@ -969,17 +984,16 @@ endif()
# Add executable
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
systeminfo
Launcher_classparser
Launcher_murmur2
nbt++
${ZLIB_LIBRARIES}
tomlplusplus::tomlplusplus
tomlc99
BuildConfig
Katabasis
Qt${QT_VERSION_MAJOR}::Widgets
ghcFilesystem::ghc_filesystem
)
if (UNIX AND NOT CYGWIN AND NOT APPLE)

View File

@ -92,4 +92,412 @@ QStringList splitArgs(QString args)
argv << current;
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())
{
}
}

View File

@ -17,7 +17,12 @@
#pragma once
#include <exception>
#include <stdexcept>
#include <QString>
#include <QVariant>
#include <QHash>
#include <QStringList>
/**
@ -34,4 +39,212 @@ namespace Commandline
* @return a QStringList containing all arguments
*/
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);
};
}

View File

@ -119,7 +119,7 @@ bool openDirectory(const QString &path, bool ensureExists)
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak())
if(!APPLICATION->isFlatpak())
{
return IndirectOpen(f);
}
@ -140,7 +140,7 @@ bool openFile(const QString &path)
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak())
if(!APPLICATION->isFlatpak())
{
return IndirectOpen(f);
}
@ -158,7 +158,7 @@ bool openFile(const QString &application, const QString &path, const QString &wo
qDebug() << "Opening file" << path << "using" << application;
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
if(!isFlatpak())
if(!APPLICATION->isFlatpak())
{
return IndirectOpen([&]()
{
@ -178,7 +178,7 @@ bool run(const QString &application, const QStringList &args, const QString &wor
{
qDebug() << "Running" << application << "with args" << args.join(' ');
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak())
if(!APPLICATION->isFlatpak())
{
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
return IndirectOpen([&]()
@ -203,7 +203,7 @@ bool openUrl(const QUrl &url)
return QDesktopServices::openUrl(url);
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak())
if(!APPLICATION->isFlatpak())
{
return IndirectOpen(f);
}
@ -216,13 +216,4 @@ bool openUrl(const QUrl &url)
#endif
}
bool isFlatpak()
{
#ifdef Q_OS_LINUX
return QFile::exists("/.flatpak-info");
#else
return false;
#endif
}
}

View File

@ -33,6 +33,4 @@ namespace DesktopServices
* Open the URL, most likely in a browser. Maybe.
*/
bool openUrl(const QUrl &url);
bool isFlatpak();
}

View File

@ -35,87 +35,76 @@
#include "FileSystem.h"
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QSaveFile>
#include <QFileInfo>
#include <QDebug>
#include <QUrl>
#include <QStandardPaths>
#include <QTextStream>
#include <QUrl>
#include "DesktopServices.h"
#include "StringUtils.h"
#if defined Q_OS_WIN32
#include <objbase.h>
#include <objidl.h>
#include <shlguid.h>
#include <shlobj.h>
#include <shobjidl.h>
#include <sys/utime.h>
#include <windows.h>
#include <winnls.h>
#include <string>
#include <windows.h>
#include <string>
#include <sys/utime.h>
#include <winnls.h>
#include <shobjidl.h>
#include <objbase.h>
#include <objidl.h>
#include <shlguid.h>
#include <shlobj.h>
#else
#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;
#include <utime.h>
#endif
namespace FS {
void ensureExists(const QDir& dir)
void ensureExists(const QDir &dir)
{
if (!QDir().mkpath(dir.absolutePath())) {
throw FileSystemException("Unable to create folder " + dir.dirName() + " (" + dir.absolutePath() + ")");
if (!QDir().mkpath(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());
QSaveFile file(filename);
if (!file.open(QSaveFile::WriteOnly)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
if (!file.open(QSaveFile::WriteOnly))
{
throw FileSystemException("Couldn't open " + filename + " for writing: " +
file.errorString());
}
if (data.size() != file.write(data)) {
throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
if (data.size() != file.write(data))
{
throw FileSystemException("Error writing data to " + filename + ": " +
file.errorString());
}
if (!file.commit()) {
throw FileSystemException("Error while committing data to " + filename + ": " + file.errorString());
if (!file.commit())
{
throw FileSystemException("Error while committing data to " + filename + ": " +
file.errorString());
}
}
QByteArray read(const QString& filename)
QByteArray read(const QString &filename)
{
QFile file(filename);
if (!file.open(QFile::ReadOnly)) {
throw FileSystemException("Unable to open " + filename + " for reading: " + file.errorString());
if (!file.open(QFile::ReadOnly))
{
throw FileSystemException("Unable to open " + filename + " for reading: " +
file.errorString());
}
const qint64 size = file.size();
QByteArray data(int(size), 0);
const qint64 ret = file.read(data.data(), size);
if (ret == -1 || ret != size) {
throw FileSystemException("Error reading data from " + filename + ": " + file.errorString());
if (ret == -1 || ret != size)
{
throw FileSystemException("Error reading data from " + filename + ": " +
file.errorString());
}
return data;
}
@ -149,102 +138,149 @@ bool ensureFolderPathExists(QString foldernamepath)
return success;
}
bool copy::operator()(const QString& offset)
bool copy::operator()(const QString &offset)
{
using copy_opts = fs::copy_options;
// NOTE always deep copy on windows. the alternatives are too messy.
#if defined Q_OS_WIN32
//NOTE always deep copy on windows. the alternatives are too messy.
#if defined Q_OS_WIN32
m_followSymlinks = true;
#endif
#endif
auto src = PathCombine(m_src.absolutePath(), offset);
auto dst = PathCombine(m_dst.absolutePath(), offset);
std::error_code err;
QFileInfo currentSrc(src);
if (!currentSrc.exists())
return false;
fs::copy_options opt = copy_opts::none;
// The default behavior is to follow symlinks
if (!m_followSymlinks)
opt |= copy_opts::copy_symlinks;
// 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(StringUtils::toStdString(src_path), StringUtils::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;
if(!m_followSymlinks && currentSrc.isSymLink())
{
qDebug() << "creating symlink" << src << " - " << dst;
if (!ensureFilePathExists(dst))
{
qWarning() << "Cannot create path!";
return false;
}
};
// 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);
return QFile::link(currentSrc.symLinkTarget(), dst);
}
// If the root src is not a directory, the previous iterator won't run.
if (!fs::is_directory(StringUtils::toStdString(src)))
copy_file(src, "");
return err.value() == 0;
else if(currentSrc.isFile())
{
qDebug() << "copying file" << src << " - " << dst;
if (!ensureFilePathExists(dst))
{
qWarning() << "Cannot create path!";
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)
{
std::error_code err;
fs::remove_all(StringUtils::toStdString(path), err);
if (err) {
qWarning() << "Failed to remove files:" << QString::fromStdString(err.message());
bool OK = true;
QFileInfo finfo(path);
if(finfo.isFile()) {
return QFile::remove(path);
}
return err.value() == 0;
}
QDir dir(path);
bool trash(QString path, QString *pathInTrash = nullptr)
{
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
return false;
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
// FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal
if (DesktopServices::isFlatpak())
return false;
return QFile::moveToTrash(path, pathInTrash);
// 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;
}
QString PathCombine(const QString& path1, const QString& path2)
QString PathCombine(const QString & path1, const QString & path2)
{
if (!path1.size())
if(!path1.size())
return path2;
if (!path2.size())
if(!path2.size())
return path1;
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);
}
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);
}
@ -256,14 +292,17 @@ QString AbsolutePath(QString path)
QString ResolveExecutable(QString path)
{
if (path.isEmpty()) {
if (path.isEmpty())
{
return QString();
}
if (!path.contains('/')) {
if(!path.contains('/'))
{
path = QStandardPaths::findExecutable(path);
}
QFileInfo pathInfo(path);
if (!pathInfo.exists() || !pathInfo.isExecutable()) {
if(!pathInfo.exists() || !pathInfo.isExecutable())
{
return QString();
}
return pathInfo.absoluteFilePath();
@ -283,9 +322,12 @@ QString NormalizePath(QString path)
QDir b(path);
QString newAbsolute = b.absolutePath();
if (newAbsolute.startsWith(currentAbsolute)) {
if (newAbsolute.startsWith(currentAbsolute))
{
return a.relativeFilePath(newAbsolute);
} else {
}
else
{
return newAbsolute;
}
}
@ -294,8 +336,10 @@ QString badFilenameChars = "\"\\/?<>:;*|!+\r\n";
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
{
for (int i = 0; i < string.length(); i++) {
if (badFilenameChars.contains(string[i])) {
for (int i = 0; i < string.length(); i++)
{
if (badFilenameChars.contains(string[i]))
{
string[i] = replaceWith;
}
}
@ -307,11 +351,15 @@ QString DirNameFromString(QString string, QString inDir)
int num = 0;
QString baseName = RemoveInvalidFilenameChars(string, '-');
QString dirName;
do {
if (num == 0) {
do
{
if(num == 0)
{
dirName = baseName;
} else {
dirName = baseName + "(" + QString::number(num) + ")";
}
else
{
dirName = baseName + QString::number(num);;
}
// If it's over 9000
@ -330,13 +378,63 @@ bool checkProblemticPathJava(QDir folder)
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()
{
return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
}
// Cross-platform Shortcut creation
bool createShortCut(QString location, QString dest, QStringList args, QString name, QString icon)
bool createShortCut(QString location, QString dest, QStringList args, QString name,
QString icon)
{
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
location = PathCombine(location, name + ".desktop");
@ -361,7 +459,8 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
stream.flush();
f.close();
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup |
QFileDevice::ExeOther);
return true;
#elif defined Q_OS_WIN
@ -389,25 +488,47 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
#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)
{
using copy_opts = fs::copy_options;
if (!FS::ensureFolderPathExists(overwritten_path))
return false;
std::error_code err;
fs::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing;
QStringList paths_to_override;
QDir root_override (override_path);
for (auto file : listFolderPaths(root_override)) {
QString destination = file;
destination.replace(override_path, overwritten_path);
ensureFilePathExists(destination);
// FIXME: hello traveller! Apparently std::copy does NOT overwrite existing files on GNU libstdc++ on Windows?
fs::copy(StringUtils::toStdString(override_path), StringUtils::toStdString(overwritten_path), opt, err);
qDebug() << QString("Applying override %1 in %2").arg(file, destination);
if (err) {
qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path);
qCritical() << "Reason:" << QString::fromStdString(err.message());
if (QFile::exists(destination))
QFile::remove(destination);
if (!QFile::rename(file, destination)) {
qCritical() << QString("Failed to apply override from %1 to %2").arg(file, destination);
return false;
}
}
return err.value() == 0;
return true;
}
}

View File

@ -41,27 +41,29 @@
#include <QDir>
#include <QFlags>
namespace FS {
namespace FS
{
class FileSystemException : public ::Exception {
public:
FileSystemException(const QString& message) : Exception(message) {}
class FileSystemException : public ::Exception
{
public:
FileSystemException(const QString &message) : Exception(message) {}
};
/**
* 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\
*/
QByteArray read(const QString& filename);
QByteArray read(const QString &filename);
/**
* 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
@ -75,31 +77,35 @@ bool ensureFilePathExists(QString filenamepath);
*/
bool ensureFolderPathExists(QString filenamepath);
class copy {
public:
copy(const QString& src, const QString& dst)
class copy
{
public:
copy(const QString & src, const QString & dst)
{
m_src.setPath(src);
m_dst.setPath(dst);
}
copy& followSymlinks(const bool follow)
copy & followSymlinks(const bool follow)
{
m_followSymlinks = follow;
return *this;
}
copy& blacklist(const IPathMatcher* filter)
copy & blacklist(const IPathMatcher * filter)
{
m_blacklist = filter;
return *this;
}
bool operator()() { return operator()(QString()); }
bool operator()()
{
return operator()(QString());
}
private:
bool operator()(const QString& offset);
private:
bool operator()(const QString &offset);
private:
private:
bool m_followSymlinks = true;
const IPathMatcher* m_blacklist = nullptr;
const IPathMatcher * m_blacklist = nullptr;
QDir m_src;
QDir m_dst;
};
@ -109,14 +115,9 @@ class copy {
*/
bool deletePath(QString path);
/**
* Trash a folder / file
*/
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 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);

View File

@ -2,9 +2,7 @@
#include <QTemporaryDir>
#include <QStandardPaths>
#include <FileSystem.h>
#include <pathmatcher/RegexpMatcher.h>
#include "FileSystem.h"
class FileSystemTest : public QObject
{
@ -82,7 +80,7 @@ slots:
void test_copy()
{
QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
QString folder = QFINDTESTDATA("testdata/test_folder");
auto f = [&folder]()
{
QTemporaryDir tempDir;
@ -113,102 +111,6 @@ slots:
f();
}
void test_copy_with_blacklist()
{
QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
auto f = [&folder]()
{
QTemporaryDir tempDir;
tempDir.setAutoRemove(true);
qDebug() << "From:" << folder << "To:" << tempDir.path();
QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
qDebug() << tempDir.path();
qDebug() << target_dir.path();
FS::copy c(folder, target_dir.path());
c.blacklist(new RegexpMatcher("[.]?mcmeta"));
c();
for(auto entry: target_dir.entryList())
{
qDebug() << entry;
}
QVERIFY(!target_dir.entryList().contains("pack.mcmeta"));
QVERIFY(target_dir.entryList().contains("assets"));
};
// first try variant without trailing /
QVERIFY(!folder.endsWith('/'));
f();
// then variant with trailing /
folder.append('/');
QVERIFY(folder.endsWith('/'));
f();
}
void test_copy_with_dot_hidden()
{
QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
auto f = [&folder]()
{
QTemporaryDir tempDir;
tempDir.setAutoRemove(true);
qDebug() << "From:" << folder << "To:" << tempDir.path();
QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
qDebug() << tempDir.path();
qDebug() << target_dir.path();
FS::copy c(folder, target_dir.path());
c();
auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden;
for (auto entry: target_dir.entryList(filter)) {
qDebug() << entry;
}
QVERIFY(target_dir.entryList(filter).contains(".secret_folder"));
target_dir.cd(".secret_folder");
QVERIFY(target_dir.entryList(filter).contains(".secret_file.txt"));
};
// first try variant without trailing /
QVERIFY(!folder.endsWith('/'));
f();
// then variant with trailing /
folder.append('/');
QVERIFY(folder.endsWith('/'));
f();
}
void test_copy_single_file()
{
QTemporaryDir tempDir;
tempDir.setAutoRemove(true);
{
QString file = QFINDTESTDATA("testdata/FileSystem/test_folder/pack.mcmeta");
qDebug() << "From:" << file << "To:" << tempDir.path();
QDir target_dir(FS::PathCombine(tempDir.path(), "pack.mcmeta"));
qDebug() << tempDir.path();
qDebug() << target_dir.path();
FS::copy c(file, target_dir.filePath("pack.mcmeta"));
c();
auto filter = QDir::Filter::Files;
for (auto entry: target_dir.entryList(filter)) {
qDebug() << entry;
}
QVERIFY(target_dir.entryList(filter).contains("pack.mcmeta"));
}
}
void test_getDesktop()
{
QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));

View File

@ -72,7 +72,7 @@ bool GZip::unzip(const QByteArray &compressedBytes, QByteArray &uncompressedByte
uncompLength *= 2;
}
strm.next_out = reinterpret_cast<Bytef *>((uncompressedBytes.data() + strm.total_out));
strm.next_out = (Bytef *)(uncompressedBytes.data() + strm.total_out);
strm.avail_out = uncompLength - strm.total_out;
// Inflate another chunk.
@ -129,7 +129,7 @@ bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes)
{
compressedBytes.resize(compressedBytes.size() * 2);
}
zs.next_out = reinterpret_cast<Bytef*>((compressedBytes.data() + offset));
zs.next_out = (Bytef *) (compressedBytes.data() + offset);
temp = zs.avail_out = compressedBytes.size() - offset;
ret = deflate(&zs, Z_FINISH);
offset += temp - zs.avail_out;

View File

@ -1,6 +1,6 @@
#include <QTest>
#include <GZip.h>
#include "GZip.h"
#include <random>
void fib(int &prev, int &cur)

View File

@ -42,7 +42,7 @@ public:
}
void put(QByteArray input)
{
hoedown_buffer_put(buf, reinterpret_cast<uint8_t *>(input.data()), input.size());
hoedown_buffer_put(buf, (uint8_t *) input.data(), input.size());
}
const uint8_t * data() const
{

View File

@ -44,7 +44,7 @@ void InstanceCopyTask::copyFinished()
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath));
inst->setName(name());
inst->setName(m_instName);
inst->setIconKey(m_instIcon);
if(!m_keepPlaytime) {
inst->resetTimePlayed();

View File

@ -1,56 +1,40 @@
#include "InstanceCreationTask.h"
#include "settings/INISettingsObject.h"
#include "FileSystem.h"
#include <QDebug>
#include <QFile>
//FIXME: remove this
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
InstanceCreationTask::InstanceCreationTask() = default;
InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version)
{
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()
{
setAbortable(true);
if (updateInstance()) {
emitSucceeded();
return;
setStatus(tr("Creating instance from version %1").arg(m_version->name()));
{
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
instanceSettings->suspendSave();
MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
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();
return;
}

View File

@ -1,46 +1,26 @@
#pragma once
#include "tasks/Task.h"
#include "net/NetJob.h"
#include <QUrl>
#include "settings/SettingsObject.h"
#include "BaseVersion.h"
#include "InstanceTask.h"
class InstanceCreationTask : public InstanceTask {
class InstanceCreationTask : public InstanceTask
{
Q_OBJECT
public:
InstanceCreationTask();
virtual ~InstanceCreationTask() = default;
public:
explicit InstanceCreationTask(BaseVersionPtr version);
explicit InstanceCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loaderVersion);
protected:
void executeTask() final override;
protected:
//! Entry point for tasks.
virtual void executeTask() override;
/**
* Tries to update an already existing instance.
*
* This can be implemented by subclasses to provide a way of updating an already existing
* 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;
private: /* data */
BaseVersionPtr m_version;
bool m_usingLoader;
QString m_loader;
BaseVersionPtr m_loaderVersion;
};

View File

@ -35,25 +35,34 @@
*/
#include "InstanceImportTask.h"
#include <QtConcurrentRun>
#include "Application.h"
#include "BaseInstance.h"
#include "FileSystem.h"
#include "MMCZip.h"
#include "NullInstance.h"
#include "icons/IconList.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 <QtConcurrentRun>
#include <algorithm>
// FIXME: this does not belong here, it's Minecraft/Flame specific
#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>
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
{
@ -63,41 +72,35 @@ InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
bool InstanceImportTask::abort()
{
if (!canAbort())
return false;
if (m_filesNetJob)
m_filesNetJob->abort();
m_extractFuture.cancel();
return Task::abort();
return false;
}
void InstanceImportTask::executeTask()
{
setAbortable(true);
if (m_sourceUrl.isLocalFile()) {
if (m_sourceUrl.isLocalFile())
{
m_archivePath = m_sourceUrl.toLocalFile();
processZipPack();
} else {
}
else
{
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
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);
entry->setStale(true);
m_archivePath = entry->getFullPath();
m_filesNetJob = new NetJob(tr("Modpack download"), APPLICATION->network());
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
m_archivePath = entry->getFullPath();
auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
connect(job, &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
connect(job, &NetJob::failed, this, &InstanceImportTask::downloadFailed);
m_filesNetJob->start();
}
}
@ -116,13 +119,7 @@ void InstanceImportTask::downloadFailed(QString reason)
void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
{
setProgress(current, total);
}
void InstanceImportTask::downloadAborted()
{
emitAborted();
m_filesNetJob.reset();
setProgress(current / 2, total);
}
void InstanceImportTask::processZipPack()
@ -258,31 +255,290 @@ void InstanceImportTask::extractFinished()
void InstanceImportTask::extractAborted()
{
emitAborted();
emitFailed(tr("Instance import has been aborted."));
return;
}
void InstanceImportTask::processFlame()
{
auto* inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent);
const static QMap<QString,QString> forgemap = {
{"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));
}
}
inst_creation_task->setName(*this);
inst_creation_task->setIcon(m_instIcon);
inst_creation_task->setGroup(m_instGroup);
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
setOverride(inst_creation_task->shouldOverride());
emitSucceeded();
QString forgeVersion;
QString fabricVersion;
// TODO: is Quilt relevant here?
for(auto &loader: pack.minecraft.modLoaders)
{
auto id = loader.id;
if(id.startsWith("forge-"))
{
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(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
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();
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, [&](qint64 current, qint64 total)
{
setProgress(current, total);
});
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, [&](QString status)
{
setStatus(status);
});
m_modIdResolver->start();
}
void InstanceImportTask::processTechnic()
@ -290,7 +546,7 @@ void InstanceImportTask::processTechnic()
shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor();
connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &InstanceImportTask::emitSucceeded);
connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &InstanceImportTask::emitFailed);
packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath);
packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath);
}
void InstanceImportTask::processMultiMC()
@ -304,7 +560,7 @@ void InstanceImportTask::processMultiMC()
instance.resetTimePlayed();
// set a new nice name
instance.setName(name());
instance.setName(m_instName);
// if the icon was specified by user, use that. otherwise pull icon from the pack
if (m_instIcon != "default") {
@ -325,26 +581,198 @@ void InstanceImportTask::processMultiMC()
emitSucceeded();
}
// https://docs.modrinth.com/docs/modpacks/format_definition/
void InstanceImportTask::processModrinth()
{
auto* inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, m_sourceUrl.toString());
std::vector<Modrinth::File> files;
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);
}
inst_creation_task->setName(*this);
inst_creation_task->setIcon(m_instIcon);
inst_creation_task->setGroup(m_instGroup);
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
bool had_optional = false;
for (auto modInfo : jsonFiles) {
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;
}
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
setOverride(inst_creation_task->shouldOverride());
emitSucceeded();
auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
auto override_path = FS::PathCombine(m_stagingPath, "overrides");
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(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
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();
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total)
{
setProgress(current, total);
});
setStatus(tr("Downloading mods..."));
m_filesNetJob->start();
}

View File

@ -58,6 +58,7 @@ class InstanceImportTask : public InstanceTask
public:
explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr);
bool canAbort() const override { return true; }
bool abort() override;
const QVector<Flame::File> &getBlockedFiles() const
{
@ -79,7 +80,6 @@ private slots:
void downloadSucceeded();
void downloadFailed(QString reason);
void downloadProgressChanged(qint64 current, qint64 total);
void downloadAborted();
void extractFinished();
void extractAborted();

View File

@ -33,32 +33,30 @@
* limitations under the License.
*/
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QSet>
#include <QFile>
#include <QThread>
#include <QTextStream>
#include <QXmlStreamReader>
#include <QTimer>
#include <QDebug>
#include <QFileSystemWatcher>
#include <QUuid>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMimeData>
#include <QSet>
#include <QStack>
#include <QPair>
#include <QTextStream>
#include <QThread>
#include <QTimer>
#include <QUuid>
#include <QXmlStreamReader>
#include "BaseInstance.h"
#include "ExponentialSeries.h"
#include "FileSystem.h"
#include "InstanceList.h"
#include "BaseInstance.h"
#include "InstanceTask.h"
#include "NullInstance.h"
#include "WatchLock.h"
#include "minecraft/MinecraftInstance.h"
#include "settings/INISettingsObject.h"
#include "NullInstance.h"
#include "minecraft/MinecraftInstance.h"
#include "FileSystem.h"
#include "ExponentialSeries.h"
#include "WatchLock.h"
#ifdef Q_OS_WIN32
#include <Windows.h>
@ -66,12 +64,13 @@
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)
{
resumeWatch();
// Create aand normalize path
if (!QDir::current().exists(instDir)) {
if (!QDir::current().exists(instDir))
{
QDir::current().mkpath(instDir);
}
@ -84,7 +83,9 @@ InstanceList::InstanceList(SettingsObjectPtr settings, const QString& instDir, Q
m_watcher->addPath(m_instDir);
}
InstanceList::~InstanceList() {}
InstanceList::~InstanceList()
{
}
Qt::DropActions InstanceList::supportedDragActions() const
{
@ -98,7 +99,7 @@ Qt::DropActions InstanceList::supportedDropActions() 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 false;
@ -106,7 +107,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)
{
if (data && data->hasFormat("application/x-instanceid")) {
if(data && data->hasFormat("application/x-instanceid")) {
return true;
}
return false;
@ -119,33 +120,35 @@ QStringList InstanceList::mimeTypes() const
return types;
}
QMimeData* InstanceList::mimeData(const QModelIndexList& indexes) const
QMimeData * InstanceList::mimeData(const QModelIndexList& indexes) const
{
auto mimeData = QAbstractListModel::mimeData(indexes);
if (indexes.size() == 1) {
if(indexes.size() == 1) {
auto instanceId = data(indexes[0], InstanceIDRole).toString();
mimeData->setData("application/x-instanceid", instanceId.toUtf8());
}
return mimeData;
}
int InstanceList::rowCount(const QModelIndex& parent) const
int InstanceList::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
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);
if (row < 0 || row >= m_instances.size())
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();
}
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
@ -190,25 +193,29 @@ QVariant InstanceList::data(const QModelIndex& index, int role) const
bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!index.isValid()) {
if (!index.isValid())
{
return false;
}
if (role != Qt::EditRole) {
if(role != Qt::EditRole)
{
return false;
}
BaseInstance* pdata = static_cast<BaseInstance*>(index.internalPointer());
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
auto newName = value.toString();
if (pdata->name() == newName) {
if(pdata->name() == newName)
{
return true;
}
pdata->setName(newName);
return true;
}
Qt::ItemFlags InstanceList::flags(const QModelIndex& index) const
Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
{
Qt::ItemFlags f;
if (index.isValid()) {
if (index.isValid())
{
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
}
return f;
@ -217,11 +224,13 @@ Qt::ItemFlags InstanceList::flags(const QModelIndex& index) const
GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
{
auto inst = getInstanceById(id);
if (!inst) {
if(!inst)
{
return GroupId();
}
auto iter = m_instanceGroupIndex.find(inst->id());
if (iter != m_instanceGroupIndex.end()) {
if(iter != m_instanceGroupIndex.end())
{
return *iter;
}
return GroupId();
@ -230,27 +239,33 @@ GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
{
auto inst = getInstanceById(id);
if (!inst) {
if(!inst)
{
qDebug() << "Attempt to set a null instance's group";
return;
}
bool changed = false;
auto iter = m_instanceGroupIndex.find(inst->id());
if (iter != m_instanceGroupIndex.end()) {
if (*iter != name) {
if(iter != m_instanceGroupIndex.end())
{
if(*iter != name)
{
*iter = name;
changed = true;
}
} else {
}
else
{
changed = true;
m_instanceGroupIndex[id] = name;
}
if (changed) {
if(changed)
{
m_groupNameCache.insert(name);
auto idx = getInstIndex(inst.get());
emit dataChanged(index(idx), index(idx), { GroupRole });
emit dataChanged(index(idx), index(idx), {GroupRole});
saveGroupList();
}
}
@ -264,20 +279,24 @@ void InstanceList::deleteGroup(const QString& name)
{
bool removed = false;
qDebug() << "Delete group" << name;
for (auto& instance : m_instances) {
const auto& instID = instance->id();
for(auto & instance: m_instances)
{
const auto & instID = instance->id();
auto instGroupName = getInstanceGroup(instID);
if (instGroupName == name) {
if(instGroupName == name)
{
m_instanceGroupIndex.remove(instID);
qDebug() << "Remove" << instID << "from group" << name;
removed = true;
auto idx = getInstIndex(instance.get());
if (idx > 0) {
emit dataChanged(index(idx), index(idx), { GroupRole });
if(idx > 0)
{
emit dataChanged(index(idx), index(idx), {GroupRole});
}
}
}
if (removed) {
if(removed)
{
saveGroupList();
}
}
@ -287,75 +306,23 @@ bool InstanceList::isGroupCollapsed(const QString& 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)
{
auto inst = getInstanceById(id);
if (!inst) {
if(!inst)
{
qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
return;
}
if (m_instanceGroupIndex.remove(id)) {
if(m_instanceGroupIndex.remove(id))
{
saveGroupList();
}
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 ...";
return;
}
@ -363,13 +330,15 @@ void InstanceList::deleteInstance(const InstanceId& id)
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;
int i = 0;
for (auto& item : list) {
for(auto & item: list)
{
auto id = item->id();
if (out.contains(id)) {
if(out.contains(id))
{
qWarning() << "Duplicate ID" << id << "in instance list";
}
out[id] = std::make_pair(item, i);
@ -378,21 +347,24 @@ static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr>&
return out;
}
QList<InstanceId> InstanceList::discoverInstances()
QList< InstanceId > InstanceList::discoverInstances()
{
qDebug() << "Discovering instances in" << m_instDir;
QList<InstanceId> out;
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();
QFileInfo dirInfo(subDir);
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
continue;
// 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 instDirInfo(m_instDir);
if (targetInfo.canonicalPath() == instDirInfo.canonicalFilePath()) {
if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath())
{
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
continue;
}
@ -416,56 +388,74 @@ InstanceList::InstListError InstanceList::loadList()
QList<InstancePtr> newList;
for (auto& id : discoverInstances()) {
if (existingIds.contains(id)) {
for(auto & id: discoverInstances())
{
if(existingIds.contains(id))
{
auto instPair = existingIds[id];
existingIds.remove(id);
qDebug() << "Should keep and soft-reload" << id;
} else {
}
else
{
InstancePtr instPtr = loadInstance(id);
if (instPtr) {
if(instPtr)
{
newList.append(instPtr);
}
}
}
// 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
auto deadList = existingIds.values();
auto orderSortPredicate = [](const InstanceLocator& a, const InstanceLocator& b) -> bool { return a.second > b.second; };
auto orderSortPredicate = [](const InstanceLocator & a, const InstanceLocator & b) -> bool
{
return a.second > b.second;
};
std::sort(deadList.begin(), deadList.end(), orderSortPredicate);
// remove the contiguous ranges of rows
int front_bookmark = -1;
int back_bookmark = -1;
int currentItem = -1;
auto removeNow = [&]() {
auto removeNow = [&]()
{
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
endRemoveRows();
front_bookmark = -1;
back_bookmark = currentItem;
};
for (auto& removedItem : deadList) {
for(auto & removedItem: deadList)
{
auto instPtr = removedItem.first;
instPtr->invalidate();
currentItem = removedItem.second;
if (back_bookmark == -1) {
if(back_bookmark == -1)
{
// no bookmark yet
back_bookmark = currentItem;
} else if (currentItem == front_bookmark - 1) {
}
else if(currentItem == front_bookmark - 1)
{
// part of contiguous sequence, continue
} else {
}
else
{
// seam between previous and current item
removeNow();
}
front_bookmark = currentItem;
}
if (back_bookmark != -1) {
if(back_bookmark != -1)
{
removeNow();
}
}
if (newList.size()) {
if(newList.size())
{
add(newList);
}
m_dirty = false;
@ -476,23 +466,26 @@ InstanceList::InstListError InstanceList::loadList()
void InstanceList::updateTotalPlayTime()
{
totalPlayTime = 0;
for (auto const& itr : m_instances) {
for(auto const& itr : m_instances)
{
totalPlayTime += itr.get()->totalTimePlayed();
}
}
void InstanceList::saveNow()
{
for (auto& item : m_instances) {
for(auto & item: m_instances)
{
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);
m_instances.append(t);
for (auto& ptr : t) {
for(auto & ptr : t)
{
connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged);
}
endInsertRows();
@ -500,74 +493,69 @@ void InstanceList::add(const QList<InstancePtr>& t)
void InstanceList::resumeWatch()
{
if (m_watchLevel > 0) {
if(m_watchLevel > 0)
{
qWarning() << "Bad suspend level resume in instance list";
return;
}
m_watchLevel++;
if (m_watchLevel > 0 && m_dirty) {
if(m_watchLevel > 0 && m_dirty)
{
loadList();
}
}
void InstanceList::suspendWatch()
{
m_watchLevel--;
m_watchLevel --;
}
void InstanceList::providerUpdated()
{
m_dirty = true;
if (m_watchLevel == 1) {
if(m_watchLevel == 1)
{
loadList();
}
}
InstancePtr InstanceList::getInstanceById(QString instId) const
{
if (instId.isEmpty())
if(instId.isEmpty())
return InstancePtr();
for (auto& inst : m_instances) {
if (inst->id() == instId) {
for(auto & inst: m_instances)
{
if (inst->id() == instId)
{
return inst;
}
}
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
{
return index(getInstIndex(getInstanceById(id).get()));
}
int InstanceList::getInstIndex(BaseInstance* inst) const
int InstanceList::getInstIndex(BaseInstance *inst) const
{
int count = m_instances.count();
for (int i = 0; i < count; i++) {
if (inst == m_instances[i].get()) {
for (int i = 0; i < count; i++)
{
if (inst == m_instances[i].get())
{
return i;
}
}
return -1;
}
void InstanceList::propertiesChanged(BaseInstance* inst)
void InstanceList::propertiesChanged(BaseInstance *inst)
{
int i = getInstIndex(inst);
if (i != -1) {
if (i != -1)
{
emit dataChanged(index(i), index(i));
updateTotalPlayTime();
}
@ -575,7 +563,8 @@ void InstanceList::propertiesChanged(BaseInstance* inst)
InstancePtr InstanceList::loadInstance(const InstanceId& id)
{
if (!m_groupsLoaded) {
if(!m_groupsLoaded)
{
loadGroupList();
}
@ -603,42 +592,50 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
void InstanceList::saveGroupList()
{
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.";
return;
}
WatchLock foo(m_watcher, m_instDir);
QString groupFileName = m_instDir + "/instgroups.json";
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 group = iter.value();
if (group.isEmpty())
continue;
if (!instanceSet.contains(id)) {
if(!instanceSet.contains(id))
{
qDebug() << "Skipping saving missing instance" << id << "to groups list.";
continue;
}
if (!reverseGroupMap.count(group)) {
if (!reverseGroupMap.count(group))
{
QSet<QString> set;
set.insert(id);
reverseGroupMap[group] = set;
} else {
QSet<QString>& set = reverseGroupMap[group];
}
else
{
QSet<QString> &set = reverseGroupMap[group];
set.insert(id);
}
}
QJsonObject toplevel;
toplevel.insert("formatVersion", QJsonValue(QString("1")));
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 name = iter.key();
QJsonObject groupObj;
QJsonArray instanceArr;
groupObj.insert("hidden", QJsonValue(m_collapsedGroups.contains(name)));
for (auto item : list) {
for (auto item : list)
{
instanceArr.append(QJsonValue(item));
}
groupObj.insert("instances", instanceArr);
@ -646,10 +643,13 @@ void InstanceList::saveGroupList()
}
toplevel.insert("groups", groupsArr);
QJsonDocument doc(toplevel);
try {
try
{
FS::write(groupFileName, doc.toJson());
qDebug() << "Group list saved.";
} catch (const FS::FileSystemException& e) {
}
catch (const FS::FileSystemException &e)
{
qCritical() << "Failed to write instance group file :" << e.cause();
}
}
@ -665,9 +665,12 @@ void InstanceList::loadGroupList()
return;
QByteArray jsonData;
try {
try
{
jsonData = FS::read(groupFileName);
} catch (const FS::FileSystemException& e) {
}
catch (const FS::FileSystemException &e)
{
qCritical() << "Failed to read instance group file :" << e.cause();
return;
}
@ -676,15 +679,17 @@ void InstanceList::loadGroupList()
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
// 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")
.arg(error.errorString(), QString::number(error.offset))
.toUtf8();
.arg(error.errorString(), QString::number(error.offset))
.toUtf8();
return;
}
// 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.";
return;
}
@ -696,7 +701,8 @@ void InstanceList::loadGroupList()
return;
// 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.";
return;
}
@ -706,20 +712,21 @@ void InstanceList::loadGroupList()
// Iterate through all the groups.
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();
// 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();
continue;
}
QJsonObject groupObj = iter.value().toObject();
if (!groupObj.value("instances").isArray()) {
qWarning() << QString("Group '%1' in the group list is invalid. It should contain an array called 'instances'.")
.arg(groupName)
.toUtf8();
if (!groupObj.value("instances").isArray())
{
qWarning() << QString("Group '%1' in the group list is invalid. It should contain an array called 'instances'.").arg(groupName).toUtf8();
continue;
}
@ -727,14 +734,15 @@ void InstanceList::loadGroupList()
groupSet.insert(groupName);
auto hidden = groupObj.value("hidden").toBool(false);
if (hidden) {
if(hidden) {
m_collapsedGroups.insert(groupName);
}
// Iterate through the list of instances in the group.
QJsonArray instancesArray = groupObj.value("instances").toArray();
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++) {
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++)
{
m_instanceGroupIndex[(*iter2).toString()] = groupName;
}
}
@ -749,11 +757,13 @@ void InstanceList::instanceDirContentsChanged(const QString& path)
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();
if (newInstDir != m_instDir) {
if (m_groupsLoaded) {
if(newInstDir != m_instDir)
{
if(m_groupsLoaded)
{
saveGroupList();
}
m_instDir = newInstDir;
@ -765,7 +775,7 @@ void InstanceList::on_InstFolderChanged(const Setting& setting, QVariant value)
void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed)
{
qDebug() << "Group" << group << (collapsed ? "collapsed" : "expanded");
if (collapsed) {
if(collapsed) {
m_collapsedGroups.insert(group);
} else {
m_collapsedGroups.remove(group);
@ -773,75 +783,89 @@ void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed)
saveGroupList();
}
class InstanceStaging : public Task {
Q_OBJECT
class InstanceStaging : public Task
{
Q_OBJECT
const unsigned minBackoff = 1;
const unsigned maxBackoff = 16;
public:
InstanceStaging(InstanceList* parent, InstanceTask* child, QString stagingPath, InstanceName const& instanceName, QString groupName)
: m_parent(parent), backoff(minBackoff, maxBackoff), m_stagingPath(std::move(stagingPath)), m_instance_name(std::move(instanceName)), m_groupName(std::move(groupName))
public:
InstanceStaging (
InstanceList * parent,
Task * child,
const QString & stagingPath,
const QString& instanceName,
const QString& groupName )
: backoff(minBackoff, maxBackoff)
{
m_parent = parent;
m_child.reset(child);
connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded);
connect(child, &Task::failed, this, &InstanceStaging::childFailed);
connect(child, &Task::aborted, this, &InstanceStaging::childAborted);
connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable);
connect(child, &Task::status, this, &InstanceStaging::setStatus);
connect(child, &Task::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);
}
virtual ~InstanceStaging(){};
virtual ~InstanceStaging() {};
// FIXME/TODO: add ability to abort during instance commit retries
bool abort() override
{
if (!canAbort())
return false;
m_child->abort();
return Task::abort();
if(m_child && m_child->canAbort())
{
return m_child->abort();
}
return false;
}
bool canAbort() const override
{
return (m_child && m_child->canAbort());
if(m_child && m_child->canAbort())
{
return true;
}
return false;
}
protected:
virtual void executeTask() override { m_child->start(); }
QStringList warnings() const override { return m_child->warnings(); }
protected:
virtual void executeTask() override
{
m_child->start();
}
QStringList warnings() const override
{
return m_child->warnings();
}
private slots:
private slots:
void childSucceded()
{
unsigned sleepTime = backoff();
if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, m_child->shouldOverride()))
if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName))
{
emitSucceeded();
return;
}
// 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."));
return;
}
qDebug() << "Failed to commit instance" << m_instance_name.name() << "Initiating backoff:" << sleepTime;
qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime;
m_backoffTimer.start(sleepTime * 500);
}
void childFailed(const QString& reason)
void childFailed(const QString & reason)
{
m_parent->destroyStagingPath(m_stagingPath);
emitFailed(reason);
}
void childAborted()
{
emitAborted();
}
private:
InstanceList * m_parent;
/*
* WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows.
* Basically, it starts messing things up while the launcher is extracting/creating instances
@ -849,18 +873,19 @@ private:
*/
ExponentialSeries backoff;
QString m_stagingPath;
unique_qobject_ptr<InstanceTask> m_child;
InstanceName m_instance_name;
InstanceList * m_parent;
unique_qobject_ptr<Task> m_child;
QString m_instanceName;
QString m_groupName;
QTimer m_backoffTimer;
};
Task* InstanceList::wrapInstanceTask(InstanceTask* task)
Task * InstanceList::wrapInstanceTask(InstanceTask * task)
{
auto stagingPath = getStagedInstancePath();
task->setStagingPath(stagingPath);
task->setParentSettings(m_globalSettings);
return new InstanceStaging(this, task, stagingPath, *task, task->group());
return new InstanceStaging(this, task, stagingPath, task->name(), task->group());
}
QString InstanceList::getStagedInstancePath()
@ -870,7 +895,8 @@ QString InstanceList::getStagedInstancePath()
QString relPath = FS::PathCombine(tempDir, key);
QDir rootPath(m_instDir);
auto path = FS::PathCombine(m_instDir, relPath);
if (!rootPath.mkpath(relPath)) {
if(!rootPath.mkpath(relPath))
{
return QString();
}
#ifdef Q_OS_WIN32
@ -880,50 +906,24 @@ QString InstanceList::getStagedInstancePath()
return path;
}
bool InstanceList::commitStagedInstance(const QString& path, InstanceName const& instanceName, const QString& groupName, bool should_override)
bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName)
{
QDir dir;
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);
}
QString instID = FS::DirNameFromString(instanceName, m_instDir);
{
WatchLock lock(m_watcher, m_instDir);
QString destination = FS::PathCombine(m_instDir, instID);
if (should_override) {
if (!FS::overrideFolder(destination, path)) {
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);
if(!dir.rename(path, destination))
{
qWarning() << "Failed to move" << path << "to" << destination;
return false;
}
m_instanceGroupIndex[instID] = groupName;
instanceSet.insert(instID);
m_groupNameCache.insert(groupName);
emit instancesChanged();
emit instanceSelectRequest(instID);
}
saveGroupList();
return true;
}
@ -933,8 +933,7 @@ bool InstanceList::destroyStagingPath(const QString& keyPath)
return FS::deletePath(keyPath);
}
int InstanceList::getTotalPlayTime()
{
int InstanceList::getTotalPlayTime() {
updateTotalPlayTime();
return totalPlayTime;
}

View File

@ -19,15 +19,13 @@
#include <QAbstractListModel>
#include <QSet>
#include <QList>
#include <QStack>
#include <QPair>
#include "BaseInstance.h"
#include "QObjectPtr.h"
class QFileSystemWatcher;
class InstanceTask;
struct InstanceName;
using InstanceId = QString;
using GroupId = QString;
using InstanceLocator = std::pair<InstancePtr, int>;
@ -48,12 +46,6 @@ enum class GroupsState
Dirty
};
struct TrashHistoryItem {
QString id;
QString polyPath;
QString trashPath;
QString groupName;
};
class InstanceList : public QAbstractListModel
{
@ -101,10 +93,7 @@ public:
InstListError loadList();
void saveNow();
/* O(n) */
InstancePtr getInstanceById(QString id) const;
/* O(n) */
InstancePtr getInstanceByManagedName(const QString& managed_name) const;
QModelIndex getInstanceIndexById(const QString &id) const;
QStringList getGroups();
bool isGroupCollapsed(const QString &groupName);
@ -113,9 +102,6 @@ public:
void setInstanceGroup(const InstanceId & id, const GroupId& name);
void deleteGroup(const GroupId & name);
bool trashInstance(const InstanceId &id);
bool trashedSomething();
void undoTrashInstance();
void deleteInstance(const InstanceId & id);
// Wrap an instance creation task in some more task machinery and make it ready to be used
@ -130,10 +116,8 @@ public:
/**
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
* 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 InstanceName& instanceName, const QString& groupName, bool should_override);
bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName);
/**
* Destroy a previously created staging area given by @keyPath - used when creation fails.
@ -196,6 +180,4 @@ private:
QSet<InstanceId> instanceSet;
bool m_groupsLoaded = false;
bool m_instancesProbed = false;
QStack<TrashHistoryItem> m_trashHistory;
};

View File

@ -37,9 +37,9 @@ public:
modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
values.append(modsPage);
values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList()));
values.append(new ResourcePackPage(onesix.get(), onesix->resourcePackList()));
values.append(new TexturePackPage(onesix.get(), onesix->texturePackList()));
values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList()));
values.append(new ResourcePackPage(onesix.get()));
values.append(new TexturePackPage(onesix.get()));
values.append(new ShaderPackPage(onesix.get()));
values.append(new NotesPage(onesix.get()));
values.append(new WorldListPage(onesix.get(), onesix->worldList()));
values.append(new ServersPage(onesix));

View File

@ -1,52 +1,9 @@
#include "InstanceTask.h"
#include "ui/dialogs/CustomMessageBox.h"
InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name)
InstanceTask::InstanceTask()
{
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;
}
QString InstanceName::name() const
InstanceTask::~InstanceTask()
{
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() {}

View File

@ -1,57 +1,52 @@
#pragma once
#include "settings/SettingsObject.h"
#include "tasks/Task.h"
#include "settings/SettingsObject.h"
/* 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 {
class InstanceTask : public Task
{
Q_OBJECT
public:
InstanceTask();
~InstanceTask() override = default;
public:
explicit InstanceTask();
virtual ~InstanceTask();
void setParentSettings(SettingsObjectPtr settings) { m_globalSettings = settings; }
void setParentSettings(SettingsObjectPtr settings)
{
m_globalSettings = settings;
}
void setStagingPath(const QString& stagingPath) { m_stagingPath = stagingPath; }
void setStagingPath(const QString &stagingPath)
{
m_stagingPath = stagingPath;
}
void setIcon(const QString& icon) { m_instIcon = icon; }
void setName(const QString &name)
{
m_instName = name;
}
QString name() const
{
return m_instName;
}
void setGroup(const QString& group) { m_instGroup = group; }
QString group() const { return m_instGroup; }
void setIcon(const QString &icon)
{
m_instIcon = icon;
}
bool shouldOverride() const { return m_override_existing; }
void setGroup(const QString &group)
{
m_instGroup = group;
}
QString group() const
{
return m_instGroup;
}
protected:
void setOverride(bool override) { m_override_existing = override; }
protected: /* data */
protected: /* data */
SettingsObjectPtr m_globalSettings;
QString m_instName;
QString m_instIcon;
QString m_instGroup;
QString m_stagingPath;
bool m_override_existing = false;
};

View File

@ -36,7 +36,7 @@
#include "JavaCommon.h"
#include "java/JavaUtils.h"
#include "ui/dialogs/CustomMessageBox.h"
#include <MMCStrings.h>
#include <QRegularExpression>
bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent)

View File

@ -93,8 +93,8 @@ void LaunchController::decideAccount()
auto reply = CustomMessageBox::selectable(
m_parentWidget,
tr("No Accounts"),
tr("In order to play Minecraft, you must have at least one Microsoft or Mojang "
"account logged in. Mojang accounts can only be used offline. "
tr("In order to play Minecraft, you must have at least one Mojang or Microsoft "
"account logged in. "
"Would you like to open the account manager to add an account now?"),
QMessageBox::Information,
QMessageBox::Yes | QMessageBox::No
@ -167,7 +167,6 @@ void LaunchController::login() {
tries++;
m_session = std::make_shared<AuthSession>();
m_session->wants_online = m_online;
m_session->demo = m_demo;
m_accountToUse->fillSession(m_session);
// Launch immediately in true offline mode
@ -185,18 +184,12 @@ void LaunchController::login() {
if(!m_session->wants_online) {
// we ask the user for a player name
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 usedname = lastOfflinePlayerName.isEmpty() ? m_session->player_name : lastOfflinePlayerName;
QString name = QInputDialog::getText(
m_parentWidget,
tr("Player name"),
message,
tr("Choose your offline mode player name."),
QLineEdit::Normal,
usedname,
&ok
@ -376,13 +369,13 @@ void LaunchController::launchInstance()
}
m_launcher->prependStep(new TextPrint(m_launcher.get(), resolved_servers, MessageLevel::Launcher));
} else {
online_mode = m_demo ? "demo" : "offline";
online_mode = "offline";
}
m_launcher->prependStep(new TextPrint(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher));
// Prepend Version
m_launcher->prependStep(new TextPrint(m_launcher.get(), BuildConfig.LAUNCHER_DISPLAYNAME + " version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::Launcher));
m_launcher->prependStep(new TextPrint(m_launcher.get(), BuildConfig.LAUNCHER_NAME + " version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::Launcher));
m_launcher->start();
}

View File

@ -63,10 +63,6 @@ public:
m_online = online;
}
void setDemo(bool demo) {
m_demo = demo;
}
void setProfiler(BaseProfilerFactory *profiler) {
m_profiler = profiler;
}
@ -105,7 +101,6 @@ private slots:
private:
BaseProfilerFactory *m_profiler = nullptr;
bool m_online = true;
bool m_demo = false;
InstancePtr m_instance;
QWidget * m_parentWidget = nullptr;
InstanceWindow *m_console = nullptr;

View File

@ -1,28 +1,26 @@
#include "StringUtils.h"
/// If you're wondering where these came from exactly, then know you're not the only one =D
#include "MMCStrings.h"
/// TAKEN FROM Qt, because it doesn't expose it intelligently
static inline QChar getNextChar(const QString& s, int location)
static inline QChar getNextChar(const QString &s, int location)
{
return (location < s.length()) ? s.at(location) : QChar();
}
/// TAKEN FROM Qt, because it doesn't expose it intelligently
int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs)
int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs)
{
int l1 = 0, l2 = 0;
while (l1 <= s1.count() && l2 <= s2.count()) {
for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2)
{
// skip spaces, tabs and 0's
QChar c1 = getNextChar(s1, l1);
while (c1.isSpace())
c1 = getNextChar(s1, ++l1);
QChar c2 = getNextChar(s2, l2);
while (c2.isSpace())
c2 = getNextChar(s2, ++l2);
if (c1.isDigit() && c2.isDigit()) {
if (c1.isDigit() && c2.isDigit())
{
while (c1.digitValue() == 0)
c1 = getNextChar(s1, ++l1);
while (c2.digitValue() == 0)
@ -32,8 +30,11 @@ int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSe
int lookAheadLocation2 = l2;
int currentReturnValue = 0;
// find the last digit, setting currentReturnValue as we go if it isn't equal
for (QChar lookAhead1 = c1, lookAhead2 = c2; (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
lookAhead1 = getNextChar(s1, ++lookAheadLocation1), lookAhead2 = getNextChar(s2, ++lookAheadLocation2)) {
for (QChar lookAhead1 = c1, lookAhead2 = c2;
(lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
lookAhead1 = getNextChar(s1, ++lookAheadLocation1),
lookAhead2 = getNextChar(s2, ++lookAheadLocation2))
{
bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit();
bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit();
if (!is1ADigit && !is2ADigit)
@ -42,10 +43,14 @@ int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSe
return -1;
if (!is2ADigit)
return 1;
if (currentReturnValue == 0) {
if (lookAhead1 < lookAhead2) {
if (currentReturnValue == 0)
{
if (lookAhead1 < lookAhead2)
{
currentReturnValue = -1;
} else if (lookAhead1 > lookAhead2) {
}
else if (lookAhead1 > lookAhead2)
{
currentReturnValue = 1;
}
}
@ -53,24 +58,19 @@ int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSe
if (currentReturnValue != 0)
return currentReturnValue;
}
if (cs == Qt::CaseInsensitive) {
if (cs == Qt::CaseInsensitive)
{
if (!c1.isLower())
c1 = c1.toLower();
if (!c2.isLower())
c2 = c2.toLower();
}
int r = QString::localeAwareCompare(c1, c2);
if (r < 0)
return -1;
if (r > 0)
return 1;
l1 += 1;
l2 += 1;
}
// The two strings are the same (02 == 2) so fall back to the normal sort
return QString::compare(s1, s2, cs);
}

8
launcher/MMCStrings.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
#include <QString>
namespace Strings
{
int naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs);
}

View File

@ -148,7 +148,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
// do not merge disabled mods.
if (!mod->enabled())
continue;
if (mod->type() == ResourceType::ZIPFILE)
if (mod->type() == Mod::MOD_ZIPFILE)
{
if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles))
{
@ -158,7 +158,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
return false;
}
}
else if (mod->type() == ResourceType::SINGLEFILE)
else if (mod->type() == Mod::MOD_SINGLEFILE)
{
// FIXME: buggy - does not work with addedFiles
auto filename = mod->fileinfo();
@ -171,7 +171,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
}
addedFiles.insert(filename.fileName());
}
else if (mod->type() == ResourceType::FOLDER)
else if (mod->type() == Mod::MOD_FOLDER)
{
// untested, but seems to be unused / not possible to reach
// FIXME: buggy - does not work with addedFiles

View File

@ -1,38 +1,3 @@
// 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
#include "BaseInstance.h"
#include "launch/LaunchTask.h"
@ -50,10 +15,6 @@ public:
void saveNow() override
{
}
void loadSpecificSettings() override
{
setSpecificSettingsLoaded(true);
}
QString getStatusbarDescription() override
{
return tr("Unknown instance type");
@ -82,7 +43,7 @@ public:
{
return QProcessEnvironment();
}
QMap<QString, QString> getVariables() override
QMap<QString, QString> getVariables() const override
{
return QMap<QString, QString>();
}
@ -119,8 +80,4 @@ public:
QString modsRoot() const override {
return QString();
}
void updateRuntimeContext()
{
// NOOP
}
};

View File

@ -1,37 +1,91 @@
#pragma once
#include <QObject>
#include <QSharedPointer>
#include <functional>
#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
* Calls deleteLater() instead of destroying the contained object immediately
*/
template <typename T>
using unique_qobject_ptr = QScopedPointer<T, QScopedPointerDeleteLater>;
template<typename T> using unique_qobject_ptr = std::unique_ptr<T, details::DeleteQObjectLater>;
/**
* A shared pointer class with shared pointer semantics intended for derivates of QObject
* Calls deleteLater() instead of destroying the contained object immediately
*/
template <typename T>
class shared_qobject_ptr : public QSharedPointer<T> {
public:
constexpr shared_qobject_ptr() : QSharedPointer<T>() {}
constexpr shared_qobject_ptr(T* ptr) : QSharedPointer<T>(ptr, &QObject::deleteLater) {}
constexpr shared_qobject_ptr(std::nullptr_t null_ptr) : QSharedPointer<T>(null_ptr, &QObject::deleteLater) {}
template <typename Derived>
constexpr shared_qobject_ptr(const shared_qobject_ptr<Derived>& other) : QSharedPointer<T>(other)
{}
void reset() { QSharedPointer<T>::reset(); }
void reset(const shared_qobject_ptr<T>& other)
class shared_qobject_ptr
{
public:
shared_qobject_ptr(){}
shared_qobject_ptr(T * wrap)
{
shared_qobject_ptr<T> t(other);
this->swap(t);
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:
void reset(T * wrap)
{
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:
std::shared_ptr <T> m_ptr;
};

View File

@ -1,88 +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/>.
*/
#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
}
};

View File

@ -1,32 +0,0 @@
#pragma once
#include <QString>
namespace StringUtils {
#if defined Q_OS_WIN32
using string = std::wstring;
inline string toStdString(QString s)
{
return s.toStdWString();
}
inline QString fromStdString(string s)
{
return QString::fromStdWString(s);
}
#else
using string = std::string;
inline string toStdString(QString s)
{
return s.toStdString();
}
inline QString fromStdString(string s)
{
return QString::fromStdString(s);
}
#endif
int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs);
} // namespace StringUtils

View File

@ -358,7 +358,7 @@ void UpdateController::fail()
msg = QObject::tr(
"Couldn't replace file %1. Changes will be reverted.\n"
"See the %2 log file for details."
).arg(m_failedFile, BuildConfig.LAUNCHER_DISPLAYNAME);
).arg(m_failedFile, BuildConfig.LAUNCHER_NAME);
doRollback = true;
QMessageBox::critical(m_parent, failTitle, msg);
break;
@ -368,7 +368,7 @@ void UpdateController::fail()
msg = QObject::tr(
"Couldn't remove file %1. Changes will be reverted.\n"
"See the %2 log file for details."
).arg(m_failedFile, BuildConfig.LAUNCHER_DISPLAYNAME);
).arg(m_failedFile, BuildConfig.LAUNCHER_NAME);
doRollback = true;
QMessageBox::critical(m_parent, failTitle, msg);
break;
@ -399,7 +399,7 @@ void UpdateController::fail()
{
msg = QObject::tr("The rollback failed too.\n"
"You will have to repair %1 manually.\n"
"Please let us know why and how this happened.").arg(BuildConfig.LAUNCHER_DISPLAYNAME);
"Please let us know why and how this happened.").arg(BuildConfig.LAUNCHER_NAME);
QMessageBox::critical(m_parent, rollFailTitle, msg);
qApp->quit();
}

View File

@ -15,7 +15,7 @@
#include <QTest>
#include <TestUtil.h>
#include "TestUtil.h"
#include <Version.h>
class ModUtilsTest : public QObject

View File

@ -1,10 +1,9 @@
#include "JavaInstall.h"
#include "StringUtils.h"
#include <MMCStrings.h>
bool JavaInstall::operator<(const JavaInstall &rhs)
{
auto archCompare = StringUtils::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
if(archCompare != 0)
return archCompare < 0;
if(id < rhs.id)
@ -15,7 +14,7 @@ bool JavaInstall::operator<(const JavaInstall &rhs)
{
return false;
}
return StringUtils::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
}
bool JavaInstall::operator==(const JavaInstall &rhs)

View File

@ -41,6 +41,7 @@
#include "java/JavaInstallList.h"
#include "java/JavaCheckerJob.h"
#include "java/JavaUtils.h"
#include "MMCStrings.h"
#include "minecraft/VersionFilterData.h"
JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent)
@ -72,7 +73,7 @@ void JavaInstallList::load()
}
}
const BaseVersion::Ptr JavaInstallList::at(int i) const
const BaseVersionPtr JavaInstallList::at(int i) const
{
return m_vlist.at(i);
}
@ -121,7 +122,7 @@ BaseVersionList::RoleList JavaInstallList::providesRoles() const
}
void JavaInstallList::updateListData(QList<BaseVersion::Ptr> versions)
void JavaInstallList::updateListData(QList<BaseVersionPtr> versions)
{
beginResetModel();
m_vlist = versions;
@ -136,7 +137,7 @@ void JavaInstallList::updateListData(QList<BaseVersion::Ptr> versions)
m_loadTask.reset();
}
bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right)
bool sortJavas(BaseVersionPtr left, BaseVersionPtr right)
{
auto rleft = std::dynamic_pointer_cast<JavaInstall>(right);
auto rright = std::dynamic_pointer_cast<JavaInstall>(left);
@ -209,11 +210,11 @@ void JavaListLoadTask::javaCheckerFinished()
}
}
QList<BaseVersion::Ptr> javas_bvp;
QList<BaseVersionPtr> javas_bvp;
for (auto java : candidates)
{
//qDebug() << java->id << java->arch << " at " << java->path;
BaseVersion::Ptr bp_java = std::dynamic_pointer_cast<BaseVersion>(java);
BaseVersionPtr bp_java = std::dynamic_pointer_cast<BaseVersion>(java);
if (bp_java)
{

View File

@ -42,7 +42,7 @@ public:
Task::Ptr getLoadTask() override;
bool isLoaded() override;
const BaseVersion::Ptr at(int i) const override;
const BaseVersionPtr at(int i) const override;
int count() const override;
void sortVersions() override;
@ -50,7 +50,7 @@ public:
RoleList providesRoles() const override;
public slots:
void updateListData(QList<BaseVersion::Ptr> versions) override;
void updateListData(QList<BaseVersionPtr> versions) override;
protected:
void load();
@ -59,7 +59,7 @@ protected:
protected:
Status m_status = Status::NotDone;
shared_qobject_ptr<JavaListLoadTask> m_loadTask;
QList<BaseVersion::Ptr> m_vlist;
QList<BaseVersionPtr> m_vlist;
};
class JavaListLoadTask : public Task

View File

@ -174,17 +174,11 @@ JavaInstallPtr JavaUtils::GetDefaultJava()
QStringList addJavasFromEnv(QList<QString> javas)
{
auto env = qEnvironmentVariable("PRISMLAUNCHER_JAVA_PATHS"); // FIXME: use launcher name from buildconfig
QByteArray env = qgetenv("POLYMC_JAVA_PATHS");
#if defined(Q_OS_WIN32)
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");
}
QList<QString> javaPaths = QString::fromLocal8Bit(env).replace("\\", "/").split(QLatin1String(";"));
#else
QList<QString> javaPaths = env.split(QLatin1String(":"));
QList<QString> javaPaths = QString::fromLocal8Bit(env).split(QLatin1String(":"));
#endif
for(QString i : javaPaths)
{
@ -379,9 +373,7 @@ QList<QString> JavaUtils::FindJavaPaths()
}
}
candidates = addJavasFromEnv(candidates);
candidates.removeDuplicates();
return candidates;
return addJavasFromEnv(candidates);
}
#elif defined(Q_OS_MAC)
@ -404,9 +396,7 @@ QList<QString> JavaUtils::FindJavaPaths()
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
}
javas = addJavasFromEnv(javas);
javas.removeDuplicates();
return javas;
return addJavasFromEnv(javas);
}
#elif defined(Q_OS_LINUX)
@ -445,16 +435,14 @@ QList<QString> JavaUtils::FindJavaPaths()
scanJavaDir("/usr/lib/jvm");
scanJavaDir("/usr/lib64/jvm");
scanJavaDir("/usr/lib32/jvm");
// javas stored in Prism Launcher's folder
// javas stored in PolyMC's folder
scanJavaDir("java");
// manually installed JDKs in /opt
scanJavaDir("/opt/jdk");
scanJavaDir("/opt/jdks");
// flatpak
scanJavaDir("/app/jdk");
javas = addJavasFromEnv(javas);
javas.removeDuplicates();
return javas;
return addJavasFromEnv(javas);
}
#else
QList<QString> JavaUtils::FindJavaPaths()

View File

@ -1,6 +1,5 @@
#include "JavaVersion.h"
#include "StringUtils.h"
#include <MMCStrings.h>
#include <QRegularExpression>
#include <QString>
@ -99,12 +98,12 @@ bool JavaVersion::operator<(const JavaVersion &rhs)
else if(thisPre && rhsPre)
{
// both are prereleases - use natural compare...
return StringUtils::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0;
return Strings::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0;
}
// neither is prerelease, so they are the same -> this cannot be less than rhs
return false;
}
else return StringUtils::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0;
else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0;
}
bool JavaVersion::operator==(const JavaVersion &rhs)

View File

@ -1,6 +1,6 @@
#include <QTest>
#include <java/JavaVersion.h>
#include "java/JavaVersion.h"
class JavaVersionTest : public QObject
{

View File

@ -37,6 +37,7 @@
#include "launch/LaunchTask.h"
#include "MessageLevel.h"
#include "MMCStrings.h"
#include "java/JavaChecker.h"
#include "tasks/Task.h"
#include <QDebug>

View File

@ -121,6 +121,7 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
emit logLine(QString("Could not start java:"), MessageLevel::Error);
emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
emit logLine(QString("\nCheck your Java settings."), MessageLevel::Launcher);
printSystemInfo(false, false);
emitFailed(QString("Could not start java!"));
return;
}
@ -129,6 +130,7 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
emit logLine(QString("Java checker returned some invalid data we don't understand:"), MessageLevel::Error);
emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher);
printSystemInfo(false, false);
emitSucceeded();
return;
}
@ -136,6 +138,7 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
{
auto instance = m_parent->instance();
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("JavaArchitecture", result.mojangPlatform);
instance->settings()->set("JavaRealArchitecture", result.realPlatform);
@ -152,3 +155,20 @@ void CheckJava::printJavaInfo(const QString& version, const QString& architectur
emit logLine(QString("Java is version %1, using %2 (%3) architecture, from %4.\n\n")
.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);
}
}
}

View File

@ -75,7 +75,7 @@ int main(int argc, char *argv[])
Q_INIT_RESOURCE(multimc);
Q_INIT_RESOURCE(backgrounds);
Q_INIT_RESOURCE(documents);
Q_INIT_RESOURCE(prismlauncher);
Q_INIT_RESOURCE(polymc);
Q_INIT_RESOURCE(pe_dark);
Q_INIT_RESOURCE(pe_light);
@ -90,7 +90,5 @@ int main(int argc, char *argv[])
return 1;
case Application::Succeeded:
return 0;
default:
return -1;
}
}

View File

@ -24,7 +24,7 @@ Index::Index(QObject *parent)
: QAbstractListModel(parent)
{
}
Index::Index(const QVector<VersionList::Ptr> &lists, QObject *parent)
Index::Index(const QVector<VersionListPtr> &lists, QObject *parent)
: QAbstractListModel(parent), m_lists(lists)
{
for (int i = 0; i < m_lists.size(); ++i)
@ -41,7 +41,7 @@ QVariant Index::data(const QModelIndex &index, int role) const
return QVariant();
}
VersionList::Ptr list = m_lists.at(index.row());
VersionListPtr list = m_lists.at(index.row());
switch (role)
{
case Qt::DisplayRole:
@ -81,9 +81,9 @@ bool Index::hasUid(const QString &uid) const
return m_uids.contains(uid);
}
VersionList::Ptr Index::get(const QString &uid)
VersionListPtr Index::get(const QString &uid)
{
VersionList::Ptr out = m_uids.value(uid, nullptr);
VersionListPtr out = m_uids.value(uid, nullptr);
if(!out)
{
out = std::make_shared<VersionList>(uid);
@ -92,7 +92,7 @@ VersionList::Ptr Index::get(const QString &uid)
return out;
}
Version::Ptr Index::get(const QString &uid, const QString &version)
VersionPtr Index::get(const QString &uid, const QString &version)
{
auto list = get(uid);
return list->getVersion(version);
@ -105,7 +105,7 @@ void Index::parse(const QJsonObject& obj)
void Index::merge(const std::shared_ptr<Index> &other)
{
const QVector<VersionList::Ptr> lists = std::dynamic_pointer_cast<Index>(other)->m_lists;
const QVector<VersionListPtr> lists = std::dynamic_pointer_cast<Index>(other)->m_lists;
// initial load, no need to merge
if (m_lists.isEmpty())
{
@ -120,7 +120,7 @@ void Index::merge(const std::shared_ptr<Index> &other)
}
else
{
for (const VersionList::Ptr &list : lists)
for (const VersionListPtr &list : lists)
{
if (m_uids.contains(list->uid()))
{
@ -138,7 +138,7 @@ void Index::merge(const std::shared_ptr<Index> &other)
}
}
void Index::connectVersionList(const int row, const VersionList::Ptr &list)
void Index::connectVersionList(const int row, const VersionListPtr &list)
{
connect(list.get(), &VersionList::nameChanged, this, [this, row]()
{

View File

@ -19,19 +19,20 @@
#include <memory>
#include "BaseEntity.h"
#include "meta/VersionList.h"
class Task;
namespace Meta
{
using VersionListPtr = std::shared_ptr<class VersionList>;
using VersionPtr = std::shared_ptr<class Version>;
class Index : public QAbstractListModel, public BaseEntity
{
Q_OBJECT
public:
explicit Index(QObject *parent = nullptr);
explicit Index(const QVector<VersionList::Ptr> &lists, QObject *parent = nullptr);
explicit Index(const QVector<VersionListPtr> &lists, QObject *parent = nullptr);
enum
{
@ -48,21 +49,21 @@ public:
QString localFilename() const override { return "index.json"; }
// queries
VersionList::Ptr get(const QString &uid);
Version::Ptr get(const QString &uid, const QString &version);
VersionListPtr get(const QString &uid);
VersionPtr get(const QString &uid, const QString &version);
bool hasUid(const QString &uid) const;
QVector<VersionList::Ptr> lists() const { return m_lists; }
QVector<VersionListPtr> lists() const { return m_lists; }
public: // for usage by parsers only
void merge(const std::shared_ptr<Index> &other);
void parse(const QJsonObject &obj) override;
private:
QVector<VersionList::Ptr> m_lists;
QHash<QString, VersionList::Ptr> m_uids;
QVector<VersionListPtr> m_lists;
QHash<QString, VersionListPtr> m_uids;
void connectVersionList(const int row, const VersionList::Ptr &list);
void connectVersionList(const int row, const VersionListPtr &list);
};
}

View File

@ -1,7 +1,7 @@
#include <QTest>
#include <meta/Index.h>
#include <meta/VersionList.h>
#include "meta/Index.h"
#include "meta/VersionList.h"
class IndexTest : public QObject
{

View File

@ -37,11 +37,11 @@ MetadataVersion currentFormatVersion()
static std::shared_ptr<Index> parseIndexInternal(const QJsonObject &obj)
{
const QVector<QJsonObject> objects = requireIsArrayOf<QJsonObject>(obj, "packages");
QVector<VersionList::Ptr> lists;
QVector<VersionListPtr> lists;
lists.reserve(objects.size());
std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject &obj)
{
VersionList::Ptr list = std::make_shared<VersionList>(requireString(obj, "uid"));
VersionListPtr list = std::make_shared<VersionList>(requireString(obj, "uid"));
list->setName(ensureString(obj, "name", QString()));
return list;
});
@ -49,9 +49,9 @@ static std::shared_ptr<Index> parseIndexInternal(const QJsonObject &obj)
}
// Version
static Version::Ptr parseCommonVersion(const QString &uid, const QJsonObject &obj)
static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj)
{
Version::Ptr version = std::make_shared<Version>(uid, requireString(obj, "version"));
VersionPtr version = std::make_shared<Version>(uid, requireString(obj, "version"));
version->setTime(QDateTime::fromString(requireString(obj, "releaseTime"), Qt::ISODate).toMSecsSinceEpoch() / 1000);
version->setType(ensureString(obj, "type", QString()));
version->setRecommended(ensureBoolean(obj, QString("recommended"), false));
@ -63,9 +63,9 @@ static Version::Ptr parseCommonVersion(const QString &uid, const QJsonObject &ob
return version;
}
static Version::Ptr parseVersionInternal(const QJsonObject &obj)
static std::shared_ptr<Version> parseVersionInternal(const QJsonObject &obj)
{
Version::Ptr version = parseCommonVersion(requireString(obj, "uid"), obj);
VersionPtr version = parseCommonVersion(requireString(obj, "uid"), obj);
version->setData(OneSixVersionFormat::versionFileFromJson(QJsonDocument(obj),
QString("%1/%2.json").arg(version->uid(), version->version()),
@ -74,12 +74,12 @@ static Version::Ptr parseVersionInternal(const QJsonObject &obj)
}
// Version list / package
static VersionList::Ptr parseVersionListInternal(const QJsonObject &obj)
static std::shared_ptr<VersionList> parseVersionListInternal(const QJsonObject &obj)
{
const QString uid = requireString(obj, "uid");
const QVector<QJsonObject> versionsRaw = requireIsArrayOf<QJsonObject>(obj, "versions");
QVector<Version::Ptr> versions;
QVector<VersionPtr> versions;
versions.reserve(versionsRaw.size());
std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject &vObj)
{
@ -88,7 +88,7 @@ static VersionList::Ptr parseVersionListInternal(const QJsonObject &obj)
return version;
});
VersionList::Ptr list = std::make_shared<VersionList>(uid);
VersionListPtr list = std::make_shared<VersionList>(uid);
list->setName(ensureString(obj, "name", QString()));
list->setVersions(versions);
return list;

View File

@ -54,7 +54,7 @@ void Meta::Version::parse(const QJsonObject& obj)
parseVersion(obj, this);
}
void Meta::Version::mergeFromList(const Meta::Version::Ptr& other)
void Meta::Version::mergeFromList(const Meta::VersionPtr& other)
{
if(other->m_providesRecommendations)
{
@ -85,7 +85,7 @@ void Meta::Version::mergeFromList(const Meta::Version::Ptr& other)
}
}
void Meta::Version::merge(const Version::Ptr &other)
void Meta::Version::merge(const VersionPtr &other)
{
mergeFromList(other);
if(other->m_data)

View File

@ -30,14 +30,13 @@
namespace Meta
{
using VersionPtr = std::shared_ptr<class Version>;
class Version : public QObject, public BaseVersion, public BaseEntity
{
Q_OBJECT
public:
using Ptr = std::shared_ptr<Version>;
public: /* con/des */
explicit Version(const QString &uid, const QString &version);
virtual ~Version();
@ -79,8 +78,8 @@ public:
return m_data != nullptr;
}
void merge(const Version::Ptr &other);
void mergeFromList(const Version::Ptr &other);
void merge(const VersionPtr &other);
void mergeFromList(const VersionPtr &other);
void parse(const QJsonObject &obj) override;
QString localFilename() const override;
@ -114,4 +113,4 @@ private:
};
}
Q_DECLARE_METATYPE(Meta::Version::Ptr)
Q_DECLARE_METATYPE(Meta::VersionPtr)

View File

@ -40,7 +40,7 @@ bool VersionList::isLoaded()
return BaseEntity::isLoaded();
}
const BaseVersion::Ptr VersionList::at(int i) const
const BaseVersionPtr VersionList::at(int i) const
{
return m_versions.at(i);
}
@ -52,7 +52,7 @@ int VersionList::count() const
void VersionList::sortVersions()
{
beginResetModel();
std::sort(m_versions.begin(), m_versions.end(), [](const Version::Ptr &a, const Version::Ptr &b)
std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b)
{
return *a.get() < *b.get();
});
@ -66,7 +66,7 @@ QVariant VersionList::data(const QModelIndex &index, int role) const
return QVariant();
}
Version::Ptr version = m_versions.at(index.row());
VersionPtr version = m_versions.at(index.row());
switch (role)
{
@ -129,9 +129,9 @@ QString VersionList::humanReadable() const
return m_name.isEmpty() ? m_uid : m_name;
}
Version::Ptr VersionList::getVersion(const QString &version)
VersionPtr VersionList::getVersion(const QString &version)
{
Version::Ptr out = m_lookup.value(version, nullptr);
VersionPtr out = m_lookup.value(version, nullptr);
if(!out)
{
out = std::make_shared<Version>(m_uid, version);
@ -143,7 +143,7 @@ Version::Ptr VersionList::getVersion(const QString &version)
bool VersionList::hasVersion(QString version) const
{
auto ver = std::find_if(m_versions.constBegin(), m_versions.constEnd(),
[&](Meta::Version::Ptr const& a){ return a->version() == version; });
[&](Meta::VersionPtr const& a){ return a->version() == version; });
return (ver != m_versions.constEnd());
}
@ -153,11 +153,11 @@ void VersionList::setName(const QString &name)
emit nameChanged(name);
}
void VersionList::setVersions(const QVector<Version::Ptr> &versions)
void VersionList::setVersions(const QVector<VersionPtr> &versions)
{
beginResetModel();
m_versions = versions;
std::sort(m_versions.begin(), m_versions.end(), [](const Version::Ptr &a, const Version::Ptr &b)
std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b)
{
return a->rawTime() > b->rawTime();
});
@ -168,7 +168,7 @@ void VersionList::setVersions(const QVector<Version::Ptr> &versions)
}
// FIXME: this is dumb, we have 'recommended' as part of the metadata already...
auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const Version::Ptr &ptr) { return ptr->type() == "release"; });
auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; });
m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt;
endResetModel();
}
@ -179,7 +179,7 @@ void VersionList::parse(const QJsonObject& obj)
}
// FIXME: this is dumb, we have 'recommended' as part of the metadata already...
static const Meta::Version::Ptr &getBetterVersion(const Meta::Version::Ptr &a, const Meta::Version::Ptr &b)
static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const Meta::VersionPtr &b)
{
if(!a)
return b;
@ -194,7 +194,7 @@ static const Meta::Version::Ptr &getBetterVersion(const Meta::Version::Ptr &a, c
return (a->type() == "release" ? a : b);
}
void VersionList::mergeFromIndex(const VersionList::Ptr &other)
void VersionList::mergeFromIndex(const VersionListPtr &other)
{
if (m_name != other->m_name)
{
@ -202,7 +202,7 @@ void VersionList::mergeFromIndex(const VersionList::Ptr &other)
}
}
void VersionList::merge(const VersionList::Ptr &other)
void VersionList::merge(const VersionListPtr &other)
{
if (m_name != other->m_name)
{
@ -216,7 +216,7 @@ void VersionList::merge(const VersionList::Ptr &other)
{
qWarning() << "Empty list loaded ...";
}
for (const Version::Ptr &version : other->m_versions)
for (const VersionPtr &version : other->m_versions)
{
// we already have the version. merge the contents
if (m_lookup.contains(version->version()))
@ -235,7 +235,7 @@ void VersionList::merge(const VersionList::Ptr &other)
endResetModel();
}
void VersionList::setupAddedVersion(const int row, const Version::Ptr &version)
void VersionList::setupAddedVersion(const int row, const VersionPtr &version)
{
// FIXME: do not disconnect from everythin, disconnect only the lambdas here
version->disconnect();
@ -244,7 +244,7 @@ void VersionList::setupAddedVersion(const int row, const Version::Ptr &version)
connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TypeRole); });
}
BaseVersion::Ptr VersionList::getRecommended() const
BaseVersionPtr VersionList::getRecommended() const
{
return m_recommended;
}

View File

@ -20,10 +20,10 @@
#include <QJsonObject>
#include <memory>
#include "meta/Version.h"
namespace Meta
{
using VersionPtr = std::shared_ptr<class Version>;
using VersionListPtr = std::shared_ptr<class VersionList>;
class VersionList : public BaseVersionList, public BaseEntity
{
@ -33,8 +33,6 @@ class VersionList : public BaseVersionList, public BaseEntity
public:
explicit VersionList(const QString &uid, QObject *parent = nullptr);
using Ptr = std::shared_ptr<VersionList>;
enum Roles
{
UidRole = Qt::UserRole + 100,
@ -45,11 +43,11 @@ public:
Task::Ptr getLoadTask() override;
bool isLoaded() override;
const BaseVersion::Ptr at(int i) const override;
const BaseVersionPtr at(int i) const override;
int count() const override;
void sortVersions() override;
BaseVersion::Ptr getRecommended() const override;
BaseVersionPtr getRecommended() const override;
QVariant data(const QModelIndex &index, int role) const override;
RoleList providesRoles() const override;
@ -67,38 +65,38 @@ public:
}
QString humanReadable() const;
Version::Ptr getVersion(const QString &version);
VersionPtr getVersion(const QString &version);
bool hasVersion(QString version) const;
QVector<Version::Ptr> versions() const
QVector<VersionPtr> versions() const
{
return m_versions;
}
public: // for usage only by parsers
void setName(const QString &name);
void setVersions(const QVector<Version::Ptr> &versions);
void merge(const VersionList::Ptr &other);
void mergeFromIndex(const VersionList::Ptr &other);
void setVersions(const QVector<VersionPtr> &versions);
void merge(const VersionListPtr &other);
void mergeFromIndex(const VersionListPtr &other);
void parse(const QJsonObject &obj) override;
signals:
void nameChanged(const QString &name);
protected slots:
void updateListData(QList<BaseVersion::Ptr>) override
void updateListData(QList<BaseVersionPtr>) override
{
}
private:
QVector<Version::Ptr> m_versions;
QHash<QString, Version::Ptr> m_lookup;
QVector<VersionPtr> m_versions;
QHash<QString, VersionPtr> m_lookup;
QString m_uid;
QString m_name;
Version::Ptr m_recommended;
VersionPtr m_recommended;
void setupAddedVersion(const int row, const Version::Ptr &version);
void setupAddedVersion(const int row, const VersionPtr &version);
};
}
Q_DECLARE_METATYPE(Meta::VersionList::Ptr)
Q_DECLARE_METATYPE(Meta::VersionListPtr)

View File

@ -1,38 +1,3 @@
// 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/Index.h>
#include "Component.h"
@ -95,7 +60,7 @@ void Component::applyTo(LaunchProfile* profile)
auto vfile = getVersionFile();
if(vfile)
{
vfile->applyTo(profile, m_parent->runtimeContext());
vfile->applyTo(profile);
}
else
{

View File

@ -1,6 +1,6 @@
#include <QTest>
#include <minecraft/GradleSpecifier.h>
#include "minecraft/GradleSpecifier.h"
class GradleSpecifierTest : public QObject
{

View File

@ -173,9 +173,9 @@ void LaunchProfile::applyCompatibleJavaMajors(QList<int>& javaMajor)
m_compatibleJavaMajors.append(javaMajor);
}
void LaunchProfile::applyLibrary(LibraryPtr library, const RuntimeContext & runtimeContext)
void LaunchProfile::applyLibrary(LibraryPtr library)
{
if(!library->isActive(runtimeContext))
if(!library->isActive())
{
return;
}
@ -205,9 +205,9 @@ void LaunchProfile::applyLibrary(LibraryPtr library, const RuntimeContext & runt
}
}
void LaunchProfile::applyMavenFile(LibraryPtr mavenFile, const RuntimeContext & runtimeContext)
void LaunchProfile::applyMavenFile(LibraryPtr mavenFile)
{
if(!mavenFile->isActive(runtimeContext))
if(!mavenFile->isActive())
{
return;
}
@ -221,10 +221,10 @@ void LaunchProfile::applyMavenFile(LibraryPtr mavenFile, const RuntimeContext &
m_mavenFiles.append(Library::limitedCopy(mavenFile));
}
void LaunchProfile::applyAgent(AgentPtr agent, const RuntimeContext & runtimeContext)
void LaunchProfile::applyAgent(AgentPtr agent)
{
auto lib = agent->library();
if(!lib->isActive(runtimeContext))
if(!lib->isActive())
{
return;
}
@ -354,7 +354,7 @@ const QList<int> & LaunchProfile::getCompatibleJavaMajors() const
}
void LaunchProfile::getLibraryFiles(
const RuntimeContext & runtimeContext,
const QString& architecture,
QStringList& jars,
QStringList& nativeJars,
const QString& overridePath,
@ -366,7 +366,7 @@ void LaunchProfile::getLibraryFiles(
nativeJars.clear();
for (auto lib : getLibraries())
{
lib->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath);
lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
}
// NOTE: order is important here, add main jar last to the lists
if(m_mainJar)
@ -379,18 +379,18 @@ void LaunchProfile::getLibraryFiles(
}
else
{
m_mainJar->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath);
m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
}
}
for (auto lib : getNativeLibraries())
{
lib->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath);
lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
}
if(runtimeContext.javaArchitecture == "32")
if(architecture == "32")
{
nativeJars.append(native32);
}
else if(runtimeContext.javaArchitecture == "64")
else if(architecture == "64")
{
nativeJars.append(native64);
}

View File

@ -56,9 +56,9 @@ public: /* application of profile variables from patches */
void applyTweakers(const QStringList &tweakers);
void applyJarMods(const QList<LibraryPtr> &jarMods);
void applyMods(const QList<LibraryPtr> &jarMods);
void applyLibrary(LibraryPtr library, const RuntimeContext & runtimeContext);
void applyMavenFile(LibraryPtr library, const RuntimeContext & runtimeContext);
void applyAgent(AgentPtr agent, const RuntimeContext & runtimeContext);
void applyLibrary(LibraryPtr library);
void applyMavenFile(LibraryPtr library);
void applyAgent(AgentPtr agent);
void applyCompatibleJavaMajors(QList<int>& javaMajor);
void applyMainJar(LibraryPtr jar);
void applyProblemSeverity(ProblemSeverity severity);
@ -83,7 +83,7 @@ public: /* getters for profile variables */
const QList<int> & getCompatibleJavaMajors() const;
const LibraryPtr getMainJar() const;
void getLibraryFiles(
const RuntimeContext & runtimeContext,
const QString & architecture,
QStringList & jars,
QStringList & nativeJars,
const QString & overridePath,

View File

@ -1,38 +1,3 @@
// 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 "MinecraftInstance.h"
@ -42,7 +7,7 @@
#include <BuildConfig.h>
void Library::getApplicableFiles(const RuntimeContext & runtimeContext, QStringList& jar, QStringList& native, QStringList& native32,
void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& native, QStringList& native32,
QStringList& native64, const QString &overridePath) const
{
bool local = isLocal();
@ -56,7 +21,7 @@ void Library::getApplicableFiles(const RuntimeContext & runtimeContext, QStringL
}
return out.absoluteFilePath();
};
QString raw_storage = storageSuffix(runtimeContext);
QString raw_storage = storageSuffix(system);
if(isNative())
{
if (raw_storage.contains("${arch}"))
@ -80,7 +45,7 @@ void Library::getApplicableFiles(const RuntimeContext & runtimeContext, QStringL
}
QList<NetAction::Ptr> Library::getDownloads(
const RuntimeContext & runtimeContext,
OpSys system,
class HttpMetaCache* cache,
QStringList& failedLocalFiles,
const QString & overridePath
@ -142,14 +107,14 @@ QList<NetAction::Ptr> Library::getDownloads(
return true;
};
QString raw_storage = storageSuffix(runtimeContext);
QString raw_storage = storageSuffix(system);
if(m_mojangDownloads)
{
if(isNative())
{
auto nativeClassifier = getCompatibleNative(runtimeContext);
if(!nativeClassifier.isNull())
if(m_nativeClassifiers.contains(system))
{
auto nativeClassifier = m_nativeClassifiers[system];
if(nativeClassifier.contains("${arch}"))
{
auto nat32Classifier = nativeClassifier;
@ -238,7 +203,7 @@ QList<NetAction::Ptr> Library::getDownloads(
return out;
}
bool Library::isActive(const RuntimeContext & runtimeContext) const
bool Library::isActive() const
{
bool result = true;
if (m_rules.empty())
@ -250,7 +215,7 @@ bool Library::isActive(const RuntimeContext & runtimeContext) const
RuleAction ruleResult = Disallow;
for (auto rule : m_rules)
{
RuleAction temp = rule->apply(this, runtimeContext);
RuleAction temp = rule->apply(this);
if (temp != Defer)
ruleResult = temp;
}
@ -258,7 +223,7 @@ bool Library::isActive(const RuntimeContext & runtimeContext) const
}
if (isNative())
{
result = result && !getCompatibleNative(runtimeContext).isNull();
result = result && m_nativeClassifiers.contains(currentSystem);
}
return result;
}
@ -273,19 +238,6 @@ bool Library::isAlwaysStale() const
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)
{
m_storagePrefix = prefix;
@ -305,7 +257,7 @@ QString Library::storagePrefix() const
return m_storagePrefix;
}
QString Library::filename(const RuntimeContext & runtimeContext) const
QString Library::filename(OpSys system) const
{
if(!m_filename.isEmpty())
{
@ -319,10 +271,9 @@ QString Library::filename(const RuntimeContext & runtimeContext) const
// otherwise native, override classifiers. Mojang HACK!
GradleSpecifier nativeSpec = m_name;
QString nativeClassifier = getCompatibleNative(runtimeContext);
if (!nativeClassifier.isNull())
if (m_nativeClassifiers.contains(system))
{
nativeSpec.setClassifier(nativeClassifier);
nativeSpec.setClassifier(m_nativeClassifiers[system]);
}
else
{
@ -331,14 +282,14 @@ QString Library::filename(const RuntimeContext & runtimeContext) const
return nativeSpec.getFileName();
}
QString Library::displayName(const RuntimeContext & runtimeContext) const
QString Library::displayName(OpSys system) const
{
if(!m_displayname.isEmpty())
return m_displayname;
return filename(runtimeContext);
return filename(system);
}
QString Library::storageSuffix(const RuntimeContext & runtimeContext) const
QString Library::storageSuffix(OpSys system) const
{
// non-native? use only the gradle specifier
if (!isNative())
@ -348,10 +299,9 @@ QString Library::storageSuffix(const RuntimeContext & runtimeContext) const
// otherwise native, override classifiers. Mojang HACK!
GradleSpecifier nativeSpec = m_name;
QString nativeClassifier = getCompatibleNative(runtimeContext);
if (!nativeClassifier.isNull())
if (m_nativeClassifiers.contains(system))
{
nativeSpec.setClassifier(nativeClassifier);
nativeSpec.setClassifier(m_nativeClassifiers[system]);
}
else
{

View File

@ -1,44 +1,8 @@
// 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
#include <QString>
#include <net/NetAction.h>
#include <QPair>
#include <QList>
#include <QString>
#include <QStringList>
#include <QMap>
#include <QDir>
@ -46,9 +10,9 @@
#include <memory>
#include "Rule.h"
#include "minecraft/OpSys.h"
#include "GradleSpecifier.h"
#include "MojangDownloadInfo.h"
#include "RuntimeContext.h"
class Library;
class MinecraftInstance;
@ -134,7 +98,7 @@ public: /* methods */
m_repositoryURL = base_url;
}
void getApplicableFiles(const RuntimeContext & runtimeContext, QStringList & jar, QStringList & native,
void getApplicableFiles(OpSys system, QStringList & jar, QStringList & native,
QStringList & native32, QStringList & native64, const QString & overridePath) const;
void setAbsoluteUrl(const QString &absolute_url)
@ -148,7 +112,7 @@ public: /* methods */
}
/// Get the file name of the library
QString filename(const RuntimeContext & runtimeContext) const;
QString filename(OpSys system) const;
// DEPRECATED: set a display name, used by jar mods only
void setDisplayName(const QString & displayName)
@ -157,7 +121,7 @@ public: /* methods */
}
/// Get the file name of the library
QString displayName(const RuntimeContext & runtimeContext) const;
QString displayName(OpSys system) const;
void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info)
{
@ -176,7 +140,7 @@ public: /* methods */
}
/// Returns true if the library should be loaded (or extracted, in case of natives)
bool isActive(const RuntimeContext & runtimeContext) const;
bool isActive() const;
/// Returns true if the library is contained in an instance and false if it is shared
bool isLocal() const;
@ -188,20 +152,18 @@ public: /* methods */
bool isForge() const;
// Get a list of downloads for this library
QList<NetAction::Ptr> getDownloads(const RuntimeContext & runtimeContext, class HttpMetaCache * cache,
QList<NetAction::Ptr> getDownloads(OpSys system, class HttpMetaCache * cache,
QStringList & failedLocalFiles, const QString & overridePath) const;
QString getCompatibleNative(const RuntimeContext & runtimeContext) const;
private: /* methods */
/// the default storage prefix used by Prism Launcher
/// the default storage prefix used by PolyMC
static QString defaultStoragePrefix();
/// Get the prefix - root of the storage to be used
QString storagePrefix() const;
/// Get the relative file path where the library should be saved
QString storageSuffix(const RuntimeContext & runtimeContext) const;
QString storageSuffix(OpSys system) const;
QString hint() const
{
@ -215,23 +177,23 @@ protected: /* data */
/// DEPRECATED URL prefix of the maven repo where the file can be downloaded
QString m_repositoryURL;
/// DEPRECATED: Prism Launcher-specific absolute URL. takes precedence over the implicit maven repo URL, if defined
/// DEPRECATED: PolyMC-specific absolute URL. takes precedence over the implicit maven repo URL, if defined
QString m_absoluteURL;
/// Prism Launcher extension - filename override
/// PolyMC extension - filename override
QString m_filename;
/// DEPRECATED Prism Launcher extension - display name
/// DEPRECATED PolyMC extension - display name
QString m_displayname;
/**
* Prism Launcher-specific type hint - modifies how the library is treated
* PolyMC-specific type hint - modifies how the library is treated
*/
QString m_hint;
/**
* storage - by default the local libraries folder in Prism Launcher, but could be elsewhere
* Prism Launcher specific, because of FTB.
* storage - by default the local libraries folder in polymc, but could be elsewhere
* PolyMC specific, because of FTB.
*/
QString m_storagePrefix;
@ -242,7 +204,7 @@ protected: /* data */
QStringList m_extractExcludes;
/// native suffixes per OS
QMap<QString, QString> m_nativeClassifiers;
QMap<OpSys, QString> m_nativeClassifiers;
/// true if the library had a rules section (even empty)
bool applyRules = false;

View File

@ -1,46 +1,10 @@
// 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 <minecraft/MojangVersionFormat.h>
#include <minecraft/OneSixVersionFormat.h>
#include <minecraft/Library.h>
#include <net/HttpMetaCache.h>
#include <FileSystem.h>
#include <RuntimeContext.h>
#include "minecraft/MojangVersionFormat.h"
#include "minecraft/OneSixVersionFormat.h"
#include "minecraft/Library.h"
#include "net/HttpMetaCache.h"
#include "FileSystem.h"
class LibraryTest : public QObject
{
@ -60,31 +24,22 @@ private:
{
return {FS::PathCombine(cache->getBasePath("libraries"), relative)};
}
RuntimeContext dummyContext(QString system = "linux", QString arch = "64", QString realArch = "amd64") {
RuntimeContext r;
r.javaArchitecture = arch;
r.javaRealArchitecture = realArch;
r.system = system;
return r;
}
private
slots:
void initTestCase()
{
cache.reset(new HttpMetaCache());
cache->addBase("libraries", QDir("libraries").absolutePath());
dataDir = QDir(QFINDTESTDATA("testdata/Library")).absolutePath();
dataDir = QDir(QFINDTESTDATA("testdata")).absolutePath();
}
void test_legacy()
{
RuntimeContext r = dummyContext();
Library test("test.package:testname:testversion");
QCOMPARE(test.artifactPrefix(), QString("test.package:testname"));
QCOMPARE(test.isNative(), false);
QStringList jar, native, native32, native64;
test.getApplicableFiles(r, jar, native, native32, native64, QString());
test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString());
QCOMPARE(jar, getStorage("test/package/testname/testversion/testname-testversion.jar"));
QCOMPARE(native, {});
QCOMPARE(native32, {});
@ -92,11 +47,10 @@ slots:
}
void test_legacy_url()
{
RuntimeContext r = dummyContext();
QStringList failedFiles;
Library test("test.package:testname:testversion");
test.setRepositoryURL("file://foo/bar");
auto downloads = test.getDownloads(r, cache.get(), failedFiles, QString());
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString());
QCOMPARE(downloads.size(), 1);
QCOMPARE(failedFiles, {});
NetAction::Ptr dl = downloads[0];
@ -104,50 +58,47 @@ slots:
}
void test_legacy_url_local_broken()
{
RuntimeContext r = dummyContext();
Library test("test.package:testname:testversion");
QCOMPARE(test.isNative(), false);
QStringList failedFiles;
test.setHint("local");
auto downloads = test.getDownloads(r, cache.get(), failedFiles, QString());
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString());
QCOMPARE(downloads.size(), 0);
QCOMPARE(failedFiles, {"testname-testversion.jar"});
}
void test_legacy_url_local_override()
{
RuntimeContext r = dummyContext();
Library test("com.paulscode:codecwav:20101023");
QCOMPARE(test.isNative(), false);
QStringList failedFiles;
test.setHint("local");
auto downloads = test.getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library"));
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QFINDTESTDATA("testdata"));
QCOMPARE(downloads.size(), 0);
qDebug() << failedFiles;
QCOMPARE(failedFiles.size(), 0);
QStringList jar, native, native32, native64;
test.getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Library"));
QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/Library/codecwav-20101023.jar")).absoluteFilePath()});
test.getApplicableFiles(currentSystem, jar, native, native32, native64, QFINDTESTDATA("testdata"));
QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
void test_legacy_native()
{
RuntimeContext r = dummyContext();
Library test("test.package:testname:testversion");
test.m_nativeClassifiers["linux"] = "linux";
test.m_nativeClassifiers[OpSys::Os_Linux]="linux";
QCOMPARE(test.isNative(), true);
test.setRepositoryURL("file://foo/bar");
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(r, jar, native, native32, native64, QString());
test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, getStorage("test/package/testname/testversion/testname-testversion-linux.jar"));
QCOMPARE(native32, {});
QCOMPARE(native64, {});
QStringList failedFiles;
auto dls = test.getDownloads(r, cache.get(), failedFiles, QString());
auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {});
auto dl = dls[0];
@ -156,52 +107,49 @@ slots:
}
void test_legacy_native_arch()
{
RuntimeContext r = dummyContext();
Library test("test.package:testname:testversion");
test.m_nativeClassifiers["linux"]="linux-${arch}";
test.m_nativeClassifiers["osx"]="osx-${arch}";
test.m_nativeClassifiers["windows"]="windows-${arch}";
test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}";
test.m_nativeClassifiers[OpSys::Os_OSX]="osx-${arch}";
test.m_nativeClassifiers[OpSys::Os_Windows]="windows-${arch}";
QCOMPARE(test.isNative(), true);
test.setRepositoryURL("file://foo/bar");
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(r, jar, native, native32, native64, QString());
test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-linux-32.jar"));
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(r, cache.get(), failedFiles, QString());
auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar"));
}
r.system = "windows";
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(r, jar, native, native32, native64, QString());
test.getApplicableFiles(Os_Windows, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-windows-32.jar"));
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-windows-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(r, cache.get(), failedFiles, QString());
auto dls = test.getDownloads(Os_Windows, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar"));
}
r.system = "osx";
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(r, jar, native, native32, native64, QString());
test.getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-osx-32.jar"));
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-osx-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(r, cache.get(), failedFiles, QString());
auto dls = test.getDownloads(Os_OSX, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar"));
@ -210,112 +158,103 @@ slots:
}
void test_legacy_native_arch_local_override()
{
RuntimeContext r = dummyContext();
Library test("test.package:testname:testversion");
test.m_nativeClassifiers["linux"]="linux-${arch}";
test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}";
test.setHint("local");
QCOMPARE(test.isNative(), true);
test.setRepositoryURL("file://foo/bar");
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Library"));
test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QFINDTESTDATA("testdata"));
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, {QFileInfo(QFINDTESTDATA("testdata/Library/testname-testversion-linux-32.jar")).absoluteFilePath()});
QCOMPARE(native64, {QFileInfo(QFINDTESTDATA("testdata/Library") + "/testname-testversion-linux-64.jar").absoluteFilePath()});
QCOMPARE(native32, {QFileInfo(QFINDTESTDATA("testdata/testname-testversion-linux-32.jar")).absoluteFilePath()});
QCOMPARE(native64, {QFileInfo(QFINDTESTDATA("testdata") + "/testname-testversion-linux-64.jar").absoluteFilePath()});
QStringList failedFiles;
auto dls = test.getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library"));
auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata"));
QCOMPARE(dls.size(), 0);
QCOMPARE(failedFiles, {QFileInfo(QFINDTESTDATA("testdata/Library") + "/testname-testversion-linux-64.jar").absoluteFilePath()});
QCOMPARE(failedFiles, {QFileInfo(QFINDTESTDATA("testdata") + "/testname-testversion-linux-64.jar").absoluteFilePath()});
}
}
void test_onenine()
{
RuntimeContext r = dummyContext("osx");
auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-simple.json"));
auto test = readMojangJson(QFINDTESTDATA("testdata/lib-simple.json"));
{
QStringList jar, native, native32, native64;
test->getApplicableFiles(r, jar, native, native32, native64, QString());
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
QCOMPARE(jar, getStorage("com/paulscode/codecwav/20101023/codecwav-20101023.jar"));
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
r.system = "linux";
{
QStringList failedFiles;
auto dls = test->getDownloads(r, cache.get(), failedFiles, QString());
auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar"));
}
r.system = "osx";
test->setHint("local");
{
QStringList jar, native, native32, native64;
test->getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Library"));
QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/Library/codecwav-20101023.jar")).absoluteFilePath()});
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QFINDTESTDATA("testdata"));
QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
r.system = "linux";
{
QStringList failedFiles;
auto dls = test->getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library"));
auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata"));
QCOMPARE(dls.size(), 0);
QCOMPARE(failedFiles, {});
}
}
void test_onenine_local_override()
{
RuntimeContext r = dummyContext("osx");
auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-simple.json"));
auto test = readMojangJson(QFINDTESTDATA("testdata/lib-simple.json"));
test->setHint("local");
{
QStringList jar, native, native32, native64;
test->getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Library"));
QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/Library/codecwav-20101023.jar")).absoluteFilePath()});
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QFINDTESTDATA("testdata"));
QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
r.system = "linux";
{
QStringList failedFiles;
auto dls = test->getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library"));
auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata"));
QCOMPARE(dls.size(), 0);
QCOMPARE(failedFiles, {});
}
}
void test_onenine_native()
{
RuntimeContext r = dummyContext("osx");
auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-native.json"));
auto test = readMojangJson(QFINDTESTDATA("testdata/lib-native.json"));
QStringList jar, native, native32, native64;
test->getApplicableFiles(r, jar, native, native32, native64, QString());
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
QCOMPARE(jar, QStringList());
QCOMPARE(native, getStorage("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
QCOMPARE(native32, {});
QCOMPARE(native64, {});
QStringList failedFiles;
auto dls = test->getDownloads(r, cache.get(), failedFiles, QString());
auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
}
void test_onenine_native_arch()
{
RuntimeContext r = dummyContext("windows");
auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-native-arch.json"));
auto test = readMojangJson(QFINDTESTDATA("testdata/lib-native-arch.json"));
QStringList jar, native, native32, native64;
test->getApplicableFiles(r, jar, native, native32, native64, QString());
test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"));
QCOMPARE(native64, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"));
QStringList failedFiles;
auto dls = test->getDownloads(r, cache.get(), failedFiles, QString());
auto dls = test->getDownloads(Os_Windows, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"));

View File

@ -43,6 +43,7 @@
#include "settings/SettingsObject.h"
#include "Application.h"
#include "MMCStrings.h"
#include "pathmatcher/RegexpMatcher.h"
#include "pathmatcher/MultiMatcher.h"
#include "FileSystem.h"
@ -75,7 +76,6 @@
#include "mod/ModFolderModel.h"
#include "mod/ResourcePackFolderModel.h"
#include "mod/ShaderPackFolderModel.h"
#include "mod/TexturePackFolderModel.h"
#include "WorldList.h"
@ -115,19 +115,6 @@ private:
MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &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
auto javaOverride = m_settings->registerSetting("OverrideJava", false);
auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false);
@ -137,66 +124,64 @@ void MinecraftInstance::loadSpecificSettings()
auto javaOrLocation = std::make_shared<OrSetting>("JavaOrLocationOverride", javaOverride, locationOverride);
auto javaOrArgs = std::make_shared<OrSetting>("JavaOrArgsOverride", javaOverride, argsOverride);
if (auto global_settings = globalSettings()) {
m_settings->registerOverride(global_settings->getSetting("JavaPath"), javaOrLocation);
m_settings->registerOverride(global_settings->getSetting("JvmArgs"), javaOrArgs);
m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation);
m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs);
m_settings->registerOverride(globalSettings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
// special!
m_settings->registerPassthrough(global_settings->getSetting("JavaTimestamp"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), javaOrLocation);
// special!
m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation);
m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation);
m_settings->registerPassthrough(globalSettings->getSetting("JavaArchitecture"), javaOrLocation);
// Window Size
auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
m_settings->registerOverride(global_settings->getSetting("LaunchMaximized"), windowSetting);
m_settings->registerOverride(global_settings->getSetting("MinecraftWinWidth"), windowSetting);
m_settings->registerOverride(global_settings->getSetting("MinecraftWinHeight"), windowSetting);
// Window Size
auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowSetting);
m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowSetting);
m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowSetting);
// Memory
auto memorySetting = m_settings->registerSetting("OverrideMemory", false);
m_settings->registerOverride(global_settings->getSetting("MinMemAlloc"), memorySetting);
m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting);
m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting);
// Memory
auto memorySetting = m_settings->registerSetting("OverrideMemory", false);
m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting);
m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting);
m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting);
// Minecraft launch method
auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
m_settings->registerOverride(global_settings->getSetting("MCLaunchMethod"), launchMethodOverride);
// Minecraft launch method
auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride);
// Native library workarounds
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
// Native library workarounds
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
// Peformance related options
auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
m_settings->registerOverride(global_settings->getSetting("EnableFeralGamemode"), performanceOverride);
m_settings->registerOverride(global_settings->getSetting("EnableMangoHud"), performanceOverride);
m_settings->registerOverride(global_settings->getSetting("UseDiscreteGpu"), performanceOverride);
// Peformance related options
auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
m_settings->registerOverride(globalSettings->getSetting("EnableFeralGamemode"), performanceOverride);
m_settings->registerOverride(globalSettings->getSetting("EnableMangoHud"), performanceOverride);
m_settings->registerOverride(globalSettings->getSetting("UseDiscreteGpu"), performanceOverride);
// Miscellaneous
auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false);
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
m_settings->set("InstanceType", "OneSix");
}
// Game time
auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false);
m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride);
m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride);
// Join server on launch, this does not have a global override
m_settings->registerSetting("JoinServerOnLaunch", false);
m_settings->registerSetting("JoinServerOnLaunchAddress", "");
qDebug() << "Instance-type specific settings were loaded!";
// Miscellaneous
auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false);
m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
setSpecificSettingsLoaded(true);
m_settings->set("InstanceType", "OneSix");
updateRuntimeContext();
m_components.reset(new PackProfile(this));
}
void MinecraftInstance::updateRuntimeContext()
void MinecraftInstance::saveNow()
{
m_runtimeContext.updateFromInstanceSettings(m_settings);
m_components->saveNow();
}
QString MinecraftInstance::typeName() const
@ -252,14 +237,6 @@ QString MinecraftInstance::getLocalLibraryPath() const
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
{
QDir jarmods_dir(FS::PathCombine(instanceRoot(), "jarmods/"));
@ -331,11 +308,12 @@ QDir MinecraftInstance::versionsPath() const
return QDir::current().absoluteFilePath("versions");
}
QStringList MinecraftInstance::getClassPath()
QStringList MinecraftInstance::getClassPath() const
{
QStringList jars, nativeJars;
auto javaArchitecture = settings()->get("JavaArchitecture").toString();
auto profile = m_components->getProfile();
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
return jars;
}
@ -345,15 +323,16 @@ QString MinecraftInstance::getMainClass() const
return profile->getMainClass();
}
QStringList MinecraftInstance::getNativeJars()
QStringList MinecraftInstance::getNativeJars() const
{
QStringList jars, nativeJars;
auto javaArchitecture = settings()->get("JavaArchitecture").toString();
auto profile = m_components->getProfile();
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
return nativeJars;
}
QStringList MinecraftInstance::extraArguments()
QStringList MinecraftInstance::extraArguments() const
{
auto list = BaseInstance::extraArguments();
auto version = getPackProfile();
@ -373,13 +352,13 @@ QStringList MinecraftInstance::extraArguments()
for (auto agent : agents)
{
QStringList jar, temp1, temp2, temp3;
agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath());
agent->library()->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, getLocalLibraryPath());
list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument()));
}
return list;
}
QStringList MinecraftInstance::javaArguments()
QStringList MinecraftInstance::javaArguments() const
{
QStringList args;
@ -436,7 +415,7 @@ QStringList MinecraftInstance::javaArguments()
return args;
}
QMap<QString, QString> MinecraftInstance::getVariables()
QMap<QString, QString> MinecraftInstance::getVariables() const
{
QMap<QString, QString> out;
out.insert("INST_NAME", name());
@ -468,11 +447,13 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
QProcessEnvironment env = createEnvironment();
#ifdef Q_OS_LINUX
if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud)
if (settings()->get("EnableMangoHud").toBool())
{
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_LIBRARY_PATH", lib_path);
env.insert("MANGOHUD", "1");
}
@ -630,7 +611,8 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
// libraries and class path.
{
QStringList jars, nativeJars;
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
auto javaArchitecture = settings()->get("JavaArchitecture").toString();
profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
for(auto file: jars)
{
launchScript += "cp " + file + "\n";
@ -686,7 +668,8 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
{
out << "Libraries:";
QStringList jars, nativeJars;
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
auto javaArchitecture = settings->get("JavaArchitecture").toString();
profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
auto printLibFile = [&](const QString & path)
{
QFileInfo info(path);
@ -717,14 +700,14 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
{
out << QString("%1:").arg(label);
auto modList = model.allMods();
std::sort(modList.begin(), modList.end(), [](auto a, auto b) {
std::sort(modList.begin(), modList.end(), [](Mod::Ptr a, Mod::Ptr b) {
auto aName = a->fileinfo().completeBaseName();
auto bName = b->fileinfo().completeBaseName();
return aName.localeAwareCompare(bName) < 0;
});
for(auto mod: modList)
{
if(mod->type() == ResourceType::FOLDER)
if(mod->type() == Mod::MOD_FOLDER)
{
out << u8" [🖿] " + mod->fileinfo().completeBaseName() + " (folder)";
continue;
@ -751,8 +734,8 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << "Jar Mods:";
for(auto & jarmod: jarMods)
{
auto displayname = jarmod->displayName(runtimeContext());
auto realname = jarmod->filename(runtimeContext());
auto displayname = jarmod->displayName(currentSystem);
auto realname = jarmod->filename(currentSystem);
if(displayname != realname)
{
out << " " + displayname + " (" + realname + ")";
@ -914,7 +897,6 @@ QString MinecraftInstance::getStatusbarDescription()
Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode)
{
updateRuntimeContext();
switch (mode)
{
case Net::Mode::Offline:
@ -931,7 +913,6 @@ Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode)
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
{
updateRuntimeContext();
// FIXME: get rid of shared_from_this ...
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this()));
auto pptr = process.get();
@ -962,9 +943,9 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(new CreateGameFolders(pptr));
}
if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool())
if (!serverToJoin && m_settings->get("JoinServerOnLaunch").toBool())
{
QString fullAddress = settings()->get("JoinServerOnLaunchAddress").toString();
QString fullAddress = m_settings->get("JoinServerOnLaunchAddress").toString();
serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress)));
}
@ -1072,10 +1053,10 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
QString MinecraftInstance::launchMethod()
{
return settings()->get("MCLaunchMethod").toString();
return m_settings->get("MCLaunchMethod").toString();
}
JavaVersion MinecraftInstance::getJavaVersion()
JavaVersion MinecraftInstance::getJavaVersion() const
{
return JavaVersion(settings()->get("JavaVersion").toString());
}
@ -1104,18 +1085,18 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const
return m_core_mod_list;
}
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList() const
std::shared_ptr<ModFolderModel> MinecraftInstance::resourcePackList() const
{
if (!m_resource_pack_list)
{
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir()));
m_resource_pack_list->enableInteraction(!isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ResourcePackFolderModel::disableInteraction);
m_resource_pack_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ModFolderModel::disableInteraction);
}
return m_resource_pack_list;
}
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList() const
std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const
{
if (!m_texture_pack_list)
{
@ -1126,11 +1107,11 @@ std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList() con
return m_texture_pack_list;
}
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList() const
std::shared_ptr<ModFolderModel> MinecraftInstance::shaderPackList() const
{
if (!m_shader_pack_list)
{
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir()));
m_shader_pack_list.reset(new ResourcePackFolderModel(shaderPacksDir()));
m_shader_pack_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_shader_pack_list.get(), &ModFolderModel::disableInteraction);
}
@ -1162,7 +1143,7 @@ QList<Mod*> MinecraftInstance::getJarMods() const
for (auto jarmod : profile->getJarMods())
{
QStringList jar, temp1, temp2, temp3;
jarmod->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, jarmodsPath().absolutePath());
jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath());
// QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem));
mods.push_back(new Mod(QFileInfo(jar[0])));
}

View File

@ -1,38 +1,3 @@
// 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
#include "BaseInstance.h"
#include <java/JavaVersion.h>
@ -42,10 +7,6 @@
#include "minecraft/launch/MinecraftServerTarget.h"
class ModFolderModel;
class ResourceFolderModel;
class ResourcePackFolderModel;
class ShaderPackFolderModel;
class TexturePackFolderModel;
class WorldList;
class GameOptions;
class LaunchStep;
@ -59,8 +20,6 @@ public:
virtual ~MinecraftInstance() {};
virtual void saveNow() override;
void loadSpecificSettings() override;
// FIXME: remove
QString typeName() const override;
// FIXME: remove
@ -104,10 +63,6 @@ public:
// where the instance-local libraries should be
QString getLocalLibraryPath() const;
/** Returns whether the instance, with its version, has support for demo mode. */
[[nodiscard]] bool supportsDemo() const;
void updateRuntimeContext();
////// Profile management //////
std::shared_ptr<PackProfile> getPackProfile() const;
@ -115,24 +70,24 @@ public:
////// Mod Lists //////
std::shared_ptr<ModFolderModel> loaderModList() const;
std::shared_ptr<ModFolderModel> coreModList() const;
std::shared_ptr<ResourcePackFolderModel> resourcePackList() const;
std::shared_ptr<TexturePackFolderModel> texturePackList() const;
std::shared_ptr<ShaderPackFolderModel> shaderPackList() const;
std::shared_ptr<ModFolderModel> resourcePackList() const;
std::shared_ptr<ModFolderModel> texturePackList() const;
std::shared_ptr<ModFolderModel> shaderPackList() const;
std::shared_ptr<WorldList> worldList() const;
std::shared_ptr<GameOptions> gameOptionsModel() const;
////// Launch stuff //////
Task::Ptr createUpdateTask(Net::Mode mode) override;
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override;
QStringList extraArguments() override;
QStringList extraArguments() const override;
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override;
QList<Mod*> getJarMods() const;
QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin);
/// get arguments passed to java
QStringList javaArguments();
QStringList javaArguments() const;
/// get variables for launch command variable substitution/environment
QMap<QString, QString> getVariables() override;
QMap<QString, QString> getVariables() const override;
/// create an environment for launching processes
QProcessEnvironment createEnvironment() override;
@ -148,16 +103,16 @@ public:
QString getStatusbarDescription() override;
// FIXME: remove
virtual QStringList getClassPath();
virtual QStringList getClassPath() const;
// FIXME: remove
virtual QStringList getNativeJars();
virtual QStringList getNativeJars() const;
// FIXME: remove
virtual QString getMainClass() const;
// FIXME: remove
virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) const;
virtual JavaVersion getJavaVersion();
virtual JavaVersion getJavaVersion() const;
protected:
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
@ -168,9 +123,9 @@ protected: // data
std::shared_ptr<PackProfile> m_components;
mutable std::shared_ptr<ModFolderModel> m_loader_mod_list;
mutable std::shared_ptr<ModFolderModel> m_core_mod_list;
mutable std::shared_ptr<ResourcePackFolderModel> m_resource_pack_list;
mutable std::shared_ptr<ShaderPackFolderModel> m_shader_pack_list;
mutable std::shared_ptr<TexturePackFolderModel> m_texture_pack_list;
mutable std::shared_ptr<ModFolderModel> m_resource_pack_list;
mutable std::shared_ptr<ModFolderModel> m_shader_pack_list;
mutable std::shared_ptr<ModFolderModel> m_texture_pack_list;
mutable std::shared_ptr<WorldList> m_world_list;
mutable std::shared_ptr<GameOptions> m_game_options;
};

View File

@ -43,7 +43,7 @@ void MinecraftUpdate::executeTask()
m_tasks.clear();
// create folders
{
m_tasks.append(new FoldersTask(m_inst));
m_tasks.append(std::make_shared<FoldersTask>(m_inst));
}
// add metadata update task if necessary
@ -53,23 +53,23 @@ void MinecraftUpdate::executeTask()
auto task = components->getCurrentTask();
if(task)
{
m_tasks.append(task);
m_tasks.append(task.unwrap());
}
}
// libraries download
{
m_tasks.append(new LibrariesTask(m_inst));
m_tasks.append(std::make_shared<LibrariesTask>(m_inst));
}
// FML libraries download and copy into the instance
{
m_tasks.append(new FMLLibrariesTask(m_inst));
m_tasks.append(std::make_shared<FMLLibrariesTask>(m_inst));
}
// assets update
{
m_tasks.append(new AssetUpdateTask(m_inst));
m_tasks.append(std::make_shared<AssetUpdateTask>(m_inst));
}
if(!m_preFailure.isEmpty())

View File

@ -50,7 +50,7 @@ private:
private:
MinecraftInstance *m_inst = nullptr;
QList<Task::Ptr> m_tasks;
QList<std::shared_ptr<Task>> m_tasks;
QString m_preFailure;
int m_currentTask = -1;
bool m_abort = false;

View File

@ -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!")
.arg(out->minimumLauncherVersion)
.arg(CURRENT_MINIMUM_LAUNCHER_VERSION)
.arg(BuildConfig.LAUNCHER_DISPLAYNAME)
.arg(BuildConfig.LAUNCHER_NAME)
);
}
}
@ -362,8 +362,11 @@ LibraryPtr MojangVersionFormat::libraryFromJson(ProblemContainer & problems, con
{
qWarning() << filename << "contains an invalid native (skipping)";
}
// FIXME: Skip unknown platforms
out->m_nativeClassifiers[it.key()] = it.value().toString();
OpSys opSys = OpSys_fromString(it.key());
if (opSys != Os_Other)
{
out->m_nativeClassifiers[opSys] = it.value().toString();
}
}
}
if (libObj.contains("rules"))
@ -392,7 +395,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
auto iter = library->m_nativeClassifiers.begin();
while (iter != library->m_nativeClassifiers.end())
{
nativeList.insert(iter.key(), iter.value());
nativeList.insert(OpSys_toString(iter.key()), iter.value());
iter++;
}
libRoot.insert("natives", nativeList);

View File

@ -1,7 +1,7 @@
#include <QTest>
#include <QDebug>
#include <minecraft/MojangVersionFormat.h>
#include "minecraft/MojangVersionFormat.h"
class MojangVersionFormatTest : public QObject
{
@ -29,7 +29,7 @@ private
slots:
void test_Through_Simple()
{
QJsonDocument doc = readJson(QFINDTESTDATA("testdata/MojangVersionFormat/1.9-simple.json"));
QJsonDocument doc = readJson(QFINDTESTDATA("testdata/1.9-simple.json"));
auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9-simple.json");
auto doc2 = MojangVersionFormat::versionFileToJson(vfile);
writeJson("1.9-simple-passthorugh.json", doc2);
@ -39,7 +39,7 @@ slots:
void test_Through()
{
QJsonDocument doc = readJson(QFINDTESTDATA("testdata/MojangVersionFormat/1.9.json"));
QJsonDocument doc = readJson(QFINDTESTDATA("testdata/1.9.json"));
auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9.json");
auto doc2 = MojangVersionFormat::versionFileToJson(vfile);
writeJson("1.9-passthorugh.json", doc2);

View File

@ -0,0 +1,46 @@
/* 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";
}
}

View File

@ -0,0 +1,38 @@
/* 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

View File

@ -273,11 +273,6 @@ void PackProfile::scheduleSave()
d->m_saveTimer.start();
}
RuntimeContext PackProfile::runtimeContext()
{
return d->m_instance->runtimeContext();
}
QString PackProfile::componentsFilePath() const
{
return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json");
@ -789,7 +784,7 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch)
return true;
}
QStringList jar, temp1, temp2, temp3;
jarMod->getApplicableFiles(d->m_instance->runtimeContext(), jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath());
jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath());
QFileInfo finfo (jar[0]);
if(finfo.exists())
{

View File

@ -1,36 +1,16 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
/* Copyright 2013-2021 MultiMC Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* http://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
@ -124,9 +104,6 @@ public:
/// if there is a save scheduled, do it now.
void saveNow();
/// helper method, returns RuntimeContext of instance
RuntimeContext runtimeContext();
signals:
void minecraftChanged();

View File

@ -1,6 +1,6 @@
#include <QTest>
#include <minecraft/ParseUtils.h>
#include "minecraft/ParseUtils.h"
class ParseUtilsTest : public QObject
{

View File

@ -1,36 +1,16 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
/* Copyright 2013-2021 MultiMC Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* http://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QJsonObject>
@ -80,10 +60,10 @@ QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules)
auto osNameVal = osObj.value("name");
if (!osNameVal.isString())
continue;
QString osName = osNameVal.toString();
OpSys requiredOs = OpSys_fromString(osNameVal.toString());
QString versionRegex = osObj.value("version").toString();
// add a new OS rule
rules.append(OsRule::create(action, osName, versionRegex));
rules.append(OsRule::create(action, requiredOs, versionRegex));
}
return rules;
}
@ -101,7 +81,7 @@ QJsonObject OsRule::toJson()
ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow"));
QJsonObject osObj;
{
osObj.insert("name", m_system);
osObj.insert("name", OpSys_toString(m_system));
if(!m_version_regexp.isEmpty())
{
osObj.insert("version", m_version_regexp);

View File

@ -1,36 +1,16 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
/* Copyright 2013-2021 MultiMC Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* http://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
@ -39,7 +19,7 @@
#include <QList>
#include <QJsonObject>
#include <memory>
#include "RuntimeContext.h"
#include "OpSys.h"
class Library;
class Rule;
@ -57,7 +37,7 @@ class Rule
{
protected:
RuleAction m_result;
virtual bool applies(const Library *parent, const RuntimeContext & runtimeContext) = 0;
virtual bool applies(const Library *parent) = 0;
public:
Rule(RuleAction result) : m_result(result)
@ -65,9 +45,9 @@ public:
}
virtual ~Rule() {};
virtual QJsonObject toJson() = 0;
RuleAction apply(const Library *parent, const RuntimeContext & runtimeContext)
RuleAction apply(const Library *parent)
{
if (applies(parent, runtimeContext))
if (applies(parent))
return m_result;
else
return Defer;
@ -78,23 +58,23 @@ class OsRule : public Rule
{
private:
// the OS
QString m_system;
OpSys m_system;
// the OS version regexp
QString m_version_regexp;
protected:
virtual bool applies(const Library *, const RuntimeContext & runtimeContext)
virtual bool applies(const Library *)
{
return runtimeContext.classifierMatches(m_system);
return (m_system == currentSystem);
}
OsRule(RuleAction result, QString system, QString version_regexp)
OsRule(RuleAction result, OpSys system, QString version_regexp)
: Rule(result), m_system(system), m_version_regexp(version_regexp)
{
}
public:
virtual QJsonObject toJson();
static std::shared_ptr<OsRule> create(RuleAction result, QString system,
static std::shared_ptr<OsRule> create(RuleAction result, OpSys system,
QString version_regexp)
{
return std::shared_ptr<OsRule>(new OsRule(result, system, version_regexp));
@ -104,7 +84,7 @@ public:
class ImplicitRule : public Rule
{
protected:
virtual bool applies(const Library *, const RuntimeContext & runtimeContext)
virtual bool applies(const Library *)
{
return true;
}

View File

@ -1,34 +0,0 @@
#include "VanillaInstanceCreationTask.h"
#include <utility>
#include "FileSystem.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "settings/INISettingsObject.h"
VanillaCreationTask::VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr 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;
}

View File

@ -1,22 +0,0 @@
#pragma once
#include "InstanceCreationTask.h"
#include <utility>
class VanillaCreationTask final : public InstanceCreationTask {
Q_OBJECT
public:
VanillaCreationTask(BaseVersion::Ptr version) : InstanceCreationTask(), m_version(std::move(version)) {}
VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version);
bool createInstance() override;
private:
// Version to update to / create of the instance.
BaseVersion::Ptr m_version;
bool m_using_loader = false;
QString m_loader;
BaseVersion::Ptr m_loader_version;
};

View File

@ -51,7 +51,7 @@ static bool isMinecraftVersion(const QString &uid)
return uid == "net.minecraft";
}
void VersionFile::applyTo(LaunchProfile *profile, const RuntimeContext & runtimeContext)
void VersionFile::applyTo(LaunchProfile *profile)
{
// Only real Minecraft can set those. Don't let anything override them.
if (isMinecraftVersion(uid))
@ -77,15 +77,15 @@ void VersionFile::applyTo(LaunchProfile *profile, const RuntimeContext & runtime
for (auto library : libraries)
{
profile->applyLibrary(library, runtimeContext);
profile->applyLibrary(library);
}
for (auto mavenFile : mavenFiles)
{
profile->applyMavenFile(mavenFile, runtimeContext);
profile->applyMavenFile(mavenFile);
}
for (auto agent : agents)
{
profile->applyAgent(agent, runtimeContext);
profile->applyAgent(agent);
}
profile->applyProblemSeverity(getProblemSeverity());
}

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