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
824 changed files with 17134 additions and 39930 deletions

2
.github/FUNDING.yml vendored
View File

@ -1 +1 @@
open_collective: prismlauncher
open_collective: polymc

View File

@ -8,9 +8,9 @@ body:
If you need help with running Minecraft, please visit us on our Discord before making a bug report.
Before submitting a bug report, please make sure you have read this *entire* form, and that:
* You have read the [Prism Launcher wiki](https://prismlauncher.org/wiki/) and it has not answered your question.
* You have read the [PolyMC wiki](https://polymc.org/wiki/) and it has not answered your question.
* Your bug is not caused by Minecraft or any mods you have installed.
* Your issue has not been reported before, [make sure to use the search function!](https://github.com/PrismLauncher/PrismLauncher/issues)
* Your issue has not been reported before, [make sure to use the search function!](https://github.com/PolyMC/PolyMC/issues)
**Do not forget to give your issue a descriptive title.** "Bug in the instance screen" makes it hard to distinguish issues at a glance.
- type: dropdown
@ -25,16 +25,9 @@ body:
- Other
- type: textarea
attributes:
label: Version of Prism Launcher
description: The version of Prism Launcher used in the bug report.
placeholder: Prism Launcher 5.0
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 Prism Launcher -> About Qt.
placeholder: Qt 6.3.0
label: Version of PolyMC
description: The version of PolyMC used in the bug report.
placeholder: PolyMC 1.3.2
validations:
required: true
- type: textarea

View File

@ -1,5 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Prism Launcher Matrix Support Room
url: https://matrix.to/#/#prism-support:matrix.org
- name: PolyMC Matrix Support Room
url: https://matrix.to/#/#support:polymc.org
about: Please ask for support here before opening an issue.

View File

@ -6,7 +6,7 @@ body:
- type: markdown
attributes:
value: |
### Use this form to suggest a larger change for Prism Launcher.
### Use this form to suggest a larger change for PolyMC.
- type: textarea
attributes:
label: Goal
@ -18,7 +18,7 @@ body:
attributes:
label: Motivation
description: |
Introduce the topic. If this is a not-well-known section of Prism Launcher, a detailed explanation of the background is recommended.
Introduce the topic. If this is a not-well-known section of PolyMC, a detailed explanation of the background is recommended.
Some example points of discussion:
- What specific problems are you facing right now that you're trying to address?
- Are there any previous discussions? Link to them and summarize them (don't force your readers to read them though!).

View File

@ -5,25 +5,25 @@ body:
- type: markdown
attributes:
value: |
### Use this form to suggest a feature for Prism Launcher.
### Use this form to suggest a feature for PolyMC.
- type: input
attributes:
label: Role
description: In what way do you use Prism Launcher that needs this feature?
description: In what way do you use PolyMC that needs this feature?
placeholder: I play modded Minecraft.
validations:
required: true
- type: input
attributes:
label: Suggestion
description: What do you want Prism Launcher to do?
description: What do you want PolyMC to do?
placeholder: I want the cat button to meow.
validations:
required: true
- type: input
attributes:
label: Benefit
description: Why do you need Prism Launcher to do this?
description: Why do you need PolyMC to do this?
placeholder: so that I can always hear a cat when I need to.
validations:
required: true

View File

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

View File

@ -1,9 +0,0 @@
<!--
Hey there! Thanks for your contribution.
Please make sure that your commits are signed off first.
If you don't know how that works, check out our contribution guidelines: https://github.com/PrismLauncher/PrismLauncher/blob/develop/CONTRIBUTING.md#signing-your-work
If you already created your commits, you can run `git rebase --signoff develop` to retroactively sign-off all your commits and `git push --force` to override what you have pushed already.
Note that signing and signing-off are two different things!
-->

View File

@ -7,17 +7,10 @@ on:
description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel)
type: string
default: Debug
is_qt_cached:
description: Enable Qt caching or not
type: string
default: true
secrets:
SPARKLE_ED25519_KEY:
description: Private key for signing Sparkle updates
required: false
CACHIX_AUTH_TOKEN:
description: Private token for authenticating against Cachix cache
required: false
jobs:
build:
@ -32,69 +25,27 @@ jobs:
- os: ubuntu-20.04
qt_ver: 6
qt_host: linux
qt_arch: ''
qt_version: '6.2.4'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
qt_path: /home/runner/work/PolyMC/Qt
- os: windows-2022
name: "Windows-MinGW-w64"
msystem: clang64
- os: windows-2022
name: "Windows-MSVC-Legacy"
msystem: ''
architecture: 'win32'
vcvars_arch: 'amd64_x86'
name: "Windows-Legacy"
msystem: mingw32
qt_ver: 5
qt_host: windows
qt_arch: 'win32_msvc2019'
qt_version: '5.15.2'
qt_modules: ''
qt_tools: 'tools_openssl_x86'
- os: windows-2022
name: "Windows-MSVC"
msystem: ''
architecture: 'x64'
vcvars_arch: 'amd64'
name: "Windows"
msystem: mingw32
qt_ver: 6
qt_host: windows
qt_arch: ''
qt_version: '6.4.2'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
- os: windows-2022
name: "Windows-MSVC-arm64"
msystem: ''
architecture: 'arm64'
vcvars_arch: 'amd64_arm64'
qt_ver: 6
qt_host: windows
qt_arch: 'win64_msvc2019_arm64'
qt_version: '6.4.2'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
- os: macos-12
name: macOS
macosx_deployment_target: 10.15
macosx_deployment_target: 10.14
qt_ver: 6
qt_host: mac
qt_arch: ''
qt_version: '6.3.0'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
- 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_tools: ''
qt_path: /Users/runner/work/PolyMC/Qt
runs-on: ${{ matrix.os }}
@ -105,7 +56,6 @@ jobs:
INSTALL_APPIMAGE_DIR: "install-appdir"
BUILD_DIR: "build"
CCACHE_VAR: ""
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
steps:
##
@ -117,39 +67,34 @@ jobs:
submodules: 'true'
- name: 'Setup MSYS2'
if: runner.os == 'Windows' && matrix.msystem != ''
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.msystem }}
update: true
install: >-
git
mingw-w64-x86_64-binutils
pacboy: >-
toolchain:p
cmake:p
extra-cmake-modules:p
ninja:p
qt6-base:p
qt6-svg:p
qt6-imageformats:p
quazip-qt6:p
qt${{ matrix.qt_ver }}-base:p
qt${{ matrix.qt_ver }}-svg:p
qt${{ matrix.qt_ver }}-imageformats:p
quazip-qt${{ matrix.qt_ver }}:p
ccache:p
qt6-5compat:p
- name: Force newer ccache
if: runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug'
run: |
choco install ccache --version 4.7.1
nsis:p
${{ matrix.qt_ver == 6 && 'qt6-5compat:p' || '' }}
- name: Setup ccache
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
uses: hendrikmuhs/ccache-action@v1.2.5
if: runner.os != 'Windows' && inputs.build_type == 'Debug'
uses: hendrikmuhs/ccache-action@v1.2.1
with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
- name: Setup ccache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
- name: Setup ccache (Windows)
if: runner.os == 'Windows' && inputs.build_type == 'Debug'
shell: msys2 {0}
run: |
ccache --set-config=cache_dir='${{ github.workspace }}\.ccache'
@ -164,14 +109,14 @@ jobs:
run: |
echo "CCACHE_VAR=ccache" >> $GITHUB_ENV
- name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
uses: actions/cache@v3.0.11
- name: Retrieve ccache cache (Windows)
if: runner.os == 'Windows' && inputs.build_type == 'Debug'
uses: actions/cache@v3.0.2
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
restore-keys: |
${{ matrix.os }}-mingw-w64
${{ matrix.os }}-qt${{ matrix.qt_ver }}
- name: Set short version
shell: bash
@ -195,40 +140,25 @@ jobs:
if: runner.os == 'Linux' && matrix.qt_ver != 6
run: |
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
- name: Install host Qt (Windows MSVC arm64)
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
uses: jurplel/install-qt-action@v3
- name: Cache Qt (macOS and AppImage)
id: cache-qt
if: matrix.qt_ver == 6 && runner.os != 'Windows'
uses: actions/cache@v3
with:
version: ${{ matrix.qt_version }}
host: 'windows'
target: 'desktop'
arch: ''
modules: ${{ matrix.qt_modules }}
tools: ${{ matrix.qt_tools }}
cache: ${{ inputs.is_qt_cached }}
cache-key-prefix: host-qt-arm64-windows
dir: ${{ github.workspace }}\HostQt
set-env: false
path: '${{ matrix.qt_path }}/${{ matrix.qt_version }}'
key: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache
- name: Install Qt (macOS, Linux, Qt 6 & Windows MSVC)
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '')
uses: jurplel/install-qt-action@v3
- name: Install Qt (macOS and AppImage)
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'
arch: ${{ matrix.qt_arch }}
modules: ${{ matrix.qt_modules }}
tools: ${{ matrix.qt_tools }}
cache: ${{ inputs.is_qt_cached }}
- name: Install MSVC (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == ''
uses: ilammy/msvc-dev-cmd@v1
with:
vsversion: 2022
arch: ${{ matrix.vcvars_arch }}
cached: ${{ steps.cache-qt.outputs.cache-hit }}
aqtversion: ==2.1.*
- name: Prepare AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
@ -239,45 +169,20 @@ jobs:
${{ github.workspace }}/.github/scripts/prepare_JREs.sh
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
run: |
echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2019_64" >> $env:GITHUB_ENV
##
# CONFIGURE
##
- 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
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 (macOS-Legacy)
if: runner.os == 'macOS' && matrix.qt_ver == 5
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja
- name: Configure CMake (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != ''
- 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=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -G Ninja
- name: Configure CMake (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == ''
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
if ("${{ env.CCACHE_VAR }}")
{
Copy-Item C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/ccache.exe -Destination C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/cl.exe
echo "CLToolExe=cl.exe" >> $env:GITHUB_ENV
echo "CLToolPath=C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/" >> $env:GITHUB_ENV
echo "TrackFileAccess=false" >> $env:GITHUB_ENV
}
# Needed for ccache, but also speeds up compile
echo "UseMultiToolTask=true" >> $env:GITHUB_ENV
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'
@ -293,17 +198,12 @@ jobs:
run: |
cmake --build ${{ env.BUILD_DIR }}
- name: Build (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != ''
- name: Build (Windows)
if: runner.os == 'Windows'
shell: msys2 {0}
run: |
cmake --build ${{ env.BUILD_DIR }}
- name: Build (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == ''
run: |
cmake --build ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
##
# TEST
##
@ -311,18 +211,13 @@ jobs:
- name: Test
if: runner.os != 'Windows'
run: |
ctest -E "^example64|example$" --test-dir build --output-on-failure
ctest --test-dir build --output-on-failure
- name: Test (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != ''
- name: Test (Windows)
if: runner.os == 'Windows'
shell: msys2 {0}
run: |
ctest -E "^example64|example$" --test-dir build --output-on-failure
- name: Test (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
run: |
ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
ctest --test-dir build --output-on-failure
##
# PACKAGE BUILDS
@ -334,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:
@ -358,39 +252,29 @@ jobs:
EOF
fi
- name: Package (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != ''
- name: Package (Windows)
if: runner.os == 'Windows'
shell: msys2 {0}
run: |
cmake --install ${{ env.BUILD_DIR }}
- name: Package (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == ''
run: |
cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
cd ${{ env.INSTALL_DIR }}
if ("${{ matrix.qt_ver }}" -eq "5")
{
Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll
Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll
}
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 MinGW-w64, portable)
if: runner.os == 'Windows' && matrix.msystem != ''
- name: Package (Windows, portable)
if: runner.os == 'Windows'
shell: msys2 {0}
run: |
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
- name: Package (Windows MSVC, portable)
if: runner.os == 'Windows' && matrix.msystem == ''
run: |
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
- 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"
@ -401,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'
@ -410,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
@ -418,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
@ -429,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/
@ -441,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
@ -451,134 +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
snap:
runs-on: ubuntu-20.04
steps:
- name: Checkout
if: inputs.build_type == 'Debug'
uses: actions/checkout@v3
with:
submodules: 'true'
- name: Set short version
shell: bash
if: inputs.build_type == 'Debug'
run: |
ver_short=`git rev-parse --short HEAD`
echo "VERSION=$ver_short" >> $GITHUB_ENV
- name: Package Snap (Linux)
id: snapcraft
if: inputs.build_type == 'Debug'
uses: snapcore/action-build@v1
- name: Upload Snap (Linux)
if: inputs.build_type == 'Debug'
uses: actions/upload-artifact@v3
with:
name: prismlauncher_${{ env.VERSION }}_amd64.snap
path: ${{ steps.snapcraft.outputs.snap }}
name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
path: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
flatpak:
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:kde-5.15-22.08
options: --privileged
steps:
- name: Checkout
uses: actions/checkout@v3
if: inputs.build_type == 'Debug'
with:
submodules: 'true'
- name: Build Flatpak (Linux)
if: inputs.build_type == 'Debug'
uses: flatpak/flatpak-github-actions/flatpak-builder@v4
with:
bundle: "Prism Launcher.flatpak"
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
cache-key: flatpak-${{ github.sha }}-x86_64
nix:
runs-on: ubuntu-latest
strategy:
matrix:
package:
- prismlauncher
- prismlauncher-qt5
steps:
- name: Clone repository
if: inputs.build_type == 'Debug'
uses: actions/checkout@v3
with:
submodules: 'true'
- name: Install nix
if: inputs.build_type == 'Debug'
uses: cachix/install-nix-action@v18
with:
install_url: https://nixos.org/nix/install
extra_nix_config: |
auto-optimise-store = true
experimental-features = nix-command flakes
- uses: cachix/cachix-action@v12
if: inputs.build_type == 'Debug'
with:
name: prismlauncher
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build
if: inputs.build_type == 'Debug'
run: nix build .#${{ matrix.package }} --print-build-logs

View File

@ -1,35 +0,0 @@
name: "CodeQL Code Scanning"
on: [ push, pull_request, workflow_dispatch ]
jobs:
CodeQL:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: 'true'
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
config-file: ./.github/codeql/codeql-config.yml
queries: security-and-quality
languages: cpp, java
- name: Install Dependencies
run:
sudo apt-get -y update
sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
- name: Configure and Build
run: |
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -DLauncher_QT_VERSION_MAJOR=5 -G Ninja
cmake --build build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@ -3,22 +3,22 @@ name: Build Application
on:
push:
branches-ignore:
- 'renovate/**'
- 'stable'
paths-ignore:
- '**.md'
- '**/LICENSE'
- 'flake.lock'
- '**.nix'
- 'packages/**'
- '.github/ISSUE_TEMPLATE/**'
- '.markdownlint**'
pull_request:
paths-ignore:
- '**.md'
- '**/LICENSE'
- 'flake.lock'
- '**.nix'
- 'packages/**'
- '.github/ISSUE_TEMPLATE/**'
- '.markdownlint**'
workflow_dispatch:
jobs:
@ -28,7 +28,5 @@ jobs:
uses: ./.github/workflows/build.yml
with:
build_type: Debug
is_qt_cached: true
secrets:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}

View File

@ -12,7 +12,6 @@ jobs:
uses: ./.github/workflows/build.yml
with:
build_type: Release
is_qt_cached: false
secrets:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
@ -26,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
@ -35,39 +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-MSVC*; do
for d in PolyMC-Windows-*; do
cd "${d}" || continue
LEGACY="$(echo -n ${d} | grep -o Legacy || true)"
ARM64="$(echo -n ${d} | grep -o arm64 || true)"
INST="$(echo -n ${d} | grep -o Setup || true)"
PORT="$(echo -n ${d} | grep -o Portable || true)"
NAME="PrismLauncher-Windows-MSVC"
NAME="PolyMC-Windows"
test -z "${LEGACY}" || NAME="${NAME}-Legacy"
test -z "${ARM64}" || NAME="${NAME}-arm64"
test -z "${PORT}" || NAME="${NAME}-Portable"
test -z "${INST}" || mv PrismLauncher-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
cd ..
done
for d in PrismLauncher-Windows-MinGW-w64*; do
cd "${d}" || continue
INST="$(echo -n ${d} | grep -o Setup || true)"
PORT="$(echo -n ${d} | grep -o Portable || true)"
NAME="PrismLauncher-Windows-MinGW-w64"
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
@ -79,27 +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-Linux-Qt6-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe
PrismLauncher-Windows-MSVC-Legacy-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Legacy-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Legacy-Setup-${{ env.VERSION }}.exe
PrismLauncher-Windows-MSVC-arm64-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-arm64-Setup-${{ env.VERSION }}.exe
PrismLauncher-Windows-MSVC-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe
PrismLauncher-macOS-${{ env.VERSION }}.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-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$'
identifier: PolyMC.PolyMC
installers-regex: '\.exe$'
token: ${{ secrets.WINGET_TOKEN }}

3
.gitignore vendored
View File

@ -47,6 +47,3 @@ result/
# Flatpak
.flatpak-builder
flatbuild
# Snap
*.snap

20
.gitmodules vendored
View File

@ -1,18 +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
[submodule "libraries/zlib"]
path = libraries/zlib
url = https://github.com/madler/zlib.git
[submodule "libraries/extra-cmake-modules"]
path = libraries/extra-cmake-modules
url = https://github.com/KDE/extra-cmake-modules

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

@ -1,5 +1,10 @@
cmake_minimum_required(VERSION 3.15) # minimum version required by QuaZip
if(WIN32)
# In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows
cmake_policy(SET CMP0020 OLD)
endif()
project(Launcher)
string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD)
@ -27,51 +32,9 @@ set(CMAKE_C_STANDARD_REQUIRED true)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11)
include(GenerateExportHeader)
if(MSVC)
# /GS Adds buffer security checks, default on but incuded anyway to mirror gcc's fstack-protector flag
# /permissive- specify standards-conforming compiler behavior, also enabled by Qt6, default on with std:c++20
# Use /W4 as /Wall includes unnesserey warnings such as added padding to structs
set(CMAKE_CXX_FLAGS "/GS /permissive- /W4 ${CMAKE_CXX_FLAGS}")
# LINK accepts /SUBSYSTEM whics sets if we are a WINDOWS (gui) or a CONSOLE programs
# This implicitly selects an entrypoint specific to the subsystem selected
# qtmain/QtEntryPointLib provides the correct entrypoint (wWinMain) for gui programs
# Additinaly LINK autodetects we use a GUI so we can omit /SUBSYSTEM
# This allows tests to still use have console without using seperate linker flags
# /LTCG allows for linking wholy optimizated programs
# /MANIFEST:NO disables generating a manifest file, we instead provide our own
# /STACK sets the stack reserve size, ATL's pack list needs 3-4 MiB as of November 2022, provide 8 MiB
set(CMAKE_EXE_LINKER_FLAGS "/LTCG /MANIFEST:NO /STACK:8388608 ${CMAKE_EXE_LINKER_FLAGS}")
# /GL enables whole program optimizations
# /Gw helps reduce binary size
# /Gy allows the compiler to package individual functions
# /guard:cf enables control flow guard
foreach(lang C CXX)
set("CMAKE_${lang}_FLAGS_RELEASE" "/GL /Gw /Gy /guard:cf")
endforeach()
# See https://github.com/ccache/ccache/issues/1040
# Note, CMake 3.25 replaces this with CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
# See https://cmake.org/cmake/help/v3.25/variable/CMAKE_MSVC_DEBUG_INFORMATION_FORMAT.html
foreach(config DEBUG RELWITHDEBINFO)
foreach(lang C CXX)
set(flags_var "CMAKE_${lang}_FLAGS_${config}")
string(REGEX REPLACE "/Z[Ii]" "/Z7" ${flags_var} "${${flags_var}}")
endforeach()
endforeach()
if(CMAKE_MSVC_RUNTIME_LIBRARY STREQUAL "MultiThreadedDLL")
set(CMAKE_MAP_IMPORTED_CONFIG_DEBUG Release "")
set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release "")
endif()
else()
set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
# ATL's pack list needs more than the default 1 Mib stack on windows
if(WIN32)
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}")
endif()
set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
if(UNIX AND APPLE)
set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}")
endif()
# Fix build with Qt 5.13
@ -79,9 +42,6 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00")
# Fix aarch64 build for toml++
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0")
# set CXXFLAGS for build targets
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
@ -91,18 +51,11 @@ if(ENABLE_LTO)
include(CheckIPOSupported)
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
if(ipo_supported)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL TRUE)
if(CMAKE_BUILD_TYPE)
if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
message(STATUS "IPO / LTO enabled")
else()
message(STATUS "Not enabling IPO / LTO on debug builds")
endif()
else()
message(STATUS "IPO / LTO will only be enabled for release builds")
endif()
if(ipo_supported AND (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel"))
message(STATUS "IPO / LTO enabled")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
elseif(ipo_supported)
message(STATUS "Not enabling IPO / LTO on debug builds")
else()
message(STATUS "IPO / LTO not supported: <${ipo_error}>")
endif()
@ -110,40 +63,28 @@ endif()
option(BUILD_TESTING "Build the testing tree." ON)
find_package(ECM QUIET NO_MODULE)
if(NOT ECM_FOUND)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/extra-cmake-modules/CMakeLists.txt")
message(STATUS "Using bundled ECM")
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/libraries/extra-cmake-modules/modules;${CMAKE_MODULE_PATH}")
else()
message(FATAL_ERROR
" Could not find ECM\n \n"
" Either install ECM using the system package manager or clone submodules\n"
" Submodules can be cloned with 'git submodule update --init --recursive'")
endif()
else()
set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}")
endif()
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 6)
set(Launcher_VERSION_MINOR 3)
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.")
@ -152,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")
@ -185,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
@ -203,21 +143,18 @@ 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 ################################
# Successive configurations of cmake without cleaning the build dir will cause zlib fallback to fail due to cached values
# Record when fallback triggered and skip this find_package
if(NOT Launcher_FORCE_BUNDLED_LIBS AND NOT FORCE_BUNDLED_ZLIB)
find_package(ZLIB QUIET)
endif()
if(NOT ZLIB_FOUND)
set(FORCE_BUNDLED_ZLIB TRUE CACHE BOOL "")
mark_as_advanced(FORCE_BUNDLED_ZLIB)
endif()
# Find the required Qt parts
include(QtVersionlessBackport)
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
@ -236,7 +173,7 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE")
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
set(QT_VERSION_MAJOR 6)
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat)
find_package(Qt6 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml Core5Compat)
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
if(NOT Launcher_FORCE_BUNDLED_LIBS)
@ -250,35 +187,21 @@ else()
message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported")
endif()
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
include(ECMQueryQt)
ecm_query_qt(QT_PLUGINS_DIR QT_INSTALL_PLUGINS)
ecm_query_qt(QT_LIBS_DIR QT_INSTALL_LIBS)
ecm_query_qt(QT_LIBEXECS_DIR QT_INSTALL_LIBEXECS)
else()
set(QT_PLUGINS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_PLUGINS})
set(QT_LIBS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBS})
set(QT_LIBEXECS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBEXECS})
endif()
include(ECMQueryQt)
ecm_query_qt(QT_PLUGINS_DIR QT_INSTALL_PLUGINS)
ecm_query_qt(QT_LIBS_DIR QT_INSTALL_LIBS)
ecm_query_qt(QT_LIBEXECS_DIR QT_INSTALL_LIBEXECS)
ecm_query_qt(QT_DATA_DIR QT_HOST_DATA)
set(QT_MKSPECS_DIR ${QT_DATA_DIR}/mkspecs)
# NOTE: Qt 6 already sets this by default
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()
include(ECMQtDeclareLoggingCategory)
####################################### 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 #######################################
@ -300,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")
@ -325,11 +248,13 @@ if(UNIX AND APPLE)
install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns)
elseif(UNIX)
include(KDEInstallDirs)
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")
set(LAUNCHER_MAN_DEST_DIR "share/man/man6" CACHE STRING "Path to the man page directory")
# install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps")
@ -337,13 +262,12 @@ elseif(UNIX)
# Set RPATH
SET(Launcher_BINARY_RPATH "$ORIGIN/")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${KDE_INSTALL_APPDIR})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${KDE_INSTALL_METAINFODIR})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps")
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${LAUNCHER_DESKTOP_DEST_DIR})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${LAUNCHER_METAINFO_DEST_DIR})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION ${LAUNCHER_ICON_DEST_DIR})
if(Launcher_ManPage)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION ${LAUNCHER_MAN_DEST_DIR})
endif()
# Install basic runner script if component "portable" is selected
@ -383,32 +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
if(FORCE_BUNDLED_ZLIB)
message(STATUS "Using bundled zlib")
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Suppress cmake warnings and allow INTERPROCEDURAL_OPTIMIZATION for zlib
set(SKIP_INSTALL_ALL ON)
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL)
# On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not.
# We cannot safely undo the rename on those systems, and they generally have packages for zlib anyway.
check_include_file(unistd.h NEED_GENERATED_ZCONF)
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" AND NOT NEED_GENERATED_ZCONF)
# zlib's cmake script renames a file, dirtying the submodule, see https://github.com/madler/zlib/issues/162
message(STATUS "Undoing Rename")
message(STATUS " ${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h")
file(RENAME "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h")
endif()
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib" "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" CACHE STRING "" FORCE)
set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}")
add_library(ZLIB::ZLIB ALIAS zlibstatic)
set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "zlib library name")
find_package(ZLIB REQUIRED)
else()
message(STATUS "Using system zlib")
endif()
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.
@ -419,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-2023 Prism Launcher Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
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,67 +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.
## Breeze icons
Copyright (C) 2014 Uri Herrera <uri_herrera@nitrux.in> and others
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
## Oxygen Icons
The Oxygen Icon Theme
Copyright (C) 2007 Nuno Pinheiro <nuno@oxygen-icons.org>
Copyright (C) 2007 David Vignoni <david@icon-king.com>
Copyright (C) 2007 David Miller <miller@oxygen-icons.org>
Copyright (C) 2007 Johann Ollivier Lapeyre <johann@oxygen-icons.org>
Copyright (C) 2007 Kenneth Wimer <kwwii@bootsplash.org>
Copyright (C) 2007 Riccardo Iaconelli <riccardo@oxygen-icons.org>
and others
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.

114
README.md
View File

@ -1,102 +1,98 @@
<p align="left">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg">
<source media="(prefers-color-scheme: light)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo.svg">
<img alt="Prism Launcher" src="/program_info/org.prismlauncher.PrismLauncher.logo.svg" width="50%">
</picture>
<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>
PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity.
Prism Launcher is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.
This is a **fork** of the MultiMC Launcher and not endorsed by MultiMC.
If you want to read about why this fork was created, check out [our FAQ page](https://polymc.org/wiki/overview/faq/).
<br>
This is a **fork** of the MultiMC Launcher and is not endorsed by MultiMC.
# Installation
- All downloads and instructions for PolyMC can be found [here](https://polymc.org/download/)
- Last build status: https://github.com/PolyMC/PolyMC/actions
## Installation
## Development Builds
<a href="https://repology.org/project/prismlauncher/versions">
<img src="https://repology.org/badge/vertical-allrepos/prismlauncher.svg" alt="Packaging status" align="right">
</a>
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.
- All downloads and instructions for Prism Launcher can be found on our [Website](https://prismlauncher.org/download/).
- Last build status can be found in the [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions).
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)
### Development Builds
# Help & Support
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.
Feel free to create an issue if you need help. However, you might find it easier to ask in the Discord server.
Prebuilt Development builds are provided for **Linux**, **Windows** and **macOS**.
[![PolyMC Discord](https://img.shields.io/discord/923671181020766230?label=PolyMC%20Discord)](https://discord.gg/xq7fxrgtMP)
For **Arch**, **Debian**, **Fedora**, **OpenSUSE (Tumbleweed)** and **Gentoo**, respectively, you can use these packages for the latest development versions:
For people who don't want to use Discord, we have a Matrix Space which is bridged to the Discord server:
[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-1793D1?style=flat-square&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-git/) [![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--qt5--git-1793D1?style=flat-square&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-qt5-git/) [![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-A80030?style=flat-square&logo=debian&logoColor=white)](https://mpr.makedeb.org/packages/prismlauncher-git) [![prismlauncher-nightly](https://img.shields.io/badge/copr-prismlauncher--nightly-51A2DA?style=flat-square&logo=fedora&logoColor=white)](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [![prismlauncher-nightly](https://img.shields.io/badge/OBS-prismlauncher--nightly-3AB6A9?style=flat-square&logo=opensuse&logoColor=white)](https://build.opensuse.org/project/show/home:getchoo) [![prismlauncher-9999](https://img.shields.io/badge/gentoo-prismlauncher--9999-4D4270?style=flat-square&logo=gentoo&logoColor=white)](https://packages.gentoo.org/packages/games-action/prismlauncher)
[![PolyMC Space](https://img.shields.io/matrix/polymc:matrix.org?label=PolyMC%20space)](https://matrix.to/#/#polymc:matrix.org)
## Community & Support
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:
Feel free to create a GitHub issue if you find a bug or want to suggest a new feature. We have multiple community spaces where other community members can help you.
[![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)
#### Join our Discord server:
[![Prism Launcher Discord server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner2)](https://discord.gg/prismlauncher)
We also have a subreddit you can post your issues and suggestions on:
#### Join our Matrix space:
[![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge&logo=matrix)](https://matrix.to/#/#prismlauncher:matrix.org)
[r/PolyMCLauncher](https://www.reddit.com/r/PolyMCLauncher/)
#### Join our Subreddit:
[![r/PrismLauncher](https://img.shields.io/reddit/subreddit-subscribers/prismlauncher?style=for-the-badge&logo=reddit)](https://www.reddit.com/r/PrismLauncher/)
# 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 Prism Launcher yourself, check the [Build Instructions](https://prismlauncher.org/wiki/development/build-instructions/).
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 PrismLauncher is hosted on [Weblate](https://hosted.weblate.org/projects/prismlauncher/launcher/) and information about translating Prism Launcher is available at <https://github.com/PrismLauncher/Translations>
The translation effort for 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 PrismLauncher and is not endorsed by or affiliated with the PrismLauncher project (<https://prismlauncher.org>).
- Go through [CMakeLists.txt](CMakeLists.txt) and change PrismLauncher'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).
- 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)
- [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 (`""`).
## Sponsors & Partners
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.
We thank all the wonderful backers over at Open Collective! Support Prism Launcher by [becoming a backer](https://opencollective.com/prismlauncher).
## 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/prismlauncher/backers.svg?width=890&limit=1000)](https://opencollective.com/prismlauncher#backers)
[![OpenCollective Backers](https://opencollective.com/polymc/backers.svg?width=890&limit=1000)](https://opencollective.com/polymc#backers)
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/).
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/)
Thanks to Weblate for hosting our translation efforts.
<a href="https://hosted.weblate.org/engage/prismlauncher/">
<img src="https://hosted.weblate.org/widgets/prismlauncher/-/open-graph.png" alt="Translation status" width="300" />
</a>
Thanks to Netlify for providing us their excellent web services, as part of their [Open Source program](https://www.netlify.com/open-source/)
<a href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-accent.svg" alt="Deploys by Netlify" /> </a>
Thanks to the awesome people over at [MacStadium](https://www.macstadium.com/), for providing M1-Macs for development purposes!
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>
## License
All launcher code is available under the GPL-3.0-only license.
![https://github.com/PrismLauncher/PrismLauncher/blob/develop/LICENSE](https://img.shields.io/github/license/PrismLauncher/PrismLauncher?style=for-the-badge&logo=gnu&color=C4282D)
The logo and related assets are under the CC BY-SA 4.0 license.

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@";
@ -76,9 +75,7 @@ Config::Config()
// Assume that builds outside of Git repos are "stable"
if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND")
|| GIT_TAG == QStringLiteral("GITDIR-NOTFOUND")
|| GIT_REFSPEC == QStringLiteral("")
|| GIT_TAG == QStringLiteral("GIT-NOTFOUND"))
|| GIT_TAG == QStringLiteral("GITDIR-NOTFOUND"))
{
GIT_REFSPEC = "refs/heads/stable";
GIT_TAG = versionString();
@ -88,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;
}
}
@ -101,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@";
@ -118,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
@ -130,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/";
@ -161,8 +163,6 @@ class Config {
QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2";
QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2";
QString FLAME_BASE_URL = "https://api.curseforge.com/v1";
QString versionString() const;
/**
* \brief Converts the Version to a string.

View File

@ -44,28 +44,5 @@
<string>${MACOSX_SPARKLE_UPDATE_PUBLIC_KEY}</string>
<key>SUFeedURL</key>
<string>${MACOSX_SPARKLE_UPDATE_FEED_URL}</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>zip</string>
<string>mrpack</string>
</array>
<key>CFBundleTypeName</key>
<string>Prism Launcher instance</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>TEXT</string>
<string>utxt</string>
<string>TUTX</string>
<string>****</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
</dict>
</array>
</dict>
</plist>

16
flake.lock generated
View File

@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1668681692,
"narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
"lastModified": 1650374568,
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "009399224d5e398d03b22badca40a37ac85412a1",
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
"type": "github"
},
"original": {
@ -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": 1671417167,
"narHash": "sha256-JkHam6WQOwZN1t2C2sbp1TqMv3TVRjzrdoejqfefwrM=",
"lastModified": 1658119717,
"narHash": "sha256-4upOZIQQ7Bc4CprqnHsKnqYfw+arJeAuU+QcpjYBXW0=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "bb31220cca6d044baa6dc2715b07497a2a7c4bc7",
"rev": "9eb60f25aff0d2218c848dd4574a0ab5e296cabe",
"type": "github"
},
"original": {

View File

@ -4,7 +4,7 @@
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
libnbtplusplus = { url = "github:PrismLauncher/libnbtplusplus"; flake = false; };
libnbtplusplus = { url = "github:PolyMC/libnbtplusplus"; flake = false; };
};
outputs = { self, nixpkgs, libnbtplusplus, ... }:
@ -22,14 +22,14 @@
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
packagesFn = pkgs: rec {
prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; };
prismlauncher = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; };
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

@ -1,83 +0,0 @@
id: org.prismlauncher.PrismLauncher
runtime: org.kde.Platform
runtime-version: "5.15-22.08"
sdk: org.kde.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.openjdk17
- org.freedesktop.Sdk.Extension.openjdk8
add-extensions:
com.valvesoftware.Steam.Utility.gamescope:
version: stable
add-ld-path: lib
no-autodownload: true
autodelete: false
directory: utils/gamescope
command: prismlauncher
finish-args:
- --share=ipc
- --socket=x11
- --socket=wayland
- --device=all
- --share=network
- --socket=pulseaudio
# for Discord RPC mods
- --filesystem=xdg-run/app/com.discordapp.Discord:create
# Mod drag&drop
- --filesystem=xdg-download:ro
modules:
- name: prismlauncher
buildsystem: cmake-ninja
config-opts:
- -DLauncher_BUILD_PLATFORM=flatpak
- -DCMAKE_BUILD_TYPE=Debug
build-options:
env:
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac
sources:
- type: dir
path: ../
- name: openjdk
buildsystem: simple
build-commands:
- mkdir -p /app/jdk/
- /usr/lib/sdk/openjdk17/install.sh
- mv /app/jre /app/jdk/17
- /usr/lib/sdk/openjdk8/install.sh
- mv /app/jre /app/jdk/8
cleanup: [/jre]
- name: xrandr
buildsystem: autotools
sources:
- type: archive
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.1.tar.xz
sha256: 7bc76daf9d72f8aff885efad04ce06b90488a1a169d118dea8a2b661832e8762
cleanup: [/share/man, /bin/xkeystone]
- name: gamemode
buildsystem: meson
config-opts:
- -Dwith-sd-bus-provider=no-daemon
- -Dwith-examples=false
post-install:
# gamemoderun is installed for users who want to use wrapper commands
# post-install is running inside the build dir, we need it from the source though
- install -Dm755 ../data/gamemoderun -t /app/bin
sources:
- type: git
url: https://github.com/FeralInteractive/gamemode
tag: "1.7"
commit: 4dc99dff76218718763a6b07fc1900fa6d1dafd9
- name: enhance
buildsystem: simple
build-commands:
- mkdir -p /app/utils/gamescope
- install -Dm755 prime-run /app/bin/prime-run
- mv /app/bin/prismlauncher /app/bin/prismrun
- install -Dm755 prismlauncher /app/bin/prismlauncher
sources:
- type: file
path: ../flatpak/prime-run
- type: file
path: ../flatpak/prismlauncher

View File

@ -1,4 +0,0 @@
#!/bin/sh
export __NV_PRIME_RENDER_OFFLOAD=1 __VK_LAYER_NV_optimus=NVIDIA_only __GLX_VENDOR_LIBRARY_NAME=nvidia
exec "$@"

View File

@ -1,11 +0,0 @@
#!/bin/bash
# discord RPC
for i in {0..9}; do
test -S "$XDG_RUNTIME_DIR"/discord-ipc-"$i" || ln -sf {app/com.discordapp.Discord,"$XDG_RUNTIME_DIR"}/discord-ipc-"$i";
done
export PATH="${PATH}${PATH:+:}/app/utils/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin"
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}${LD_LIBRARY_PATH:+:}/usr/lib/extensions/vulkan/MangoHud/\$LIB/"
exec /app/bin/prismrun "$@"

View File

@ -1,12 +1,8 @@
// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
//
// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
* Copyright (C) 2022 Tayou <tayou@gmx.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
@ -41,14 +37,10 @@
#include "Application.h"
#include "BuildConfig.h"
#include "DataMigrationTask.h"
#include "net/PasteUpload.h"
#include "pathmatcher/MultiMatcher.h"
#include "pathmatcher/SimplePrefixMatcher.h"
#include "ui/MainWindow.h"
#include "ui/InstanceWindow.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/instanceview/AccessibleInstanceView.h"
#include "ui/pages/BasePageProvider.h"
@ -62,6 +54,12 @@
#include "ui/pages/global/APIPage.h"
#include "ui/pages/global/CustomCommandsPage.h"
#include "ui/themes/ITheme.h"
#include "ui/themes/SystemTheme.h"
#include "ui/themes/DarkTheme.h"
#include "ui/themes/BrightTheme.h"
#include "ui/themes/CustomTheme.h"
#include "ui/setupwizard/SetupWizard.h"
#include "ui/setupwizard/LanguageWizardPage.h"
#include "ui/setupwizard/JavaWizardPage.h"
@ -71,14 +69,11 @@
#include "ui/pagedialog/PageDialog.h"
#include "ui/themes/ThemeManager.h"
#include "ApplicationMessage.h"
#include <iostream>
#include <QAccessible>
#include <QCommandLineParser>
#include <QDir>
#include <QFileInfo>
#include <QNetworkAccessManager>
@ -92,7 +87,6 @@
#include <QIcon>
#include "InstanceList.h"
#include "MTPixmapCache.h"
#include <minecraft/auth/AccountList.h>
#include "icons/IconList.h"
@ -112,18 +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"
#include "MangoHud.h"
#endif
#if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN
@ -138,15 +127,26 @@
static const QLatin1String liveCheckFile("live.check");
PixmapCache* PixmapCache::s_instance = nullptr;
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 {
/** This is used so that we can output to the log file in addition to the CLI. */
void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
QString out = qFormatLogMessage(type, context, msg);
out += QChar::LineFeed;
const char *levels = "DWCFIS";
const QString format("%1 %2 %3\n");
qint64 msecstotal = APPLICATION->timeSinceStart();
qint64 seconds = msecstotal / 1000;
qint64 msecs = msecstotal % 1000;
QString foo;
char buf[1025] = {0};
::snprintf(buf, 1024, "%5lld.%03lld", seconds, msecs);
QString out = format.arg(buf).arg(levels[type]).arg(msg);
APPLICATION->logFile->write(out.toUtf8());
APPLICATION->logFile->flush();
@ -225,7 +225,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN);
setApplicationName(BuildConfig.LAUNCHER_NAME);
setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()));
setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT);
setApplicationVersion(BuildConfig.printableVersionString());
setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
startTime = QDateTime::currentDateTime();
@ -233,29 +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"},
{"show", "Opens the window for the specified instance (by instance ID)", "show"}
});
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");
m_instanceIdToShowWindowOf = parser.value("show");
// 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())
@ -286,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
@ -300,6 +351,16 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
dataPath = foo.absolutePath();
adjustedBy = "Persistent data path";
#ifdef Q_OS_LINUX
// TODO: this should be removed in a future version
// TODO: provide a migration path similar to macOS migration
QDir bar(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "polymc"));
if (bar.exists()) {
dataPath = bar.absolutePath();
adjustedBy = "Legacy data path";
}
#endif
#ifndef Q_OS_MACOS
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
dataPath = m_rootPath;
@ -315,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"
@ -330,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"
@ -410,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"
@ -419,26 +489,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
return;
}
qInstallMessageHandler(appDebugOutput);
qSetMessagePattern(
"%{time process}" " "
"%{if-debug}D%{endif}" "%{if-info}I%{endif}" "%{if-warning}W%{endif}" "%{if-critical}C%{endif}" "%{if-fatal}F%{endif}"
" " "|" " "
"%{if-category}[%{category}]: %{endif}"
"%{message}");
qDebug() << "<> Log initialized.";
}
{
bool migrated = false;
if (!migrated)
migrated = handleDataMigration(dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC", "polymc.cfg");
if (!migrated)
migrated = handleDataMigration(dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), "MultiMC", "multimc.cfg");
}
{
qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
@ -488,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);
@ -498,7 +550,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Theming
m_settings->registerSetting("IconTheme", QString("pe_colored"));
m_settings->registerSetting("ApplicationTheme", QString("system"));
m_settings->registerSetting("BackgroundCat", QString("kitteh"));
// Remembered state
m_settings->registerSetting("LastUsedGroupForNewInstance", QString());
@ -563,7 +614,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Memory
m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512);
m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, suitableMaxMem());
m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 4096);
m_settings->registerSetting("PermGen", 128);
// Java Settings
@ -611,8 +662,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// The cat
m_settings->registerSetting("TheCat", false);
m_settings->registerSetting("ToolbarsLocked", false);
m_settings->registerSetting("InstSortMode", "Name");
m_settings->registerSetting("SelectedInstance", QString());
@ -631,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", "");
@ -693,9 +740,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_globalSettingsProvider->addPage<AccountListPage>();
m_globalSettingsProvider->addPage<APIPage>();
}
PixmapCache::setInstance(new PixmapCache(this));
qDebug() << "<> Settings loaded.";
}
@ -730,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.";
}
@ -752,8 +796,29 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Instance icons intialized.";
}
// Themes
m_themeManager = std::make_unique<ThemeManager>(m_mainWindow);
// Icon themes
{
// TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies!
// set icon theme search path!
auto searchPaths = QIcon::themeSearchPaths();
searchPaths.append("iconthemes");
QIcon::setThemeSearchPaths(searchPaths);
qDebug() << "<> Icon themes initialized.";
}
// Initialize widget themes
{
auto insertTheme = [this](ITheme * theme)
{
m_themes.insert(std::make_pair(theme->id(), std::unique_ptr<ITheme>(theme)));
};
auto darkTheme = new DarkTheme();
insertTheme(new SystemTheme());
insertTheme(darkTheme);
insertTheme(new BrightTheme());
insertTheme(new CustomTheme(darkTheme, "custom"));
qDebug() << "<> Widget themes initialized.";
}
// initialize and load all instances
{
@ -801,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());
@ -852,13 +916,10 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Application theme set.";
}
updateCapabilities();
if(createSetupWizard())
{
return;
}
performMainStartupAction();
}
@ -918,24 +979,18 @@ bool Application::createSetupWizard()
return false;
}
bool Application::event(QEvent* event)
{
bool Application::event(QEvent* event) {
#ifdef Q_OS_MACOS
if (event->type() == QEvent::ApplicationStateChange) {
auto ev = static_cast<QApplicationStateChangeEvent*>(event);
if (m_prevAppState == Qt::ApplicationActive && ev->applicationState() == Qt::ApplicationActive) {
if (m_prevAppState == Qt::ApplicationActive
&& ev->applicationState() == Qt::ApplicationActive) {
emit clickedOnDock();
}
m_prevAppState = ev->applicationState();
}
#endif
if (event->type() == QEvent::FileOpen) {
auto ev = static_cast<QFileOpenEvent*>(event);
m_mainWindow->droppedURLs({ ev->url() });
}
return QApplication::event(event);
}
@ -973,17 +1028,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse;
}
launch(inst, true, false, nullptr, serverToJoin, accountToUse);
return;
}
}
if(!m_instanceIdToShowWindowOf.isEmpty())
{
auto inst = instances()->getInstanceById(m_instanceIdToShowWindowOf);
if(inst)
{
qDebug() << "<> Showing window of instance " << m_instanceIdToShowWindowOf;
showInstanceWindow(inst);
launch(inst, true, nullptr, serverToJoin, accountToUse);
return;
}
}
@ -1087,7 +1132,6 @@ void Application::messageReceived(const QByteArray& message)
launch(
instance,
true,
false,
nullptr,
serverObject,
accountObject
@ -1113,25 +1157,51 @@ std::shared_ptr<JavaInstallList> Application::javalist()
return m_javalist;
}
QList<ITheme*> Application::getValidApplicationThemes()
std::vector<ITheme *> Application::getValidApplicationThemes()
{
return m_themeManager->getValidApplicationThemes();
std::vector<ITheme *> ret;
auto iter = m_themes.cbegin();
while (iter != m_themes.cend())
{
ret.push_back((*iter).second.get());
iter++;
}
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)
{
m_themeManager->setApplicationTheme(name, initial);
auto systemPalette = qApp->palette();
auto themeIter = m_themes.find(name);
if(themeIter != m_themes.end())
{
auto & theme = (*themeIter).second;
theme->apply(initial);
}
else
{
qWarning() << "Tried to set invalid theme:" << name;
}
}
void Application::setIconTheme(const QString& name)
{
m_themeManager->setIconTheme(name);
QIcon::setThemeName(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);
}
@ -1153,7 +1223,6 @@ bool Application::openJsonEditor(const QString &filename)
bool Application::launch(
InstancePtr instance,
bool online,
bool demo,
BaseProfilerFactory *profiler,
MinecraftServerTargetPtr serverToJoin,
MinecraftAccountPtr accountToUse
@ -1177,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);
@ -1348,7 +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()));
if(minimized)
{
m_mainWindow->showMinimized();
@ -1501,32 +1568,24 @@ 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;
if (!MangoHud::getLibraryString().isEmpty())
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"),
FS::PathCombine(applicationDirPath(), "..", "jars") // from inside build dir, for debuging
FS::PathCombine(applicationDirPath(), "jars")
};
for(QString p : potentialPaths)
{
@ -1577,102 +1636,3 @@ QString Application::getUserAgentUncached()
return BuildConfig.USER_AGENT_UNCACHED;
}
int Application::suitableMaxMem()
{
float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte;
int maxMemoryAlloc;
// If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB
if (totalRAM < (4096 * 1.5))
maxMemoryAlloc = (int) (totalRAM / 1.5);
else
maxMemoryAlloc = 4096;
return maxMemoryAlloc;
}
bool Application::handleDataMigration(const QString& currentData,
const QString& oldData,
const QString& name,
const QString& configFile) const
{
QString nomigratePath = FS::PathCombine(currentData, name + "_nomigrate.txt");
QStringList configPaths = { FS::PathCombine(oldData, configFile), FS::PathCombine(oldData, BuildConfig.LAUNCHER_CONFIGFILE) };
QLocale locale;
// Is there a valid config at the old location?
bool configExists = false;
for (QString configPath : configPaths) {
configExists |= QFileInfo::exists(configPath);
}
if (!configExists || QFileInfo::exists(nomigratePath)) {
qDebug() << "<> No migration needed from" << name;
return false;
}
QString message;
bool currentExists = QFileInfo::exists(FS::PathCombine(currentData, BuildConfig.LAUNCHER_CONFIGFILE));
if (currentExists) {
message = tr("Old data from %1 was found, but you already have existing data for %2. Sadly you will need to migrate yourself. Do "
"you want to be reminded of the pending data migration next time you start %2?")
.arg(name, BuildConfig.LAUNCHER_DISPLAYNAME);
} else {
message = tr("It looks like you used %1 before. Do you want to migrate your data to the new location of %2?")
.arg(name, BuildConfig.LAUNCHER_DISPLAYNAME);
QFileInfo logInfo(FS::PathCombine(oldData, name + "-0.log"));
if (logInfo.exists()) {
QString lastModified = logInfo.lastModified().toString(locale.dateFormat());
message = tr("It looks like you used %1 on %2 before. Do you want to migrate your data to the new location of %3?")
.arg(name, lastModified, BuildConfig.LAUNCHER_DISPLAYNAME);
}
}
QMessageBox::StandardButton askMoveDialogue =
QMessageBox::question(nullptr, BuildConfig.LAUNCHER_DISPLAYNAME, message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
auto setDoNotMigrate = [&nomigratePath] {
QFile file(nomigratePath);
file.open(QIODevice::WriteOnly);
};
// create no-migrate file if user doesn't want to migrate
if (askMoveDialogue != QMessageBox::Yes) {
qDebug() << "<> Migration declined for" << name;
setDoNotMigrate();
return currentExists; // cancel further migrations, if we already have a data directory
}
if (!currentExists) {
// Migrate!
auto matcher = std::make_shared<MultiMatcher>();
matcher->add(std::make_shared<SimplePrefixMatcher>(configFile));
matcher->add(std::make_shared<SimplePrefixMatcher>(
BuildConfig.LAUNCHER_CONFIGFILE)); // it's possible that we already used that directory before
matcher->add(std::make_shared<SimplePrefixMatcher>("accounts.json"));
matcher->add(std::make_shared<SimplePrefixMatcher>("accounts/"));
matcher->add(std::make_shared<SimplePrefixMatcher>("assets/"));
matcher->add(std::make_shared<SimplePrefixMatcher>("icons/"));
matcher->add(std::make_shared<SimplePrefixMatcher>("instances/"));
matcher->add(std::make_shared<SimplePrefixMatcher>("libraries/"));
matcher->add(std::make_shared<SimplePrefixMatcher>("mods/"));
matcher->add(std::make_shared<SimplePrefixMatcher>("themes/"));
ProgressDialog diag;
DataMigrationTask task(nullptr, oldData, currentData, matcher);
if (diag.execWithTask(&task)) {
qDebug() << "<> Migration succeeded";
setDoNotMigrate();
} else {
QString reason = task.failReason();
QMessageBox::critical(nullptr, BuildConfig.LAUNCHER_DISPLAYNAME, tr("Migration failed! Reason: %1").arg(reason));
}
} else {
qWarning() << "<> Migration was skipped, due to existing data";
}
return true;
}

View File

@ -1,8 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Tayou <tayou@gmx.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
@ -69,7 +68,6 @@ class BaseDetachedToolFactory;
class TranslationsModel;
class ITheme;
class MCEditTool;
class ThemeManager;
namespace Meta {
class Index;
@ -97,8 +95,6 @@ public:
SupportsMSA = 1 << 0,
SupportsFlame = 1 << 1,
SupportsGameMode = 1 << 2,
SupportsMangoHud = 1 << 3,
};
Q_DECLARE_FLAGS(Capabilities, Capability)
@ -118,9 +114,11 @@ public:
QIcon getThemedIcon(const QString& name);
bool isFlatpak();
void setIconTheme(const QString& name);
QList<ITheme*> getValidApplicationThemes();
std::vector<ITheme *> getValidApplicationThemes();
void setApplicationTheme(const QString& name, bool initial);
@ -164,7 +162,7 @@ public:
shared_qobject_ptr<Meta::Index> metadataIndex();
void updateCapabilities();
Capabilities currentCapabilities();
/*!
* Finds and returns the full path to a jar file.
@ -182,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
@ -200,8 +194,6 @@ public:
void ShowGlobalSettings(class QWidget * parent, QString open_page = QString());
int suitableMaxMem();
signals:
void updateAllowedChanged(bool status);
void globalSettingsAboutToOpen();
@ -215,7 +207,6 @@ public slots:
bool launch(
InstancePtr instance,
bool online = true,
bool demo = false,
BaseProfilerFactory *profiler = nullptr,
MinecraftServerTargetPtr serverToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr
@ -231,7 +222,6 @@ private slots:
void setupWizardFinished(int status);
private:
bool handleDataMigration(const QString & currentData, const QString & oldData, const QString & name, const QString & configFile) const;
bool createSetupWizard();
void performMainStartupAction();
@ -260,15 +250,14 @@ private:
std::shared_ptr<JavaInstallList> m_javalist;
std::shared_ptr<TranslationsModel> m_translations;
std::shared_ptr<GenericPageProvider> m_globalSettingsProvider;
std::map<QString, std::unique_ptr<ITheme>> m_themes;
std::unique_ptr<MCEditTool> m_mcedit;
QSet<QString> m_features;
std::unique_ptr<ThemeManager> m_themeManager;
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;
QString m_rootPath;
Status m_status = Application::StartingUp;
Capabilities m_capabilities;
#ifdef Q_OS_MACOS
Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;
@ -304,7 +293,6 @@ public:
QString m_profileToUse;
bool m_liveCheck = false;
QUrl m_zipToImport;
QString m_instanceIdToShowWindowOf;
std::unique_ptr<QFile> logFile;
};

View File

@ -47,8 +47,8 @@ void ApplicationMessage::parse(const QByteArray & input) {
args.clear();
auto parsedArgs = root.value("args").toObject();
for(auto iter = parsedArgs.constBegin(); iter != parsedArgs.constEnd(); iter++) {
args.insert(iter.key(), iter.value().toString());
for(auto iter = parsedArgs.begin(); iter != parsedArgs.end(); iter++) {
args[iter.key()] = iter.value().toString();
}
}
@ -56,8 +56,8 @@ QByteArray ApplicationMessage::serialize() {
QJsonObject root;
root.insert("command", command);
QJsonObject outArgs;
for (auto iter = args.constBegin(); iter != args.constEnd(); iter++) {
outArgs.insert(iter.key(), iter.value());
for (auto iter = args.begin(); iter != args.end(); iter++) {
outArgs[iter.key()] = iter.value();
}
root.insert("args", outArgs);

View File

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

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,22 +140,21 @@ 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([[maybe_unused]] const QString &line, MessageLevel::Enum level)
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
{
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)
{
@ -95,12 +95,12 @@ BaseVersionList::RoleList BaseVersionList::providesRoles() const
int BaseVersionList::rowCount(const QModelIndex &parent) const
{
// Return count
return parent.isValid() ? 0 : count();
return count();
}
int BaseVersionList::columnCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : 1;
return 1;
}
QHash<int, QByteArray> BaseVersionList::roleNames() const

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,15 +24,12 @@ 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
InstanceCreationTask.cpp
InstanceCopyPrefs.h
InstanceCopyPrefs.cpp
InstanceCopyTask.h
InstanceCopyTask.cpp
InstanceImportTask.h
@ -89,18 +86,13 @@ set(CORE_SOURCES
# Time
MMCTime.h
MMCTime.cpp
MTPixmapCache.h
)
if (UNIX AND NOT CYGWIN AND NOT APPLE)
set(CORE_SOURCES
${CORE_SOURCES}
# MangoHud
MangoHud.h
MangoHud.cpp
)
endif()
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
@ -108,7 +100,6 @@ set(PATHMATCHER_SOURCES
pathmatcher/IPathMatcher.h
pathmatcher/MultiMatcher.h
pathmatcher/RegexpMatcher.h
pathmatcher/SimplePrefixMatcher.h
)
set(NET_SOURCES
@ -303,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
@ -310,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
@ -327,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
@ -368,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
@ -389,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
@ -405,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
@ -420,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
@ -474,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
@ -501,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
@ -512,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
@ -528,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
@ -551,26 +570,14 @@ set(ATLAUNCHER_SOURCES
modplatform/atlauncher/ATLShareCode.h
)
######## Logging categories ########
ecm_qt_declare_logging_category(CORE_SOURCES
HEADER Logging.h
IDENTIFIER authCredentials
CATEGORY_NAME "launcher.auth.credentials"
DEFAULT_SEVERITY Warning
DESCRIPTION "Secrets and credentials for debugging purposes"
EXPORT "${Launcher_Name}"
)
if(KDE_INSTALL_LOGGINGCATEGORIESDIR) # only install if there is a standard path for this
ecm_qt_install_logging_categories(
EXPORT "${Launcher_Name}"
DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}"
)
endif()
ecm_add_test(meta/Index_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME Index)
################################ COMPILE ################################
# we need zlib
find_package(ZLIB REQUIRED)
set(LOGIC_SOURCES
${CORE_SOURCES}
${PATHMATCHER_SOURCES}
@ -605,8 +612,6 @@ SET(LAUNCHER_SOURCES
# Application base
Application.h
Application.cpp
DataMigrationTask.h
DataMigrationTask.cpp
UpdateController.cpp
UpdateController.h
ApplicationMessage.h
@ -630,12 +635,9 @@ SET(LAUNCHER_SOURCES
resources/pe_light/pe_light.qrc
resources/pe_colored/pe_colored.qrc
resources/pe_blue/pe_blue.qrc
resources/breeze_dark/breeze_dark.qrc
resources/breeze_light/breeze_light.qrc
resources/OSX/OSX.qrc
resources/iOS/iOS.qrc
resources/flat/flat.qrc
resources/flat_white/flat_white.qrc
resources/documents/documents.qrc
../${Launcher_Branding_LogoQRC}
@ -683,8 +685,6 @@ SET(LAUNCHER_SOURCES
ui/themes/ITheme.h
ui/themes/SystemTheme.cpp
ui/themes/SystemTheme.h
ui/themes/ThemeManager.cpp
ui/themes/ThemeManager.h
# Processes
LaunchController.h
@ -709,8 +709,6 @@ SET(LAUNCHER_SOURCES
ui/pages/instance/GameOptionsPage.h
ui/pages/instance/VersionPage.cpp
ui/pages/instance/VersionPage.h
ui/pages/instance/ManagedPackPage.cpp
ui/pages/instance/ManagedPackPage.h
ui/pages/instance/TexturePackPage.h
ui/pages/instance/ResourcePackPage.h
ui/pages/instance/ShaderPackPage.h
@ -768,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
@ -827,8 +823,6 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ExportInstanceDialog.h
ui/dialogs/IconPickerDialog.cpp
ui/dialogs/IconPickerDialog.h
ui/dialogs/ImportResourcePackDialog.cpp
ui/dialogs/ImportResourcePackDialog.h
ui/dialogs/LoginDialog.cpp
ui/dialogs/LoginDialog.h
ui/dialogs/MSALoginDialog.cpp
@ -857,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
@ -885,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
@ -894,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
@ -940,7 +926,6 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/instance/OtherLogsPage.ui
ui/pages/instance/InstanceSettingsPage.ui
ui/pages/instance/VersionPage.ui
ui/pages/instance/ManagedPackPage.ui
ui/pages/instance/WorldListPage.ui
ui/pages/instance/ScreenshotsPage.ui
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
@ -955,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
@ -968,7 +953,6 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/SkinUploadDialog.ui
ui/dialogs/ExportInstanceDialog.ui
ui/dialogs/IconPickerDialog.ui
ui/dialogs/ImportResourcePackDialog.ui
ui/dialogs/MSALoginDialog.ui
ui/dialogs/OfflineLoginDialog.ui
ui/dialogs/AboutDialog.ui
@ -976,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
)
@ -987,8 +970,6 @@ qt_add_resources(LAUNCHER_RESOURCES
resources/pe_light/pe_light.qrc
resources/pe_colored/pe_colored.qrc
resources/pe_blue/pe_blue.qrc
resources/breeze_dark/breeze_dark.qrc
resources/breeze_light/breeze_light.qrc
resources/OSX/OSX.qrc
resources/iOS/iOS.qrc
resources/flat/flat.qrc
@ -1003,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)
@ -1095,99 +1075,96 @@ if(INSTALL_BUNDLE STREQUAL "full")
COMPONENT Runtime
)
# Bundle plugins
# Image formats
install(
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "tga|tiff|mng" EXCLUDE
)
install(
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
CONFIGURATIONS Release MinSizeRel
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "tga|tiff|mng" EXCLUDE
REGEX "d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
)
# Icon engines
install(
DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "fontawesome" EXCLUDE
)
install(
DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
CONFIGURATIONS Release MinSizeRel
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "fontawesome" EXCLUDE
REGEX "d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
)
# Platform plugins
install(
DIRECTORY "${QT_PLUGINS_DIR}/platforms"
CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "minimal|linuxfb|offscreen" EXCLUDE
)
install(
DIRECTORY "${QT_PLUGINS_DIR}/platforms"
CONFIGURATIONS Release MinSizeRel
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "minimal|linuxfb|offscreen" EXCLUDE
REGEX "[^2]d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
)
# Style plugins
if(EXISTS "${QT_PLUGINS_DIR}/styles")
if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
# Image formats
install(
DIRECTORY "${QT_PLUGINS_DIR}/styles"
CONFIGURATIONS Debug RelWithDebInfo ""
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "tga|tiff|mng" EXCLUDE
)
# Icon engines
install(
DIRECTORY "${QT_PLUGINS_DIR}/styles"
CONFIGURATIONS Release MinSizeRel
DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "fontawesome" EXCLUDE
)
# Platform plugins
install(
DIRECTORY "${QT_PLUGINS_DIR}/platforms"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "minimal|linuxfb|offscreen" EXCLUDE
)
# Style plugins
if(EXISTS "${QT_PLUGINS_DIR}/styles")
install(
DIRECTORY "${QT_PLUGINS_DIR}/styles"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
)
endif()
# TLS plugins (Qt 6 only)
if(EXISTS "${QT_PLUGINS_DIR}/tls")
install(
DIRECTORY "${QT_PLUGINS_DIR}/tls"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
)
endif()
else()
# Image formats
install(
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "tga|tiff|mng" EXCLUDE
REGEX "d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
)
endif()
# TLS plugins (Qt 6 only)
if(EXISTS "${QT_PLUGINS_DIR}/tls")
# Icon engines
install(
DIRECTORY "${QT_PLUGINS_DIR}/tls"
CONFIGURATIONS Debug RelWithDebInfo ""
DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
PATTERN "*qopensslbackend*" EXCLUDE
PATTERN "*qcertonlybackend*" EXCLUDE
)
install(
DIRECTORY "${QT_PLUGINS_DIR}/tls"
CONFIGURATIONS Release MinSizeRel
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "dd\\." EXCLUDE
REGEX "fontawesome" EXCLUDE
REGEX "d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
PATTERN "*qopensslbackend*" EXCLUDE
PATTERN "*qcertonlybackend*" EXCLUDE
)
# Platform plugins
install(
DIRECTORY "${QT_PLUGINS_DIR}/platforms"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "minimal|linuxfb|offscreen" EXCLUDE
REGEX "d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
)
# Style plugins
if(EXISTS "${QT_PLUGINS_DIR}/styles")
install(
DIRECTORY "${QT_PLUGINS_DIR}/styles"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
)
endif()
# TLS plugins (Qt 6 only)
if(EXISTS "${QT_PLUGINS_DIR}/tls")
install(
DIRECTORY "${QT_PLUGINS_DIR}/tls"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
)
endif()
endif()
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"

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

@ -1,96 +0,0 @@
// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
//
// SPDX-License-Identifier: GPL-3.0-only
#include "DataMigrationTask.h"
#include "FileSystem.h"
#include <QDirIterator>
#include <QFileInfo>
#include <QMap>
#include <QtConcurrent>
DataMigrationTask::DataMigrationTask(QObject* parent,
const QString& sourcePath,
const QString& targetPath,
const IPathMatcher::Ptr pathMatcher)
: Task(parent), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
{
m_copy.matcher(m_pathMatcher.get()).whitelist(true);
}
void DataMigrationTask::executeTask()
{
setStatus(tr("Scanning files..."));
// 1. Scan
// Check how many files we gotta copy
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
return m_copy(true); // dry run to collect amount of files
});
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::dryRunAborted);
m_copyFutureWatcher.setFuture(m_copyFuture);
}
void DataMigrationTask::dryRunFinished()
{
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::dryRunAborted);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
if (!m_copyFuture.isValid() || !m_copyFuture.result()) {
#else
if (!m_copyFuture.result()) {
#endif
emitFailed(tr("Failed to scan source path."));
return;
}
// 2. Copy
// Actually copy all files now.
m_toCopy = m_copy.totalCopied();
connect(&m_copy, &FS::copy::fileCopied, [&, this](const QString& relativeName) {
QString shortenedName = relativeName;
// shorten the filename to hopefully fit into one line
if (shortenedName.length() > 50)
shortenedName = relativeName.left(20) + "" + relativeName.right(29);
setProgress(m_copy.totalCopied(), m_toCopy);
setStatus(tr("Copying %1…").arg(shortenedName));
});
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
return m_copy(false); // actually copy now
});
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::copyAborted);
m_copyFutureWatcher.setFuture(m_copyFuture);
}
void DataMigrationTask::dryRunAborted()
{
emitFailed(tr("Aborted"));
}
void DataMigrationTask::copyFinished()
{
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::copyAborted);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
if (!m_copyFuture.isValid() || !m_copyFuture.result()) {
#else
if (!m_copyFuture.result()) {
#endif
emitFailed(tr("Some paths could not be copied!"));
return;
}
emitSucceeded();
}
void DataMigrationTask::copyAborted()
{
emitFailed(tr("Aborted"));
}

View File

@ -1,42 +0,0 @@
// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
//
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include "FileSystem.h"
#include "pathmatcher/IPathMatcher.h"
#include "tasks/Task.h"
#include <QFuture>
#include <QFutureWatcher>
/*
* Migrate existing data from other MMC-like launchers.
*/
class DataMigrationTask : public Task {
Q_OBJECT
public:
explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathmatcher);
~DataMigrationTask() override = default;
protected:
virtual void executeTask() override;
protected slots:
void dryRunFinished();
void dryRunAborted();
void copyFinished();
void copyAborted();
private:
const QString& m_sourcePath;
const QString& m_targetPath;
const IPathMatcher::Ptr m_pathMatcher;
FS::copy m_copy;
int m_toCopy = 0;
QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher;
};

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,89 +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
#define WIN32_LEAN_AND_MEAN
#include <objbase.h>
#include <objidl.h>
#include <shlguid.h>
#include <shlobj.h>
#include <shobjidl.h>
#include <sys/utime.h>
#include <versionhelpers.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;
}
@ -151,113 +138,149 @@ bool ensureFolderPathExists(QString foldernamepath)
return success;
}
/// @brief Copies a directory and it's contents from src to dest
/// @param offset subdirectory form src to copy to dest
/// @return if there was an error during the filecopy
bool copy::operator()(const QString& offset, bool dryRun)
bool copy::operator()(const QString &offset)
{
using copy_opts = fs::copy_options;
m_copied = 0; // reset counter
// 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_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
return;
auto dst_path = PathCombine(dst, relative_dst_path);
if (!dryRun) {
ensureFilePathExists(dst_path);
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
if(!m_followSymlinks && currentSrc.isSymLink())
{
qDebug() << "creating symlink" << src << " - " << dst;
if (!ensureFilePathExists(dst))
{
qWarning() << "Cannot create path!";
return false;
}
if (err) {
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
qDebug() << "Source file:" << src_path;
qDebug() << "Destination file:" << dst_path;
}
m_copied++;
emit fileCopied(relative_dst_path);
};
// 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;
#else
// FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal
if (DesktopServices::isFlatpak())
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
if (IsWindowsServer())
return false;
#endif
return QFile::moveToTrash(path, pathInTrash);
QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath());
auto wString = nativePath.toStdWString();
DWORD dwAttrs = GetFileAttributesW(wString.c_str());
// Windows: check for junctions, reparse points and other nasty things of that sort
if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT)
{
if (info.isFile())
{
OK &= QFile::remove(info.absoluteFilePath());
}
else if (info.isDir())
{
OK &= dir.rmdir(info.absoluteFilePath());
}
}
#else
// We do not trust Qt with reparse points, but do trust it with unix symlinks.
if(info.isSymLink())
{
OK &= QFile::remove(info.absoluteFilePath());
}
#endif
else if (info.isDir())
{
OK &= deletePath(info.absoluteFilePath());
}
else if (info.isFile())
{
OK &= QFile::remove(info.absoluteFilePath());
}
else
{
OK = false;
qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath();
}
}
OK &= dir.rmdir(dir.absolutePath());
return OK;
}
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);
}
@ -269,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();
@ -296,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;
}
}
@ -307,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;
}
}
@ -320,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
@ -343,41 +378,68 @@ 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 destination, QString target, QStringList args, QString name, QString icon)
bool createShortCut(QString location, QString dest, QStringList args, QString name,
QString icon)
{
#if defined(Q_OS_MACOS)
destination += ".command";
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
location = PathCombine(location, name + ".desktop");
QFile f(destination);
f.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream stream(&f);
QString argstring;
if (!args.empty())
argstring = " \"" + args.join("\" \"") + "\"";
stream << "#!/bin/bash"
<< "\n";
stream << "\""
<< target
<< "\" "
<< argstring
<< "\n";
stream.flush();
f.close();
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
return true;
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
QFile f(destination);
QFile f(location);
f.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream stream(&f);
@ -389,170 +451,84 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
<< "\n";
stream << "Type=Application"
<< "\n";
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
stream << "TryExec=" << dest.toLocal8Bit() << "\n";
stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n";
stream << "Name=" << name.toLocal8Bit() << "\n";
if (!icon.isEmpty())
{
stream << "Icon=" << icon.toLocal8Bit() << "\n";
}
stream << "Icon=" << icon.toLocal8Bit() << "\n";
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)
QFileInfo targetInfo(target);
#elif defined Q_OS_WIN
// TODO: Fix
// QFile file(PathCombine(location, name + ".lnk"));
// WCHAR *file_w;
// WCHAR *dest_w;
// WCHAR *args_w;
// file.fileName().toWCharArray(file_w);
// dest.toWCharArray(dest_w);
if (!targetInfo.exists())
{
qWarning() << "Target file does not exist!";
return false;
}
// QString argStr;
// for (int i = 0; i < args.count(); i++)
// {
// argStr.append(args[i]);
// argStr.append(" ");
// }
// argStr.toWCharArray(args_w);
target = targetInfo.absoluteFilePath();
if (target.length() >= MAX_PATH)
{
qWarning() << "Target file path is too long!";
return false;
}
if (!icon.isEmpty() && icon.length() >= MAX_PATH)
{
qWarning() << "Icon path is too long!";
return false;
}
destination += ".lnk";
if (destination.length() >= MAX_PATH)
{
qWarning() << "Destination path is too long!";
return false;
}
QString argStr;
int argCount = args.count();
for (int i = 0; i < argCount; i++)
{
if (args[i].contains(' '))
{
argStr.append('"').append(args[i]).append('"');
}
else
{
argStr.append(args[i]);
}
if (i < argCount - 1)
{
argStr.append(" ");
}
}
if (argStr.length() >= MAX_PATH)
{
qWarning() << "Arguments string is too long!";
return false;
}
HRESULT hres;
// ...yes, you need to initialize the entire COM stack just to make a shortcut
hres = CoInitialize(nullptr);
if (FAILED(hres))
{
qWarning() << "Failed to initialize COM!";
return false;
}
WCHAR wsz[MAX_PATH];
IShellLink* psl;
// create an IShellLink instance - this stores the shortcut's attributes
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
if (SUCCEEDED(hres))
{
wmemset(wsz, 0, MAX_PATH);
target.toWCharArray(wsz);
psl->SetPath(wsz);
wmemset(wsz, 0, MAX_PATH);
argStr.toWCharArray(wsz);
psl->SetArguments(wsz);
wmemset(wsz, 0, MAX_PATH);
targetInfo.absolutePath().toWCharArray(wsz);
psl->SetWorkingDirectory(wsz); // "Starts in" attribute
if (!icon.isEmpty())
{
wmemset(wsz, 0, MAX_PATH);
icon.toWCharArray(wsz);
psl->SetIconLocation(wsz, 0);
}
// query an IPersistFile interface from our IShellLink instance
// this is the interface that will actually let us save the shortcut to disk!
IPersistFile* ppf;
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
if (SUCCEEDED(hres))
{
wmemset(wsz, 0, MAX_PATH);
destination.toWCharArray(wsz);
hres = ppf->Save(wsz, TRUE);
if (FAILED(hres))
{
qWarning() << "IPresistFile->Save() failed";
qWarning() << "hres = " << hres;
}
ppf->Release();
}
else
{
qWarning() << "Failed to query IPersistFile interface from IShellLink instance";
qWarning() << "hres = " << hres;
}
psl->Release();
}
else
{
qWarning() << "Failed to create IShellLink instance";
qWarning() << "hres = " << hres;
}
// go away COM, nobody likes you
CoUninitialize();
return SUCCEEDED(hres);
// return SUCCEEDED(CreateLink(file_w, dest_w, args_w));
return false;
#else
qWarning("Desktop Shortcuts not supported on your platform!");
return false;
#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

@ -40,29 +40,30 @@
#include <QDir>
#include <QFlags>
#include <QObject>
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
@ -76,49 +77,37 @@ bool ensureFilePathExists(QString filenamepath);
*/
bool ensureFolderPathExists(QString filenamepath);
/// @brief Copies a directory and it's contents from src to dest
class copy : public QObject {
Q_OBJECT
public:
copy(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
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& matcher(const IPathMatcher* filter)
copy & blacklist(const IPathMatcher * filter)
{
m_matcher = filter;
m_blacklist = filter;
return *this;
}
copy& whitelist(bool whitelist)
bool operator()()
{
m_whitelist = whitelist;
return *this;
return operator()(QString());
}
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
private:
bool operator()(const QString &offset);
int totalCopied() { return m_copied; }
signals:
void fileCopied(const QString& relativeName);
// TODO: maybe add a "shouldCopy" signal in the future?
private:
bool operator()(const QString& offset, bool dryRun = false);
private:
private:
bool m_followSymlinks = true;
const IPathMatcher* m_matcher = nullptr;
bool m_whitelist = false;
const IPathMatcher * m_blacklist = nullptr;
QDir m_src;
QDir m_dst;
int m_copied;
};
/**
@ -126,14 +115,9 @@ class copy : public QObject {
*/
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);
@ -172,9 +156,4 @@ QString getDesktopDir();
// Overrides one folder with the contents of another, preserving items exclusive to the first folder
// Equivalent to doing QDir::rename, but allowing for overrides
bool overrideFolder(QString overwritten_path, QString override_path);
/**
* Creates a shortcut to the specified target file at the specified destination path.
*/
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon);
}

View File

@ -0,0 +1,122 @@
#include <QTest>
#include <QTemporaryDir>
#include <QStandardPaths>
#include "FileSystem.h"
class FileSystemTest : public QObject
{
Q_OBJECT
const QString bothSlash = "/foo/";
const QString trailingSlash = "foo/";
const QString leadingSlash = "/foo";
private
slots:
void test_pathCombine()
{
QCOMPARE(QString("/foo/foo"), FS::PathCombine(bothSlash, bothSlash));
QCOMPARE(QString("foo/foo"), FS::PathCombine(trailingSlash, trailingSlash));
QCOMPARE(QString("/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash));
QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(bothSlash, bothSlash, bothSlash));
QCOMPARE(QString("foo/foo/foo"), FS::PathCombine(trailingSlash, trailingSlash, trailingSlash));
QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash, leadingSlash));
}
void test_PathCombine1_data()
{
QTest::addColumn<QString>("result");
QTest::addColumn<QString>("path1");
QTest::addColumn<QString>("path2");
QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc/def" << "ghi/jkl";
QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/def/" << "ghi/jkl";
#if defined(Q_OS_WIN)
QTest::newRow("win native, from C:") << "C:/abc" << "C:" << "abc";
QTest::newRow("win native 1") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def" << "ghi\\jkl";
QTest::newRow("win native 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def\\" << "ghi\\jkl";
#endif
}
void test_PathCombine1()
{
QFETCH(QString, result);
QFETCH(QString, path1);
QFETCH(QString, path2);
QCOMPARE(FS::PathCombine(path1, path2), result);
}
void test_PathCombine2_data()
{
QTest::addColumn<QString>("result");
QTest::addColumn<QString>("path1");
QTest::addColumn<QString>("path2");
QTest::addColumn<QString>("path3");
QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc" << "def" << "ghi/jkl";
QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/" << "def" << "ghi/jkl";
QTest::newRow("qt 3") << "/abc/def/ghi/jkl" << "/abc" << "def/" << "ghi/jkl";
QTest::newRow("qt 4") << "/abc/def/ghi/jkl" << "/abc/" << "def/" << "ghi/jkl";
#if defined(Q_OS_WIN)
QTest::newRow("win 1") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def" << "ghi\\jkl";
QTest::newRow("win 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl";
QTest::newRow("win 3") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def\\" << "ghi\\jkl";
QTest::newRow("win 4") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl";
#endif
}
void test_PathCombine2()
{
QFETCH(QString, result);
QFETCH(QString, path1);
QFETCH(QString, path2);
QFETCH(QString, path3);
QCOMPARE(FS::PathCombine(path1, path2, path3), result);
}
void test_copy()
{
QString folder = QFINDTESTDATA("testdata/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();
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_getDesktop()
{
QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
}
};
QTEST_GUILESS_MAIN(FileSystemTest)
#include "FileSystem_test.moc"

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)
@ -24,7 +24,7 @@ slots:
QByteArray compressed;
QByteArray decompressed;
std::default_random_engine eng((std::random_device())());
std::uniform_int_distribution<uint16_t> idis(0, std::numeric_limits<uint8_t>::max());
std::uniform_int_distribution<uint8_t> idis(0, std::numeric_limits<uint8_t>::max());
// initialize random buffer
for(int i = 0; i < size; i++)

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

@ -1,135 +0,0 @@
//
// Created by marcelohdez on 10/22/22.
//
#include "InstanceCopyPrefs.h"
bool InstanceCopyPrefs::allTrue() const
{
return copySaves &&
keepPlaytime &&
copyGameOptions &&
copyResourcePacks &&
copyShaderPacks &&
copyServers &&
copyMods &&
copyScreenshots;
}
// Returns a single RegEx string of the selected folders/files to filter out (ex: ".minecraft/saves|.minecraft/server.dat")
QString InstanceCopyPrefs::getSelectedFiltersAsRegex() const
{
QStringList filters;
if(!copySaves)
filters << "saves";
if(!copyGameOptions)
filters << "options.txt";
if(!copyResourcePacks)
filters << "resourcepacks" << "texturepacks";
if(!copyShaderPacks)
filters << "shaderpacks";
if(!copyServers)
filters << "servers.dat" << "servers.dat_old" << "server-resource-packs";
if(!copyMods)
filters << "coremods" << "mods" << "config";
if(!copyScreenshots)
filters << "screenshots";
// If we have any filters to add, join them as a single regex string to return:
if (!filters.isEmpty()) {
const QString MC_ROOT = "[.]?minecraft/";
// Ensure first filter starts with root, then join other filters with OR regex before root (ex: ".minecraft/saves|.minecraft/mods"):
return MC_ROOT + filters.join("|" + MC_ROOT);
}
return {};
}
// ======= Getters =======
bool InstanceCopyPrefs::isCopySavesEnabled() const
{
return copySaves;
}
bool InstanceCopyPrefs::isKeepPlaytimeEnabled() const
{
return keepPlaytime;
}
bool InstanceCopyPrefs::isCopyGameOptionsEnabled() const
{
return copyGameOptions;
}
bool InstanceCopyPrefs::isCopyResourcePacksEnabled() const
{
return copyResourcePacks;
}
bool InstanceCopyPrefs::isCopyShaderPacksEnabled() const
{
return copyShaderPacks;
}
bool InstanceCopyPrefs::isCopyServersEnabled() const
{
return copyServers;
}
bool InstanceCopyPrefs::isCopyModsEnabled() const
{
return copyMods;
}
bool InstanceCopyPrefs::isCopyScreenshotsEnabled() const
{
return copyScreenshots;
}
// ======= Setters =======
void InstanceCopyPrefs::enableCopySaves(bool b)
{
copySaves = b;
}
void InstanceCopyPrefs::enableKeepPlaytime(bool b)
{
keepPlaytime = b;
}
void InstanceCopyPrefs::enableCopyGameOptions(bool b)
{
copyGameOptions = b;
}
void InstanceCopyPrefs::enableCopyResourcePacks(bool b)
{
copyResourcePacks = b;
}
void InstanceCopyPrefs::enableCopyShaderPacks(bool b)
{
copyShaderPacks = b;
}
void InstanceCopyPrefs::enableCopyServers(bool b)
{
copyServers = b;
}
void InstanceCopyPrefs::enableCopyMods(bool b)
{
copyMods = b;
}
void InstanceCopyPrefs::enableCopyScreenshots(bool b)
{
copyScreenshots = b;
}

View File

@ -1,41 +0,0 @@
//
// Created by marcelohdez on 10/22/22.
//
#pragma once
#include <QStringList>
struct InstanceCopyPrefs {
public:
[[nodiscard]] bool allTrue() const;
[[nodiscard]] QString getSelectedFiltersAsRegex() const;
// Getters
[[nodiscard]] bool isCopySavesEnabled() const;
[[nodiscard]] bool isKeepPlaytimeEnabled() const;
[[nodiscard]] bool isCopyGameOptionsEnabled() const;
[[nodiscard]] bool isCopyResourcePacksEnabled() const;
[[nodiscard]] bool isCopyShaderPacksEnabled() const;
[[nodiscard]] bool isCopyServersEnabled() const;
[[nodiscard]] bool isCopyModsEnabled() const;
[[nodiscard]] bool isCopyScreenshotsEnabled() const;
// Setters
void enableCopySaves(bool b);
void enableKeepPlaytime(bool b);
void enableCopyGameOptions(bool b);
void enableCopyResourcePacks(bool b);
void enableCopyShaderPacks(bool b);
void enableCopyServers(bool b);
void enableCopyMods(bool b);
void enableCopyScreenshots(bool b);
protected: // data
bool copySaves = true;
bool keepPlaytime = true;
bool copyGameOptions = true;
bool copyResourcePacks = true;
bool copyShaderPacks = true;
bool copyServers = true;
bool copyMods = true;
bool copyScreenshots = true;
};

View File

@ -5,17 +5,15 @@
#include "pathmatcher/RegexpMatcher.h"
#include <QtConcurrentRun>
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs)
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime)
{
m_origInstance = origInstance;
m_keepPlaytime = prefs.isKeepPlaytimeEnabled();
m_keepPlaytime = keepPlaytime;
QString filters = prefs.getSelectedFiltersAsRegex();
if (!filters.isEmpty())
if(!copySaves)
{
// Set regex filter:
// FIXME: get this from the original instance type...
auto matcherReal = new RegexpMatcher(filters);
auto matcherReal = new RegexpMatcher("[.]?minecraft/saves");
matcherReal->caseSensitive(false);
m_matcher.reset(matcherReal);
}
@ -25,12 +23,10 @@ void InstanceCopyTask::executeTask()
{
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]{
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
folderCopy.followSymlinks(false).matcher(m_matcher.get());
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
folderCopy.followSymlinks(false).blacklist(m_matcher.get());
return folderCopy();
});
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
m_copyFutureWatcher.setFuture(m_copyFuture);
@ -48,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,21 +1,20 @@
#pragma once
#include "tasks/Task.h"
#include "net/NetJob.h"
#include <QUrl>
#include <QFuture>
#include <QFutureWatcher>
#include <QUrl>
#include "BaseInstance.h"
#include "BaseVersion.h"
#include "InstanceCopyPrefs.h"
#include "InstanceTask.h"
#include "net/NetJob.h"
#include "settings/SettingsObject.h"
#include "tasks/Task.h"
#include "BaseVersion.h"
#include "BaseInstance.h"
#include "InstanceTask.h"
class InstanceCopyTask : public InstanceTask
{
Q_OBJECT
public:
explicit InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs);
explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime);
protected:
//! Entry point for tasks.
@ -23,8 +22,7 @@ protected:
void copyFinished();
void copyAborted();
private:
/* data */
private: /* data */
InstancePtr m_origInstance;
QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher;

View File

@ -1,60 +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:\n%1").arg(m_error_message));
} else {
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,67 +35,72 @@
*/
#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>
// 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>
#include <quazip/quazipdir.h>
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent, QMap<QString, QString>&& extra_info)
: m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent)
{}
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
{
m_sourceUrl = sourceUrl;
m_parent = 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();
}
}
@ -114,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()
@ -162,14 +161,18 @@ void InstanceImportTask::processZipPack()
}
else
{
QStringList paths_to_ignore { "overrides/" };
QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
if (QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg", paths_to_ignore); !mmcRoot.isNull()) {
if (!mmcRoot.isNull())
{
// process as MultiMC instance/pack
qDebug() << "MultiMC:" << mmcRoot;
root = mmcRoot;
m_modpackType = ModpackType::MultiMC;
} else if (QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json", paths_to_ignore); !flameRoot.isNull()) {
}
else if(!flameRoot.isNull())
{
// process as Flame pack
qDebug() << "Flame:" << flameRoot;
root = flameRoot;
@ -252,51 +255,290 @@ void InstanceImportTask::extractFinished()
void InstanceImportTask::extractAborted()
{
emitAborted();
emitFailed(tr("Instance import has been aborted."));
return;
}
void InstanceImportTask::processFlame()
{
FlameCreationTask* inst_creation_task = nullptr;
if (!m_extra_info.isEmpty()) {
auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
auto pack_id = pack_id_it.value();
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
Q_ASSERT(pack_version_id_it != m_extra_info.constEnd());
auto pack_version_id = pack_version_id_it.value();
QString original_instance_id;
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
if (original_instance_id_it != m_extra_info.constEnd())
original_instance_id = original_instance_id_it.value();
inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
} else {
// FIXME: Find a way to get IDs in directly imported ZIPs
inst_creation_task = 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);
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
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()
@ -304,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()
@ -318,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") {
@ -339,53 +581,198 @@ void InstanceImportTask::processMultiMC()
emitSucceeded();
}
// https://docs.modrinth.com/docs/modpacks/format_definition/
void InstanceImportTask::processModrinth()
{
ModrinthCreationTask* inst_creation_task = nullptr;
if (!m_extra_info.isEmpty()) {
auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
auto pack_id = pack_id_it.value();
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);
}
QString pack_version_id;
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
if (pack_version_id_it != m_extra_info.constEnd())
pack_version_id = pack_version_id_it.value();
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");
QString original_instance_id;
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
if (original_instance_id_it != m_extra_info.constEnd())
original_instance_id = original_instance_id_it.value();
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();
}
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
} else {
QString pack_id;
if (!m_sourceUrl.isEmpty()) {
QRegularExpression regex(R"(data\/(.*)\/versions)");
pack_id = regex.match(m_sourceUrl.toString()).captured(1);
if (file.path.endsWith(".jar"))
file.path += ".disabled";
}
}
QJsonObject hashes = Json::requireObject(modInfo, "hashes");
QString hash;
QCryptographicHash::Algorithm hashAlgorithm;
hash = Json::ensureString(hashes, "sha1");
hashAlgorithm = QCryptographicHash::Sha1;
if (hash.isEmpty()) {
hash = Json::ensureString(hashes, "sha512");
hashAlgorithm = QCryptographicHash::Sha512;
if (hash.isEmpty()) {
hash = Json::ensureString(hashes, "sha256");
hashAlgorithm = QCryptographicHash::Sha256;
if (hash.isEmpty()) {
throw JSONValidationError("No hash found for: " + file.path);
}
}
}
file.hash = QByteArray::fromHex(hash.toLatin1());
file.hashAlgorithm = hashAlgorithm;
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
// (as Modrinth seems to incorrectly handle spaces)
auto download_arr = Json::ensureArray(modInfo, "downloads");
for(auto download : download_arr) {
qWarning() << download.toString();
bool is_last = download.toString() == download_arr.last().toString();
auto download_url = QUrl(download.toString());
if (!download_url.isValid()) {
qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL")
.arg(download_url.toString(), file.path);
if(is_last && file.downloads.isEmpty())
throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
}
else {
file.downloads.push_back(download_url);
}
}
files.push_back(file);
}
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key();
if (name == "minecraft") {
minecraftVersion = Json::requireString(*it, "Minecraft version");
}
else if (name == "fabric-loader") {
fabricVersion = Json::requireString(*it, "Fabric Loader version");
}
else if (name == "quilt-loader") {
quiltVersion = Json::requireString(*it, "Quilt Loader version");
}
else if (name == "forge") {
forgeVersion = Json::requireString(*it, "Forge version");
}
else {
throw JSONValidationError("Unknown dependency type: " + name);
}
}
} else {
throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion));
}
QFile::remove(indexPath);
} catch (const JSONValidationError& e) {
emitFailed(tr("Could not understand pack index:\n") + e.cause());
return;
}
auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
// FIXME: Find a way to get the ID in directly imported ZIPs
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id);
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;
}
}
inst_creation_task->setName(*this);
inst_creation_task->setIcon(m_instIcon);
inst_creation_task->setGroup(m_instGroup);
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
emitSucceeded();
// 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

@ -56,8 +56,9 @@ class InstanceImportTask : public InstanceTask
{
Q_OBJECT
public:
explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
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();
@ -101,10 +101,6 @@ private: /* data */
Modrinth,
} m_modpackType = ModpackType::Unknown;
// Extra info we might need, that's available before, but can't be derived from
// the source URL / the resource it points to alone.
QMap<QString, QString> m_extra_info;
//FIXME: nuke
QWidget* m_parent;
};

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.get()))
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,47 +906,24 @@ QString InstanceList::getStagedInstancePath()
return path;
}
bool InstanceList::commitStagedInstance(const QString& path, InstanceName const& instanceName, const QString& groupName, InstanceTask const& commiting)
bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName)
{
QDir dir;
QString instID;
InstancePtr inst;
auto should_override = commiting.shouldOverride();
if (should_override) {
instID = commiting.originalInstanceID();
} else {
instID = FS::DirNameFromString(instanceName.modifiedName(), m_instDir);
}
Q_ASSERT(!instID.isEmpty());
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;
}
@ -930,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, const InstanceTask&);
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

@ -5,7 +5,6 @@
#include "ui/pages/BasePageProvider.h"
#include "ui/pages/instance/LogPage.h"
#include "ui/pages/instance/VersionPage.h"
#include "ui/pages/instance/ManagedPackPage.h"
#include "ui/pages/instance/ModFolderPage.h"
#include "ui/pages/instance/ResourcePackPage.h"
#include "ui/pages/instance/TexturePackPage.h"
@ -34,14 +33,13 @@ public:
values.append(new LogPage(inst));
std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst);
values.append(new VersionPage(onesix.get()));
values.append(ManagedPackPage::createPage(onesix.get()));
auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList());
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,75 +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;
}
ShouldUpdate askIfShouldUpdate(QWidget *parent, QString original_version_name)
InstanceTask::~InstanceTask()
{
auto info = CustomMessageBox::selectable(
parent, QObject::tr("Similar modpack was found!"),
QObject::tr("One or more of your instances are from this same modpack%1. Do you want to create a "
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
.arg(original_version_name),
QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
info->setButtonText(QMessageBox::Ok, QObject::tr("Update existing instance"));
info->setButtonText(QMessageBox::Abort, QObject::tr("Create new instance"));
info->setButtonText(QMessageBox::Reset, QObject::tr("Cancel"));
info->exec();
if (info->clickedButton() == info->button(QMessageBox::Ok))
return ShouldUpdate::Update;
if (info->clickedButton() == info->button(QMessageBox::Abort))
return ShouldUpdate::SkipUpdating;
return ShouldUpdate::Cancel;
}
QString InstanceName::name() const
{
if (!m_modified_name.isEmpty())
return modifiedName();
return QString("%1 %2").arg(m_original_name, m_original_version);
}
QString InstanceName::originalName() const
{
return m_original_name;
}
QString InstanceName::modifiedName() const
{
if (!m_modified_name.isEmpty())
return m_modified_name;
return m_original_name;
}
QString InstanceName::version() const
{
return m_original_version;
}
void InstanceName::setName(InstanceName& other)
{
m_original_name = other.m_original_name;
m_original_version = other.m_original_version;
m_modified_name = other.m_modified_name;
}
InstanceTask::InstanceTask() : Task(), InstanceName() {}

View File

@ -1,72 +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);
enum class ShouldUpdate { Update, SkipUpdating, Cancel };
[[nodiscard]] ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_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 setStagingPath(const QString& stagingPath) { m_stagingPath = stagingPath; }
void setIcon(const QString& icon) { m_instIcon = icon; }
void setGroup(const QString& group) { m_instGroup = group; }
QString group() const { return m_instGroup; }
[[nodiscard]] bool shouldConfirmUpdate() const { return m_confirm_update; }
void setConfirmUpdate(bool confirm) { m_confirm_update = confirm; }
bool shouldOverride() const { return m_override_existing; }
[[nodiscard]] QString originalInstanceID() const { return m_original_instance_id; };
protected:
void setOverride(bool override, QString instance_id_to_override = {})
void setParentSettings(SettingsObjectPtr settings)
{
m_override_existing = override;
if (!instance_id_to_override.isEmpty())
m_original_instance_id = instance_id_to_override;
m_globalSettings = settings;
}
protected: /* data */
void setStagingPath(const QString &stagingPath)
{
m_stagingPath = stagingPath;
}
void setName(const QString &name)
{
m_instName = name;
}
QString name() const
{
return m_instName;
}
void setIcon(const QString &icon)
{
m_instIcon = icon;
}
void setGroup(const QString &group)
{
m_instGroup = group;
}
QString group() const
{
return m_instGroup;
}
protected: /* data */
SettingsObjectPtr m_globalSettings;
QString m_instName;
QString m_instIcon;
QString m_instGroup;
QString m_stagingPath;
bool m_override_existing = false;
bool m_confirm_update = true;
QString m_original_instance_id;
};

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

@ -28,11 +28,11 @@ QString Time::prettifyDuration(int64_t duration) {
int days = (int) (duration / 24);
if((hours == 0)&&(days == 0))
{
return QObject::tr("%1min %2s").arg(minutes).arg(seconds);
return QObject::tr("%1m %2s").arg(minutes).arg(seconds);
}
if (days == 0)
{
return QObject::tr("%1h %2min").arg(hours).arg(minutes);
return QObject::tr("%1h %2m").arg(hours).arg(minutes);
}
return QObject::tr("%1d %2h %3min").arg(days).arg(hours).arg(minutes);
return QObject::tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes);
}

View File

@ -39,7 +39,6 @@
#include "MMCZip.h"
#include "FileSystem.h"
#include <QCoreApplication>
#include <QDebug>
// ours
@ -149,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))
{
@ -159,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();
@ -172,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
@ -229,27 +228,23 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
}
// ours
QString MMCZip::findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root)
QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root)
{
QuaZipDir rootDir(zip, root);
for (auto&& fileName : rootDir.entryList(QDir::Files)) {
if (fileName == what)
for(auto fileName: rootDir.entryList(QDir::Files))
{
if(fileName == what)
return root;
QCoreApplication::processEvents();
}
// Recurse the search to non-ignored subfolders
for (auto&& fileName : rootDir.entryList(QDir::Dirs)) {
if (ignore_paths.contains(fileName))
continue;
QString result = findFolderOfFileInZip(zip, what, ignore_paths, root + fileName);
if (!result.isEmpty())
for(auto fileName: rootDir.entryList(QDir::Dirs))
{
QString result = findFolderOfFileInZip(zip, what, root + fileName);
if(!result.isEmpty())
{
return result;
}
}
return {};
return QString();
}
// ours

View File

@ -80,11 +80,9 @@ namespace MMCZip
/**
* Find a single file in archive by file name (not path)
*
* \param ignore_paths paths to skip when recursing the search
*
* \return the path prefix where the file is
*/
QString findFolderOfFileInZip(QuaZip * zip, const QString & what, const QStringList& ignore_paths = {}, const QString &root = QString(""));
QString findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString(""));
/**
* Find a multiple files of the same name in archive by file name

View File

@ -1,95 +0,0 @@
#pragma once
#include <QCoreApplication>
#include <QPixmapCache>
#include <QThread>
#define GET_TYPE() \
Qt::ConnectionType type; \
if (QThread::currentThread() != QCoreApplication::instance()->thread()) \
type = Qt::BlockingQueuedConnection; \
else \
type = Qt::DirectConnection;
#define DEFINE_FUNC_NO_PARAM(NAME, RET_TYPE) \
static RET_TYPE NAME() \
{ \
RET_TYPE ret; \
GET_TYPE() \
QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret)); \
return ret; \
}
#define DEFINE_FUNC_ONE_PARAM(NAME, RET_TYPE, PARAM_1_TYPE) \
static RET_TYPE NAME(PARAM_1_TYPE p1) \
{ \
RET_TYPE ret; \
GET_TYPE() \
QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret), Q_ARG(PARAM_1_TYPE, p1)); \
return ret; \
}
#define DEFINE_FUNC_TWO_PARAM(NAME, RET_TYPE, PARAM_1_TYPE, PARAM_2_TYPE) \
static RET_TYPE NAME(PARAM_1_TYPE p1, PARAM_2_TYPE p2) \
{ \
RET_TYPE ret; \
GET_TYPE() \
QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret), Q_ARG(PARAM_1_TYPE, p1), \
Q_ARG(PARAM_2_TYPE, p2)); \
return ret; \
}
/** A wrapper around QPixmapCache with thread affinity with the main thread.
*/
class PixmapCache final : public QObject {
Q_OBJECT
public:
PixmapCache(QObject* parent) : QObject(parent) {}
~PixmapCache() override = default;
static PixmapCache& instance() { return *s_instance; }
static void setInstance(PixmapCache* i) { s_instance = i; }
public:
DEFINE_FUNC_NO_PARAM(cacheLimit, int)
DEFINE_FUNC_NO_PARAM(clear, bool)
DEFINE_FUNC_TWO_PARAM(find, bool, const QString&, QPixmap*)
DEFINE_FUNC_TWO_PARAM(find, bool, const QPixmapCache::Key&, QPixmap*)
DEFINE_FUNC_TWO_PARAM(insert, bool, const QString&, const QPixmap&)
DEFINE_FUNC_ONE_PARAM(insert, QPixmapCache::Key, const QPixmap&)
DEFINE_FUNC_ONE_PARAM(remove, bool, const QString&)
DEFINE_FUNC_ONE_PARAM(remove, bool, const QPixmapCache::Key&)
DEFINE_FUNC_TWO_PARAM(replace, bool, const QPixmapCache::Key&, const QPixmap&)
DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, int)
// NOTE: Every function returns something non-void to simplify the macros.
private slots:
int _cacheLimit() { return QPixmapCache::cacheLimit(); }
bool _clear()
{
QPixmapCache::clear();
return true;
}
bool _find(const QString& key, QPixmap* pixmap) { return QPixmapCache::find(key, pixmap); }
bool _find(const QPixmapCache::Key& key, QPixmap* pixmap) { return QPixmapCache::find(key, pixmap); }
bool _insert(const QString& key, const QPixmap& pixmap) { return QPixmapCache::insert(key, pixmap); }
QPixmapCache::Key _insert(const QPixmap& pixmap) { return QPixmapCache::insert(pixmap); }
bool _remove(const QString& key)
{
QPixmapCache::remove(key);
return true;
}
bool _remove(const QPixmapCache::Key& key)
{
QPixmapCache::remove(key);
return true;
}
bool _replace(const QPixmapCache::Key& key, const QPixmap& pixmap) { return QPixmapCache::replace(key, pixmap); }
bool _setCacheLimit(int n)
{
QPixmapCache::setCacheLimit(n);
return true;
}
private:
static PixmapCache* s_instance;
};

View File

@ -1,90 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PrismLauncher - Minecraft Launcher
* Copyright (C) 2022 Jan Drögehoff <sentrycraft123@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <QStringList>
#include <QDir>
#include <QString>
#include <QtGlobal>
#include "MangoHud.h"
#include "FileSystem.h"
#include "Json.h"
namespace MangoHud {
QString getLibraryString()
{
/*
* Check for vulkan layers in this order:
*
* $VK_LAYER_PATH
* $XDG_DATA_DIRS (/usr/local/share/:/usr/share/)
* $XDG_DATA_HOME (~/.local/share)
* /etc
* $XDG_CONFIG_DIRS (/etc/xdg)
* $XDG_CONFIG_HOME (~/.config)
*/
QStringList vkLayerList;
{
QString home = QDir::homePath();
QString vkLayerPath = qEnvironmentVariable("VK_LAYER_PATH");
if (!vkLayerPath.isEmpty()) {
vkLayerList << vkLayerPath;
}
QStringList xdgDataDirs = qEnvironmentVariable("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/").split(QLatin1String(":"));
for (QString dir : xdgDataDirs) {
vkLayerList << FS::PathCombine(dir, "vulkan", "implicit_layer.d");
}
QString xdgDataHome = qEnvironmentVariable("XDG_DATA_HOME");
if (xdgDataHome.isEmpty()) {
xdgDataHome = FS::PathCombine(home, ".local", "share");
}
vkLayerList << FS::PathCombine(xdgDataHome, "vulkan", "implicit_layer.d");
vkLayerList << "/etc";
QStringList xdgConfigDirs = qEnvironmentVariable("XDG_CONFIG_DIRS", "/etc/xdg").split(QLatin1String(":"));
for (QString dir : xdgConfigDirs) {
vkLayerList << FS::PathCombine(dir, "vulkan", "implicit_layer.d");
}
QString xdgConfigHome = qEnvironmentVariable("XDG_CONFIG_HOME");
if (xdgConfigHome.isEmpty()) {
xdgConfigHome = FS::PathCombine(home, ".config");
}
vkLayerList << FS::PathCombine(xdgConfigHome, "vulkan", "implicit_layer.d");
}
for (QString vkLayer : vkLayerList) {
QString filePath = FS::PathCombine(vkLayer, "MangoHud.json");
if (!QFile::exists(filePath))
continue;
auto conf = Json::requireDocument(filePath, vkLayer);
auto confObject = Json::requireObject(conf, vkLayer);
auto layer = Json::ensureObject(confObject, "layer");
return Json::ensureString(layer, "library_path");
}
return QString();
}
} // namespace MangoHud

View File

@ -1,27 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PrismLauncher - Minecraft Launcher
* Copyright (C) 2022 Jan Drögehoff <sentrycraft123@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
#include <QStringList>
namespace MangoHud {
QString getLibraryString();
}

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

@ -311,14 +311,14 @@ QModelIndex VersionProxyModel::index(int row, int column, const QModelIndex &par
int VersionProxyModel::columnCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : m_columns.size();
return m_columns.size();
}
int VersionProxyModel::rowCount(const QModelIndex &parent) const
{
if(sourceModel())
{
return sourceModel()->rowCount(parent);
return sourceModel()->rowCount();
}
return 0;
}

View File

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

View File

@ -242,7 +242,7 @@ Qt::DropActions IconList::supportedDropActions() const
return Qt::CopyAction;
}
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column, [[maybe_unused]] const QModelIndex &parent)
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
return true;
@ -302,7 +302,7 @@ QVariant IconList::data(const QModelIndex &index, int role) const
int IconList::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : icons.size();
return icons.size();
}
void IconList::installIcons(const QStringList &iconFiles)

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)
@ -439,31 +429,20 @@ QList<QString> JavaUtils::FindJavaPaths()
javas.append(FS::PathCombine(prefix, "bin/java"));
}
};
// java installed in a snap is installed in the standard directory, but underneath $SNAP
auto snap = qEnvironmentVariable("SNAP");
auto scanJavaDirs = [&](const QString & dirPath)
{
scanJavaDir(dirPath);
if (!snap.isNull()) {
scanJavaDir(snap + dirPath);
}
};
// oracle RPMs
scanJavaDirs("/usr/java");
scanJavaDir("/usr/java");
// general locations used by distro packaging
scanJavaDirs("/usr/lib/jvm");
scanJavaDirs("/usr/lib64/jvm");
scanJavaDirs("/usr/lib32/jvm");
// javas stored in Prism Launcher's folder
scanJavaDirs("java");
scanJavaDir("/usr/lib/jvm");
scanJavaDir("/usr/lib64/jvm");
scanJavaDir("/usr/lib32/jvm");
// javas stored in PolyMC's folder
scanJavaDir("java");
// manually installed JDKs in /opt
scanJavaDirs("/opt/jdk");
scanJavaDirs("/opt/jdks");
scanJavaDir("/opt/jdk");
scanJavaDir("/opt/jdks");
// flatpak
scanJavaDirs("/app/jdk");
javas = addJavasFromEnv(javas);
javas.removeDuplicates();
return javas;
scanJavaDir("/app/jdk");
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,25 +75,20 @@ 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);
Q_INIT_RESOURCE(pe_blue);
Q_INIT_RESOURCE(pe_colored);
Q_INIT_RESOURCE(breeze_dark);
Q_INIT_RESOURCE(breeze_light);
Q_INIT_RESOURCE(OSX);
Q_INIT_RESOURCE(iOS);
Q_INIT_RESOURCE(flat);
Q_INIT_RESOURCE(flat_white);
return app.exec();
}
case Application::Failed:
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:
@ -58,11 +58,11 @@ QVariant Index::data(const QModelIndex &index, int role) const
}
int Index::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : m_lists.size();
return m_lists.size();
}
int Index::columnCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : 1;
return 1;
}
QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
{
@ -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

@ -60,6 +60,11 @@ struct Require
QString suggests;
};
inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW
{
return qHash(key.uid, seed);
}
using RequireSet = std::set<Require>;
void parseIndex(const QJsonObject &obj, Index *ptr);

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)

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