diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 2163db45b..528b128b1 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -2,3 +2,6 @@
# tabs -> spaces
bbb3b3e6f6e3c0f95873f22e6d0a4aaf350f49d9
+
+# (nix) alejandra -> nixfmt
+4c81d8c53d09196426568c4a31a4e752ed05397a
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1b230ddc6..b6abf0ef4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -39,6 +39,9 @@ on:
APPLE_NOTARIZE_PASSWORD:
description: Password used for notarizing macOS 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
@@ -56,14 +59,14 @@ jobs:
qt_ver: 5
qt_host: linux
qt_arch: ""
- qt_version: "5.12.8"
+ qt_version: "5.15.2"
qt_modules: "qtnetworkauth"
- os: ubuntu-20.04
qt_ver: 6
qt_host: linux
qt_arch: ""
- qt_version: "6.2.4"
+ qt_version: "6.5.3"
qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: windows-2022
@@ -79,8 +82,10 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: ""
- qt_version: "6.7.2"
+ qt_version: "6.7.3"
qt_modules: "qt5compat qtimageformats qtnetworkauth"
+ nscurl_tag: "v24.9.26.122"
+ nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
- os: windows-2022
name: "Windows-MSVC-arm64"
@@ -90,8 +95,10 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: "win64_msvc2019_arm64"
- qt_version: "6.7.2"
+ qt_version: "6.7.3"
qt_modules: "qt5compat qtimageformats qtnetworkauth"
+ nscurl_tag: "v24.9.26.122"
+ nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
- os: macos-14
name: macOS
@@ -99,7 +106,7 @@ jobs:
qt_ver: 6
qt_host: mac
qt_arch: ""
- qt_version: "6.7.2"
+ qt_version: "6.7.3"
qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: macos-14
@@ -166,7 +173,7 @@ jobs:
- name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
- uses: actions/cache@v4.0.2
+ uses: actions/cache@v4.2.0
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
@@ -199,7 +206,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 appstream libxcb-cursor-dev
- name: Install Dependencies (macOS)
if: runner.os == 'macOS'
@@ -272,23 +279,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 -DENABLE_JAVA_DOWNLOADER=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_ENABLE_JAVA_DOWNLOADER=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
- 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 -DENABLE_JAVA_DOWNLOADER=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="" -DCMAKE_OSX_ARCHITECTURES="x86_64" -G Ninja
+ cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=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="" -DCMAKE_OSX_ARCHITECTURES="x86_64" -G Ninja
- name: Configure CMake (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != ''
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 -DENABLE_JAVA_DOWNLOADER=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
+ cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=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 -DENABLE_JAVA_DOWNLOADER=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 }}
+ cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=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 }}")
{
@@ -303,7 +310,7 @@ jobs:
- 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 -DENABLE_JAVA_DOWNLOADER=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_ENABLE_JAVA_DOWNLOADER=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
##
# BUILD
@@ -403,9 +410,8 @@ jobs:
if: matrix.name == 'macOS'
run: |
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
- brew install openssl@3
echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem
- signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
+ signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
rm ed25519-priv.pem
cat >> $GITHUB_STEP_SUMMARY << EOF
### Artifact Information :information_source:
@@ -471,6 +477,16 @@ jobs:
- name: Package (Windows, installer)
if: runner.os == 'Windows'
run: |
+ if ('${{ matrix.nscurl_tag }}') {
+ New-Item -Name NSISPlugins -ItemType Directory
+ Invoke-Webrequest https://github.com/negrutiu/nsis-nscurl/releases/download/${{ matrix.nscurl_tag }}/NScurl.zip -OutFile NSISPlugins\NScurl.zip
+ $nscurl_hash = Get-FileHash NSISPlugins\NScurl.zip -Algorithm Sha256 | Select-Object -ExpandProperty Hash
+ if ( $nscurl_hash -ne "${{ matrix.nscurl_sha256 }}") {
+ echo "::error:: NSCurl.zip sha256 mismatch"
+ exit 1
+ }
+ Expand-Archive -Path NSISPlugins\NScurl.zip -DestinationPath NSISPlugins\NScurl
+ }
cd ${{ env.INSTALL_DIR }}
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
@@ -617,17 +633,72 @@ jobs:
flatpak:
runs-on: ubuntu-latest
container:
- image: bilelmoussaoui/flatpak-github-actions:kde-6.7
+ image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.8
options: --privileged
steps:
- name: Checkout
uses: actions/checkout@v4
if: inputs.build_type == 'Debug'
with:
- submodules: "true"
+ submodules: true
+
+ - name: Set short version
+ shell: bash
+ run: echo "VERSION=${GITHUB_SHA::7}" >> $GITHUB_ENV
+
- name: Build Flatpak (Linux)
if: inputs.build_type == 'Debug'
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
- bundle: "Freesm Launcher.flatpak"
+ bundle: FreesmLauncher-${{ runner.os }}-${{ env.VERSION }}-Flatpak.flatpak
manifest-path: flatpak/org.freesmTeam.freesmlauncher.yml
+
+ nix:
+ name: Nix (${{ matrix.system }})
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - os: ubuntu-22.04
+ system: x86_64-linux
+
+ - os: macos-13
+ system: x86_64-darwin
+
+ - os: macos-14
+ system: aarch64-darwin
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Install Nix
+ uses: cachix/install-nix-action@v30
+
+ # For PRs
+ - name: Setup Nix Magic Cache
+ uses: DeterminateSystems/magic-nix-cache-action@v8
+
+ # For in-tree builds
+ - name: Setup Cachix
+ uses: cachix/cachix-action@v15
+ with:
+ name: prismlauncher
+ authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
+
+ - name: Run flake checks
+ run: |
+ nix flake check --print-build-logs --show-trace
+
+ - name: Build debug package
+ if: ${{ inputs.build_type == 'Debug' }}
+ run: |
+ nix build --print-build-logs .#prismlauncher-debug
+
+ - name: Build release package
+ if: ${{ inputs.build_type != 'Debug' }}
+ run: |
+ nix build --print-build-logs .#prismlauncher
diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml
index 750055fb6..d960fdea8 100644
--- a/.github/workflows/trigger_builds.yml
+++ b/.github/workflows/trigger_builds.yml
@@ -40,5 +40,6 @@ jobs:
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
+ CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml
index b987ab7af..392ad59e7 100644
--- a/.github/workflows/trigger_release.yml
+++ b/.github/workflows/trigger_release.yml
@@ -23,6 +23,7 @@ jobs:
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
+ CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml
index da0ca0e6f..a1ad72325 100644
--- a/.github/workflows/update-flake.yml
+++ b/.github/workflows/update-flake.yml
@@ -17,9 +17,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- - uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b # v27
+ - uses: cachix/install-nix-action@08dcb3a5e62fa31e2da3d490afc4176ef55ecd72 # v30
- - uses: DeterminateSystems/update-flake-lock@v23
+ - uses: DeterminateSystems/update-flake-lock@v24
with:
commit-msg: "chore(nix): update lockfile"
pr-title: "chore(nix): update lockfile"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e501e5a1f..9c7297049 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -99,7 +99,7 @@ if ((CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebI
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
+ # 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-")
@@ -176,10 +176,12 @@ endif()
set(Launcher_NEWS_RSS_URL "https://prismlauncher.org/feed/feed.xml" CACHE STRING "URL to fetch Prism Launcher's news RSS feed from.")
set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL that gets opened when the user clicks 'More News'")
set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
+set(Launcher_LOGIN_CALLBACK_URL "https://prismlauncher.org/successful-login" CACHE STRING "URL that gets opened when the user successfully logins.")
+set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for FML Libraries.")
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 9)
-set(Launcher_VERSION_MINOR 0)
+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")
@@ -204,33 +206,34 @@ set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can g
set(Launcher_BUG_TRACKER_URL "https://github.com/PrismLauncher/PrismLauncher/issues" CACHE STRING "URL for the bug tracker.")
# Translations Platform URL
-set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/launcher/launcher/" CACHE STRING "URL for the translations platform.")
+set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/prismlauncher/launcher/" CACHE STRING "URL for the translations platform.")
+set(Launcher_TRANSLATION_FILES_URL "https://i18n.prismlauncher.org/" CACHE STRING "URL for the translations files.")
# Matrix Space
-set(Launcher_MATRIX_URL "https://launcher.org/matrix" CACHE STRING "URL to the Matrix Space")
+set(Launcher_MATRIX_URL "https://prismlauncher.org/matrix" CACHE STRING "URL to the Matrix Space")
# Discord URL
-set(Launcher_DISCORD_URL "https://launcher.org/discord" CACHE STRING "URL for the Discord guild.")
+set(Launcher_DISCORD_URL "https://prismlauncher.org/discord" CACHE STRING "URL for the Discord guild.")
# Subreddit URL
-set(Launcher_SUBREDDIT_URL "https://launcher.org/reddit" CACHE STRING "URL for the subreddit.")
+set(Launcher_SUBREDDIT_URL "https://prismlauncher.org/reddit" 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")
# Java downloader
-set(ENABLE_JAVA_DOWNLOADER_DEFAULT ON)
+set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT ON)
-# Although we recommend enabling this, we cannot guarantee binary compatibility on
+# Although we recommend enabling this, we cannot guarantee binary compatibility on
# differing Linux/BSD/etc distributions. Downstream packagers should be explicitly opt-ing into this
# feature if they know it will work with their distribution.
if(UNIX AND NOT APPLE)
- set(ENABLE_JAVA_DOWNLOADER_DEFAULT OFF)
+ set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT OFF)
endif()
# Java downloader
-option(ENABLE_JAVA_DOWNLOADER "Build the java downloader feature" ${ENABLE_JAVA_DOWNLOADER_DEFAULT})
+option(Launcher_ENABLE_JAVA_DOWNLOADER "Build the java downloader feature" ${Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT})
# Native libraries
if(UNIX AND APPLE)
@@ -258,7 +261,7 @@ set(Launcher_MSA_CLIENT_ID "c36a9fb6-4f2a-41ff-90bd-ae7cc92031eb" CACHE STRING "
# By using this key in your builds you accept the terms and conditions laid down in
# https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions
# NOTE: CurseForge requires you to change this if you make any kind of derivative work.
-# This key was issued specifically for Launcher
+# 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})
@@ -394,8 +397,8 @@ if(UNIX AND APPLE)
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_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.5.2/Sparkle-2.5.2.tar.xz" CACHE STRING "URL to Sparkle release archive")
- set(MACOSX_SPARKLE_SHA256 "572dd67ae398a466f19f343a449e1890bac1ef74885b4739f68f979a8a89884b" CACHE STRING "SHA256 checksum for Sparkle release archive")
+ set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.6.4/Sparkle-2.6.4.tar.xz" CACHE STRING "URL to Sparkle release archive")
+ set(MACOSX_SPARKLE_SHA256 "50612a06038abc931f16011d7903b8326a362c1074dabccb718404ce8e585f0b" CACHE STRING "SHA256 checksum for Sparkle release archive")
set(MACOSX_SPARKLE_DIR "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
# directories to look for dependencies
@@ -435,10 +438,10 @@ elseif(UNIX)
set(PLUGIN_DEST_DIR "plugins")
set(BUNDLE_DEST_DIR ".")
set(RESOURCES_DEST_DIR ".")
-
+
# Apps to bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${Launcher_APP_BINARY_NAME}")
-
+
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
endif()
@@ -492,7 +495,7 @@ if(FORCE_BUNDLED_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.
+ # 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)
@@ -529,10 +532,12 @@ else()
endif()
if(NOT cmark_FOUND)
message(STATUS "Using bundled cmark")
+ set(ORIGINAL_BUILD_TESTING ${BUILD_TESTING})
set(BUILD_TESTING 0)
- set(BUILD_SHARED_LIBS 0)
+ set(BUILD_SHARED_LIBS 0)
add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser
add_library(cmark::cmark ALIAS cmark)
+ set(BUILD_TESTING ${ORIGINAL_BUILD_TESTING})
else()
message(STATUS "Using system cmark")
endif()
diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in
index a2b5c2187..b48232b43 100644
--- a/buildconfig/BuildConfig.cpp.in
+++ b/buildconfig/BuildConfig.cpp.in
@@ -81,8 +81,8 @@ Config::Config()
UPDATER_ENABLED = true;
}
- #cmakedefine01 ENABLE_JAVA_DOWNLOADER
- JAVA_DOWNLOADER_ENABLED = ENABLE_JAVA_DOWNLOADER;
+ #cmakedefine01 Launcher_ENABLE_JAVA_DOWNLOADER
+ JAVA_DOWNLOADER_ENABLED = Launcher_ENABLE_JAVA_DOWNLOADER;
GIT_COMMIT = "@Launcher_GIT_COMMIT@";
GIT_TAG = "@Launcher_GIT_TAG@";
@@ -116,16 +116,19 @@ Config::Config()
NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@";
NEWS_OPEN_URL = "@Launcher_NEWS_OPEN_URL@";
HELP_URL = "@Launcher_HELP_URL@";
+ LOGIN_CALLBACK_URL = "@Launcher_LOGIN_CALLBACK_URL@";
IMGUR_CLIENT_ID = "@Launcher_IMGUR_CLIENT_ID@";
MSA_CLIENT_ID = "@Launcher_MSA_CLIENT_ID@";
FLAME_API_KEY = "@Launcher_CURSEFORGE_API_KEY@";
META_URL = "@Launcher_META_URL@";
+ FMLLIBS_BASE_URL = "@Launcher_FMLLIBS_BASE_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@";
+ TRANSLATION_FILES_URL = "@Launcher_TRANSLATION_FILES_URL@";
MATRIX_URL = "@Launcher_MATRIX_URL@";
DISCORD_URL = "@Launcher_DISCORD_URL@";
SUBREDDIT_URL = "@Launcher_SUBREDDIT_URL@";
diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h
index bb633f297..ae705d098 100644
--- a/buildconfig/BuildConfig.h
+++ b/buildconfig/BuildConfig.h
@@ -133,6 +133,11 @@ class Config {
*/
QString HELP_URL;
+ /**
+ * URL that gets opened when the user succesfully logins.
+ */
+ QString LOGIN_CALLBACK_URL;
+
/**
* Client ID you can get from Imgur when you register an application
*/
@@ -165,8 +170,8 @@ class Config {
QString RESOURCE_BASE = "https://resources.download.minecraft.net/";
QString LIBRARY_BASE = "https://libraries.minecraft.net/";
QString IMGUR_BASE_URL = "https://api.imgur.com/3/";
- QString FMLLIBS_BASE_URL = "https://files.prismlauncher.org/fmllibs/"; // FIXME: move into CMakeLists
- QString TRANSLATIONS_BASE_URL = "https://i18n.prismlauncher.org/"; // FIXME: move into CMakeLists
+ QString FMLLIBS_BASE_URL;
+ QString TRANSLATION_FILES_URL;
QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/";
diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in
index f207faf77..f4d044042 100644
--- a/cmake/MacOSXBundleInfo.plist.in
+++ b/cmake/MacOSXBundleInfo.plist.in
@@ -8,6 +8,8 @@
A Minecraft mod wants to access your microphone.
NSDownloadsFolderUsageDescription
Freesm uses access to your Downloads folder to help you more quickly add mods that can't be automatically downloaded to your instance. You can change where Freesm scans for downloaded mods in Settings or the prompt that appears.
+ NSLocalNetworkUsageDescription
+ Minecraft uses the local network to find and connect to LAN servers.
NSPrincipalClass
NSApplication
NSHighResolutionCapable
diff --git a/flake.lock b/flake.lock
index b64c6b472..f145022db 100644
--- a/flake.lock
+++ b/flake.lock
@@ -49,11 +49,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1721379653,
- "narHash": "sha256-8MUgifkJ7lkZs3u99UDZMB4kbOxvMEXQZ31FO3SopZ0=",
+ "lastModified": 1729256560,
+ "narHash": "sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c+cHUJwA=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "1d9c2c9b3e71b9ee663d11c5d298727dace8d374",
+ "rev": "4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index dedfabd30..dc2d95d09 100644
--- a/flake.nix
+++ b/flake.nix
@@ -2,8 +2,10 @@
description = "A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once (Fork of MultiMC)";
nixConfig = {
- extra-substituters = [ "https://cache.garnix.io" ];
- extra-trusted-public-keys = [ "cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=" ];
+ extra-substituters = [ "https://prismlauncher.cachix.org" ];
+ extra-trusted-public-keys = [
+ "prismlauncher.cachix.org-1:9/n/FGyABA2jLUVfY+DEp4hKds/rwO+SCOtbOkDzd+c="
+ ];
};
inputs = {
@@ -83,24 +85,18 @@
formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style);
- overlays.default =
- final: prev:
- let
- version = builtins.substring 0 8 self.lastModifiedDate or "dirty";
- in
- {
- prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
- inherit
- libnbtplusplus
- nix-filter
- self
- version
- ;
- };
-
- prismlauncher = final.callPackage ./nix/wrapper.nix { };
+ overlays.default = final: prev: {
+ prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
+ inherit
+ libnbtplusplus
+ nix-filter
+ self
+ ;
};
+ prismlauncher = final.callPackage ./nix/wrapper.nix { };
+ };
+
packages = forAllSystems (
system:
let
@@ -118,5 +114,24 @@
# Only output them if they're available on the current system
lib.filterAttrs (_: lib.meta.availableOn pkgs.stdenv.hostPlatform) packages
);
+
+ # We put these under legacyPackages as they are meant for CI, not end user consumption
+ legacyPackages = forAllSystems (
+ system:
+ let
+ prismPackages = self.packages.${system};
+ legacyPackages = self.legacyPackages.${system};
+ in
+ {
+ prismlauncher-debug = prismPackages.prismlauncher.override {
+ prismlauncher-unwrapped = legacyPackages.prismlauncher-unwrapped-debug;
+ };
+
+ prismlauncher-unwrapped-debug = prismPackages.prismlauncher-unwrapped.overrideAttrs {
+ cmakeBuildType = "Debug";
+ dontStrip = true;
+ };
+ }
+ );
};
}
diff --git a/flatpak/flite.json b/flatpak/flite.json
new file mode 100644
index 000000000..1bf280af1
--- /dev/null
+++ b/flatpak/flite.json
@@ -0,0 +1,20 @@
+{
+ "name": "flite",
+ "config-opts": [
+ "--enable-shared",
+ "--with-audio=pulseaudio"
+ ],
+ "no-parallel-make": true,
+ "sources": [
+ {
+ "type": "git",
+ "url": "https://github.com/festvox/flite.git",
+ "tag": "v2.2",
+ "commit": "e9e2e37c329dbe98bfeb27a1828ef9a71fa84f88",
+ "x-checker-data": {
+ "type": "git",
+ "tag-pattern": "^v([\\d.]+)$"
+ }
+ }
+ ]
+}
diff --git a/flatpak/libdecor.json b/flatpak/libdecor.json
index 589310a35..1652a2f04 100644
--- a/flatpak/libdecor.json
+++ b/flatpak/libdecor.json
@@ -1,22 +1,18 @@
{
- "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"
- ]
+ "name": "libdecor",
+ "buildsystem": "meson",
+ "config-opts": [
+ "-Ddemo=false"
+ ],
+ "sources": [
+ {
+ "type": "git",
+ "url": "https://gitlab.freedesktop.org/libdecor/libdecor.git",
+ "commit": "c2bd8ad6fa42c0cb17553ce77ad8a87d1f543b1f"
+ }
+ ],
+ "cleanup": [
+ "/include",
+ "/lib/pkgconfig"
+ ]
}
diff --git a/flatpak/org.freesmTeam.freesmlauncher.yml b/flatpak/org.freesmTeam.freesmlauncher.yml
index 622fee63d..b3a3fae42 100644
--- a/flatpak/org.freesmTeam.freesmlauncher.yml
+++ b/flatpak/org.freesmTeam.freesmlauncher.yml
@@ -1,11 +1,9 @@
id: org.freesmTeam.freesmlauncher
runtime: org.kde.Platform
-runtime-version: 6.7
+runtime-version: '6.8'
sdk: org.kde.Sdk
sdk-extensions:
- - org.freedesktop.Sdk.Extension.openjdk21
- org.freedesktop.Sdk.Extension.openjdk17
- - org.freedesktop.Sdk.Extension.openjdk8
command: freesmlauncher
finish-args:
@@ -21,9 +19,12 @@ finish-args:
- --filesystem=xdg-download:ro
# FTBApp import
- --filesystem=~/.ftba:ro
-
-cleanup:
- - /lib/libGLU*
+ # Userspace visibility for manual hugepages configuration
+ # Required for -XX:+UseLargePages
+ - --filesystem=/sys/kernel/mm/hugepages:ro
+ # Userspace visibility for transparent hugepages configuration
+ # Required for -XX:+UseTransparentHugePages
+ - --filesystem=/sys/kernel/mm/transparent_hugepage:ro
modules:
# Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31)
@@ -32,50 +33,39 @@ modules:
# Needed for proper Wayland support
- libdecor.json
+ # Text to Speech in the game
+ - flite.json
+
- name: freesmlauncher
buildsystem: cmake-ninja
builddir: true
config-opts:
- -DLauncher_BUILD_PLATFORM=flatpak
+ # This allows us to manage and update Java independently of this Flatpak
+ - -DLauncher_ENABLE_JAVA_DOWNLOADER=ON
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
build-options:
env:
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac
+ run-tests: true
sources:
- type: dir
path: ../
- - name: openjdk
- buildsystem: simple
- build-commands:
- - mkdir -p /app/jdk/
- - /usr/lib/sdk/openjdk21/install.sh
- - mv /app/jre /app/jdk/21
- - /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:BOOL=ON
+ - -DGLFW_BUILD_WAYLAND:BOOL=ON
- -DGLFW_BUILD_DOCS:BOOL=OFF
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
+ commit: 7b6aead9fb88b3623e3b3725ebb42670cbe4c579 # 3.4
- type: patch
- path: patches/0007-Platform-Prefer-Wayland-over-X11.patch
+ path: patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch
cleanup:
- /include
- /lib/cmake
@@ -85,8 +75,8 @@ modules:
buildsystem: autotools
sources:
- type: archive
- url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.2.tar.xz
- sha256: c8bee4790d9058bacc4b6246456c58021db58a87ddda1a9d0139bf5f18f1f240
+ url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.3.tar.xz
+ sha256: f8dd7566adb74147fab9964680b6bbadee87cf406a7fcff51718a5e6949b841c
x-checker-data:
type: anitya
project-id: 14957
@@ -108,8 +98,8 @@ modules:
sources:
- type: archive
dest-filename: gamemode.tar.gz
- url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.1
- sha256: 969cf85b5ca3944f3e315cd73a0ee9bea4f9c968cd7d485e9f4745bc1e679c4e
+ url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.2
+ sha256: 2886d4ce543c78bd2a364316d5e7fd59ef06b71de63f896b37c6d3dc97658f60
x-checker-data:
type: json
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest
diff --git a/flatpak/patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch b/flatpak/patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch
deleted file mode 100644
index 9130e856c..000000000
--- a/flatpak/patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch
+++ /dev/null
@@ -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,
diff --git a/flatpak/patches/0005-Add-warning-about-being-an-unofficial-patch.patch b/flatpak/patches/0005-Add-warning-about-being-an-unofficial-patch.patch
deleted file mode 100644
index b031d739f..000000000
--- a/flatpak/patches/0005-Add-warning-about-being-an-unofficial-patch.patch
+++ /dev/null
@@ -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;
- }
-
diff --git a/flatpak/patches/0007-Platform-Prefer-Wayland-over-X11.patch b/flatpak/patches/0007-Platform-Prefer-Wayland-over-X11.patch
deleted file mode 100644
index 4eeb81309..000000000
--- a/flatpak/patches/0007-Platform-Prefer-Wayland-over-X11.patch
+++ /dev/null
@@ -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)
diff --git a/flatpak/patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch b/flatpak/patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch
new file mode 100644
index 000000000..70cec9981
--- /dev/null
+++ b/flatpak/patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch
@@ -0,0 +1,59 @@
+From 9997ae55a47de469ea26f8437c30b51483abda5f Mon Sep 17 00:00:00 2001
+From: Dan Klishch
+Date: Sat, 30 Sep 2023 23:38:05 -0400
+Subject: Defer setting cursor position until the cursor is locked
+
+---
+ src/wl_platform.h | 3 +++
+ src/wl_window.c | 14 ++++++++++++--
+ 2 files changed, 15 insertions(+), 2 deletions(-)
+
+diff --git a/src/wl_platform.h b/src/wl_platform.h
+index ca34f66e..cd1f227f 100644
+--- a/src/wl_platform.h
++++ b/src/wl_platform.h
+@@ -403,6 +403,9 @@ typedef struct _GLFWwindowWayland
+ int scaleSize;
+ int compositorPreferredScale;
+
++ double askedCursorPosX, askedCursorPosY;
++ GLFWbool didAskForSetCursorPos;
++
+ struct zwp_relative_pointer_v1* relativePointer;
+ struct zwp_locked_pointer_v1* lockedPointer;
+ struct zwp_confined_pointer_v1* confinedPointer;
+diff --git a/src/wl_window.c b/src/wl_window.c
+index 1de26558..0df16747 100644
+--- a/src/wl_window.c
++++ b/src/wl_window.c
+@@ -2586,8 +2586,9 @@ void _glfwGetCursorPosWayland(_GLFWwindow* window, double* xpos, double* ypos)
+
+ void _glfwSetCursorPosWayland(_GLFWwindow* window, double x, double y)
+ {
+- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
+- "Wayland: The platform does not support setting the cursor position");
++ window->wl.didAskForSetCursorPos = true;
++ window->wl.askedCursorPosX = x;
++ window->wl.askedCursorPosY = y;
+ }
+
+ void _glfwSetCursorModeWayland(_GLFWwindow* window, int mode)
+@@ -2819,6 +2820,15 @@ static const struct zwp_relative_pointer_v1_listener relativePointerListener =
+ static void lockedPointerHandleLocked(void* userData,
+ struct zwp_locked_pointer_v1* lockedPointer)
+ {
++ _GLFWwindow* window = userData;
++
++ if (window->wl.didAskForSetCursorPos)
++ {
++ window->wl.didAskForSetCursorPos = false;
++ zwp_locked_pointer_v1_set_cursor_position_hint(window->wl.lockedPointer,
++ wl_fixed_from_double(window->wl.askedCursorPosX),
++ wl_fixed_from_double(window->wl.askedCursorPosY));
++ }
+ }
+
+ static void lockedPointerHandleUnlocked(void* userData,
+--
+2.42.0
+
diff --git a/flatpak/patches/weird_libdecor.patch b/flatpak/patches/weird_libdecor.patch
deleted file mode 100644
index 3a400b820..000000000
--- a/flatpak/patches/weird_libdecor.patch
+++ /dev/null
@@ -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 *
diff --git a/garnix.yaml b/garnix.yaml
deleted file mode 100644
index a7c1b48a9..000000000
--- a/garnix.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-builds:
- exclude:
- # Currently broken on Garnix's end
- - "*.x86_64-darwin.*"
- include:
- - "checks.x86_64-linux.*"
- - "packages.x86_64-linux.*"
- - "packages.aarch64-linux.*"
- - "packages.x86_64-darwin.*"
- - "packages.aarch64-darwin.*"
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 3cc368745..b78f801f4 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -48,6 +48,7 @@
#include "net/PasteUpload.h"
#include "pathmatcher/MultiMatcher.h"
#include "pathmatcher/SimplePrefixMatcher.h"
+#include "tasks/Task.h"
#include "tools/GenericProfiler.h"
#include "ui/InstanceWindow.h"
#include "ui/MainWindow.h"
@@ -67,8 +68,10 @@
#include "ui/pages/global/MinecraftPage.h"
#include "ui/pages/global/ProxyPage.h"
+#include "ui/setupwizard/AutoJavaWizardPage.h"
#include "ui/setupwizard/JavaWizardPage.h"
#include "ui/setupwizard/LanguageWizardPage.h"
+#include "ui/setupwizard/LoginWizardPage.h"
#include "ui/setupwizard/PasteWizardPage.h"
#include "ui/setupwizard/SetupWizard.h"
#include "ui/setupwizard/ThemeWizardPage.h"
@@ -559,6 +562,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("IconTheme", QString("fluent_dark"));
m_settings->registerSetting("ApplicationTheme", QString("freesm"));
m_settings->registerSetting("BackgroundCat", QString("typescript"));
+ m_settings->registerSetting("BackgroundCat", QString("miside-screenshot"));
// Remembered state
m_settings->registerSetting("LastUsedGroupForNewInstance", QString());
@@ -650,6 +654,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
auto defaultEnableAutoJava = m_settings->get("JavaPath").toString().isEmpty();
m_settings->registerSetting("AutomaticJavaSwitch", defaultEnableAutoJava);
m_settings->registerSetting("AutomaticJavaDownload", defaultEnableAutoJava);
+ m_settings->registerSetting("UserAskedAboutAutomaticJavaDownload", false);
// Legacy settings
m_settings->registerSetting("OnlineFixes", false);
@@ -777,6 +782,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// FTBApp instances
m_settings->registerSetting("FTBAppInstancesPath", "");
+ // Custom Technic Client ID
+ m_settings->registerSetting("TechnicClientID", "");
+
// Init page provider
{
m_globalSettingsProvider = std::make_shared(tr("Settings"));
@@ -1019,7 +1027,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
// notify user if /tmp is mounted with `noexec` (#1693)
- {
+ QString jvmArgs = m_settings->get("JvmArgs").toString();
+ if (jvmArgs.indexOf("java.io.tmpdir") == -1) { /* java.io.tmpdir is a valid workaround, so don't annoy */
bool is_tmp_noexec = false;
#if defined(Q_OS_LINUX)
@@ -1039,7 +1048,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
if (is_tmp_noexec) {
auto infoMsg =
tr("Your /tmp directory is currently mounted with the 'noexec' flag enabled.\n"
- "Some versions of Minecraft may not launch.\n");
+ "Some versions of Minecraft may not launch.\n"
+ "\n"
+ "You may solve this issue by remounting /tmp as 'exec' or setting "
+ "the java.io.tmpdir JVM argument to a writeable directory in a "
+ "filesystem where the 'exec' flag is set (e.g., /home/user/.local/tmp)\n");
auto msgBox = new QMessageBox(QMessageBox::Information, tr("Incompatible system configuration"), infoMsg, QMessageBox::Ok);
msgBox->setDefaultButton(QMessageBox::Ok);
msgBox->setAttribute(Qt::WA_DeleteOnClose);
@@ -1060,6 +1073,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
bool Application::createSetupWizard()
{
bool javaRequired = [&]() {
+ if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_settings->get("AutomaticJavaDownload").toBool()) {
+ return false;
+ }
bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool();
if (ignoreJavaWizard) {
return false;
@@ -1072,18 +1088,17 @@ bool Application::createSetupWizard()
}
QString currentJavaPath = settings()->get("JavaPath").toString();
QString actualPath = FS::ResolveExecutable(currentJavaPath);
- if (actualPath.isNull()) {
- return true;
- }
- return false;
+ return actualPath.isNull();
}();
+ bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() &&
+ !m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool();
bool languageRequired = settings()->get("Language").toString().isEmpty();
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
bool validWidgets = m_themeManager->isValidApplicationTheme(settings()->get("ApplicationTheme").toString());
bool validIcons = m_themeManager->isValidIconTheme(settings()->get("IconTheme").toString());
+ bool login = !m_accounts->anyAccountIsValid() && capabilities() & Application::SupportsMSA;
bool themeInterventionRequired = !validWidgets || !validIcons;
- bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired;
-
+ bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired || askjava || login;
if (wizardRequired) {
// set default theme after going into theme wizard
if (!validIcons)
@@ -1100,6 +1115,8 @@ bool Application::createSetupWizard()
if (javaRequired) {
m_setupWizard->addPage(new JavaWizardPage(m_setupWizard));
+ } else if (askjava) {
+ m_setupWizard->addPage(new AutoJavaWizardPage(m_setupWizard));
}
if (pasteInterventionRequired) {
@@ -1110,11 +1127,14 @@ bool Application::createSetupWizard()
m_setupWizard->addPage(new ThemeWizardPage(m_setupWizard));
}
+ if (login) {
+ m_setupWizard->addPage(new LoginWizardPage(m_setupWizard));
+ }
connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished);
m_setupWizard->show();
- return true;
}
- return false;
+
+ return wizardRequired || login;
}
bool Application::updaterEnabled()
@@ -1259,16 +1279,23 @@ Application::~Application()
void Application::messageReceived(const QByteArray& message)
{
- if (status() != Initialized) {
- qDebug() << "Received message" << message << "while still initializing. It will be ignored.";
- return;
- }
-
ApplicationMessage received;
received.parse(message);
auto& command = received.command;
+ if (status() != Initialized) {
+ bool isLoginAtempt = false;
+ if (command == "import") {
+ QString url = received.args["url"];
+ isLoginAtempt = !url.isEmpty() && normalizeImportUrl(url).scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME;
+ }
+ if (!isLoginAtempt) {
+ qDebug() << "Received message" << message << "while still initializing. It will be ignored.";
+ return;
+ }
+ }
+
if (command == "activate") {
showMainWindow();
} else if (command == "import") {
@@ -1355,6 +1382,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
if (m_updateRunning) {
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
} else if (instance->canLaunch()) {
+ QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[instance->id()];
auto window = extras.window;
if (window) {
@@ -1379,7 +1407,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed);
connect(controller.get(), &LaunchController::aborted, this, [this] { controllerFailed(tr("Aborted")); });
addRunningInstance();
- controller->start();
+ QMetaObject::invokeMethod(controller.get(), &Task::start, Qt::QueuedConnection);
return true;
} else if (instance->isRunning()) {
showInstanceWindow(instance, "console");
@@ -1397,9 +1425,11 @@ bool Application::kill(InstancePtr instance)
qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running.";
return false;
}
+ QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[instance->id()];
// NOTE: copy of the shared pointer keeps it alive
auto controller = extras.controller;
+ locker.unlock();
if (controller) {
return controller->abort();
}
@@ -1453,12 +1483,14 @@ void Application::controllerSucceeded()
if (!controller)
return;
auto id = controller->id();
+
+ QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[id];
// on success, do...
if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) {
if (extras.window) {
- extras.window->close();
+ QMetaObject::invokeMethod(extras.window, &QWidget::close, Qt::QueuedConnection);
}
}
extras.controller.reset();
@@ -1478,6 +1510,7 @@ void Application::controllerFailed(const QString& error)
if (!controller)
return;
auto id = controller->id();
+ QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[id];
// on failure, do... nothing
@@ -1535,6 +1568,7 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
if (!instance)
return nullptr;
auto id = instance->id();
+ QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[id];
auto& window = extras.window;
@@ -1572,6 +1606,7 @@ void Application::on_windowClose()
m_openWindows--;
auto instWindow = qobject_cast(QObject::sender());
if (instWindow) {
+ QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[instWindow->instanceId()];
extras.window = nullptr;
if (extras.controller) {
@@ -1819,7 +1854,7 @@ bool Application::handleDataMigration(const QString& currentData,
matcher->add(std::make_shared("themes/"));
ProgressDialog diag;
- DataMigrationTask task(nullptr, oldData, currentData, matcher);
+ DataMigrationTask task(oldData, currentData, matcher);
if (diag.execWithTask(&task)) {
qDebug() << "<> Migration succeeded";
setDoNotMigrate();
@@ -1853,7 +1888,36 @@ QUrl Application::normalizeImportUrl(QString const& url)
return QUrl::fromUserInput(url);
}
}
+
const QString Application::javaPath()
{
return m_settings->get("JavaDir").toString();
}
+
+void Application::addQSavePath(QString path)
+{
+ QMutexLocker locker(&m_qsaveResourcesMutex);
+ m_qsaveResources[path] = m_qsaveResources.value(path, 0) + 1;
+}
+
+void Application::removeQSavePath(QString path)
+{
+ QMutexLocker locker(&m_qsaveResourcesMutex);
+ auto count = m_qsaveResources.value(path, 0) - 1;
+ if (count <= 0) {
+ m_qsaveResources.remove(path);
+ } else {
+ m_qsaveResources[path] = count;
+ }
+}
+
+bool Application::checkQSavePath(QString path)
+{
+ QMutexLocker locker(&m_qsaveResourcesMutex);
+ for (auto partialPath : m_qsaveResources.keys()) {
+ if (path.startsWith(partialPath) && m_qsaveResources.value(partialPath, 0) > 0) {
+ return true;
+ }
+ }
+ return false;
+}
\ No newline at end of file
diff --git a/launcher/Application.h b/launcher/Application.h
index 7432c9683..164ec3a4f 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -42,6 +42,7 @@
#include
#include
#include
+#include
#include
#include
@@ -81,6 +82,12 @@ class Index;
#endif
#define APPLICATION (static_cast(QCoreApplication::instance()))
+// Used for checking if is a test
+#if defined(APPLICATION_DYN)
+#undef APPLICATION_DYN
+#endif
+#define APPLICATION_DYN (dynamic_cast(QCoreApplication::instance()))
+
class Application : public QApplication {
// friends for the purpose of limiting access to deprecated stuff
Q_OBJECT
@@ -272,6 +279,7 @@ class Application : public QApplication {
shared_qobject_ptr controller;
};
std::map m_instanceExtras;
+ mutable QMutex m_instanceExtrasMutex;
// main state variables
size_t m_openWindows = 0;
@@ -297,4 +305,13 @@ class Application : public QApplication {
QList m_urlsToImport;
QString m_instanceIdToShowWindowOf;
std::unique_ptr logFile;
+
+ public:
+ void addQSavePath(QString);
+ void removeQSavePath(QString);
+ bool checkQSavePath(QString);
+
+ private:
+ QHash m_qsaveResources;
+ mutable QMutex m_qsaveResourcesMutex;
};
diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h
index 8c80331bc..2be28d1ec 100644
--- a/launcher/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -181,7 +181,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this createUpdateTask() = 0;
/// returns a valid launcher (task container)
virtual shared_qobject_ptr createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) = 0;
@@ -215,7 +215,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this
-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)
+DataMigrationTask::DataMigrationTask(const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathMatcher)
+ : Task(), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
{
m_copy.matcher(m_pathMatcher.get()).whitelist(true);
}
diff --git a/launcher/DataMigrationTask.h b/launcher/DataMigrationTask.h
index aba9f2399..fc613cd5e 100644
--- a/launcher/DataMigrationTask.h
+++ b/launcher/DataMigrationTask.h
@@ -18,7 +18,7 @@
class DataMigrationTask : public Task {
Q_OBJECT
public:
- explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
+ explicit DataMigrationTask(const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
~DataMigrationTask() override = default;
protected:
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index b5c2c95cf..512de28c2 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -45,7 +45,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -54,6 +53,7 @@
#include
#include "DesktopServices.h"
+#include "PSaveFile.h"
#include "StringUtils.h"
#if defined Q_OS_WIN32
@@ -191,8 +191,8 @@ void ensureExists(const QDir& dir)
void write(const QString& filename, const QByteArray& data)
{
ensureExists(QFileInfo(filename).dir());
- QSaveFile file(filename);
- if (!file.open(QSaveFile::WriteOnly)) {
+ PSaveFile file(filename);
+ if (!file.open(PSaveFile::WriteOnly)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
}
if (data.size() != file.write(data)) {
@@ -213,8 +213,8 @@ void appendSafe(const QString& filename, const QByteArray& data)
buffer = QByteArray();
}
buffer.append(data);
- QSaveFile file(filename);
- if (!file.open(QSaveFile::WriteOnly)) {
+ PSaveFile file(filename);
+ if (!file.open(PSaveFile::WriteOnly)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
}
if (buffer.size() != file.write(buffer)) {
@@ -921,6 +921,10 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (destination.isEmpty()) {
destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name));
}
+ if (!ensureFilePathExists(destination)) {
+ qWarning() << "Destination path can't be created!";
+ return false;
+ }
#if defined(Q_OS_MACOS)
// Create the Application
QDir applicationDirectory =
@@ -967,8 +971,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (!args.empty())
argstring = " \"" + args.join("\" \"") + "\"";
- stream << "#!/bin/bash"
- << "\n";
+ stream << "#!/bin/bash" << "\n";
stream << "\"" << target << "\" " << argstring << "\n";
stream.flush();
@@ -1012,12 +1015,9 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (!args.empty())
argstring = " '" + args.join("' '") + "'";
- stream << "[Desktop Entry]"
- << "\n";
- stream << "Type=Application"
- << "\n";
- stream << "Categories=Game;ActionGame;AdventureGame;Simulation"
- << "\n";
+ stream << "[Desktop Entry]" << "\n";
+ stream << "Type=Application" << "\n";
+ stream << "Categories=Game;ActionGame;AdventureGame;Simulation" << "\n";
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
stream << "Name=" << name.toLocal8Bit() << "\n";
if (!icon.isEmpty()) {
diff --git a/launcher/Filter.h b/launcher/Filter.h
index a8c9c14d8..ae835e724 100644
--- a/launcher/Filter.h
+++ b/launcher/Filter.h
@@ -57,5 +57,5 @@ class ExactListFilter : public Filter {
bool accepts(const QString& value) override;
private:
- const QStringList& m_pattern;
+ QStringList m_pattern;
};
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index ff2d37723..0220a4144 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -173,7 +173,11 @@ void InstanceCopyTask::copyFinished()
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);
+ try {
+ FS::write(allowed_symlinks_file.filePath(), allowed_symlinks);
+ } catch (const FS::FileSystemException& e) {
+ qCritical() << "Failed to write symlink :" << e.cause();
+ }
}
emitSucceeded();
diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp
index 9c17dfc9f..bd3514798 100644
--- a/launcher/InstanceCreationTask.cpp
+++ b/launcher/InstanceCreationTask.cpp
@@ -38,22 +38,29 @@ void InstanceCreationTask::executeTask()
// files scheduled to, and we'd better not let the user abort in the middle of it, since it'd
// put the instance in an invalid state.
if (shouldOverride()) {
+ bool deleteFailed = false;
+
setAbortable(false);
setStatus(tr("Removing old conflicting files..."));
qDebug() << "Removing old files";
- for (auto path : m_files_to_remove) {
+ for (const QString& path : m_files_to_remove) {
if (!QFile::exists(path))
continue;
+
qDebug() << "Removing" << path;
- if (!FS::deletePath(path)) {
- qCritical() << "Couldn't remove the old conflicting files.";
- emitFailed(tr("Failed to remove old conflicting files."));
- return;
+
+ if (!QFile::remove(path)) {
+ qCritical() << "Could not remove" << path;
+ deleteFailed = true;
}
}
- }
- emitSucceeded();
- return;
+ if (deleteFailed) {
+ emitFailed(tr("Failed to remove old conflicting files."));
+ return;
+ }
+ }
+ if (!m_abort)
+ emitSucceeded();
}
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index 57cc77527..71630656d 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -69,9 +69,11 @@ bool InstanceImportTask::abort()
if (!canAbort())
return false;
- if (task)
- task->abort();
- return Task::abort();
+ bool wasAborted = false;
+ if (m_task)
+ wasAborted = m_task->abort();
+ Task::abort();
+ return wasAborted;
}
void InstanceImportTask::executeTask()
@@ -104,7 +106,7 @@ void InstanceImportTask::downloadFromUrl()
connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed);
connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted);
- task.reset(filesNetJob);
+ m_task.reset(filesNetJob);
filesNetJob->start();
}
@@ -193,7 +195,7 @@ void InstanceImportTask::processZipPack()
stepProgress(*progressStep);
});
- connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished);
+ connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished, Qt::QueuedConnection);
connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
progressStep->state = TaskStepState::Failed;
@@ -210,12 +212,13 @@ void InstanceImportTask::processZipPack()
progressStep->status = status;
stepProgress(*progressStep);
});
- task.reset(zipTask);
+ m_task.reset(zipTask);
zipTask->start();
}
void InstanceImportTask::extractFinished()
{
+ setAbortable(false);
QDir extractDir(m_stagingPath);
qDebug() << "Fixing permissions for extracted pack files...";
@@ -289,8 +292,11 @@ void InstanceImportTask::processFlame()
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());
+ auto weak = inst_creation_task.toWeakRef();
+ connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
+ if (auto sp = weak.lock()) {
+ setOverride(sp->shouldOverride(), sp->originalInstanceID());
+ }
emitSucceeded();
});
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
@@ -299,11 +305,12 @@ void InstanceImportTask::processFlame()
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
- connect(this, &Task::aborted, inst_creation_task.get(), &InstanceCreationTask::abort);
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
- inst_creation_task->start();
+ m_task.reset(inst_creation_task);
+ setAbortable(true);
+ m_task->start();
}
void InstanceImportTask::processTechnic()
@@ -350,7 +357,7 @@ void InstanceImportTask::processMultiMC()
void InstanceImportTask::processModrinth()
{
- ModrinthCreationTask* inst_creation_task = nullptr;
+ shared_qobject_ptr 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());
@@ -367,7 +374,7 @@ void InstanceImportTask::processModrinth()
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);
+ makeShared(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
} else {
QString pack_id;
if (!m_sourceUrl.isEmpty()) {
@@ -376,7 +383,7 @@ void InstanceImportTask::processModrinth()
}
// FIXME: Find a way to get the ID in directly imported ZIPs
- inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id);
+ inst_creation_task = makeShared(m_stagingPath, m_globalSettings, m_parent, pack_id);
}
inst_creation_task->setName(*this);
@@ -384,20 +391,23 @@ void InstanceImportTask::processModrinth()
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());
+ auto weak = inst_creation_task.toWeakRef();
+ connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
+ if (auto sp = weak.lock()) {
+ setOverride(sp->shouldOverride(), sp->originalInstanceID());
+ }
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);
- connect(inst_creation_task, &Task::aborted, this, &Task::abort);
- connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable);
-
- inst_creation_task->start();
+ 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.get(), &Task::aborted, this, &Task::abort);
+ connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
+
+ m_task.reset(inst_creation_task);
+ setAbortable(true);
+ m_task->start();
}
diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h
index cf86af4ea..8884e0801 100644
--- a/launcher/InstanceImportTask.h
+++ b/launcher/InstanceImportTask.h
@@ -40,16 +40,13 @@
#include
#include "InstanceTask.h"
-#include
-#include
-
class QuaZip;
class InstanceImportTask : public InstanceTask {
Q_OBJECT
public:
explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap&& extra_info = {});
-
+ virtual ~InstanceImportTask() = default;
bool abort() override;
protected:
@@ -70,7 +67,7 @@ class InstanceImportTask : public InstanceTask {
private: /* data */
QUrl m_sourceUrl;
QString m_archivePath;
- Task::Ptr task;
+ Task::Ptr m_task;
enum class ModpackType {
Unknown,
MultiMC,
diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp
index 3cbf9f9d5..188edb943 100644
--- a/launcher/JavaCommon.cpp
+++ b/launcher/JavaCommon.cpp
@@ -116,7 +116,7 @@ void JavaCommon::TestCheck::run()
emit finished();
return;
}
- checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0, this));
+ checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0));
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
checker->start();
}
@@ -128,7 +128,7 @@ void JavaCommon::TestCheck::checkFinished(const JavaChecker::Result& result)
emit finished();
return;
}
- checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0, this));
+ checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0));
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
checker->start();
}
diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp
index 73800574f..aaf84eac4 100644
--- a/launcher/LaunchController.cpp
+++ b/launcher/LaunchController.cpp
@@ -53,6 +53,7 @@
#include
#include
#include
+#include
#include
#include "BuildConfig.h"
@@ -60,7 +61,7 @@
#include "launch/steps/TextPrint.h"
#include "tasks/Task.h"
-LaunchController::LaunchController(QObject* parent) : Task(parent) {}
+LaunchController::LaunchController() : Task() {}
void LaunchController::executeTask()
{
diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h
index 6e2a94258..af52ad450 100644
--- a/launcher/LaunchController.h
+++ b/launcher/LaunchController.h
@@ -47,7 +47,7 @@ class LaunchController : public Task {
public:
void executeTask() override;
- LaunchController(QObject* parent = nullptr);
+ LaunchController();
virtual ~LaunchController() = default;
void setInstance(InstancePtr instance) { m_instance = instance; }
diff --git a/launcher/Launcher.in b/launcher/Launcher.in
index 1a23f2555..706d7022b 100755
--- a/launcher/Launcher.in
+++ b/launcher/Launcher.in
@@ -39,8 +39,16 @@ if [ "x$DEPS_LIST" = "x" ]; then
# Just to be sure...
chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}"
+ ARGS=("${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}")
+
+ if [ -f portable.txt ]; then
+ ARGS+=("-d" "${LAUNCHER_DIR}")
+ fi
+
+ ARGS+=("$@")
+
# Run the launcher
- exec -a "${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"
+ exec -a "${ARGS[@]}"
# Run the launcher in valgrind
# valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"
diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp
index fadd64e68..35ce4e0e5 100644
--- a/launcher/LoggedProcess.cpp
+++ b/launcher/LoggedProcess.cpp
@@ -39,7 +39,8 @@
#include
#include "MessageLevel.h"
-LoggedProcess::LoggedProcess(QObject* parent) : QProcess(parent)
+LoggedProcess::LoggedProcess(const QTextCodec* output_codec, QObject* parent)
+ : QProcess(parent), m_err_decoder(output_codec), m_out_decoder(output_codec)
{
// QProcess has a strange interface... let's map a lot of those into a few.
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
diff --git a/launcher/LoggedProcess.h b/launcher/LoggedProcess.h
index 46bdaa830..75ba15dfd 100644
--- a/launcher/LoggedProcess.h
+++ b/launcher/LoggedProcess.h
@@ -49,7 +49,7 @@ class LoggedProcess : public QProcess {
enum State { NotRunning, Starting, FailedToStart, Running, Finished, Crashed, Aborted };
public:
- explicit LoggedProcess(QObject* parent = 0);
+ explicit LoggedProcess(const QTextCodec* output_codec = QTextCodec::codecForLocale(), QObject* parent = 0);
virtual ~LoggedProcess();
State state() const;
@@ -80,8 +80,8 @@ class LoggedProcess : public QProcess {
QStringList reprocess(const QByteArray& data, QTextDecoder& decoder);
private:
- QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale());
- QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale());
+ QTextDecoder m_err_decoder;
+ QTextDecoder m_out_decoder;
QString m_leftover_line;
bool m_killed = false;
State m_state = NotRunning;
diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp
index dcf3d566f..b38aca17a 100644
--- a/launcher/MMCZip.cpp
+++ b/launcher/MMCZip.cpp
@@ -378,7 +378,7 @@ std::optional extractDir(QString fileCompressed, QString dir)
if (fileInfo.size() == 22) {
return QStringList();
}
- qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
+ qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
;
return std::nullopt;
}
@@ -395,7 +395,7 @@ std::optional extractDir(QString fileCompressed, QString subdir, QS
if (fileInfo.size() == 22) {
return QStringList();
}
- qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
+ qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
;
return std::nullopt;
}
@@ -412,7 +412,7 @@ bool extractFile(QString fileCompressed, QString file, QString target)
if (fileInfo.size() == 22) {
return true;
}
- qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
+ qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
return false;
}
return extractRelFile(&zip, file, target);
@@ -577,7 +577,7 @@ auto ExtractZipTask::extractZip() -> ZipResult
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size()));
auto original_name = relative_file_name;
- setStatus("Unziping: " + relative_file_name);
+ setStatus("Unpacking: " + relative_file_name);
// Fix subdirs/files ending with a / getting transformed into absolute paths
if (relative_file_name.startsWith('/'))
diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h
index 1635f8b32..d81df9d81 100644
--- a/launcher/MMCZip.h
+++ b/launcher/MMCZip.h
@@ -153,6 +153,7 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q
#if defined(LAUNCHER_APPLICATION)
class ExportToZipTask : public Task {
+ Q_OBJECT
public:
ExportToZipTask(QString outputPath,
QDir dir,
@@ -207,6 +208,7 @@ class ExportToZipTask : public Task {
};
class ExtractZipTask : public Task {
+ Q_OBJECT
public:
ExtractZipTask(QString input, QDir outputDir, QString subdirectory = "")
: ExtractZipTask(std::make_shared(input), outputDir, subdirectory)
diff --git a/launcher/MTPixmapCache.h b/launcher/MTPixmapCache.h
index b6bd13045..0ba9c5ac8 100644
--- a/launcher/MTPixmapCache.h
+++ b/launcher/MTPixmapCache.h
@@ -101,7 +101,7 @@ class PixmapCache final : public QObject {
*/
bool _markCacheMissByEviciton()
{
- static constexpr uint maxInt = static_cast(std::numeric_limits::max());
+ static constexpr uint maxCache = static_cast(std::numeric_limits::max()) / 4;
static constexpr uint step = 10240;
static constexpr int oneSecond = 1000;
@@ -118,8 +118,8 @@ class PixmapCache final : public QObject {
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
// increase the cache size
uint newSize = _cacheLimit() + step;
- if (newSize >= maxInt) { // increase it until you overflow :D
- newSize = maxInt;
+ if (newSize >= maxCache) { // increase it until you overflow :D
+ newSize = maxCache;
qDebug() << m_consecutive_fast_evicitons
<< tr("pixmap cache misses by eviction happened too fast, doing nothing as the cache size reached it's limit");
} else {
diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h
index 3ee38e76c..3d01c9d33 100644
--- a/launcher/NullInstance.h
+++ b/launcher/NullInstance.h
@@ -53,7 +53,7 @@ class NullInstance : public BaseInstance {
QSet traits() const override { return {}; };
QString instanceConfigFolder() const override { return instanceRoot(); };
shared_qobject_ptr createLaunchTask(AuthSessionPtr, MinecraftTarget::Ptr) override { return nullptr; }
- shared_qobject_ptr createUpdateTask([[maybe_unused]] Net::Mode mode) override { return nullptr; }
+ QList createUpdateTask() override { return {}; }
QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); }
QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); }
QMap getVariables() override { return QMap(); }
diff --git a/launcher/PSaveFile.h b/launcher/PSaveFile.h
new file mode 100644
index 000000000..ba6154ad8
--- /dev/null
+++ b/launcher/PSaveFile.h
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023-2024 Trial97
+ *
+ * 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 .
+ */
+#pragma once
+
+#include
+#include
+#include "Application.h"
+
+#if defined(LAUNCHER_APPLICATION)
+
+/* PSaveFile
+ * A class that mimics QSaveFile for Windows.
+ *
+ * When reading resources, we need to avoid accessing temporary files
+ * generated by QSaveFile. If we start reading such a file, we may
+ * inadvertently keep it open while QSaveFile is trying to remove it,
+ * or we might detect the file just before it is removed, leading to
+ * race conditions and errors.
+ *
+ * Unfortunately, QSaveFile doesn't provide a way to retrieve the
+ * temporary file name or to set a specific template for the temporary
+ * file name it uses. By default, QSaveFile appends a `.XXXXXX` suffix
+ * to the original file name, where the `XXXXXX` part is dynamically
+ * generated to ensure uniqueness.
+ *
+ * This class acts like a lock by adding and removing the target file
+ * name into/from a global string set, helping to manage access to
+ * files during critical operations.
+ *
+ * Note: Please do not use the `setFileName` function directly, as it
+ * is not virtual and cannot be overridden.
+ */
+class PSaveFile : public QSaveFile {
+ public:
+ PSaveFile(const QString& name) : QSaveFile(name) { addPath(name); }
+ PSaveFile(const QString& name, QObject* parent) : QSaveFile(name, parent) { addPath(name); }
+ virtual ~PSaveFile()
+ {
+ if (auto app = APPLICATION_DYN) {
+ app->removeQSavePath(m_absoluteFilePath);
+ }
+ }
+
+ private:
+ void addPath(const QString& path)
+ {
+ m_absoluteFilePath = QFileInfo(path).absoluteFilePath() + "."; // add dot for tmp files only
+ if (auto app = APPLICATION_DYN) {
+ app->addQSavePath(m_absoluteFilePath);
+ }
+ }
+ QString m_absoluteFilePath;
+};
+#else
+#define PSaveFile QSaveFile
+#endif
\ No newline at end of file
diff --git a/launcher/RuntimeContext.h b/launcher/RuntimeContext.h
index c57140d28..85304a5bc 100644
--- a/launcher/RuntimeContext.h
+++ b/launcher/RuntimeContext.h
@@ -20,13 +20,13 @@
#include
#include
+#include "SysInfo.h"
#include "settings/SettingsObject.h"
struct RuntimeContext {
QString javaArchitecture;
QString javaRealArchitecture;
- QString javaPath;
- QString system;
+ QString system = SysInfo::currentSystem();
QString mappedJavaRealArchitecture() const
{
@@ -45,8 +45,6 @@ struct RuntimeContext {
{
javaArchitecture = instanceSettings->get("JavaArchitecture").toString();
javaRealArchitecture = instanceSettings->get("JavaRealArchitecture").toString();
- javaPath = instanceSettings->get("JavaPath").toString();
- system = currentSystem();
}
QString getClassifier() const { return system + "-" + mappedJavaRealArchitecture(); }
@@ -68,21 +66,4 @@ struct RuntimeContext {
return x;
}
-
- static QString currentSystem()
- {
-#if defined(Q_OS_LINUX)
- return "linux";
-#elif defined(Q_OS_MACOS)
- return "osx";
-#elif defined(Q_OS_WINDOWS)
- return "windows";
-#elif defined(Q_OS_FREEBSD)
- return "freebsd";
-#elif defined(Q_OS_OPENBSD)
- return "openbsd";
-#else
- return "unknown";
-#endif
- }
};
diff --git a/launcher/SysInfo.cpp b/launcher/SysInfo.cpp
index 0dfa74de7..cfcf63805 100644
--- a/launcher/SysInfo.cpp
+++ b/launcher/SysInfo.cpp
@@ -81,9 +81,9 @@ QString getSupportedJavaArchitecture()
if (arch == "arm64")
return "mac-os-arm64";
if (arch.contains("64"))
- return "mac-os-64";
+ return "mac-os-x64";
if (arch.contains("86"))
- return "mac-os-86";
+ return "mac-os-x86";
// Unknown, maybe something new, appending arch
return "mac-os-" + arch;
} else if (sys == "linux") {
diff --git a/launcher/Version.cpp b/launcher/Version.cpp
index 511aa9c35..2edb17e72 100644
--- a/launcher/Version.cpp
+++ b/launcher/Version.cpp
@@ -123,8 +123,7 @@ QDebug operator<<(QDebug debug, const Version& v)
first = false;
}
- debug.nospace() << " ]"
- << " }";
+ debug.nospace() << " ]" << " }";
return debug;
}
diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp
index 552900d35..12a82f73d 100644
--- a/launcher/VersionProxyModel.cpp
+++ b/launcher/VersionProxyModel.cpp
@@ -140,9 +140,9 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation,
case Path:
return tr("Filesystem path to this version");
case JavaName:
- return tr("The alternative name of the java version");
+ return tr("The alternative name of the Java version");
case JavaMajor:
- return tr("The java major version");
+ return tr("The Java major version");
case Time:
return tr("Release date of this version");
}
diff --git a/launcher/icons/IconUtils.cpp b/launcher/icons/IconUtils.cpp
index 6825dd6da..87e948729 100644
--- a/launcher/icons/IconUtils.cpp
+++ b/launcher/icons/IconUtils.cpp
@@ -39,7 +39,7 @@
#include "FileSystem.h"
namespace {
-static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg" } };
+static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg", "webp" } };
}
namespace IconUtils {
diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp
index c54a5b04b..772c90e42 100644
--- a/launcher/java/JavaChecker.cpp
+++ b/launcher/java/JavaChecker.cpp
@@ -44,8 +44,8 @@
#include "FileSystem.h"
#include "java/JavaUtils.h"
-JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id, QObject* parent)
- : Task(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id)
+JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id)
+ : Task(), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id)
{}
void JavaChecker::executeTask()
diff --git a/launcher/java/JavaChecker.h b/launcher/java/JavaChecker.h
index 171a18b76..a04b68170 100644
--- a/launcher/java/JavaChecker.h
+++ b/launcher/java/JavaChecker.h
@@ -1,7 +1,6 @@
#pragma once
#include
#include
-#include
#include "JavaVersion.h"
#include "QObjectPtr.h"
@@ -26,7 +25,7 @@ class JavaChecker : public Task {
enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
};
- explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0, QObject* parent = 0);
+ explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0);
signals:
void checkFinished(const Result& result);
diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp
index 569fda306..aa7fab8a0 100644
--- a/launcher/java/JavaInstallList.cpp
+++ b/launcher/java/JavaInstallList.cpp
@@ -163,7 +163,7 @@ void JavaListLoadTask::executeTask()
JavaUtils ju;
QList candidate_paths = m_only_managed_versions ? getPrismJavaBundle() : ju.FindJavaPaths();
- ConcurrentTask::Ptr job(new ConcurrentTask(this, "Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
+ ConcurrentTask::Ptr job(new ConcurrentTask("Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
m_job.reset(job);
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
@@ -171,7 +171,7 @@ void JavaListLoadTask::executeTask()
qDebug() << "Probing the following Java paths: ";
int id = 0;
for (QString candidate : candidate_paths) {
- auto checker = new JavaChecker(candidate, "", 0, 0, 0, id, this);
+ auto checker = new JavaChecker(candidate, "", 0, 0, 0, id);
connect(checker, &JavaChecker::checkFinished, [this](const JavaChecker::Result& result) { m_results << result; });
job->addTask(Task::Ptr(checker));
id++;
diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp
index bc8026348..f3200428e 100644
--- a/launcher/java/JavaUtils.cpp
+++ b/launcher/java/JavaUtils.cpp
@@ -102,6 +102,8 @@ QProcessEnvironment CleanEnviroment()
QString newValue = stripVariableEntries(key, value, rawenv.value("LAUNCHER_" + key));
qDebug() << "Env: stripped" << key << value << "to" << newValue;
+
+ value = newValue;
}
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
// Strip IBus
diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp
index 5e9700012..bca50f2c9 100644
--- a/launcher/java/JavaVersion.cpp
+++ b/launcher/java/JavaVersion.cpp
@@ -48,6 +48,12 @@ bool JavaVersion::requiresPermGen() const
return !m_parseable || m_major < 8;
}
+bool JavaVersion::defaultsToUtf8() const
+{
+ // starting from Java 18, UTF-8 is the default charset: https://openjdk.org/jeps/400
+ return m_parseable && m_major >= 18;
+}
+
bool JavaVersion::isModular() const
{
return m_parseable && m_major >= 9;
diff --git a/launcher/java/JavaVersion.h b/launcher/java/JavaVersion.h
index dfb4770da..c070bdeec 100644
--- a/launcher/java/JavaVersion.h
+++ b/launcher/java/JavaVersion.h
@@ -25,7 +25,7 @@ class JavaVersion {
bool operator>(const JavaVersion& rhs);
bool requiresPermGen() const;
-
+ bool defaultsToUtf8() const;
bool isModular() const;
QString toString() const;
diff --git a/launcher/java/download/ArchiveDownloadTask.cpp b/launcher/java/download/ArchiveDownloadTask.cpp
index ba1c96faf..bb7cc568d 100644
--- a/launcher/java/download/ArchiveDownloadTask.cpp
+++ b/launcher/java/download/ArchiveDownloadTask.cpp
@@ -65,7 +65,7 @@ void ArchiveDownloadTask::executeTask()
void ArchiveDownloadTask::extractJava(QString input)
{
- setStatus(tr("Extracting java"));
+ setStatus(tr("Extracting Java"));
if (input.endsWith("tar")) {
setStatus(tr("Extracting Java (Progress is not reported for tar archives)"));
QFile in(input);
@@ -95,7 +95,7 @@ void ArchiveDownloadTask::extractJava(QString input)
}
auto files = zip->getFileNameList();
if (files.isEmpty()) {
- emitFailed(tr("No files were found in the supplied zip file,"));
+ emitFailed(tr("No files were found in the supplied zip file."));
return;
}
m_task = makeShared(zip, m_final_path, files[0]);
diff --git a/launcher/java/download/ManifestDownloadTask.cpp b/launcher/java/download/ManifestDownloadTask.cpp
index 836afeaac..20b39e751 100644
--- a/launcher/java/download/ManifestDownloadTask.cpp
+++ b/launcher/java/download/ManifestDownloadTask.cpp
@@ -86,11 +86,10 @@ void ManifestDownloadTask::downloadJava(const QJsonDocument& doc)
if (type == "directory") {
FS::ensureFolderPathExists(file);
} else if (type == "link") {
- // this is linux only !
+ // this is *nix only !
auto path = Json::ensureString(meta, "target");
if (!path.isEmpty()) {
- auto target = FS::PathCombine(file, "../" + path);
- QFile(target).link(file);
+ QFile::link(path, file);
}
} else if (type == "file") {
// TODO download compressed version if it exists ?
diff --git a/launcher/java/download/SymlinkTask.cpp b/launcher/java/download/SymlinkTask.cpp
new file mode 100644
index 000000000..843c7caa9
--- /dev/null
+++ b/launcher/java/download/SymlinkTask.cpp
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023-2024 Trial97
+ *
+ * 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 .
+ */
+#include "java/download/SymlinkTask.h"
+#include
+
+#include "FileSystem.h"
+
+namespace Java {
+SymlinkTask::SymlinkTask(QString final_path) : m_path(final_path) {}
+
+QString findBinPath(QString root, QString pattern)
+{
+ auto path = FS::PathCombine(root, pattern);
+ if (QFileInfo::exists(path)) {
+ return path;
+ }
+
+ auto entries = QDir(root).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (auto& entry : entries) {
+ path = FS::PathCombine(entry.absoluteFilePath(), pattern);
+ if (QFileInfo::exists(path)) {
+ return path;
+ }
+ }
+
+ return {};
+}
+
+void SymlinkTask::executeTask()
+{
+ setStatus(tr("Checking for Java binary path"));
+ const auto binPath = FS::PathCombine("bin", "java");
+ const auto wantedPath = FS::PathCombine(m_path, binPath);
+ if (QFileInfo::exists(wantedPath)) {
+ emitSucceeded();
+ return;
+ }
+
+ setStatus(tr("Searching for Java binary path"));
+ const auto contentsPartialPath = FS::PathCombine("Contents", "Home", binPath);
+ const auto relativePathToBin = findBinPath(m_path, contentsPartialPath);
+ if (relativePathToBin.isEmpty()) {
+ emitFailed(tr("Failed to find Java binary path"));
+ return;
+ }
+ const auto folderToLink = relativePathToBin.chopped(binPath.length());
+
+ setStatus(tr("Collecting folders to symlink"));
+ auto entries = QDir(folderToLink).entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries);
+ QList files;
+ setProgress(0, entries.length());
+ for (auto& entry : entries) {
+ files.append({ entry.absoluteFilePath(), FS::PathCombine(m_path, entry.fileName()) });
+ }
+
+ setStatus(tr("Symlinking Java binary path"));
+ FS::create_link folderLink(files);
+ connect(&folderLink, &FS::create_link::fileLinked, [this](QString src, QString dst) { setProgress(m_progress + 1, m_progressTotal); });
+ if (!folderLink()) {
+ emitFailed(folderLink.getOSError().message().c_str());
+ } else {
+ emitSucceeded();
+ }
+}
+
+} // namespace Java
\ No newline at end of file
diff --git a/launcher/java/download/SymlinkTask.h b/launcher/java/download/SymlinkTask.h
new file mode 100644
index 000000000..88cb20dd7
--- /dev/null
+++ b/launcher/java/download/SymlinkTask.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023-2024 Trial97
+ *
+ * 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 .
+ */
+
+#pragma once
+
+#include "tasks/Task.h"
+namespace Java {
+
+class SymlinkTask : public Task {
+ Q_OBJECT
+ public:
+ SymlinkTask(QString final_path);
+ virtual ~SymlinkTask() = default;
+
+ void executeTask() override;
+
+ protected:
+ QString m_path;
+ Task::Ptr m_task;
+};
+} // namespace Java
\ No newline at end of file
diff --git a/launcher/launch/LaunchStep.cpp b/launcher/launch/LaunchStep.cpp
index ebc534617..0b352ea9f 100644
--- a/launcher/launch/LaunchStep.cpp
+++ b/launcher/launch/LaunchStep.cpp
@@ -16,9 +16,8 @@
#include "LaunchStep.h"
#include "LaunchTask.h"
-void LaunchStep::bind(LaunchTask* parent)
+LaunchStep::LaunchStep(LaunchTask* parent) : Task(), m_parent(parent)
{
- m_parent = parent;
connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch);
connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);
connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines);
diff --git a/launcher/launch/LaunchStep.h b/launcher/launch/LaunchStep.h
index 6a28afb1f..d49d7545b 100644
--- a/launcher/launch/LaunchStep.h
+++ b/launcher/launch/LaunchStep.h
@@ -24,11 +24,8 @@ class LaunchTask;
class LaunchStep : public Task {
Q_OBJECT
public: /* methods */
- explicit LaunchStep(LaunchTask* parent) : Task(nullptr), m_parent(parent) { bind(parent); };
- virtual ~LaunchStep() {};
-
- private: /* methods */
- void bind(LaunchTask* parent);
+ explicit LaunchStep(LaunchTask* parent);
+ virtual ~LaunchStep() = default;
signals:
void logLines(QStringList lines, MessageLevel::Enum level);
diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp
index 06a32bd28..0251b302d 100644
--- a/launcher/launch/LaunchTask.cpp
+++ b/launcher/launch/LaunchTask.cpp
@@ -44,7 +44,6 @@
#include
#include
#include "MessageLevel.h"
-#include "java/JavaChecker.h"
#include "tasks/Task.h"
void LaunchTask::init()
@@ -52,14 +51,14 @@ void LaunchTask::init()
m_instance->setRunning(true);
}
-shared_qobject_ptr LaunchTask::create(InstancePtr inst)
+shared_qobject_ptr LaunchTask::create(MinecraftInstancePtr inst)
{
shared_qobject_ptr proc(new LaunchTask(inst));
proc->init();
return proc;
}
-LaunchTask::LaunchTask(InstancePtr instance) : m_instance(instance) {}
+LaunchTask::LaunchTask(MinecraftInstancePtr instance) : m_instance(instance) {}
void LaunchTask::appendStep(shared_qobject_ptr step)
{
diff --git a/launcher/launch/LaunchTask.h b/launcher/launch/LaunchTask.h
index ae24b9a26..56065af5b 100644
--- a/launcher/launch/LaunchTask.h
+++ b/launcher/launch/LaunchTask.h
@@ -37,31 +37,31 @@
#pragma once
#include
+#include
#include
#include "BaseInstance.h"
#include "LaunchStep.h"
#include "LogModel.h"
-#include "LoggedProcess.h"
#include "MessageLevel.h"
class LaunchTask : public Task {
Q_OBJECT
protected:
- explicit LaunchTask(InstancePtr instance);
+ explicit LaunchTask(MinecraftInstancePtr instance);
void init();
public:
enum State { NotStarted, Running, Waiting, Failed, Aborted, Finished };
public: /* methods */
- static shared_qobject_ptr create(InstancePtr inst);
- virtual ~LaunchTask() {};
+ static shared_qobject_ptr create(MinecraftInstancePtr inst);
+ virtual ~LaunchTask() = default;
void appendStep(shared_qobject_ptr step);
void prependStep(shared_qobject_ptr step);
void setCensorFilter(QMap filter);
- InstancePtr instance() { return m_instance; }
+ MinecraftInstancePtr instance() { return m_instance; }
void setPid(qint64 pid) { m_pid = pid; }
@@ -116,7 +116,7 @@ class LaunchTask : public Task {
void finalizeSteps(bool successful, const QString& error);
protected: /* data */
- InstancePtr m_instance;
+ MinecraftInstancePtr m_instance;
shared_qobject_ptr m_logModel;
QList> m_steps;
QMap m_censorFilter;
diff --git a/launcher/launch/TaskStepWrapper.cpp b/launcher/launch/TaskStepWrapper.cpp
new file mode 100644
index 000000000..db9e8fad2
--- /dev/null
+++ b/launcher/launch/TaskStepWrapper.cpp
@@ -0,0 +1,67 @@
+/* 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 "TaskStepWrapper.h"
+#include "tasks/Task.h"
+
+void TaskStepWrapper::executeTask()
+{
+ if (m_state == Task::State::AbortedByUser) {
+ emitFailed(tr("Task aborted."));
+ return;
+ }
+ connect(m_task.get(), &Task::finished, this, &TaskStepWrapper::updateFinished);
+ connect(m_task.get(), &Task::progress, this, &TaskStepWrapper::setProgress);
+ connect(m_task.get(), &Task::stepProgress, this, &TaskStepWrapper::propagateStepProgress);
+ connect(m_task.get(), &Task::status, this, &TaskStepWrapper::setStatus);
+ connect(m_task.get(), &Task::details, this, &TaskStepWrapper::setDetails);
+ emit progressReportingRequest();
+}
+
+void TaskStepWrapper::proceed()
+{
+ m_task->start();
+}
+
+void TaskStepWrapper::updateFinished()
+{
+ if (m_task->wasSuccessful()) {
+ m_task.reset();
+ emitSucceeded();
+ } else {
+ QString reason = tr("Instance update failed because: %1\n\n").arg(m_task->failReason());
+ m_task.reset();
+ emit logLine(reason, MessageLevel::Fatal);
+ emitFailed(reason);
+ }
+}
+
+bool TaskStepWrapper::canAbort() const
+{
+ if (m_task) {
+ return m_task->canAbort();
+ }
+ return true;
+}
+
+bool TaskStepWrapper::abort()
+{
+ if (m_task && m_task->canAbort()) {
+ auto status = m_task->abort();
+ emitFailed("Aborted.");
+ return status;
+ }
+ return Task::abort();
+}
diff --git a/launcher/launch/steps/Update.h b/launcher/launch/TaskStepWrapper.h
similarity index 73%
rename from launcher/launch/steps/Update.h
rename to launcher/launch/TaskStepWrapper.h
index 878a43e7e..aec1b7037 100644
--- a/launcher/launch/steps/Update.h
+++ b/launcher/launch/TaskStepWrapper.h
@@ -21,12 +21,11 @@
#include
#include
-// FIXME: stupid. should be defined by the instance type? or even completely abstracted away...
-class Update : public LaunchStep {
+class TaskStepWrapper : public LaunchStep {
Q_OBJECT
public:
- explicit Update(LaunchTask* parent, Net::Mode mode) : LaunchStep(parent), m_mode(mode) {};
- virtual ~Update() {};
+ explicit TaskStepWrapper(LaunchTask* parent, Task::Ptr task) : LaunchStep(parent), m_task(task) {};
+ virtual ~TaskStepWrapper() = default;
void executeTask() override;
bool canAbort() const override;
@@ -38,7 +37,5 @@ class Update : public LaunchStep {
void updateFinished();
private:
- Task::Ptr m_updateTask;
- bool m_aborted = false;
- Net::Mode m_mode = Net::Mode::Offline;
+ Task::Ptr m_task;
};
diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp
index 99ff62b67..0f8d27e94 100644
--- a/launcher/launch/steps/CheckJava.cpp
+++ b/launcher/launch/steps/CheckJava.cpp
@@ -94,7 +94,7 @@ void CheckJava::executeTask()
// if timestamps are not the same, or something is missing, check!
if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 ||
storedRealArchitecture.size() == 0 || storedVendor.size() == 0) {
- m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0, this));
+ m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0));
emit logLine(QString("Checking Java version..."), MessageLevel::Launcher);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
m_JavaChecker->start();
@@ -106,6 +106,7 @@ void CheckJava::executeTask()
auto vendorString = instance->settings()->get("JavaVendor").toString();
printJavaInfo(verString, archString, realArchString, vendorString);
}
+ m_parent->instance()->updateRuntimeContext();
emitSucceeded();
}
@@ -124,6 +125,7 @@ void CheckJava::checkJavaFinished(const JavaChecker::Result& result)
emit logLine(QString("Java checker returned some invalid data we don't understand:"), MessageLevel::Error);
emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher);
+ m_parent->instance()->updateRuntimeContext();
emitSucceeded();
return;
}
@@ -135,6 +137,7 @@ void CheckJava::checkJavaFinished(const JavaChecker::Result& result)
instance->settings()->set("JavaRealArchitecture", result.realPlatform);
instance->settings()->set("JavaVendor", result.javaVendor);
instance->settings()->set("JavaSignature", m_javaSignature);
+ m_parent->instance()->updateRuntimeContext();
emitSucceeded();
return;
}
diff --git a/launcher/launch/steps/CheckJava.h b/launcher/launch/steps/CheckJava.h
index 7bb1ceb7e..1c59b0053 100644
--- a/launcher/launch/steps/CheckJava.h
+++ b/launcher/launch/steps/CheckJava.h
@@ -23,7 +23,7 @@ class CheckJava : public LaunchStep {
Q_OBJECT
public:
explicit CheckJava(LaunchTask* parent) : LaunchStep(parent) {};
- virtual ~CheckJava() {};
+ virtual ~CheckJava() = default;
virtual void executeTask();
virtual bool canAbort() const { return false; }
diff --git a/launcher/launch/steps/QuitAfterGameStop.h b/launcher/launch/steps/QuitAfterGameStop.h
index d4324cce6..19ca59632 100644
--- a/launcher/launch/steps/QuitAfterGameStop.h
+++ b/launcher/launch/steps/QuitAfterGameStop.h
@@ -24,7 +24,7 @@ class QuitAfterGameStop : public LaunchStep {
Q_OBJECT
public:
explicit QuitAfterGameStop(LaunchTask* parent) : LaunchStep(parent) {};
- virtual ~QuitAfterGameStop() {};
+ virtual ~QuitAfterGameStop() = default;
virtual void executeTask();
virtual bool canAbort() const { return false; }
diff --git a/launcher/launch/steps/Update.cpp b/launcher/launch/steps/Update.cpp
deleted file mode 100644
index f23c0bb4b..000000000
--- a/launcher/launch/steps/Update.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Update.h"
-#include
-
-void Update::executeTask()
-{
- if (m_aborted) {
- emitFailed(tr("Task aborted."));
- return;
- }
- m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode));
- if (m_updateTask) {
- connect(m_updateTask.get(), &Task::finished, this, &Update::updateFinished);
- connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress);
- connect(m_updateTask.get(), &Task::stepProgress, this, &Update::propagateStepProgress);
- connect(m_updateTask.get(), &Task::status, this, &Update::setStatus);
- connect(m_updateTask.get(), &Task::details, this, &Update::setDetails);
- emit progressReportingRequest();
- return;
- }
- emitSucceeded();
-}
-
-void Update::proceed()
-{
- m_updateTask->start();
-}
-
-void Update::updateFinished()
-{
- if (m_updateTask->wasSuccessful()) {
- m_updateTask.reset();
- emitSucceeded();
- } else {
- QString reason = tr("Instance update failed because: %1\n\n").arg(m_updateTask->failReason());
- m_updateTask.reset();
- emit logLine(reason, MessageLevel::Fatal);
- emitFailed(reason);
- }
-}
-
-bool Update::canAbort() const
-{
- if (m_updateTask) {
- return m_updateTask->canAbort();
- }
- return true;
-}
-
-bool Update::abort()
-{
- m_aborted = true;
- if (m_updateTask) {
- if (m_updateTask->canAbort()) {
- return m_updateTask->abort();
- }
- }
- return true;
-}
diff --git a/launcher/meta/Index.cpp b/launcher/meta/Index.cpp
index bd0745b6b..1707854be 100644
--- a/launcher/meta/Index.cpp
+++ b/launcher/meta/Index.cpp
@@ -140,8 +140,8 @@ Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mo
}
auto versionList = get(uid);
- auto loadTask = makeShared(
- this, tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version));
+ auto loadTask =
+ makeShared(tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version));
if (status() != BaseEntity::LoadStatus::Remote || force) {
loadTask->addTask(this->loadTask(mode));
}
diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp
index 8b98223d1..f96355658 100644
--- a/launcher/meta/VersionList.cpp
+++ b/launcher/meta/VersionList.cpp
@@ -16,6 +16,7 @@
#include "VersionList.h"
#include
+#include
#include "Application.h"
#include "Index.h"
@@ -33,8 +34,7 @@ VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList(
Task::Ptr VersionList::getLoadTask()
{
- auto loadTask =
- makeShared(this, tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
+ auto loadTask = makeShared(tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online));
loadTask->addTask(this->loadTask(Net::Mode::Online));
return loadTask;
@@ -99,7 +99,7 @@ QVariant VersionList::data(const QModelIndex& index, int role) const
case VersionPtrRole:
return QVariant::fromValue(version);
case RecommendedRole:
- return version->isRecommended();
+ return version->isRecommended() || m_externalRecommendsVersions.contains(version->version());
case JavaMajorRole: {
auto major = version->version();
if (major.startsWith("java")) {
@@ -192,6 +192,16 @@ void VersionList::parse(const QJsonObject& obj)
parseVersionList(obj, this);
}
+void VersionList::addExternalRecommends(const QStringList& recommends)
+{
+ m_externalRecommendsVersions.append(recommends);
+}
+
+void VersionList::clearExternalRecommends()
+{
+ m_externalRecommendsVersions.clear();
+}
+
// FIXME: this is dumb, we have 'recommended' as part of the metadata already...
static const Meta::Version::Ptr& getBetterVersion(const Meta::Version::Ptr& a, const Meta::Version::Ptr& b)
{
@@ -276,4 +286,35 @@ void VersionList::waitToLoad()
task->start();
ev.exec();
}
+
+Version::Ptr VersionList::getRecommendedForParent(const QString& uid, const QString& version)
+{
+ auto foundExplicit = std::find_if(m_versions.begin(), m_versions.end(), [uid, version](Version::Ptr ver) -> bool {
+ auto& reqs = ver->requiredSet();
+ auto parentReq = std::find_if(reqs.begin(), reqs.end(), [uid, version](const Require& req) -> bool {
+ return req.uid == uid && req.equalsVersion == version;
+ });
+ return parentReq != reqs.end() && ver->isRecommended();
+ });
+ if (foundExplicit != m_versions.end()) {
+ return *foundExplicit;
+ }
+ return nullptr;
+}
+
+Version::Ptr VersionList::getLatestForParent(const QString& uid, const QString& version)
+{
+ Version::Ptr latestCompat = nullptr;
+ for (auto ver : m_versions) {
+ auto& reqs = ver->requiredSet();
+ auto parentReq = std::find_if(reqs.begin(), reqs.end(), [uid, version](const Require& req) -> bool {
+ return req.uid == uid && req.equalsVersion == version;
+ });
+ if (parentReq != reqs.end()) {
+ latestCompat = getBetterVersion(latestCompat, ver);
+ }
+ }
+ return latestCompat;
+}
+
} // namespace Meta
diff --git a/launcher/meta/VersionList.h b/launcher/meta/VersionList.h
index 94aaae22c..4215439db 100644
--- a/launcher/meta/VersionList.h
+++ b/launcher/meta/VersionList.h
@@ -43,6 +43,8 @@ class VersionList : public BaseVersionList, public BaseEntity {
void sortVersions() override;
BaseVersion::Ptr getRecommended() const override;
+ Version::Ptr getRecommendedForParent(const QString& uid, const QString& version);
+ Version::Ptr getLatestForParent(const QString& uid, const QString& version);
QVariant data(const QModelIndex& index, int role) const override;
RoleList providesRoles() const override;
@@ -70,6 +72,8 @@ class VersionList : public BaseVersionList, public BaseEntity {
void merge(const VersionList::Ptr& other);
void mergeFromIndex(const VersionList::Ptr& other);
void parse(const QJsonObject& obj) override;
+ void addExternalRecommends(const QStringList& recommends);
+ void clearExternalRecommends();
signals:
void nameChanged(const QString& name);
@@ -79,6 +83,7 @@ class VersionList : public BaseVersionList, public BaseEntity {
private:
QVector m_versions;
+ QStringList m_externalRecommendsVersions;
QHash m_lookup;
QString m_uid;
QString m_name;
diff --git a/launcher/minecraft/Component.cpp b/launcher/minecraft/Component.cpp
index 32a1deb68..ad7ef545c 100644
--- a/launcher/minecraft/Component.cpp
+++ b/launcher/minecraft/Component.cpp
@@ -44,10 +44,19 @@
#include "OneSixVersionFormat.h"
#include "VersionFile.h"
#include "meta/Version.h"
+#include "minecraft/Component.h"
#include "minecraft/PackProfile.h"
#include
+const QMap Component::KNOWN_MODLOADERS = {
+ { "net.neoforged", { ModPlatform::NeoForge, { "net.minecraftforge", "net.fabricmc.fabric-loader", "org.quiltmc.quilt-loader" } } },
+ { "net.minecraftforge", { ModPlatform::Forge, { "net.neoforged", "net.fabricmc.fabric-loader", "org.quiltmc.quilt-loader" } } },
+ { "net.fabricmc.fabric-loader", { ModPlatform::Fabric, { "net.minecraftforge", "net.neoforged", "org.quiltmc.quilt-loader" } } },
+ { "org.quiltmc.quilt-loader", { ModPlatform::Quilt, { "net.minecraftforge", "net.neoforged", "net.fabricmc.fabric-loader" } } },
+ { "com.mumfrey.liteloader", { ModPlatform::LiteLoader, {} } }
+};
+
Component::Component(PackProfile* parent, const QString& uid)
{
assert(parent);
@@ -213,16 +222,33 @@ bool Component::isMoveable()
return true;
}
-bool Component::isVersionChangeable()
+bool Component::isVersionChangeable(bool wait)
{
auto list = getVersionList();
if (list) {
- list->waitToLoad();
+ if (wait)
+ list->waitToLoad();
return list->count() != 0;
}
return false;
}
+bool Component::isKnownModloader()
+{
+ auto iter = KNOWN_MODLOADERS.find(m_uid);
+ return iter != KNOWN_MODLOADERS.cend();
+}
+
+QStringList Component::knownConflictingComponents()
+{
+ auto iter = KNOWN_MODLOADERS.find(m_uid);
+ if (iter != KNOWN_MODLOADERS.cend()) {
+ return (*iter).knownConflictingComponents;
+ } else {
+ return {};
+ }
+}
+
void Component::setImportant(bool state)
{
if (m_important != state) {
@@ -235,7 +261,8 @@ ProblemSeverity Component::getProblemSeverity() const
{
auto file = getVersionFile();
if (file) {
- return file->getProblemSeverity();
+ auto severity = file->getProblemSeverity();
+ return m_componentProblemSeverity > severity ? m_componentProblemSeverity : severity;
}
return ProblemSeverity::Error;
}
@@ -244,11 +271,31 @@ const QList Component::getProblems() const
{
auto file = getVersionFile();
if (file) {
- return file->getProblems();
+ auto problems = file->getProblems();
+ problems.append(m_componentProblems);
+ return problems;
}
return { { ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.") } };
}
+void Component::addComponentProblem(ProblemSeverity severity, const QString& description)
+{
+ if (severity > m_componentProblemSeverity) {
+ m_componentProblemSeverity = severity;
+ }
+ m_componentProblems.append({ severity, description });
+
+ emit dataChanged();
+}
+
+void Component::resetComponentProblems()
+{
+ m_componentProblems.clear();
+ m_componentProblemSeverity = ProblemSeverity::None;
+
+ emit dataChanged();
+}
+
void Component::setVersion(const QString& version)
{
if (version == m_version) {
@@ -402,3 +449,36 @@ void Component::updateCachedData()
emit dataChanged();
}
}
+
+void Component::waitLoadMeta()
+{
+ if (!m_loaded) {
+ if (!m_metaVersion || !m_metaVersion->isLoaded()) {
+ // wait for the loaded version from meta
+ m_metaVersion = APPLICATION->metadataIndex()->getLoadedVersion(m_uid, m_version);
+ }
+ m_loaded = true;
+ updateCachedData();
+ }
+}
+
+void Component::setUpdateAction(UpdateAction action)
+{
+ m_updateAction = action;
+}
+
+UpdateAction Component::getUpdateAction()
+{
+ return m_updateAction;
+}
+
+void Component::clearUpdateAction()
+{
+ m_updateAction = UpdateAction{ UpdateActionNone{} };
+}
+
+QDebug operator<<(QDebug d, const Component& comp)
+{
+ d << "Component(" << comp.m_uid << " : " << comp.m_cachedVersion << ")";
+ return d;
+}
diff --git a/launcher/minecraft/Component.h b/launcher/minecraft/Component.h
index 8aa6b4743..203cc2241 100644
--- a/launcher/minecraft/Component.h
+++ b/launcher/minecraft/Component.h
@@ -4,9 +4,12 @@
#include
#include
#include
+#include
+#include
#include "ProblemProvider.h"
#include "QObjectPtr.h"
#include "meta/JsonFormat.h"
+#include "modplatform/ModIndex.h"
class PackProfile;
class LaunchProfile;
@@ -16,6 +19,36 @@ class VersionList;
} // namespace Meta
class VersionFile;
+struct UpdateActionChangeVersion {
+ /// version to change to
+ QString targetVersion;
+};
+struct UpdateActionLatestRecommendedCompatible {
+ /// Parent uid
+ QString parentUid;
+ QString parentName;
+ /// Parent version
+ QString version;
+ ///
+};
+struct UpdateActionRemove {};
+struct UpdateActionImportantChanged {
+ QString oldVersion;
+};
+
+using UpdateActionNone = std::monostate;
+
+using UpdateAction = std::variant;
+
+struct ModloaderMapEntry {
+ ModPlatform::ModLoaderType type;
+ QStringList knownConflictingComponents;
+};
+
class Component : public QObject, public ProblemProvider {
Q_OBJECT
public:
@@ -26,6 +59,8 @@ class Component : public QObject, public ProblemProvider {
virtual ~Component() {}
+ static const QMap KNOWN_MODLOADERS;
+
void applyTo(LaunchProfile* profile);
bool isEnabled();
@@ -37,7 +72,9 @@ class Component : public QObject, public ProblemProvider {
bool isRevertible();
bool isRemovable();
bool isCustom();
- bool isVersionChangeable();
+ bool isVersionChangeable(bool wait = true);
+ bool isKnownModloader();
+ QStringList knownConflictingComponents();
// DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code
void setOrder(int order);
@@ -58,6 +95,8 @@ class Component : public QObject, public ProblemProvider {
const QList getProblems() const override;
ProblemSeverity getProblemSeverity() const override;
+ void addComponentProblem(ProblemSeverity severity, const QString& description);
+ void resetComponentProblems();
void setVersion(const QString& version);
bool customize();
@@ -65,6 +104,12 @@ class Component : public QObject, public ProblemProvider {
void updateCachedData();
+ void waitLoadMeta();
+
+ void setUpdateAction(UpdateAction action);
+ void clearUpdateAction();
+ UpdateAction getUpdateAction();
+
signals:
void dataChanged();
@@ -102,6 +147,11 @@ class Component : public QObject, public ProblemProvider {
std::shared_ptr m_metaVersion;
std::shared_ptr m_file;
bool m_loaded = false;
+
+ private:
+ QList m_componentProblems;
+ ProblemSeverity m_componentProblemSeverity = ProblemSeverity::None;
+ UpdateAction m_updateAction = UpdateAction{ UpdateActionNone{} };
};
using ComponentPtr = shared_qobject_ptr;
diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp
index 4d205af6c..36a07ee72 100644
--- a/launcher/minecraft/ComponentUpdateTask.cpp
+++ b/launcher/minecraft/ComponentUpdateTask.cpp
@@ -1,13 +1,16 @@
#include "ComponentUpdateTask.h"
+#include
#include "Component.h"
#include "ComponentUpdateTask_p.h"
#include "PackProfile.h"
#include "PackProfile_p.h"
+#include "ProblemProvider.h"
#include "Version.h"
#include "cassert"
#include "meta/Index.h"
#include "meta/Version.h"
+#include "minecraft/MinecraftInstance.h"
#include "minecraft/OneSixVersionFormat.h"
#include "minecraft/ProfileUtils.h"
#include "net/Mode.h"
@@ -15,6 +18,8 @@
#include "Application.h"
#include "tasks/Task.h"
+#include "minecraft/Logging.h"
+
/*
* This is responsible for loading the components of a component list AND resolving dependency issues between them
*/
@@ -33,10 +38,10 @@
* If the component list changes, start over.
*/
-ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent) : Task(parent)
+ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list) : Task()
{
d.reset(new ComponentUpdateTaskData);
- d->m_list = list;
+ d->m_profile = list;
d->mode = mode;
d->netmode = netmode;
}
@@ -45,7 +50,7 @@ ComponentUpdateTask::~ComponentUpdateTask() {}
void ComponentUpdateTask::executeTask()
{
- qDebug() << "Loading components";
+ qCDebug(instanceProfileResolveC) << "Loading components";
loadComponents();
}
@@ -63,7 +68,7 @@ LoadResult composeLoadResult(LoadResult a, LoadResult b)
static LoadResult loadComponent(ComponentPtr component, Task::Ptr& loadTask, Net::Mode netmode)
{
if (component->m_loaded) {
- qDebug() << component->getName() << "is already loaded";
+ qCDebug(instanceProfileResolveC) << component->getName() << "is already loaded";
return LoadResult::LoadedLocal;
}
@@ -144,10 +149,11 @@ void ComponentUpdateTask::loadComponents()
d->remoteLoadSuccessful = true;
// load all the components OR their lists...
- for (auto component : d->m_list->d->components) {
+ for (auto component : d->m_profile->d->components) {
Task::Ptr loadTask;
LoadResult singleResult;
RemoteLoadStatus::Type loadType;
+ component->resetComponentProblems();
// FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now,
// ignore all that...
#if 0
@@ -175,7 +181,8 @@ void ComponentUpdateTask::loadComponents()
}
result = composeLoadResult(result, singleResult);
if (loadTask) {
- qDebug() << "Remote loading is being run for" << component->getName();
+ qCDebug(instanceProfileResolveC) << d->m_profile->d->m_instance->name() << "|"
+ << "Remote loading is being run for" << component->getName();
connect(loadTask.get(), &Task::succeeded, this, [this, taskIndex]() { remoteLoadSucceeded(taskIndex); });
connect(loadTask.get(), &Task::failed, this, [this, taskIndex](const QString& error) { remoteLoadFailed(taskIndex, error); });
connect(loadTask.get(), &Task::aborted, this, [this, taskIndex]() { remoteLoadFailed(taskIndex, tr("Aborted")); });
@@ -192,6 +199,7 @@ void ComponentUpdateTask::loadComponents()
switch (result) {
case LoadResult::LoadedLocal: {
// Everything got loaded. Advance to dependency resolution.
+ performUpdateActions();
resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline);
break;
}
@@ -270,8 +278,8 @@ static bool gatherRequirementsFromComponents(const ComponentContainer& input, Re
output.erase(componenRequireEx);
output.insert(result.outcome);
} else {
- qCritical() << "Conflicting requirements:" << componentRequire.uid << "versions:" << componentRequire.equalsVersion
- << ";" << (*found).equalsVersion;
+ qCCritical(instanceProfileResolveC) << "Conflicting requirements:" << componentRequire.uid
+ << "versions:" << componentRequire.equalsVersion << ";" << (*found).equalsVersion;
}
succeeded &= result.ok;
} else {
@@ -353,22 +361,22 @@ static bool getTrivialComponentChanges(const ComponentIndex& index, const Requir
} while (false);
switch (decision) {
case Decision::Undetermined:
- qCritical() << "No decision for" << reqStr;
+ qCCritical(instanceProfileResolveC) << "No decision for" << reqStr;
succeeded = false;
break;
case Decision::Met:
- qDebug() << reqStr << "Is met.";
+ qCDebug(instanceProfileResolveC) << reqStr << "Is met.";
break;
case Decision::Missing:
- qDebug() << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee;
+ qCDebug(instanceProfileResolveC) << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee;
toAdd.insert(req);
break;
case Decision::VersionNotSame:
- qDebug() << reqStr << "already has different version that can be changed.";
+ qCDebug(instanceProfileResolveC) << reqStr << "already has different version that can be changed.";
toChange.insert(req);
break;
case Decision::LockedVersionNotSame:
- qDebug() << reqStr << "already has different version that cannot be changed.";
+ qCDebug(instanceProfileResolveC) << reqStr << "already has different version that cannot be changed.";
succeeded = false;
break;
}
@@ -376,12 +384,48 @@ static bool getTrivialComponentChanges(const ComponentIndex& index, const Requir
return succeeded;
}
+ComponentContainer ComponentUpdateTask::collectTreeLinked(const QString& uid)
+{
+ ComponentContainer linked;
+
+ auto& components = d->m_profile->d->components;
+ auto& componentIndex = d->m_profile->d->componentIndex;
+ auto& instance = d->m_profile->d->m_instance;
+ for (auto comp : components) {
+ qCDebug(instanceProfileResolveC) << instance->name() << "|"
+ << "scanning" << comp->getID() << ":" << comp->getVersion() << "for tree link";
+ auto dep = std::find_if(comp->m_cachedRequires.cbegin(), comp->m_cachedRequires.cend(),
+ [uid](const Meta::Require& req) -> bool { return req.uid == uid; });
+ if (dep != comp->m_cachedRequires.cend()) {
+ qCDebug(instanceProfileResolveC) << instance->name() << "|" << comp->getID() << ":" << comp->getVersion() << "depends on"
+ << uid;
+ linked.append(comp);
+ }
+ }
+ auto iter = componentIndex.find(uid);
+ if (iter != componentIndex.end()) {
+ ComponentPtr comp = *iter;
+ comp->updateCachedData();
+ qCDebug(instanceProfileResolveC) << instance->name() << "|" << comp->getID() << ":" << comp->getVersion() << "has"
+ << comp->m_cachedRequires.size() << "dependencies";
+ for (auto dep : comp->m_cachedRequires) {
+ qCDebug(instanceProfileC) << instance->name() << "|" << uid << "depends on" << dep.uid;
+ auto found = componentIndex.find(dep.uid);
+ if (found != componentIndex.end()) {
+ qCDebug(instanceProfileC) << instance->name() << "|" << (*found)->getID() << "is present";
+ linked.append(*found);
+ }
+ }
+ }
+ return linked;
+}
+
// FIXME, TODO: decouple dependency resolution from loading
// FIXME: This works directly with the PackProfile internals. It shouldn't! It needs richer data types than PackProfile uses.
// FIXME: throw all this away and use a graph
void ComponentUpdateTask::resolveDependencies(bool checkOnly)
{
- qDebug() << "Resolving dependencies";
+ qCDebug(instanceProfileResolveC) << "Resolving dependencies";
/*
* this is a naive dependency resolving algorithm. all it does is check for following conditions and react in simple ways:
* 1. There are conflicting dependencies on the same uid with different exact version numbers
@@ -393,8 +437,8 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
*
* NOTE: this is a placeholder and should eventually be replaced with something 'serious'
*/
- auto& components = d->m_list->d->components;
- auto& componentIndex = d->m_list->d->componentIndex;
+ auto& components = d->m_profile->d->components;
+ auto& componentIndex = d->m_profile->d->componentIndex;
RequireExSet allRequires;
QStringList toRemove;
@@ -402,15 +446,16 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
allRequires.clear();
toRemove.clear();
if (!gatherRequirementsFromComponents(components, allRequires)) {
+ finalizeComponents();
emitFailed(tr("Conflicting requirements detected during dependency checking!"));
return;
}
getTrivialRemovals(components, allRequires, toRemove);
if (!toRemove.isEmpty()) {
- qDebug() << "Removing obsolete components...";
+ qCDebug(instanceProfileResolveC) << "Removing obsolete components...";
for (auto& remove : toRemove) {
- qDebug() << "Removing" << remove;
- d->m_list->remove(remove);
+ qCDebug(instanceProfileResolveC) << "Removing" << remove;
+ d->m_profile->remove(remove);
}
}
} while (!toRemove.isEmpty());
@@ -418,10 +463,12 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
RequireExSet toChange;
bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange);
if (!succeeded) {
+ finalizeComponents();
emitFailed(tr("Instance has conflicting dependencies."));
return;
}
if (checkOnly) {
+ finalizeComponents();
if (toAdd.size() || toChange.size()) {
emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch."));
} else {
@@ -434,14 +481,15 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
if (toAdd.size()) {
// add stuff...
for (auto& add : toAdd) {
- auto component = makeShared(d->m_list, add.uid);
+ auto component = makeShared(d->m_profile, add.uid);
if (!add.equalsVersion.isEmpty()) {
// exact version
- qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee;
+ qCDebug(instanceProfileResolveC)
+ << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee;
component->m_version = add.equalsVersion;
} else {
// version needs to be decided
- qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee;
+ qCDebug(instanceProfileResolveC) << "Adding" << add.uid << "at position" << add.indexOfFirstDependee;
// ############################################################################################################
// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
if (!add.suggests.isEmpty()) {
@@ -464,7 +512,7 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
}
component->m_dependencyOnly = true;
// FIXME: this should not work directly with the component list
- d->m_list->insertComponent(add.indexOfFirstDependee, component);
+ d->m_profile->insertComponent(add.indexOfFirstDependee, component);
componentIndex[add.uid] = component;
}
recursionNeeded = true;
@@ -473,7 +521,7 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
// change a version of something that exists
for (auto& change : toChange) {
// FIXME: this should not work directly with the component list
- qDebug() << "Setting version of " << change.uid << "to" << change.equalsVersion;
+ qCDebug(instanceProfileResolveC) << "Setting version of " << change.uid << "to" << change.equalsVersion;
auto component = componentIndex[change.uid];
component->setVersion(change.equalsVersion);
}
@@ -483,14 +531,182 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
if (recursionNeeded) {
loadComponents();
} else {
+ finalizeComponents();
emitSucceeded();
}
}
+// Variant visitation via lambda
+template
+struct overload : Ts... {
+ using Ts::operator()...;
+};
+template
+overload(Ts...) -> overload;
+
+void ComponentUpdateTask::performUpdateActions()
+{
+ auto& instance = d->m_profile->d->m_instance;
+ bool addedActions;
+ QStringList toRemove;
+ do {
+ addedActions = false;
+ toRemove.clear();
+ auto& components = d->m_profile->d->components;
+ auto& componentIndex = d->m_profile->d->componentIndex;
+ for (auto component : components) {
+ if (!component) {
+ continue;
+ }
+ auto action = component->getUpdateAction();
+ auto visitor =
+ overload{ [](const UpdateActionNone&) {
+ // noop
+ },
+ [&component, &instance](const UpdateActionChangeVersion& cv) {
+ qCDebug(instanceProfileResolveC) << instance->name() << "|"
+ << "UpdateActionChangeVersion" << component->getID() << ":"
+ << component->getVersion() << "change to" << cv.targetVersion;
+ component->setVersion(cv.targetVersion);
+ component->waitLoadMeta();
+ },
+ [&component, &instance](const UpdateActionLatestRecommendedCompatible lrc) {
+ qCDebug(instanceProfileResolveC)
+ << instance->name() << "|"
+ << "UpdateActionLatestRecommendedCompatible" << component->getID() << ":" << component->getVersion()
+ << "updating to latest recommend or compatible with" << lrc.parentUid << lrc.version;
+ auto versionList = APPLICATION->metadataIndex()->get(component->getID());
+ if (versionList) {
+ versionList->waitToLoad();
+ auto recommended = versionList->getRecommendedForParent(lrc.parentUid, lrc.version);
+ if (!recommended) {
+ recommended = versionList->getLatestForParent(lrc.parentUid, lrc.version);
+ }
+ if (recommended) {
+ component->setVersion(recommended->version());
+ component->waitLoadMeta();
+ return;
+ } else {
+ component->addComponentProblem(ProblemSeverity::Error,
+ QObject::tr("No compatible version of %1 found for %2 %3")
+ .arg(component->getName(), lrc.parentName, lrc.version));
+ }
+ } else {
+ component->addComponentProblem(
+ ProblemSeverity::Error,
+ QObject::tr("No version list in metadata index for %1").arg(component->getID()));
+ }
+ },
+ [&component, &instance, &toRemove](const UpdateActionRemove&) {
+ qCDebug(instanceProfileResolveC)
+ << instance->name() << "|"
+ << "UpdateActionRemove" << component->getID() << ":" << component->getVersion() << "removing";
+ toRemove.append(component->getID());
+ },
+ [this, &component, &instance, &addedActions, &componentIndex](const UpdateActionImportantChanged& ic) {
+ qCDebug(instanceProfileResolveC)
+ << instance->name() << "|"
+ << "UpdateImportantChanged" << component->getID() << ":" << component->getVersion() << "was changed from"
+ << ic.oldVersion << "updating linked components";
+ auto oldVersion = APPLICATION->metadataIndex()->getLoadedVersion(component->getID(), ic.oldVersion);
+ for (auto oldReq : oldVersion->requiredSet()) {
+ auto currentlyRequired = component->m_cachedRequires.find(oldReq);
+ if (currentlyRequired == component->m_cachedRequires.cend()) {
+ auto oldReqComp = componentIndex.find(oldReq.uid);
+ if (oldReqComp != componentIndex.cend()) {
+ (*oldReqComp)->setUpdateAction(UpdateAction{ UpdateActionRemove{} });
+ addedActions = true;
+ }
+ }
+ }
+ auto linked = collectTreeLinked(component->getID());
+ for (auto comp : linked) {
+ if (comp->isCustom()) {
+ continue;
+ }
+ auto compUid = comp->getID();
+ auto parentReq = std::find_if(component->m_cachedRequires.begin(), component->m_cachedRequires.end(),
+ [compUid](const Meta::Require& req) { return req.uid == compUid; });
+ if (parentReq != component->m_cachedRequires.end()) {
+ auto newVersion = parentReq->equalsVersion.isEmpty() ? parentReq->suggests : parentReq->equalsVersion;
+ if (!newVersion.isEmpty()) {
+ comp->setUpdateAction(UpdateAction{ UpdateActionChangeVersion{ newVersion } });
+ } else {
+ comp->setUpdateAction(UpdateAction{ UpdateActionLatestRecommendedCompatible{
+ component->getID(),
+ component->getName(),
+ component->getVersion(),
+ } });
+ }
+ } else {
+ comp->setUpdateAction(UpdateAction{ UpdateActionLatestRecommendedCompatible{
+ component->getID(),
+ component->getName(),
+ component->getVersion(),
+ } });
+ }
+ addedActions = true;
+ }
+ } };
+ std::visit(visitor, action);
+ component->clearUpdateAction();
+ for (auto uid : toRemove) {
+ d->m_profile->remove(uid);
+ }
+ }
+ } while (addedActions);
+}
+
+void ComponentUpdateTask::finalizeComponents()
+{
+ auto& components = d->m_profile->d->components;
+ auto& componentIndex = d->m_profile->d->componentIndex;
+ for (auto component : components) {
+ for (auto req : component->m_cachedRequires) {
+ auto found = componentIndex.find(req.uid);
+ if (found == componentIndex.cend()) {
+ component->addComponentProblem(
+ ProblemSeverity::Error,
+ QObject::tr("%1 is missing requirement %2 %3")
+ .arg(component->getName(), req.uid, req.equalsVersion.isEmpty() ? req.suggests : req.equalsVersion));
+ } else {
+ auto reqComp = *found;
+ if (!reqComp->getProblems().isEmpty()) {
+ component->addComponentProblem(
+ reqComp->getProblemSeverity(),
+ QObject::tr("%1, a dependency of this component, has reported issues").arg(reqComp->getName()));
+ }
+ if (!req.equalsVersion.isEmpty() && req.equalsVersion != reqComp->getVersion()) {
+ component->addComponentProblem(ProblemSeverity::Error,
+ QObject::tr("%1, a dependency of this component, is not the required version %2")
+ .arg(reqComp->getName(), req.equalsVersion));
+ } else if (!req.suggests.isEmpty() && req.suggests != reqComp->getVersion()) {
+ component->addComponentProblem(ProblemSeverity::Warning,
+ QObject::tr("%1, a dependency of this component, is not the suggested version %2")
+ .arg(reqComp->getName(), req.suggests));
+ }
+ }
+ }
+ for (auto conflict : component->knownConflictingComponents()) {
+ auto found = componentIndex.find(conflict);
+ if (found != componentIndex.cend()) {
+ auto foundComp = *found;
+ if (foundComp->isCustom()) {
+ continue;
+ }
+ component->addComponentProblem(
+ ProblemSeverity::Warning,
+ QObject::tr("%1 and %2 are known to not work together. It is recommended to remove one of them.")
+ .arg(component->getName(), foundComp->getName()));
+ }
+ }
+ }
+}
+
void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
{
if (static_cast(d->remoteLoadStatusList.size()) < taskIndex) {
- qWarning() << "Got task index outside of results" << taskIndex;
+ qCWarning(instanceProfileResolveC) << "Got task index outside of results" << taskIndex;
return;
}
auto& taskSlot = d->remoteLoadStatusList[taskIndex];
@@ -498,16 +714,16 @@ void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
disconnect(taskSlot.task.get(), &Task::failed, this, nullptr);
disconnect(taskSlot.task.get(), &Task::aborted, this, nullptr);
if (taskSlot.finished) {
- qWarning() << "Got multiple results from remote load task" << taskIndex;
+ qCWarning(instanceProfileResolveC) << "Got multiple results from remote load task" << taskIndex;
return;
}
- qDebug() << "Remote task" << taskIndex << "succeeded";
+ qCDebug(instanceProfileResolveC) << "Remote task" << taskIndex << "succeeded";
taskSlot.succeeded = false;
taskSlot.finished = true;
d->remoteTasksInProgress--;
// update the cached data of the component from the downloaded version file.
if (taskSlot.type == RemoteLoadStatus::Type::Version) {
- auto component = d->m_list->getComponent(taskSlot.PackProfileIndex);
+ auto component = d->m_profile->getComponent(taskSlot.PackProfileIndex);
component->m_loaded = true;
component->updateCachedData();
}
@@ -517,7 +733,7 @@ void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg)
{
if (static_cast(d->remoteLoadStatusList.size()) < taskIndex) {
- qWarning() << "Got task index outside of results" << taskIndex;
+ qCWarning(instanceProfileResolveC) << "Got task index outside of results" << taskIndex;
return;
}
auto& taskSlot = d->remoteLoadStatusList[taskIndex];
@@ -525,10 +741,10 @@ void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg)
disconnect(taskSlot.task.get(), &Task::failed, this, nullptr);
disconnect(taskSlot.task.get(), &Task::aborted, this, nullptr);
if (taskSlot.finished) {
- qWarning() << "Got multiple results from remote load task" << taskIndex;
+ qCWarning(instanceProfileResolveC) << "Got multiple results from remote load task" << taskIndex;
return;
}
- qDebug() << "Remote task" << taskIndex << "failed: " << msg;
+ qCDebug(instanceProfileResolveC) << "Remote task" << taskIndex << "failed: " << msg;
d->remoteLoadSuccessful = false;
taskSlot.succeeded = false;
taskSlot.finished = true;
@@ -546,6 +762,7 @@ void ComponentUpdateTask::checkIfAllFinished()
if (d->remoteLoadSuccessful) {
// nothing bad happened... clear the temp load status and proceed with looking at dependencies
d->remoteLoadStatusList.clear();
+ performUpdateActions();
resolveDependencies(d->mode == Mode::Launch);
} else {
// remote load failed... report error and bail
diff --git a/launcher/minecraft/ComponentUpdateTask.h b/launcher/minecraft/ComponentUpdateTask.h
index 2f396a049..64c55877b 100644
--- a/launcher/minecraft/ComponentUpdateTask.h
+++ b/launcher/minecraft/ComponentUpdateTask.h
@@ -1,5 +1,6 @@
#pragma once
+#include "minecraft/Component.h"
#include "net/Mode.h"
#include "tasks/Task.h"
@@ -13,7 +14,7 @@ class ComponentUpdateTask : public Task {
enum class Mode { Launch, Resolution };
public:
- explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent = 0);
+ explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list);
virtual ~ComponentUpdateTask();
protected:
@@ -21,7 +22,11 @@ class ComponentUpdateTask : public Task {
private:
void loadComponents();
+ /// collects components that are dependent on or dependencies of the component
+ QList collectTreeLinked(const QString& uid);
void resolveDependencies(bool checkOnly);
+ void performUpdateActions();
+ void finalizeComponents();
void remoteLoadSucceeded(size_t index);
void remoteLoadFailed(size_t index, const QString& msg);
diff --git a/launcher/minecraft/ComponentUpdateTask_p.h b/launcher/minecraft/ComponentUpdateTask_p.h
index b82553700..2fc0b6d9a 100644
--- a/launcher/minecraft/ComponentUpdateTask_p.h
+++ b/launcher/minecraft/ComponentUpdateTask_p.h
@@ -6,6 +6,8 @@
#include "net/Mode.h"
#include "tasks/Task.h"
+#include "minecraft/ComponentUpdateTask.h"
+
class PackProfile;
struct RemoteLoadStatus {
@@ -18,7 +20,7 @@ struct RemoteLoadStatus {
};
struct ComponentUpdateTaskData {
- PackProfile* m_list = nullptr;
+ PackProfile* m_profile = nullptr;
QList remoteLoadStatusList;
bool remoteLoadSuccessful = true;
size_t remoteTasksInProgress = 0;
diff --git a/launcher/minecraft/LaunchProfile.cpp b/launcher/minecraft/LaunchProfile.cpp
index 468798850..c11a0f915 100644
--- a/launcher/minecraft/LaunchProfile.cpp
+++ b/launcher/minecraft/LaunchProfile.cpp
@@ -164,6 +164,7 @@ void LaunchProfile::applyCompatibleJavaMajors(QList& javaMajor)
{
m_compatibleJavaMajors.append(javaMajor);
}
+
void LaunchProfile::applyCompatibleJavaName(QString javaName)
{
if (!javaName.isEmpty())
diff --git a/launcher/minecraft/Logging.cpp b/launcher/minecraft/Logging.cpp
new file mode 100644
index 000000000..92596de3e
--- /dev/null
+++ b/launcher/minecraft/Logging.cpp
@@ -0,0 +1,25 @@
+
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 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
+ * 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 .
+ *
+ */
+
+#include "minecraft/Logging.h"
+#include
+
+Q_LOGGING_CATEGORY(instanceProfileC, "launcher.instance.profile")
+Q_LOGGING_CATEGORY(instanceProfileResolveC, "launcher.instance.profile.resolve")
diff --git a/launcher/minecraft/Logging.h b/launcher/minecraft/Logging.h
new file mode 100644
index 000000000..00d43f419
--- /dev/null
+++ b/launcher/minecraft/Logging.h
@@ -0,0 +1,26 @@
+
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 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
+ * 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 .
+ *
+ */
+
+#pragma once
+
+#include
+
+Q_DECLARE_LOGGING_CATEGORY(instanceProfileC)
+Q_DECLARE_LOGGING_CATEGORY(instanceProfileResolveC)
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index d861056bf..d6d45af6b 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -43,6 +43,9 @@
#include "minecraft/launch/CreateGameFolders.h"
#include "minecraft/launch/ExtractNatives.h"
#include "minecraft/launch/PrintInstanceInfo.h"
+#include "minecraft/update/AssetUpdateTask.h"
+#include "minecraft/update/FMLLibrariesTask.h"
+#include "minecraft/update/LibrariesTask.h"
#include "settings/Setting.h"
#include "settings/SettingsObject.h"
@@ -53,13 +56,13 @@
#include "pathmatcher/RegexpMatcher.h"
#include "launch/LaunchTask.h"
+#include "launch/TaskStepWrapper.h"
#include "launch/steps/CheckJava.h"
#include "launch/steps/LookupServerAddress.h"
#include "launch/steps/PostLaunchCommand.h"
#include "launch/steps/PreLaunchCommand.h"
#include "launch/steps/QuitAfterGameStop.h"
#include "launch/steps/TextPrint.h"
-#include "launch/steps/Update.h"
#include "minecraft/launch/ClaimAccount.h"
#include "minecraft/launch/LauncherPartLaunch.h"
@@ -70,9 +73,6 @@
#include "java/JavaUtils.h"
-#include "meta/Index.h"
-#include "meta/VersionList.h"
-
#include "icons/IconList.h"
#include "mod/ModFolderModel.h"
@@ -84,7 +84,6 @@
#include "AssetsUtils.h"
#include "MinecraftLoadAndCheck.h"
-#include "MinecraftUpdate.h"
#include "PackProfile.h"
#include "minecraft/gameoptions/GameOptions.h"
#include "minecraft/update/FoldersTask.h"
@@ -194,7 +193,7 @@ void MinecraftInstance::loadSpecificSettings()
}
// Join server on launch, this does not have a global override
- m_settings->registerSetting({ "JoinServerOnLaunch", "JoinOnLaunch" }, false);
+ m_settings->registerSetting("JoinServerOnLaunch", false);
m_settings->registerSetting("JoinServerOnLaunchAddress", "");
m_settings->registerSetting("JoinWorldOnLaunch", "");
@@ -218,6 +217,7 @@ void MinecraftInstance::loadSpecificSettings()
void MinecraftInstance::updateRuntimeContext()
{
m_runtimeContext.updateFromInstanceSettings(m_settings);
+ m_components->invalidateLaunchProfile();
}
QString MinecraftInstance::typeName() const
@@ -1030,18 +1030,18 @@ QString MinecraftInstance::getStatusbarDescription()
return description;
}
-Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode)
+QList MinecraftInstance::createUpdateTask()
{
- updateRuntimeContext();
- switch (mode) {
- case Net::Mode::Offline: {
- return Task::Ptr(new MinecraftLoadAndCheck(this));
- }
- case Net::Mode::Online: {
- return Task::Ptr(new MinecraftUpdate(this));
- }
- }
- return nullptr;
+ return {
+ // create folders
+ makeShared(this),
+ // libraries download
+ makeShared(this),
+ // FML libraries download and copy into the instance
+ makeShared(this),
+ // assets update
+ makeShared(this),
+ };
}
shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin)
@@ -1063,7 +1063,7 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(makeShared(pptr));
}
- if (!targetToJoin && settings()->get("JoinOnLaunch").toBool()) {
+ if (!targetToJoin && settings()->get("JoinServerOnLaunch").toBool()) {
QString fullAddress = settings()->get("JoinServerOnLaunchAddress").toString();
if (!fullAddress.isEmpty()) {
targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(fullAddress, false)));
@@ -1090,14 +1090,10 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(step);
}
- // if we aren't in offline mode,.
- if (session->status != AuthSession::PlayableOffline) {
- if (!session->demo) {
- process->appendStep(makeShared(pptr, session));
- }
- process->appendStep(makeShared(pptr, Net::Mode::Online));
- } else {
- process->appendStep(makeShared(pptr, Net::Mode::Offline));
+ // load meta
+ {
+ auto mode = session->status != AuthSession::PlayableOffline ? Net::Mode::Online : Net::Mode::Offline;
+ process->appendStep(makeShared(pptr, makeShared(this, mode)));
}
// check java
@@ -1106,6 +1102,16 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(makeShared(pptr));
}
+ // if we aren't in offline mode,.
+ if (session->status != AuthSession::PlayableOffline) {
+ if (!session->demo) {
+ process->appendStep(makeShared(pptr, session));
+ }
+ for (auto t : createUpdateTask()) {
+ process->appendStep(makeShared(pptr, t));
+ }
+ }
+
// if there are any jar mods
{
process->appendStep(makeShared(pptr));
diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h
index ad2cda186..75e97ae45 100644
--- a/launcher/minecraft/MinecraftInstance.h
+++ b/launcher/minecraft/MinecraftInstance.h
@@ -104,7 +104,7 @@ class MinecraftInstance : public BaseInstance {
/** Returns whether the instance, with its version, has support for demo mode. */
[[nodiscard]] bool supportsDemo() const;
- void updateRuntimeContext();
+ void updateRuntimeContext() override;
////// Profile management //////
std::shared_ptr getPackProfile() const;
@@ -120,7 +120,7 @@ class MinecraftInstance : public BaseInstance {
std::shared_ptr gameOptionsModel();
////// Launch stuff //////
- Task::Ptr createUpdateTask(Net::Mode mode) override;
+ QList createUpdateTask() override;
shared_qobject_ptr createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) override;
QStringList extraArguments() override;
QStringList verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) override;
diff --git a/launcher/minecraft/MinecraftLoadAndCheck.cpp b/launcher/minecraft/MinecraftLoadAndCheck.cpp
index 818e90cfc..b9fb7eb0c 100644
--- a/launcher/minecraft/MinecraftLoadAndCheck.cpp
+++ b/launcher/minecraft/MinecraftLoadAndCheck.cpp
@@ -2,41 +2,42 @@
#include "MinecraftInstance.h"
#include "PackProfile.h"
-MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, QObject* parent) : Task(parent), m_inst(inst) {}
+MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode) : m_inst(inst), m_netmode(netmode) {}
void MinecraftLoadAndCheck::executeTask()
{
// add offline metadata load task
auto components = m_inst->getPackProfile();
- components->reload(Net::Mode::Offline);
+ components->reload(m_netmode);
m_task = components->getCurrentTask();
if (!m_task) {
emitSucceeded();
return;
}
- connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded);
- connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed);
- connect(m_task.get(), &Task::aborted, this, [this] { subtaskFailed(tr("Aborted")); });
- connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress);
+ connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::emitSucceeded);
+ connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::emitFailed);
+ connect(m_task.get(), &Task::aborted, this, [this] { emitFailed(tr("Aborted")); });
+ connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::setProgress);
connect(m_task.get(), &Task::stepProgress, this, &MinecraftLoadAndCheck::propagateStepProgress);
connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus);
+ connect(m_task.get(), &Task::details, this, &MinecraftLoadAndCheck::setDetails);
}
-void MinecraftLoadAndCheck::subtaskSucceeded()
+bool MinecraftLoadAndCheck::canAbort() const
{
- if (isFinished()) {
- qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!";
- return;
+ if (m_task) {
+ return m_task->canAbort();
}
- emitSucceeded();
+ return true;
}
-void MinecraftLoadAndCheck::subtaskFailed(QString error)
+bool MinecraftLoadAndCheck::abort()
{
- if (isFinished()) {
- qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!";
- return;
+ if (m_task && m_task->canAbort()) {
+ auto status = m_task->abort();
+ emitFailed("Aborted.");
+ return status;
}
- emitFailed(error);
-}
+ return Task::abort();
+}
\ No newline at end of file
diff --git a/launcher/minecraft/MinecraftLoadAndCheck.h b/launcher/minecraft/MinecraftLoadAndCheck.h
index 09edaf909..c05698bca 100644
--- a/launcher/minecraft/MinecraftLoadAndCheck.h
+++ b/launcher/minecraft/MinecraftLoadAndCheck.h
@@ -15,32 +15,24 @@
#pragma once
-#include
-#include
-#include
-
-#include
+#include "net/Mode.h"
#include "tasks/Task.h"
-#include "QObjectPtr.h"
-
-class MinecraftVersion;
class MinecraftInstance;
class MinecraftLoadAndCheck : public Task {
Q_OBJECT
public:
- explicit MinecraftLoadAndCheck(MinecraftInstance* inst, QObject* parent = 0);
- virtual ~MinecraftLoadAndCheck() {};
+ explicit MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode);
+ virtual ~MinecraftLoadAndCheck() = default;
void executeTask() override;
- private slots:
- void subtaskSucceeded();
- void subtaskFailed(QString error);
+ bool canAbort() const override;
+ public slots:
+ bool abort() override;
private:
MinecraftInstance* m_inst = nullptr;
Task::Ptr m_task;
- QString m_preFailure;
- QString m_fail_reason;
+ Net::Mode m_netmode;
};
diff --git a/launcher/minecraft/MinecraftUpdate.cpp b/launcher/minecraft/MinecraftUpdate.cpp
deleted file mode 100644
index b63430aa8..000000000
--- a/launcher/minecraft/MinecraftUpdate.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "MinecraftUpdate.h"
-#include "MinecraftInstance.h"
-
-#include "minecraft/PackProfile.h"
-
-#include "tasks/SequentialTask.h"
-#include "update/AssetUpdateTask.h"
-#include "update/FMLLibrariesTask.h"
-#include "update/FoldersTask.h"
-#include "update/LibrariesTask.h"
-
-MinecraftUpdate::MinecraftUpdate(MinecraftInstance* inst, QObject* parent) : SequentialTask(parent), m_inst(inst) {}
-
-void MinecraftUpdate::executeTask()
-{
- m_queue.clear();
- // create folders
- {
- addTask(makeShared(m_inst));
- }
-
- // add metadata update task if necessary
- {
- auto components = m_inst->getPackProfile();
- components->reload(Net::Mode::Online);
- auto task = components->getCurrentTask();
- if (task) {
- addTask(task);
- }
- }
-
- // libraries download
- {
- addTask(makeShared(m_inst));
- }
-
- // FML libraries download and copy into the instance
- {
- addTask(makeShared(m_inst));
- }
-
- // assets update
- {
- addTask(makeShared(m_inst));
- }
-
- SequentialTask::executeTask();
-}
diff --git a/launcher/minecraft/MinecraftUpdate.h b/launcher/minecraft/MinecraftUpdate.h
deleted file mode 100644
index 456a13518..000000000
--- a/launcher/minecraft/MinecraftUpdate.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "tasks/SequentialTask.h"
-
-class MinecraftInstance;
-
-// this needs to be a task because components->reload does stuff that may block
-class MinecraftUpdate : public SequentialTask {
- Q_OBJECT
- public:
- explicit MinecraftUpdate(MinecraftInstance* inst, QObject* parent = 0);
- virtual ~MinecraftUpdate() = default;
-
- void executeTask() override;
-
- private:
- MinecraftInstance* m_inst = nullptr;
-};
diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp
index a8860935c..f1d2473c2 100644
--- a/launcher/minecraft/PackProfile.cpp
+++ b/launcher/minecraft/PackProfile.cpp
@@ -38,6 +38,7 @@
*/
#include
+#include
#include
#include
#include
@@ -47,10 +48,16 @@
#include
#include
#include
+#include
+#include
+#include "Application.h"
#include "Exception.h"
#include "FileSystem.h"
#include "Json.h"
+#include "meta/Index.h"
+#include "meta/JsonFormat.h"
+#include "minecraft/Component.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/OneSixVersionFormat.h"
#include "minecraft/ProfileUtils.h"
@@ -60,11 +67,9 @@
#include "PackProfile_p.h"
#include "modplatform/ModIndex.h"
-static const QMap modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
- { "net.minecraftforge", ModPlatform::Forge },
- { "net.fabricmc.fabric-loader", ModPlatform::Fabric },
- { "org.quiltmc.quilt-loader", ModPlatform::Quilt },
- { "com.mumfrey.liteloader", ModPlatform::LiteLoader } };
+#include "minecraft/Logging.h"
+
+#include "ui/dialogs/CustomMessageBox.h"
PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel()
{
@@ -153,16 +158,16 @@ static bool savePackProfile(const QString& filename, const ComponentContainer& c
obj.insert("components", orderArray);
QSaveFile outFile(filename);
if (!outFile.open(QFile::WriteOnly)) {
- qCritical() << "Couldn't open" << outFile.fileName() << "for writing:" << outFile.errorString();
+ qCCritical(instanceProfileC) << "Couldn't open" << outFile.fileName() << "for writing:" << outFile.errorString();
return false;
}
auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented);
if (outFile.write(data) != data.size()) {
- qCritical() << "Couldn't write all the data into" << outFile.fileName() << "because:" << outFile.errorString();
+ qCCritical(instanceProfileC) << "Couldn't write all the data into" << outFile.fileName() << "because:" << outFile.errorString();
return false;
}
if (!outFile.commit()) {
- qCritical() << "Couldn't save" << outFile.fileName() << "because:" << outFile.errorString();
+ qCCritical(instanceProfileC) << "Couldn't save" << outFile.fileName() << "because:" << outFile.errorString();
}
return true;
}
@@ -175,12 +180,12 @@ static bool loadPackProfile(PackProfile* parent,
{
QFile componentsFile(filename);
if (!componentsFile.exists()) {
- qWarning() << "Components file doesn't exist. This should never happen.";
+ qCWarning(instanceProfileC) << "Components file" << filename << "doesn't exist. This should never happen.";
return false;
}
if (!componentsFile.open(QFile::ReadOnly)) {
- qCritical() << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString();
- qWarning() << "Ignoring overridden order";
+ qCCritical(instanceProfileC) << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString();
+ qCWarning(instanceProfileC) << "Ignoring overridden order";
return false;
}
@@ -188,8 +193,8 @@ static bool loadPackProfile(PackProfile* parent,
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error);
if (error.error != QJsonParseError::NoError) {
- qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString();
- qWarning() << "Ignoring overridden order";
+ qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString();
+ qCWarning(instanceProfileC) << "Ignoring overridden order";
return false;
}
@@ -207,7 +212,7 @@ static bool loadPackProfile(PackProfile* parent,
container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj));
}
} catch ([[maybe_unused]] const JSONValidationError& err) {
- qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format";
+ qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ": bad file format";
container.clear();
return false;
}
@@ -240,12 +245,12 @@ void PackProfile::buildingFromScratch()
void PackProfile::scheduleSave()
{
if (!d->loaded) {
- qDebug() << "Component list should never save if it didn't successfully load, instance:" << d->m_instance->name();
+ qDebug() << d->m_instance->name() << "|" << "Component list should never save if it didn't successfully load";
return;
}
if (!d->dirty) {
d->dirty = true;
- qDebug() << "Component list save is scheduled for" << d->m_instance->name();
+ qDebug() << d->m_instance->name() << "|" << "Component list save is scheduled";
}
d->m_saveTimer.start();
}
@@ -272,7 +277,7 @@ QString PackProfile::patchFilePathForUid(const QString& uid) const
void PackProfile::save_internal()
{
- qDebug() << "Component list save performed now for" << d->m_instance->name();
+ qDebug() << d->m_instance->name() << "|" << "Component list save performed now";
auto filename = componentsFilePath();
savePackProfile(filename, d->components);
d->dirty = false;
@@ -285,7 +290,7 @@ bool PackProfile::load()
// load the new component list and swap it with the current one...
ComponentContainer newComponents;
if (!loadPackProfile(this, filename, patchesPattern(), newComponents)) {
- qCritical() << "Failed to load the component config for instance" << d->m_instance->name();
+ qCritical() << d->m_instance->name() << "|" << "Failed to load the component config";
return false;
} else {
// FIXME: actually use fine-grained updates, not this...
@@ -298,7 +303,7 @@ bool PackProfile::load()
d->componentIndex.clear();
for (auto component : newComponents) {
if (d->componentIndex.contains(component->m_uid)) {
- qWarning() << "Ignoring duplicate component entry" << component->m_uid;
+ qWarning() << d->m_instance->name() << "|" << "Ignoring duplicate component entry" << component->m_uid;
continue;
}
connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
@@ -346,14 +351,14 @@ void PackProfile::resolve(Net::Mode netmode)
void PackProfile::updateSucceeded()
{
- qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name();
+ qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Component list update/resolve task succeeded";
d->m_updateTask.reset();
invalidateLaunchProfile();
}
void PackProfile::updateFailed(const QString& error)
{
- qDebug() << "Component list update/resolve task failed for" << d->m_instance->name() << "Reason:" << error;
+ qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Component list update/resolve task failed " << "Reason:" << error;
d->m_updateTask.reset();
invalidateLaunchProfile();
}
@@ -369,11 +374,11 @@ void PackProfile::insertComponent(size_t index, ComponentPtr component)
{
auto id = component->getID();
if (id.isEmpty()) {
- qWarning() << "Attempt to add a component with empty ID!";
+ qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Attempt to add a component with empty ID!";
return;
}
if (d->componentIndex.contains(id)) {
- qWarning() << "Attempt to add a component that is already present!";
+ qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Attempt to add a component that is already present!";
return;
}
beginInsertRows(QModelIndex(), static_cast(index), static_cast(index));
@@ -388,7 +393,7 @@ void PackProfile::componentDataChanged()
{
auto objPtr = qobject_cast(sender());
if (!objPtr) {
- qWarning() << "PackProfile got dataChanged signal from a non-Component!";
+ qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "PackProfile got dataChanged signal from a non-Component!";
return;
}
if (objPtr->getID() == "net.minecraft") {
@@ -404,19 +409,20 @@ void PackProfile::componentDataChanged()
}
index++;
}
- qWarning() << "PackProfile got dataChanged signal from a Component which does not belong to it!";
+ qCWarning(instanceProfileC) << d->m_instance->name() << "|"
+ << "PackProfile got dataChanged signal from a Component which does not belong to it!";
}
bool PackProfile::remove(const int index)
{
auto patch = getComponent(index);
if (!patch->isRemovable()) {
- qWarning() << "Patch" << patch->getID() << "is non-removable";
+ qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "is non-removable";
return false;
}
if (!removeComponent_internal(patch)) {
- qCritical() << "Patch" << patch->getID() << "could not be removed";
+ qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "could not be removed";
return false;
}
@@ -445,11 +451,11 @@ bool PackProfile::customize(int index)
{
auto patch = getComponent(index);
if (!patch->isCustomizable()) {
- qDebug() << "Patch" << patch->getID() << "is not customizable";
+ qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "is not customizable";
return false;
}
if (!patch->customize()) {
- qCritical() << "Patch" << patch->getID() << "could not be customized";
+ qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "could not be customized";
return false;
}
invalidateLaunchProfile();
@@ -461,11 +467,11 @@ bool PackProfile::revertToBase(int index)
{
auto patch = getComponent(index);
if (!patch->isRevertible()) {
- qDebug() << "Patch" << patch->getID() << "is not revertible";
+ qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "is not revertible";
return false;
}
if (!patch->revert()) {
- qCritical() << "Patch" << patch->getID() << "could not be reverted";
+ qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "could not be reverted";
return false;
}
invalidateLaunchProfile();
@@ -678,7 +684,8 @@ bool PackProfile::installComponents(QStringList selectedFiles)
const QString target = FS::PathCombine(patchDir, versionFile->uid + ".json");
if (!QFile::copy(source, target)) {
- qWarning() << "Component" << source << "could not be copied to target" << target;
+ qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Component" << source << "could not be copied to target"
+ << target;
result = false;
continue;
}
@@ -711,7 +718,8 @@ bool PackProfile::installEmpty(const QString& uid, const QString& name)
QString patchFileName = FS::PathCombine(patchDir, uid + ".json");
QFile file(patchFileName);
if (!file.open(QFile::WriteOnly)) {
- qCritical() << "Error opening" << file.fileName() << "for reading:" << file.errorString();
+ qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Error opening" << file.fileName()
+ << "for reading:" << file.errorString();
return false;
}
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
@@ -731,7 +739,8 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch)
if (fileName.size()) {
QFile patchFile(fileName);
if (patchFile.exists() && !patchFile.remove()) {
- qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString();
+ qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "File" << fileName
+ << "could not be removed because:" << patchFile.errorString();
return false;
}
}
@@ -747,7 +756,8 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch)
if (finfo.exists()) {
QFile jarModFile(jar[0]);
if (!jarModFile.remove()) {
- qCritical() << "File" << jar[0] << "could not be removed because:" << jarModFile.errorString();
+ qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "File" << jar[0]
+ << "could not be removed because:" << jarModFile.errorString();
return false;
}
return true;
@@ -804,7 +814,8 @@ bool PackProfile::installJarMods_internal(QStringList filepaths)
QFile file(patchFileName);
if (!file.open(QFile::WriteOnly)) {
- qCritical() << "Error opening" << file.fileName() << "for reading:" << file.errorString();
+ qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Error opening" << file.fileName()
+ << "for reading:" << file.errorString();
return false;
}
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
@@ -858,7 +869,8 @@ bool PackProfile::installCustomJar_internal(QString filepath)
QFile file(patchFileName);
if (!file.open(QFile::WriteOnly)) {
- qCritical() << "Error opening" << file.fileName() << "for reading:" << file.errorString();
+ qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Error opening" << file.fileName()
+ << "for reading:" << file.errorString();
return false;
}
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
@@ -913,7 +925,8 @@ bool PackProfile::installAgents_internal(QStringList filepaths)
QFile patchFile(FS::PathCombine(patchDir, targetId + ".json"));
if (!patchFile.open(QFile::WriteOnly)) {
- qCritical() << "Error opening" << patchFile.fileName() << "for reading:" << patchFile.errorString();
+ qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Error opening" << patchFile.fileName()
+ << "for reading:" << patchFile.errorString();
return false;
}
@@ -935,12 +948,13 @@ std::shared_ptr PackProfile::getProfile() const
try {
auto profile = std::make_shared();
for (auto file : d->components) {
- qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD");
+ qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Applying" << file->getID()
+ << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD");
file->applyTo(profile.get());
}
d->m_profile = profile;
} catch (const Exception& error) {
- qWarning() << "Couldn't apply profile patches because: " << error.cause();
+ qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Couldn't apply profile patches because: " << error.cause();
}
}
return d->m_profile;
@@ -953,8 +967,16 @@ bool PackProfile::setComponentVersion(const QString& uid, const QString& version
ComponentPtr component = *iter;
// set existing
if (component->revert()) {
+ // set new version
+ auto oldVersion = component->getVersion();
component->setVersion(version);
component->setImportant(important);
+
+ if (important) {
+ component->setUpdateAction(UpdateAction{ UpdateActionImportantChanged{ oldVersion } });
+ resolve(Net::Mode::Online);
+ }
+
return true;
}
return false;
@@ -993,12 +1015,12 @@ std::optional PackProfile::getModLoaders()
ModPlatform::ModLoaderTypes result;
bool has_any_loader = false;
- QMapIterator i(modloaderMapping);
+ QMapIterator i(Component::KNOWN_MODLOADERS);
while (i.hasNext()) {
i.next();
if (auto c = getComponent(i.key()); c != nullptr && c->isEnabled()) {
- result |= i.value();
+ result |= i.value().type;
has_any_loader = true;
}
}
@@ -1026,8 +1048,8 @@ QList PackProfile::getModLoadersList()
{
QList result;
for (auto c : d->components) {
- if (c->isEnabled() && modloaderMapping.contains(c->getID())) {
- result.append(modloaderMapping[c->getID()]);
+ if (c->isEnabled() && Component::KNOWN_MODLOADERS.contains(c->getID())) {
+ result.append(Component::KNOWN_MODLOADERS[c->getID()].type);
}
}
diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h
index 9b6710cc3..b2de26ea0 100644
--- a/launcher/minecraft/PackProfile.h
+++ b/launcher/minecraft/PackProfile.h
@@ -148,13 +148,13 @@ class PackProfile : public QAbstractListModel {
std::optional getSupportedModLoaders();
QList getModLoadersList();
+ /// apply the component patches. Catches all the errors and returns true/false for success/failure
+ void invalidateLaunchProfile();
+
private:
void scheduleSave();
bool saveIsScheduled() const;
- /// apply the component patches. Catches all the errors and returns true/false for success/failure
- void invalidateLaunchProfile();
-
/// insert component so that its index is ideally the specified one (returns real index)
void insertComponent(size_t index, ComponentPtr component);
diff --git a/launcher/minecraft/PackProfile_p.h b/launcher/minecraft/PackProfile_p.h
index 0cd4fb839..4fb3621f0 100644
--- a/launcher/minecraft/PackProfile_p.h
+++ b/launcher/minecraft/PackProfile_p.h
@@ -3,8 +3,8 @@
#include
#include
#include
-#include