Compare commits

..

49 Commits

Author SHA1 Message Date
Sefa Eyeoglu
790e077ca8
Merge pull request #457 from Scrumplex/chore-bump-5.2 2022-11-15 17:15:09 +01:00
Sefa Eyeoglu
10ac6cb266
chore: bump version
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2022-11-15 16:51:56 +01:00
Sefa Eyeoglu
4c9859b16e
Merge pull request #445 from flowln/optimize_edit_instance 2022-11-15 14:47:36 +01:00
flow
4b2c7a8a64
Merge pull request #406 from Scrumplex/fix-clear-metadata 2022-11-15 14:47:36 +01:00
Sefa Eyeoglu
0120d170b7
Merge pull request #397 from flowln/windows_mod_updater_fixes_maybe
Fixes https://github.com/PrismLauncher/PrismLauncher/issues/226
2022-11-15 14:47:36 +01:00
Sefa Eyeoglu
79617d0c50
Merge pull request #415 from TayouVR/fix-main-warning 2022-11-15 14:47:36 +01:00
txtsd
6e58635bfb
Merge pull request #374 from flowln/fix_lto 2022-11-15 14:47:36 +01:00
txtsd
3aee1d60d8
Merge pull request #379 from TheLastRar/win-stacksize 2022-11-15 14:47:36 +01:00
txtsd
a1fbf2b137
Merge pull request #346 from ryanccn/more-user-friendly-java-warning-text-ahaha 2022-11-15 14:47:36 +01:00
Sefa Eyeoglu
0b98a0884e
Merge pull request #368 from PrismLauncher/credits 2022-11-15 14:47:36 +01:00
Sefa Eyeoglu
1582bb57ed
Merge pull request #365 from Scrumplex/fix-logo 2022-11-15 14:47:36 +01:00
Sefa Eyeoglu
d8044ababe
Merge pull request #332 from Scrumplex/chore-bump-5.1 2022-11-01 11:01:49 +01:00
timoreo
32b526b729
Merge pull request #333 from flowln/fix_atl_packs_post_modpack_update 2022-11-01 11:01:15 +01:00
timoreo
7f6515dbe4
Merge pull request #329 from flowln/only_safe_workarounds 2022-11-01 11:01:15 +01:00
DioEgizio
a39390b8b4
Merge pull request #359 from Chrono-byte/develop 2022-11-01 09:48:13 +01:00
Sefa Eyeoglu
63a3dd1919
Merge pull request #351 from Scrumplex/fix-trash 2022-10-31 22:58:09 +01:00
DioEgizio
7a5a4de6ea
Merge pull request #354 from Scrumplex/translations-maybe
Improve display names of certain languages
2022-10-31 11:31:47 +01:00
DioEgizio
664d4e701e
Merge pull request #352 from TheLastRar/Win-setDarkWinTitlebar-10OrGreater 2022-10-31 11:31:47 +01:00
flow
a4ba8d8288
Merge pull request #353 from FayneAldan/accounts-consistency 2022-10-31 01:42:20 +01:00
txtsd
392bf7a97b
Merge pull request #342 from fn2006/prism-svg-fix 2022-10-29 23:11:37 +02:00
Sefa Eyeoglu
34687049b1
Merge pull request #338 from Scrumplex/fix-credits-1 2022-10-29 16:15:58 +02:00
Sefa Eyeoglu
9337ec6706
Merge pull request #173 from Scrumplex/fix-icons 2022-10-29 00:36:15 +02:00
Sefa Eyeoglu
5bcb6962c4
chore: bump version
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2022-10-28 21:54:10 +02:00
txtsd
0617b43190
Merge pull request #322 from DioEgizio/64bit-it 2022-10-28 21:34:59 +02:00
Sefa Eyeoglu
ed28234cfb
Merge pull request #319 from Scrumplex/fix-avoid-mr-segfault 2022-10-28 21:34:59 +02:00
DioEgizio
9c4455ca03
Merge pull request #301 from DioEgizio/clang-attempt
feat(actions): use clang32 for building on windows
2022-10-28 21:34:59 +02:00
Sefa Eyeoglu
9ec7837275
Merge pull request #318 from TheLastRar/manifest-platform
Fix: Don't specify x86 in manifest
2022-10-28 21:34:59 +02:00
Sefa Eyeoglu
549b5a6488
Merge pull request #231 from tobimori/patch-1 2022-10-28 21:34:59 +02:00
DioEgizio
2652f37453
Merge pull request #206 from flowln/changelog_height_fix 2022-10-28 21:34:59 +02:00
Sefa Eyeoglu
0eaff22145
Merge pull request #283 from flowln/fix_abort_on_autosearch 2022-10-28 21:34:59 +02:00
flow
2f5393b9d0
Merge pull request #281 from Scrumplex/fix-nsis-displayname 2022-10-28 21:34:59 +02:00
Sefa Eyeoglu
e28480a8e4
Merge pull request #274 from Protrikk/patch-1 2022-10-28 21:34:59 +02:00
flow
fcef6321fc
Merge pull request #228 from bensuperpc/change_cast 2022-10-28 21:34:59 +02:00
Sefa Eyeoglu
35e792c5de
Merge pull request #240 from jn64/fix/version-label-width 2022-10-28 21:34:59 +02:00
flow
c08b632b51
Merge pull request #234 from AliceDTRH/fix/dedupejava 2022-10-28 21:34:59 +02:00
flow
cac800bfd8
Merge pull request #233 from jamierocks/atl-fix-aborting-installs 2022-10-28 21:34:59 +02:00
Sefa Eyeoglu
2eb8173951
Merge pull request #224 from jamierocks/atl-abort-close-optional-mods-dialog 2022-10-28 21:34:59 +02:00
Sefa Eyeoglu
75abf2c124
Merge pull request #225 from Scrumplex/fix-segfault-fileresolver 2022-10-28 21:34:59 +02:00
flow
d40a18d6c5
Merge pull request #218 from getchoo/change-jars-path 2022-10-28 21:34:59 +02:00
Sefa Eyeoglu
a74fdc588c
Merge pull request #185 from flowln/fix_blocked_mods_crash 2022-10-28 21:34:59 +02:00
flow
25b0ec6eff
Merge pull request #147 from Minion3665/enhancement/update-nix-derivation 2022-10-28 21:34:59 +02:00
txtsd
04e8982d33
Merge pull request #198 from PrismLauncher/renovate/hendrikmuhs-ccache-action-1.x 2022-10-28 21:34:59 +02:00
txtsd
58bd449db8
Merge pull request #197 from PrismLauncher/renovate/actions-cache-3.x 2022-10-28 21:34:59 +02:00
Sefa Eyeoglu
c984e9b5d6
Merge pull request #202 from Heath123/patch-1 2022-10-28 21:34:59 +02:00
Sefa Eyeoglu
ddd319369a
Merge pull request #39 from Sebbl0508/mod_dialog_fontsize 2022-10-28 21:34:59 +02:00
Sefa Eyeoglu
de3c336213
Merge pull request #184 from Chrono-byte/develop 2022-10-28 21:34:59 +02:00
Sefa Eyeoglu
6e94e9bff1
Merge pull request #148 from ZombieNub/prismlauncher-rename 2022-10-28 21:34:59 +02:00
flow
93b8d9e454
Merge pull request #123 from MMK21Hub/patch-1 2022-10-28 21:34:59 +02:00
flow
6d46081864
Merge pull request #100 from TheEvilSkeleton/improve-approachability 2022-10-28 21:34:59 +02:00
1336 changed files with 40972 additions and 84471 deletions

View File

@ -1,12 +1,10 @@
---
BasedOnStyle: Chromium
IndentWidth: 4
AllowShortIfStatementsOnASingleLine: false
ColumnLimit: 140
---
Language: Cpp
AlignConsecutiveMacros: None
AlignConsecutiveAssignments: None
BasedOnStyle: Chromium
IndentWidth: 4
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AllowShortIfStatementsOnASingleLine: false
BraceWrapping:
AfterFunction: true
SplitEmptyFunction: false
@ -14,4 +12,5 @@ BraceWrapping:
SplitEmptyNamespace: false
BreakBeforeBraces: Custom
BreakConstructorInitializers: BeforeComma
ColumnLimit: 140
Cpp11BracedListStyle: false

View File

@ -1,4 +0,0 @@
Checks:
- modernize-use-using
SystemHeaders: false

View File

@ -1,8 +0,0 @@
# EditorConfig specs and documentation: https://EditorConfig.org
# top-most EditorConfig file
root = true
# C++ Code Style settings
[*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}]
cpp_generate_documentation_comments = doxygen_slash_star

2
.envrc
View File

@ -1,2 +0,0 @@
use flake
watch_file nix/*.nix

View File

@ -1,4 +0,0 @@
# .git-blame-ignore-revs
# tabs -> spaces
bbb3b3e6f6e3c0f95873f22e6d0a4aaf350f49d9

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,15 +25,15 @@ 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
label: Version of PolyMC
description: The version of PolyMC used in the bug report.
placeholder: PolyMC 1.4.1
validations:
required: true
- type: textarea
attributes:
label: Version of Qt
description: The version of Qt used in the bug report. You can find it in Help -> About Prism Launcher -> About Qt.
description: The version of Qt used in the bug report. You can find it in Help -> About PolyMC -> About Qt.
placeholder: Qt 6.3.0
validations:
required: true

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,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

@ -1,32 +0,0 @@
name: Backport
on:
pull_request_target:
types: [closed, labeled]
# WARNING:
# When extending this action, be aware that $GITHUB_TOKEN allows write access to
# the GitHub repository. This means that it should not evaluate user input in a
# way that allows code injection.
permissions:
contents: read
jobs:
backport:
permissions:
contents: write # for korthout/backport-action to create branch
pull-requests: write # for korthout/backport-action to create PR to backport
name: Backport Pull Request
if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs
uses: korthout/backport-action@v2.1.0
with:
# Config README: https://github.com/korthout/backport-action#backport-action
pull_description: |-
Bot-based backport to `${target_branch}`, triggered by a label in #${pull_number}.

View File

@ -7,29 +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
WINDOWS_CODESIGN_CERT:
description: Certificate for signing Windows builds
required: false
WINDOWS_CODESIGN_PASSWORD:
description: Password for signing Windows builds
required: false
CACHIX_AUTH_TOKEN:
description: Private token for authenticating against Cachix cache
required: false
GPG_PRIVATE_KEY:
description: Private key for AppImage signing
required: false
GPG_PRIVATE_KEY_ID:
description: ID for the GPG_PRIVATE_KEY, to select the signing key
required: false
jobs:
build:
@ -37,64 +18,41 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-20.04
qt_ver: 5
- os: ubuntu-20.04
qt_ver: 6
qt_host: linux
qt_arch: ""
qt_version: "6.2.4"
qt_modules: "qt5compat qtimageformats"
qt_tools: ""
qt_version: '6.2.4'
qt_modules: 'qt5compat qtimageformats'
- os: windows-2022
name: "Windows-MinGW-w64"
name: "Windows-Legacy"
msystem: clang32
qt_ver: 5
- os: windows-2022
name: "Windows"
msystem: clang64
vcvars_arch: "amd64_x86"
- os: windows-2022
name: "Windows-MSVC"
msystem: ""
architecture: "x64"
vcvars_arch: "amd64"
qt_ver: 6
qt_host: windows
qt_arch: ''
qt_version: '6.6.0'
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.6.0'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
- os: macos-12
name: macOS
macosx_deployment_target: 11.0
macosx_deployment_target: 10.15
qt_ver: 6
qt_host: mac
qt_arch: ''
qt_version: '6.6.0'
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_version: '5.15.2'
qt_modules: ''
runs-on: ${{ matrix.os }}
@ -105,19 +63,26 @@ jobs:
INSTALL_APPIMAGE_DIR: "install-appdir"
BUILD_DIR: "build"
CCACHE_VAR: ""
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
steps:
##
# PREPARE
##
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
submodules: "true"
submodules: 'true'
- name: "Setup MSYS2"
if: runner.os == 'Windows' && matrix.msystem != ''
- name: Initialize CodeQL
if: runner.os == 'Linux' && matrix.qt_ver == 6
uses: github/codeql-action/init@v2
with:
config-file: ./.github/codeql/codeql-config.yml
queries: security-and-quality
languages: cpp, java
- name: 'Setup MSYS2'
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.msystem }}
@ -130,36 +95,21 @@ jobs:
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
cmark:p
- name: Force newer ccache
if: runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug'
run: |
choco install ccache --version 4.7.1
${{ 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.10
if: runner.os != 'Windows' && inputs.build_type == 'Debug'
uses: hendrikmuhs/ccache-action@v1.2.3
with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
- name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
uses: actions/cache@v3.3.2
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
restore-keys: |
${{ matrix.os }}-mingw-w64-ccache
- 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'
@ -174,6 +124,15 @@ jobs:
run: |
echo "CCACHE_VAR=ccache" >> $GITHUB_ENV
- name: Retrieve ccache cache (Windows)
if: runner.os == 'Windows' && inputs.build_type == 'Debug'
uses: actions/cache@v3.0.11
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
restore-keys: |
${{ matrix.os }}-qt${{ matrix.qt_ver }}
- name: Set short version
shell: bash
run: |
@ -184,7 +143,7 @@ jobs:
if: runner.os == 'Linux'
run: |
sudo apt-get -y update
sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream
sudo apt-get -y install ninja-build extra-cmake-modules scdoc
- name: Install Dependencies (macOS)
if: runner.os == 'macOS'
@ -197,43 +156,16 @@ jobs:
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'
- name: Install Qt (macOS and AppImage)
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS'
uses: jurplel/install-qt-action@v3
with:
aqtversion: "==3.1.*"
py7zrversion: ">=0.20.2"
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
- 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
with:
aqtversion: "==3.1.*"
py7zrversion: ">=0.20.2"
version: ${{ matrix.qt_version }}
host: ${{ matrix.qt_host }}
target: "desktop"
arch: ${{ matrix.qt_arch }}
target: 'desktop'
modules: ${{ matrix.qt_modules }}
tools: ${{ matrix.qt_tools }}
cache: ${{ inputs.is_qt_cached }}
- name: Install MSVC (Windows MSVC)
if: runner.os == 'Windows' # We want this for MinGW builds as well, as we need SignTool
uses: ilammy/msvc-dev-cmd@v1
with:
vsversion: 2022
arch: ${{ matrix.vcvars_arch }}
cache: true
cache-key-prefix: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache
- name: Prepare AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
@ -242,15 +174,7 @@ jobs:
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage"
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage"
${{ github.workspace }}/.github/scripts/prepare_JREs.sh
sudo apt install libopengl0
- 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
@ -259,38 +183,23 @@ jobs:
- name: Configure CMake (macOS)
if: runner.os == 'macOS' && matrix.qt_ver == 6
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=official -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 -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja
- name: Configure CMake (macOS-Legacy)
if: runner.os == 'macOS' && matrix.qt_ver == 5
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -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=official -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 -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -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=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }}
# 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 }} -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -G Ninja
- name: Configure CMake (Linux)
if: runner.os == 'Linux'
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -G Ninja
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=Linux -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja
##
# BUILD
@ -301,17 +210,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
##
@ -319,18 +223,21 @@ 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
ctest --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 }}
##
# CODE SCAN
##
- name: Perform CodeQL Analysis
if: runner.os == 'Linux' && matrix.qt_ver == 6
uses: github/codeql-action/analyze@v2
##
# PACKAGE BUILDS
@ -366,61 +273,23 @@ 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 }}
touch ${{ env.INSTALL_DIR }}/manifest.txt
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt
- 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 ${{ runner.workspace }}/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll
Copy-Item ${{ runner.workspace }}/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll
}
cd ${{ github.workspace }}
if [ "${{ matrix.qt_ver }}" == "5" ]; then
cp /clang32/bin/libcrypto-1_1.dll /clang32/bin/libssl-1_1.dll ./
fi
Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
- name: Fetch codesign certificate (Windows)
- name: Package (Windows, portable)
if: runner.os == 'Windows'
shell: bash # yes, we are not using MSYS2 or PowerShell here
run: |
echo '${{ secrets.WINDOWS_CODESIGN_CERT }}' | base64 --decode > codesign.pfx
- name: Sign executable (Windows)
if: runner.os == 'Windows'
run: |
if (Get-Content ./codesign.pfx){
cd ${{ env.INSTALL_DIR }}
# We ship the exact same executable for portable and non-portable editions, so signing just once is fine
SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_updater.exe prismlauncher_filelink.exe
} else {
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
}
- name: Package (Windows MinGW-w64, portable)
if: runner.os == 'Windows' && matrix.msystem != ''
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
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
- 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
Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
- name: Package (Windows, installer)
if: runner.os == 'Windows'
@ -428,20 +297,10 @@ jobs:
cd ${{ env.INSTALL_DIR }}
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
- name: Sign installer (Windows)
if: runner.os == 'Windows'
run: |
if (Get-Content ./codesign.pfx){
SignTool sign /fd sha256 /td sha256 /f codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com PrismLauncher-Setup.exe
} else {
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
}
- name: Package (Linux)
if: runner.os == 'Linux'
run: |
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_DIR }}/manifest.txt
cd ${{ env.INSTALL_DIR }}
tar --owner root --group root -czf ../PrismLauncher.tar.gz *
@ -451,8 +310,6 @@ jobs:
run: |
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }}
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
cd ${{ env.INSTALL_PORTABLE_DIR }}
tar -czf ../PrismLauncher-portable.tar.gz *
@ -460,15 +317,10 @@ jobs:
- name: Package AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
shell: bash
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
run: |
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml
export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated
export OUTPUT="PrismLauncher-Linux-x86_64.AppImage"
export OUTPUT="PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
chmod +x linuxdeploy-*.AppImage
@ -479,11 +331,10 @@ jobs:
cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk
cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
cp -r /home/runner/work/PrismLauncher/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}//usr/lib/
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server"
@ -492,25 +343,8 @@ jobs:
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib"
export LD_LIBRARY_PATH
chmod +x AppImageUpdate-x86_64.AppImage
cp AppImageUpdate-x86_64.AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync"
if [ '${{ secrets.GPG_PRIVATE_KEY_ID }}' != '' ]; then
export SIGN=1
export SIGN_KEY=${{ secrets.GPG_PRIVATE_KEY_ID }}
mkdir -p ~/.gnupg/
echo "$GPG_PRIVATE_KEY" > ~/.gnupg/private.key
gpg --import ~/.gnupg/private.key
else
echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY
fi
./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
mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
##
# UPLOAD BUILDS
##
@ -547,14 +381,14 @@ jobs:
if: runner.os == 'Linux' && matrix.qt_ver != 6
uses: actions/upload-artifact@v3
with:
name: PrismLauncher-${{ runner.os }}-Qt5-${{ env.VERSION }}-${{ inputs.build_type }}
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher.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 }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
name: PrismLauncher-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-portable.tar.gz
- name: Upload binary tarball (Linux, Qt 6)
@ -578,33 +412,4 @@ jobs:
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
- name: Upload AppImage Zsync (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.zsync
path: PrismLauncher-Linux-x86_64.AppImage.zsync
- name: ccache stats (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0}
run: |
ccache -s
flatpak:
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:kde-5.15-22.08
options: --privileged
steps:
- name: Checkout
uses: actions/checkout@v4
if: inputs.build_type == 'Debug'
with:
submodules: "true"
- name: Build Flatpak (Linux)
if: inputs.build_type == 'Debug'
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: "Prism Launcher.flatpak"
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml

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@v4
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,35 +3,32 @@ name: Build Application
on:
push:
branches-ignore:
- "renovate/**"
- 'stable'
paths-ignore:
- "**.md"
- "**/LICENSE"
- "flake.lock"
- "packages/**"
- ".github/ISSUE_TEMPLATE/**"
- ".markdownlint**"
- '**.md'
- '**/LICENSE'
- 'flake.lock'
- '**.nix'
- 'packages/**'
- '.github/ISSUE_TEMPLATE/**'
- '.markdownlint**'
pull_request:
paths-ignore:
- "**.md"
- "**/LICENSE"
- "flake.lock"
- "packages/**"
- ".github/ISSUE_TEMPLATE/**"
- ".markdownlint**"
- '**.md'
- '**/LICENSE'
- 'flake.lock'
- '**.nix'
- 'packages/**'
- '.github/ISSUE_TEMPLATE/**'
- '.markdownlint**'
workflow_dispatch:
jobs:
build_debug:
name: Build Debug
uses: ./.github/workflows/build.yml
with:
build_type: Debug
is_qt_cached: true
secrets:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}

View File

@ -3,22 +3,17 @@ name: Build Application and Make Release
on:
push:
tags:
- "*"
- '*'
jobs:
build_release:
name: Build Release
uses: ./.github/workflows/build.yml
with:
build_type: Release
is_qt_cached: false
secrets:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
create_release:
needs: build_release
@ -27,10 +22,10 @@ jobs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
submodules: "true"
path: "PrismLauncher-source"
submodules: 'true'
path: 'PrismLauncher-source'
- name: Download artifacts
uses: actions/download-artifact@v3
- name: Grab and store version
@ -42,35 +37,21 @@ jobs:
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-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
mv PrismLauncher-Linux-Qt5*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
mv PrismLauncher-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
tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
tar -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
for d in PrismLauncher-Windows-MSVC*; do
for d in PrismLauncher-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="PrismLauncher-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 -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
@ -80,28 +61,25 @@ jobs:
- name: Create release
id: create_release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ github.ref }}
name: Prism Launcher ${{ env.VERSION }}
draft: true
prerelease: false
files: |
PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-x86_64.AppImage
PrismLauncher-Linux-x86_64.AppImage.zsync
PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage
PrismLauncher-Windows-Legacy-${{ env.VERSION }}.zip
PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MinGW-w64-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-Windows-Legacy-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-Legacy-Setup-${{ env.VERSION }}.exe
PrismLauncher-Windows-${{ env.VERSION }}.zip
PrismLauncher-Windows-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-Setup-${{ env.VERSION }}.exe
PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
PrismLauncher-${{ env.VERSION }}.tar.gz

View File

@ -1,30 +0,0 @@
name: Update Flake Lockfile
on:
schedule:
# run weekly on sunday
- cron: "0 0 * * 0"
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
update-flake:
if: github.repository == 'PrismLauncher/PrismLauncher'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23
- uses: DeterminateSystems/update-flake-lock@v20
with:
commit-msg: "chore(nix): update lockfile"
pr-title: "chore(nix): update lockfile"
pr-labels: |
Linux
packaging
simple change
changelog:omit

View File

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

11
.gitignore vendored
View File

@ -11,14 +11,10 @@ html/
*.pro.user
CMakeLists.txt.user
CMakeLists.txt.user.*
CMakeSettings.json
/CMakeFiles
CMakeCache.txt
/.project
/.settings
/.idea
/.vscode
/.vs
cmake-build-*/
Debug
@ -46,13 +42,8 @@ run/
.cache/
# Nix/NixOS
.direnv/
.pre-commit-config.yaml
result
result/
# Flatpak
.flatpak-builder
flatbuild
# Snap
*.snap

12
.gitmodules vendored
View File

@ -10,15 +10,3 @@
[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
[submodule "libraries/cmark"]
path = libraries/cmark
url = https://github.com/commonmark/cmark.git
[submodule "flatpak/shared-modules"]
path = flatpak/shared-modules
url = https://github.com/flathub/shared-modules.git

View File

@ -1,3 +1,53 @@
# Build Instructions
Full build instructions are available on [the website](https://prismlauncher.org/wiki/development/build-instructions/).
Full build instructions will be available on [the website](https://prismlauncher.org/wiki/development/build-instructions/).
If you would like to contribute or fix an issue with the Build instructions you will be able to do so [here](https://github.com/PrismLauncher/website/blob/master/src/wiki/development/build-instructions.md).
## Getting the source
Clone the source code using git, and grab all the submodules. This is generic for all platforms you want to build on.
```
git clone --recursive https://github.com/PrismLauncher/PrismLauncher
cd PrismLauncher
```
## Linux
This guide will mostly mention dependant packages by their Debian naming and commands are done by a user in the sudoers file.
### Dependencies
- A C++ compiler capable of building C++17 code (can be found in the package `build-essential`).
- Qt Development tools 5.12 or newer (on Debian 11 or Debian-based distributions, `qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5`).
- `cmake` 3.15 or newer.
- `extra-cmake-modules`.
- zlib (`zlib1g-dev` on Debian 11 or Debian-based distributions).
- Java Development Kit (Java JDK) (`openjdk-17-jdk` on Debian 11 or Debian-based distributions).
- Mesa GL headers (`libgl1-mesa-dev` on Debian 11 or Debian-based distributions).
- (Optional) `scdoc` to generate man pages.
In conclusion, to check if all you need is installed (including optional):
```
sudo apt install build-essential qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 cmake extra-cmake-modules zlib1g-dev openjdk-17-jdk libgl1-mesa-dev scdoc
```
### Compiling
#### Building and installing on the system
This is usually the suggested way to build the client.
```
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="/usr" -DENABLE_LTO=ON
cmake --build build -j$(nproc)
sudo cmake --install build
```
#### Building a portable binary
```
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=install
cmake --build build -j$(nproc)
cmake --install build
cmake --install build --component portable
```

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,58 +32,11 @@ 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}")
set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
# /EHs Enables stack unwind semantics for standard C++ exceptions to ensure stackframes are unwound
# and object deconstructors are called when an exception is caught.
# without it memory leaks and a warning is printed
# /EHc tells the compiler to assume that functions declared as extern "C" never throw a C++ exception
# This appears to not always be a defualt compiler option in CMAKE
set(CMAKE_CXX_FLAGS "/EHsc ${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)
# ATL's packlist needs more than the default 1 Mib stack on windows
if(WIN32)
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}")
endif()
endif()
# Fix build with Qt 5.13
@ -86,63 +44,20 @@ 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}")
option(DEBUG_ADDRESS_SANITIZER "Enable Address Sanitizer in Debug builds" OFF)
# If this is a Debug build turn on address sanitiser
if ((CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") AND DEBUG_ADDRESS_SANITIZER)
message(STATUS "Address Sanitizer enabled for Debug builds, Turn it off with -DDEBUG_ADDRESS_SANITIZER=off")
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
# using clang with clang-cl front end
message(STATUS "Address Sanitizer available on Clang MSVC frontend")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /Oy-")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /Oy-")
else()
# AppleClang and Clang
message(STATUS "Address Sanitizer available on Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
endif()
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
# GCC
message(STATUS "Address Sanitizer available on GCC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
link_libraries("asan")
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
message(STATUS "Address Sanitizer available on MSVC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /Oy-")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /Oy-")
else()
message(STATUS "Address Sanitizer not available on compiler ${CMAKE_CXX_COMPILER_ID}")
endif()
endif()
option(ENABLE_LTO "Enable Link Time Optimization" off)
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")
if(ipo_supported AND (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel"))
message(STATUS "IPO / LTO enabled")
else()
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
elseif(ipo_supported)
message(STATUS "Not enabling IPO / LTO on debug builds")
endif()
else()
message(STATUS "IPO / LTO will only be enabled for release builds")
endif()
else()
message(STATUS "IPO / LTO not supported: <${ipo_error}>")
endif()
@ -150,20 +65,8 @@ 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)
@ -178,21 +81,18 @@ set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL th
set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 8)
set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_MAJOR 5)
set(Launcher_VERSION_MINOR 2)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0")
set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},0,0")
# Build platform.
set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
set(Launcher_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
# Github repo URL with releases for updater
set(Launcher_UPDATER_GITHUB_REPO "https://github.com/PrismLauncher/PrismLauncher" CACHE STRING "Base github URL for the updater.")
# Name to help updater identify valid artifacts
set(Launcher_BUILD_ARTIFACT "" CACHE STRING "Artifact name to help the updater identify valid artifacts.")
# Channel list URL
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.")
@ -207,29 +107,17 @@ set(Launcher_BUG_TRACKER_URL "https://github.com/PrismLauncher/PrismLauncher/iss
set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/prismlauncher/launcher/" CACHE STRING "URL for the translations platform.")
# Matrix Space
set(Launcher_MATRIX_URL "https://prismlauncher.org/matrix" CACHE STRING "URL to the Matrix Space")
set(Launcher_MATRIX_URL "https://matrix.to/#/#prismlauncher:matrix.org" CACHE STRING "URL to the Matrix Space")
# Discord URL
set(Launcher_DISCORD_URL "https://prismlauncher.org/discord" CACHE STRING "URL for the Discord guild.")
set(Launcher_DISCORD_URL "https://discord.gg/prismlauncher" CACHE STRING "URL for the Discord guild.")
# Subreddit URL
set(Launcher_SUBREDDIT_URL "https://prismlauncher.org/reddit" CACHE STRING "URL for the subreddit.")
set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PrismLauncher/" 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")
set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against")
# Native libraries
if(UNIX AND APPLE)
set(Launcher_GLFW_LIBRARY_NAME "libglfw.dylib" CACHE STRING "Name of native glfw library")
set(Launcher_OPENAL_LIBRARY_NAME "libopenal.dylib" CACHE STRING "Name of native openal library")
elseif(UNIX)
set(Launcher_GLFW_LIBRARY_NAME "libglfw.so" CACHE STRING "Name of native glfw library")
set(Launcher_OPENAL_LIBRARY_NAME "libopenal.so" CACHE STRING "Name of native openal library")
elseif(WIN32)
set(Launcher_GLFW_LIBRARY_NAME "glfw.dll" CACHE STRING "Name of native glfw library")
set(Launcher_OPENAL_LIBRARY_NAME "OpenAL.dll" CACHE STRING "Name of native openal library")
endif()
set(Launcher_QT_VERSION_MAJOR "5" CACHE STRING "Major Qt version to build against")
# API Keys
# NOTE: These API keys are here for convenience. If you rebrand this software or intend to break the terms of service
@ -248,11 +136,6 @@ set(Launcher_MSA_CLIENT_ID "c36a9fb6-4f2a-41ff-90bd-ae7cc92031eb" CACHE STRING "
# 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_COMPILER_NAME ${CMAKE_CXX_COMPILER_ID})
set(Launcher_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION})
set(Launcher_COMPILER_TARGET_SYSTEM ${CMAKE_SYSTEM_NAME})
set(Launcher_COMPILER_TARGET_SYSTEM_VERSION ${CMAKE_SYSTEM_VERSION})
set(Launcher_COMPILER_TARGET_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR})
#### Check the current Git commit and branch
include(GetGitRevisionDescription)
@ -268,16 +151,6 @@ set(Launcher_BUILD_TIMESTAMP "${TODAY}")
################################ 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)
@ -296,7 +169,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)
@ -310,16 +183,12 @@ 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)
@ -332,13 +201,8 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS)
# Find ghc_filesystem
find_package(ghc_filesystem QUIET)
# Find cmark
find_package(cmark QUIET)
endif()
include(ECMQtDeclareLoggingCategory)
####################################### Program Info #######################################
set(Launcher_APP_BINARY_NAME "prismlauncher" CACHE STRING "Name of the Launcher binary")
@ -346,13 +210,6 @@ add_subdirectory(program_info)
####################################### Install layout #######################################
set(Launcher_ENABLE_UPDATER NO)
set(Launcher_BUILD_UPDATER NO)
if (NOT APPLE AND (NOT Launcher_UPDATER_GITHUB_REPO STREQUAL "" AND NOT Launcher_BUILD_ARTIFACT STREQUAL ""))
set(Launcher_BUILD_UPDATER YES)
endif()
if(NOT (UNIX AND APPLE))
# Install "portable.txt" if selected component is "portable"
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_Portable_File}" DESTINATION "." COMPONENT portable EXCLUDE_FROM_ALL)
@ -377,9 +234,9 @@ if(UNIX AND APPLE)
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
set(MACOSX_BUNDLE_COPYRIGHT "© 2022-2023 ${Launcher_Copyright_Mac}")
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed")
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed")
set(MACOSX_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_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")
@ -388,38 +245,33 @@ if(UNIX AND APPLE)
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ${MACOSX_SPARKLE_DIR})
if(NOT MACOSX_SPARKLE_UPDATE_PUBLIC_KEY STREQUAL "" AND NOT MACOSX_SPARKLE_UPDATE_FEED_URL STREQUAL "")
set(Launcher_ENABLE_UPDATER YES)
endif()
# install as bundle
set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies")
set(INSTALL_BUNDLE "full")
# Add the icon
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_Name}")
set(JARS_DEST_DIR "share/${Launcher_APP_BINARY_NAME}")
set(LAUNCHER_DESKTOP_DEST_DIR "share/applications" CACHE STRING "Path to the desktop file directory")
set(LAUNCHER_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" CACHE STRING "Use fixup_bundle to bundle dependencies")
set(INSTALL_BUNDLE "nodeps")
# 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_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "share/${Launcher_Name}")
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
@ -440,13 +292,11 @@ elseif(WIN32)
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
# install as bundle
set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies")
set(INSTALL_BUNDLE "full")
else()
message(FATAL_ERROR "Platform not supported")
endif()
################################ Included Libs ################################
include(ExternalProject)
@ -458,34 +308,9 @@ option(NBT_BUILD_TESTS "Build NBT library tests" OFF) #FIXME: fix unit tests.
add_subdirectory(libraries/libnbtplusplus)
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()
if (FORCE_BUNDLED_QUAZIP)
message(STATUS "Using bundled QuaZip")
set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts.
@ -502,26 +327,17 @@ if(NOT tomlplusplus_FOUND)
else()
message(STATUS "Using system tomlplusplus")
endif()
if(NOT cmark_FOUND)
message(STATUS "Using bundled cmark")
set(CMARK_STATIC ON CACHE BOOL "Build static libcmark library" FORCE)
set(CMARK_SHARED OFF CACHE BOOL "Build shared libcmark library" FORCE)
set(CMARK_TESTS OFF CACHE BOOL "Build cmark tests and enable testing" FORCE)
add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser
add_library(cmark::cmark ALIAS cmark_static)
else()
message(STATUS "Using system cmark")
endif()
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()
add_subdirectory(libraries/qdcss) # css parser
############################### Built Artifacts ###############################

View File

@ -19,7 +19,7 @@ In an effort to ensure that the code you contribute is actually compatible with
This can be done by appending `-s` to your `git commit` call, or by manually appending the following text to your commit message:
```text
```
<commit message>
Signed-off-by: Author name <Author email>
@ -27,7 +27,7 @@ Signed-off-by: Author name <Author email>
By signing off your work, you agree to the terms below:
```text
```
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
@ -61,9 +61,3 @@ As a bonus, you can also [cryptographically sign your commits][gh-signing-commit
[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
## Backporting to Release Branches
We use [automated backports](https://github.com/PrismLauncher/PrismLauncher/blob/develop/.github/workflows/backport.yml) to merge specific contributions from develop into `release` branches.
This is done when pull requests are merged and have labels such as `backport release-7.x` - which should be added along with the milestone for the release.

View File

@ -1,7 +1,7 @@
## Prism Launcher
Prism Launcher - Minecraft Launcher
Copyright (C) 2022-2023 Prism Launcher Contributors
Copyright (C) 2022 Prism Launcher Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -156,34 +156,23 @@
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
## cmark
## Hoedown
Copyright (c) 2014, John MacFarlane
Copyright (c) 2008, Natacha Porté
Copyright (c) 2011, Vicent Martí
Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors
All rights reserved.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
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
@ -409,45 +398,3 @@
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/>.

107
README.md
View File

@ -1,112 +1,29 @@
<p align="center">
<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="40%">
</picture>
<img src="./program_info/org.prismlauncher.PrismLauncher.logo.svg#gh-light-mode-only" alt="Prism Launcher logo" width="50%"/>
<img src="./program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg#gh-dark-mode-only" alt="Prism Launcher logo" width="50%"/>
</p>
<p align="center">
Prism Launcher is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.<br />
<br />This is a <b>fork</b> of the MultiMC Launcher and is <b>not</b> endorsed by it.
</p>
Prism Launcher is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.
We are working on a website and other media, for more info we have a [Discord server](https://discord.gg/prismlauncher).
## Installation
<a href="https://repology.org/project/prismlauncher/versions">
<img src="https://repology.org/badge/vertical-allrepos/prismlauncher.svg" alt="Packaging status" align="right">
</a>
- 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) tab (this also includes the pull requests status).
- All downloads and instructions for Prism Launcher will soon be available.
- Last build status can be found [here](https://github.com/PrismLauncher/PrismLauncher/actions).
### Development Builds
Please understand that these builds are not intended for most users. There may be bugs, and other instabilities. You have been warned.
There are development builds available [here](https://github.com/PrismLauncher/PrismLauncher/actions). These have debug information in the binaries, so their file sizes are relatively larger.
There are development builds available through:
Portable builds are provided for AppImage on Linux, Windows, and macOS.
- [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions) (includes builds from pull requests opened by contribuitors)
- [nightly.link](https://nightly.link/PrismLauncher/PrismLauncher/workflows/trigger_builds/develop) (this will always point only to the latest version of develop)
## Help & Support
These have debug information in the binaries, so their file sizes are relatively larger.
Prebuilt Development builds are provided for **Linux**, **Windows** and **macOS**.
[![Join the Discord Server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner3)](https://discord.gg/hX4g537UNE)
For **Arch**, **Debian**, **Fedora**, **OpenSUSE (Tumbleweed)** and **Gentoo**, respectively, you can use these packages for the latest development versions:
[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-1793D1?label=AUR&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-git) [![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--qt5--git-1793D1?label=AUR&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-qt5-git) [![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-A80030?label=MPR&logo=debian&logoColor=white)](https://mpr.makedeb.org/packages/prismlauncher-git)<br />[![prismlauncher-nightly](https://img.shields.io/badge/copr-prismlauncher--nightly-51A2DA?label=COPR&logo=fedora&logoColor=white)](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [![prismlauncher-nightly](https://img.shields.io/badge/OBS-prismlauncher--nightly-3AB6A9?logo=opensuse&logoColor=white)](https://build.opensuse.org/project/show/home:getchoo) [![prismlauncher-9999](https://img.shields.io/badge/gentoo-prismlauncher--9999-4D4270?label=Gentoo&logo=gentoo&logoColor=white)](https://packages.gentoo.org/packages/games-action/prismlauncher)
These packages are also available to all the distributions based on the ones mentioned above.
## Community & Support
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:
- **Our Discord server:**
[![Prism Launcher Discord server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner3)](https://prismlauncher.org/discord)
- **Our Matrix space:**
[![Prism Launcher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge&label=Matrix%20Space&logo=matrix&color=purple)](https://prismlauncher.org/matrix)
- **Our Subreddit:**
[![r/PrismLauncher](https://img.shields.io/reddit/subreddit-subscribers/prismlauncher?style=for-the-badge&logo=reddit)](https://prismlauncher.org/reddit)
## Translations
The translation effort for Prism Launcher 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>.
## Building
If you want to build Prism Launcher yourself, check the [Build Instructions](https://prismlauncher.org/wiki/development/build-instructions/).
## Sponsors & Partners
We thank all the wonderful backers over at Open Collective! Support Prism Launcher by [becoming a backer](https://opencollective.com/prismlauncher).
[![OpenCollective Backers](https://opencollective.com/prismlauncher/backers.svg?width=890&limit=1000)](https://opencollective.com/prismlauncher#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/).
[![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!
<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>
## Forking/Redistributing/Custom builds policy
You are free to fork, redistribute and provide custom builds 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 Prism Launcher and is not endorsed by or affiliated with the Prism Launcher project (<https://prismlauncher.org>).
- Go through [CMakeLists.txt](CMakeLists.txt) and change Prism Launcher'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.
If you are just building Prism Launcher for your distribution, please make sure to set the `Launcher_BUILD_PLATFORM` to a slug representing your distribution. Examples are `archlinux`, `fedora` and `nixpkgs`.
Note that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions:
- [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use)
- [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions)
If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file by setting them to an empty string (`""`).
## License [![https://github.com/PrismLauncher/PrismLauncher/blob/develop/LICENSE](https://img.shields.io/github/license/PrismLauncher/PrismLauncher?label=License&logo=gnu&color=C4282D)](LICENSE)
## License
All launcher code is available under the GPL-3.0-only license.
The logo and related assets are under the CC BY-SA 4.0 license.

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -33,7 +33,6 @@
* limitations under the License.
*/
#include <qstringliteral.h>
#include "BuildConfig.h"
#include <QObject>
@ -60,25 +59,15 @@ Config::Config()
VERSION_MINOR = @Launcher_VERSION_MINOR@;
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
BUILD_ARTIFACT = "@Launcher_BUILD_ARTIFACT@";
BUILD_DATE = "@Launcher_BUILD_TIMESTAMP@";
UPDATER_GITHUB_REPO = "@Launcher_UPDATER_GITHUB_REPO@";
COMPILER_NAME = "@Launcher_COMPILER_NAME@";
COMPILER_VERSION = "@Launcher_COMPILER_VERSION@";
COMPILER_TARGET_SYSTEM = "@Launcher_COMPILER_TARGET_SYSTEM@";
COMPILER_TARGET_SYSTEM_VERSION = "@Launcher_COMPILER_TARGET_SYSTEM_VERSION@";
COMPILER_TARGET_SYSTEM_PROCESSOR = "@Launcher_COMPILER_TARGET_PROCESSOR@";
UPDATER_BASE = "@Launcher_UPDATER_BASE@";
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@";
if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty())
if (BUILD_PLATFORM == "macOS" && !MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty())
{
UPDATER_ENABLED = true;
} else if(!UPDATER_GITHUB_REPO.isEmpty() && !BUILD_ARTIFACT.isEmpty()) {
UPDATER_ENABLED = true;
}
GIT_COMMIT = "@Launcher_GIT_COMMIT@";
@ -87,19 +76,19 @@ 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();
GIT_COMMIT = "";
}
if (GIT_REFSPEC.startsWith("refs/heads/"))
{
VERSION_CHANNEL = GIT_REFSPEC;
VERSION_CHANNEL.remove("refs/heads/");
if(!UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty()) {
UPDATER_ENABLED = true;
}
}
else if (!GIT_COMMIT.isEmpty())
{
@ -118,9 +107,6 @@ Config::Config()
FLAME_API_KEY = "@Launcher_CURSEFORGE_API_KEY@";
META_URL = "@Launcher_META_URL@";
GLFW_LIBRARY_NAME = "@Launcher_GLFW_LIBRARY_NAME@";
OPENAL_LIBRARY_NAME = "@Launcher_OPENAL_LIBRARY_NAME@";
BUG_TRACKER_URL = "@Launcher_BUG_TRACKER_URL@";
TRANSLATIONS_URL = "@Launcher_TRANSLATIONS_URL@";
MATRIX_URL = "@Launcher_MATRIX_URL@";
@ -144,16 +130,3 @@ QString Config::printableVersionString() const
}
return vstr;
}
QString Config::compilerID() const
{
if (COMPILER_VERSION.isEmpty())
return COMPILER_NAME;
return QStringLiteral("%1 - %2").arg(COMPILER_NAME).arg(COMPILER_VERSION);
}
QString Config::systemID() const
{
return QStringLiteral("%1 %2 %3").arg(COMPILER_TARGET_SYSTEM, COMPILER_TARGET_SYSTEM_VERSION, COMPILER_TARGET_SYSTEM_PROCESSOR);
}

View File

@ -1,9 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -36,7 +35,6 @@
*/
#pragma once
#include <QList>
#include <QString>
/**
@ -68,32 +66,14 @@ class Config {
bool UPDATER_ENABLED = false;
/// A short string identifying this build's platform or distribution.
/// A short string identifying this build's platform. For example, "lin64" or "win32".
QString BUILD_PLATFORM;
/// A short string identifying this build's valid artifacts int he updater. For example, "lin64" or "win32".
QString BUILD_ARTIFACT;
/// A string containing the build timestamp
QString BUILD_DATE;
/// A string identifying the compiler use to build
QString COMPILER_NAME;
/// A string identifying the compiler version used to build
QString COMPILER_VERSION;
/// A string identifying the compiler target system os
QString COMPILER_TARGET_SYSTEM;
/// A String identifying the compiler target system version
QString COMPILER_TARGET_SYSTEM_VERSION;
/// A String identifying the compiler target processor
QString COMPILER_TARGET_SYSTEM_PROCESSOR;
/// URL for the updater's channel
QString UPDATER_GITHUB_REPO;
QString UPDATER_BASE;
/// The public key used to sign releases for the Sparkle updater appcast
QString MAC_SPARKLE_PUB_KEY;
@ -152,9 +132,6 @@ class Config {
*/
QString META_URL;
QString GLFW_LIBRARY_NAME;
QString OPENAL_LIBRARY_NAME;
QString BUG_TRACKER_URL;
QString TRANSLATIONS_URL;
QString MATRIX_URL;
@ -183,9 +160,6 @@ class Config {
QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2";
QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2";
QStringList MODRINTH_MRPACK_HOSTS{ "cdn.modrinth.com", "github.com", "raw.githubusercontent.com", "gitlab.com" };
QString FLAME_BASE_URL = "https://api.curseforge.com/v1";
QString versionString() const;
/**
@ -193,18 +167,6 @@ class Config {
* \return The version number in string format (major.minor.revision.build).
*/
QString printableVersionString() const;
/**
* \brief Compiler ID String
* \return a string of the form "Name - Version" of just "Name" if the version is empty
*/
QString compilerID() const;
/**
* \brief System ID String
* \return a string of the form "OS Verison Processor"
*/
QString systemID() const;
};
extern const Config BuildConfig;

View File

@ -1,155 +0,0 @@
#
# Function to set compiler warnings with reasonable defaults at the project level.
# Taken from https://github.com/aminya/project_options/blob/main/src/CompilerWarnings.cmake
# under the folowing license:
#
# MIT License
#
# Copyright (c) 2022-2100 Amin Yahyaabadi
#
# 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.
#
include_guard()
function(_set_project_warnings_add_target_link_option TARGET OPTIONS)
target_link_options(${_project_name} INTERFACE ${OPTIONS})
endfunction()
# Set the compiler warnings
#
# https://clang.llvm.org/docs/DiagnosticsReference.html
# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md
function(
set_project_warnings
_project_name
MSVC_WARNINGS
CLANG_WARNINGS
GCC_WARNINGS
)
if("${MSVC_WARNINGS}" STREQUAL "")
set(MSVC_WARNINGS
/W4 # Baseline reasonable warnings
/w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data
/w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
/w14263 # 'function': member function does not override any base class virtual member function
/w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not
# be destructed correctly
/w14287 # 'operator': unsigned/negative constant mismatch
/we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside
# the for-loop scope
/w14296 # 'operator': expression is always 'boolean_value'
/w14311 # 'variable': pointer truncation from 'type1' to 'type2'
/w14545 # expression before comma evaluates to a function which is missing an argument list
/w14546 # function call before comma missing argument list
/w14547 # 'operator': operator before comma has no effect; expected operator with side-effect
/w14549 # 'operator': operator before comma has no effect; did you intend 'operator'?
/w14555 # expression has no effect; expected expression with side- effect
/w14619 # pragma warning: there is no warning number 'number'
/w14640 # Enable warning on thread un-safe static member initialization
/w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior.
/w14905 # wide string literal cast to 'LPSTR'
/w14906 # string literal cast to 'LPWSTR'
/w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied
/permissive- # standards conformance mode for MSVC compiler.
)
endif()
if("${CLANG_WARNINGS}" STREQUAL "")
set(CLANG_WARNINGS
-Wall
-Wextra # reasonable and standard
-Wshadow # warn the user if a variable declaration shadows one from a parent context
-Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps
# catch hard to track down memory errors
-Wold-style-cast # warn for c-style casts
-Wcast-align # warn for potential performance problem casts
-Wunused # warn on anything being unused
-Woverloaded-virtual # warn if you overload (not override) a virtual function
-Wpedantic # warn if non-standard C++ is used
-Wconversion # warn on type conversions that may lose data
-Wsign-conversion # warn on sign conversions
-Wnull-dereference # warn if a null dereference is detected
-Wdouble-promotion # warn if float is implicit promoted to double
-Wformat=2 # warn on security issues around functions that format output (ie printf)
-Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation
# -Wgnu-zero-variadic-macro-arguments (part of -pedantic) is triggered by every qCDebug() call and therefore results
# in a lot of noise. This warning is only notifying us that clang is emulating the GCC behaviour
# instead of the exact standard wording so we can safely ignore it
-Wno-gnu-zero-variadic-macro-arguments
)
endif()
if("${GCC_WARNINGS}" STREQUAL "")
set(GCC_WARNINGS
${CLANG_WARNINGS}
-Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist
-Wduplicated-cond # warn if if / else chain has duplicated conditions
-Wduplicated-branches # warn if if / else branches have duplicated code
-Wlogical-op # warn about logical operations being used where bitwise were probably wanted
-Wuseless-cast # warn if you perform a cast to the same type
)
endif()
if(MSVC)
set(PROJECT_WARNINGS_CXX ${MSVC_WARNINGS})
elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
set(PROJECT_WARNINGS_CXX ${CLANG_WARNINGS})
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(PROJECT_WARNINGS_CXX ${GCC_WARNINGS})
else()
message(AUTHOR_WARNING "No compiler warnings set for CXX compiler: '${CMAKE_CXX_COMPILER_ID}'")
# TODO support Intel compiler
endif()
# Add C warnings
set(PROJECT_WARNINGS_C "${PROJECT_WARNINGS_CXX}")
list(
REMOVE_ITEM
PROJECT_WARNINGS_C
-Wnon-virtual-dtor
-Wold-style-cast
-Woverloaded-virtual
-Wuseless-cast
-Wextra-semi
)
target_compile_options(
${_project_name}
INTERFACE # C++ warnings
$<$<COMPILE_LANGUAGE:CXX>:${PROJECT_WARNINGS_CXX}>
# C warnings
$<$<COMPILE_LANGUAGE:C>:${PROJECT_WARNINGS_C}>
)
# If we are using the compiler as a linker driver pass the warnings to it
# (most useful when using LTO or warnings as errors)
if(CMAKE_CXX_LINK_EXECUTABLE MATCHES "^<CMAKE_CXX_COMPILER>")
_set_project_warnings_add_target_link_option(
${_project_name} "$<$<COMPILE_LANGUAGE:CXX>:${PROJECT_WARNINGS_CXX}>"
)
endif()
if(CMAKE_C_LINK_EXECUTABLE MATCHES "^<CMAKE_C_COMPILER>")
_set_project_warnings_add_target_link_option(
${_project_name} "$<$<COMPILE_LANGUAGE:C>:${PROJECT_WARNINGS_C}>"
)
endif()
endfunction()

View File

@ -44,39 +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>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Curseforge</string>
<key>CFBundleURLSchemes</key>
<array>
<string>curseforge</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@ -1,14 +1 @@
(
import
(
let
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
in
fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash;
}
)
{src = ./.;}
)
.defaultNix
(import nix/flake-compat.nix).defaultNix

View File

@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"lastModified": 1650374568,
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
"type": "github"
},
"original": {
@ -16,71 +16,14 @@
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1698882062,
"narHash": "sha256-HkhafUayIqxXyHH1X8d9RDl1M2CkFgZLjKD3MzabiEo=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "8c9fa2545007b49a5db5f650ae91f227672c3877",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1685518550,
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1660459072,
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"libnbtplusplus": {
"flake": false,
"locked": {
"lastModified": 1690036783,
"narHash": "sha256-A5kTgICnx+Qdq3Fir/bKTfdTt/T1NQP2SC+nhN1ENug=",
"lastModified": 1650031308,
"narHash": "sha256-TvVOjkUobYJD9itQYueELJX3wmecvEdCbJ0FinW2mL4=",
"owner": "PrismLauncher",
"repo": "libnbtplusplus",
"rev": "a5e8fd52b8bf4ab5d5bcc042b2a247867589985f",
"rev": "2203af7eeb48c45398139b583615134efd8d407f",
"type": "github"
},
"original": {
@ -89,28 +32,13 @@
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1694857738,
"narHash": "sha256-bxxNyLHjhu0N8T3REINXQ2ZkJco0ABFPn6PIe2QUfqo=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "41fd48e00c22b4ced525af521ead8792402de0ea",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "nix-filter",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1699094435,
"narHash": "sha256-YLZ5/KKZ1PyLrm2MO8UxRe4H3M0/oaYqNhSlq6FDeeA=",
"lastModified": 1666057921,
"narHash": "sha256-VpQqtXdj6G7cH//SvoprjR7XT3KS7p+tCVebGK1N6tE=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "9d5d25bbfe8c0297ebe85324addcb5020ed1a454",
"rev": "88eab1e431cabd0ed621428d8b40d425a07af39f",
"type": "github"
},
"original": {
@ -120,74 +48,27 @@
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"dir": "lib",
"lastModified": 1698611440,
"narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735",
"type": "github"
},
"original": {
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": [
"flake-compat"
],
"flake-utils": "flake-utils",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1698852633,
"narHash": "sha256-Hsc/cCHud8ZXLvmm8pxrXpuaPEeNaaUttaCvtdX/Wug=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "dec10399e5b56aa95fcd530e0338be72ad6462a0",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"libnbtplusplus": "libnbtplusplus",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks"
"tomlplusplus": "tomlplusplus"
}
},
"systems": {
"tomlplusplus": {
"flake": false,
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"lastModified": 1666091090,
"narHash": "sha256-djpMCFPvkJcfynV8WnsYdtwLq+J7jpV1iM4C6TojiyM=",
"owner": "marzer",
"repo": "tomlplusplus",
"rev": "1e4a3833d013aee08f58c5b31c69f709afc69f73",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"owner": "marzer",
"repo": "tomlplusplus",
"type": "github"
}
}

View File

@ -3,42 +3,36 @@
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
nix-filter.url = "github:numtide/nix-filter";
pre-commit-hooks = {
url = "github:cachix/pre-commit-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs";
inputs.nixpkgs-stable.follows = "nixpkgs";
inputs.flake-compat.follows = "flake-compat";
};
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
libnbtplusplus = {
url = "github:PrismLauncher/libnbtplusplus";
flake = false;
};
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
libnbtplusplus = { url = "github:PrismLauncher/libnbtplusplus"; flake = false; };
tomlplusplus = { url = "github:marzer/tomlplusplus"; flake = false; };
};
outputs = {
flake-parts,
pre-commit-hooks,
...
} @ inputs:
flake-parts.lib.mkFlake {inherit inputs;} {
imports = [
pre-commit-hooks.flakeModule
outputs = { self, nixpkgs, libnbtplusplus, tomlplusplus, ... }:
let
# User-friendly version number.
version = builtins.substring 0 8 self.lastModifiedDate;
./nix/dev.nix
./nix/distribution.nix
];
# Supported systems (qtbase is currently broken for "aarch64-darwin")
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" ];
systems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
# Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'.
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
# Nixpkgs instantiated for supported systems.
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
packagesFn = pkgs: rec {
prismlauncher = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
prismlauncher-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
};
in
{
packages = forAllSystems (system:
let packages = packagesFn pkgs.${system}; in
packages // { default = packages.prismlauncher; }
);
overlay = final: packagesFn;
};
}

View File

@ -1,22 +0,0 @@
{
"name": "libdecor",
"buildsystem": "meson",
"config-opts": [
"-Ddemo=false"
],
"sources": [
{
"type": "git",
"url": "https://gitlab.freedesktop.org/libdecor/libdecor.git",
"commit": "73260393a97291c887e1074ab7f318e031be0ac6"
},
{
"type": "patch",
"path": "patches/weird_libdecor.patch"
}
],
"cleanup": [
"/include",
"/lib/pkgconfig"
]
}

View File

@ -1,158 +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
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
# FTBApp import
- --filesystem=~/.ftba:ro
cleanup:
- /lib/libGLU*
modules:
# Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31)
- shared-modules/libusb/libusb.json
# Needed for proper Wayland support
- libdecor.json
- name: prismlauncher
buildsystem: cmake-ninja
builddir: true
config-opts:
- -DLauncher_BUILD_PLATFORM=flatpak
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
- -DLauncher_QT_VERSION_MAJOR=5
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: glfw
buildsystem: cmake-ninja
config-opts:
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
- -DBUILD_SHARED_LIBS:BOOL=ON
- -DGLFW_USE_WAYLAND=ON
sources:
- type: git
url: https://github.com/glfw/glfw.git
commit: 3fa2360720eeba1964df3c0ecf4b5df8648a8e52
- type: patch
path: patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch
- type: patch
path: patches/0005-Add-warning-about-being-an-unofficial-patch.patch
- type: patch
path: patches/0007-Platform-Prefer-Wayland-over-X11.patch
cleanup:
- /include
- /lib/cmake
- /lib/pkgconfig
- name: xrandr
buildsystem: autotools
sources:
- type: archive
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.2.tar.xz
sha256: c8bee4790d9058bacc4b6246456c58021db58a87ddda1a9d0139bf5f18f1f240
x-checker-data:
type: anitya
project-id: 14957
stable-only: true
url-template: https://xorg.freedesktop.org/archive/individual/app/xrandr-$version.tar.xz
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: archive
archive-type: tar-gzip
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.7
sha256: 57ce73ba605d1cf12f8d13725006a895182308d93eba0f69f285648449641803
x-checker-data:
type: json
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest
version-query: .tag_name
url-query: .tarball_url
timestamp-query: .published_at
cleanup:
- /include
- /lib/pkgconfig
- /lib/libgamemodeauto.a
- name: glxinfo
buildsystem: meson
config-opts:
- --bindir=/app/mesa-demos
- -Degl=disabled
- -Dglut=disabled
- -Dosmesa=disabled
- -Dvulkan=disabled
- -Dwayland=disabled
post-install:
- mv -v /app/mesa-demos/glxinfo /app/bin
sources:
- type: archive
url: https://archive.mesa3d.org/demos/mesa-demos-9.0.0.tar.xz
sha256: 3046a3d26a7b051af7ebdd257a5f23bfeb160cad6ed952329cdff1e9f1ed496b
x-checker-data:
type: anitya
project-id: 16781
stable-only: true
url-template: https://archive.mesa3d.org/demos/mesa-demos-$version.tar.xz
cleanup:
- /include
- /mesa-demos
- /share
modules:
- shared-modules/glu/glu-9.json
- name: enhance
buildsystem: simple
build-commands:
- 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: prime-run
- type: file
path: prismlauncher

View File

@ -1,24 +0,0 @@
diff --git a/src/wl_window.c b/src/wl_window.c
index 52d3b9eb..4ac4eb5d 100644
--- a/src/wl_window.c
+++ b/src/wl_window.c
@@ -2117,8 +2117,7 @@ void _glfwSetWindowTitleWayland(_GLFWwindow* window, const char* title)
void _glfwSetWindowIconWayland(_GLFWwindow* window,
int count, const GLFWimage* images)
{
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
- "Wayland: The platform does not support setting the window icon");
+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the window icon\n");
}
void _glfwGetWindowPosWayland(_GLFWwindow* window, int* xpos, int* ypos)
@@ -2361,8 +2360,7 @@ void _glfwRequestWindowAttentionWayland(_GLFWwindow* window)
void _glfwFocusWindowWayland(_GLFWwindow* window)
{
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
- "Wayland: The platform does not support setting the input focus");
+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the input focus\n");
}
void _glfwSetWindowMonitorWayland(_GLFWwindow* window,

View File

@ -1,17 +0,0 @@
diff --git a/src/init.c b/src/init.c
index 06dbb3f2..a7c6da86 100644
--- a/src/init.c
+++ b/src/init.c
@@ -449,6 +449,12 @@ GLFWAPI int glfwInit(void)
_glfw.initialized = GLFW_TRUE;
glfwDefaultWindowHints();
+
+ fprintf(stderr, "!!! Patched GLFW from https://github.com/Admicos/minecraft-wayland\n"
+ "!!! If any issues with the window, or some issues with rendering, occur, "
+ "first try with the built-in GLFW, and if that solves the issue, report there first.\n"
+ "!!! Use outside Minecraft is untested, and things might break.\n");
+
return GLFW_TRUE;
}

View File

@ -1,20 +0,0 @@
diff --git a/src/platform.c b/src/platform.c
index c5966ae7..3e7442f9 100644
--- a/src/platform.c
+++ b/src/platform.c
@@ -49,12 +49,12 @@ static const struct
#if defined(_GLFW_COCOA)
{ GLFW_PLATFORM_COCOA, _glfwConnectCocoa },
#endif
-#if defined(_GLFW_X11)
- { GLFW_PLATFORM_X11, _glfwConnectX11 },
-#endif
#if defined(_GLFW_WAYLAND)
{ GLFW_PLATFORM_WAYLAND, _glfwConnectWayland },
#endif
+#if defined(_GLFW_X11)
+ { GLFW_PLATFORM_X11, _glfwConnectX11 },
+#endif
};
GLFWbool _glfwSelectPlatform(int desiredID, _GLFWplatform* platform)

View File

@ -1,40 +0,0 @@
diff --git a/src/libdecor.c b/src/libdecor.c
index a9c1106..1aa38b3 100644
--- a/src/libdecor.c
+++ b/src/libdecor.c
@@ -1391,22 +1391,32 @@ calculate_priority(const struct libdecor_plugin_description *plugin_description)
static bool
check_symbol_conflicts(const struct libdecor_plugin_description *plugin_description)
{
+ bool ret = true;
char * const *symbol;
+ void* main_prog = dlopen(NULL, RTLD_LAZY);
+ if (!main_prog) {
+ fprintf(stderr, "Plugin \"%s\" couldn't check conflicting symbols: \"%s\".\n",
+ plugin_description->description, dlerror());
+ return false;
+ }
+
symbol = plugin_description->conflicting_symbols;
while (*symbol) {
dlerror();
- dlsym (RTLD_DEFAULT, *symbol);
+ dlsym (main_prog, *symbol);
if (!dlerror()) {
fprintf(stderr, "Plugin \"%s\" uses conflicting symbol \"%s\".\n",
plugin_description->description, *symbol);
- return false;
+ ret = false;
+ break;
}
symbol++;
}
- return true;
+ dlclose(main_prog);
+ return ret;
}
static struct plugin_loader *

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:+:}/usr/lib/extensions/vulkan/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin"
export VK_LAYER_PATH="/usr/lib/extensions/vulkan/share/vulkan/implicit_layer.d/"
exec /app/bin/prismrun "$@"

@ -1 +0,0 @@
Subproject commit 45094ca570be383d06df729b6972830ec63bd3df

View File

@ -1,6 +0,0 @@
builds:
exclude: []
include:
- "checks.x86_64-linux.*"
- "devShells.*.*"
- "packages.*.*"

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +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 <git@tayou.org>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -38,17 +36,17 @@
#pragma once
#include <QApplication>
#include <QDateTime>
#include <memory>
#include <QDebug>
#include <QFlag>
#include <QIcon>
#include <QDateTime>
#include <QUrl>
#include <memory>
#include <updater/GoUpdate.h>
#include <BaseInstance.h>
#include "minecraft/launch/MinecraftServerTarget.h"
#include "ui/themes/CatPack.h"
class LaunchController;
class LocalPeer;
@ -64,29 +62,33 @@ class AccountList;
class IconList;
class QNetworkAccessManager;
class JavaInstallList;
class ExternalUpdater;
class UpdateChecker;
class BaseProfilerFactory;
class BaseDetachedToolFactory;
class TranslationsModel;
class ITheme;
class MCEditTool;
class ThemeManager;
class IconTheme;
namespace Meta {
class Index;
class Index;
}
#if defined(APPLICATION)
#undef APPLICATION
#endif
#define APPLICATION (static_cast<Application*>(QCoreApplication::instance()))
#define APPLICATION (static_cast<Application *>(QCoreApplication::instance()))
class Application : public QApplication {
class Application : public QApplication
{
// friends for the purpose of limiting access to deprecated stuff
Q_OBJECT
public:
enum Status { StartingUp, Failed, Succeeded, Initialized };
public:
enum Status {
StartingUp,
Failed,
Succeeded,
Initialized
};
enum Capability {
None = 0,
@ -98,39 +100,59 @@ class Application : public QApplication {
};
Q_DECLARE_FLAGS(Capabilities, Capability)
public:
Application(int& argc, char** argv);
public:
Application(int &argc, char **argv);
virtual ~Application();
bool event(QEvent* event) override;
std::shared_ptr<SettingsObject> settings() const { return m_settings; }
std::shared_ptr<SettingsObject> settings() const {
return m_settings;
}
qint64 timeSinceStart() const { return startTime.msecsTo(QDateTime::currentDateTime()); }
qint64 timeSinceStart() const {
return startTime.msecsTo(QDateTime::currentDateTime());
}
QIcon getThemedIcon(const QString& name);
ThemeManager* themeManager() { return m_themeManager.get(); }
void setIconTheme(const QString& name);
shared_qobject_ptr<ExternalUpdater> updater() { return m_updater; }
std::vector<ITheme *> getValidApplicationThemes();
void triggerUpdateCheck();
void setApplicationTheme(const QString& name, bool initial);
shared_qobject_ptr<UpdateChecker> updateChecker() {
return m_updateChecker;
}
std::shared_ptr<TranslationsModel> translations();
std::shared_ptr<JavaInstallList> javalist();
std::shared_ptr<InstanceList> instances() const { return m_instances; }
std::shared_ptr<InstanceList> instances() const {
return m_instances;
}
std::shared_ptr<IconList> icons() const { return m_icons; }
std::shared_ptr<IconList> icons() const {
return m_icons;
}
MCEditTool* mcedit() const { return m_mcedit.get(); }
MCEditTool *mcedit() const {
return m_mcedit.get();
}
shared_qobject_ptr<AccountList> accounts() const { return m_accounts; }
shared_qobject_ptr<AccountList> accounts() const {
return m_accounts;
}
Status status() const { return m_status; }
Status status() const {
return m_status;
}
const QMap<QString, std::shared_ptr<BaseProfilerFactory>>& profilers() const { return m_profilers; }
const QMap<QString, std::shared_ptr<BaseProfilerFactory>> &profilers() const {
return m_profilers;
}
void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password);
@ -142,8 +164,6 @@ class Application : public QApplication {
void updateCapabilities();
void detectLibraries();
/*!
* Finds and returns the full path to a jar file.
* Returns a null-string if it could not be found.
@ -152,86 +172,78 @@ class Application : public QApplication {
QString getMSAClientID();
QString getFlameAPIKey();
QString getModrinthAPIToken();
QString getUserAgent();
QString getUserAgentUncached();
/// this is the root of the 'installation'. Used for automatic updates
const QString& root() { return m_rootPath; }
const QString &root() {
return m_rootPath;
}
/// the data path the application is using
const QString& dataRoot() { return m_dataPath; }
bool isPortable() { return m_portable; }
const Capabilities capabilities() { return m_capabilities; }
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
*/
bool openJsonEditor(const QString& filename);
bool openJsonEditor(const QString &filename);
InstanceWindow* showInstanceWindow(InstancePtr instance, QString page = QString());
MainWindow* showMainWindow(bool minimized = false);
InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString());
MainWindow *showMainWindow(bool minimized = false);
void updateIsRunning(bool running);
bool updatesAreAllowed();
void ShowGlobalSettings(class QWidget* parent, QString open_page = QString());
void ShowGlobalSettings(class QWidget * parent, QString open_page = QString());
int suitableMaxMem();
bool updaterEnabled();
QString updaterBinaryName();
QUrl normalizeImportUrl(QString const& url);
signals:
signals:
void updateAllowedChanged(bool status);
void globalSettingsAboutToOpen();
void globalSettingsClosed();
int currentCatChanged(int index);
#ifdef Q_OS_MACOS
void clickedOnDock();
#endif
public slots:
bool launch(InstancePtr instance,
public slots:
bool launch(
InstancePtr instance,
bool online = true,
bool demo = false,
BaseProfilerFactory *profiler = nullptr,
MinecraftServerTargetPtr serverToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr);
MinecraftAccountPtr accountToUse = nullptr
);
bool kill(InstancePtr instance);
void closeCurrentWindow();
private slots:
private slots:
void on_windowClose();
void messageReceived(const QByteArray& message);
void messageReceived(const QByteArray & message);
void controllerSucceeded();
void controllerFailed(const QString& error);
void controllerFailed(const QString & error);
void setupWizardFinished(int status);
private:
bool handleDataMigration(const QString& currentData, const QString& oldData, const QString& name, const QString& configFile) const;
private:
bool createSetupWizard();
void performMainStartupAction();
// sets the fatal error message and m_status to Failed.
void showFatalErrorMessage(const QString& title, const QString& content);
void showFatalErrorMessage(const QString & title, const QString & content);
private:
private:
void addRunningInstance();
void subRunningInstance();
bool shouldExitNow() const;
private:
private:
QDateTime startTime;
shared_qobject_ptr<QNetworkAccessManager> m_network;
shared_qobject_ptr<ExternalUpdater> m_updater;
shared_qobject_ptr<UpdateChecker> m_updateChecker;
shared_qobject_ptr<AccountList> m_accounts;
shared_qobject_ptr<HttpMetaCache> m_metacache;
@ -243,17 +255,15 @@ class Application : public QApplication {
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;
QString m_dataPath;
Status m_status = Application::StartingUp;
Capabilities m_capabilities;
bool m_portable = false;
#ifdef Q_OS_MACOS
Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;
@ -266,7 +276,7 @@ class Application : public QApplication {
// FIXME: attach to instances instead.
struct InstanceXtras {
InstanceWindow* window = nullptr;
InstanceWindow * window = nullptr;
shared_qobject_ptr<LaunchController> controller;
};
std::map<QString, InstanceXtras> m_instanceExtras;
@ -277,21 +287,18 @@ class Application : public QApplication {
bool m_updateRunning = false;
// main window, if any
MainWindow* m_mainWindow = nullptr;
MainWindow * m_mainWindow = nullptr;
// peer launcher instance connector - used to implement single instance launcher and signalling
LocalPeer* m_peerInstance = nullptr;
LocalPeer * m_peerInstance = nullptr;
SetupWizard* m_setupWizard = nullptr;
public:
QString m_detectedGLFWPath;
QString m_detectedOpenALPath;
SetupWizard * m_setupWizard = nullptr;
public:
QString m_instanceIdToLaunch;
QString m_serverToJoin;
QString m_profileToUse;
bool m_liveCheck = false;
QList<QUrl> m_urlsToImport;
QString m_instanceIdToShowWindowOf;
QUrl m_zipToImport;
std::unique_ptr<QFile> logFile;
};

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -39,8 +39,7 @@
#include <QJsonObject>
#include "Json.h"
void ApplicationMessage::parse(const QByteArray& input)
{
void ApplicationMessage::parse(const QByteArray & input) {
auto doc = Json::requireDocument(input, "ApplicationMessage");
auto root = Json::requireObject(doc, "ApplicationMessage");
@ -48,18 +47,17 @@ 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();
}
}
QByteArray ApplicationMessage::serialize()
{
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,13 +1,13 @@
#pragma once
#include <QByteArray>
#include <QHash>
#include <QString>
#include <QMap>
#include <QByteArray>
struct ApplicationMessage {
QString command;
QHash<QString, QString> args;
QMap<QString, QString> args;
QByteArray serialize();
void parse(const QByteArray& input);
void parse(const QByteArray & input);
};

View File

@ -18,21 +18,27 @@
#include "BaseInstaller.h"
#include "minecraft/MinecraftInstance.h"
BaseInstaller::BaseInstaller() {}
BaseInstaller::BaseInstaller()
{
bool BaseInstaller::isApplied(MinecraftInstance* on)
}
bool BaseInstaller::isApplied(MinecraftInstance *on)
{
return QFile::exists(filename(on->instanceRoot()));
}
bool BaseInstaller::add(MinecraftInstance* to)
bool BaseInstaller::add(MinecraftInstance *to)
{
if (!patchesDir(to->instanceRoot()).exists()) {
if (!patchesDir(to->instanceRoot()).exists())
{
QDir(to->instanceRoot()).mkdir("patches");
}
if (isApplied(to)) {
if (!remove(to)) {
if (isApplied(to))
{
if (!remove(to))
{
return false;
}
}
@ -40,16 +46,16 @@ bool BaseInstaller::add(MinecraftInstance* to)
return true;
}
bool BaseInstaller::remove(MinecraftInstance* from)
bool BaseInstaller::remove(MinecraftInstance *from)
{
return QFile::remove(filename(from->instanceRoot()));
}
QString BaseInstaller::filename(const QString& root) const
QString BaseInstaller::filename(const QString &root) const
{
return patchesDir(root).absoluteFilePath(id() + ".json");
}
QDir BaseInstaller::patchesDir(const QString& root) const
QDir BaseInstaller::patchesDir(const QString &root) const
{
return QDir(root + "/patches/");
}

View File

@ -26,19 +26,20 @@ class QObject;
class Task;
class BaseVersion;
class BaseInstaller {
public:
class BaseInstaller
{
public:
BaseInstaller();
virtual ~BaseInstaller(){};
bool isApplied(MinecraftInstance* on);
bool isApplied(MinecraftInstance *on);
virtual bool add(MinecraftInstance* to);
virtual bool remove(MinecraftInstance* from);
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, BaseVersion::Ptr version, QObject *parent) = 0;
protected:
protected:
virtual QString id() const = 0;
QString filename(const QString& root) const;
QDir patchesDir(const QString& root) const;
QString filename(const QString &root) const;
QDir patchesDir(const QString &root) const;
};

View File

@ -1,9 +1,8 @@
// 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 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -37,22 +36,21 @@
#include "BaseInstance.h"
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDir>
#include <QDebug>
#include <QRegularExpression>
#include "settings/INISettingsObject.h"
#include "settings/OverrideSetting.h"
#include "settings/Setting.h"
#include "settings/OverrideSetting.h"
#include "BuildConfig.h"
#include "Commandline.h"
#include "FileSystem.h"
#include "Commandline.h"
#include "BuildConfig.h"
BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir) : QObject()
BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
: QObject()
{
m_settings = settings;
m_global_settings = globalSettings;
@ -66,8 +64,6 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("totalTimePlayed", 0);
m_settings->registerSetting("lastTimePlayed", 0);
m_settings->registerSetting("linkedInstances", "[]");
// Game time override
auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false);
m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride);
@ -79,7 +75,7 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("InstanceType", "");
// Custom Commands
auto commandSetting = m_settings->registerSetting({ "OverrideCommands", "OverrideLaunchCmd" }, false);
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting);
m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting);
m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting);
@ -101,8 +97,6 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("ManagedPackName", "");
m_settings->registerSetting("ManagedPackVersionID", "");
m_settings->registerSetting("ManagedPackVersionName", "");
m_settings->registerSetting("Profiler", "");
}
QString BaseInstance::getPreLaunchCommand()
@ -150,11 +144,7 @@ QString BaseInstance::getManagedPackVersionName() const
return m_settings->get("ManagedPackVersionName").toString();
}
void BaseInstance::setManagedPack(const QString& type,
const QString& id,
const QString& name,
const QString& versionId,
const QString& version)
void BaseInstance::setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version)
{
m_settings->set("ManagedPack", true);
m_settings->set("ManagedPackType", type);
@ -179,7 +169,8 @@ int BaseInstance::getConsoleMaxLines() const
auto lineSetting = m_settings->getSetting("ConsoleMaxLines");
bool conversionOk = false;
int maxLines = lineSetting->get().toInt(&conversionOk);
if (!conversionOk) {
if(!conversionOk)
{
maxLines = lineSetting->defValue().toInt();
qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines;
}
@ -191,41 +182,10 @@ bool BaseInstance::shouldStopOnConsoleOverflow() const
return m_settings->get("ConsoleOverflowStop").toBool();
}
QStringList BaseInstance::getLinkedInstances() const
{
return m_settings->get("linkedInstances").toStringList();
}
void BaseInstance::setLinkedInstances(const QStringList& list)
{
auto linkedInstances = m_settings->get("linkedInstances").toStringList();
m_settings->set("linkedInstances", list);
}
void BaseInstance::addLinkedInstanceId(const QString& id)
{
auto linkedInstances = m_settings->get("linkedInstances").toStringList();
linkedInstances.append(id);
setLinkedInstances(linkedInstances);
}
bool BaseInstance::removeLinkedInstanceId(const QString& id)
{
auto linkedInstances = m_settings->get("linkedInstances").toStringList();
int numRemoved = linkedInstances.removeAll(id);
setLinkedInstances(linkedInstances);
return numRemoved > 0;
}
bool BaseInstance::isLinkedToInstanceId(const QString& id) const
{
auto linkedInstances = m_settings->get("linkedInstances").toStringList();
return linkedInstances.contains(id);
}
void BaseInstance::iconUpdated(QString key)
{
if (iconKey() == key) {
if(iconKey() == key)
{
emit propertiesChanged(this);
}
}
@ -239,7 +199,8 @@ void BaseInstance::invalidate()
void BaseInstance::changeStatus(BaseInstance::Status newStatus)
{
Status status = currentStatus();
if (status != newStatus) {
if(status != newStatus)
{
m_status = newStatus;
emit statusChanged(status, newStatus);
}
@ -262,19 +223,23 @@ bool BaseInstance::isRunning() const
void BaseInstance::setRunning(bool running)
{
if (running == m_isRunning)
if(running == m_isRunning)
return;
m_isRunning = running;
if (!m_settings->get("RecordGameTime").toBool()) {
if(!m_settings->get("RecordGameTime").toBool())
{
emit runningStatusChanged(running);
return;
}
if (running) {
if(running)
{
m_timeStarted = QDateTime::currentDateTime();
} else {
}
else
{
QDateTime timeEnded = QDateTime::currentDateTime();
qint64 current = settings()->get("totalTimePlayed").toLongLong();
@ -290,7 +255,8 @@ void BaseInstance::setRunning(bool running)
int64_t BaseInstance::totalTimePlayed() const
{
qint64 current = m_settings->get("totalTimePlayed").toLongLong();
if (m_isRunning) {
if(m_isRunning)
{
QDateTime timeNow = QDateTime::currentDateTime();
return current + m_timeStarted.secsTo(timeNow);
}
@ -299,7 +265,8 @@ int64_t BaseInstance::totalTimePlayed() const
int64_t BaseInstance::lastTimePlayed() const
{
if (m_isRunning) {
if(m_isRunning)
{
QDateTime timeNow = QDateTime::currentDateTime();
return m_timeStarted.secsTo(timeNow);
}
@ -346,14 +313,14 @@ qint64 BaseInstance::lastLaunch() const
void BaseInstance::setLastLaunch(qint64 val)
{
// FIXME: if no change, do not set. setting involves saving a file.
//FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("lastLaunchTime", val);
emit propertiesChanged(this);
}
void BaseInstance::setNotes(QString val)
{
// FIXME: if no change, do not set. setting involves saving a file.
//FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("notes", val);
}
@ -364,7 +331,7 @@ QString BaseInstance::notes() const
void BaseInstance::setIconKey(QString val)
{
// FIXME: if no change, do not set. setting involves saving a file.
//FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("iconKey", val);
emit propertiesChanged(this);
}
@ -376,7 +343,7 @@ QString BaseInstance::iconKey() const
void BaseInstance::setName(QString val)
{
// FIXME: if no change, do not set. setting involves saving a file.
//FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("name", val);
emit propertiesChanged(this);
}
@ -388,7 +355,7 @@ QString BaseInstance::name() const
QString BaseInstance::windowTitle() const
{
return BuildConfig.LAUNCHER_DISPLAYNAME + ": " + name();
return BuildConfig.LAUNCHER_DISPLAYNAME + ": " + name().replace(QRegularExpression("\\s+"), " ");
}
// FIXME: why is this here? move it to MinecraftInstance!!!

View File

@ -1,9 +1,8 @@
// 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 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -38,25 +37,24 @@
#pragma once
#include <cassert>
#include <QDateTime>
#include <QMenu>
#include <QObject>
#include <QProcess>
#include <QSet>
#include "QObjectPtr.h"
#include <QDateTime>
#include <QSet>
#include <QProcess>
#include "settings/SettingsObject.h"
#include "BaseVersionList.h"
#include "MessageLevel.h"
#include "minecraft/auth/MinecraftAccount.h"
#include "pathmatcher/IPathMatcher.h"
#include "settings/INIFile.h"
#include "BaseVersionList.h"
#include "minecraft/auth/MinecraftAccount.h"
#include "MessageLevel.h"
#include "pathmatcher/IPathMatcher.h"
#include "net/Mode.h"
#include "RuntimeContext.h"
#include "minecraft/launch/MinecraftServerTarget.h"
#include "RuntimeContext.h"
class QDir;
class Task;
@ -64,7 +62,7 @@ class LaunchTask;
class BaseInstance;
// pointer for lazy people
using InstancePtr = std::shared_ptr<BaseInstance>;
typedef std::shared_ptr<BaseInstance> InstancePtr;
/*!
* \brief Base class for instances.
@ -74,21 +72,23 @@ using InstancePtr = std::shared_ptr<BaseInstance>;
* To create a new instance type, create a new class inheriting from this class
* and implement the pure virtual functions.
*/
class BaseInstance : public QObject, public std::enable_shared_from_this<BaseInstance> {
class BaseInstance : public QObject, public std::enable_shared_from_this<BaseInstance>
{
Q_OBJECT
protected:
protected:
/// no-touchy!
BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir);
BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
public: /* types */
enum class Status {
public: /* types */
enum class Status
{
Present,
Gone // either nuked or invalidated
};
public:
public:
/// virtual destructor to make sure the destruction is COMPLETE
virtual ~BaseInstance() {}
virtual ~BaseInstance() {};
virtual void saveNow() = 0;
@ -117,7 +117,10 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
QString instanceRoot() const;
/// Path to the instance's game root directory.
virtual QString gameRoot() const { return instanceRoot(); }
virtual QString gameRoot() const
{
return instanceRoot();
}
/// Path to the instance's mods directory.
virtual QString modsRoot() const = 0;
@ -148,12 +151,15 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
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) { return level; }
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
{
return level;
};
virtual QStringList extraArguments();
/// Traits. Normally inside the version, depends on instance implementation.
virtual QSet<QString> traits() const = 0;
virtual QSet <QString> traits() const = 0;
/**
* Gets the time that the instance was last launched.
@ -183,7 +189,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
virtual Task::Ptr createUpdateTask(Net::Mode mode) = 0;
/// returns a valid launcher (task container)
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) = 0;
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(
AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) = 0;
/// returns the current launch task (if any)
shared_qobject_ptr<LaunchTask> getLaunchTask();
@ -215,30 +222,45 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
virtual QString typeName() const = 0;
void updateRuntimeContext();
RuntimeContext runtimeContext() const { return m_runtimeContext; }
RuntimeContext runtimeContext() const
{
return m_runtimeContext;
}
bool hasVersionBroken() const { return m_hasBrokenVersion; }
bool hasVersionBroken() const
{
return m_hasBrokenVersion;
}
void setVersionBroken(bool value)
{
if (m_hasBrokenVersion != value) {
if(m_hasBrokenVersion != value)
{
m_hasBrokenVersion = value;
emit propertiesChanged(this);
}
}
bool hasUpdateAvailable() const { return m_hasUpdate; }
bool hasUpdateAvailable() const
{
return m_hasUpdate;
}
void setUpdateAvailable(bool value)
{
if (m_hasUpdate != value) {
if(m_hasUpdate != value)
{
m_hasUpdate = value;
emit propertiesChanged(this);
}
}
bool hasCrashed() const { return m_crashed; }
bool hasCrashed() const
{
return m_crashed;
}
void setCrashed(bool value)
{
if (m_crashed != value) {
if(m_crashed != value)
{
m_crashed = value;
emit propertiesChanged(this);
}
@ -248,8 +270,6 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
virtual bool canEdit() const = 0;
virtual bool canExport() const = 0;
virtual void populateLaunchMenu(QMenu* menu) = 0;
bool reloadSettings();
/**
@ -262,38 +282,30 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
int getConsoleMaxLines() const;
bool shouldStopOnConsoleOverflow() const;
QStringList getLinkedInstances() const;
void setLinkedInstances(const QStringList& list);
void addLinkedInstanceId(const QString& id);
bool removeLinkedInstanceId(const QString& id);
bool isLinkedToInstanceId(const QString& id) const;
protected:
protected:
void changeStatus(Status newStatus);
SettingsObjectPtr globalSettings() const { return m_global_settings.lock(); }
SettingsObjectPtr globalSettings() const { return m_global_settings.lock(); };
bool isSpecificSettingsLoaded() const { return m_specific_settings_loaded; }
void setSpecificSettingsLoaded(bool loaded) { m_specific_settings_loaded = loaded; }
signals:
signals:
/*!
* \brief Signal emitted when properties relevant to the instance view change
*/
void propertiesChanged(BaseInstance* inst);
void propertiesChanged(BaseInstance *inst);
void launchTaskChanged(shared_qobject_ptr<LaunchTask>);
void runningStatusChanged(bool running);
void profilerChanged();
void statusChanged(Status from, Status to);
protected slots:
protected slots:
void iconUpdated(QString key);
protected: /* data */
protected: /* data */
QString m_rootDir;
SettingsObjectPtr m_settings;
// InstanceFlags m_flags;
@ -302,7 +314,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
QDateTime m_timeStarted;
RuntimeContext m_runtimeContext;
private: /* data */
private: /* data */
Status m_status = Status::Present;
bool m_crashed = false;
bool m_hasUpdate = false;
@ -310,8 +322,9 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
SettingsObjectWeakPtr m_global_settings;
bool m_specific_settings_loaded = false;
};
Q_DECLARE_METATYPE(shared_qobject_ptr<BaseInstance>)
// Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
// Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)
//Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
//Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)

View File

@ -15,15 +15,16 @@
#pragma once
#include <QMetaType>
#include <QString>
#include <memory>
#include <QString>
#include <QMetaType>
/*!
* An abstract base class for versions.
*/
class BaseVersion {
public:
class BaseVersion
{
public:
using Ptr = std::shared_ptr<BaseVersion>;
virtual ~BaseVersion() {}
/*!
@ -43,8 +44,15 @@ class BaseVersion {
* the kind of version this is (Stable, Beta, Snapshot, whatever)
*/
virtual QString typeString() const = 0;
virtual bool operator<(BaseVersion& a) { return name() < a.name(); }
virtual bool operator>(BaseVersion& a) { return name() > a.name(); }
virtual bool operator<(BaseVersion &a)
{
return name() < a.name();
};
virtual bool operator>(BaseVersion &a)
{
return name() > a.name();
};
};
Q_DECLARE_METATYPE(BaseVersion::Ptr)

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -36,11 +36,14 @@
#include "BaseVersionList.h"
#include "BaseVersion.h"
BaseVersionList::BaseVersionList(QObject* parent) : QAbstractListModel(parent) {}
BaseVersion::Ptr BaseVersionList::findVersion(const QString& descriptor)
BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent)
{
for (int i = 0; i < count(); i++) {
}
BaseVersion::Ptr BaseVersionList::findVersion(const QString &descriptor)
{
for (int i = 0; i < count(); i++)
{
if (at(i)->descriptor() == descriptor)
return at(i);
}
@ -55,7 +58,7 @@ BaseVersion::Ptr BaseVersionList::getRecommended() const
return at(0);
}
QVariant BaseVersionList::data(const QModelIndex& index, int role) const
QVariant BaseVersionList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
@ -65,7 +68,8 @@ QVariant BaseVersionList::data(const QModelIndex& index, int role) const
BaseVersion::Ptr version = at(index.row());
switch (role) {
switch (role)
{
case VersionPointerRole:
return QVariant::fromValue(version);
@ -85,18 +89,18 @@ QVariant BaseVersionList::data(const QModelIndex& index, int role) const
BaseVersionList::RoleList BaseVersionList::providesRoles() const
{
return { VersionPointerRole, VersionRole, VersionIdRole, TypeRole };
return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole};
}
int BaseVersionList::rowCount(const QModelIndex& parent) const
int BaseVersionList::rowCount(const QModelIndex &parent) const
{
// Return count
return parent.isValid() ? 0 : count();
return count();
}
int BaseVersionList::columnCount(const QModelIndex& parent) const
int BaseVersionList::columnCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : 1;
return 1;
}
QHash<int, QByteArray> BaseVersionList::roleNames() const

View File

@ -15,13 +15,13 @@
#pragma once
#include <QAbstractListModel>
#include <QObject>
#include <QVariant>
#include <QAbstractListModel>
#include "BaseVersion.h"
#include "QObjectPtr.h"
#include "tasks/Task.h"
#include "QObjectPtr.h"
/*!
* \brief Class that each instance type's version list derives from.
@ -35,10 +35,12 @@
* all have a default implementation, but they can be overridden by plugins to
* change the behavior of the list.
*/
class BaseVersionList : public QAbstractListModel {
class BaseVersionList : public QAbstractListModel
{
Q_OBJECT
public:
enum ModelRoles {
public:
enum ModelRoles
{
VersionPointerRole = Qt::UserRole,
VersionRole,
VersionIdRole,
@ -51,9 +53,9 @@ class BaseVersionList : public QAbstractListModel {
ArchitectureRole,
SortRole
};
using RoleList = QList<int>;
typedef QList<int> RoleList;
explicit BaseVersionList(QObject* parent = 0);
explicit BaseVersionList(QObject *parent = 0);
/*!
* \brief Gets a task that will reload the version list.
@ -64,7 +66,7 @@ class BaseVersionList : public QAbstractListModel {
virtual Task::Ptr getLoadTask() = 0;
//! Checks whether or not the list is loaded. If this returns false, the list should be
// loaded.
//loaded.
virtual bool isLoaded() = 0;
//! Gets the version at the given index.
@ -74,9 +76,9 @@ class BaseVersionList : public QAbstractListModel {
virtual int count() const = 0;
//////// List Model Functions ////////
QVariant data(const QModelIndex& index, int role) const override;
int rowCount(const QModelIndex& parent) const override;
int columnCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QHash<int, QByteArray> roleNames() const override;
//! which roles are provided by this version list?
@ -88,7 +90,7 @@ class BaseVersionList : public QAbstractListModel {
* \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 BaseVersion::Ptr findVersion(const QString &descriptor);
/*!
* \brief Gets the recommended version from this list
@ -101,7 +103,8 @@ class BaseVersionList : public QAbstractListModel {
*/
virtual void sortVersions() = 0;
protected slots:
protected
slots:
/*!
* Updates this list with the given list of versions.
* This is done by copying each version in the given list and inserting it

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -41,7 +41,8 @@
* @file libutil/src/cmdutils.cpp
*/
namespace Commandline {
namespace Commandline
{
// commandline splitter
QStringList splitArgs(QString args)
@ -50,15 +51,19 @@ QStringList splitArgs(QString args)
QString current;
bool escape = false;
QChar inquotes;
for (int i = 0; i < args.length(); i++) {
for (int i = 0; i < args.length(); i++)
{
QChar cchar = args.at(i);
// \ escaped
if (escape) {
if (escape)
{
current += cchar;
escape = false;
// in "quotes"
} else if (!inquotes.isNull()) {
}
else if (!inquotes.isNull())
{
if (cchar == '\\')
escape = true;
else if (cchar == inquotes)
@ -66,13 +71,18 @@ QStringList splitArgs(QString args)
else
current += cchar;
// otherwise
} else {
if (cchar == ' ') {
if (!current.isEmpty()) {
}
else
{
if (cchar == ' ')
{
if (!current.isEmpty())
{
argv << current;
current.clear();
}
} else if (cchar == '"' || cchar == '\'')
}
else if (cchar == '"' || cchar == '\'')
inquotes = cchar;
else
current += cchar;
@ -82,4 +92,4 @@ QStringList splitArgs(QString args)
argv << current;
return argv;
}
} // namespace Commandline
}

View File

@ -25,7 +25,8 @@
* @brief commandline parsing and processing utilities
*/
namespace Commandline {
namespace Commandline
{
/**
* @brief split a string into argv items like a shell would do
@ -33,4 +34,4 @@ namespace Commandline {
* @return a QStringList containing all arguments
*/
QStringList splitArgs(QString args);
} // namespace Commandline
}

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

@ -1,21 +1,33 @@
#pragma once
template <typename T>
class DefaultVariable {
public:
DefaultVariable(const T& value) { defaultValue = value; }
DefaultVariable<T>& operator=(const T& value)
class DefaultVariable
{
public:
DefaultVariable(const T & value)
{
defaultValue = value;
}
DefaultVariable<T> & operator =(const T & value)
{
currentValue = value;
is_default = currentValue == defaultValue;
is_explicit = true;
return *this;
}
operator const T&() const { return is_default ? defaultValue : currentValue; }
bool isDefault() const { return is_default; }
bool isExplicit() const { return is_explicit; }
private:
operator const T &() const
{
return is_default ? defaultValue : currentValue;
}
bool isDefault() const
{
return is_default;
}
bool isExplicit() const
{
return is_explicit;
}
private:
T currentValue;
T defaultValue;
bool is_default = true;

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 dada513 <dada513@protonmail.com>
*
* This program is free software: you can redistribute it and/or modify
@ -33,37 +33,41 @@
* limitations under the License.
*/
#include "DesktopServices.h"
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QDesktopServices>
#include <QProcess>
#include <QDebug>
#include "Application.h"
/**
* This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing.
*/
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
template <typename T>
bool IndirectOpen(T callable, qint64* pid_forked = nullptr)
bool IndirectOpen(T callable, qint64 *pid_forked = nullptr)
{
auto pid = fork();
if (pid_forked) {
if (pid > 0)
if(pid_forked)
{
if(pid > 0)
*pid_forked = pid;
else
*pid_forked = 0;
}
if (pid == -1) {
if(pid == -1)
{
qWarning() << "IndirectOpen failed to fork: " << errno;
return false;
}
// child - do the stuff
if (pid == 0) {
if(pid == 0)
{
// unset all this garbage so it doesn't get passed to the child process
qunsetenv("LD_PRELOAD");
qunsetenv("LD_LIBRARY_PATH");
@ -79,14 +83,19 @@ bool IndirectOpen(T callable, qint64* pid_forked = nullptr)
// die. now. do not clean up anything, it would just hang forever.
_exit(status ? 0 : 1);
} else {
// parent - assume it worked.
}
else
{
//parent - assume it worked.
int status;
while (waitpid(pid, &status, 0)) {
if (WIFEXITED(status)) {
while (waitpid(pid, &status, 0))
{
if(WIFEXITED(status))
{
return WEXITSTATUS(status) == 0;
}
if (WIFSIGNALED(status)) {
if(WIFSIGNALED(status))
{
return false;
}
}
@ -96,19 +105,26 @@ bool IndirectOpen(T callable, qint64* pid_forked = nullptr)
#endif
namespace DesktopServices {
bool openDirectory(const QString& path, [[maybe_unused]] bool ensureExists)
bool openDirectory(const QString &path, bool ensureExists)
{
qDebug() << "Opening directory" << path;
QDir parentPath;
QDir dir(path);
if (ensureExists && !dir.exists()) {
if (!dir.exists())
{
parentPath.mkpath(dir.absolutePath());
}
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); };
auto f = [&]()
{
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
if(!isFlatpak())
{
return IndirectOpen(f);
} else {
}
else
{
return f();
}
#else
@ -116,14 +132,20 @@ bool openDirectory(const QString& path, [[maybe_unused]] bool ensureExists)
#endif
}
bool openFile(const QString& path)
bool openFile(const QString &path)
{
qDebug() << "Opening file" << path;
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); };
auto f = [&]()
{
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
if(!isFlatpak())
{
return IndirectOpen(f);
} else {
}
else
{
return f();
}
#else
@ -131,14 +153,20 @@ bool openFile(const QString& path)
#endif
}
bool openFile(const QString& application, const QString& path, const QString& workingDirectory, qint64* pid)
bool openFile(const QString &application, const QString &path, const QString &workingDirectory, qint64 *pid)
{
qDebug() << "Opening file" << path << "using" << application;
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
if (!isSandbox()) {
return IndirectOpen([&]() { return QProcess::startDetached(application, QStringList() << path, workingDirectory); }, pid);
} else {
if(!isFlatpak())
{
return IndirectOpen([&]()
{
return QProcess::startDetached(application, QStringList() << path, workingDirectory);
}, pid);
}
else
{
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
}
#else
@ -146,14 +174,20 @@ bool openFile(const QString& application, const QString& path, const QString& wo
#endif
}
bool run(const QString& application, const QStringList& args, const QString& workingDirectory, qint64* pid)
bool run(const QString &application, const QStringList &args, const QString &workingDirectory, qint64 *pid)
{
qDebug() << "Running" << application << "with args" << args.join(' ');
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
if(!isFlatpak())
{
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
return IndirectOpen([&]() { return QProcess::startDetached(application, args, workingDirectory); }, pid);
} else {
return IndirectOpen([&]()
{
return QProcess::startDetached(application, args, workingDirectory);
}, pid);
}
else
{
return QProcess::startDetached(application, args, workingDirectory, pid);
}
#else
@ -161,14 +195,20 @@ bool run(const QString& application, const QStringList& args, const QString& wor
#endif
}
bool openUrl(const QUrl& url)
bool openUrl(const QUrl &url)
{
qDebug() << "Opening URL" << url.toString();
auto f = [&]() { return QDesktopServices::openUrl(url); };
auto f = [&]()
{
return QDesktopServices::openUrl(url);
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
if(!isFlatpak())
{
return IndirectOpen(f);
} else {
}
else
{
return f();
}
#else
@ -185,18 +225,4 @@ bool isFlatpak()
#endif
}
bool isSnap()
{
#ifdef Q_OS_LINUX
return getenv("SNAP");
#else
return false;
#endif
}
bool isSandbox()
{
return isSnap() || isFlatpak();
}
} // namespace DesktopServices

View File

@ -1,50 +1,38 @@
#pragma once
#include <QString>
#include <QUrl>
#include <QString>
/**
* This wraps around QDesktopServices and adds workarounds where needed
* Use this instead of QDesktopServices!
*/
namespace DesktopServices {
/**
namespace DesktopServices
{
/**
* Open a file in whatever application is applicable
*/
bool openFile(const QString& path);
bool openFile(const QString &path);
/**
/**
* Open a file in the specified application
*/
bool openFile(const QString& application, const QString& path, const QString& workingDirectory = QString(), qint64* pid = 0);
bool openFile(const QString &application, const QString &path, const QString & workingDirectory = QString(), qint64 *pid = 0);
/**
/**
* Run an application
*/
bool run(const QString& application, const QStringList& args, const QString& workingDirectory = QString(), qint64* pid = 0);
bool run(const QString &application,const QStringList &args, const QString & workingDirectory = QString(), qint64 *pid = 0);
/**
/**
* Open a directory
*/
bool openDirectory(const QString& path, bool ensureExists = false);
bool openDirectory(const QString &path, bool ensureExists = false);
/**
/**
* Open the URL, most likely in a browser. Maybe.
*/
bool openUrl(const QUrl& url);
bool openUrl(const QUrl &url);
/**
* Determine whether the launcher is running in a Flatpak environment
*/
bool isFlatpak();
/**
* Determine whether the launcher is running in a Snap environment
*/
bool isSnap();
/**
* Determine whether the launcher is running in a sandboxed (Flatpak or Snap) environment
*/
bool isSandbox();
} // namespace DesktopServices
bool isFlatpak();
}

View File

@ -2,18 +2,31 @@
#pragma once
#include <QDebug>
#include <QString>
#include <QDebug>
#include <exception>
class Exception : public std::exception {
public:
Exception(const QString& message) : std::exception(), m_message(message) { qCritical() << "Exception:" << message; }
Exception(const Exception& other) : std::exception(), m_message(other.cause()) {}
class Exception : public std::exception
{
public:
Exception(const QString &message) : std::exception(), m_message(message)
{
qCritical() << "Exception:" << message;
}
Exception(const Exception &other)
: std::exception(), m_message(other.cause())
{
}
virtual ~Exception() noexcept {}
const char* what() const noexcept { return m_message.toLatin1().constData(); }
QString cause() const { return m_message; }
const char *what() const noexcept
{
return m_message.toLatin1().constData();
}
QString cause() const
{
return m_message;
}
private:
private:
QString m_message;
};

View File

@ -4,24 +4,31 @@
template <typename T>
inline void clamp(T& current, T min, T max)
{
if (current < min) {
if (current < min)
{
current = min;
} else if (current > max) {
}
else if(current > max)
{
current = max;
}
}
// List of numbers from min to max. Next is exponent times bigger than previous.
class ExponentialSeries {
public:
class ExponentialSeries
{
public:
ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2)
{
m_current = m_min = min;
m_max = max;
m_exponent = exponent;
}
void reset() { m_current = m_min; }
void reset()
{
m_current = m_min;
}
unsigned operator()()
{
unsigned retval = m_current;

View File

@ -1,47 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 "FastFileIconProvider.h"
#include <QApplication>
#include <QStyle>
QIcon FastFileIconProvider::icon(const QFileInfo& info) const
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
bool link = info.isSymbolicLink() || info.isAlias() || info.isShortcut();
#else
// in versions prior to 6.4 we don't have access to isAlias
bool link = info.isSymLink();
#endif
QStyle::StandardPixmap icon;
if (info.isDir()) {
if (link)
icon = QStyle::SP_DirLinkIcon;
else
icon = QStyle::SP_DirIcon;
} else {
if (link)
icon = QStyle::SP_FileLinkIcon;
else
icon = QStyle::SP_FileIcon;
}
return QApplication::style()->standardIcon(icon);
}

View File

@ -1,26 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 <QFileIconProvider>
class FastFileIconProvider : public QFileIconProvider {
public:
QIcon icon(const QFileInfo& info) const override;
};

View File

@ -1,276 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "FileIgnoreProxy.h"
#include <QDebug>
#include <QFileSystemModel>
#include <QSortFilterProxyModel>
#include <QStack>
#include <algorithm>
#include "FileSystem.h"
#include "SeparatorPrefixTree.h"
#include "StringUtils.h"
FileIgnoreProxy::FileIgnoreProxy(QString root, QObject* parent) : QSortFilterProxyModel(parent), root(root) {}
// NOTE: Sadly, we have to do sorting ourselves.
bool FileIgnoreProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const
{
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel());
if (!fsm) {
return QSortFilterProxyModel::lessThan(left, right);
}
bool asc = sortOrder() == Qt::AscendingOrder ? true : false;
QFileInfo leftFileInfo = fsm->fileInfo(left);
QFileInfo rightFileInfo = fsm->fileInfo(right);
if (!leftFileInfo.isDir() && rightFileInfo.isDir()) {
return !asc;
}
if (leftFileInfo.isDir() && !rightFileInfo.isDir()) {
return asc;
}
// sort and proxy model breaks the original model...
if (sortColumn() == 0) {
return StringUtils::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), Qt::CaseInsensitive) < 0;
}
if (sortColumn() == 1) {
auto leftSize = leftFileInfo.size();
auto rightSize = rightFileInfo.size();
if ((leftSize == rightSize) || (leftFileInfo.isDir() && rightFileInfo.isDir())) {
return StringUtils::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), Qt::CaseInsensitive) < 0 ? asc : !asc;
}
return leftSize < rightSize;
}
return QSortFilterProxyModel::lessThan(left, right);
}
Qt::ItemFlags FileIgnoreProxy::flags(const QModelIndex& index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
auto sourceIndex = mapToSource(index);
Qt::ItemFlags flags = sourceIndex.flags();
if (index.column() == 0) {
flags |= Qt::ItemIsUserCheckable;
if (sourceIndex.model()->hasChildren(sourceIndex)) {
flags |= Qt::ItemIsAutoTristate;
}
}
return flags;
}
QVariant FileIgnoreProxy::data(const QModelIndex& index, int role) const
{
QModelIndex sourceIndex = mapToSource(index);
if (index.column() == 0 && role == Qt::CheckStateRole) {
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel());
auto blockedPath = relPath(fsm->filePath(sourceIndex));
auto cover = blocked.cover(blockedPath);
if (!cover.isNull()) {
return QVariant(Qt::Unchecked);
} else if (blocked.exists(blockedPath)) {
return QVariant(Qt::PartiallyChecked);
} else {
return QVariant(Qt::Checked);
}
}
return sourceIndex.data(role);
}
bool FileIgnoreProxy::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (index.column() == 0 && role == Qt::CheckStateRole) {
Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt());
return setFilterState(index, state);
}
QModelIndex sourceIndex = mapToSource(index);
return QSortFilterProxyModel::sourceModel()->setData(sourceIndex, value, role);
}
QString FileIgnoreProxy::relPath(const QString& path) const
{
return QDir(root).relativeFilePath(path);
}
bool FileIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state)
{
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel());
if (!fsm) {
return false;
}
QModelIndex sourceIndex = mapToSource(index);
auto blockedPath = relPath(fsm->filePath(sourceIndex));
bool changed = false;
if (state == Qt::Unchecked) {
// blocking a path
auto& node = blocked.insert(blockedPath);
// get rid of all blocked nodes below
node.clear();
changed = true;
} else if (state == Qt::Checked || state == Qt::PartiallyChecked) {
if (!blocked.remove(blockedPath)) {
auto cover = blocked.cover(blockedPath);
qDebug() << "Blocked by cover" << cover;
// uncover
blocked.remove(cover);
// block all contents, except for any cover
QModelIndex rootIndex = fsm->index(FS::PathCombine(root, cover));
QModelIndex doing = rootIndex;
int row = 0;
QStack<QModelIndex> todo;
while (1) {
auto node = fsm->index(row, 0, doing);
if (!node.isValid()) {
if (!todo.size()) {
break;
} else {
doing = todo.pop();
row = 0;
continue;
}
}
auto relpath = relPath(fsm->filePath(node));
if (blockedPath.startsWith(relpath)) // cover found?
{
// continue processing cover later
todo.push(node);
} else {
// or just block this one.
blocked.insert(relpath);
}
row++;
}
}
changed = true;
}
if (changed) {
// update the thing
emit dataChanged(index, index, { Qt::CheckStateRole });
// update everything above index
QModelIndex up = index.parent();
while (1) {
if (!up.isValid())
break;
emit dataChanged(up, up, { Qt::CheckStateRole });
up = up.parent();
}
// and everything below the index
QModelIndex doing = index;
int row = 0;
QStack<QModelIndex> todo;
while (1) {
auto node = this->index(row, 0, doing);
if (!node.isValid()) {
if (!todo.size()) {
break;
} else {
doing = todo.pop();
row = 0;
continue;
}
}
emit dataChanged(node, node, { Qt::CheckStateRole });
todo.push(node);
row++;
}
// siblings and unrelated nodes are ignored
}
return true;
}
bool FileIgnoreProxy::shouldExpand(QModelIndex index)
{
QModelIndex sourceIndex = mapToSource(index);
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel());
if (!fsm) {
return false;
}
auto blockedPath = relPath(fsm->filePath(sourceIndex));
auto found = blocked.find(blockedPath);
if (found) {
return !found->leaf();
}
return false;
}
void FileIgnoreProxy::setBlockedPaths(QStringList paths)
{
beginResetModel();
blocked.clear();
blocked.insert(paths);
endResetModel();
}
bool FileIgnoreProxy::filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const
{
Q_UNUSED(source_parent)
// adjust the columns you want to filter out here
// return false for those that will be hidden
if (source_column == 2 || source_column == 3)
return false;
return true;
}
bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
{
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel());
auto fileInfo = fsm->fileInfo(index);
return !ignoreFile(fileInfo);
}
bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
{
return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()));
}
bool FileIgnoreProxy::filterFile(const QString& fileName) const
{
return blocked.covers(fileName) || ignoreFile(QFileInfo(QDir(root), fileName));
}

View File

@ -1,85 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 <QFileInfo>
#include <QSortFilterProxyModel>
#include "SeparatorPrefixTree.h"
class FileIgnoreProxy : public QSortFilterProxyModel {
Q_OBJECT
public:
FileIgnoreProxy(QString root, QObject* parent);
// NOTE: Sadly, we have to do sorting ourselves.
bool lessThan(const QModelIndex& left, const QModelIndex& right) const;
virtual Qt::ItemFlags flags(const QModelIndex& index) const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole);
QString relPath(const QString& path) const;
bool setFilterState(QModelIndex index, Qt::CheckState state);
bool shouldExpand(QModelIndex index);
void setBlockedPaths(QStringList paths);
inline const SeparatorPrefixTree<'/'>& blockedPaths() const { return blocked; }
inline SeparatorPrefixTree<'/'>& blockedPaths() { return blocked; }
// list of file names that need to be removed completely from model
inline QStringList& ignoreFilesWithName() { return m_ignoreFiles; }
// list of relative paths that need to be removed completely from model
inline SeparatorPrefixTree<'/'>& ignoreFilesWithPath() { return m_ignoreFilePaths; }
bool filterFile(const QString& fileName) const;
protected:
bool filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const;
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
bool ignoreFile(QFileInfo file) const;
private:
const QString root;
SeparatorPrefixTree<'/'> blocked;
QStringList m_ignoreFiles;
SeparatorPrefixTree<'/'> m_ignoreFilePaths;
};

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +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 TheKodeToad <TheKodeToad@proton.me>
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.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
@ -40,14 +38,8 @@
#include "Exception.h"
#include "pathmatcher/IPathMatcher.h"
#include <system_error>
#include <QDir>
#include <QFlags>
#include <QLocalServer>
#include <QObject>
#include <QPair>
#include <QThread>
namespace FS {
@ -61,16 +53,6 @@ class FileSystemException : public ::Exception {
*/
void write(const QString& filename, const QByteArray& data);
/**
* append data to a file safely
*/
void appendSafe(const QString& filename, const QByteArray& data);
/**
* append data to a file
*/
void append(const QString& filename, const QByteArray& data);
/**
* read data from a file safely\
*/
@ -93,13 +75,9 @@ 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
class copy {
public:
copy(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
copy(const QString& src, const QString& dst)
{
m_src.setPath(src);
m_dst.setPath(dst);
@ -109,176 +87,23 @@ class copy : public QObject {
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)
{
m_whitelist = whitelist;
return *this;
}
copy& overwrite(const bool overwrite)
{
m_overwrite = overwrite;
return *this;
}
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
qsizetype totalCopied() { return m_copied; }
qsizetype totalFailed() { return m_failedPaths.length(); }
QStringList failed() { return m_failedPaths; }
signals:
void fileCopied(const QString& relativeName);
void copyFailed(const QString& relativeName);
// TODO: maybe add a "shouldCopy" signal in the future?
bool operator()() { return operator()(QString()); }
private:
bool operator()(const QString& offset, bool dryRun = false);
bool operator()(const QString& offset);
private:
bool m_followSymlinks = true;
const IPathMatcher* m_matcher = nullptr;
bool m_whitelist = false;
bool m_overwrite = false;
const IPathMatcher* m_blacklist = nullptr;
QDir m_src;
QDir m_dst;
qsizetype m_copied;
QStringList m_failedPaths;
};
struct LinkPair {
QString src;
QString dst;
};
struct LinkResult {
QString src;
QString dst;
QString err_msg;
int err_value;
};
class ExternalLinkFileProcess : public QThread {
Q_OBJECT
public:
ExternalLinkFileProcess(QString server, bool useHardLinks, QObject* parent = nullptr)
: QThread(parent), m_useHardLinks(useHardLinks), m_server(server)
{}
void run() override
{
runLinkFile();
emit processExited();
}
signals:
void processExited();
private:
void runLinkFile();
bool m_useHardLinks = false;
QString m_server;
};
/**
* @brief links (a file / a directory and it's contents) from src to dest
*/
class create_link : public QObject {
Q_OBJECT
public:
create_link(const QList<LinkPair> path_pairs, QObject* parent = nullptr) : QObject(parent) { m_path_pairs.append(path_pairs); }
create_link(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
{
LinkPair pair = { src, dst };
m_path_pairs.append(pair);
}
create_link& useHardLinks(const bool useHard)
{
m_useHardLinks = useHard;
return *this;
}
create_link& matcher(const IPathMatcher* filter)
{
m_matcher = filter;
return *this;
}
create_link& whitelist(bool whitelist)
{
m_whitelist = whitelist;
return *this;
}
create_link& linkRecursively(bool recursive)
{
m_recursive = recursive;
return *this;
}
create_link& setMaxDepth(int depth)
{
m_max_depth = depth;
return *this;
}
create_link& debug(bool d)
{
m_debug = d;
return *this;
}
std::error_code getOSError() { return m_os_err; }
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
int totalLinked() { return m_linked; }
void runPrivileged() { runPrivileged(QString()); }
void runPrivileged(const QString& offset);
QList<LinkResult> getResults() { return m_path_results; }
signals:
void fileLinked(const QString& srcName, const QString& dstName);
void linkFailed(const QString& srcName, const QString& dstName, const QString& err_msg, int err_value);
void finished();
void finishedPrivileged(bool gotResults);
private:
bool operator()(const QString& offset, bool dryRun = false);
void make_link_list(const QString& offset);
bool make_links();
private:
bool m_useHardLinks = false;
const IPathMatcher* m_matcher = nullptr;
bool m_whitelist = false;
bool m_recursive = true;
/// @brief >= -1 = infinite, 0 = link files at src/* to dest/*, 1 = link files at src/*/* to dest/*/*, etc.
int m_max_depth = -1;
QList<LinkPair> m_path_pairs;
QList<LinkResult> m_path_results;
QList<LinkPair> m_links_to_make;
int m_linked;
bool m_debug = false;
std::error_code m_os_err;
QLocalServer m_linkServer;
};
/**
* @brief moves a file by renaming it
* @param source source file path
* @param dest destination filepath
*
*/
bool move(const QString& source, const QString& dest);
/**
* Delete a folder recursively
*/
@ -287,30 +112,13 @@ bool deletePath(QString path);
/**
* Trash a folder / file
*/
bool trash(QString path, QString* pathInTrash = nullptr);
bool trash(QString path, QString *pathInTrash);
QString PathCombine(const QString& path1, const QString& path2);
QString PathCombine(const QString& path1, const QString& path2, const QString& path3);
QString PathCombine(const QString& path1, const QString& path2, const QString& path3, const QString& path4);
QString AbsolutePath(const QString& path);
/**
* @brief depth of path. "foo.txt" -> 0 , "bar/foo.txt" -> 1, /baz/bar/foo.txt -> 2, etc.
*
* @param path path to measure
* @return int number of components before base path
*/
int pathDepth(const QString& path);
/**
* @brief cut off segments of path until it is a max of length depth
*
* @param path path to truncate
* @param depth max depth of new path
* @return QString truncated path
*/
QString pathTruncate(const QString& path, int depth);
QString AbsolutePath(QString path);
/**
* Resolve an executable
@ -347,202 +155,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);
enum class FilesystemType {
FAT,
NTFS,
REFS,
EXT,
EXT_2_OLD,
EXT_2_3_4,
XFS,
BTRFS,
NFS,
ZFS,
APFS,
HFS,
HFSPLUS,
HFSX,
FUSEBLK,
F2FS,
UNKNOWN
};
/**
* @brief Ordered Mapping of enum types to reported filesystem names
* this mapping is non exsaustive, it just attempts to capture the filesystems which could be reasonalbly be in use .
* all string values are in uppercase, use `QString.toUpper()` or equivalent during lookup.
*
* QMap is ordered
*
*/
static const QMap<FilesystemType, QStringList> s_filesystem_type_names = { { FilesystemType::FAT, { "FAT" } },
{ FilesystemType::NTFS, { "NTFS" } },
{ FilesystemType::REFS, { "REFS" } },
{ FilesystemType::EXT_2_OLD, { "EXT_2_OLD", "EXT2_OLD" } },
{ FilesystemType::EXT_2_3_4,
{ "EXT2/3/4", "EXT_2_3_4", "EXT2", "EXT3", "EXT4" } },
{ FilesystemType::EXT, { "EXT" } },
{ FilesystemType::XFS, { "XFS" } },
{ FilesystemType::BTRFS, { "BTRFS" } },
{ FilesystemType::NFS, { "NFS" } },
{ FilesystemType::ZFS, { "ZFS" } },
{ FilesystemType::APFS, { "APFS" } },
{ FilesystemType::HFS, { "HFS" } },
{ FilesystemType::HFSPLUS, { "HFSPLUS" } },
{ FilesystemType::HFSX, { "HFSX" } },
{ FilesystemType::FUSEBLK, { "FUSEBLK" } },
{ FilesystemType::F2FS, { "F2FS" } },
{ FilesystemType::UNKNOWN, { "UNKNOWN" } } };
/**
* @brief Get the string name of Filesystem enum object
*
* @param type
* @return QString
*/
QString getFilesystemTypeName(FilesystemType type);
/**
* @brief Get the Filesystem enum object from a name
* Does a lookup of the type name and returns an exact match
*
* @param name
* @return FilesystemType
*/
FilesystemType getFilesystemType(const QString& name);
/**
* @brief Get the Filesystem enum object from a name
* Does a fuzzy lookup of the type name and returns an apropreate match
*
* @param name
* @return FilesystemType
*/
FilesystemType getFilesystemTypeFuzzy(const QString& name);
struct FilesystemInfo {
FilesystemType fsType = FilesystemType::UNKNOWN;
QString fsTypeName;
int blockSize;
qint64 bytesAvailable;
qint64 bytesFree;
qint64 bytesTotal;
QString name;
QString rootPath;
};
/**
* @brief path to the near ancestor that exists
*
*/
QString nearestExistentAncestor(const QString& path);
/**
* @brief colect information about the filesystem under a file
*
*/
FilesystemInfo statFS(const QString& path);
static const QList<FilesystemType> s_clone_filesystems = { FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS,
FilesystemType::XFS, FilesystemType::REFS };
/**
* @brief if the Filesystem is reflink/clone capable
*
*/
bool canCloneOnFS(const QString& path);
bool canCloneOnFS(const FilesystemInfo& info);
bool canCloneOnFS(FilesystemType type);
/**
* @brief if the Filesystems are reflink/clone capable and both are on the same device
*
*/
bool canClone(const QString& src, const QString& dst);
/**
* @brief Copies a directory and it's contents from src to dest
*/
class clone : public QObject {
Q_OBJECT
public:
clone(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
{
m_src.setPath(src);
m_dst.setPath(dst);
}
clone& matcher(const IPathMatcher* filter)
{
m_matcher = filter;
return *this;
}
clone& whitelist(bool whitelist)
{
m_whitelist = whitelist;
return *this;
}
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
qsizetype totalCloned() { return m_cloned; }
qsizetype totalFailed() { return m_failedClones.length(); }
QList<QPair<QString, QString>> failed() { return m_failedClones; }
signals:
void fileCloned(const QString& src, const QString& dst);
void cloneFailed(const QString& src, const QString& dst);
private:
bool operator()(const QString& offset, bool dryRun = false);
private:
const IPathMatcher* m_matcher = nullptr;
bool m_whitelist = false;
QDir m_src;
QDir m_dst;
qsizetype m_cloned;
QList<QPair<QString, QString>> m_failedClones;
};
/**
* @brief clone/reflink file from src to dst
*
*/
bool clone_file(const QString& src, const QString& dst, std::error_code& ec);
#if defined(Q_OS_WIN)
bool win_ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, std::error_code& ec);
#elif defined(Q_OS_LINUX)
bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std::error_code& ec);
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec);
#endif
static const QList<FilesystemType> s_non_link_filesystems = {
FilesystemType::FAT,
};
/**
* @brief if the Filesystem is symlink capable
*
*/
bool canLinkOnFS(const QString& path);
bool canLinkOnFS(const FilesystemInfo& info);
bool canLinkOnFS(FilesystemType type);
/**
* @brief if the Filesystem is symlink capable on both ends
*
*/
bool canLink(const QString& src, const QString& dst);
uintmax_t hardLinkCount(const QString& path);
} // namespace FS
}

View File

@ -1,33 +1,28 @@
#include "Filter.h"
Filter::~Filter() {}
Filter::~Filter(){}
ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern) {}
ContainsFilter::~ContainsFilter() {}
ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern){}
ContainsFilter::~ContainsFilter(){}
bool ContainsFilter::accepts(const QString& value)
{
return value.contains(pattern);
}
ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern) {}
ExactFilter::~ExactFilter() {}
ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern){}
ExactFilter::~ExactFilter(){}
bool ExactFilter::accepts(const QString& value)
{
return value == pattern;
}
ExactIfPresentFilter::ExactIfPresentFilter(const QString& pattern) : pattern(pattern) {}
bool ExactIfPresentFilter::accepts(const QString& value)
{
return value.isEmpty() || value == pattern;
}
RegexpFilter::RegexpFilter(const QString& regexp, bool invert) : invert(invert)
RegexpFilter::RegexpFilter(const QString& regexp, bool invert)
:invert(invert)
{
pattern.setPattern(regexp);
pattern.optimize();
}
RegexpFilter::~RegexpFilter() {}
RegexpFilter::~RegexpFilter(){}
bool RegexpFilter::accepts(const QString& value)
{
auto match = pattern.match(value);

View File

@ -1,51 +1,42 @@
#pragma once
#include <QRegularExpression>
#include <QString>
#include <QRegularExpression>
class Filter {
public:
class Filter
{
public:
virtual ~Filter();
virtual bool accepts(const QString& value) = 0;
virtual bool accepts(const QString & value) = 0;
};
class ContainsFilter : public Filter {
public:
ContainsFilter(const QString& pattern);
class ContainsFilter: public Filter
{
public:
ContainsFilter(const QString &pattern);
virtual ~ContainsFilter();
bool accepts(const QString& value) override;
private:
bool accepts(const QString & value) override;
private:
QString pattern;
};
class ExactFilter : public Filter {
public:
ExactFilter(const QString& pattern);
class ExactFilter: public Filter
{
public:
ExactFilter(const QString &pattern);
virtual ~ExactFilter();
bool accepts(const QString& value) override;
private:
bool accepts(const QString & value) override;
private:
QString pattern;
};
class ExactIfPresentFilter : public Filter {
public:
ExactIfPresentFilter(const QString& pattern);
~ExactIfPresentFilter() override = default;
bool accepts(const QString& value) override;
private:
QString pattern;
};
class RegexpFilter : public Filter {
public:
RegexpFilter(const QString& regexp, bool invert);
class RegexpFilter: public Filter
{
public:
RegexpFilter(const QString &regexp, bool invert);
virtual ~RegexpFilter();
bool accepts(const QString& value) override;
private:
bool accepts(const QString & value) override;
private:
QRegularExpression pattern;
bool invert = false;
};

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -37,9 +37,10 @@
#include <zlib.h>
#include <QByteArray>
bool GZip::unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes)
bool GZip::unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes)
{
if (compressedBytes.size() == 0) {
if (compressedBytes.size() == 0)
{
uncompressedBytes = compressedBytes;
return true;
}
@ -50,37 +51,42 @@ bool GZip::unzip(const QByteArray& compressedBytes, QByteArray& uncompressedByte
z_stream strm;
memset(&strm, 0, sizeof(strm));
strm.next_in = (Bytef*)compressedBytes.data();
strm.next_in = (Bytef *)compressedBytes.data();
strm.avail_in = compressedBytes.size();
bool done = false;
if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK) {
if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK)
{
return false;
}
int err = Z_OK;
while (!done) {
while (!done)
{
// If our output buffer is too small
if (strm.total_out >= uncompLength) {
if (strm.total_out >= uncompLength)
{
uncompressedBytes.resize(uncompLength * 2);
uncompLength *= 2;
}
strm.next_out = reinterpret_cast<Bytef*>((uncompressedBytes.data() + strm.total_out));
strm.next_out = reinterpret_cast<Bytef *>((uncompressedBytes.data() + strm.total_out));
strm.avail_out = uncompLength - strm.total_out;
// Inflate another chunk.
err = inflate(&strm, Z_SYNC_FLUSH);
if (err == Z_STREAM_END)
done = true;
else if (err != Z_OK) {
else if (err != Z_OK)
{
break;
}
}
if (inflateEnd(&strm) != Z_OK || !done) {
if (inflateEnd(&strm) != Z_OK || !done)
{
return false;
}
@ -88,9 +94,10 @@ bool GZip::unzip(const QByteArray& compressedBytes, QByteArray& uncompressedByte
return true;
}
bool GZip::zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes)
bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes)
{
if (uncompressedBytes.size() == 0) {
if (uncompressedBytes.size() == 0)
{
compressedBytes = uncompressedBytes;
return true;
}
@ -102,7 +109,8 @@ bool GZip::zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes)
z_stream zs;
memset(&zs, 0, sizeof(zs));
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK) {
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK)
{
return false;
}
@ -114,9 +122,11 @@ bool GZip::zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes)
unsigned offset = 0;
unsigned temp = 0;
do {
do
{
auto remaining = compressedBytes.size() - offset;
if (remaining < 1) {
if(remaining < 1)
{
compressedBytes.resize(compressedBytes.size() * 2);
}
zs.next_out = reinterpret_cast<Bytef*>((compressedBytes.data() + offset));
@ -127,11 +137,13 @@ bool GZip::zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes)
compressedBytes.resize(offset);
if (deflateEnd(&zs) != Z_OK) {
if (deflateEnd(&zs) != Z_OK)
{
return false;
}
if (ret != Z_STREAM_END) {
if (ret != Z_STREAM_END)
{
return false;
}
return true;

View File

@ -1,8 +1,10 @@
#pragma once
#include <QByteArray>
class GZip {
public:
static bool unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes);
static bool zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes);
class GZip
{
public:
static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes);
static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes);
};

76
launcher/HoeDown.h Normal file
View File

@ -0,0 +1,76 @@
/* 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 <hoedown/html.h>
#include <hoedown/document.h>
#include <QString>
#include <QByteArray>
/**
* hoedown wrapper, because dealing with resource lifetime in C is stupid
*/
class HoeDown
{
public:
class buffer
{
public:
buffer(size_t unit = 4096)
{
buf = hoedown_buffer_new(unit);
}
~buffer()
{
hoedown_buffer_free(buf);
}
const char * cstr()
{
return hoedown_buffer_cstr(buf);
}
void put(QByteArray input)
{
hoedown_buffer_put(buf, reinterpret_cast<uint8_t *>(input.data()), input.size());
}
const uint8_t * data() const
{
return buf->data;
}
size_t size() const
{
return buf->size;
}
hoedown_buffer * buf;
} ib, ob;
HoeDown()
{
renderer = hoedown_html_renderer_new((hoedown_html_flags) 0,0);
document = hoedown_document_new(renderer, (hoedown_extensions) 0, 8);
}
~HoeDown()
{
hoedown_document_free(document);
hoedown_html_renderer_free(renderer);
}
QString process(QByteArray input)
{
ib.put(input);
hoedown_document_render(document, ob.buf, ib.data(), ib.size());
return ob.cstr();
}
private:
hoedown_document * document;
hoedown_renderer * renderer;
};

View File

@ -1,192 +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
{
return getSelectedFiltersAsRegex({});
}
QString InstanceCopyPrefs::getSelectedFiltersAsRegex(const QStringList& additionalFilters) 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";
for (auto filter : additionalFilters) {
filters << filter;
}
// 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;
}
bool InstanceCopyPrefs::isUseSymLinksEnabled() const
{
return useSymLinks;
}
bool InstanceCopyPrefs::isUseHardLinksEnabled() const
{
return useHardLinks;
}
bool InstanceCopyPrefs::isLinkRecursivelyEnabled() const
{
return linkRecursively;
}
bool InstanceCopyPrefs::isDontLinkSavesEnabled() const
{
return dontLinkSaves;
}
bool InstanceCopyPrefs::isUseCloneEnabled() const
{
return useClone;
}
// ======= 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;
}
void InstanceCopyPrefs::enableUseSymLinks(bool b)
{
useSymLinks = b;
}
void InstanceCopyPrefs::enableLinkRecursively(bool b)
{
linkRecursively = b;
}
void InstanceCopyPrefs::enableUseHardLinks(bool b)
{
useHardLinks = b;
}
void InstanceCopyPrefs::enableDontLinkSaves(bool b)
{
dontLinkSaves = b;
}
void InstanceCopyPrefs::enableUseClone(bool b)
{
useClone = b;
}

View File

@ -1,57 +0,0 @@
//
// Created by marcelohdez on 10/22/22.
//
#pragma once
#include <QStringList>
struct InstanceCopyPrefs {
public:
[[nodiscard]] bool allTrue() const;
[[nodiscard]] QString getSelectedFiltersAsRegex() const;
[[nodiscard]] QString getSelectedFiltersAsRegex(const QStringList& additionalFilters) 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;
[[nodiscard]] bool isUseSymLinksEnabled() const;
[[nodiscard]] bool isLinkRecursivelyEnabled() const;
[[nodiscard]] bool isUseHardLinksEnabled() const;
[[nodiscard]] bool isDontLinkSavesEnabled() const;
[[nodiscard]] bool isUseCloneEnabled() 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);
void enableUseSymLinks(bool b);
void enableLinkRecursively(bool b);
void enableUseHardLinks(bool b);
void enableDontLinkSaves(bool b);
void enableUseClone(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;
bool useSymLinks = false;
bool linkRecursively = false;
bool useHardLinks = false;
bool dontLinkSaves = false;
bool useClone = false;
};

View File

@ -1,34 +1,19 @@
#include "InstanceCopyTask.h"
#include <QDebug>
#include <QtConcurrentRun>
#include "settings/INISettingsObject.h"
#include "FileSystem.h"
#include "NullInstance.h"
#include "pathmatcher/RegexpMatcher.h"
#include "settings/INISettingsObject.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_useLinks = prefs.isUseSymLinksEnabled();
m_linkRecursively = prefs.isLinkRecursivelyEnabled();
m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled();
m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled();
m_useClone = prefs.isUseCloneEnabled();
m_keepPlaytime = keepPlaytime;
QString filters = prefs.getSelectedFiltersAsRegex();
if (m_useLinks || m_useHardLinks) {
if (!filters.isEmpty())
filters += "|";
filters += "instance.cfg";
}
qDebug() << "CopyFilters:" << filters;
if (!filters.isEmpty()) {
// Set regex filter:
if(!copySaves)
{
// 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);
}
@ -38,88 +23,10 @@ void InstanceCopyTask::executeTask()
{
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
auto copySaves = [&]() {
QFileInfo mcDir(FS::PathCombine(m_stagingPath, "minecraft"));
QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft"));
QString staging_mc_dir;
if (mcDir.exists() && !dotMCDir.exists())
staging_mc_dir = mcDir.filePath();
else
staging_mc_dir = dotMCDir.filePath();
FS::copy savesCopy(FS::PathCombine(m_origInstance->gameRoot(), "saves"), FS::PathCombine(staging_mc_dir, "saves"));
savesCopy.followSymlinks(true);
return savesCopy();
};
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves] {
if (m_useClone) {
FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath);
folderClone.matcher(m_matcher.get());
return folderClone();
} else if (m_useLinks || m_useHardLinks) {
FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder
folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
bool there_were_errors = false;
if (!folderLink()) {
#if defined Q_OS_WIN32
if (!m_useHardLinks) {
qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
qDebug() << "attempting to run with privelage";
QEventLoop loop;
bool got_priv_results = false;
connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults) {
if (!gotResults) {
qDebug() << "Privileged run exited without results!";
}
got_priv_results = gotResults;
loop.quit();
});
folderLink.runPrivileged();
loop.exec(); // wait for the finished signal
for (auto result : folderLink.getResults()) {
if (result.err_value != 0) {
there_were_errors = true;
}
}
if (m_copySaves) {
there_were_errors |= !copySaves();
}
return got_priv_results && !there_were_errors;
} else {
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
}
#else
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
#endif
return false;
}
if (m_copySaves) {
there_were_errors |= !copySaves();
}
return !there_were_errors;
} else {
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
folderCopy.followSymlinks(false).matcher(m_matcher.get());
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);
@ -128,41 +35,20 @@ void InstanceCopyTask::executeTask()
void InstanceCopyTask::copyFinished()
{
auto successful = m_copyFuture.result();
if (!successful) {
if(!successful)
{
emitFailed(tr("Instance folder copy failed."));
return;
}
// FIXME: shouldn't this be able to report errors?
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->setIconKey(m_instIcon);
if (!m_keepPlaytime) {
if(!m_keepPlaytime) {
inst->resetTimePlayed();
}
if (m_useLinks)
inst->addLinkedInstanceId(m_origInstance->id());
if (m_useLinks) {
auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt"));
QByteArray allowed_symlinks;
if (allowed_symlinks_file.exists()) {
allowed_symlinks.append(FS::read(allowed_symlinks_file.filePath()));
if (allowed_symlinks.right(1) != "\n")
allowed_symlinks.append("\n"); // we want to be on a new line
}
allowed_symlinks.append(m_origInstance->gameRoot().toUtf8());
allowed_symlinks.append("\n");
if (allowed_symlinks_file.isSymLink())
FS::deletePath(
allowed_symlinks_file
.filePath()); // we dont want to modify the original. also make sure the resulting file is not itself a link.
FS::write(allowed_symlinks_file.filePath(), allowed_symlinks);
}
emitSucceeded();
}

View File

@ -1,37 +1,31 @@
#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 {
class InstanceCopyTask : public InstanceTask
{
Q_OBJECT
public:
explicit InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs);
public:
explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime);
protected:
protected:
//! Entry point for tasks.
virtual void executeTask() override;
void copyFinished();
void copyAborted();
private:
/* data */
private: /* data */
InstancePtr m_origInstance;
QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher;
std::unique_ptr<IPathMatcher> m_matcher;
bool m_keepPlaytime;
bool m_useLinks = false;
bool m_useHardLinks = false;
bool m_copySaves = false;
bool m_linkRecursively = false;
bool m_useClone = false;
};

View File

@ -25,13 +25,9 @@ void InstanceCreationTask::executeTask()
return;
qWarning() << "Instance creation failed!";
if (!m_error_message.isEmpty()) {
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;
}

View File

@ -34,7 +34,7 @@ class InstanceCreationTask : public InstanceTask {
QString getError() const { return m_error_message; }
protected:
void setError(const QString& message) { m_error_message = message; };
void setError(QString message) { m_error_message = message; };
protected:
bool m_abort = false;

View File

@ -1,6 +1,6 @@
// 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 flowln <flowlnlnln@gmail.com>
*
@ -41,27 +41,25 @@
#include "MMCZip.h"
#include "NullInstance.h"
#include "QObjectPtr.h"
#include "icons/IconList.h"
#include "icons/IconUtils.h"
#include "modplatform/flame/FlameInstanceCreationTask.h"
#include "modplatform/modrinth/ModrinthInstanceCreationTask.h"
#include "modplatform/technic/TechnicPackProcessor.h"
#include "modplatform/modrinth/ModrinthInstanceCreationTask.h"
#include "modplatform/flame/FlameInstanceCreationTask.h"
#include "settings/INISettingsObject.h"
#include "tasks/Task.h"
#include "net/ApiDownload.h"
#include <QtConcurrentRun>
#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()
{
@ -70,12 +68,7 @@ bool InstanceImportTask::abort()
if (m_filesNetJob)
m_filesNetJob->abort();
if (m_extractFuture.isRunning()) {
// NOTE: The tasks created by QtConcurrent::run() can't actually get cancelled,
// but we can use this call to check the state when the extraction finishes.
m_extractFuture.cancel();
m_extractFuture.waitForFinished();
}
return Task::abort();
}
@ -91,25 +84,22 @@ void InstanceImportTask::executeTask()
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
m_downloadRequired = true;
downloadFromUrl();
}
}
const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path());
void InstanceImportTask::downloadFromUrl()
{
const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
entry->setStale(true);
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
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::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
m_filesNetJob->start();
}
}
void InstanceImportTask::downloadSucceeded()
@ -143,7 +133,8 @@ void InstanceImportTask::processZipPack()
// open the zip and find relevant files in it
m_packZip.reset(new QuaZip(m_archivePath));
if (!m_packZip->open(QuaZip::mdUnzip)) {
if (!m_packZip->open(QuaZip::mdUnzip))
{
emitFailed(tr("Unable to open supplied modpack zip file."));
return;
}
@ -157,81 +148,96 @@ void InstanceImportTask::processZipPack()
// NOTE: Prioritize modpack platforms that aren't searched for recursively.
// Especially Flame has a very common filename for its manifest, which may appear inside overrides for example
if (modrinthFound) {
if(modrinthFound)
{
// process as Modrinth pack
qDebug() << "Modrinth:" << modrinthFound;
m_modpackType = ModpackType::Modrinth;
} else if (technicFound) {
}
else if (technicFound)
{
// process as Technic pack
qDebug() << "Technic:" << technicFound;
extractDir.mkpath(".minecraft");
extractDir.cd(".minecraft");
m_modpackType = ModpackType::Technic;
} else {
QStringList paths_to_ignore{ "overrides/" };
}
else
{
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;
m_modpackType = ModpackType::Flame;
}
}
if (m_modpackType == ModpackType::Unknown) {
if(m_modpackType == ModpackType::Unknown)
{
emitFailed(tr("Archive does not contain a recognized modpack type."));
return;
}
// make sure we extract just the pack
m_extractFuture =
QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath());
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath());
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished);
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &InstanceImportTask::extractAborted);
m_extractFutureWatcher.setFuture(m_extractFuture);
}
void InstanceImportTask::extractFinished()
{
m_packZip.reset();
if (m_extractFuture.isCanceled())
return;
if (!m_extractFuture.result().has_value()) {
if (!m_extractFuture.result())
{
emitFailed(tr("Failed to extract modpack"));
return;
}
QDir extractDir(m_stagingPath);
qDebug() << "Fixing permissions for extracted pack files...";
QDirIterator it(extractDir, QDirIterator::Subdirectories);
while (it.hasNext()) {
while (it.hasNext())
{
auto filepath = it.next();
QFileInfo file(filepath);
auto permissions = QFile::permissions(filepath);
auto origPermissions = permissions;
if (file.isDir()) {
if(file.isDir())
{
// Folder +rwx for current user
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser;
} else {
}
else
{
// File +rw for current user
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
}
if (origPermissions != permissions) {
if (!QFile::setPermissions(filepath, permissions)) {
if(origPermissions != permissions)
{
if(!QFile::setPermissions(filepath, permissions))
{
logWarning(tr("Could not fix permissions for %1").arg(filepath));
} else {
}
else
{
qDebug() << "Fixed" << filepath;
}
}
}
switch (m_modpackType) {
switch(m_modpackType)
{
case ModpackType::MultiMC:
processMultiMC();
return;
@ -250,55 +256,38 @@ void InstanceImportTask::extractFinished()
}
}
void InstanceImportTask::extractAborted()
{
emitAborted();
}
void InstanceImportTask::processFlame()
{
shared_qobject_ptr<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 =
makeShared<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 = makeShared<FlameCreationTask>(m_stagingPath, m_globalSettings, m_parent, QString(), QString());
}
auto* inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent);
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.get(), &Task::succeeded, this, [this, inst_creation_task] {
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
setOverride(inst_creation_task->shouldOverride());
emitSucceeded();
});
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress);
connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
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.get(), &InstanceCreationTask::abort);
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
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();
}
void InstanceImportTask::processTechnic()
{
shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor{ new Technic::TechnicPackProcessor };
shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor();
connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &InstanceImportTask::emitSucceeded);
connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &InstanceImportTask::emitFailed);
packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath);
@ -338,49 +327,19 @@ void InstanceImportTask::processMultiMC()
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();
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();
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 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);
}
// 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* inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, m_sourceUrl.toString());
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());
setOverride(inst_creation_task->shouldOverride());
emitSucceeded();
});
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails);
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort);

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -35,49 +35,55 @@
#pragma once
#include "InstanceTask.h"
#include "net/NetJob.h"
#include <QUrl>
#include <QFuture>
#include <QFutureWatcher>
#include <QUrl>
#include "InstanceTask.h"
#include "settings/SettingsObject.h"
#include "QObjectPtr.h"
#include "modplatform/flame/PackManifest.h"
#include "net/NetJob.h"
#include "settings/SettingsObject.h"
#include <optional>
class QuaZip;
namespace Flame {
class FileResolvingTask;
namespace Flame
{
class FileResolvingTask;
}
class InstanceImportTask : public InstanceTask {
class InstanceImportTask : public InstanceTask
{
Q_OBJECT
public:
explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
public:
explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr);
bool abort() override;
const QVector<Flame::File>& getBlockedFiles() const { return m_blockedMods; }
const QVector<Flame::File> &getBlockedFiles() const
{
return m_blockedMods;
}
protected:
protected:
//! Entry point for tasks.
virtual void executeTask() override;
private:
private:
void processZipPack();
void processMultiMC();
void processTechnic();
void processFlame();
void processModrinth();
private slots:
private slots:
void downloadSucceeded();
void downloadFailed(QString reason);
void downloadProgressChanged(qint64 current, qint64 total);
void downloadAborted();
void extractFinished();
void extractAborted();
private: /* data */
private: /* data */
NetJob::Ptr m_filesNetJob;
shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver;
QUrl m_sourceUrl;
@ -87,7 +93,7 @@ class InstanceImportTask : public InstanceTask {
QFuture<std::optional<QStringList>> m_extractFuture;
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
QVector<Flame::File> m_blockedMods;
enum class ModpackType {
enum class ModpackType{
Unknown,
MultiMC,
Technic,
@ -95,11 +101,6 @@ class InstanceImportTask : public InstanceTask {
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
//FIXME: nuke
QWidget* m_parent;
void downloadFromUrl();
};

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) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -42,9 +41,9 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QMimeData>
#include <QPair>
#include <QSet>
#include <QStack>
#include <QPair>
#include <QTextStream>
#include <QThread>
#include <QTimer>
@ -97,11 +96,7 @@ Qt::DropActions InstanceList::supportedDropActions() const
return Qt::MoveAction;
}
bool InstanceList::canDropMimeData(const QMimeData* data,
[[maybe_unused]] Qt::DropAction action,
[[maybe_unused]] int row,
[[maybe_unused]] int column,
[[maybe_unused]] const QModelIndex& parent) const
bool InstanceList::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
{
if (data && data->hasFormat("application/x-instanceid")) {
return true;
@ -109,11 +104,7 @@ bool InstanceList::canDropMimeData(const QMimeData* data,
return false;
}
bool InstanceList::dropMimeData(const QMimeData* data,
[[maybe_unused]] Qt::DropAction action,
[[maybe_unused]] int row,
[[maybe_unused]] int column,
[[maybe_unused]] const QModelIndex& parent)
bool InstanceList::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
{
if (data && data->hasFormat("application/x-instanceid")) {
return true;
@ -138,16 +129,6 @@ QMimeData* InstanceList::mimeData(const QModelIndexList& indexes) const
return mimeData;
}
QStringList InstanceList::getLinkedInstancesById(const QString& id) const
{
QStringList linkedInstances;
for (auto inst : m_instances) {
if (inst->isLinkedToInstanceId(id))
linkedInstances.append(inst->id());
}
return linkedInstances;
}
int InstanceList::rowCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
@ -167,30 +148,38 @@ QVariant InstanceList::data(const QModelIndex& index, int role) const
if (!index.isValid()) {
return QVariant();
}
BaseInstance* pdata = static_cast<BaseInstance*>(index.internalPointer());
switch (role) {
case InstancePointerRole: {
QVariant v = QVariant::fromValue((void*)pdata);
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
switch (role)
{
case InstancePointerRole:
{
QVariant v = QVariant::fromValue((void *)pdata);
return v;
}
case InstanceIDRole: {
case InstanceIDRole:
{
return pdata->id();
}
case Qt::EditRole:
case Qt::DisplayRole: {
case Qt::DisplayRole:
{
return pdata->name();
}
case Qt::AccessibleTextRole: {
case Qt::AccessibleTextRole:
{
return tr("%1 Instance").arg(pdata->name());
}
case Qt::ToolTipRole: {
case Qt::ToolTipRole:
{
return pdata->instanceRoot();
}
case Qt::DecorationRole: {
case Qt::DecorationRole:
{
return pdata->iconKey();
}
// HACK: see InstanceView.h in gui!
case GroupRole: {
case GroupRole:
{
return getInstanceGroup(pdata->id());
}
default:
@ -238,11 +227,8 @@ GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
return GroupId();
}
void InstanceList::setInstanceGroup(const InstanceId& id, GroupId name)
void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
{
if (name.isEmpty() && !name.isNull())
name = QString();
auto inst = getInstanceById(id);
if (!inst) {
qDebug() << "Attempt to set a null instance's group";
@ -253,7 +239,6 @@ void InstanceList::setInstanceGroup(const InstanceId& id, GroupId name)
auto iter = m_instanceGroupIndex.find(inst->id());
if (iter != m_instanceGroupIndex.end()) {
if (*iter != name) {
decreaseGroupCount(*iter);
*iter = name;
changed = true;
}
@ -263,7 +248,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, GroupId name)
}
if (changed) {
increaseGroupCount(name);
m_groupNameCache.insert(name);
auto idx = getInstIndex(inst.get());
emit dataChanged(index(idx), index(idx), { GroupRole });
saveGroupList();
@ -272,55 +257,29 @@ void InstanceList::setInstanceGroup(const InstanceId& id, GroupId name)
QStringList InstanceList::getGroups()
{
return m_groupNameCache.keys();
return m_groupNameCache.values();
}
void InstanceList::deleteGroup(const GroupId& name)
void InstanceList::deleteGroup(const QString& name)
{
m_groupNameCache.remove(name);
m_collapsedGroups.remove(name);
bool removed = false;
qDebug() << "Delete group" << name;
for (auto& instance : m_instances) {
const QString& instID = instance->id();
const QString instGroupName = getInstanceGroup(instID);
const auto& instID = instance->id();
auto instGroupName = getInstanceGroup(instID);
if (instGroupName == name) {
m_instanceGroupIndex.remove(instID);
qDebug() << "Remove" << instID << "from group" << name;
removed = true;
auto idx = getInstIndex(instance.get());
if (idx >= 0)
if (idx > 0) {
emit dataChanged(index(idx), index(idx), { GroupRole });
}
}
if (removed)
saveGroupList();
}
void InstanceList::renameGroup(const QString& src, const QString& dst)
{
m_groupNameCache.remove(src);
if (m_collapsedGroups.remove(src))
m_collapsedGroups.insert(dst);
bool modified = false;
qDebug() << "Rename group" << src << "to" << dst;
for (auto& instance : m_instances) {
const QString& instID = instance->id();
const QString instGroupName = getInstanceGroup(instID);
if (instGroupName == src) {
m_instanceGroupIndex[instID] = dst;
increaseGroupCount(dst);
qDebug() << "Set" << instID << "group to" << dst;
modified = true;
auto idx = getInstIndex(instance.get());
if (idx >= 0)
emit dataChanged(index(idx), index(idx), { GroupRole });
}
}
if (modified)
if (removed) {
saveGroupList();
}
}
bool InstanceList::isGroupCollapsed(const QString& group)
@ -336,13 +295,12 @@ bool InstanceList::trashInstance(const InstanceId& id)
return false;
}
QString cachedGroupId = m_instanceGroupIndex[id];
auto cachedGroupId = m_instanceGroupIndex[id];
qDebug() << "Will trash instance" << id;
QString trashedLoc;
if (m_instanceGroupIndex.remove(id)) {
decreaseGroupCount(cachedGroupId);
saveGroupList();
}
@ -352,18 +310,16 @@ bool InstanceList::trashInstance(const InstanceId& id)
}
qDebug() << "Instance" << id << "has been trashed by the launcher.";
m_trashHistory.push({ id, inst->instanceRoot(), trashedLoc, cachedGroupId });
m_trashHistory.push({id, inst->instanceRoot(), trashedLoc, cachedGroupId});
return true;
}
bool InstanceList::trashedSomething()
{
bool InstanceList::trashedSomething() {
return !m_trashHistory.empty();
}
void InstanceList::undoTrashInstance()
{
void InstanceList::undoTrashInstance() {
if (m_trashHistory.empty()) {
qWarning() << "Nothing to recover from trash.";
return;
@ -380,7 +336,7 @@ void InstanceList::undoTrashInstance()
QFile(top.trashPath).rename(top.polyPath);
m_instanceGroupIndex[top.id] = top.groupName;
increaseGroupCount(top.groupName);
m_groupNameCache.insert(top.groupName);
saveGroupList();
emit instancesChanged();
@ -394,10 +350,7 @@ void InstanceList::deleteInstance(const InstanceId& id)
return;
}
QString cachedGroupId = m_instanceGroupIndex[id];
if (m_instanceGroupIndex.remove(id)) {
decreaseGroupCount(cachedGroupId);
saveGroupList();
}
@ -595,7 +548,7 @@ InstancePtr InstanceList::getInstanceByManagedName(const QString& managed_name)
return {};
}
QModelIndex InstanceList::getInstanceIndexById(const QString& id) const
QModelIndex InstanceList::getInstanceIndexById(const QString &id) const
{
return index(getInstIndex(getInstanceById(id).get()));
}
@ -634,36 +587,19 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
QString inst_type = instanceSettings->get("InstanceType").toString();
// NOTE: Some PolyMC versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a OneSix
// instance
if (inst_type == "OneSix" || inst_type.isEmpty()) {
// NOTE: Some PolyMC versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a OneSix instance
if (inst_type == "OneSix" || inst_type.isEmpty())
{
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
} else {
}
else
{
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
}
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
return inst;
}
void InstanceList::increaseGroupCount(const QString& group)
{
if (group.isEmpty())
return;
++m_groupNameCache[group];
}
void InstanceList::decreaseGroupCount(const QString& group)
{
if (group.isEmpty())
return;
if (--m_groupNameCache[group] < 1) {
m_groupNameCache.remove(group);
m_collapsedGroups.remove(group);
}
}
void InstanceList::saveGroupList()
{
qDebug() << "Will save group list now.";
@ -675,7 +611,7 @@ void InstanceList::saveGroupList()
QString groupFileName = m_instDir + "/instgroups.json";
QMap<QString, QSet<QString>> reverseGroupMap;
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) {
const QString& id = iter.key();
QString id = iter.key();
QString group = iter.value();
if (group.isEmpty())
continue;
@ -765,22 +701,17 @@ void InstanceList::loadGroupList()
return;
}
QSet<QString> groupSet;
m_instanceGroupIndex.clear();
m_groupNameCache.clear();
// Iterate through all the groups.
QJsonObject groupMapping = rootObj.value("groups").toObject();
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) {
QString groupName = iter.key();
if (iter.key().isEmpty()) {
qWarning() << "Redundant empty group found";
continue;
}
// If not an object, complain and skip to the next one.
if (!iter.value().isObject()) {
qWarning() << QString("Group '%1' in the group list should be an object").arg(groupName).toUtf8();
qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
continue;
}
@ -792,19 +723,23 @@ void InstanceList::loadGroupList()
continue;
}
// keep a list/set of groups for choosing
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 (auto value : instancesArray) {
m_instanceGroupIndex[value.toString()] = groupName;
increaseGroupCount(groupName);
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++) {
m_instanceGroupIndex[(*iter2).toString()] = groupName;
}
}
m_groupsLoaded = true;
m_groupNameCache.unite(groupSet);
qDebug() << "Group list loaded.";
}
@ -814,7 +749,7 @@ void InstanceList::instanceDirContentsChanged(const QString& path)
emit instancesChanged();
}
void InstanceList::on_InstFolderChanged([[maybe_unused]] const Setting& setting, QVariant value)
void InstanceList::on_InstFolderChanged(const Setting& setting, QVariant value)
{
QString newInstDir = QDir(value.toString()).canonicalPath();
if (newInstDir != m_instDir) {
@ -842,25 +777,18 @@ 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))
: m_parent(parent), backoff(minBackoff, maxBackoff), m_stagingPath(std::move(stagingPath)), m_instance_name(std::move(instanceName)), m_groupName(std::move(groupName))
{
m_child.reset(child);
connect(child, &Task::succeeded, this, &InstanceStaging::childSucceeded);
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::details, this, &InstanceStaging::setDetails);
connect(child, &Task::progress, this, &InstanceStaging::setProgress);
connect(child, &Task::stepProgress, this, &InstanceStaging::propagateStepProgress);
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceeded);
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
}
virtual ~InstanceStaging(){};
@ -875,17 +803,21 @@ class InstanceStaging : public Task {
return Task::abort();
}
bool canAbort() const override { return (m_child && m_child->canAbort()); }
bool canAbort() const override
{
return (m_child && m_child->canAbort());
}
protected:
virtual void executeTask() override { m_child->start(); }
QStringList warnings() const override { return m_child->warnings(); }
private slots:
void childSucceeded()
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_instance_name, m_groupName, m_child->shouldOverride()))
{
emitSucceeded();
return;
}
@ -903,10 +835,13 @@ class InstanceStaging : public Task {
emitFailed(reason);
}
void childAborted() { emitAborted(); }
void childAborted()
{
emitAborted();
}
private:
InstanceList* m_parent;
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
@ -930,7 +865,7 @@ Task* InstanceList::wrapInstanceTask(InstanceTask* task)
QString InstanceList::getStagedInstancePath()
{
QString key = QUuid::createUuid().toString(QUuid::WithoutBraces);
QString key = QUuid::createUuid().toString();
QString tempDir = ".LAUNCHER_TEMP/";
QString relPath = FS::PathCombine(tempDir, key);
QDir rootPath(m_instDir);
@ -945,28 +880,25 @@ QString InstanceList::getStagedInstancePath()
return path;
}
bool InstanceList::commitStagedInstance(const QString& path,
InstanceName const& instanceName,
QString groupName,
InstanceTask const& commiting)
bool InstanceList::commitStagedInstance(const QString& path, InstanceName const& instanceName, const QString& groupName, bool should_override)
{
if (groupName.isEmpty() && !groupName.isNull())
groupName = QString();
QDir dir;
QString instID;
InstancePtr inst;
auto should_override = commiting.shouldOverride();
if (should_override) {
instID = commiting.originalInstanceID();
// This is to avoid problems when the instance folder gets manually renamed
if ((inst = getInstanceByManagedName(instanceName.originalName()))) {
instID = QFileInfo(inst->instanceRoot()).fileName();
} else if ((inst = getInstanceByManagedName(instanceName.modifiedName()))) {
instID = QFileInfo(inst->instanceRoot()).fileName();
} else {
instID = FS::RemoveInvalidFilenameChars(instanceName.modifiedName(), '-');
}
} else {
instID = FS::DirNameFromString(instanceName.modifiedName(), m_instDir);
}
Q_ASSERT(!instID.isEmpty());
{
WatchLock lock(m_watcher, m_instDir);
QString destination = FS::PathCombine(m_instDir, instID);
@ -983,7 +915,7 @@ bool InstanceList::commitStagedInstance(const QString& path,
}
m_instanceGroupIndex[instID] = groupName;
increaseGroupCount(groupName);
m_groupNameCache.insert(groupName);
}
instanceSet.insert(instID);

View File

@ -1,24 +1,4 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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
/* 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.
@ -35,12 +15,12 @@
#pragma once
#include <QAbstractListModel>
#include <QList>
#include <QObject>
#include <QPair>
#include <QAbstractListModel>
#include <QSet>
#include <QList>
#include <QStack>
#include <QPair>
#include "BaseInstance.h"
@ -52,9 +32,21 @@ using InstanceId = QString;
using GroupId = QString;
using InstanceLocator = std::pair<InstancePtr, int>;
enum class InstCreateError { NoCreateError = 0, NoSuchVersion, UnknownCreateError, InstExists, CantCreateDir };
enum class InstCreateError
{
NoCreateError = 0,
NoSuchVersion,
UnknownCreateError,
InstExists,
CantCreateDir
};
enum class GroupsState { NotLoaded, Steady, Dirty };
enum class GroupsState
{
NotLoaded,
Steady,
Dirty
};
struct TrashHistoryItem {
QString id;
@ -63,22 +55,24 @@ struct TrashHistoryItem {
QString groupName;
};
class InstanceList : public QAbstractListModel {
class InstanceList : public QAbstractListModel
{
Q_OBJECT
public:
explicit InstanceList(SettingsObjectPtr settings, const QString& instDir, QObject* parent = 0);
public:
explicit InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent = 0);
virtual ~InstanceList();
public:
QModelIndex index(int row, int column = 0, const QModelIndex& parent = QModelIndex()) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
public:
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex& index, const QVariant& value, int role) override;
bool setData(const QModelIndex & index, const QVariant & value, int role) override;
enum AdditionalRoles {
enum AdditionalRoles
{
GroupRole = Qt::UserRole,
InstancePointerRole = 0x34B1CB48, ///< Return pointer to real instance
InstanceIDRole = 0x34B1CB49 ///< Return id if the instance
@ -88,11 +82,21 @@ class InstanceList : public QAbstractListModel {
* NoError Indicates that no error occurred.
* UnknownError indicates that an unspecified error occurred.
*/
enum InstListError { NoError = 0, UnknownError };
enum InstListError
{
NoError = 0,
UnknownError
};
InstancePtr at(int i) const { return m_instances.at(i); }
InstancePtr at(int i) const
{
return m_instances.at(i);
}
int count() const { return m_instances.count(); }
int count() const
{
return m_instances.count();
}
InstListError loadList();
void saveNow();
@ -101,22 +105,21 @@ class InstanceList : public QAbstractListModel {
InstancePtr getInstanceById(QString id) const;
/* O(n) */
InstancePtr getInstanceByManagedName(const QString& managed_name) const;
QModelIndex getInstanceIndexById(const QString& id) const;
QModelIndex getInstanceIndexById(const QString &id) const;
QStringList getGroups();
bool isGroupCollapsed(const QString& groupName);
bool isGroupCollapsed(const QString &groupName);
GroupId getInstanceGroup(const InstanceId& id) const;
void setInstanceGroup(const InstanceId& id, GroupId name);
GroupId getInstanceGroup(const InstanceId & id) const;
void setInstanceGroup(const InstanceId & id, const GroupId& name);
void deleteGroup(const GroupId& name);
void renameGroup(const GroupId& src, const GroupId& dst);
bool trashInstance(const InstanceId& id);
void deleteGroup(const GroupId & name);
bool trashInstance(const InstanceId &id);
bool trashedSomething();
void undoTrashInstance();
void deleteInstance(const InstanceId& id);
void deleteInstance(const InstanceId & id);
// Wrap an instance creation task in some more task machinery and make it ready to be used
Task* wrapInstanceTask(InstanceTask* task);
Task * wrapInstanceTask(InstanceTask * task);
/**
* Create a new empty staging area for instance creation and @return a path/key top commit it later.
@ -130,13 +133,13 @@ class InstanceList : public QAbstractListModel {
* 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, QString groupName, const InstanceTask&);
bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, const QString& groupName, bool should_override);
/**
* Destroy a previously created staging area given by @keyPath - used when creation fails.
* Used by instance manipulation tasks.
*/
bool destroyStagingPath(const QString& keyPath);
bool destroyStagingPath(const QString & keyPath);
int getTotalPlayTime();
@ -144,55 +147,49 @@ class InstanceList : public QAbstractListModel {
Qt::DropActions supportedDropActions() const override;
bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override;
bool canDropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) const override;
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override;
bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override;
QStringList mimeTypes() const override;
QMimeData* mimeData(const QModelIndexList& indexes) const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
QStringList getLinkedInstancesById(const QString& id) const;
signals:
signals:
void dataIsInvalid();
void instancesChanged();
void instanceSelectRequest(QString instanceId);
void groupsChanged(QSet<QString> groups);
public slots:
void on_InstFolderChanged(const Setting& setting, QVariant value);
void on_GroupStateChanged(const QString& group, bool collapsed);
public slots:
void on_InstFolderChanged(const Setting &setting, QVariant value);
void on_GroupStateChanged(const QString &group, bool collapsed);
private slots:
void propertiesChanged(BaseInstance* inst);
private slots:
void propertiesChanged(BaseInstance *inst);
void providerUpdated();
void instanceDirContentsChanged(const QString& path);
void instanceDirContentsChanged(const QString &path);
private:
int getInstIndex(BaseInstance* inst) const;
private:
int getInstIndex(BaseInstance *inst) const;
void updateTotalPlayTime();
void suspendWatch();
void resumeWatch();
void add(const QList<InstancePtr>& list);
void add(const QList<InstancePtr> &list);
void loadGroupList();
void saveGroupList();
QList<InstanceId> discoverInstances();
InstancePtr loadInstance(const InstanceId& id);
void increaseGroupCount(const QString& group);
void decreaseGroupCount(const QString& group);
private:
private:
int m_watchLevel = 0;
int totalPlayTime = 0;
bool m_dirty = false;
QList<InstancePtr> m_instances;
// id -> refs
QMap<QString, int> m_groupNameCache;
QSet<QString> m_groupNameCache;
SettingsObjectPtr m_globalSettings;
QString m_instDir;
QFileSystemWatcher* m_watcher;
QFileSystemWatcher * m_watcher;
// FIXME: this is so inefficient that looking at it is almost painful.
QSet<QString> m_collapsedGroups;
QMap<InstanceId, GroupId> m_instanceGroupIndex;

View File

@ -1,40 +1,42 @@
#pragma once
#include <FileSystem.h>
#include "minecraft/MinecraftInstance.h"
#include <FileSystem.h>
#include "ui/pages/BasePage.h"
#include "ui/pages/BasePageProvider.h"
#include "ui/pages/instance/InstanceSettingsPage.h"
#include "ui/pages/instance/LogPage.h"
#include "ui/pages/instance/ManagedPackPage.h"
#include "ui/pages/instance/ModFolderPage.h"
#include "ui/pages/instance/NotesPage.h"
#include "ui/pages/instance/OtherLogsPage.h"
#include "ui/pages/instance/ResourcePackPage.h"
#include "ui/pages/instance/ScreenshotsPage.h"
#include "ui/pages/instance/ServersPage.h"
#include "ui/pages/instance/ShaderPackPage.h"
#include "ui/pages/instance/TexturePackPage.h"
#include "ui/pages/instance/VersionPage.h"
#include "ui/pages/instance/ModFolderPage.h"
#include "ui/pages/instance/ResourcePackPage.h"
#include "ui/pages/instance/TexturePackPage.h"
#include "ui/pages/instance/ShaderPackPage.h"
#include "ui/pages/instance/NotesPage.h"
#include "ui/pages/instance/ScreenshotsPage.h"
#include "ui/pages/instance/InstanceSettingsPage.h"
#include "ui/pages/instance/OtherLogsPage.h"
#include "ui/pages/instance/WorldListPage.h"
#include "ui/pages/instance/ServersPage.h"
#include "ui/pages/instance/GameOptionsPage.h"
class InstancePageProvider : protected QObject, public BasePageProvider {
class InstancePageProvider : public QObject, public BasePageProvider
{
Q_OBJECT
public:
explicit InstancePageProvider(InstancePtr parent) { inst = parent; }
virtual ~InstancePageProvider(){};
virtual QList<BasePage*> getPages() override
public:
explicit InstancePageProvider(InstancePtr parent)
{
QList<BasePage*> values;
inst = parent;
}
virtual ~InstancePageProvider() {};
virtual QList<BasePage *> getPages() override
{
QList<BasePage *> values;
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 *.nilmod)");
modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
values.append(modsPage);
values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList()));
values.append(new NilModFolderPage(onesix.get(), onesix->nilModList()));
values.append(new ResourcePackPage(onesix.get(), onesix->resourcePackList()));
values.append(new TexturePackPage(onesix.get(), onesix->texturePackList()));
values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList()));
@ -45,14 +47,18 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
values.append(new InstanceSettingsPage(onesix.get()));
auto logMatcher = inst->getLogFileMatcher();
if (logMatcher) {
if(logMatcher)
{
values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher));
}
return values;
}
virtual QString dialogTitle() override { return tr("Edit Instance (%1)").arg(inst->name()); }
protected:
virtual QString dialogTitle() override
{
return tr("Edit Instance (%1)").arg(inst->name());
}
protected:
InstancePtr inst;
};

View File

@ -18,37 +18,11 @@ InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& ol
return InstanceNameChange::ShouldKeep;
}
ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name)
{
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();
if (!m_original_version.isEmpty())
return QString("%1 %2").arg(m_original_name, m_original_version);
return m_original_name;
}
QString InstanceName::originalName() const

View File

@ -6,8 +6,6 @@
/* 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:
@ -44,20 +42,10 @@ class InstanceTask : public Task, public InstanceName {
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 = {})
{
m_override_existing = override;
if (!instance_id_to_override.isEmpty())
m_original_instance_id = instance_id_to_override;
}
void setOverride(bool override) { m_override_existing = override; }
protected: /* data */
SettingsObjectPtr m_globalSettings;
@ -66,7 +54,4 @@ class InstanceTask : public Task, public InstanceName {
QString m_stagingPath;
bool m_override_existing = false;
bool m_confirm_update = true;
QString m_original_instance_id;
};

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -39,39 +39,43 @@
#include <QRegularExpression>
bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent)
bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent)
{
if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegularExpression("-Xm[sx]")) || jvmargs.contains("-XX-MaxHeapSize") ||
jvmargs.contains("-XX:InitialHeapSize")) {
if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegularExpression("-Xm[sx]"))
|| jvmargs.contains("-XX-MaxHeapSize") || jvmargs.contains("-XX:InitialHeapSize"))
{
auto warnStr = QObject::tr(
"You tried to manually set a JVM memory option (using \"-XX:PermSize\", \"-XX-MaxHeapSize\", \"-XX:InitialHeapSize\", \"-Xmx\" "
"or \"-Xms\").\n"
"You tried to manually set a JVM memory option (using \"-XX:PermSize\", \"-XX-MaxHeapSize\", \"-XX:InitialHeapSize\", \"-Xmx\" or \"-Xms\").\n"
"There are dedicated boxes for these in the settings (Java tab, in the Memory group at the top).\n"
"This message will be displayed until you remove them from the JVM arguments.");
CustomMessageBox::selectable(parent, QObject::tr("JVM arguments warning"), warnStr, QMessageBox::Warning)->exec();
CustomMessageBox::selectable(
parent, QObject::tr("JVM arguments warning"),
warnStr,
QMessageBox::Warning)->exec();
return false;
}
// block lunacy with passing required version to the JVM
if (jvmargs.contains(QRegularExpression("-version:.*"))) {
auto warnStr = QObject::tr(
"You tried to pass required Java version argument to the JVM (using \"-version:xxx\"). This is not safe and will not be "
"allowed.\n"
"You tried to pass required Java version argument to the JVM (using \"-version:xxx\"). This is not safe and will not be allowed.\n"
"This message will be displayed until you remove this from the JVM arguments.");
CustomMessageBox::selectable(parent, QObject::tr("JVM arguments warning"), warnStr, QMessageBox::Warning)->exec();
CustomMessageBox::selectable(
parent, QObject::tr("JVM arguments warning"),
warnStr,
QMessageBox::Warning)->exec();
return false;
}
return true;
}
void JavaCommon::javaWasOk(QWidget* parent, const JavaCheckResult& result)
void JavaCommon::javaWasOk(QWidget *parent, JavaCheckResult result)
{
QString text;
text += QObject::tr(
"Java test succeeded!<br />Platform reported: %1<br />Java version "
text += QObject::tr("Java test succeeded!<br />Platform reported: %1<br />Java version "
"reported: %2<br />Java vendor "
"reported: %3<br />")
.arg(result.realPlatform, result.javaVersion.toString(), result.javaVendor);
if (result.errorLog.size()) {
"reported: %3<br />").arg(result.realPlatform, result.javaVersion.toString(), result.javaVendor);
if (result.errorLog.size())
{
auto htmlError = result.errorLog;
htmlError.replace('\n', "<br />");
text += QObject::tr("<br />Warnings:<br /><font color=\"orange\">%1</font>").arg(htmlError);
@ -79,7 +83,7 @@ void JavaCommon::javaWasOk(QWidget* parent, const JavaCheckResult& result)
CustomMessageBox::selectable(parent, QObject::tr("Java test success"), text, QMessageBox::Information)->show();
}
void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaCheckResult& result)
void JavaCommon::javaArgsWereBad(QWidget *parent, JavaCheckResult result)
{
auto htmlError = result.errorLog;
QString text;
@ -89,7 +93,7 @@ void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaCheckResult& result)
CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show();
}
void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaCheckResult& result)
void JavaCommon::javaBinaryWasBad(QWidget *parent, JavaCheckResult result)
{
QString text;
text += QObject::tr(
@ -98,7 +102,7 @@ void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaCheckResult& result
CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show();
}
void JavaCommon::javaCheckNotFound(QWidget* parent)
void JavaCommon::javaCheckNotFound(QWidget *parent)
{
QString text;
text += QObject::tr("Java checker library could not be found. Please check your installation.");
@ -107,7 +111,8 @@ void JavaCommon::javaCheckNotFound(QWidget* parent)
void JavaCommon::TestCheck::run()
{
if (!JavaCommon::checkJVMArgs(m_args, m_parent)) {
if (!JavaCommon::checkJVMArgs(m_args, m_parent))
{
emit finished();
return;
}
@ -117,25 +122,29 @@ void JavaCommon::TestCheck::run()
return;
}
checker.reset(new JavaChecker());
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
SLOT(checkFinished(JavaCheckResult)));
checker->m_path = m_path;
checker->performCheck();
}
void JavaCommon::TestCheck::checkFinished(JavaCheckResult result)
{
if (result.validity != JavaCheckResult::Validity::Valid) {
if (result.validity != JavaCheckResult::Validity::Valid)
{
javaBinaryWasBad(m_parent, result);
emit finished();
return;
}
checker.reset(new JavaChecker());
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
SLOT(checkFinishedWithArgs(JavaCheckResult)));
checker->m_path = m_path;
checker->m_args = m_args;
checker->m_minMem = m_minMem;
checker->m_maxMem = m_maxMem;
if (result.javaVersion.requiresPermGen()) {
if (result.javaVersion.requiresPermGen())
{
checker->m_permGen = m_permGen;
}
checker->performCheck();
@ -143,7 +152,8 @@ void JavaCommon::TestCheck::checkFinished(JavaCheckResult result)
void JavaCommon::TestCheck::checkFinishedWithArgs(JavaCheckResult result)
{
if (result.validity == JavaCheckResult::Validity::Valid) {
if (result.validity == JavaCheckResult::Validity::Valid)
{
javaWasOk(m_parent, result);
emit finished();
return;
@ -151,3 +161,4 @@ void JavaCommon::TestCheck::checkFinishedWithArgs(JavaCheckResult result)
javaArgsWereBad(m_parent, result);
emit finished();
}

View File

@ -6,25 +6,28 @@ class QWidget;
/**
* Common UI bits for the java pages to use.
*/
namespace JavaCommon {
bool checkJVMArgs(QString args, QWidget* parent);
namespace JavaCommon
{
bool checkJVMArgs(QString args, QWidget *parent);
// Show a dialog saying that the Java binary was usable
void javaWasOk(QWidget* parent, const JavaCheckResult& result);
// Show a dialog saying that the Java binary was not usable because of bad options
void javaArgsWereBad(QWidget* parent, const JavaCheckResult& result);
// Show a dialog saying that the Java binary was not usable
void javaBinaryWasBad(QWidget* parent, const JavaCheckResult& result);
// Show a dialog if we couldn't find Java Checker
void javaCheckNotFound(QWidget* parent);
// Show a dialog saying that the Java binary was usable
void javaWasOk(QWidget *parent, JavaCheckResult result);
// Show a dialog saying that the Java binary was not usable because of bad options
void javaArgsWereBad(QWidget *parent, JavaCheckResult result);
// Show a dialog saying that the Java binary was not usable
void javaBinaryWasBad(QWidget *parent, JavaCheckResult result);
// Show a dialog if we couldn't find Java Checker
void javaCheckNotFound(QWidget *parent);
class TestCheck : public QObject {
class TestCheck : public QObject
{
Q_OBJECT
public:
TestCheck(QWidget* parent, QString path, QString args, int minMem, int maxMem, int permGen)
: m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen)
{}
virtual ~TestCheck(){};
TestCheck(QWidget *parent, QString path, QString args, int minMem, int maxMem, int permGen)
:m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen)
{
}
virtual ~TestCheck() {};
void run();
@ -37,11 +40,11 @@ class TestCheck : public QObject {
private:
std::shared_ptr<JavaChecker> checker;
QWidget* m_parent = nullptr;
QWidget *m_parent = nullptr;
QString m_path;
QString m_args;
int m_minMem = 0;
int m_maxMem = 0;
int m_permGen = 64;
};
} // namespace JavaCommon
};
}

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -37,211 +37,222 @@
#include <QFile>
#include <math.h>
#include "FileSystem.h"
#include <math.h>
namespace Json {
void write(const QJsonDocument& doc, const QString& filename)
namespace Json
{
void write(const QJsonDocument &doc, const QString &filename)
{
FS::write(filename, doc.toJson());
}
void write(const QJsonObject& object, const QString& filename)
void write(const QJsonObject &object, const QString &filename)
{
write(QJsonDocument(object), filename);
}
void write(const QJsonArray& array, const QString& filename)
void write(const QJsonArray &array, const QString &filename)
{
write(QJsonDocument(array), filename);
}
QByteArray toText(const QJsonObject& obj)
QByteArray toText(const QJsonObject &obj)
{
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
}
QByteArray toText(const QJsonArray& array)
QByteArray toText(const QJsonArray &array)
{
return QJsonDocument(array).toJson(QJsonDocument::Compact);
}
static bool isBinaryJson(const QByteArray& data)
static bool isBinaryJson(const QByteArray &data)
{
decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag;
return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0;
}
QJsonDocument requireDocument(const QByteArray& data, const QString& what)
QJsonDocument requireDocument(const QByteArray &data, const QString &what)
{
if (isBinaryJson(data)) {
if (isBinaryJson(data))
{
// FIXME: Is this needed?
throw JsonException(what + ": Invalid JSON. Binary JSON unsupported");
} else {
}
else
{
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
if (error.error != QJsonParseError::NoError)
{
throw JsonException(what + ": Error parsing JSON: " + error.errorString());
}
return doc;
}
}
QJsonDocument requireDocument(const QString& filename, const QString& what)
QJsonDocument requireDocument(const QString &filename, const QString &what)
{
return requireDocument(FS::read(filename), what);
}
QJsonObject requireObject(const QJsonDocument& doc, const QString& what)
QJsonObject requireObject(const QJsonDocument &doc, const QString &what)
{
if (!doc.isObject()) {
if (!doc.isObject())
{
throw JsonException(what + " is not an object");
}
return doc.object();
}
QJsonArray requireArray(const QJsonDocument& doc, const QString& what)
QJsonArray requireArray(const QJsonDocument &doc, const QString &what)
{
if (!doc.isArray()) {
if (!doc.isArray())
{
throw JsonException(what + " is not an array");
}
return doc.array();
}
void writeString(QJsonObject& to, const QString& key, const QString& value)
void writeString(QJsonObject &to, const QString &key, const QString &value)
{
if (!value.isEmpty()) {
if (!value.isEmpty())
{
to.insert(key, value);
}
}
void writeStringList(QJsonObject& to, const QString& key, const QStringList& values)
void writeStringList(QJsonObject &to, const QString &key, const QStringList &values)
{
if (!values.isEmpty()) {
if (!values.isEmpty())
{
QJsonArray array;
for (auto value : values) {
for(auto value: values)
{
array.append(value);
}
to.insert(key, array);
}
}
template <>
QJsonValue toJson<QUrl>(const QUrl& url)
template<>
QJsonValue toJson<QUrl>(const QUrl &url)
{
return QJsonValue(url.toString(QUrl::FullyEncoded));
}
template <>
QJsonValue toJson<QByteArray>(const QByteArray& data)
template<>
QJsonValue toJson<QByteArray>(const QByteArray &data)
{
return QJsonValue(QString::fromLatin1(data.toHex()));
}
template <>
QJsonValue toJson<QDateTime>(const QDateTime& datetime)
template<>
QJsonValue toJson<QDateTime>(const QDateTime &datetime)
{
return QJsonValue(datetime.toString(Qt::ISODate));
}
template <>
QJsonValue toJson<QDir>(const QDir& dir)
template<>
QJsonValue toJson<QDir>(const QDir &dir)
{
return QDir::current().relativeFilePath(dir.absolutePath());
}
template <>
QJsonValue toJson<QUuid>(const QUuid& uuid)
template<>
QJsonValue toJson<QUuid>(const QUuid &uuid)
{
return uuid.toString();
}
template <>
QJsonValue toJson<QVariant>(const QVariant& variant)
template<>
QJsonValue toJson<QVariant>(const QVariant &variant)
{
return QJsonValue::fromVariant(variant);
}
template <>
QByteArray requireIsType<QByteArray>(const QJsonValue& value, const QString& what)
template<> QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what)
{
const QString string = ensureIsType<QString>(value, what);
// ensure that the string can be safely cast to Latin1
if (string != QString::fromLatin1(string.toLatin1())) {
if (string != QString::fromLatin1(string.toLatin1()))
{
throw JsonException(what + " is not encodable as Latin1");
}
return QByteArray::fromHex(string.toLatin1());
}
template <>
QJsonArray requireIsType<QJsonArray>(const QJsonValue& value, const QString& what)
template<> QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what)
{
if (!value.isArray()) {
if (!value.isArray())
{
throw JsonException(what + " is not an array");
}
return value.toArray();
}
template <>
QString requireIsType<QString>(const QJsonValue& value, const QString& what)
template<> QString requireIsType<QString>(const QJsonValue &value, const QString &what)
{
if (!value.isString()) {
if (!value.isString())
{
throw JsonException(what + " is not a string");
}
return value.toString();
}
template <>
bool requireIsType<bool>(const QJsonValue& value, const QString& what)
template<> bool requireIsType<bool>(const QJsonValue &value, const QString &what)
{
if (!value.isBool()) {
if (!value.isBool())
{
throw JsonException(what + " is not a bool");
}
return value.toBool();
}
template <>
double requireIsType<double>(const QJsonValue& value, const QString& what)
template<> double requireIsType<double>(const QJsonValue &value, const QString &what)
{
if (!value.isDouble()) {
if (!value.isDouble())
{
throw JsonException(what + " is not a double");
}
return value.toDouble();
}
template <>
int requireIsType<int>(const QJsonValue& value, const QString& what)
template<> int requireIsType<int>(const QJsonValue &value, const QString &what)
{
const double doubl = requireIsType<double>(value, what);
if (fmod(doubl, 1) != 0) {
if (fmod(doubl, 1) != 0)
{
throw JsonException(what + " is not an integer");
}
return int(doubl);
}
template <>
QDateTime requireIsType<QDateTime>(const QJsonValue& value, const QString& what)
template<> QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what)
{
const QString string = requireIsType<QString>(value, what);
const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate);
if (!datetime.isValid()) {
if (!datetime.isValid())
{
throw JsonException(what + " is not a ISO formatted date/time value");
}
return datetime;
}
template <>
QUrl requireIsType<QUrl>(const QJsonValue& value, const QString& what)
template<> QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what)
{
const QString string = ensureIsType<QString>(value, what);
if (string.isEmpty()) {
if (string.isEmpty())
{
return QUrl();
}
const QUrl url = QUrl(string, QUrl::StrictMode);
if (!url.isValid()) {
if (!url.isValid())
{
throw JsonException(what + " is not a correctly formatted URL");
}
return url;
}
template <>
QDir requireIsType<QDir>(const QJsonValue& value, const QString& what)
template<> QDir requireIsType<QDir>(const QJsonValue &value, const QString &what)
{
const QString string = requireIsType<QString>(value, what);
// FIXME: does not handle invalid characters!
return QDir::current().absoluteFilePath(string);
}
template <>
QUuid requireIsType<QUuid>(const QJsonValue& value, const QString& what)
template<> QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what)
{
const QString string = requireIsType<QString>(value, what);
const QUuid uuid = QUuid(string);
@ -252,31 +263,31 @@ QUuid requireIsType<QUuid>(const QJsonValue& value, const QString& what)
return uuid;
}
template <>
QJsonObject requireIsType<QJsonObject>(const QJsonValue& value, const QString& what)
template<> QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what)
{
if (!value.isObject()) {
if (!value.isObject())
{
throw JsonException(what + " is not an object");
}
return value.toObject();
}
template <>
QVariant requireIsType<QVariant>(const QJsonValue& value, const QString& what)
template<> QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what)
{
if (value.isNull() || value.isUndefined()) {
if (value.isNull() || value.isUndefined())
{
throw JsonException(what + " is null or undefined");
}
return value.toVariant();
}
template <>
QJsonValue requireIsType<QJsonValue>(const QJsonValue& value, const QString& what)
template<> QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what)
{
if (value.isNull() || value.isUndefined()) {
if (value.isNull() || value.isUndefined())
{
throw JsonException(what + " is null or undefined");
}
return value;
}
} // namespace Json
}

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -35,71 +35,74 @@
#pragma once
#include <QDateTime>
#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QDateTime>
#include <QUrl>
#include <QDir>
#include <QUuid>
#include <QVariant>
#include <memory>
#include "Exception.h"
namespace Json {
class JsonException : public ::Exception {
public:
JsonException(const QString& message) : Exception(message) {}
namespace Json
{
class JsonException : public ::Exception
{
public:
JsonException(const QString &message) : Exception(message) {}
};
/// @throw FileSystemException
void write(const QJsonDocument& doc, const QString& filename);
void write(const QJsonDocument &doc, const QString &filename);
/// @throw FileSystemException
void write(const QJsonObject& object, const QString& filename);
void write(const QJsonObject &object, const QString &filename);
/// @throw FileSystemException
void write(const QJsonArray& array, const QString& filename);
void write(const QJsonArray &array, const QString &filename);
QByteArray toText(const QJsonObject& obj);
QByteArray toText(const QJsonArray& array);
QByteArray toText(const QJsonObject &obj);
QByteArray toText(const QJsonArray &array);
/// @throw JsonException
QJsonDocument requireDocument(const QByteArray& data, const QString& what = "Document");
QJsonDocument requireDocument(const QByteArray &data, const QString &what = "Document");
/// @throw JsonException
QJsonDocument requireDocument(const QString& filename, const QString& what = "Document");
QJsonDocument requireDocument(const QString &filename, const QString &what = "Document");
/// @throw JsonException
QJsonObject requireObject(const QJsonDocument& doc, const QString& what = "Document");
QJsonObject requireObject(const QJsonDocument &doc, const QString &what = "Document");
/// @throw JsonException
QJsonArray requireArray(const QJsonDocument& doc, const QString& what = "Document");
QJsonArray requireArray(const QJsonDocument &doc, const QString &what = "Document");
/////////////////// WRITING ////////////////////
void writeString(QJsonObject& to, const QString& key, const QString& value);
void writeStringList(QJsonObject& to, const QString& key, const QStringList& values);
void writeString(QJsonObject & to, const QString &key, const QString &value);
void writeStringList(QJsonObject & to, const QString &key, const QStringList &values);
template <typename T>
QJsonValue toJson(const T& t)
template<typename T>
QJsonValue toJson(const T &t)
{
return QJsonValue(t);
}
template <>
QJsonValue toJson<QUrl>(const QUrl& url);
template <>
QJsonValue toJson<QByteArray>(const QByteArray& data);
template <>
QJsonValue toJson<QDateTime>(const QDateTime& datetime);
template <>
QJsonValue toJson<QDir>(const QDir& dir);
template <>
QJsonValue toJson<QUuid>(const QUuid& uuid);
template <>
QJsonValue toJson<QVariant>(const QVariant& variant);
template<>
QJsonValue toJson<QUrl>(const QUrl &url);
template<>
QJsonValue toJson<QByteArray>(const QByteArray &data);
template<>
QJsonValue toJson<QDateTime>(const QDateTime &datetime);
template<>
QJsonValue toJson<QDir>(const QDir &dir);
template<>
QJsonValue toJson<QUuid>(const QUuid &uuid);
template<>
QJsonValue toJson<QVariant>(const QVariant &variant);
template <typename T>
QJsonArray toJsonArray(const QList<T>& container)
template<typename T>
QJsonArray toJsonArray(const QList<T> &container)
{
QJsonArray array;
for (const T item : container) {
for (const T item : container)
{
array.append(toJson<T>(item));
}
return array;
@ -109,110 +112,106 @@ QJsonArray toJsonArray(const QList<T>& container)
/// @throw JsonException
template <typename T>
T requireIsType(const QJsonValue& value, const QString& what = "Value");
T requireIsType(const QJsonValue &value, const QString &what = "Value");
/// @throw JsonException
template <>
double requireIsType<double>(const QJsonValue& value, const QString& what);
template<> double requireIsType<double>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
bool requireIsType<bool>(const QJsonValue& value, const QString& what);
template<> bool requireIsType<bool>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
int requireIsType<int>(const QJsonValue& value, const QString& what);
template<> int requireIsType<int>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QJsonObject requireIsType<QJsonObject>(const QJsonValue& value, const QString& what);
template<> QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QJsonArray requireIsType<QJsonArray>(const QJsonValue& value, const QString& what);
template<> QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QJsonValue requireIsType<QJsonValue>(const QJsonValue& value, const QString& what);
template<> QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QByteArray requireIsType<QByteArray>(const QJsonValue& value, const QString& what);
template<> QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QDateTime requireIsType<QDateTime>(const QJsonValue& value, const QString& what);
template<> QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QVariant requireIsType<QVariant>(const QJsonValue& value, const QString& what);
template<> QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QString requireIsType<QString>(const QJsonValue& value, const QString& what);
template<> QString requireIsType<QString>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QUuid requireIsType<QUuid>(const QJsonValue& value, const QString& what);
template<> QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QDir requireIsType<QDir>(const QJsonValue& value, const QString& what);
template<> QDir requireIsType<QDir>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template <>
QUrl requireIsType<QUrl>(const QJsonValue& value, const QString& what);
template<> QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what);
// the following functions are higher level functions, that make use of the above functions for
// type conversion
template <typename T>
T ensureIsType(const QJsonValue& value, const T default_ = T(), const QString& what = "Value")
T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &what = "Value")
{
if (value.isUndefined() || value.isNull()) {
if (value.isUndefined() || value.isNull())
{
return default_;
}
try {
try
{
return requireIsType<T>(value, what);
} catch (const JsonException&) {
}
catch (const JsonException &)
{
return default_;
}
}
/// @throw JsonException
template <typename T>
T requireIsType(const QJsonObject& parent, const QString& key, const QString& what = "__placeholder__")
T requireIsType(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key)) {
if (!parent.contains(key))
{
throw JsonException(localWhat + "s parent does not contain " + localWhat);
}
return requireIsType<T>(parent.value(key), localWhat);
}
template <typename T>
T ensureIsType(const QJsonObject& parent, const QString& key, const T default_ = T(), const QString& what = "__placeholder__")
T ensureIsType(const QJsonObject &parent, const QString &key, const T default_ = T(), const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key)) {
if (!parent.contains(key))
{
return default_;
}
return ensureIsType<T>(parent.value(key), default_, localWhat);
}
template <typename T>
QVector<T> requireIsArrayOf(const QJsonDocument& doc)
QVector<T> requireIsArrayOf(const QJsonDocument &doc)
{
const QJsonArray array = requireArray(doc);
QVector<T> out;
for (const QJsonValue val : array) {
for (const QJsonValue val : array)
{
out.append(requireIsType<T>(val, "Document"));
}
return out;
}
template <typename T>
QVector<T> ensureIsArrayOf(const QJsonValue& value, const QString& what = "Value")
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value")
{
const QJsonArray array = ensureIsType<QJsonArray>(value, QJsonArray(), what);
QVector<T> out;
for (const QJsonValue val : array) {
for (const QJsonValue val : array)
{
out.append(requireIsType<T>(val, what));
}
return out;
}
template <typename T>
QVector<T> ensureIsArrayOf(const QJsonValue& value, const QVector<T> default_, const QString& what = "Value")
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QVector<T> default_, const QString &what = "Value")
{
if (value.isUndefined()) {
if (value.isUndefined())
{
return default_;
}
return ensureIsArrayOf<T>(value, what);
@ -220,23 +219,23 @@ QVector<T> ensureIsArrayOf(const QJsonValue& value, const QVector<T> default_, c
/// @throw JsonException
template <typename T>
QVector<T> requireIsArrayOf(const QJsonObject& parent, const QString& key, const QString& what = "__placeholder__")
QVector<T> requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key)) {
if (!parent.contains(key))
{
throw JsonException(localWhat + "s parent does not contain " + localWhat);
}
return ensureIsArrayOf<T>(parent.value(key), localWhat);
}
template <typename T>
QVector<T> ensureIsArrayOf(const QJsonObject& parent,
const QString& key,
const QVector<T>& default_ = QVector<T>(),
const QString& what = "__placeholder__")
QVector<T> ensureIsArrayOf(const QJsonObject &parent, const QString &key,
const QVector<T> &default_ = QVector<T>(), const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key)) {
if (!parent.contains(key))
{
return default_;
}
return ensureIsArrayOf<T>(parent.value(key), default_, localWhat);
@ -244,20 +243,19 @@ QVector<T> ensureIsArrayOf(const QJsonObject& parent,
// this macro part could be replaced by variadic functions that just pass on their arguments, but that wouldn't work well with IDE helpers
#define JSON_HELPERFUNCTIONS(NAME, TYPE) \
inline TYPE require##NAME(const QJsonValue& value, const QString& what = "Value") \
inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \
{ \
return requireIsType<TYPE>(value, what); \
} \
inline TYPE ensure##NAME(const QJsonValue& value, const TYPE default_ = TYPE(), const QString& what = "Value") \
inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \
{ \
return ensureIsType<TYPE>(value, default_, what); \
} \
inline TYPE require##NAME(const QJsonObject& parent, const QString& key, const QString& what = "__placeholder__") \
inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \
{ \
return requireIsType<TYPE>(parent, key, what); \
} \
inline TYPE ensure##NAME(const QJsonObject& parent, const QString& key, const TYPE default_ = TYPE(), \
const QString& what = "__placeholder") \
inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \
{ \
return ensureIsType<TYPE>(parent, key, default_, what); \
}
@ -278,5 +276,5 @@ JSON_HELPERFUNCTIONS(Variant, QVariant)
#undef JSON_HELPERFUNCTIONS
} // namespace Json
}
using JSONValidationError = Json::JsonException;

View File

@ -1,26 +1,42 @@
#include "KonamiCode.h"
#include <QDebug>
#include <array>
#include <QDebug>
namespace {
const std::array<Qt::Key, 10> konamiCode = { { Qt::Key_Up, Qt::Key_Up, Qt::Key_Down, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right,
Qt::Key_Left, Qt::Key_Right, Qt::Key_B, Qt::Key_A } };
const std::array<Qt::Key, 10> konamiCode =
{
{
Qt::Key_Up, Qt::Key_Up,
Qt::Key_Down, Qt::Key_Down,
Qt::Key_Left, Qt::Key_Right,
Qt::Key_Left, Qt::Key_Right,
Qt::Key_B, Qt::Key_A
}
};
}
KonamiCode::KonamiCode(QObject* parent) : QObject(parent)
{
}
KonamiCode::KonamiCode(QObject* parent) : QObject(parent) {}
void KonamiCode::input(QEvent* event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if( event->type() == QEvent::KeyPress )
{
QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event );
auto key = Qt::Key(keyEvent->key());
if (key == konamiCode[m_progress]) {
m_progress++;
} else {
if(key == konamiCode[m_progress])
{
m_progress ++;
}
else
{
m_progress = 0;
}
if (m_progress == static_cast<int>(konamiCode.size())) {
if(m_progress == static_cast<int>(konamiCode.size()))
{
m_progress = 0;
emit triggered();
}

View File

@ -2,15 +2,16 @@
#include <QKeyEvent>
class KonamiCode : public QObject {
class KonamiCode : public QObject
{
Q_OBJECT
public:
KonamiCode(QObject* parent = 0);
void input(QEvent* event);
public:
KonamiCode(QObject *parent = 0);
void input(QEvent *event);
signals:
signals:
void triggered();
private:
private:
int m_progress = 0;
};

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) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -35,41 +34,44 @@
*/
#include "LaunchController.h"
#include "Application.h"
#include "minecraft/auth/AccountList.h"
#include "Application.h"
#include "ui/InstanceWindow.h"
#include "ui/MainWindow.h"
#include "ui/InstanceWindow.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ProfileSelectDialog.h"
#include "ui/dialogs/ProfileSetupDialog.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ProfileSetupDialog.h"
#include <QHostAddress>
#include <QHostInfo>
#include <QInputDialog>
#include <QLineEdit>
#include <QList>
#include <QPushButton>
#include <QInputDialog>
#include <QStringList>
#include <QHostInfo>
#include <QList>
#include <QHostAddress>
#include <QPushButton>
#include "BuildConfig.h"
#include "JavaCommon.h"
#include "launch/steps/TextPrint.h"
#include "minecraft/auth/AccountTask.h"
#include "tasks/Task.h"
#include "minecraft/auth/AccountTask.h"
#include "launch/steps/TextPrint.h"
LaunchController::LaunchController(QObject* parent) : Task(parent) {}
LaunchController::LaunchController(QObject *parent) : Task(parent)
{
}
void LaunchController::executeTask()
{
if (!m_instance) {
if (!m_instance)
{
emitFailed(tr("No instance specified!"));
return;
}
if (!JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget)) {
if(!JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget)) {
emitFailed(tr("Invalid Java arguments specified. Please fix this first."));
return;
}
@ -79,43 +81,46 @@ void LaunchController::executeTask()
void LaunchController::decideAccount()
{
if (m_accountToUse) {
if(m_accountToUse) {
return;
}
// Find an account to use.
auto accounts = APPLICATION->accounts();
if (accounts->count() <= 0) {
if (accounts->count() <= 0)
{
// Tell the user they need to log in at least one account in order to play.
auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"),
tr("In order to play Minecraft, you must have at least one Microsoft "
"account which owns Minecraft logged in. "
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. "
"Would you like to open the account manager to add an account now?"),
QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)
->exec();
QMessageBox::Information,
QMessageBox::Yes | QMessageBox::No
)->exec();
if (reply == QMessageBox::Yes) {
if (reply == QMessageBox::Yes)
{
// Open the account manager.
APPLICATION->ShowGlobalSettings(m_parentWidget, "accounts");
} else if (reply == QMessageBox::No) {
}
else if (reply == QMessageBox::No)
{
// Do not open "profile select" dialog.
return;
}
}
// Select the account to use. If the instance has a specific account set, that will be used. Otherwise, the default account will be used
auto instanceAccountId = m_instance->settings()->get("InstanceAccountId").toString();
auto instanceAccountIndex = accounts->findAccountByProfileId(instanceAccountId);
if (instanceAccountIndex == -1 || instanceAccountId.isEmpty()) {
m_accountToUse = accounts->defaultAccount();
} else {
m_accountToUse = accounts->at(instanceAccountIndex);
}
if (!m_accountToUse) {
if (!m_accountToUse)
{
// If no default account is set, ask the user which one to use.
ProfileSelectDialog selectDialog(tr("Which account would you like to use?"), ProfileSelectDialog::GlobalDefaultCheckbox,
m_parentWidget);
ProfileSelectDialog selectDialog(
tr("Which account would you like to use?"),
ProfileSelectDialog::GlobalDefaultCheckbox,
m_parentWidget
);
selectDialog.exec();
@ -129,12 +134,13 @@ void LaunchController::decideAccount()
}
}
void LaunchController::login()
{
void LaunchController::login() {
decideAccount();
// if no account is selected, we bail
if (!m_accountToUse) {
if (!m_accountToUse)
{
emitFailed(tr("No account selected for launch."));
return;
}
@ -143,11 +149,15 @@ void LaunchController::login()
bool tryagain = true;
unsigned int tries = 0;
while (tryagain) {
while (tryagain)
{
if (tries > 0 && tries % 3 == 0) {
auto result =
QMessageBox::question(m_parentWidget, tr("Continue launch?"),
tr("It looks like we couldn't launch after %1 tries. Do you want to continue trying?").arg(tries));
auto result = QMessageBox::question(
m_parentWidget,
tr("Continue launch?"),
tr("It looks like we couldn't launch after %1 tries. Do you want to continue trying?")
.arg(tries)
);
if (result == QMessageBox::No) {
emitAborted();
@ -161,48 +171,60 @@ void LaunchController::login()
m_accountToUse->fillSession(m_session);
// Launch immediately in true offline mode
if (m_accountToUse->isOffline()) {
if(m_accountToUse->isOffline()) {
launchInstance();
return;
}
switch (m_accountToUse->accountState()) {
switch(m_accountToUse->accountState()) {
case AccountState::Offline: {
m_session->wants_online = false;
// NOTE: fallthrough is intentional
}
/* fallthrough */
case AccountState::Online: {
if (!m_session->wants_online) {
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) {
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, QLineEdit::Normal, usedname, &ok);
if (!ok) {
QString name = QInputDialog::getText(
m_parentWidget,
tr("Player name"),
message,
QLineEdit::Normal,
usedname,
&ok
);
if (!ok)
{
tryagain = false;
break;
}
if (name.length()) {
if (name.length())
{
usedname = name;
APPLICATION->settings()->set("LastOfflinePlayerName", usedname);
}
m_session->MakeOffline(usedname);
// offline flavored game from here :3
}
if (m_accountToUse->ownsMinecraft()) {
if (!m_accountToUse->hasProfile()) {
if(m_accountToUse->ownsMinecraft()) {
if(!m_accountToUse->hasProfile()) {
// Now handle setting up a profile name here...
ProfileSetupDialog dialog(m_accountToUse, m_parentWidget);
if (dialog.exec() == QDialog::Accepted) {
if (dialog.exec() == QDialog::Accepted)
{
tryagain = true;
continue;
} else {
}
else
{
emitFailed(tr("Received undetermined session status during login."));
return;
}
@ -210,24 +232,24 @@ void LaunchController::login()
// we own Minecraft, there is a profile, it's all ready to go!
launchInstance();
return;
} else {
}
else {
// play demo ?
QMessageBox box(m_parentWidget);
box.setWindowTitle(tr("Play demo?"));
box.setText(
tr("This account does not own Minecraft.\nYou need to purchase the game first to play it.\n\nDo you want to play "
"the demo?"));
box.setText(tr("This account does not own Minecraft.\nYou need to purchase the game first to play it.\n\nDo you want to play the demo?"));
box.setIcon(QMessageBox::Warning);
auto demoButton = box.addButton(tr("Play Demo"), QMessageBox::ButtonRole::YesRole);
auto cancelButton = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::NoRole);
box.setDefaultButton(cancelButton);
box.exec();
if (box.clickedButton() == demoButton) {
if(box.clickedButton() == demoButton) {
// play demo here
m_session->MakeDemo();
launchInstance();
} else {
}
else {
emitFailed(tr("Launch cancelled - account does not own Minecraft."));
}
}
@ -237,12 +259,13 @@ void LaunchController::login()
// This means some sort of soft error that we can fix with a refresh ... so let's refresh.
case AccountState::Unchecked: {
m_accountToUse->refresh();
// NOTE: fallthrough intentional
}
/* fallthrough */
case AccountState::Working: {
// refresh is in progress, we need to wait for it to finish to proceed.
ProgressDialog progDialog(m_parentWidget);
if (m_online) {
if (m_online)
{
progDialog.setSkipButton(true, tr("Play Offline"));
}
auto task = m_accountToUse->currentTask();
@ -257,24 +280,37 @@ void LaunchController::login()
*/
case AccountState::Expired: {
auto errorString = tr("The account has expired and needs to be logged into manually again.");
QMessageBox::warning(m_parentWidget, tr("Account refresh failed"), errorString, QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok);
QMessageBox::warning(
m_parentWidget,
tr("Account refresh failed"),
errorString,
QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok
);
emitFailed(errorString);
return;
}
case AccountState::Disabled: {
auto errorString = tr("The launcher's client identification has changed. Please remove this account and add it again.");
QMessageBox::warning(m_parentWidget, tr("Client identification changed"), errorString, QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok);
QMessageBox::warning(
m_parentWidget,
tr("Client identification changed"),
errorString,
QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok
);
emitFailed(errorString);
return;
}
case AccountState::Gone: {
auto errorString =
tr("The account no longer exists on the servers. It may have been migrated, in which case please add the new account "
"you migrated this one to.");
QMessageBox::warning(m_parentWidget, tr("Account gone"), errorString, QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok);
auto errorString = tr("The account no longer exists on the servers. It may have been migrated, in which case please add the new account you migrated this one to.");
QMessageBox::warning(
m_parentWidget,
tr("Account gone"),
errorString,
QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok
);
emitFailed(errorString);
return;
}
@ -288,21 +324,24 @@ void LaunchController::launchInstance()
Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL");
Q_ASSERT_X(m_session.get() != nullptr, "launchInstance", "session is NULL");
if (!m_instance->reloadSettings()) {
if(!m_instance->reloadSettings())
{
QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't load the instance profile."));
emitFailed(tr("Couldn't load the instance profile."));
return;
}
m_launcher = m_instance->createLaunchTask(m_session, m_serverToJoin);
if (!m_launcher) {
if (!m_launcher)
{
emitFailed(tr("Couldn't instantiate a launcher."));
return;
}
auto console = qobject_cast<InstanceWindow*>(m_parentWidget);
auto console = qobject_cast<InstanceWindow *>(m_parentWidget);
auto showConsole = m_instance->settings()->get("ShowConsole").toBool();
if (!console && showConsole) {
if(!console && showConsole)
{
APPLICATION->showInstanceWindow(m_instance);
}
connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch);
@ -312,21 +351,21 @@ void LaunchController::launchInstance()
// Prepend Online and Auth Status
QString online_mode;
if (m_session->wants_online) {
if(m_session->wants_online) {
online_mode = "online";
// Prepend Server Status
QStringList servers = { "authserver.mojang.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com" };
QStringList servers = {"authserver.mojang.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com"};
QString resolved_servers = "";
QHostInfo host_info;
for (QString server : servers) {
for(QString server : servers) {
host_info = QHostInfo::fromName(server);
resolved_servers = resolved_servers + server + " resolves to:\n [";
if (!host_info.addresses().isEmpty()) {
for (QHostAddress address : host_info.addresses()) {
if(!host_info.addresses().isEmpty()) {
for(QHostAddress address : host_info.addresses()) {
resolved_servers = resolved_servers + address.toString();
if (!host_info.addresses().endsWith(address)) {
if(!host_info.addresses().endsWith(address)) {
resolved_servers = resolved_servers + ", ";
}
}
@ -335,52 +374,51 @@ void LaunchController::launchInstance()
}
resolved_servers = resolved_servers + "]\n\n";
}
m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), resolved_servers, MessageLevel::Launcher));
m_launcher->prependStep(new TextPrint(m_launcher.get(), resolved_servers, MessageLevel::Launcher));
} else {
online_mode = m_demo ? "demo" : "offline";
}
m_launcher->prependStep(
makeShared<TextPrint>(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher));
m_launcher->prependStep(new TextPrint(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher));
// Prepend Version
{
auto versionString = QString("%1 version: %2 (%3)")
.arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString(), BuildConfig.BUILD_PLATFORM);
m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), versionString + "\n\n", MessageLevel::Launcher));
}
m_launcher->prependStep(new TextPrint(m_launcher.get(), BuildConfig.LAUNCHER_DISPLAYNAME + " version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::Launcher));
m_launcher->start();
}
void LaunchController::readyForLaunch()
{
if (!m_profiler) {
if (!m_profiler)
{
m_launcher->proceed();
return;
}
QString error;
if (!m_profiler->check(&error)) {
if (!m_profiler->check(&error))
{
m_launcher->abort();
QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't start profiler: %1").arg(error));
emitFailed("Profiler startup failed!");
QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Profiler check for %1 failed: %2").arg(m_profiler->name(), error));
return;
}
BaseProfiler* profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this);
BaseProfiler *profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this);
connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString& message) {
QMessageBox msg(m_parentWidget);
connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString & message)
{
QMessageBox msg;
msg.setText(tr("The game launch is delayed until you press the "
"button. This is the right time to setup the profiler, as the "
"profiler server is running now.\n\n%1")
.arg(message));
"profiler server is running now.\n\n%1").arg(message));
msg.setWindowTitle(tr("Waiting."));
msg.setIcon(QMessageBox::Information);
msg.addButton(tr("&Launch"), QMessageBox::AcceptRole);
msg.addButton(tr("Launch"), QMessageBox::AcceptRole);
msg.setModal(true);
msg.exec();
m_launcher->proceed();
});
connect(profilerInstance, &BaseProfiler::abortLaunch, [this](const QString& message) {
connect(profilerInstance, &BaseProfiler::abortLaunch, [this](const QString & message)
{
QMessageBox msg;
msg.setText(tr("Couldn't start the profiler: %1").arg(message));
msg.setWindowTitle(tr("Error"));
@ -401,7 +439,8 @@ void LaunchController::onSucceeded()
void LaunchController::onFailed(QString reason)
{
if (m_instance->settings()->get("ShowConsoleOnError").toBool()) {
if(m_instance->settings()->get("ShowConsoleOnError").toBool())
{
APPLICATION->showInstanceWindow(m_instance, "console");
}
emitFailed(reason);
@ -417,18 +456,21 @@ void LaunchController::onProgressRequested(Task* task)
bool LaunchController::abort()
{
if (!m_launcher) {
if(!m_launcher)
{
return true;
}
if (!m_launcher->canAbort()) {
if(!m_launcher->canAbort())
{
return false;
}
auto response = CustomMessageBox::selectable(m_parentWidget, tr("Kill Minecraft?"),
auto response = CustomMessageBox::selectable(
m_parentWidget, tr("Kill Minecraft?"),
tr("This can cause the instance to get corrupted and should only be used if Minecraft "
"is frozen for some reason"),
QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)
->exec();
if (response == QMessageBox::Yes) {
QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec();
if (response == QMessageBox::Yes)
{
return m_launcher->abort();
}
return false;

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -34,61 +34,81 @@
*/
#pragma once
#include <QObject>
#include <BaseInstance.h>
#include <tools/BaseProfiler.h>
#include <QObject>
#include "minecraft/auth/MinecraftAccount.h"
#include "minecraft/launch/MinecraftServerTarget.h"
#include "minecraft/auth/MinecraftAccount.h"
class InstanceWindow;
class LaunchController : public Task {
class LaunchController: public Task
{
Q_OBJECT
public:
public:
void executeTask() override;
LaunchController(QObject* parent = nullptr);
LaunchController(QObject * parent = nullptr);
virtual ~LaunchController(){};
void setInstance(InstancePtr instance) { m_instance = instance; }
void setInstance(InstancePtr instance) {
m_instance = instance;
}
InstancePtr instance() { return m_instance; }
InstancePtr instance() {
return m_instance;
}
void setOnline(bool online) { m_online = online; }
void setOnline(bool online) {
m_online = online;
}
void setDemo(bool demo) { m_demo = demo; }
void setDemo(bool demo) {
m_demo = demo;
}
void setProfiler(BaseProfilerFactory* profiler) { m_profiler = profiler; }
void setProfiler(BaseProfilerFactory *profiler) {
m_profiler = profiler;
}
void setParentWidget(QWidget* widget) { m_parentWidget = widget; }
void setParentWidget(QWidget * widget) {
m_parentWidget = widget;
}
void setServerToJoin(MinecraftServerTargetPtr serverToJoin) { m_serverToJoin = std::move(serverToJoin); }
void setServerToJoin(MinecraftServerTargetPtr serverToJoin) {
m_serverToJoin = std::move(serverToJoin);
}
void setAccountToUse(MinecraftAccountPtr accountToUse) { m_accountToUse = std::move(accountToUse); }
void setAccountToUse(MinecraftAccountPtr accountToUse) {
m_accountToUse = std::move(accountToUse);
}
QString id() { return m_instance->id(); }
QString id()
{
return m_instance->id();
}
bool abort() override;
private:
private:
void login();
void launchInstance();
void decideAccount();
private slots:
private slots:
void readyForLaunch();
void onSucceeded();
void onFailed(QString reason);
void onProgressRequested(Task* task);
void onProgressRequested(Task *task);
private:
BaseProfilerFactory* m_profiler = nullptr;
private:
BaseProfilerFactory *m_profiler = nullptr;
bool m_online = true;
bool m_demo = false;
InstancePtr m_instance;
QWidget* m_parentWidget = nullptr;
InstanceWindow* m_console = nullptr;
QWidget * m_parentWidget = nullptr;
InstanceWindow *m_console = nullptr;
MinecraftAccountPtr m_accountToUse = nullptr;
AuthSessionPtr m_session;
shared_qobject_ptr<LaunchTask> m_launcher;

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
# Basic start script for running the launcher with the libs packaged with it.
function printerror {

View File

@ -1,8 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022,2023 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2023 flowln <flowlnlnln@gmail.com>
* 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
@ -39,35 +38,36 @@
#include <QTextDecoder>
#include "MessageLevel.h"
LoggedProcess::LoggedProcess(QObject* parent) : QProcess(parent)
LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
{
// QProcess has a strange interface... let's map a lot of those into a few.
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr);
connect(this, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &LoggedProcess::on_exit);
connect(this, &QProcess::errorOccurred, this, &LoggedProcess::on_error);
connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus)));
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
connect(this, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError)));
#else
connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError)));
#endif
connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange);
}
LoggedProcess::~LoggedProcess()
{
if (m_is_detachable) {
if(m_is_detachable)
{
setProcessState(QProcess::NotRunning);
}
}
QStringList LoggedProcess::reprocess(const QByteArray& data, QTextDecoder& decoder)
QStringList reprocess(const QByteArray& data, QTextDecoder& decoder)
{
auto str = decoder.toUnicode(data);
if (!m_leftover_line.isEmpty()) {
str.prepend(m_leftover_line);
m_leftover_line = "";
}
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed);
m_leftover_line = lines.takeLast();
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, QString::SkipEmptyParts);
#else
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, Qt::SkipEmptyParts);
#endif
return lines;
}
@ -89,31 +89,39 @@ void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status)
m_exit_code = exit_code;
// based on state, send signals
if (!m_is_aborting) {
if (status == QProcess::NormalExit) {
if (!m_is_aborting)
{
if (status == QProcess::NormalExit)
{
//: Message displayed on instance exit
emit log({ tr("Process exited with code %1.").arg(exit_code) }, MessageLevel::Launcher);
emit log({tr("Process exited with code %1.").arg(exit_code)}, MessageLevel::Launcher);
changeState(LoggedProcess::Finished);
} else {
//: Message displayed on instance crashed
if (exit_code == -1)
emit log({ tr("Process crashed.") }, MessageLevel::Launcher);
}
else
emit log({ tr("Process crashed with exitcode %1.").arg(exit_code) }, MessageLevel::Launcher);
{
//: Message displayed on instance crashed
if(exit_code == -1)
emit log({tr("Process crashed.")}, MessageLevel::Launcher);
else
emit log({tr("Process crashed with exitcode %1.").arg(exit_code)}, MessageLevel::Launcher);
changeState(LoggedProcess::Crashed);
}
} else {
}
else
{
//: Message displayed after the instance exits due to kill request
emit log({ tr("Process was killed by user.") }, MessageLevel::Error);
emit log({tr("Process was killed by user.")}, MessageLevel::Error);
changeState(LoggedProcess::Aborted);
}
}
void LoggedProcess::on_error(QProcess::ProcessError error)
{
switch (error) {
case QProcess::FailedToStart: {
emit log({ tr("The process failed to start.") }, MessageLevel::Fatal);
switch(error)
{
case QProcess::FailedToStart:
{
emit log({tr("The process failed to start.")}, MessageLevel::Fatal);
changeState(LoggedProcess::FailedToStart);
break;
}
@ -140,7 +148,7 @@ int LoggedProcess::exitCode() const
void LoggedProcess::changeState(LoggedProcess::State state)
{
if (state == m_state)
if(state == m_state)
return;
m_state = state;
emit stateChanged(m_state);
@ -153,19 +161,24 @@ LoggedProcess::State LoggedProcess::state() const
void LoggedProcess::on_stateChange(QProcess::ProcessState state)
{
switch (state) {
switch(state)
{
case QProcess::NotRunning:
break; // let's not - there are too many that handle this already.
case QProcess::Starting: {
if (m_state != LoggedProcess::NotRunning) {
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int)LoggedProcess::Starting;
case QProcess::Starting:
{
if(m_state != LoggedProcess::NotRunning)
{
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Starting;
}
changeState(LoggedProcess::Starting);
return;
}
case QProcess::Running: {
if (m_state != LoggedProcess::Starting) {
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int)LoggedProcess::Running;
case QProcess::Running:
{
if(m_state != LoggedProcess::Starting)
{
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Running;
}
changeState(LoggedProcess::Running);
return;

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022,2023 Sefa Eyeoglu <contact@scrumplex.net>
* 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
@ -43,12 +43,22 @@
* This is a basic process.
* It has line-based logging support and hides some of the nasty bits.
*/
class LoggedProcess : public QProcess {
Q_OBJECT
public:
enum State { NotRunning, Starting, FailedToStart, Running, Finished, Crashed, Aborted };
class LoggedProcess : public QProcess
{
Q_OBJECT
public:
enum State
{
NotRunning,
Starting,
FailedToStart,
Running,
Finished,
Crashed,
Aborted
};
public:
public:
explicit LoggedProcess(QObject* parent = 0);
virtual ~LoggedProcess();
@ -57,32 +67,30 @@ class LoggedProcess : public QProcess {
void setDetachable(bool detachable);
signals:
signals:
void log(QStringList lines, MessageLevel::Enum level);
void stateChanged(LoggedProcess::State state);
public slots:
public slots:
/**
* @brief kill the process - equivalent to kill -9
*/
void kill();
private slots:
private slots:
void on_stdErr();
void on_stdOut();
void on_exit(int exit_code, QProcess::ExitStatus status);
void on_error(QProcess::ProcessError error);
void on_stateChange(QProcess::ProcessState);
private:
private:
void changeState(LoggedProcess::State state);
QStringList reprocess(const QByteArray& data, QTextDecoder& decoder);
private:
private:
QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale());
QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale());
QString m_leftover_line;
bool m_killed = false;
State m_state = NotRunning;
int m_exit_code = 0;

View File

@ -16,87 +16,23 @@
*/
#include <MMCTime.h>
#include <qobject.h>
#include <QDateTime>
#include <QObject>
#include <QTextStream>
QString Time::prettifyDuration(int64_t duration, bool noDays)
{
int seconds = (int)(duration % 60);
QString Time::prettifyDuration(int64_t duration) {
int seconds = (int) (duration % 60);
duration /= 60;
int minutes = (int)(duration % 60);
int minutes = (int) (duration % 60);
duration /= 60;
int hours = (int)(noDays ? duration : (duration % 24));
int days = (int)(noDays ? 0 : (duration / 24));
if ((hours == 0) && (days == 0)) {
return QObject::tr("%1min %2s").arg(minutes).arg(seconds);
int hours = (int) (duration % 24);
int days = (int) (duration / 24);
if((hours == 0)&&(days == 0))
{
return QObject::tr("%1m %2s").arg(minutes).arg(seconds);
}
if (days == 0) {
return QObject::tr("%1h %2min").arg(hours).arg(minutes);
if (days == 0)
{
return QObject::tr("%1h %2m").arg(hours).arg(minutes);
}
return QObject::tr("%1d %2h %3min").arg(days).arg(hours).arg(minutes);
}
QString Time::humanReadableDuration(double duration, int precision)
{
using days = std::chrono::duration<int, std::ratio<86400>>;
QString outStr;
QTextStream os(&outStr);
bool neg = false;
if (duration < 0) {
neg = true; // flag
duration *= -1; // invert
}
auto std_duration = std::chrono::duration<double>(duration);
auto d = std::chrono::duration_cast<days>(std_duration);
std_duration -= d;
auto h = std::chrono::duration_cast<std::chrono::hours>(std_duration);
std_duration -= h;
auto m = std::chrono::duration_cast<std::chrono::minutes>(std_duration);
std_duration -= m;
auto s = std::chrono::duration_cast<std::chrono::seconds>(std_duration);
std_duration -= s;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(std_duration);
auto dc = d.count();
auto hc = h.count();
auto mc = m.count();
auto sc = s.count();
auto msc = ms.count();
if (neg) {
os << '-';
}
if (dc) {
os << dc << QObject::tr("days");
}
if (hc) {
if (dc)
os << " ";
os << qSetFieldWidth(2) << hc << QObject::tr("h"); // hours
}
if (mc) {
if (dc || hc)
os << " ";
os << qSetFieldWidth(2) << mc << QObject::tr("m"); // minutes
}
if (dc || hc || mc || sc) {
if (dc || hc || mc)
os << " ";
os << qSetFieldWidth(2) << sc << QObject::tr("s"); // seconds
}
if ((msc && (precision > 0)) || !(dc || hc || mc || sc)) {
if (dc || hc || mc || sc)
os << " ";
os << qSetFieldWidth(0) << qSetRealNumberPrecision(precision) << msc << QObject::tr("ms"); // miliseconds
}
os.flush();
return outStr;
return QObject::tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes);
}

View File

@ -20,15 +20,6 @@
namespace Time {
QString prettifyDuration(int64_t duration, bool noDays = false);
QString prettifyDuration(int64_t duration);
/**
* @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`.
* miliseconds are only included if `precision` is greater than 0.
*
* @param duration a number of seconds as floating point
* @param precision number of decmial points to display on fractons of a second, defualts to 0.
* @return QString
*/
QString humanReadableDuration(double duration, int precision = 0);
} // namespace Time
}

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