Merge branch 'develop' into update-qt660

Signed-off-by: Tayou <git@tayou.org>
This commit is contained in:
Tayou 2023-10-16 19:57:05 +02:00 committed by GitHub
commit aae65e3e4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 3418 additions and 124 deletions

View File

@ -37,28 +37,27 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- os: ubuntu-20.04 - os: ubuntu-20.04
qt_ver: 5 qt_ver: 5
- os: ubuntu-20.04 - os: ubuntu-20.04
qt_ver: 6 qt_ver: 6
qt_host: linux qt_host: linux
qt_arch: '' qt_arch: ""
qt_version: '6.2.4' qt_version: "6.2.4"
qt_modules: 'qt5compat qtimageformats' qt_modules: "qt5compat qtimageformats"
qt_tools: '' qt_tools: ""
- os: windows-2022 - os: windows-2022
name: "Windows-MinGW-w64" name: "Windows-MinGW-w64"
msystem: clang64 msystem: clang64
vcvars_arch: 'amd64_x86' vcvars_arch: "amd64_x86"
- os: windows-2022 - os: windows-2022
name: "Windows-MSVC" name: "Windows-MSVC"
msystem: '' msystem: ""
architecture: 'x64' architecture: "x64"
vcvars_arch: 'amd64' vcvars_arch: "amd64"
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: '' qt_arch: ''
@ -68,9 +67,9 @@ jobs:
- os: windows-2022 - os: windows-2022
name: "Windows-MSVC-arm64" name: "Windows-MSVC-arm64"
msystem: '' msystem: ""
architecture: 'arm64' architecture: "arm64"
vcvars_arch: 'amd64_arm64' vcvars_arch: "amd64_arm64"
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: 'win64_msvc2019_arm64' qt_arch: 'win64_msvc2019_arm64'
@ -93,9 +92,9 @@ jobs:
macosx_deployment_target: 10.13 macosx_deployment_target: 10.13
qt_ver: 5 qt_ver: 5
qt_host: mac qt_host: mac
qt_version: '5.15.2' qt_version: "5.15.2"
qt_modules: '' qt_modules: ""
qt_tools: '' qt_tools: ""
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -115,9 +114,9 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
submodules: 'true' submodules: "true"
- name: 'Setup MSYS2' - name: "Setup MSYS2"
if: runner.os == 'Windows' && matrix.msystem != '' if: runner.os == 'Windows' && matrix.msystem != ''
uses: msys2/setup-msys2@v2 uses: msys2/setup-msys2@v2
with: with:
@ -157,7 +156,7 @@ jobs:
path: '${{ github.workspace }}\.ccache' path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
restore-keys: | restore-keys: |
${{ matrix.os }}-mingw-w64-ccache ${{ matrix.os }}-mingw-w64-ccache
- name: Setup ccache (Windows MinGW-w64) - name: Setup ccache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
@ -202,35 +201,35 @@ jobs:
if: runner.os == 'Windows' && matrix.architecture == 'arm64' if: runner.os == 'Windows' && matrix.architecture == 'arm64'
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v3
with: with:
aqtversion: '==3.1.*' aqtversion: "==3.1.*"
py7zrversion: '>=0.20.2' py7zrversion: ">=0.20.2"
version: ${{ matrix.qt_version }} version: ${{ matrix.qt_version }}
host: 'windows' host: "windows"
target: 'desktop' target: "desktop"
arch: '' arch: ""
modules: ${{ matrix.qt_modules }} modules: ${{ matrix.qt_modules }}
tools: ${{ matrix.qt_tools }} tools: ${{ matrix.qt_tools }}
cache: ${{ inputs.is_qt_cached }} cache: ${{ inputs.is_qt_cached }}
cache-key-prefix: host-qt-arm64-windows cache-key-prefix: host-qt-arm64-windows
dir: ${{ github.workspace }}\HostQt dir: ${{ github.workspace }}\HostQt
set-env: false set-env: false
- name: Install Qt (macOS, Linux, Qt 6 & Windows MSVC) - name: Install Qt (macOS, Linux, Qt 6 & Windows MSVC)
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '') if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '')
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v3
with: with:
aqtversion: '==3.1.*' aqtversion: "==3.1.*"
py7zrversion: '>=0.20.2' py7zrversion: ">=0.20.2"
version: ${{ matrix.qt_version }} version: ${{ matrix.qt_version }}
host: ${{ matrix.qt_host }} host: ${{ matrix.qt_host }}
target: 'desktop' target: "desktop"
arch: ${{ matrix.qt_arch }} arch: ${{ matrix.qt_arch }}
modules: ${{ matrix.qt_modules }} modules: ${{ matrix.qt_modules }}
tools: ${{ matrix.qt_tools }} tools: ${{ matrix.qt_tools }}
cache: ${{ inputs.is_qt_cached }} cache: ${{ inputs.is_qt_cached }}
- name: Install MSVC (Windows MSVC) - name: Install MSVC (Windows MSVC)
if: runner.os == 'Windows' # We want this for MinGW builds as well, as we need SignTool if: runner.os == 'Windows' # We want this for MinGW builds as well, as we need SignTool
uses: ilammy/msvc-dev-cmd@v1 uses: ilammy/msvc-dev-cmd@v1
with: with:
vsversion: 2022 vsversion: 2022
@ -271,12 +270,12 @@ jobs:
if: runner.os == 'Windows' && matrix.msystem != '' if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0} shell: msys2 {0}
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=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 -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=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) - name: Configure CMake (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == '' if: runner.os == 'Windows' && matrix.msystem == ''
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }}
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
if ("${{ env.CCACHE_VAR }}") if ("${{ env.CCACHE_VAR }}")
{ {
@ -291,7 +290,7 @@ jobs:
- name: Configure CMake (Linux) - name: Configure CMake (Linux)
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -G Ninja
## ##
# BUILD # BUILD
@ -331,7 +330,7 @@ jobs:
- name: Test (Windows MSVC) - name: Test (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64' if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
run: | run: |
ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }} ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
## ##
# PACKAGE BUILDS # PACKAGE BUILDS
@ -373,7 +372,7 @@ jobs:
run: | run: |
cmake --install ${{ env.BUILD_DIR }} cmake --install ${{ env.BUILD_DIR }}
touch ${{ env.INSTALL_DIR }}/manifest.txt touch ${{ env.INSTALL_DIR }}/manifest.txt
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt
- name: Package (Windows MSVC) - name: Package (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == '' if: runner.os == 'Windows' && matrix.msystem == ''
@ -390,10 +389,9 @@ jobs:
Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
- name: Fetch codesign certificate (Windows) - name: Fetch codesign certificate (Windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
shell: bash # yes, we are not using MSYS2 or PowerShell here shell: bash # yes, we are not using MSYS2 or PowerShell here
run: | run: |
echo '${{ secrets.WINDOWS_CODESIGN_CERT }}' | base64 --decode > codesign.pfx echo '${{ secrets.WINDOWS_CODESIGN_CERT }}' | base64 --decode > codesign.pfx
@ -403,7 +401,7 @@ jobs:
if (Get-Content ./codesign.pfx){ if (Get-Content ./codesign.pfx){
cd ${{ env.INSTALL_DIR }} cd ${{ env.INSTALL_DIR }}
# We ship the exact same executable for portable and non-portable editions, so signing just once is fine # We ship the exact same executable for portable and non-portable editions, so signing just once is fine
SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_filelink.exe SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_updater.exe prismlauncher_filelink.exe
} else { } else {
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY ":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
} }
@ -495,15 +493,7 @@ jobs:
export LD_LIBRARY_PATH export LD_LIBRARY_PATH
chmod +x AppImageUpdate-x86_64.AppImage chmod +x AppImageUpdate-x86_64.AppImage
./AppImageUpdate-x86_64.AppImage --appimage-extract cp AppImageUpdate-x86_64.AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/optional
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins
cp -r squashfs-root/usr/bin/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
cp -r squashfs-root/usr/lib/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib
cp -r squashfs-root/usr/optional/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/optional
cp -r squashfs-root/usr/optional/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins
export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync" export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync"
@ -557,14 +547,14 @@ jobs:
if: runner.os == 'Linux' && matrix.qt_ver != 6 if: runner.os == 'Linux' && matrix.qt_ver != 6
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }} name: PrismLauncher-${{ runner.os }}-Qt5-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher.tar.gz path: PrismLauncher.tar.gz
- name: Upload binary tarball (Linux, portable, Qt 5) - name: Upload binary tarball (Linux, portable, Qt 5)
if: runner.os == 'Linux' && matrix.qt_ver != 6 if: runner.os == 'Linux' && matrix.qt_ver != 6
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: PrismLauncher-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }} name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-portable.tar.gz path: PrismLauncher-portable.tar.gz
- name: Upload binary tarball (Linux, Qt 6) - name: Upload binary tarball (Linux, Qt 6)
@ -587,7 +577,7 @@ jobs:
with: with:
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
- name: Upload AppImage Zsync (Linux) - name: Upload AppImage Zsync (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5 if: runner.os == 'Linux' && matrix.qt_ver != 5
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@ -611,10 +601,10 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
if: inputs.build_type == 'Debug' if: inputs.build_type == 'Debug'
with: with:
submodules: 'true' submodules: "true"
- name: Build Flatpak (Linux) - name: Build Flatpak (Linux)
if: inputs.build_type == 'Debug' if: inputs.build_type == 'Debug'
uses: flatpak/flatpak-github-actions/flatpak-builder@v6 uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with: with:
bundle: "Prism Launcher.flatpak" bundle: "Prism Launcher.flatpak"
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml

View File

@ -3,26 +3,25 @@ name: Build Application
on: on:
push: push:
branches-ignore: branches-ignore:
- 'renovate/**' - "renovate/**"
paths-ignore: paths-ignore:
- '**.md' - "**.md"
- '**/LICENSE' - "**/LICENSE"
- 'flake.lock' - "flake.lock"
- 'packages/**' - "packages/**"
- '.github/ISSUE_TEMPLATE/**' - ".github/ISSUE_TEMPLATE/**"
- '.markdownlint**' - ".markdownlint**"
pull_request: pull_request:
paths-ignore: paths-ignore:
- '**.md' - "**.md"
- '**/LICENSE' - "**/LICENSE"
- 'flake.lock' - "flake.lock"
- 'packages/**' - "packages/**"
- '.github/ISSUE_TEMPLATE/**' - ".github/ISSUE_TEMPLATE/**"
- '.markdownlint**' - ".markdownlint**"
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build_debug: build_debug:
name: Build Debug name: Build Debug
uses: ./.github/workflows/build.yml uses: ./.github/workflows/build.yml
@ -34,3 +33,5 @@ jobs:
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }} WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }} WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}

View File

@ -17,6 +17,8 @@ jobs:
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }} WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }} WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
create_release: create_release:
needs: build_release needs: build_release
@ -39,9 +41,9 @@ jobs:
run: | run: |
mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }} mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }}
mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
mv PrismLauncher-Linux-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
mv PrismLauncher-Linux*/PrismLauncher.tar.gz PrismLauncher-Linux-${{ env.VERSION }}.tar.gz mv PrismLauncher-Linux-Qt5*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
@ -85,8 +87,8 @@ jobs:
draft: true draft: true
prerelease: false prerelease: false
files: | files: |
PrismLauncher-Linux-${{ env.VERSION }}.tar.gz PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-x86_64.AppImage PrismLauncher-Linux-x86_64.AppImage
PrismLauncher-Linux-x86_64.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz

View File

@ -188,8 +188,11 @@ set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_M
# Build platform. # Build platform.
set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.") set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
# Channel list URL # Github repo URL with releases for updater
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.") set(Launcher_UPDATER_GITHUB_REPO "https://github.com/PrismLauncher/PrismLauncher" CACHE STRING "Base github URL for the updater.")
# Name to help updater identify valid artifacts
set(Launcher_BUILD_ARTIFACT "" CACHE STRING "Artifact name to help the updater identify valid artifacts.")
# The metadata server # The metadata server
set(Launcher_META_URL "https://meta.prismlauncher.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.") set(Launcher_META_URL "https://meta.prismlauncher.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.")
@ -245,6 +248,11 @@ set(Launcher_MSA_CLIENT_ID "c36a9fb6-4f2a-41ff-90bd-ae7cc92031eb" CACHE STRING "
# This key was issued specifically for Prism 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_CURSEFORGE_API_KEY "$2a$10$wuAJuNZuted3NORVmpgUC.m8sI.pv1tOPKZyBgLFGjxFp/br0lZCC" CACHE STRING "API key for the CurseForge platform")
set(Launcher_COMPILER_NAME ${CMAKE_CXX_COMPILER_ID})
set(Launcher_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION})
set(Launcher_COMPILER_TARGET_SYSTEM ${CMAKE_SYSTEM_NAME})
set(Launcher_COMPILER_TARGET_SYSTEM_VERSION ${CMAKE_SYSTEM_VERSION})
set(Launcher_COMPILER_TARGET_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR})
#### Check the current Git commit and branch #### Check the current Git commit and branch
include(GetGitRevisionDescription) include(GetGitRevisionDescription)

View File

@ -33,6 +33,7 @@
* limitations under the License. * limitations under the License.
*/ */
#include <qstringliteral.h>
#include "BuildConfig.h" #include "BuildConfig.h"
#include <QObject> #include <QObject>
@ -59,8 +60,16 @@ Config::Config()
VERSION_MINOR = @Launcher_VERSION_MINOR@; VERSION_MINOR = @Launcher_VERSION_MINOR@;
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@"; BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
BUILD_ARTIFACT = "@Launcher_BUILD_ARTIFACT@";
BUILD_DATE = "@Launcher_BUILD_TIMESTAMP@"; BUILD_DATE = "@Launcher_BUILD_TIMESTAMP@";
UPDATER_BASE = "@Launcher_UPDATER_BASE@"; UPDATER_GITHUB_REPO = "@Launcher_UPDATER_GITHUB_REPO@";
COMPILER_NAME = "@Launcher_COMPILER_NAME@";
COMPILER_VERSION = "@Launcher_COMPILER_VERSION@";
COMPILER_TARGET_SYSTEM = "@Launcher_COMPILER_TARGET_SYSTEM@";
COMPILER_TARGET_SYSTEM_VERSION = "@Launcher_COMPILER_TARGET_SYSTEM_VERSION@";
COMPILER_TARGET_SYSTEM_PROCESSOR = "@Launcher_COMPILER_TARGET_PROCESSOR@";
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@"; MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@"; MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@";
@ -68,6 +77,8 @@ Config::Config()
if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty()) if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty())
{ {
UPDATER_ENABLED = true; UPDATER_ENABLED = true;
} else if(!UPDATER_GITHUB_REPO.isEmpty() && !BUILD_ARTIFACT.isEmpty()) {
UPDATER_ENABLED = true;
} }
GIT_COMMIT = "@Launcher_GIT_COMMIT@"; GIT_COMMIT = "@Launcher_GIT_COMMIT@";
@ -88,10 +99,7 @@ Config::Config()
if (GIT_REFSPEC.startsWith("refs/heads/")) if (GIT_REFSPEC.startsWith("refs/heads/"))
{ {
VERSION_CHANNEL = GIT_REFSPEC; VERSION_CHANNEL = GIT_REFSPEC;
VERSION_CHANNEL.remove("refs/heads/"); VERSION_CHANNEL.remove("refs/heads/");
if(!UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty()) {
UPDATER_ENABLED = true;
}
} }
else if (!GIT_COMMIT.isEmpty()) else if (!GIT_COMMIT.isEmpty())
{ {
@ -136,3 +144,16 @@ QString Config::printableVersionString() const
} }
return vstr; return vstr;
} }
QString Config::compilerID() const
{
if (COMPILER_VERSION.isEmpty())
return COMPILER_NAME;
return QStringLiteral("%1 - %2").arg(COMPILER_NAME).arg(COMPILER_VERSION);
}
QString Config::systemID() const
{
return QStringLiteral("%1 %2 %3").arg(COMPILER_TARGET_SYSTEM, COMPILER_TARGET_SYSTEM_VERSION, COMPILER_TARGET_SYSTEM_PROCESSOR);
}

View File

@ -71,11 +71,29 @@ class Config {
/// A short string identifying this build's platform or distribution. /// A short string identifying this build's platform or distribution.
QString BUILD_PLATFORM; QString BUILD_PLATFORM;
/// A short string identifying this build's valid artifacts int he updater. For example, "lin64" or "win32".
QString BUILD_ARTIFACT;
/// A string containing the build timestamp /// A string containing the build timestamp
QString BUILD_DATE; QString BUILD_DATE;
/// A string identifying the compiler use to build
QString COMPILER_NAME;
/// A string identifying the compiler version used to build
QString COMPILER_VERSION;
/// A string identifying the compiler target system os
QString COMPILER_TARGET_SYSTEM;
/// A String identifying the compiler target system version
QString COMPILER_TARGET_SYSTEM_VERSION;
/// A String identifying the compiler target processor
QString COMPILER_TARGET_SYSTEM_PROCESSOR;
/// URL for the updater's channel /// URL for the updater's channel
QString UPDATER_BASE; QString UPDATER_GITHUB_REPO;
/// The public key used to sign releases for the Sparkle updater appcast /// The public key used to sign releases for the Sparkle updater appcast
QString MAC_SPARKLE_PUB_KEY; QString MAC_SPARKLE_PUB_KEY;
@ -175,6 +193,18 @@ class Config {
* \return The version number in string format (major.minor.revision.build). * \return The version number in string format (major.minor.revision.build).
*/ */
QString printableVersionString() const; QString printableVersionString() const;
/**
* \brief Compiler ID String
* \return a string of the form "Name - Version" of just "Name" if the version is empty
*/
QString compilerID() const;
/**
* \brief System ID String
* \return a string of the form "OS Verison Processor"
*/
QString systemID() const;
}; };
extern const Config BuildConfig; extern const Config BuildConfig;

View File

@ -122,6 +122,7 @@
#include <FileSystem.h> #include <FileSystem.h>
#include <LocalPeer.h> #include <LocalPeer.h>
#include <stdlib.h>
#include <sys.h> #include <sys.h>
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
@ -130,9 +131,13 @@
#include "gamemode_client.h" #include "gamemode_client.h"
#endif #endif
#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED) #if defined(Q_OS_MAC)
#if defined(SPARKLE_ENABLED)
#include "updater/MacSparkleUpdater.h" #include "updater/MacSparkleUpdater.h"
#endif #endif
#else
#include "updater/PrismExternalUpdater.h"
#endif
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
#include "WindowsConsole.h" #include "WindowsConsole.h"
@ -164,6 +169,34 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt
} // namespace } // namespace
std::tuple<QDateTime, QString, QString, QString, QString> read_lock_File(const QString& path)
{
auto contents = QString(FS::read(path));
auto lines = contents.split('\n');
QDateTime timestamp;
QString from, to, target, data_path;
for (auto line : lines) {
auto index = line.indexOf("=");
if (index < 0)
continue;
auto left = line.left(index);
auto right = line.mid(index + 1);
if (left.toLower() == "timestamp") {
timestamp = QDateTime::fromString(right, Qt::ISODate);
} else if (left.toLower() == "from") {
from = right;
} else if (left.toLower() == "to") {
to = right;
} else if (left.toLower() == "target") {
target = right;
} else if (left.toLower() == "data_path") {
data_path = right;
}
}
return std::make_tuple(timestamp, from, to, target, data_path);
}
Application::Application(int& argc, char** argv) : QApplication(argc, argv) Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{ {
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
@ -296,6 +329,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
.arg(dataPath)); .arg(dataPath));
return; return;
} }
m_dataPath = dataPath;
/* /*
* Establish the mechanism for communication with an already running PrismLauncher that uses the same data path. * Establish the mechanism for communication with an already running PrismLauncher that uses the same data path.
@ -450,11 +484,16 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
} }
{ {
qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT; qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME) << ", (c) 2022-2023 "
<< qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
qDebug() << "Version : " << BuildConfig.printableVersionString(); qDebug() << "Version : " << BuildConfig.printableVersionString();
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM; qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC; qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
qDebug() << "Compiled for : " << BuildConfig.systemID();
qDebug() << "Compiled by : " << BuildConfig.compilerID();
qDebug() << "Build Artifact : " << BuildConfig.BUILD_ARTIFACT;
qDebug() << "Updates Enabled : " << (updaterEnabled() ? "Yes" : "No");
if (adjustedBy.size()) { if (adjustedBy.size()) {
qDebug() << "Work dir before adjustment : " << origcwdPath; qDebug() << "Work dir before adjustment : " << origcwdPath;
qDebug() << "Work dir after adjustment : " << QDir::currentPath(); qDebug() << "Work dir after adjustment : " << QDir::currentPath();
@ -741,15 +780,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
qDebug() << "<> Translations loaded."; qDebug() << "<> Translations loaded.";
} }
// initialize the updater
if (BuildConfig.UPDATER_ENABLED) {
qDebug() << "Initializing updater";
#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
m_updater.reset(new MacSparkleUpdater());
#endif
qDebug() << "<> Updater started.";
}
// Instance icons // Instance icons
{ {
auto setting = APPLICATION->settings()->getSetting("IconsDir"); auto setting = APPLICATION->settings()->getSetting("IconsDir");
@ -852,6 +882,107 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
detectLibraries(); detectLibraries();
// check update locks
{
auto update_log_path = FS::PathCombine(m_dataPath, "logs", "prism_launcher_update.log");
auto update_lock = QFileInfo(FS::PathCombine(m_dataPath, ".prism_launcher_update.lock"));
if (update_lock.exists()) {
auto [timestamp, from, to, target, data_path] = read_lock_File(update_lock.absoluteFilePath());
auto infoMsg = tr("This installation has a update lock file present at: %1\n"
"\n"
"Timestamp: %2\n"
"Updating from version %3 to %4\n"
"Target install path: %5\n"
"Data Path: %6"
"\n"
"This likely means that a update attempt failed. Please ensure your installation is in working order before "
"proceeding.\n"
"Check the Prism Launcher updater log at: \n"
"%7\n"
"for details on the last update attempt.\n"
"\n"
"To delete this lock and proceed select \"Ignore\" below.")
.arg(update_lock.absoluteFilePath())
.arg(timestamp.toString(Qt::ISODate), from, to, target, data_path)
.arg(update_log_path);
auto msgBox = QMessageBox(QMessageBox::Warning, tr("Update In Progress"), infoMsg, QMessageBox::Ignore | QMessageBox::Abort);
msgBox.setDefaultButton(QMessageBox::Abort);
msgBox.setModal(true);
msgBox.setDetailedText(FS::read(update_log_path));
msgBox.setMinimumWidth(460);
msgBox.adjustSize();
auto res = msgBox.exec();
switch (res) {
case QMessageBox::Ignore: {
FS::deletePath(update_lock.absoluteFilePath());
break;
}
case QMessageBox::Abort:
[[fallthrough]];
default: {
qDebug() << "Exiting because update lockfile is present";
QMetaObject::invokeMethod(
this, []() { exit(1); }, Qt::QueuedConnection);
return;
}
}
}
auto update_fail_marker = QFileInfo(FS::PathCombine(m_dataPath, ".prism_launcher_update.fail"));
if (update_fail_marker.exists()) {
auto infoMsg = tr("An update attempt failed\n"
"\n"
"Please ensure your installation is in working order before "
"proceeding.\n"
"Check the Prism Launcher updater log at: \n"
"%1\n"
"for details on the last update attempt.")
.arg(update_log_path);
auto msgBox = QMessageBox(QMessageBox::Warning, tr("Update Failed"), infoMsg, QMessageBox::Ignore | QMessageBox::Abort);
msgBox.setDefaultButton(QMessageBox::Abort);
msgBox.setModal(true);
msgBox.setDetailedText(FS::read(update_log_path));
msgBox.setMinimumWidth(460);
msgBox.adjustSize();
auto res = msgBox.exec();
switch (res) {
case QMessageBox::Ignore: {
FS::deletePath(update_fail_marker.absoluteFilePath());
break;
}
case QMessageBox::Abort:
[[fallthrough]];
default: {
qDebug() << "Exiting because update lockfile is present";
QMetaObject::invokeMethod(
this, []() { exit(1); }, Qt::QueuedConnection);
return;
}
}
}
auto update_success_marker = QFileInfo(FS::PathCombine(m_dataPath, ".prism_launcher_update.success"));
if (update_success_marker.exists()) {
auto infoMsg = tr("Update succeeded\n"
"\n"
"You are now running %1 .\n"
"Check the Prism Launcher updater log at: \n"
"%1\n"
"for details.")
.arg(BuildConfig.printableVersionString())
.arg(update_log_path);
auto msgBox = new QMessageBox(QMessageBox::Information, tr("Update Succeeded"), infoMsg, QMessageBox::Ok);
msgBox->setDefaultButton(QMessageBox::Ok);
msgBox->setDetailedText(FS::read(update_log_path));
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setMinimumWidth(460);
msgBox->adjustSize();
msgBox->open();
FS::deletePath(update_success_marker.absoluteFilePath());
}
}
if (createSetupWizard()) { if (createSetupWizard()) {
return; return;
} }
@ -920,6 +1051,26 @@ bool Application::createSetupWizard()
return false; return false;
} }
bool Application::updaterEnabled()
{
#if defined(Q_OS_MAC)
return BuildConfig.UPDATER_ENABLED;
#else
return BuildConfig.UPDATER_ENABLED && QFileInfo(FS::PathCombine(m_rootPath, updaterBinaryName())).isFile();
#endif
}
QString Application::updaterBinaryName()
{
auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME);
#if defined Q_OS_WIN32
exe_name.append(".exe");
#else
exe_name.prepend("bin/");
#endif
return exe_name;
}
bool Application::event(QEvent* event) bool Application::event(QEvent* event)
{ {
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
@ -988,6 +1139,20 @@ void Application::performMainStartupAction()
showMainWindow(false); showMainWindow(false);
qDebug() << "<> Main window shown."; qDebug() << "<> Main window shown.";
} }
// initialize the updater
if (updaterEnabled()) {
qDebug() << "Initializing updater";
#ifdef Q_OS_MAC
#if defined(SPARKLE_ENABLED)
m_updater.reset(new MacSparkleUpdater());
#endif
#else
m_updater.reset(new PrismExternalUpdater(m_mainWindow, m_rootPath, m_dataPath));
#endif
qDebug() << "<> Updater started.";
}
if (!m_urlsToImport.isEmpty()) { if (!m_urlsToImport.isEmpty()) {
qDebug() << "<> Importing from url:" << m_urlsToImport; qDebug() << "<> Importing from url:" << m_urlsToImport;
m_mainWindow->processURLs(m_urlsToImport); m_mainWindow->processURLs(m_urlsToImport);

View File

@ -159,6 +159,9 @@ class Application : public QApplication {
/// this is the root of the 'installation'. Used for automatic updates /// this is the root of the 'installation'. Used for automatic updates
const QString& root() { return m_rootPath; } const QString& root() { return m_rootPath; }
/// the data path the application is using
const QString& dataRoot() { return m_dataPath; }
bool isPortable() { return m_portable; } bool isPortable() { return m_portable; }
const Capabilities capabilities() { return m_capabilities; } const Capabilities capabilities() { return m_capabilities; }
@ -179,6 +182,9 @@ class Application : public QApplication {
int suitableMaxMem(); int suitableMaxMem();
bool updaterEnabled();
QString updaterBinaryName();
QUrl normalizeImportUrl(QString const& url); QUrl normalizeImportUrl(QString const& url);
signals: signals:
@ -244,6 +250,7 @@ class Application : public QApplication {
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers; QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;
QString m_rootPath; QString m_rootPath;
QString m_dataPath;
Status m_status = Application::StartingUp; Status m_status = Application::StartingUp;
Capabilities m_capabilities; Capabilities m_capabilities;
bool m_portable = false; bool m_portable = false;

View File

@ -181,6 +181,11 @@ set(MAC_UPDATE_SOURCES
updater/MacSparkleUpdater.mm updater/MacSparkleUpdater.mm
) )
set(PRISM_UPDATE_SOURCES
updater/PrismExternalUpdater.h
updater/PrismExternalUpdater.cpp
)
# Backend for the news bar... there's usually no news. # Backend for the news bar... there's usually no news.
set(NEWS_SOURCES set(NEWS_SOURCES
# News System # News System
@ -579,6 +584,63 @@ set(LINKEXE_SOURCES
DesktopServices.cpp DesktopServices.cpp
) )
set(PRISMUPDATER_SOURCES
updater/prismupdater/PrismUpdater.h
updater/prismupdater/PrismUpdater.cpp
updater/prismupdater/UpdaterDialogs.h
updater/prismupdater/UpdaterDialogs.cpp
updater/prismupdater/GitHubRelease.h
updater/prismupdater/GitHubRelease.cpp
Json.h
Json.cpp
FileSystem.h
FileSystem.cpp
StringUtils.h
StringUtils.cpp
DesktopServices.h
DesktopServices.cpp
Version.h
Version.cpp
Markdown.h
Markdown.cpp
# Zip
MMCZip.h
MMCZip.cpp
# Time
MMCTime.h
MMCTime.cpp
net/ByteArraySink.h
net/ChecksumValidator.h
net/Download.cpp
net/Download.h
net/FileSink.cpp
net/FileSink.h
net/HttpMetaCache.cpp
net/HttpMetaCache.h
net/Logging.h
net/Logging.cpp
net/NetAction.h
net/NetRequest.cpp
net/NetRequest.h
net/NetJob.cpp
net/NetJob.h
net/NetUtils.h
net/Sink.h
net/Validator.h
net/HeaderProxy.h
net/RawHeaderProxy.h
ui/dialogs/ProgressDialog.cpp
ui/dialogs/ProgressDialog.h
ui/widgets/SubTaskProgressBar.h
ui/widgets/SubTaskProgressBar.cpp
)
######## Logging categories ######## ######## Logging categories ########
ecm_qt_declare_logging_category(CORE_SOURCES ecm_qt_declare_logging_category(CORE_SOURCES
@ -675,6 +737,8 @@ set(LOGIC_SOURCES
if(APPLE AND Launcher_ENABLE_UPDATER) if(APPLE AND Launcher_ENABLE_UPDATER)
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES}) set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES})
else()
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${PRISM_UPDATE_SOURCES})
endif() endif()
SET(LAUNCHER_SOURCES SET(LAUNCHER_SOURCES
@ -1033,6 +1097,15 @@ SET(LAUNCHER_SOURCES
ui/instanceview/VisualGroup.h ui/instanceview/VisualGroup.h
) )
if (NOT Apple)
set(LAUNCHER_SOURCES
${LAUNCHER_SOURCES}
ui/dialogs/UpdateAvailableDialog.h
ui/dialogs/UpdateAvailableDialog.cpp
)
endif()
if(WIN32) if(WIN32)
set(LAUNCHER_SOURCES set(LAUNCHER_SOURCES
WindowsConsole.cpp WindowsConsole.cpp
@ -1103,6 +1176,14 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/ChooseProviderDialog.ui ui/dialogs/ChooseProviderDialog.ui
) )
qt_wrap_ui(PRISM_UPDATE_UI
ui/dialogs/UpdateAvailableDialog.ui
)
if (NOT Apple)
set (LAUNCHER_UI ${LAUNCHER_UI} ${PRISM_UPDATE_UI})
endif()
qt_add_resources(LAUNCHER_RESOURCES qt_add_resources(LAUNCHER_RESOURCES
resources/backgrounds/backgrounds.qrc resources/backgrounds/backgrounds.qrc
resources/multimc/multimc.qrc resources/multimc/multimc.qrc
@ -1119,6 +1200,12 @@ qt_add_resources(LAUNCHER_RESOURCES
../${Launcher_Branding_LogoQRC} ../${Launcher_Branding_LogoQRC}
) )
qt_wrap_ui(PRISMUPDATER_UI
updater/prismupdater/SelectReleaseDialog.ui
ui/widgets/SubTaskProgressBar.ui
ui/dialogs/ProgressDialog.ui
)
######## Windows resource files ######## ######## Windows resource files ########
if(WIN32) if(WIN32)
set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC}) set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
@ -1137,6 +1224,7 @@ set_project_warnings(Launcher_logic
"${Launcher_GCC_WARNINGS}") "${Launcher_GCC_WARNINGS}")
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION) target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
target_link_libraries(Launcher_logic target_link_libraries(Launcher_logic
systeminfo systeminfo
Launcher_murmur2 Launcher_murmur2
@ -1218,7 +1306,45 @@ install(TARGETS ${Launcher_Name}
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
) )
if(WIN32) if(NOT APPLE OR (DEFINED Launcher_BUILD_UPDATER AND Launcher_BUILD_UPDATER))
# Updater
add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI})
target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(prism_updater_logic
QuaZip::QuaZip
${ZLIB_LIBRARIES}
systeminfo
BuildConfig
ghcFilesystem::ghc_filesystem
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Network
${Launcher_QT_LIBS}
cmark::cmark
Katabasis
)
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
target_sources("${Launcher_Name}_updater" PRIVATE updater/prismupdater/updater.exe.manifest)
target_link_libraries("${Launcher_Name}_updater" prism_updater_logic)
if(DEFINED Launcher_APP_BINARY_NAME)
set_target_properties("${Launcher_Name}_updater" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_updater")
endif()
if(DEFINED Launcher_BINARY_RPATH)
SET_TARGET_PROPERTIES("${Launcher_Name}_updater" PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}")
endif()
install(TARGETS "${Launcher_Name}_updater"
BUNDLE DESTINATION "." COMPONENT Runtime
LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime
RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
)
endif()
if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER))
# File link
add_library(filelink_logic STATIC ${LINKEXE_SOURCES}) add_library(filelink_logic STATIC ${LINKEXE_SOURCES})
set_project_warnings(filelink_logic set_project_warnings(filelink_logic
"${Launcher_MSVC_WARNINGS}" "${Launcher_MSVC_WARNINGS}"
@ -1237,7 +1363,7 @@ if(WIN32)
${Launcher_QT_LIBS} ${Launcher_QT_LIBS}
) )
add_executable("${Launcher_Name}_filelink" WIN32 filelink/main.cpp) add_executable("${Launcher_Name}_filelink" WIN32 filelink/filelink_main.cpp)
target_sources("${Launcher_Name}_filelink" PRIVATE filelink/filelink.exe.manifest) target_sources("${Launcher_Name}_filelink" PRIVATE filelink/filelink.exe.manifest)

View File

@ -194,6 +194,40 @@ void write(const QString& filename, const QByteArray& data)
} }
} }
void appendSafe(const QString& filename, const QByteArray& data)
{
ensureExists(QFileInfo(filename).dir());
QByteArray buffer;
try {
buffer = read(filename);
} catch (FileSystemException&) {
buffer = QByteArray();
}
buffer.append(data);
QSaveFile file(filename);
if (!file.open(QSaveFile::WriteOnly)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
}
if (buffer.size() != file.write(buffer)) {
throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
}
if (!file.commit()) {
throw FileSystemException("Error while committing data to " + filename + ": " + file.errorString());
}
}
void append(const QString& filename, const QByteArray& data)
{
ensureExists(QFileInfo(filename).dir());
QFile file(filename);
if (!file.open(QFile::Append)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
}
if (data.size() != file.write(data)) {
throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
}
}
QByteArray read(const QString& filename) QByteArray read(const QString& filename)
{ {
QFile file(filename); QFile file(filename);
@ -287,6 +321,9 @@ bool copy::operator()(const QString& offset, bool dryRun)
if (!m_followSymlinks) if (!m_followSymlinks)
opt |= copy_opts::copy_symlinks; opt |= copy_opts::copy_symlinks;
if (m_overwrite)
opt |= copy_opts::overwrite_existing;
// Function that'll do the actual copying // Function that'll do the actual copying
auto copy_file = [&](QString src_path, QString relative_dst_path) { auto copy_file = [&](QString src_path, QString relative_dst_path) {
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))

View File

@ -61,6 +61,16 @@ class FileSystemException : public ::Exception {
*/ */
void write(const QString& filename, const QByteArray& data); void write(const QString& filename, const QByteArray& data);
/**
* append data to a file safely
*/
void appendSafe(const QString& filename, const QByteArray& data);
/**
* append data to a file
*/
void append(const QString& filename, const QByteArray& data);
/** /**
* read data from a file safely\ * read data from a file safely\
*/ */
@ -109,6 +119,11 @@ class copy : public QObject {
m_whitelist = whitelist; m_whitelist = whitelist;
return *this; return *this;
} }
copy& overwrite(const bool overwrite)
{
m_overwrite = overwrite;
return *this;
}
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
@ -128,6 +143,7 @@ class copy : public QObject {
bool m_followSymlinks = true; bool m_followSymlinks = true;
const IPathMatcher* m_matcher = nullptr; const IPathMatcher* m_matcher = nullptr;
bool m_whitelist = false; bool m_whitelist = false;
bool m_overwrite = false;
QDir m_src; QDir m_src;
QDir m_dst; QDir m_dst;
qsizetype m_copied; qsizetype m_copied;

View File

@ -42,7 +42,11 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include <QUrl>
#if defined(LAUNCHER_APPLICATION)
#include <QtConcurrentRun> #include <QtConcurrentRun>
#endif
namespace MMCZip { namespace MMCZip {
// ours // ours
@ -132,6 +136,7 @@ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files,
return result; return result;
} }
#if defined(LAUNCHER_APPLICATION)
// ours // ours
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods) bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods)
{ {
@ -217,6 +222,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
} }
return true; return true;
} }
#endif
// ours // ours
QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root) QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root)
@ -422,6 +428,7 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q
return true; return true;
} }
#if defined(LAUNCHER_APPLICATION)
void ExportToZipTask::executeTask() void ExportToZipTask::executeTask()
{ {
setStatus("Adding files..."); setStatus("Adding files...");
@ -500,5 +507,6 @@ bool ExportToZipTask::abort()
} }
return false; return false;
} }
#endif
} // namespace MMCZip } // namespace MMCZip

View File

@ -48,7 +48,10 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <optional> #include <optional>
#if defined(LAUNCHER_APPLICATION)
#include "minecraft/mod/Mod.h" #include "minecraft/mod/Mod.h"
#endif
#include "tasks/Task.h" #include "tasks/Task.h"
namespace MMCZip { namespace MMCZip {
@ -79,11 +82,12 @@ bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool follow
*/ */
bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false); bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false);
#if defined(LAUNCHER_APPLICATION)
/** /**
* take a source jar, add mods to it, resulting in target jar * take a source jar, add mods to it, resulting in target jar
*/ */
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods); bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods);
#endif
/** /**
* Find a single file in archive by file name (not path) * Find a single file in archive by file name (not path)
* *
@ -147,6 +151,7 @@ bool extractFile(QString fileCompressed, QString file, QString dir);
*/ */
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter); bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter);
#if defined(LAUNCHER_APPLICATION)
class ExportToZipTask : public Task { class ExportToZipTask : public Task {
public: public:
ExportToZipTask(QString outputPath, QDir dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false) ExportToZipTask(QString outputPath, QDir dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
@ -189,4 +194,5 @@ class ExportToZipTask : public Task {
QFuture<ZipResult> m_build_zip_future; QFuture<ZipResult> m_build_zip_future;
QFutureWatcher<ZipResult> m_build_zip_watcher; QFutureWatcher<ZipResult> m_build_zip_watcher;
}; };
#endif
} // namespace MMCZip } // namespace MMCZip

View File

@ -35,6 +35,7 @@
*/ */
#include "StringUtils.h" #include "StringUtils.h"
#include <qpair.h>
#include <QRegularExpression> #include <QRegularExpression>
#include <QUuid> #include <QUuid>
@ -149,7 +150,7 @@ QString StringUtils::truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_
} }
if ((url_compact.length() >= max_len) && hard_limit) { if ((url_compact.length() >= max_len) && hard_limit) {
// still too long, truncate normaly // still too long, truncate normally
url_compact = QString(str_url); url_compact = QString(str_url);
auto to_remove = url_compact.length() - max_len + 3; auto to_remove = url_compact.length() - max_len + 3;
url_compact.remove(url_compact.length() - to_remove - 1, to_remove); url_compact.remove(url_compact.length() - to_remove - 1, to_remove);
@ -182,3 +183,32 @@ QString StringUtils::getRandomAlphaNumeric()
{ {
return QUuid::createUuid().toString(QUuid::Id128); return QUuid::createUuid().toString(QUuid::Id128);
} }
QPair<QString, QString> StringUtils::splitFirst(const QString& s, const QString& sep, Qt::CaseSensitivity cs)
{
QString left, right;
auto index = s.indexOf(sep, 0, cs);
left = s.mid(0, index);
right = s.mid(index + sep.length());
return qMakePair(left, right);
}
QPair<QString, QString> StringUtils::splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs)
{
QString left, right;
auto index = s.indexOf(sep, 0, cs);
left = s.mid(0, index);
right = s.mid(left.length() + 1);
return qMakePair(left, right);
}
QPair<QString, QString> StringUtils::splitFirst(const QString& s, const QRegularExpression& re)
{
QString left, right;
QRegularExpressionMatch match;
auto index = s.indexOf(re, 0, &match);
left = s.mid(0, index);
auto end = match.hasMatch() ? left.length() + match.capturedLength() : left.length() + 1;
right = s.mid(end);
return qMakePair(left, right);
}

View File

@ -36,8 +36,10 @@
#pragma once #pragma once
#include <QPair>
#include <QString> #include <QString>
#include <QUrl> #include <QUrl>
#include <utility>
namespace StringUtils { namespace StringUtils {
@ -70,12 +72,17 @@ int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs)
/** /**
* @brief Truncate a url while keeping its readability py placing the `...` in the middle of the path * @brief Truncate a url while keeping its readability py placing the `...` in the middle of the path
* @param url Url to truncate * @param url Url to truncate
* @param max_len max lenght of url in charaters * @param max_len max length of url in characters
* @param hard_limit if truncating the path can't get the url short enough, truncate it normaly. * @param hard_limit if truncating the path can't get the url short enough, truncate it normally.
*/ */
QString truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_limit = false); QString truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_limit = false);
QString humanReadableFileSize(double bytes, bool use_si = false, int decimal_points = 1); QString humanReadableFileSize(double bytes, bool use_si = false, int decimal_points = 1);
QString getRandomAlphaNumeric(); QString getRandomAlphaNumeric();
QPair<QString, QString> splitFirst(const QString& s, const QString& sep, Qt::CaseSensitivity cs = Qt::CaseSensitive);
QPair<QString, QString> splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs = Qt::CaseSensitive);
QPair<QString, QString> splitFirst(const QString& s, const QRegularExpression& re);
} // namespace StringUtils } // namespace StringUtils

View File

@ -56,6 +56,7 @@ class Version {
bool operator!=(const Version& other) const; bool operator!=(const Version& other) const;
QString toString() const { return m_string; } QString toString() const { return m_string; }
bool isEmpty() const { return m_string.isEmpty(); }
friend QDebug operator<<(QDebug debug, const Version& v); friend QDebug operator<<(QDebug debug, const Version& v);

View File

@ -93,6 +93,7 @@ FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv),
joinServer(serverToJoin); joinServer(serverToJoin);
} else { } else {
qDebug() << "no server to join"; qDebug() << "no server to join";
m_status = Failed;
exit(); exit();
} }
} }
@ -108,6 +109,7 @@ void FileLinkApp::joinServer(QString server)
connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs); connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) { connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) {
m_status = Failed;
switch (socketError) { switch (socketError) {
case QLocalSocket::ServerNotFoundError: case QLocalSocket::ServerNotFoundError:
qDebug() qDebug()
@ -132,6 +134,7 @@ void FileLinkApp::joinServer(QString server)
connect(&socket, &QLocalSocket::disconnected, this, [&]() { connect(&socket, &QLocalSocket::disconnected, this, [&]() {
qDebug() << "disconnected from server, should exit"; qDebug() << "disconnected from server, should exit";
m_status = Succeeded;
exit(); exit();
}); });

View File

@ -41,8 +41,10 @@ class FileLinkApp : public QCoreApplication {
// friends for the purpose of limiting access to deprecated stuff // friends for the purpose of limiting access to deprecated stuff
Q_OBJECT Q_OBJECT
public: public:
enum Status { Starting, Failed, Succeeded, Initialized };
FileLinkApp(int& argc, char** argv); FileLinkApp(int& argc, char** argv);
virtual ~FileLinkApp(); virtual ~FileLinkApp();
Status status() const { return m_status; }
private: private:
void joinServer(QString server); void joinServer(QString server);
@ -50,6 +52,8 @@ class FileLinkApp : public QCoreApplication {
void runLink(); void runLink();
void sendResults(); void sendResults();
Status m_status = Status::Starting;
bool m_useHardLinks = false; bool m_useHardLinks = false;
QDateTime m_startTime; QDateTime m_startTime;

View File

@ -26,5 +26,16 @@ int main(int argc, char* argv[])
{ {
FileLinkApp ldh(argc, argv); FileLinkApp ldh(argc, argv);
return ldh.exec(); switch (ldh.status()) {
case FileLinkApp::Starting:
case FileLinkApp::Initialized: {
return ldh.exec();
}
case FileLinkApp::Failed:
return 1;
case FileLinkApp::Succeeded:
return 0;
default:
return -1;
}
} }

View File

@ -568,7 +568,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
m_mod_id_resolver.reset(); m_mod_id_resolver.reset();
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { connect(m_files_job.get(), &NetJob::succeeded, this, [&]() {
m_files_job.reset(); m_files_job.reset();
validateZIPResouces(); validateZIPResources();
}); });
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
m_files_job.reset(); m_files_job.reset();
@ -617,7 +617,7 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
setAbortable(true); setAbortable(true);
} }
void FlameCreationTask::validateZIPResouces() void FlameCreationTask::validateZIPResources()
{ {
qDebug() << "Validating whether resources stored as .zip are in the right place"; qDebug() << "Validating whether resources stored as .zip are in the right place";
for (auto [fileName, targetFolder] : m_ZIP_resources) { for (auto [fileName, targetFolder] : m_ZIP_resources) {
@ -670,8 +670,8 @@ void FlameCreationTask::validateZIPResouces()
validatePath(fileName, targetFolder, "datapacks"); validatePath(fileName, targetFolder, "datapacks");
break; break;
case PackedResourceType::ShaderPack: case PackedResourceType::ShaderPack:
// in theroy flame API can't do this but who knows, that *may* change ? // in theory flame API can't do this but who knows, that *may* change ?
// better to handle it if it *does* occure in the future // better to handle it if it *does* occur in the future
validatePath(fileName, targetFolder, "shaderpacks"); validatePath(fileName, targetFolder, "shaderpacks");
break; break;
case PackedResourceType::WorldSave: case PackedResourceType::WorldSave:

View File

@ -74,7 +74,7 @@ class FlameCreationTask final : public InstanceCreationTask {
void idResolverSucceeded(QEventLoop&); void idResolverSucceeded(QEventLoop&);
void setupDownloadJob(QEventLoop&); void setupDownloadJob(QEventLoop&);
void copyBlockedMods(QList<BlockedMod> const& blocked_mods); void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
void validateZIPResouces(); void validateZIPResources();
QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion); QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion);
private: private:

View File

@ -89,4 +89,4 @@ QNetworkReply* Download::getReply(QNetworkRequest& request)
{ {
return m_network->get(request); return m_network->get(request);
} }
} // namespace Net } // namespace Net

View File

@ -36,11 +36,16 @@
*/ */
#include "NetJob.h" #include "NetJob.h"
#if defined(LAUNCHER_APPLICATION)
#include "Application.h" #include "Application.h"
#endif
NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network) NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network) : ConcurrentTask(nullptr, job_name), m_network(network)
: ConcurrentTask(nullptr, job_name, APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()), m_network(network) {
{} #if defined(LAUNCHER_APPLICATION)
setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
#endif
}
auto NetJob::addNetAction(NetAction::Ptr action) -> bool auto NetJob::addNetAction(NetAction::Ptr action) -> bool
{ {

View File

@ -46,6 +46,7 @@
#if defined(LAUNCHER_APPLICATION) #if defined(LAUNCHER_APPLICATION)
#include "Application.h" #include "Application.h"
#endif #endif
#include "BuildConfig.h"
#include "net/NetAction.h" #include "net/NetAction.h"

View File

@ -219,7 +219,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
ui->actionDISCORD->setVisible(!BuildConfig.DISCORD_URL.isEmpty()); ui->actionDISCORD->setVisible(!BuildConfig.DISCORD_URL.isEmpty());
ui->actionREDDIT->setVisible(!BuildConfig.SUBREDDIT_URL.isEmpty()); ui->actionREDDIT->setVisible(!BuildConfig.SUBREDDIT_URL.isEmpty());
ui->actionCheckUpdate->setVisible(BuildConfig.UPDATER_ENABLED); ui->actionCheckUpdate->setVisible(APPLICATION->updaterEnabled());
#ifndef Q_OS_MAC #ifndef Q_OS_MAC
ui->actionAddToPATH->setVisible(false); ui->actionAddToPATH->setVisible(false);
@ -377,7 +377,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
updateNewsLabel(); updateNewsLabel();
} }
if (BuildConfig.UPDATER_ENABLED) { if (APPLICATION->updaterEnabled()) {
bool updatesAllowed = APPLICATION->updatesAreAllowed(); bool updatesAllowed = APPLICATION->updatesAreAllowed();
updatesAllowedChanged(updatesAllowed); updatesAllowedChanged(updatesAllowed);
@ -677,7 +677,7 @@ void MainWindow::repopulateAccountsMenu()
void MainWindow::updatesAllowedChanged(bool allowed) void MainWindow::updatesAllowedChanged(bool allowed)
{ {
if (!BuildConfig.UPDATER_ENABLED) { if (!APPLICATION->updaterEnabled()) {
return; return;
} }
ui->actionCheckUpdate->setEnabled(allowed); ui->actionCheckUpdate->setEnabled(allowed);
@ -1218,7 +1218,7 @@ void MainWindow::refreshInstances()
void MainWindow::checkForUpdates() void MainWindow::checkForUpdates()
{ {
if (BuildConfig.UPDATER_ENABLED) { if (APPLICATION->updaterEnabled()) {
APPLICATION->triggerUpdateCheck(); APPLICATION->triggerUpdateCheck();
} else { } else {
qWarning() << "Updater not set up. Cannot check for updates."; qWarning() << "Updater not set up. Cannot check for updates.";

View File

@ -48,6 +48,9 @@
<property name="text"> <property name="text">
<string>Global Task Status...</string> <string>Global Task Status...</string>
</property> </property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -109,8 +112,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>464</width> <width>460</width>
<height>96</height> <height>108</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="taskProgressLayout"> <layout class="QVBoxLayout" name="taskProgressLayout">

View File

@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// 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 <https://www.gnu.org/licenses/>.
*
*/
#include "UpdateAvailableDialog.h"
#include <QPushButton>
#include "Application.h"
#include "BuildConfig.h"
#include "Markdown.h"
#include "ui_UpdateAvailableDialog.h"
UpdateAvailableDialog::UpdateAvailableDialog(const QString& currentVersion,
const QString& availableVersion,
const QString& releaseNotes,
QWidget* parent)
: QDialog(parent), ui(new Ui::UpdateAvailableDialog)
{
ui->setupUi(this);
QString launcherName = BuildConfig.LAUNCHER_DISPLAYNAME;
ui->headerLabel->setText(tr("A new version of %1 is available!").arg(launcherName));
ui->versionAvailableLabel->setText(
tr("Version %1 is now available - you have %2 . Would you like to download it now?").arg(availableVersion).arg(currentVersion));
ui->icon->setPixmap(APPLICATION->getThemedIcon("checkupdate").pixmap(64));
auto releaseNotesHtml = markdownToHTML(releaseNotes);
ui->releaseNotes->setHtml(releaseNotesHtml);
ui->releaseNotes->setOpenExternalLinks(true);
connect(ui->skipButton, &QPushButton::clicked, this, [this]() {
setResult(ResultCode::Skip);
done(ResultCode::Skip);
});
connect(ui->delayButton, &QPushButton::clicked, this, [this]() {
setResult(ResultCode::DontInstall);
done(ResultCode::DontInstall);
});
connect(ui->installButton, &QPushButton::clicked, this, [this]() {
setResult(ResultCode::Install);
done(ResultCode::Install);
});
}

View File

@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <QDialog>
namespace Ui {
class UpdateAvailableDialog;
}
class UpdateAvailableDialog : public QDialog {
Q_OBJECT
public:
enum ResultCode {
Install = 10,
DontInstall = 11,
Skip = 12,
};
explicit UpdateAvailableDialog(const QString& currentVersion,
const QString& availableVersion,
const QString& releaseNotes,
QWidget* parent = 0);
~UpdateAvailableDialog() = default;
private:
Ui::UpdateAvailableDialog* ui;
};

View File

@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UpdateAvailableDialog</class>
<widget class="QDialog" name="UpdateAvailableDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>636</width>
<height>352</height>
</rect>
</property>
<property name="windowTitle">
<string>Update Available</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
<item>
<layout class="QVBoxLayout" name="leftsideLayout">
<item>
<widget class="QLabel" name="icon">
<property name="minimumSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="mainLayout">
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="QLabel" name="headerLabel">
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>A new version is available!</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="versionAvailableLabel">
<property name="text">
<string>Version %1 is now available - you have %2 . Would you like to download it now?</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="releaseNotesLabel">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Release Notes:</string>
</property>
</widget>
</item>
<item>
<widget class="QTextBrowser" name="releaseNotes"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="skipButton">
<property name="text">
<string>Skip This Version</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="delayButton">
<property name="text">
<string>Remind Me Later</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="installButton">
<property name="text">
<string>Install Update</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,354 @@
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// 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 <https://www.gnu.org/licenses/>.
*
*/
#include "PrismExternalUpdater.h"
#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QMessageBox>
#include <QProcess>
#include <QProgressDialog>
#include <QSettings>
#include <QTimer>
#include <memory>
#include "StringUtils.h"
#include "BuildConfig.h"
#include "ui/dialogs/UpdateAvailableDialog.h"
class PrismExternalUpdater::Private {
public:
QDir appDir;
QDir dataDir;
QTimer updateTimer;
bool allowBeta;
bool autoCheck;
double updateInterval;
QDateTime lastCheck;
std::unique_ptr<QSettings> settings;
QWidget* parent;
};
PrismExternalUpdater::PrismExternalUpdater(QWidget* parent, const QString& appDir, const QString& dataDir)
{
priv = new PrismExternalUpdater::Private();
priv->appDir = QDir(appDir);
priv->dataDir = QDir(dataDir);
auto settings_file = priv->dataDir.absoluteFilePath("prismlauncher_update.cfg");
priv->settings = std::make_unique<QSettings>(settings_file, QSettings::Format::IniFormat);
priv->allowBeta = priv->settings->value("allow_beta", false).toBool();
priv->autoCheck = priv->settings->value("auto_check", false).toBool();
bool interval_ok;
// default once per day
priv->updateInterval = priv->settings->value("update_interval", 86400).toInt(&interval_ok);
if (!interval_ok)
priv->updateInterval = 86400;
auto last_check = priv->settings->value("last_check");
if (!last_check.isNull() && last_check.isValid()) {
priv->lastCheck = QDateTime::fromString(last_check.toString(), Qt::ISODate);
}
priv->parent = parent;
connectTimer();
resetAutoCheckTimer();
}
PrismExternalUpdater::~PrismExternalUpdater()
{
if (priv->updateTimer.isActive())
priv->updateTimer.stop();
disconnectTimer();
priv->settings->sync();
delete priv;
}
void PrismExternalUpdater::checkForUpdates()
{
QProgressDialog progress(tr("Checking for updates..."), "", 0, 0, priv->parent);
progress.setCancelButton(nullptr);
progress.adjustSize();
progress.show();
QCoreApplication::processEvents();
QProcess proc;
auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME);
#if defined Q_OS_WIN32
exe_name.append(".exe");
auto env = QProcessEnvironment::systemEnvironment();
env.insert("__COMPAT_LAYER", "RUNASINVOKER");
proc.setProcessEnvironment(env);
#else
exe_name = QString("bin/%1").arg(exe_name);
#endif
QStringList args = { "--check-only", "--dir", priv->dataDir.absolutePath(), "--debug" };
if (priv->allowBeta)
args.append("--pre-release");
proc.start(priv->appDir.absoluteFilePath(exe_name), args);
auto result_start = proc.waitForStarted(5000);
if (!result_start) {
auto err = proc.error();
qDebug() << "Failed to start updater after 5 seconds."
<< "reason:" << err << proc.errorString();
auto msgBox =
QMessageBox(QMessageBox::Information, tr("Update Check Failed"),
tr("Failed to start after 5 seconds\nReason: %1.").arg(proc.errorString()), QMessageBox::Ok, priv->parent);
msgBox.setMinimumWidth(460);
msgBox.adjustSize();
msgBox.exec();
priv->lastCheck = QDateTime::currentDateTime();
priv->settings->setValue("last_check", priv->lastCheck.toString(Qt::ISODate));
priv->settings->sync();
resetAutoCheckTimer();
return;
}
QCoreApplication::processEvents();
auto result_finished = proc.waitForFinished(60000);
if (!result_finished) {
proc.kill();
auto err = proc.error();
auto output = proc.readAll();
qDebug() << "Updater failed to close after 60 seconds."
<< "reason:" << err << proc.errorString();
auto msgBox =
QMessageBox(QMessageBox::Information, tr("Update Check Failed"),
tr("Updater failed to close 60 seconds\nReason: %1.").arg(proc.errorString()), QMessageBox::Ok, priv->parent);
msgBox.setDetailedText(output);
msgBox.setMinimumWidth(460);
msgBox.adjustSize();
msgBox.exec();
priv->lastCheck = QDateTime::currentDateTime();
priv->settings->setValue("last_check", priv->lastCheck.toString(Qt::ISODate));
priv->settings->sync();
resetAutoCheckTimer();
return;
}
auto exit_code = proc.exitCode();
auto std_output = proc.readAllStandardOutput();
auto std_error = proc.readAllStandardError();
progress.hide();
QCoreApplication::processEvents();
switch (exit_code) {
case 0:
// no update available
{
qDebug() << "No update available";
auto msgBox = QMessageBox(QMessageBox::Information, tr("No Update Available"), tr("You are running the latest version."),
QMessageBox::Ok, priv->parent);
msgBox.setMinimumWidth(460);
msgBox.adjustSize();
msgBox.exec();
}
break;
case 1:
// there was an error
{
qDebug() << "Updater subprocess error" << qPrintable(std_error);
auto msgBox = QMessageBox(QMessageBox::Warning, tr("Update Check Error"),
tr("There was an error running the update check."), QMessageBox::Ok, priv->parent);
msgBox.setDetailedText(QString(std_error));
msgBox.setMinimumWidth(460);
msgBox.adjustSize();
msgBox.exec();
}
break;
case 100:
// update available
{
auto [first_line, remainder1] = StringUtils::splitFirst(std_output, '\n');
auto [second_line, remainder2] = StringUtils::splitFirst(remainder1, '\n');
auto [third_line, release_notes] = StringUtils::splitFirst(remainder2, '\n');
auto version_name = StringUtils::splitFirst(first_line, ": ").second.trimmed();
auto version_tag = StringUtils::splitFirst(second_line, ": ").second.trimmed();
auto release_timestamp = QDateTime::fromString(StringUtils::splitFirst(third_line, ": ").second.trimmed(), Qt::ISODate);
qDebug() << "Update available:" << version_name << version_tag << release_timestamp;
qDebug() << "Update release notes:" << release_notes;
offerUpdate(version_name, version_tag, release_notes);
}
break;
default:
// unknown error code
{
qDebug() << "Updater exited with unknown code" << exit_code;
auto msgBox =
QMessageBox(QMessageBox::Information, tr("Unknown Update Error"),
tr("The updater exited with an unknown condition.\nExit Code: %1").arg(QString::number(exit_code)),
QMessageBox::Ok, priv->parent);
auto detail_txt = tr("StdOut: %1\nStdErr: %2").arg(QString(std_output)).arg(QString(std_error));
msgBox.setDetailedText(detail_txt);
msgBox.setMinimumWidth(460);
msgBox.adjustSize();
msgBox.exec();
}
}
priv->lastCheck = QDateTime::currentDateTime();
priv->settings->setValue("last_check", priv->lastCheck.toString(Qt::ISODate));
priv->settings->sync();
resetAutoCheckTimer();
}
bool PrismExternalUpdater::getAutomaticallyChecksForUpdates()
{
return priv->autoCheck;
}
double PrismExternalUpdater::getUpdateCheckInterval()
{
return priv->updateInterval;
}
bool PrismExternalUpdater::getBetaAllowed()
{
return priv->allowBeta;
}
void PrismExternalUpdater::setAutomaticallyChecksForUpdates(bool check)
{
priv->autoCheck = check;
priv->settings->setValue("auto_check", check);
priv->settings->sync();
resetAutoCheckTimer();
}
void PrismExternalUpdater::setUpdateCheckInterval(double seconds)
{
priv->updateInterval = seconds;
priv->settings->setValue("update_interval", seconds);
priv->settings->sync();
resetAutoCheckTimer();
}
void PrismExternalUpdater::setBetaAllowed(bool allowed)
{
priv->allowBeta = allowed;
priv->settings->setValue("auto_beta", allowed);
priv->settings->sync();
}
void PrismExternalUpdater::resetAutoCheckTimer()
{
if (priv->autoCheck) {
int timeoutDuration = 0;
auto now = QDateTime::currentDateTime();
if (priv->lastCheck.isValid()) {
auto diff = priv->lastCheck.secsTo(now);
auto secs_left = priv->updateInterval - diff;
if (secs_left < 0)
secs_left = 0;
timeoutDuration = secs_left * 1000; // to msec
}
qDebug() << "Auto update timer starting," << timeoutDuration / 1000 << "seconds left";
priv->updateTimer.start(timeoutDuration);
} else {
if (priv->updateTimer.isActive())
priv->updateTimer.stop();
}
}
void PrismExternalUpdater::connectTimer()
{
connect(&priv->updateTimer, &QTimer::timeout, this, &PrismExternalUpdater::autoCheckTimerFired);
}
void PrismExternalUpdater::disconnectTimer()
{
disconnect(&priv->updateTimer, &QTimer::timeout, this, &PrismExternalUpdater::autoCheckTimerFired);
}
void PrismExternalUpdater::autoCheckTimerFired()
{
qDebug() << "Auto update Timer fired";
checkForUpdates();
}
void PrismExternalUpdater::offerUpdate(const QString& version_name, const QString& version_tag, const QString& release_notes)
{
priv->settings->beginGroup("skip");
auto should_skip = priv->settings->value(version_tag, false).toBool();
priv->settings->endGroup();
if (should_skip) {
auto msgBox = QMessageBox(QMessageBox::Information, tr("No Update Available"), tr("There are no new updates available."),
QMessageBox::Ok, priv->parent);
msgBox.setMinimumWidth(460);
msgBox.adjustSize();
msgBox.exec();
return;
}
UpdateAvailableDialog dlg(BuildConfig.printableVersionString(), version_name, release_notes);
auto result = dlg.exec();
qDebug() << "offer dlg result" << result;
switch (result) {
case UpdateAvailableDialog::Install: {
performUpdate(version_tag);
return;
}
case UpdateAvailableDialog::Skip: {
priv->settings->beginGroup("skip");
priv->settings->setValue(version_tag, true);
priv->settings->endGroup();
priv->settings->sync();
return;
}
case UpdateAvailableDialog::DontInstall: {
return;
}
}
}
void PrismExternalUpdater::performUpdate(const QString& version_tag)
{
QProcess proc;
auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME);
#if defined Q_OS_WIN32
exe_name.append(".exe");
auto env = QProcessEnvironment::systemEnvironment();
env.insert("__COMPAT_LAYER", "RUNASINVOKER");
proc.setProcessEnvironment(env);
#else
exe_name = QString("bin/%1").arg(exe_name);
#endif
QStringList args = { "--dir", priv->dataDir.absolutePath(), "--install-version", version_tag };
if (priv->allowBeta)
args.append("--pre-release");
auto result = proc.startDetached(priv->appDir.absoluteFilePath(exe_name), args);
if (!result) {
qDebug() << "Failed to start updater:" << proc.error() << proc.errorString();
}
QCoreApplication::exit();
}

View File

@ -0,0 +1,95 @@
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <QObject>
#include "ExternalUpdater.h"
/*!
* An implementation for the updater on windows and linux that uses out external updater.
*/
class PrismExternalUpdater : public ExternalUpdater {
Q_OBJECT
public:
PrismExternalUpdater(QWidget* parent, const QString& appDir, const QString& dataDir);
~PrismExternalUpdater() override;
/*!
* Check for updates manually, showing the user a progress bar and an alert if no updates are found.
*/
void checkForUpdates() override;
/*!
* Indicates whether or not to check for updates automatically.
*/
bool getAutomaticallyChecksForUpdates() override;
/*!
* Indicates the current automatic update check interval in seconds.
*/
double getUpdateCheckInterval() override;
/*!
* Indicates whether or not beta updates should be checked for in addition to regular releases.
*/
bool getBetaAllowed() override;
/*!
* Set whether or not to check for updates automatically.
*
* The update schedule cycle will be reset in a short delay after the propertys new value is set. This is to allow
* reverting this property without kicking off a schedule change immediately."
*/
void setAutomaticallyChecksForUpdates(bool check) override;
/*!
* Set the current automatic update check interval in seconds.
*
* The update schedule cycle will be reset in a short delay after the propertys new value is set. This is to allow
* reverting this property without kicking off a schedule change immediately."
*/
void setUpdateCheckInterval(double seconds) override;
/*!
* Set whether or not beta updates should be checked for in addition to regular releases.
*/
void setBetaAllowed(bool allowed) override;
void resetAutoCheckTimer();
void disconnectTimer();
void connectTimer();
void offerUpdate(const QString& version_name, const QString& version_tag, const QString& release_notes);
void performUpdate(const QString& version_tag);
public slots:
void autoCheckTimerFired();
private:
class Private;
Private* priv;
};

View File

@ -0,0 +1,93 @@
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// 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 <https://www.gnu.org/licenses/>.
*
*/
#include "GitHubRelease.h"
QDebug operator<<(QDebug debug, const GitHubReleaseAsset& asset)
{
QDebugStateSaver saver(debug);
debug.nospace() << "GitHubReleaseAsset( "
"id: "
<< asset.id
<< ", "
"name "
<< asset.name
<< ", "
"label: "
<< asset.label
<< ", "
"content_type: "
<< asset.content_type
<< ", "
"size: "
<< asset.size
<< ", "
"created_at: "
<< asset.created_at
<< ", "
"updated_at: "
<< asset.updated_at
<< ", "
"browser_download_url: "
<< asset.browser_download_url
<< " "
")";
return debug;
}
QDebug operator<<(QDebug debug, const GitHubRelease& rls)
{
QDebugStateSaver saver(debug);
debug.nospace() << "GitHubRelease( "
"id: "
<< rls.id
<< ", "
"name "
<< rls.name
<< ", "
"tag_name: "
<< rls.tag_name
<< ", "
"created_at: "
<< rls.created_at
<< ", "
"published_at: "
<< rls.published_at
<< ", "
"prerelease: "
<< rls.prerelease
<< ", "
"draft: "
<< rls.draft
<< ", "
"version"
<< rls.version
<< ", "
"body: "
<< rls.body
<< ", "
"assets: "
<< rls.assets
<< " "
")";
return debug;
}

View File

@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <QDateTime>
#include <QList>
#include <QString>
#include <QDebug>
#include "Version.h"
struct GitHubReleaseAsset {
int id = -1;
QString name;
QString label;
QString content_type;
int size;
QDateTime created_at;
QDateTime updated_at;
QString browser_download_url;
bool isValid() { return id > 0; }
};
struct GitHubRelease {
int id = -1;
QString name;
QString tag_name;
QDateTime created_at;
QDateTime published_at;
bool prerelease;
bool draft;
QString body;
QList<GitHubReleaseAsset> assets;
Version version;
bool isValid() const { return id > 0; }
};
QDebug operator<<(QDebug debug, const GitHubReleaseAsset& rls);
QDebug operator<<(QDebug debug, const GitHubRelease& rls);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,143 @@
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <QtCore>
#include <QApplication>
#include <QDataStream>
#include <QDateTime>
#include <QDebug>
#include <QFlag>
#include <QIcon>
#include <QLocalSocket>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QUrl>
#include <memory>
#include <optional>
#include "QObjectPtr.h"
#include "net/Download.h"
#define PRISM_EXTERNAL_EXE
#include "FileSystem.h"
#include "GitHubRelease.h"
class PrismUpdaterApp : public QApplication {
// friends for the purpose of limiting access to deprecated stuff
Q_OBJECT
public:
enum Status { Starting, Failed, Succeeded, Initialized, Aborted };
PrismUpdaterApp(int& argc, char** argv);
virtual ~PrismUpdaterApp();
void loadReleaseList();
void run();
Status status() const { return m_status; }
private:
void fail(const QString& reason);
void abort(const QString& reason);
void showFatalErrorMessage(const QString& title, const QString& content);
bool loadPrismVersionFromExe(const QString& exe_path);
void downloadReleasePage(const QString& api_url, int page);
int parseReleasePage(const QByteArray* response);
bool needUpdate(const GitHubRelease& release);
GitHubRelease getLatestRelease();
GitHubRelease selectRelease();
QList<GitHubRelease> newerReleases();
QList<GitHubRelease> nonDraftReleases();
void printReleases();
QList<GitHubReleaseAsset> validReleaseArtifacts(const GitHubRelease& release);
GitHubReleaseAsset selectAsset(const QList<GitHubReleaseAsset>& assets);
void performUpdate(const GitHubRelease& release);
void performInstall(QFileInfo file);
void unpackAndInstall(QFileInfo file);
void backupAppDir();
std::optional<QDir> unpackArchive(QFileInfo file);
QFileInfo downloadAsset(const GitHubReleaseAsset& asset);
bool callAppImageUpdate();
void moveAndFinishUpdate(QDir target);
public slots:
void downloadError(QString reason);
private:
const QString& root() { return m_rootPath; }
bool isPortable() { return m_isPortable; }
void clearUpdateLog();
void logUpdate(const QString& msg);
QString m_rootPath;
QString m_dataPath;
bool m_isPortable = false;
bool m_isAppimage = false;
bool m_isFlatpak = false;
QString m_appimagePath;
QString m_prismExecutable;
QUrl m_prismRepoUrl;
Version m_userSelectedVersion;
bool m_checkOnly;
bool m_forceUpdate;
bool m_printOnly;
bool m_selectUI;
bool m_allowDowngrade;
bool m_allowPreRelease;
QString m_updateLogPath;
QString m_prismBinaryName;
QString m_prismVersion;
int m_prismVersionMajor = -1;
int m_prismVersionMinor = -1;
QString m_prsimVersionChannel;
QString m_prismGitCommit;
GitHubRelease m_install_release;
Status m_status = Status::Starting;
shared_qobject_ptr<QNetworkAccessManager> m_network;
QString m_current_url;
Task::Ptr m_current_task;
QList<GitHubRelease> m_releases;
public:
std::unique_ptr<QFile> logFile;
bool logToConsole = false;
#if defined Q_OS_WIN32
// used on Windows to attach the standard IO streams
bool consoleAttached = false;
#endif
};

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SelectReleaseDialog</class>
<widget class="QDialog" name="SelectReleaseDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>468</width>
<height>385</height>
</rect>
</property>
<property name="windowTitle">
<string>Select Release to Install</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1,0">
<item>
<widget class="QLabel" name="eplainLabel">
<property name="text">
<string>Please select the release you wish to update to.</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="versionsTree">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QTextBrowser" name="changelogTextBrowser"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SelectReleaseDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SelectReleaseDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,168 @@
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// 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 <https://www.gnu.org/licenses/>.
*
*/
#include "UpdaterDialogs.h"
#include "ui_SelectReleaseDialog.h"
#include <QTextBrowser>
#include "Markdown.h"
SelectReleaseDialog::SelectReleaseDialog(const Version& current_version, const QList<GitHubRelease>& releases, QWidget* parent)
: QDialog(parent), m_releases(releases), m_currentVersion(current_version), ui(new Ui::SelectReleaseDialog)
{
ui->setupUi(this);
ui->changelogTextBrowser->setOpenExternalLinks(true);
ui->changelogTextBrowser->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth);
ui->changelogTextBrowser->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
ui->versionsTree->setColumnCount(2);
ui->versionsTree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
ui->versionsTree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
ui->versionsTree->setHeaderLabels({ tr("Version"), tr("Published Date") });
ui->versionsTree->header()->setStretchLastSection(false);
ui->eplainLabel->setText(tr("Select a version to install.\n"
"\n"
"Currently installed version: %1")
.arg(m_currentVersion.toString()));
loadReleases();
connect(ui->versionsTree, &QTreeWidget::currentItemChanged, this, &SelectReleaseDialog::selectionChanged);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SelectReleaseDialog::accept);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectReleaseDialog::reject);
}
SelectReleaseDialog::~SelectReleaseDialog()
{
delete ui;
}
void SelectReleaseDialog::loadReleases()
{
for (auto rls : m_releases) {
appendRelease(rls);
}
}
void SelectReleaseDialog::appendRelease(GitHubRelease const& release)
{
auto rls_item = new QTreeWidgetItem(ui->versionsTree);
rls_item->setText(0, release.tag_name);
rls_item->setExpanded(true);
rls_item->setText(1, release.published_at.toString());
rls_item->setData(0, Qt::UserRole, QVariant(release.id));
ui->versionsTree->addTopLevelItem(rls_item);
}
GitHubRelease SelectReleaseDialog::getRelease(QTreeWidgetItem* item)
{
int id = item->data(0, Qt::UserRole).toInt();
GitHubRelease release;
for (auto rls : m_releases) {
if (rls.id == id)
release = rls;
}
return release;
}
void SelectReleaseDialog::selectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
{
GitHubRelease release = getRelease(current);
QString body = markdownToHTML(release.body.toUtf8());
m_selectedRelease = release;
ui->changelogTextBrowser->setHtml(body);
}
SelectReleaseAssetDialog::SelectReleaseAssetDialog(const QList<GitHubReleaseAsset>& assets, QWidget* parent)
: QDialog(parent), m_assets(assets), ui(new Ui::SelectReleaseDialog)
{
ui->setupUi(this);
ui->changelogTextBrowser->setOpenExternalLinks(true);
ui->changelogTextBrowser->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth);
ui->changelogTextBrowser->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
ui->versionsTree->setColumnCount(2);
ui->versionsTree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
ui->versionsTree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
ui->versionsTree->setHeaderLabels({ tr("Version"), tr("Published Date") });
ui->versionsTree->header()->setStretchLastSection(false);
ui->eplainLabel->setText(tr("Select a version to install."));
ui->changelogTextBrowser->setHidden(true);
loadAssets();
connect(ui->versionsTree, &QTreeWidget::currentItemChanged, this, &SelectReleaseAssetDialog::selectionChanged);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SelectReleaseAssetDialog::accept);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectReleaseAssetDialog::reject);
}
SelectReleaseAssetDialog::~SelectReleaseAssetDialog()
{
delete ui;
}
void SelectReleaseAssetDialog::loadAssets()
{
for (auto rls : m_assets) {
appendAsset(rls);
}
}
void SelectReleaseAssetDialog::appendAsset(GitHubReleaseAsset const& asset)
{
auto rls_item = new QTreeWidgetItem(ui->versionsTree);
rls_item->setText(0, asset.name);
rls_item->setExpanded(true);
rls_item->setText(1, asset.updated_at.toString());
rls_item->setData(0, Qt::UserRole, QVariant(asset.id));
ui->versionsTree->addTopLevelItem(rls_item);
}
GitHubReleaseAsset SelectReleaseAssetDialog::getAsset(QTreeWidgetItem* item)
{
int id = item->data(0, Qt::UserRole).toInt();
GitHubReleaseAsset selected_asset;
for (auto asset : m_assets) {
if (asset.id == id)
selected_asset = asset;
}
return selected_asset;
}
void SelectReleaseAssetDialog::selectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
{
GitHubReleaseAsset asset = getAsset(current);
m_selectedAsset = asset;
}

View File

@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <QDialog>
#include <QTreeWidgetItem>
#include "GitHubRelease.h"
#include "Version.h"
namespace Ui {
class SelectReleaseDialog;
}
class SelectReleaseDialog : public QDialog {
Q_OBJECT
public:
explicit SelectReleaseDialog(const Version& cur_version, const QList<GitHubRelease>& releases, QWidget* parent = 0);
~SelectReleaseDialog();
void loadReleases();
void appendRelease(GitHubRelease const& release);
GitHubRelease selectedRelease() { return m_selectedRelease; }
private slots:
GitHubRelease getRelease(QTreeWidgetItem* item);
void selectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous);
protected:
QList<GitHubRelease> m_releases;
GitHubRelease m_selectedRelease;
Version m_currentVersion;
Ui::SelectReleaseDialog* ui;
};
class SelectReleaseAssetDialog : public QDialog {
Q_OBJECT
public:
explicit SelectReleaseAssetDialog(const QList<GitHubReleaseAsset>& assets, QWidget* parent = 0);
~SelectReleaseAssetDialog();
void loadAssets();
void appendAsset(GitHubReleaseAsset const& asset);
GitHubReleaseAsset selectedAsset() { return m_selectedAsset; }
private slots:
GitHubReleaseAsset getAsset(QTreeWidgetItem* item);
void selectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous);
protected:
QList<GitHubReleaseAsset> m_assets;
GitHubReleaseAsset m_selectedAsset;
Ui::SelectReleaseDialog* ui;
};

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10, Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

View File

@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "PrismUpdater.h"
int main(int argc, char* argv[])
{
PrismUpdaterApp wUpApp(argc, argv);
switch (wUpApp.status()) {
case PrismUpdaterApp::Starting:
case PrismUpdaterApp::Initialized: {
return wUpApp.exec();
}
case PrismUpdaterApp::Failed:
return 1;
case PrismUpdaterApp::Succeeded:
return 0;
default:
return -1;
}
}

View File

@ -350,6 +350,7 @@ Section "@Launcher_DisplayName@"
File "@Launcher_APP_BINARY_NAME@.exe" File "@Launcher_APP_BINARY_NAME@.exe"
File "@Launcher_APP_BINARY_NAME@_filelink.exe" File "@Launcher_APP_BINARY_NAME@_filelink.exe"
File "@Launcher_APP_BINARY_NAME@_updater.exe"
File "qt.conf" File "qt.conf"
File "qtlogging.ini" File "qtlogging.ini"
File *.dll File *.dll
@ -435,6 +436,7 @@ Section "Uninstall"
Delete $INSTDIR\@Launcher_APP_BINARY_NAME@.exe Delete $INSTDIR\@Launcher_APP_BINARY_NAME@.exe
Delete $INSTDIR\@Launcher_APP_BINARY_NAME@_filelink.exe Delete $INSTDIR\@Launcher_APP_BINARY_NAME@_filelink.exe
Delete $INSTDIR\@Launcher_APP_BINARY_NAME@_updater.exe
Delete $INSTDIR\qt.conf Delete $INSTDIR\qt.conf
Delete $INSTDIR\*.dll Delete $INSTDIR\*.dll
@ -472,7 +474,6 @@ Function .onInit
${GetParameters} $R0 ${GetParameters} $R0
${GetOptions} $R0 "/NoShortcuts" $R1 ${GetOptions} $R0 "/NoShortcuts" $R1
${IfNot} ${Errors} ${IfNot} ${Errors}
${OrIf} ${FileExists} "$InstDir\@Launcher_APP_BINARY_NAME@.exe"
!insertmacro UnselectSection ${SM_SHORTCUTS} !insertmacro UnselectSection ${SM_SHORTCUTS}
!insertmacro UnselectSection ${DESKTOP_SHORTCUTS} !insertmacro UnselectSection ${DESKTOP_SHORTCUTS}
${EndIf} ${EndIf}