Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into import_zip
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
commit
9400879242
132
.github/workflows/build.yml
vendored
132
.github/workflows/build.yml
vendored
@ -37,56 +37,43 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
|
||||
- os: ubuntu-20.04
|
||||
qt_ver: 5
|
||||
|
||||
- os: ubuntu-20.04
|
||||
qt_ver: 6
|
||||
qt_host: linux
|
||||
qt_arch: ''
|
||||
qt_version: '6.2.4'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
qt_arch: ""
|
||||
qt_version: "6.2.4"
|
||||
qt_modules: "qt5compat qtimageformats"
|
||||
qt_tools: ""
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MinGW-w64"
|
||||
msystem: clang64
|
||||
vcvars_arch: 'amd64_x86'
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MSVC-Legacy"
|
||||
msystem: ''
|
||||
architecture: 'win32'
|
||||
vcvars_arch: 'amd64_x86'
|
||||
qt_ver: 5
|
||||
qt_host: windows
|
||||
qt_arch: 'win32_msvc2019'
|
||||
qt_version: '5.15.2'
|
||||
qt_modules: ''
|
||||
qt_tools: 'tools_openssl_x86'
|
||||
vcvars_arch: "amd64_x86"
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MSVC"
|
||||
msystem: ''
|
||||
architecture: 'x64'
|
||||
vcvars_arch: 'amd64'
|
||||
msystem: ""
|
||||
architecture: "x64"
|
||||
vcvars_arch: "amd64"
|
||||
qt_ver: 6
|
||||
qt_host: windows
|
||||
qt_arch: ''
|
||||
qt_version: '6.5.2'
|
||||
qt_version: '6.6.0'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MSVC-arm64"
|
||||
msystem: ''
|
||||
architecture: 'arm64'
|
||||
vcvars_arch: 'amd64_arm64'
|
||||
msystem: ""
|
||||
architecture: "arm64"
|
||||
vcvars_arch: "amd64_arm64"
|
||||
qt_ver: 6
|
||||
qt_host: windows
|
||||
qt_arch: 'win64_msvc2019_arm64'
|
||||
qt_version: '6.5.2'
|
||||
qt_version: '6.6.0'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
@ -96,7 +83,7 @@ jobs:
|
||||
qt_ver: 6
|
||||
qt_host: mac
|
||||
qt_arch: ''
|
||||
qt_version: '6.5.2'
|
||||
qt_version: '6.6.0'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
@ -105,9 +92,9 @@ jobs:
|
||||
macosx_deployment_target: 10.13
|
||||
qt_ver: 5
|
||||
qt_host: mac
|
||||
qt_version: '5.15.2'
|
||||
qt_modules: ''
|
||||
qt_tools: ''
|
||||
qt_version: "5.15.2"
|
||||
qt_modules: ""
|
||||
qt_tools: ""
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@ -127,9 +114,9 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
submodules: "true"
|
||||
|
||||
- name: 'Setup MSYS2'
|
||||
- name: "Setup MSYS2"
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
@ -169,7 +156,7 @@ jobs:
|
||||
path: '${{ github.workspace }}\.ccache'
|
||||
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ matrix.os }}-mingw-w64-ccache
|
||||
${{ matrix.os }}-mingw-w64-ccache
|
||||
|
||||
- name: Setup ccache (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||
@ -214,35 +201,35 @@ jobs:
|
||||
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
aqtversion: '==3.1.*'
|
||||
py7zrversion: '>=0.20.2'
|
||||
version: ${{ matrix.qt_version }}
|
||||
host: 'windows'
|
||||
target: 'desktop'
|
||||
arch: ''
|
||||
modules: ${{ matrix.qt_modules }}
|
||||
tools: ${{ matrix.qt_tools }}
|
||||
cache: ${{ inputs.is_qt_cached }}
|
||||
cache-key-prefix: host-qt-arm64-windows
|
||||
dir: ${{ github.workspace }}\HostQt
|
||||
set-env: false
|
||||
aqtversion: "==3.1.*"
|
||||
py7zrversion: ">=0.20.2"
|
||||
version: ${{ matrix.qt_version }}
|
||||
host: "windows"
|
||||
target: "desktop"
|
||||
arch: ""
|
||||
modules: ${{ matrix.qt_modules }}
|
||||
tools: ${{ matrix.qt_tools }}
|
||||
cache: ${{ inputs.is_qt_cached }}
|
||||
cache-key-prefix: host-qt-arm64-windows
|
||||
dir: ${{ github.workspace }}\HostQt
|
||||
set-env: false
|
||||
|
||||
- name: Install Qt (macOS, Linux, Qt 6 & Windows MSVC)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '')
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
aqtversion: '==3.1.*'
|
||||
py7zrversion: '>=0.20.2'
|
||||
version: ${{ matrix.qt_version }}
|
||||
host: ${{ matrix.qt_host }}
|
||||
target: 'desktop'
|
||||
arch: ${{ matrix.qt_arch }}
|
||||
modules: ${{ matrix.qt_modules }}
|
||||
tools: ${{ matrix.qt_tools }}
|
||||
cache: ${{ inputs.is_qt_cached }}
|
||||
aqtversion: "==3.1.*"
|
||||
py7zrversion: ">=0.20.2"
|
||||
version: ${{ matrix.qt_version }}
|
||||
host: ${{ matrix.qt_host }}
|
||||
target: "desktop"
|
||||
arch: ${{ matrix.qt_arch }}
|
||||
modules: ${{ matrix.qt_modules }}
|
||||
tools: ${{ matrix.qt_tools }}
|
||||
cache: ${{ inputs.is_qt_cached }}
|
||||
|
||||
- name: Install MSVC (Windows MSVC)
|
||||
if: runner.os == 'Windows' # 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
|
||||
with:
|
||||
vsversion: 2022
|
||||
@ -283,12 +270,12 @@ jobs:
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -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)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }}
|
||||
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
|
||||
if ("${{ env.CCACHE_VAR }}")
|
||||
{
|
||||
@ -303,7 +290,7 @@ jobs:
|
||||
- name: Configure CMake (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -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
|
||||
@ -343,7 +330,7 @@ jobs:
|
||||
- name: Test (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
|
||||
run: |
|
||||
ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
|
||||
ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
|
||||
|
||||
##
|
||||
# PACKAGE BUILDS
|
||||
@ -385,7 +372,7 @@ jobs:
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }}
|
||||
touch ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
|
||||
- name: Package (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
@ -402,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
|
||||
|
||||
|
||||
- name: Fetch codesign certificate (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: |
|
||||
echo '${{ secrets.WINDOWS_CODESIGN_CERT }}' | base64 --decode > codesign.pfx
|
||||
|
||||
@ -415,7 +401,7 @@ jobs:
|
||||
if (Get-Content ./codesign.pfx){
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
# We ship the exact same executable for portable and non-portable editions, so signing just once is fine
|
||||
SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_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 {
|
||||
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
|
||||
}
|
||||
@ -507,15 +493,7 @@ jobs:
|
||||
export LD_LIBRARY_PATH
|
||||
|
||||
chmod +x AppImageUpdate-x86_64.AppImage
|
||||
./AppImageUpdate-x86_64.AppImage --appimage-extract
|
||||
|
||||
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
|
||||
cp AppImageUpdate-x86_64.AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
|
||||
|
||||
export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync"
|
||||
|
||||
@ -569,14 +547,14 @@ jobs:
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
name: PrismLauncher-${{ runner.os }}-Qt5-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: PrismLauncher.tar.gz
|
||||
|
||||
- name: Upload binary tarball (Linux, portable, Qt 5)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: PrismLauncher-portable.tar.gz
|
||||
|
||||
- name: Upload binary tarball (Linux, Qt 6)
|
||||
@ -599,7 +577,7 @@ jobs:
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
||||
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
||||
|
||||
|
||||
- name: Upload AppImage Zsync (Linux)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
uses: actions/upload-artifact@v3
|
||||
@ -623,10 +601,10 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
if: inputs.build_type == 'Debug'
|
||||
with:
|
||||
submodules: 'true'
|
||||
submodules: "true"
|
||||
- name: Build Flatpak (Linux)
|
||||
if: inputs.build_type == 'Debug'
|
||||
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
||||
with:
|
||||
bundle: "Prism Launcher.flatpak"
|
||||
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
|
||||
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
|
||||
|
29
.github/workflows/trigger_builds.yml
vendored
29
.github/workflows/trigger_builds.yml
vendored
@ -3,26 +3,25 @@ name: Build Application
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'renovate/**'
|
||||
- "renovate/**"
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**/LICENSE'
|
||||
- 'flake.lock'
|
||||
- 'packages/**'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
- '.markdownlint**'
|
||||
- "**.md"
|
||||
- "**/LICENSE"
|
||||
- "flake.lock"
|
||||
- "packages/**"
|
||||
- ".github/ISSUE_TEMPLATE/**"
|
||||
- ".markdownlint**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**/LICENSE'
|
||||
- 'flake.lock'
|
||||
- 'packages/**'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
- '.markdownlint**'
|
||||
- "**.md"
|
||||
- "**/LICENSE"
|
||||
- "flake.lock"
|
||||
- "packages/**"
|
||||
- ".github/ISSUE_TEMPLATE/**"
|
||||
- ".markdownlint**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
build_debug:
|
||||
name: Build Debug
|
||||
uses: ./.github/workflows/build.yml
|
||||
@ -34,3 +33,5 @@ jobs:
|
||||
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
||||
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||
|
22
.github/workflows/trigger_release.yml
vendored
22
.github/workflows/trigger_release.yml
vendored
@ -3,10 +3,9 @@ name: Build Application and Make Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
|
||||
build_release:
|
||||
name: Build Release
|
||||
uses: ./.github/workflows/build.yml
|
||||
@ -18,6 +17,8 @@ jobs:
|
||||
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
||||
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||
|
||||
create_release:
|
||||
needs: build_release
|
||||
@ -28,8 +29,8 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
path: 'PrismLauncher-source'
|
||||
submodules: "true"
|
||||
path: "PrismLauncher-source"
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
- name: Grab and store version
|
||||
@ -40,9 +41,9 @@ jobs:
|
||||
run: |
|
||||
mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }}
|
||||
mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-Linux-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-Linux*/PrismLauncher.tar.gz PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-Linux-Qt5*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
|
||||
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||
mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
||||
@ -86,8 +87,8 @@ jobs:
|
||||
draft: true
|
||||
prerelease: false
|
||||
files: |
|
||||
PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-Linux-x86_64.AppImage
|
||||
PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||
PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||
@ -95,9 +96,6 @@ jobs:
|
||||
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe
|
||||
PrismLauncher-Windows-MSVC-Legacy-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-Legacy-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-Legacy-Setup-${{ env.VERSION }}.exe
|
||||
PrismLauncher-Windows-MSVC-arm64-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-arm64-Setup-${{ env.VERSION }}.exe
|
||||
|
@ -188,8 +188,11 @@ set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_M
|
||||
# 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.")
|
||||
|
||||
# Channel list URL
|
||||
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
|
||||
# Github repo URL with releases for updater
|
||||
set(Launcher_UPDATER_GITHUB_REPO "https://github.com/PrismLauncher/PrismLauncher" CACHE STRING "Base github URL for the updater.")
|
||||
|
||||
# Name to help updater identify valid artifacts
|
||||
set(Launcher_BUILD_ARTIFACT "" CACHE STRING "Artifact name to help the updater identify valid artifacts.")
|
||||
|
||||
# The metadata server
|
||||
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
|
||||
set(Launcher_CURSEFORGE_API_KEY "$2a$10$wuAJuNZuted3NORVmpgUC.m8sI.pv1tOPKZyBgLFGjxFp/br0lZCC" CACHE STRING "API key for the CurseForge platform")
|
||||
|
||||
set(Launcher_COMPILER_NAME ${CMAKE_CXX_COMPILER_ID})
|
||||
set(Launcher_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION})
|
||||
set(Launcher_COMPILER_TARGET_SYSTEM ${CMAKE_SYSTEM_NAME})
|
||||
set(Launcher_COMPILER_TARGET_SYSTEM_VERSION ${CMAKE_SYSTEM_VERSION})
|
||||
set(Launcher_COMPILER_TARGET_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR})
|
||||
|
||||
#### Check the current Git commit and branch
|
||||
include(GetGitRevisionDescription)
|
||||
|
@ -33,6 +33,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <qstringliteral.h>
|
||||
#include "BuildConfig.h"
|
||||
#include <QObject>
|
||||
|
||||
@ -59,8 +60,16 @@ Config::Config()
|
||||
VERSION_MINOR = @Launcher_VERSION_MINOR@;
|
||||
|
||||
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
|
||||
BUILD_ARTIFACT = "@Launcher_BUILD_ARTIFACT@";
|
||||
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_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@";
|
||||
@ -68,6 +77,8 @@ Config::Config()
|
||||
if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty())
|
||||
{
|
||||
UPDATER_ENABLED = true;
|
||||
} else if(!UPDATER_GITHUB_REPO.isEmpty() && !BUILD_ARTIFACT.isEmpty()) {
|
||||
UPDATER_ENABLED = true;
|
||||
}
|
||||
|
||||
GIT_COMMIT = "@Launcher_GIT_COMMIT@";
|
||||
@ -88,10 +99,7 @@ Config::Config()
|
||||
if (GIT_REFSPEC.startsWith("refs/heads/"))
|
||||
{
|
||||
VERSION_CHANNEL = GIT_REFSPEC;
|
||||
VERSION_CHANNEL.remove("refs/heads/");
|
||||
if(!UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty()) {
|
||||
UPDATER_ENABLED = true;
|
||||
}
|
||||
VERSION_CHANNEL.remove("refs/heads/");
|
||||
}
|
||||
else if (!GIT_COMMIT.isEmpty())
|
||||
{
|
||||
@ -136,3 +144,16 @@ QString Config::printableVersionString() const
|
||||
}
|
||||
return vstr;
|
||||
}
|
||||
|
||||
QString Config::compilerID() const
|
||||
{
|
||||
if (COMPILER_VERSION.isEmpty())
|
||||
return COMPILER_NAME;
|
||||
return QStringLiteral("%1 - %2").arg(COMPILER_NAME).arg(COMPILER_VERSION);
|
||||
}
|
||||
|
||||
QString Config::systemID() const
|
||||
{
|
||||
return QStringLiteral("%1 %2 %3").arg(COMPILER_TARGET_SYSTEM, COMPILER_TARGET_SYSTEM_VERSION, COMPILER_TARGET_SYSTEM_PROCESSOR);
|
||||
}
|
||||
|
||||
|
@ -71,11 +71,29 @@ class Config {
|
||||
/// A short string identifying this build's platform or distribution.
|
||||
QString BUILD_PLATFORM;
|
||||
|
||||
/// A short string identifying this build's valid artifacts int he updater. For example, "lin64" or "win32".
|
||||
QString BUILD_ARTIFACT;
|
||||
|
||||
/// A string containing the build timestamp
|
||||
QString BUILD_DATE;
|
||||
|
||||
/// A string identifying the compiler use to build
|
||||
QString COMPILER_NAME;
|
||||
|
||||
/// A string identifying the compiler version used to build
|
||||
QString COMPILER_VERSION;
|
||||
|
||||
/// A string identifying the compiler target system os
|
||||
QString COMPILER_TARGET_SYSTEM;
|
||||
|
||||
/// A String identifying the compiler target system version
|
||||
QString COMPILER_TARGET_SYSTEM_VERSION;
|
||||
|
||||
/// A String identifying the compiler target processor
|
||||
QString COMPILER_TARGET_SYSTEM_PROCESSOR;
|
||||
|
||||
/// URL for the updater's channel
|
||||
QString UPDATER_BASE;
|
||||
QString UPDATER_GITHUB_REPO;
|
||||
|
||||
/// The public key used to sign releases for the Sparkle updater appcast
|
||||
QString MAC_SPARKLE_PUB_KEY;
|
||||
@ -175,6 +193,18 @@ class Config {
|
||||
* \return The version number in string format (major.minor.revision.build).
|
||||
*/
|
||||
QString printableVersionString() const;
|
||||
|
||||
/**
|
||||
* \brief Compiler ID String
|
||||
* \return a string of the form "Name - Version" of just "Name" if the version is empty
|
||||
*/
|
||||
QString compilerID() const;
|
||||
|
||||
/**
|
||||
* \brief System ID String
|
||||
* \return a string of the form "OS Verison Processor"
|
||||
*/
|
||||
QString systemID() const;
|
||||
};
|
||||
|
||||
extern const Config BuildConfig;
|
||||
|
12
flake.lock
12
flake.lock
@ -106,11 +106,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1696661029,
|
||||
"narHash": "sha256-GIB5VTkvsDIqfMpdtuetOzpm64P8wm8nBSv5Eo8XM3Y=",
|
||||
"lastModified": 1697009197,
|
||||
"narHash": "sha256-viVRhBTFT8fPJTb1N3brQIpFZnttmwo3JVKNuWRVc3s=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2de1be5b51c3d6fa833f1c1f222dc867dd054b31",
|
||||
"rev": "01441e14af5e29c9d27ace398e6dd0b293e25a54",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -153,11 +153,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1696516544,
|
||||
"narHash": "sha256-8rKE8Je6twTNFRTGF63P9mE3lZIq917RAicdc4XJO80=",
|
||||
"lastModified": 1696846637,
|
||||
"narHash": "sha256-0hv4kbXxci2+pxhuXlVgftj/Jq79VSmtAyvfabCCtYk=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "66c352d33e0907239e4a69416334f64af2c685cc",
|
||||
"rev": "42e1b6095ef80a51f79595d9951eb38e91c4e6ca",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -122,6 +122,7 @@
|
||||
#include <FileSystem.h>
|
||||
#include <LocalPeer.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys.h>
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
@ -130,9 +131,13 @@
|
||||
#include "gamemode_client.h"
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
|
||||
#if defined(Q_OS_MAC)
|
||||
#if defined(SPARKLE_ENABLED)
|
||||
#include "updater/MacSparkleUpdater.h"
|
||||
#endif
|
||||
#else
|
||||
#include "updater/PrismExternalUpdater.h"
|
||||
#endif
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#include "WindowsConsole.h"
|
||||
@ -164,6 +169,34 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt
|
||||
|
||||
} // 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)
|
||||
{
|
||||
#if defined Q_OS_WIN32
|
||||
@ -296,6 +329,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
.arg(dataPath));
|
||||
return;
|
||||
}
|
||||
m_dataPath = dataPath;
|
||||
|
||||
/*
|
||||
* 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() << "Platform : " << BuildConfig.BUILD_PLATFORM;
|
||||
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
|
||||
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()) {
|
||||
qDebug() << "Work dir before adjustment : " << origcwdPath;
|
||||
qDebug() << "Work dir after adjustment : " << QDir::currentPath();
|
||||
@ -582,6 +621,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
m_settings->registerSetting("IgnoreJavaCompatibility", false);
|
||||
m_settings->registerSetting("IgnoreJavaWizard", false);
|
||||
|
||||
// Legacy settings
|
||||
m_settings->registerSetting("OnlineFixes", false);
|
||||
|
||||
// Native library workarounds
|
||||
m_settings->registerSetting("UseNativeOpenAL", false);
|
||||
m_settings->registerSetting("CustomOpenALPath", "");
|
||||
@ -738,15 +780,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
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
|
||||
{
|
||||
auto setting = APPLICATION->settings()->getSetting("IconsDir");
|
||||
@ -849,6 +882,107 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
@ -917,6 +1051,26 @@ bool Application::createSetupWizard()
|
||||
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)
|
||||
{
|
||||
#ifdef Q_OS_MACOS
|
||||
@ -985,6 +1139,20 @@ void Application::performMainStartupAction()
|
||||
showMainWindow(false);
|
||||
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()) {
|
||||
qDebug() << "<> Importing from url:" << m_urlsToImport;
|
||||
m_mainWindow->processURLs(m_urlsToImport);
|
||||
|
@ -159,6 +159,9 @@ class Application : public QApplication {
|
||||
/// this is the root of the 'installation'. Used for automatic updates
|
||||
const QString& root() { return m_rootPath; }
|
||||
|
||||
/// the data path the application is using
|
||||
const QString& dataRoot() { return m_dataPath; }
|
||||
|
||||
bool isPortable() { return m_portable; }
|
||||
|
||||
const Capabilities capabilities() { return m_capabilities; }
|
||||
@ -179,6 +182,9 @@ class Application : public QApplication {
|
||||
|
||||
int suitableMaxMem();
|
||||
|
||||
bool updaterEnabled();
|
||||
QString updaterBinaryName();
|
||||
|
||||
QUrl normalizeImportUrl(QString const& url);
|
||||
|
||||
signals:
|
||||
@ -244,6 +250,7 @@ class Application : public QApplication {
|
||||
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;
|
||||
|
||||
QString m_rootPath;
|
||||
QString m_dataPath;
|
||||
Status m_status = Application::StartingUp;
|
||||
Capabilities m_capabilities;
|
||||
bool m_portable = false;
|
||||
|
@ -181,6 +181,11 @@ set(MAC_UPDATE_SOURCES
|
||||
updater/MacSparkleUpdater.mm
|
||||
)
|
||||
|
||||
set(PRISM_UPDATE_SOURCES
|
||||
updater/PrismExternalUpdater.h
|
||||
updater/PrismExternalUpdater.cpp
|
||||
)
|
||||
|
||||
# Backend for the news bar... there's usually no news.
|
||||
set(NEWS_SOURCES
|
||||
# News System
|
||||
@ -579,6 +584,63 @@ set(LINKEXE_SOURCES
|
||||
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 ########
|
||||
|
||||
ecm_qt_declare_logging_category(CORE_SOURCES
|
||||
@ -675,6 +737,8 @@ set(LOGIC_SOURCES
|
||||
|
||||
if(APPLE AND Launcher_ENABLE_UPDATER)
|
||||
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES})
|
||||
else()
|
||||
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${PRISM_UPDATE_SOURCES})
|
||||
endif()
|
||||
|
||||
SET(LAUNCHER_SOURCES
|
||||
@ -906,6 +970,9 @@ SET(LAUNCHER_SOURCES
|
||||
ui/pages/modplatform/ImportPage.cpp
|
||||
ui/pages/modplatform/ImportPage.h
|
||||
|
||||
ui/pages/modplatform/OptionalModDialog.cpp
|
||||
ui/pages/modplatform/OptionalModDialog.h
|
||||
|
||||
ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
|
||||
ui/pages/modplatform/modrinth/ModrinthResourceModels.h
|
||||
ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
|
||||
@ -1030,6 +1097,15 @@ SET(LAUNCHER_SOURCES
|
||||
ui/instanceview/VisualGroup.h
|
||||
)
|
||||
|
||||
if (NOT Apple)
|
||||
set(LAUNCHER_SOURCES
|
||||
${LAUNCHER_SOURCES}
|
||||
|
||||
ui/dialogs/UpdateAvailableDialog.h
|
||||
ui/dialogs/UpdateAvailableDialog.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(LAUNCHER_SOURCES
|
||||
WindowsConsole.cpp
|
||||
@ -1068,6 +1144,7 @@ qt_wrap_ui(LAUNCHER_UI
|
||||
ui/pages/modplatform/legacy_ftb/Page.ui
|
||||
ui/pages/modplatform/import_ftb/ImportFTBPage.ui
|
||||
ui/pages/modplatform/ImportPage.ui
|
||||
ui/pages/modplatform/OptionalModDialog.ui
|
||||
ui/pages/modplatform/modrinth/ModrinthPage.ui
|
||||
ui/pages/modplatform/technic/TechnicPage.ui
|
||||
ui/widgets/InstanceCardWidget.ui
|
||||
@ -1099,6 +1176,14 @@ qt_wrap_ui(LAUNCHER_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
|
||||
resources/backgrounds/backgrounds.qrc
|
||||
resources/multimc/multimc.qrc
|
||||
@ -1115,6 +1200,12 @@ qt_add_resources(LAUNCHER_RESOURCES
|
||||
../${Launcher_Branding_LogoQRC}
|
||||
)
|
||||
|
||||
qt_wrap_ui(PRISMUPDATER_UI
|
||||
updater/prismupdater/SelectReleaseDialog.ui
|
||||
ui/widgets/SubTaskProgressBar.ui
|
||||
ui/dialogs/ProgressDialog.ui
|
||||
)
|
||||
|
||||
######## Windows resource files ########
|
||||
if(WIN32)
|
||||
set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
|
||||
@ -1133,6 +1224,7 @@ set_project_warnings(Launcher_logic
|
||||
"${Launcher_GCC_WARNINGS}")
|
||||
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
|
||||
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
|
||||
target_link_libraries(Launcher_logic
|
||||
systeminfo
|
||||
Launcher_murmur2
|
||||
@ -1214,7 +1306,45 @@ install(TARGETS ${Launcher_Name}
|
||||
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})
|
||||
set_project_warnings(filelink_logic
|
||||
"${Launcher_MSVC_WARNINGS}"
|
||||
@ -1233,7 +1363,7 @@ if(WIN32)
|
||||
${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)
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
QFile file(filename);
|
||||
@ -238,6 +272,28 @@ bool ensureFolderPathExists(QString foldernamepath)
|
||||
return success;
|
||||
}
|
||||
|
||||
bool copyFileAttributes(QString src, QString dst)
|
||||
{
|
||||
#ifdef Q_OS_WIN32
|
||||
auto attrs = GetFileAttributesW(src.toStdWString().c_str());
|
||||
if (attrs == INVALID_FILE_ATTRIBUTES)
|
||||
return false;
|
||||
return SetFileAttributesW(dst.toStdWString().c_str(), attrs);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// needs folders to exists
|
||||
void copyFolderAttributes(QString src, QString dst, QString relative)
|
||||
{
|
||||
auto path = PathCombine(src, relative);
|
||||
QDir dsrc(src);
|
||||
while ((path = QFileInfo(path).path()).length() >= src.length()) {
|
||||
auto dst_path = PathCombine(dst, dsrc.relativeFilePath(path));
|
||||
copyFileAttributes(path, dst_path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copies a directory and it's contents from src to dest
|
||||
* @param offset subdirectory form src to copy to dest
|
||||
@ -265,6 +321,9 @@ bool copy::operator()(const QString& offset, bool dryRun)
|
||||
if (!m_followSymlinks)
|
||||
opt |= copy_opts::copy_symlinks;
|
||||
|
||||
if (m_overwrite)
|
||||
opt |= copy_opts::overwrite_existing;
|
||||
|
||||
// Function that'll do the actual copying
|
||||
auto copy_file = [&](QString src_path, QString relative_dst_path) {
|
||||
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
|
||||
@ -273,6 +332,9 @@ bool copy::operator()(const QString& offset, bool dryRun)
|
||||
auto dst_path = PathCombine(dst, relative_dst_path);
|
||||
if (!dryRun) {
|
||||
ensureFilePathExists(dst_path);
|
||||
#ifdef Q_OS_WIN32
|
||||
copyFolderAttributes(src, dst, relative_dst_path);
|
||||
#endif
|
||||
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
|
||||
}
|
||||
if (err) {
|
||||
|
@ -61,6 +61,16 @@ class FileSystemException : public ::Exception {
|
||||
*/
|
||||
void write(const QString& filename, const QByteArray& data);
|
||||
|
||||
/**
|
||||
* append data to a file safely
|
||||
*/
|
||||
void appendSafe(const QString& filename, const QByteArray& data);
|
||||
|
||||
/**
|
||||
* append data to a file
|
||||
*/
|
||||
void append(const QString& filename, const QByteArray& data);
|
||||
|
||||
/**
|
||||
* read data from a file safely\
|
||||
*/
|
||||
@ -109,6 +119,11 @@ class copy : public QObject {
|
||||
m_whitelist = whitelist;
|
||||
return *this;
|
||||
}
|
||||
copy& overwrite(const bool overwrite)
|
||||
{
|
||||
m_overwrite = overwrite;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
|
||||
|
||||
@ -128,6 +143,7 @@ class copy : public QObject {
|
||||
bool m_followSymlinks = true;
|
||||
const IPathMatcher* m_matcher = nullptr;
|
||||
bool m_whitelist = false;
|
||||
bool m_overwrite = false;
|
||||
QDir m_src;
|
||||
QDir m_dst;
|
||||
qsizetype m_copied;
|
||||
|
@ -2,6 +2,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -237,8 +238,11 @@ GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
|
||||
return GroupId();
|
||||
}
|
||||
|
||||
void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
void InstanceList::setInstanceGroup(const InstanceId& id, GroupId name)
|
||||
{
|
||||
if (name.isEmpty() && !name.isNull())
|
||||
name = QString();
|
||||
|
||||
auto inst = getInstanceById(id);
|
||||
if (!inst) {
|
||||
qDebug() << "Attempt to set a null instance's group";
|
||||
@ -249,6 +253,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
auto iter = m_instanceGroupIndex.find(inst->id());
|
||||
if (iter != m_instanceGroupIndex.end()) {
|
||||
if (*iter != name) {
|
||||
decreaseGroupCount(*iter);
|
||||
*iter = name;
|
||||
changed = true;
|
||||
}
|
||||
@ -258,7 +263,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
m_groupNameCache.insert(name);
|
||||
increaseGroupCount(name);
|
||||
auto idx = getInstIndex(inst.get());
|
||||
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||
saveGroupList();
|
||||
@ -267,29 +272,55 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
|
||||
QStringList InstanceList::getGroups()
|
||||
{
|
||||
return m_groupNameCache.values();
|
||||
return m_groupNameCache.keys();
|
||||
}
|
||||
|
||||
void InstanceList::deleteGroup(const QString& name)
|
||||
void InstanceList::deleteGroup(const GroupId& name)
|
||||
{
|
||||
m_groupNameCache.remove(name);
|
||||
m_collapsedGroups.remove(name);
|
||||
|
||||
bool removed = false;
|
||||
qDebug() << "Delete group" << name;
|
||||
for (auto& instance : m_instances) {
|
||||
const auto& instID = instance->id();
|
||||
auto instGroupName = getInstanceGroup(instID);
|
||||
const QString& instID = instance->id();
|
||||
const QString instGroupName = getInstanceGroup(instID);
|
||||
if (instGroupName == name) {
|
||||
m_instanceGroupIndex.remove(instID);
|
||||
qDebug() << "Remove" << instID << "from group" << name;
|
||||
removed = true;
|
||||
auto idx = getInstIndex(instance.get());
|
||||
if (idx > 0) {
|
||||
if (idx > 0)
|
||||
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removed) {
|
||||
if (removed)
|
||||
saveGroupList();
|
||||
}
|
||||
|
||||
void InstanceList::renameGroup(const QString& src, const QString& dst)
|
||||
{
|
||||
m_groupNameCache.remove(src);
|
||||
if (m_collapsedGroups.remove(src))
|
||||
m_collapsedGroups.insert(dst);
|
||||
|
||||
bool modified = false;
|
||||
qDebug() << "Rename group" << src << "to" << dst;
|
||||
for (auto& instance : m_instances) {
|
||||
const QString& instID = instance->id();
|
||||
const QString instGroupName = getInstanceGroup(instID);
|
||||
if (instGroupName == src) {
|
||||
m_instanceGroupIndex[instID] = dst;
|
||||
increaseGroupCount(dst);
|
||||
qDebug() << "Set" << instID << "group to" << dst;
|
||||
modified = true;
|
||||
auto idx = getInstIndex(instance.get());
|
||||
if (idx > 0)
|
||||
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||
}
|
||||
}
|
||||
if (modified)
|
||||
saveGroupList();
|
||||
}
|
||||
|
||||
bool InstanceList::isGroupCollapsed(const QString& group)
|
||||
@ -305,12 +336,13 @@ bool InstanceList::trashInstance(const InstanceId& id)
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cachedGroupId = m_instanceGroupIndex[id];
|
||||
QString cachedGroupId = m_instanceGroupIndex[id];
|
||||
|
||||
qDebug() << "Will trash instance" << id;
|
||||
QString trashedLoc;
|
||||
|
||||
if (m_instanceGroupIndex.remove(id)) {
|
||||
decreaseGroupCount(cachedGroupId);
|
||||
saveGroupList();
|
||||
}
|
||||
|
||||
@ -348,7 +380,7 @@ void InstanceList::undoTrashInstance()
|
||||
QFile(top.trashPath).rename(top.polyPath);
|
||||
|
||||
m_instanceGroupIndex[top.id] = top.groupName;
|
||||
m_groupNameCache.insert(top.groupName);
|
||||
increaseGroupCount(top.groupName);
|
||||
|
||||
saveGroupList();
|
||||
emit instancesChanged();
|
||||
@ -362,7 +394,10 @@ void InstanceList::deleteInstance(const InstanceId& id)
|
||||
return;
|
||||
}
|
||||
|
||||
QString cachedGroupId = m_instanceGroupIndex[id];
|
||||
|
||||
if (m_instanceGroupIndex.remove(id)) {
|
||||
decreaseGroupCount(cachedGroupId);
|
||||
saveGroupList();
|
||||
}
|
||||
|
||||
@ -610,6 +645,25 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
|
||||
return inst;
|
||||
}
|
||||
|
||||
void InstanceList::increaseGroupCount(const QString& group)
|
||||
{
|
||||
if (group.isEmpty())
|
||||
return;
|
||||
|
||||
++m_groupNameCache[group];
|
||||
}
|
||||
|
||||
void InstanceList::decreaseGroupCount(const QString& group)
|
||||
{
|
||||
if (group.isEmpty())
|
||||
return;
|
||||
|
||||
if (--m_groupNameCache[group] < 1) {
|
||||
m_groupNameCache.remove(group);
|
||||
m_collapsedGroups.remove(group);
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::saveGroupList()
|
||||
{
|
||||
qDebug() << "Will save group list now.";
|
||||
@ -621,7 +675,7 @@ void InstanceList::saveGroupList()
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
QMap<QString, QSet<QString>> reverseGroupMap;
|
||||
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) {
|
||||
QString id = iter.key();
|
||||
const QString& id = iter.key();
|
||||
QString group = iter.value();
|
||||
if (group.isEmpty())
|
||||
continue;
|
||||
@ -711,17 +765,22 @@ void InstanceList::loadGroupList()
|
||||
return;
|
||||
}
|
||||
|
||||
QSet<QString> groupSet;
|
||||
m_instanceGroupIndex.clear();
|
||||
m_groupNameCache.clear();
|
||||
|
||||
// Iterate through all the groups.
|
||||
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
||||
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) {
|
||||
QString groupName = iter.key();
|
||||
|
||||
if (iter.key().isEmpty()) {
|
||||
qWarning() << "Redundant empty group found";
|
||||
continue;
|
||||
}
|
||||
|
||||
// If not an object, complain and skip to the next one.
|
||||
if (!iter.value().isObject()) {
|
||||
qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
|
||||
qWarning() << QString("Group '%1' in the group list should be an object").arg(groupName).toUtf8();
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -733,23 +792,19 @@ void InstanceList::loadGroupList()
|
||||
continue;
|
||||
}
|
||||
|
||||
// keep a list/set of groups for choosing
|
||||
groupSet.insert(groupName);
|
||||
|
||||
auto hidden = groupObj.value("hidden").toBool(false);
|
||||
if (hidden) {
|
||||
if (hidden)
|
||||
m_collapsedGroups.insert(groupName);
|
||||
}
|
||||
|
||||
// Iterate through the list of instances in the group.
|
||||
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
||||
|
||||
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++) {
|
||||
m_instanceGroupIndex[(*iter2).toString()] = groupName;
|
||||
for (auto value : instancesArray) {
|
||||
m_instanceGroupIndex[value.toString()] = groupName;
|
||||
increaseGroupCount(groupName);
|
||||
}
|
||||
}
|
||||
m_groupsLoaded = true;
|
||||
m_groupNameCache.unite(groupSet);
|
||||
qDebug() << "Group list loaded.";
|
||||
}
|
||||
|
||||
@ -925,7 +980,7 @@ bool InstanceList::commitStagedInstance(const QString& path,
|
||||
}
|
||||
|
||||
m_instanceGroupIndex[instID] = groupName;
|
||||
m_groupNameCache.insert(groupName);
|
||||
increaseGroupCount(groupName);
|
||||
}
|
||||
|
||||
instanceSet.insert(instID);
|
||||
|
@ -1,16 +1,36 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is 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.
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* 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.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@ -86,9 +106,10 @@ class InstanceList : public QAbstractListModel {
|
||||
bool isGroupCollapsed(const QString& groupName);
|
||||
|
||||
GroupId getInstanceGroup(const InstanceId& id) const;
|
||||
void setInstanceGroup(const InstanceId& id, const GroupId& name);
|
||||
void setInstanceGroup(const InstanceId& id, GroupId name);
|
||||
|
||||
void deleteGroup(const GroupId& name);
|
||||
void renameGroup(const GroupId& src, const GroupId& dst);
|
||||
bool trashInstance(const InstanceId& id);
|
||||
bool trashedSomething();
|
||||
void undoTrashInstance();
|
||||
@ -158,12 +179,16 @@ class InstanceList : public QAbstractListModel {
|
||||
QList<InstanceId> discoverInstances();
|
||||
InstancePtr loadInstance(const InstanceId& id);
|
||||
|
||||
void increaseGroupCount(const QString& group);
|
||||
void decreaseGroupCount(const QString& group);
|
||||
|
||||
private:
|
||||
int m_watchLevel = 0;
|
||||
int totalPlayTime = 0;
|
||||
bool m_dirty = false;
|
||||
QList<InstancePtr> m_instances;
|
||||
QSet<QString> m_groupNameCache;
|
||||
// id -> refs
|
||||
QMap<QString, int> m_groupNameCache;
|
||||
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
QString m_instDir;
|
||||
|
@ -42,7 +42,11 @@
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
#include <QtConcurrentRun>
|
||||
#endif
|
||||
|
||||
namespace MMCZip {
|
||||
// ours
|
||||
@ -132,6 +136,7 @@ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files,
|
||||
return result;
|
||||
}
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
// ours
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ours
|
||||
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;
|
||||
}
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
void ExportToZipTask::executeTask()
|
||||
{
|
||||
setStatus("Adding files...");
|
||||
@ -606,4 +613,5 @@ bool ExtractZipTask::abort()
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace MMCZip
|
||||
#endif
|
||||
} // namespace MMCZip
|
||||
|
@ -48,7 +48,10 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#endif
|
||||
#include "tasks/Task.h"
|
||||
|
||||
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);
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
/**
|
||||
* take a source jar, add mods to it, resulting in target jar
|
||||
*/
|
||||
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods);
|
||||
|
||||
#endif
|
||||
/**
|
||||
* 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);
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
class ExportToZipTask : public Task {
|
||||
public:
|
||||
ExportToZipTask(QString outputPath, QDir dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
|
||||
@ -214,4 +219,5 @@ class ExtractZipTask : public Task {
|
||||
QFuture<ZipResult> m_zip_future;
|
||||
QFutureWatcher<ZipResult> m_zip_watcher;
|
||||
};
|
||||
#endif
|
||||
} // namespace MMCZip
|
||||
|
@ -35,6 +35,7 @@
|
||||
*/
|
||||
|
||||
#include "StringUtils.h"
|
||||
#include <qpair.h>
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QUuid>
|
||||
@ -149,7 +150,7 @@ QString StringUtils::truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_
|
||||
}
|
||||
|
||||
if ((url_compact.length() >= max_len) && hard_limit) {
|
||||
// still too long, truncate normaly
|
||||
// still too long, truncate normally
|
||||
url_compact = QString(str_url);
|
||||
auto to_remove = url_compact.length() - max_len + 3;
|
||||
url_compact.remove(url_compact.length() - to_remove - 1, to_remove);
|
||||
@ -182,3 +183,32 @@ QString StringUtils::getRandomAlphaNumeric()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
@ -36,8 +36,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <utility>
|
||||
|
||||
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
|
||||
* @param url Url to truncate
|
||||
* @param max_len max lenght of url in charaters
|
||||
* @param hard_limit if truncating the path can't get the url short enough, truncate it normaly.
|
||||
* @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 normally.
|
||||
*/
|
||||
QString truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_limit = false);
|
||||
|
||||
QString humanReadableFileSize(double bytes, bool use_si = false, int decimal_points = 1);
|
||||
|
||||
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
|
||||
|
@ -56,6 +56,7 @@ class Version {
|
||||
bool operator!=(const Version& other) const;
|
||||
|
||||
QString toString() const { return m_string; }
|
||||
bool isEmpty() const { return m_string.isEmpty(); }
|
||||
|
||||
friend QDebug operator<<(QDebug debug, const Version& v);
|
||||
|
||||
|
@ -93,6 +93,7 @@ FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv),
|
||||
joinServer(serverToJoin);
|
||||
} else {
|
||||
qDebug() << "no server to join";
|
||||
m_status = Failed;
|
||||
exit();
|
||||
}
|
||||
}
|
||||
@ -108,6 +109,7 @@ void FileLinkApp::joinServer(QString server)
|
||||
connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
|
||||
|
||||
connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) {
|
||||
m_status = Failed;
|
||||
switch (socketError) {
|
||||
case QLocalSocket::ServerNotFoundError:
|
||||
qDebug()
|
||||
@ -132,6 +134,7 @@ void FileLinkApp::joinServer(QString server)
|
||||
|
||||
connect(&socket, &QLocalSocket::disconnected, this, [&]() {
|
||||
qDebug() << "disconnected from server, should exit";
|
||||
m_status = Succeeded;
|
||||
exit();
|
||||
});
|
||||
|
||||
|
@ -41,8 +41,10 @@ class FileLinkApp : public QCoreApplication {
|
||||
// friends for the purpose of limiting access to deprecated stuff
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Status { Starting, Failed, Succeeded, Initialized };
|
||||
FileLinkApp(int& argc, char** argv);
|
||||
virtual ~FileLinkApp();
|
||||
Status status() const { return m_status; }
|
||||
|
||||
private:
|
||||
void joinServer(QString server);
|
||||
@ -50,6 +52,8 @@ class FileLinkApp : public QCoreApplication {
|
||||
void runLink();
|
||||
void sendResults();
|
||||
|
||||
Status m_status = Status::Starting;
|
||||
|
||||
bool m_useHardLinks = false;
|
||||
|
||||
QDateTime m_startTime;
|
||||
|
@ -26,5 +26,16 @@ int main(int argc, char* 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;
|
||||
}
|
||||
}
|
@ -45,10 +45,12 @@ QString JavaVersion::toString() const
|
||||
|
||||
bool JavaVersion::requiresPermGen()
|
||||
{
|
||||
if (m_parseable) {
|
||||
return m_major < 8;
|
||||
}
|
||||
return true;
|
||||
return !m_parseable || m_major < 8;
|
||||
}
|
||||
|
||||
bool JavaVersion::isModular()
|
||||
{
|
||||
return m_parseable && m_major >= 9;
|
||||
}
|
||||
|
||||
bool JavaVersion::operator<(const JavaVersion& rhs)
|
||||
|
@ -25,6 +25,8 @@ class JavaVersion {
|
||||
|
||||
bool requiresPermGen();
|
||||
|
||||
bool isModular();
|
||||
|
||||
QString toString() const;
|
||||
|
||||
int major() { return m_major; }
|
||||
|
@ -184,6 +184,10 @@ void MinecraftInstance::loadSpecificSettings()
|
||||
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
|
||||
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
|
||||
|
||||
// Legacy-related options
|
||||
auto legacySettings = m_settings->registerSetting("OverrideLegacySettings", false);
|
||||
m_settings->registerOverride(global_settings->getSetting("OnlineFixes"), legacySettings);
|
||||
|
||||
m_settings->set("InstanceType", "OneSix");
|
||||
}
|
||||
|
||||
@ -513,20 +517,28 @@ QStringList MinecraftInstance::javaArguments()
|
||||
|
||||
args << "-Duser.language=en";
|
||||
|
||||
if (javaVersion.isModular() && shouldApplyOnlineFixes())
|
||||
// allow reflective access to java.net - required by the skin fix
|
||||
args << "--add-opens"
|
||||
<< "java.base/java.net=ALL-UNNAMED";
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
QString MinecraftInstance::getLauncher()
|
||||
{
|
||||
auto profile = m_components->getProfile();
|
||||
|
||||
// use legacy launcher if the traits are set
|
||||
if (profile->getTraits().contains("legacyLaunch") || profile->getTraits().contains("alphaLaunch"))
|
||||
if (traits().contains("legacyLaunch") || traits().contains("alphaLaunch"))
|
||||
return "legacy";
|
||||
|
||||
return "standard";
|
||||
}
|
||||
|
||||
bool MinecraftInstance::shouldApplyOnlineFixes()
|
||||
{
|
||||
return traits().contains("legacyServices") && settings()->get("OnlineFixes").toBool();
|
||||
}
|
||||
|
||||
QMap<QString, QString> MinecraftInstance::getVariables()
|
||||
{
|
||||
QMap<QString, QString> out;
|
||||
@ -716,6 +728,9 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
|
||||
launchScript += "traits " + trait + "\n";
|
||||
}
|
||||
|
||||
if (shouldApplyOnlineFixes())
|
||||
launchScript += "onlineFixes true\n";
|
||||
|
||||
launchScript += "launcher " + getLauncher() + "\n";
|
||||
|
||||
// qDebug() << "Generated launch script:" << launchScript;
|
||||
|
@ -129,6 +129,7 @@ class MinecraftInstance : public BaseInstance {
|
||||
/// get arguments passed to java
|
||||
QStringList javaArguments();
|
||||
QString getLauncher();
|
||||
bool shouldApplyOnlineFixes();
|
||||
|
||||
/// get variables for launch command variable substitution/environment
|
||||
QMap<QString, QString> getVariables() override;
|
||||
|
@ -105,6 +105,17 @@ void LauncherPartLaunch::executeTask()
|
||||
auto instance = m_parent->instance();
|
||||
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
|
||||
|
||||
QString legacyJarPath;
|
||||
if (minecraftInstance->getLauncher() == "legacy" || minecraftInstance->shouldApplyOnlineFixes()) {
|
||||
legacyJarPath = APPLICATION->getJarPath("NewLaunchLegacy.jar");
|
||||
if (legacyJarPath.isEmpty()) {
|
||||
const char* reason = QT_TR_NOOP("Legacy launcher library could not be found. Please check your installation.");
|
||||
emit logLine(tr(reason), MessageLevel::Fatal);
|
||||
emitFailed(tr(reason));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_launchScript = minecraftInstance->createLaunchScript(m_session, m_serverToJoin);
|
||||
QStringList args = minecraftInstance->javaArguments();
|
||||
QString allArgs = args.join(", ");
|
||||
@ -120,6 +131,9 @@ void LauncherPartLaunch::executeTask()
|
||||
auto classPath = minecraftInstance->getClassPath();
|
||||
classPath.prepend(jarPath);
|
||||
|
||||
if (!legacyJarPath.isEmpty())
|
||||
classPath.prepend(legacyJarPath);
|
||||
|
||||
auto natPath = minecraftInstance->getNativePath();
|
||||
#ifdef Q_OS_WIN
|
||||
if (!fitsInLocal8bit(natPath)) {
|
||||
|
@ -43,5 +43,5 @@ void ATLauncher::loadIndexedPack(ATLauncher::IndexedPack& m, QJsonObject& obj)
|
||||
m.system = Json::ensureBoolean(obj, QString("system"), false);
|
||||
m.description = Json::ensureString(obj, "description", "");
|
||||
|
||||
m.safeName = Json::requireString(obj, "name").replace(QRegularExpression("[^A-Za-z0-9]"), "");
|
||||
m.safeName = Json::requireString(obj, "name").replace(QRegularExpression("[^A-Za-z0-9]"), "").toLower() + ".png";
|
||||
}
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include "ATLPackInstallTask.h"
|
||||
|
||||
#include <QtConcurrent>
|
||||
#include <algorithm>
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
|
||||
@ -50,6 +51,7 @@
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/OneSixVersionFormat.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "modplatform/atlauncher/ATLPackManifest.h"
|
||||
#include "net/ChecksumValidator.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
|
||||
@ -57,6 +59,7 @@
|
||||
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "ui/dialogs/BlockedModsDialog.h"
|
||||
|
||||
namespace ATLauncher {
|
||||
|
||||
@ -717,6 +720,8 @@ void PackInstallTask::downloadMods()
|
||||
|
||||
jarmods.clear();
|
||||
jobPtr.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
|
||||
|
||||
QList<VersionMod> blocked_mods;
|
||||
for (const auto& mod : m_version.mods) {
|
||||
// skip non-client mods
|
||||
if (!mod.client)
|
||||
@ -731,9 +736,10 @@ void PackInstallTask::downloadMods()
|
||||
case DownloadType::Server:
|
||||
url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url;
|
||||
break;
|
||||
case DownloadType::Browser:
|
||||
emitFailed(tr("Unsupported download type: %1").arg(mod.download_raw));
|
||||
return;
|
||||
case DownloadType::Browser: {
|
||||
blocked_mods.append(mod);
|
||||
continue;
|
||||
}
|
||||
case DownloadType::Direct:
|
||||
url = mod.url;
|
||||
break;
|
||||
@ -805,24 +811,86 @@ void PackInstallTask::downloadMods()
|
||||
modsToCopy[entry->getFullPath()] = path;
|
||||
}
|
||||
}
|
||||
if (!blocked_mods.isEmpty()) {
|
||||
QList<BlockedMod> mods;
|
||||
|
||||
for (auto mod : blocked_mods) {
|
||||
BlockedMod blocked_mod;
|
||||
blocked_mod.name = mod.file;
|
||||
blocked_mod.websiteUrl = mod.url;
|
||||
blocked_mod.hash = mod.md5;
|
||||
blocked_mod.matched = false;
|
||||
blocked_mod.localPath = "";
|
||||
|
||||
mods.append(blocked_mod);
|
||||
}
|
||||
|
||||
qWarning() << "Blocked mods found, displaying mod list";
|
||||
|
||||
BlockedModsDialog message_dialog(nullptr, tr("Blocked mods found"),
|
||||
tr("The following files are not available for download in third party launchers.<br/>"
|
||||
"You will need to manually download them and add them to the instance."),
|
||||
mods, "md5");
|
||||
|
||||
message_dialog.setModal(true);
|
||||
|
||||
if (message_dialog.exec()) {
|
||||
qDebug() << "Post dialog blocked mods list: " << mods;
|
||||
for (auto blocked : mods) {
|
||||
if (!blocked.matched) {
|
||||
qDebug() << blocked.name << "was not matched to a local file, skipping copy";
|
||||
continue;
|
||||
}
|
||||
auto modIter = std::find_if(blocked_mods.begin(), blocked_mods.end(),
|
||||
[blocked](const VersionMod& mod) { return mod.url == blocked.websiteUrl; });
|
||||
if (modIter == blocked_mods.end())
|
||||
continue;
|
||||
auto mod = *modIter;
|
||||
if (mod.type == ModType::Extract || mod.type == ModType::TexturePackExtract || mod.type == ModType::ResourcePackExtract) {
|
||||
modsToExtract.insert(blocked.localPath, mod);
|
||||
} else if (mod.type == ModType::Decomp) {
|
||||
modsToDecomp.insert(blocked.localPath, mod);
|
||||
} else {
|
||||
auto relpath = getDirForModType(mod.type, mod.type_raw);
|
||||
if (relpath == Q_NULLPTR)
|
||||
continue;
|
||||
|
||||
auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file);
|
||||
|
||||
if (mod.type == ModType::Forge) {
|
||||
auto ver = getComponentVersion("net.minecraftforge", mod.version);
|
||||
if (ver) {
|
||||
componentsToInstall.insert("net.minecraftforge", ver);
|
||||
continue;
|
||||
}
|
||||
|
||||
qDebug() << "Jarmod: " + path;
|
||||
jarmods.push_back(path);
|
||||
}
|
||||
|
||||
if (mod.type == ModType::Jar) {
|
||||
qDebug() << "Jarmod: " + path;
|
||||
jarmods.push_back(path);
|
||||
}
|
||||
|
||||
modsToCopy[blocked.localPath] = path;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emitFailed(tr("Unknown download type: %1").arg("browser"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
connect(jobPtr.get(), &NetJob::succeeded, this, &PackInstallTask::onModsDownloaded);
|
||||
connect(jobPtr.get(), &NetJob::failed, [&](QString reason) {
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
|
||||
connect(jobPtr.get(), &NetJob::progress, [this](qint64 current, qint64 total) {
|
||||
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
||||
abortable = true;
|
||||
setProgress(current, total);
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
|
||||
connect(jobPtr.get(), &NetJob::aborted, [&] {
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
emitAborted();
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::aborted, &PackInstallTask::emitAborted);
|
||||
connect(jobPtr.get(), &NetJob::failed, &PackInstallTask::emitFailed);
|
||||
|
||||
jobPtr->start();
|
||||
}
|
||||
@ -843,7 +911,7 @@ void PackInstallTask::onModsDownloaded()
|
||||
QtConcurrent::run(QThreadPool::globalInstance(), this, &PackInstallTask::extractMods, modsToExtract, modsToDecomp, modsToCopy);
|
||||
#endif
|
||||
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onModsExtracted);
|
||||
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]() { emitAborted(); });
|
||||
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::emitAborted);
|
||||
m_modExtractFutureWatcher.setFuture(m_modExtractFuture);
|
||||
} else {
|
||||
install();
|
||||
|
@ -62,6 +62,7 @@
|
||||
#include "minecraft/World.h"
|
||||
#include "minecraft/mod/tasks/LocalResourceParse.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "ui/pages/modplatform/OptionalModDialog.h"
|
||||
|
||||
static const FlameAPI api;
|
||||
|
||||
@ -509,13 +510,33 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
||||
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||
{
|
||||
m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
|
||||
for (const auto& result : m_mod_id_resolver->getResults().files) {
|
||||
QString filename = result.fileName;
|
||||
auto results = m_mod_id_resolver->getResults().files;
|
||||
|
||||
QStringList optionalFiles;
|
||||
for (auto& result : results) {
|
||||
if (!result.required) {
|
||||
filename += ".disabled";
|
||||
optionalFiles << FS::PathCombine(result.targetFolder, result.fileName);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList selectedOptionalMods;
|
||||
if (!optionalFiles.empty()) {
|
||||
OptionalModDialog optionalModDialog(m_parent, optionalFiles);
|
||||
if (optionalModDialog.exec() == QDialog::Rejected) {
|
||||
emitAborted();
|
||||
loop.quit();
|
||||
return;
|
||||
}
|
||||
|
||||
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
|
||||
selectedOptionalMods = optionalModDialog.getResult();
|
||||
}
|
||||
for (const auto& result : results) {
|
||||
auto relpath = FS::PathCombine(result.targetFolder, result.fileName);
|
||||
if (!result.required && !selectedOptionalMods.contains(relpath)) {
|
||||
relpath += ".disabled";
|
||||
}
|
||||
|
||||
relpath = FS::PathCombine("minecraft", relpath);
|
||||
auto path = FS::PathCombine(m_stagingPath, relpath);
|
||||
|
||||
switch (result.type) {
|
||||
@ -547,7 +568,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||
m_mod_id_resolver.reset();
|
||||
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() {
|
||||
m_files_job.reset();
|
||||
validateZIPResouces();
|
||||
validateZIPResources();
|
||||
});
|
||||
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
|
||||
m_files_job.reset();
|
||||
@ -596,7 +617,7 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
|
||||
setAbortable(true);
|
||||
}
|
||||
|
||||
void FlameCreationTask::validateZIPResouces()
|
||||
void FlameCreationTask::validateZIPResources()
|
||||
{
|
||||
qDebug() << "Validating whether resources stored as .zip are in the right place";
|
||||
for (auto [fileName, targetFolder] : m_ZIP_resources) {
|
||||
@ -649,8 +670,8 @@ void FlameCreationTask::validateZIPResouces()
|
||||
validatePath(fileName, targetFolder, "datapacks");
|
||||
break;
|
||||
case PackedResourceType::ShaderPack:
|
||||
// in theroy flame API can't do this but who knows, that *may* change ?
|
||||
// better to handle it if it *does* occure in the future
|
||||
// in theory flame API can't do this but who knows, that *may* change ?
|
||||
// better to handle it if it *does* occur in the future
|
||||
validatePath(fileName, targetFolder, "shaderpacks");
|
||||
break;
|
||||
case PackedResourceType::WorldSave:
|
||||
|
@ -74,7 +74,7 @@ class FlameCreationTask final : public InstanceCreationTask {
|
||||
void idResolverSucceeded(QEventLoop&);
|
||||
void setupDownloadJob(QEventLoop&);
|
||||
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
|
||||
void validateZIPResouces();
|
||||
void validateZIPResources();
|
||||
QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion);
|
||||
|
||||
private:
|
||||
|
@ -1,4 +1,6 @@
|
||||
#include "FlamePackIndex.h"
|
||||
#include <QFileInfo>
|
||||
#include <QUrl>
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
@ -9,8 +11,8 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)
|
||||
pack.description = Json::ensureString(obj, "summary", "");
|
||||
|
||||
auto logo = Json::requireObject(obj, "logo");
|
||||
pack.logoName = Json::requireString(logo, "title");
|
||||
pack.logoUrl = Json::requireString(logo, "thumbnailUrl");
|
||||
pack.logoName = Json::requireString(obj, "slug") + "." + QFileInfo(QUrl(pack.logoUrl).fileName()).suffix();
|
||||
|
||||
auto authors = Json::requireArray(obj, "authors");
|
||||
for (auto authorIter : authors) {
|
||||
|
@ -48,7 +48,7 @@ struct File {
|
||||
|
||||
int projectId = 0;
|
||||
int fileId = 0;
|
||||
// NOTE: the opposite to 'optional'. This is at the time of writing unused.
|
||||
// NOTE: the opposite to 'optional'
|
||||
bool required = true;
|
||||
QString hash;
|
||||
// NOTE: only set on blocked files ! Empty otherwise.
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "modplatform/helpers/OverrideUtils.h"
|
||||
|
||||
#include "modplatform/modrinth/ModrinthPackManifest.h"
|
||||
#include "net/ChecksumValidator.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
@ -16,8 +17,10 @@
|
||||
#include "settings/INISettingsObject.h"
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/pages/modplatform/OptionalModDialog.h"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <vector>
|
||||
|
||||
bool ModrinthCreationTask::abort()
|
||||
{
|
||||
@ -319,10 +322,10 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
|
||||
}
|
||||
|
||||
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
|
||||
bool had_optional = false;
|
||||
std::vector<Modrinth::File> optionalFiles;
|
||||
for (const auto& modInfo : jsonFiles) {
|
||||
Modrinth::File file;
|
||||
file.path = Json::requireString(modInfo, "path");
|
||||
file.path = Json::requireString(modInfo, "path").replace("\\", "/");
|
||||
|
||||
auto env = Json::ensureObject(modInfo, "env");
|
||||
// 'env' field is optional
|
||||
@ -331,18 +334,7 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
|
||||
if (support == "unsupported") {
|
||||
continue;
|
||||
} else if (support == "optional") {
|
||||
// TODO: Make a review dialog for choosing which ones the user wants!
|
||||
if (!had_optional && show_optional_dialog) {
|
||||
had_optional = true;
|
||||
auto info = CustomMessageBox::selectable(
|
||||
m_parent, tr("Optional mod detected!"),
|
||||
tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"),
|
||||
QMessageBox::Information);
|
||||
info->exec();
|
||||
}
|
||||
|
||||
if (file.path.endsWith(".jar"))
|
||||
file.path += ".disabled";
|
||||
file.required = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -385,9 +377,29 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
|
||||
}
|
||||
}
|
||||
|
||||
files.push_back(file);
|
||||
(file.required ? files : optionalFiles).push_back(file);
|
||||
}
|
||||
|
||||
if (!optionalFiles.empty()) {
|
||||
QStringList oFiles;
|
||||
for (auto file : optionalFiles)
|
||||
oFiles.push_back(file.path);
|
||||
OptionalModDialog optionalModDialog(m_parent, oFiles);
|
||||
if (optionalModDialog.exec() == QDialog::Rejected) {
|
||||
emitAborted();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto selectedMods = optionalModDialog.getResult();
|
||||
for (auto file : optionalFiles) {
|
||||
if (selectedMods.contains(file.path)) {
|
||||
file.required = true;
|
||||
} else {
|
||||
file.path += ".disabled";
|
||||
}
|
||||
files.push_back(file);
|
||||
}
|
||||
}
|
||||
if (set_internal_data) {
|
||||
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
||||
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
|
||||
|
@ -35,6 +35,7 @@
|
||||
*/
|
||||
|
||||
#include "ModrinthPackManifest.h"
|
||||
#include <QFileInfo>
|
||||
#include "Json.h"
|
||||
|
||||
#include "modplatform/modrinth/ModrinthAPI.h"
|
||||
@ -56,8 +57,8 @@ void loadIndexedPack(Modpack& pack, QJsonObject& obj)
|
||||
pack.description = Json::ensureString(obj, "description");
|
||||
auto temp_author_name = Json::ensureString(obj, "author");
|
||||
pack.author = std::make_tuple(temp_author_name, api.getAuthorURL(temp_author_name));
|
||||
pack.iconName = QString("modrinth_%1").arg(Json::ensureString(obj, "slug"));
|
||||
pack.iconUrl = Json::ensureString(obj, "icon_url");
|
||||
pack.iconName = QString("modrinth_%1.%2").arg(Json::ensureString(obj, "slug"), QFileInfo(pack.iconUrl.fileName()).suffix());
|
||||
}
|
||||
|
||||
void loadIndexedInfo(Modpack& pack, QJsonObject& obj)
|
||||
@ -111,9 +112,8 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
|
||||
unsortedVersions.append(file);
|
||||
}
|
||||
auto orderSortPredicate = [](const ModpackVersion& a, const ModpackVersion& b) -> bool {
|
||||
bool a_better_release = a.version_type <= b.version_type;
|
||||
// dates are in RFC 3339 format
|
||||
return a.date > b.date && a_better_release;
|
||||
return a.date > b.date;
|
||||
};
|
||||
|
||||
std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
|
||||
|
@ -57,6 +57,7 @@ struct File {
|
||||
QCryptographicHash::Algorithm hashAlgorithm;
|
||||
QByteArray hash;
|
||||
QQueue<QUrl> downloads;
|
||||
bool required = true;
|
||||
};
|
||||
|
||||
struct DonationData {
|
||||
|
@ -89,4 +89,4 @@ QNetworkReply* Download::getReply(QNetworkRequest& request)
|
||||
{
|
||||
return m_network->get(request);
|
||||
}
|
||||
} // namespace Net
|
||||
} // namespace Net
|
||||
|
@ -36,11 +36,16 @@
|
||||
*/
|
||||
|
||||
#include "NetJob.h"
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
#include "Application.h"
|
||||
#endif
|
||||
|
||||
NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network)
|
||||
: ConcurrentTask(nullptr, job_name, APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()), m_network(network)
|
||||
{}
|
||||
NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network) : ConcurrentTask(nullptr, job_name), m_network(network)
|
||||
{
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
|
||||
#endif
|
||||
}
|
||||
|
||||
auto NetJob::addNetAction(NetAction::Ptr action) -> bool
|
||||
{
|
||||
|
@ -46,6 +46,7 @@
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
#include "Application.h"
|
||||
#endif
|
||||
#include "BuildConfig.h"
|
||||
|
||||
#include "net/NetAction.h"
|
||||
|
||||
|
@ -206,7 +206,7 @@ void TranslationsModel::indexReceived()
|
||||
reloadLocalFiles();
|
||||
|
||||
auto language = d->m_system_locale;
|
||||
if (!findLanguage(language)) {
|
||||
if (!findLanguageAsOptional(language).has_value()) {
|
||||
language = d->m_system_language;
|
||||
}
|
||||
selectLanguage(language);
|
||||
@ -417,14 +417,17 @@ int TranslationsModel::columnCount([[maybe_unused]] const QModelIndex& parent) c
|
||||
return 2;
|
||||
}
|
||||
|
||||
Language* TranslationsModel::findLanguage(const QString& key)
|
||||
QVector<Language>::Iterator TranslationsModel::findLanguage(const QString& key)
|
||||
{
|
||||
auto found = std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language& lang) { return lang.key == key; });
|
||||
if (found == d->m_languages.end()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return found;
|
||||
}
|
||||
return std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language& lang) { return lang.key == key; });
|
||||
}
|
||||
|
||||
std::optional<Language> TranslationsModel::findLanguageAsOptional(const QString& key)
|
||||
{
|
||||
auto found = findLanguage(key);
|
||||
if (found != d->m_languages.end())
|
||||
return *found;
|
||||
return {};
|
||||
}
|
||||
|
||||
void TranslationsModel::setUseSystemLocale(bool useSystemLocale)
|
||||
@ -436,13 +439,13 @@ void TranslationsModel::setUseSystemLocale(bool useSystemLocale)
|
||||
bool TranslationsModel::selectLanguage(QString key)
|
||||
{
|
||||
QString& langCode = key;
|
||||
auto langPtr = findLanguage(key);
|
||||
auto langPtr = findLanguageAsOptional(key);
|
||||
|
||||
if (langCode.isEmpty()) {
|
||||
d->no_language_set = true;
|
||||
}
|
||||
|
||||
if (!langPtr) {
|
||||
if (!langPtr.has_value()) {
|
||||
qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode;
|
||||
langCode = defaultLangCode;
|
||||
} else {
|
||||
@ -527,9 +530,8 @@ bool TranslationsModel::selectLanguage(QString key)
|
||||
QModelIndex TranslationsModel::selectedIndex()
|
||||
{
|
||||
auto found = findLanguage(d->m_selectedLanguage);
|
||||
if (found) {
|
||||
// QVector iterator freely converts to pointer to contained type
|
||||
return index(found - d->m_languages.begin(), 0, QModelIndex());
|
||||
if (found != d->m_languages.end()) {
|
||||
return index(std::distance(d->m_languages.begin(), found), 0, QModelIndex());
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
@ -562,8 +564,8 @@ void TranslationsModel::updateLanguage(QString key)
|
||||
qWarning() << "Cannot update builtin language" << key;
|
||||
return;
|
||||
}
|
||||
auto found = findLanguage(key);
|
||||
if (!found) {
|
||||
auto found = findLanguageAsOptional(key);
|
||||
if (!found.has_value()) {
|
||||
qWarning() << "Cannot update invalid language" << key;
|
||||
return;
|
||||
}
|
||||
@ -578,8 +580,8 @@ void TranslationsModel::downloadTranslation(QString key)
|
||||
d->m_nextDownload = key;
|
||||
return;
|
||||
}
|
||||
auto lang = findLanguage(key);
|
||||
if (!lang) {
|
||||
auto lang = findLanguageAsOptional(key);
|
||||
if (!lang.has_value()) {
|
||||
qWarning() << "Will not download an unknown translation" << key;
|
||||
return;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
struct Language;
|
||||
|
||||
@ -40,7 +41,8 @@ class TranslationsModel : public QAbstractListModel {
|
||||
void setUseSystemLocale(bool useSystemLocale);
|
||||
|
||||
private:
|
||||
Language* findLanguage(const QString& key);
|
||||
QVector<Language>::Iterator findLanguage(const QString& key);
|
||||
std::optional<Language> findLanguageAsOptional(const QString& key);
|
||||
void reloadLocalFiles();
|
||||
void downloadTranslation(QString key);
|
||||
void downloadNext();
|
||||
|
@ -219,7 +219,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
ui->actionDISCORD->setVisible(!BuildConfig.DISCORD_URL.isEmpty());
|
||||
ui->actionREDDIT->setVisible(!BuildConfig.SUBREDDIT_URL.isEmpty());
|
||||
|
||||
ui->actionCheckUpdate->setVisible(BuildConfig.UPDATER_ENABLED);
|
||||
ui->actionCheckUpdate->setVisible(APPLICATION->updaterEnabled());
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
ui->actionAddToPATH->setVisible(false);
|
||||
@ -377,7 +377,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
updateNewsLabel();
|
||||
}
|
||||
|
||||
if (BuildConfig.UPDATER_ENABLED) {
|
||||
if (APPLICATION->updaterEnabled()) {
|
||||
bool updatesAllowed = APPLICATION->updatesAreAllowed();
|
||||
updatesAllowedChanged(updatesAllowed);
|
||||
|
||||
@ -514,10 +514,10 @@ void MainWindow::showInstanceContextMenu(const QPoint& pos)
|
||||
} else {
|
||||
auto group = view->groupNameAt(pos);
|
||||
|
||||
QAction* actionVoid = new QAction(BuildConfig.LAUNCHER_DISPLAYNAME, this);
|
||||
QAction* actionVoid = new QAction(group.isNull() ? BuildConfig.LAUNCHER_DISPLAYNAME : group, this);
|
||||
actionVoid->setEnabled(false);
|
||||
|
||||
QAction* actionCreateInstance = new QAction(tr("Create instance"), this);
|
||||
QAction* actionCreateInstance = new QAction(tr("&Create instance"), this);
|
||||
actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip());
|
||||
if (!group.isNull()) {
|
||||
QVariantMap instance_action_data;
|
||||
@ -531,12 +531,13 @@ void MainWindow::showInstanceContextMenu(const QPoint& pos)
|
||||
actions.prepend(actionVoid);
|
||||
actions.append(actionCreateInstance);
|
||||
if (!group.isNull()) {
|
||||
QAction* actionDeleteGroup = new QAction(tr("Delete group '%1'").arg(group), this);
|
||||
QVariantMap delete_group_action_data;
|
||||
delete_group_action_data["group"] = group;
|
||||
actionDeleteGroup->setData(delete_group_action_data);
|
||||
connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup()));
|
||||
QAction* actionDeleteGroup = new QAction(tr("&Delete group"), this);
|
||||
connect(actionDeleteGroup, &QAction::triggered, this, [this, group] { deleteGroup(group); });
|
||||
actions.append(actionDeleteGroup);
|
||||
|
||||
QAction* actionRenameGroup = new QAction(tr("&Rename group"), this);
|
||||
connect(actionRenameGroup, &QAction::triggered, this, [this, group] { renameGroup(group); });
|
||||
actions.append(actionRenameGroup);
|
||||
}
|
||||
}
|
||||
QMenu myMenu;
|
||||
@ -676,7 +677,7 @@ void MainWindow::repopulateAccountsMenu()
|
||||
|
||||
void MainWindow::updatesAllowedChanged(bool allowed)
|
||||
{
|
||||
if (!BuildConfig.UPDATER_ENABLED) {
|
||||
if (!APPLICATION->updaterEnabled()) {
|
||||
return;
|
||||
}
|
||||
ui->actionCheckUpdate->setEnabled(allowed);
|
||||
@ -1128,40 +1129,49 @@ void MainWindow::on_actionChangeInstGroup_triggered()
|
||||
if (!m_selectedInstance)
|
||||
return;
|
||||
|
||||
bool ok = false;
|
||||
InstanceId instId = m_selectedInstance->id();
|
||||
QString name(APPLICATION->instances()->getInstanceGroup(instId));
|
||||
auto groups = APPLICATION->instances()->getGroups();
|
||||
groups.insert(0, "");
|
||||
groups.sort(Qt::CaseInsensitive);
|
||||
int foo = groups.indexOf(name);
|
||||
QString src(APPLICATION->instances()->getInstanceGroup(instId));
|
||||
|
||||
QStringList groups = APPLICATION->instances()->getGroups();
|
||||
groups.prepend("");
|
||||
int index = groups.indexOf(src);
|
||||
bool ok = false;
|
||||
QString dst = QInputDialog::getItem(this, tr("Group name"), tr("Enter a new group name."), groups, index, true, &ok);
|
||||
dst = dst.simplified();
|
||||
|
||||
name = QInputDialog::getItem(this, tr("Group name"), tr("Enter a new group name."), groups, foo, true, &ok);
|
||||
name = name.simplified();
|
||||
if (ok) {
|
||||
APPLICATION->instances()->setInstanceGroup(instId, name);
|
||||
APPLICATION->instances()->setInstanceGroup(instId, dst);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::deleteGroup()
|
||||
void MainWindow::deleteGroup(QString group)
|
||||
{
|
||||
QObject* obj = sender();
|
||||
if (!obj)
|
||||
Q_ASSERT(!group.isEmpty());
|
||||
|
||||
const int reply = QMessageBox::question(this, tr("Delete group"), tr("Are you sure you want to delete the group '%1'?").arg(group),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (reply == QMessageBox::Yes)
|
||||
APPLICATION->instances()->deleteGroup(group);
|
||||
}
|
||||
|
||||
void MainWindow::renameGroup(QString group)
|
||||
{
|
||||
Q_ASSERT(!group.isEmpty());
|
||||
|
||||
QString name = QInputDialog::getText(this, tr("Rename group"), tr("Enter a new group name."), QLineEdit::Normal, group);
|
||||
name = name.simplified();
|
||||
if (name.isNull() || name == group)
|
||||
return;
|
||||
QAction* action = qobject_cast<QAction*>(obj);
|
||||
if (!action)
|
||||
|
||||
const bool empty = name.isEmpty();
|
||||
const bool duplicate = APPLICATION->instances()->getGroups().contains(name, Qt::CaseInsensitive) && group.toLower() != name.toLower();
|
||||
|
||||
if (empty || duplicate) {
|
||||
QMessageBox::warning(this, tr("Cannot rename group"), empty ? tr("Cannot set empty name.") : tr("Group already exists. :/"));
|
||||
return;
|
||||
auto map = action->data().toMap();
|
||||
if (!map.contains("group"))
|
||||
return;
|
||||
QString groupName = map["group"].toString();
|
||||
if (!groupName.isEmpty()) {
|
||||
auto reply = QMessageBox::question(this, tr("Delete group"), tr("Are you sure you want to delete the group %1?").arg(groupName),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (reply == QMessageBox::Yes) {
|
||||
APPLICATION->instances()->deleteGroup(groupName);
|
||||
}
|
||||
}
|
||||
|
||||
APPLICATION->instances()->renameGroup(group, name);
|
||||
}
|
||||
|
||||
void MainWindow::undoTrashInstance()
|
||||
@ -1208,7 +1218,7 @@ void MainWindow::refreshInstances()
|
||||
|
||||
void MainWindow::checkForUpdates()
|
||||
{
|
||||
if (BuildConfig.UPDATER_ENABLED) {
|
||||
if (APPLICATION->updaterEnabled()) {
|
||||
APPLICATION->triggerUpdateCheck();
|
||||
} else {
|
||||
qWarning() << "Updater not set up. Cannot check for updates.";
|
||||
|
@ -148,7 +148,8 @@ class MainWindow : public QMainWindow {
|
||||
|
||||
void on_actionDeleteInstance_triggered();
|
||||
|
||||
void deleteGroup();
|
||||
void deleteGroup(QString group);
|
||||
void renameGroup(QString group);
|
||||
void undoTrashInstance();
|
||||
|
||||
inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); }
|
||||
|
@ -41,8 +41,8 @@
|
||||
#include <QPushButton>
|
||||
#include <QStandardPaths>
|
||||
|
||||
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods)
|
||||
: QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods)
|
||||
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods, QString hash_type)
|
||||
: QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods), m_hash_type(hash_type)
|
||||
{
|
||||
m_hashing_task = shared_qobject_ptr<ConcurrentTask>(
|
||||
new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
|
||||
@ -255,7 +255,7 @@ void BlockedModsDialog::addHashTask(QString path)
|
||||
/// @param path the path to the local file being hashed
|
||||
void BlockedModsDialog::buildHashTask(QString path)
|
||||
{
|
||||
auto hash_task = Hashing::createBlockedModHasher(path, ModPlatform::ResourceProvider::FLAME, "sha1");
|
||||
auto hash_task = Hashing::createBlockedModHasher(path, ModPlatform::ResourceProvider::FLAME, m_hash_type);
|
||||
|
||||
qDebug() << "[Blocked Mods Dialog] Creating Hash task for path: " << path;
|
||||
|
||||
@ -335,6 +335,13 @@ bool BlockedModsDialog::checkValidPath(QString path)
|
||||
|
||||
for (auto& mod : m_mods) {
|
||||
if (compare(filename, mod.name)) {
|
||||
// if the mod is not yet matched and doesn't have a hash then
|
||||
// just match it with the file that has the exact same name
|
||||
if (!mod.matched && mod.hash.isEmpty()) {
|
||||
mod.matched = true;
|
||||
mod.localPath = path;
|
||||
return false;
|
||||
}
|
||||
qDebug() << "[Blocked Mods Dialog] Name match found:" << mod.name << "| From path:" << path;
|
||||
return true;
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ class BlockedModsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods);
|
||||
BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods, QString hash_type = "sha1");
|
||||
|
||||
~BlockedModsDialog() override;
|
||||
|
||||
@ -73,6 +73,7 @@ class BlockedModsDialog : public QDialog {
|
||||
QSet<QString> m_pending_hash_paths;
|
||||
bool m_rehash_pending;
|
||||
QPushButton* m_openMissingButton;
|
||||
QString m_hash_type;
|
||||
|
||||
void openAll(bool missingOnly);
|
||||
void addDownloadFolder();
|
||||
|
@ -2,6 +2,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -61,22 +62,14 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget* parent)
|
||||
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
|
||||
ui->instNameTextBox->setText(original->name());
|
||||
ui->instNameTextBox->setFocus();
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
auto groupList = APPLICATION->instances()->getGroups();
|
||||
QSet<QString> groups(groupList.begin(), groupList.end());
|
||||
groupList = QStringList(groups.values());
|
||||
#else
|
||||
auto groups = APPLICATION->instances()->getGroups().toSet();
|
||||
auto groupList = QStringList(groups.toList());
|
||||
#endif
|
||||
groupList.sort(Qt::CaseInsensitive);
|
||||
groupList.removeOne("");
|
||||
groupList.push_front("");
|
||||
ui->groupBox->addItems(groupList);
|
||||
int index = groupList.indexOf(APPLICATION->instances()->getInstanceGroup(m_original->id()));
|
||||
if (index == -1) {
|
||||
|
||||
QStringList groups = APPLICATION->instances()->getGroups();
|
||||
groups.prepend("");
|
||||
ui->groupBox->addItems(groups);
|
||||
int index = groups.indexOf(APPLICATION->instances()->getInstanceGroup(m_original->id()));
|
||||
if (index == -1)
|
||||
index = 0;
|
||||
}
|
||||
|
||||
ui->groupBox->setCurrentIndex(index);
|
||||
ui->groupBox->lineEdit()->setPlaceholderText(tr("No group"));
|
||||
ui->copySavesCheckbox->setChecked(m_selectedOptions.isCopySavesEnabled());
|
||||
|
@ -2,6 +2,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -75,23 +76,14 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
|
||||
InstIconKey = "default";
|
||||
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
auto groupList = APPLICATION->instances()->getGroups();
|
||||
auto groups = QSet<QString>(groupList.begin(), groupList.end());
|
||||
groupList = groups.values();
|
||||
#else
|
||||
auto groups = APPLICATION->instances()->getGroups().toSet();
|
||||
auto groupList = QStringList(groups.toList());
|
||||
#endif
|
||||
groupList.sort(Qt::CaseInsensitive);
|
||||
groupList.removeOne("");
|
||||
groupList.push_front(initialGroup);
|
||||
groupList.push_front("");
|
||||
ui->groupBox->addItems(groupList);
|
||||
int index = groupList.indexOf(initialGroup);
|
||||
QStringList groups = APPLICATION->instances()->getGroups();
|
||||
groups.prepend("");
|
||||
int index = groups.indexOf(initialGroup);
|
||||
if (index == -1) {
|
||||
index = 0;
|
||||
index = 1;
|
||||
groups.insert(index, initialGroup);
|
||||
}
|
||||
ui->groupBox->addItems(groups);
|
||||
ui->groupBox->setCurrentIndex(index);
|
||||
ui->groupBox->lineEdit()->setPlaceholderText(tr("No group"));
|
||||
|
||||
@ -237,8 +229,7 @@ void NewInstanceDialog::setSuggestedIcon(const QString& key)
|
||||
|
||||
InstanceTask* NewInstanceDialog::extractTask()
|
||||
{
|
||||
InstanceTask* extracted = creationTask.get();
|
||||
creationTask.release();
|
||||
InstanceTask* extracted = creationTask.release();
|
||||
|
||||
InstanceName inst_name(ui->instNameTextBox->placeholderText().trimmed(), importVersion);
|
||||
inst_name.setName(ui->instNameTextBox->text().trimmed());
|
||||
|
@ -48,6 +48,9 @@
|
||||
<property name="text">
|
||||
<string>Global Task Status...</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@ -109,8 +112,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>464</width>
|
||||
<height>96</height>
|
||||
<width>460</width>
|
||||
<height>108</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="taskProgressLayout">
|
||||
|
63
launcher/ui/dialogs/UpdateAvailableDialog.cpp
Normal file
63
launcher/ui/dialogs/UpdateAvailableDialog.cpp
Normal 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);
|
||||
});
|
||||
}
|
48
launcher/ui/dialogs/UpdateAvailableDialog.h
Normal file
48
launcher/ui/dialogs/UpdateAvailableDialog.h
Normal 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;
|
||||
};
|
155
launcher/ui/dialogs/UpdateAvailableDialog.ui
Normal file
155
launcher/ui/dialogs/UpdateAvailableDialog.ui
Normal 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>
|
@ -119,6 +119,9 @@ void MinecraftPage::applySettings()
|
||||
// Miscellaneous
|
||||
s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked());
|
||||
s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked());
|
||||
|
||||
// Legacy settings
|
||||
s->set("OnlineFixes", ui->onlineFixes->isChecked());
|
||||
}
|
||||
|
||||
void MinecraftPage::loadSettings()
|
||||
@ -170,6 +173,8 @@ void MinecraftPage::loadSettings()
|
||||
|
||||
ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool());
|
||||
ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool());
|
||||
|
||||
ui->onlineFixes->setChecked(s->get("OnlineFixes").toBool());
|
||||
}
|
||||
|
||||
void MinecraftPage::retranslate()
|
||||
|
@ -138,7 +138,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showGameTimeWithoutDays">
|
||||
<property name="text">
|
||||
<string>Show time spent playing in hours</string>
|
||||
@ -197,6 +197,25 @@
|
||||
<string>Tweaks</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_12">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="legacySettingsGroupBox">
|
||||
<property name="title">
|
||||
<string>Legacy settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="onlineFixes">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Emulates usages of old online services which are no longer operating.</p><p>This currently allows modern skins to be used.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable online fixes (experimental)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
|
||||
<property name="title">
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -253,6 +254,14 @@ void InstanceSettingsPage::applySettings()
|
||||
m_settings->reset("InstanceAccountId");
|
||||
}
|
||||
|
||||
bool overrideLegacySettings = ui->legacySettingsGroupBox->isChecked();
|
||||
m_settings->set("OverrideLegacySettings", overrideLegacySettings);
|
||||
if (overrideLegacySettings) {
|
||||
m_settings->set("OnlineFixes", ui->onlineFixes->isChecked());
|
||||
} else {
|
||||
m_settings->reset("OnlineFixes");
|
||||
}
|
||||
|
||||
// FIXME: This should probably be called by a signal instead
|
||||
m_instance->updateRuntimeContext();
|
||||
}
|
||||
@ -356,6 +365,9 @@ void InstanceSettingsPage::loadSettings()
|
||||
|
||||
ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool());
|
||||
updateAccountsMenu();
|
||||
|
||||
ui->legacySettingsGroupBox->setChecked(m_settings->get("OverrideLegacySettings").toBool());
|
||||
ui->onlineFixes->setChecked(m_settings->get("OnlineFixes").toBool());
|
||||
}
|
||||
|
||||
void InstanceSettingsPage::on_javaDetectBtn_clicked()
|
||||
|
@ -583,6 +583,31 @@
|
||||
<string>Miscellaneous</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="legacySettingsGroupBox">
|
||||
<property name="title">
|
||||
<string>Legacy settings</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_17">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="onlineFixes">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Emulates usages of old online services which are no longer operating.</p><p>This currently allows modern skins to be used.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable online fixes (experimental)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gameTimeGroupBox">
|
||||
<property name="enabled">
|
||||
|
63
launcher/ui/pages/modplatform/OptionalModDialog.cpp
Normal file
63
launcher/ui/pages/modplatform/OptionalModDialog.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "OptionalModDialog.h"
|
||||
#include "ui_OptionalModDialog.h"
|
||||
|
||||
OptionalModDialog::OptionalModDialog(QWidget* parent, const QStringList& mods) : QDialog(parent), ui(new Ui::OptionalModDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
for (const QString& mod : mods) {
|
||||
auto item = new QListWidgetItem(mod, ui->list);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
item->setData(Qt::UserRole, mod);
|
||||
}
|
||||
|
||||
connect(ui->selectAllButton, &QPushButton::clicked, ui->list, [this] {
|
||||
for (int i = 0; i < ui->list->count(); i++)
|
||||
ui->list->item(i)->setCheckState(Qt::Checked);
|
||||
});
|
||||
connect(ui->clearAllButton, &QPushButton::clicked, ui->list, [this] {
|
||||
for (int i = 0; i < ui->list->count(); i++)
|
||||
ui->list->item(i)->setCheckState(Qt::Unchecked);
|
||||
});
|
||||
connect(ui->list, &QListWidget::itemActivated, [](QListWidgetItem* item) {
|
||||
if (item->checkState() == Qt::Checked)
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
else
|
||||
item->setCheckState(Qt::Checked);
|
||||
});
|
||||
}
|
||||
|
||||
OptionalModDialog::~OptionalModDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
QStringList OptionalModDialog::getResult()
|
||||
{
|
||||
QStringList result;
|
||||
result.reserve(ui->list->count());
|
||||
for (int i = 0; i < ui->list->count(); i++) {
|
||||
auto item = ui->list->item(i);
|
||||
if (item->checkState() == Qt::Checked)
|
||||
result.append(item->data(Qt::UserRole).toString());
|
||||
}
|
||||
return result;
|
||||
}
|
39
launcher/ui/pages/modplatform/OptionalModDialog.h
Normal file
39
launcher/ui/pages/modplatform/OptionalModDialog.h
Normal file
@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class OptionalModDialog;
|
||||
}
|
||||
|
||||
class OptionalModDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OptionalModDialog(QWidget* parent, const QStringList& mods);
|
||||
~OptionalModDialog() override;
|
||||
|
||||
QStringList getResult();
|
||||
|
||||
private:
|
||||
Ui::OptionalModDialog* ui;
|
||||
};
|
113
launcher/ui/pages/modplatform/OptionalModDialog.ui
Normal file
113
launcher/ui/pages/modplatform/OptionalModDialog.ui
Normal file
@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OptionalModDialog</class>
|
||||
<widget class="QDialog" name="OptionalModDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>550</width>
|
||||
<height>310</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Select Optional Mods</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QListWidget" name="list">
|
||||
<property name="defaultDropAction">
|
||||
<enum>Qt::IgnoreAction</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="selectAllButton">
|
||||
<property name="text">
|
||||
<string>Select All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="clearAllButton">
|
||||
<property name="text">
|
||||
<string>Deselect All</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="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Unchecked mods will be disabled.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>OptionalModDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>274</x>
|
||||
<y>284</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>274</x>
|
||||
<y>154</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>OptionalModDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>274</x>
|
||||
<y>284</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>274</x>
|
||||
<y>154</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -63,7 +63,7 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
|
||||
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1").arg(pack.safeName);
|
||||
((ListModel*)this)->requestLogo(pack.safeName, url);
|
||||
|
||||
return icon;
|
||||
|
@ -38,7 +38,7 @@
|
||||
#include <QAbstractListModel>
|
||||
#include <QDialog>
|
||||
|
||||
#include "modplatform/atlauncher/ATLPackIndex.h"
|
||||
#include "modplatform/atlauncher/ATLPackManifest.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
namespace Ui {
|
||||
|
@ -114,8 +114,8 @@ void AtlPage::suggestCurrent()
|
||||
auto uiSupport = new AtlUserInteractionSupportImpl(this);
|
||||
dialog->setSuggestedPack(selected.name, selectedVersion, new ATLauncher::PackInstallTask(uiSupport, selected.name, selectedVersion));
|
||||
|
||||
auto editedLogoName = selected.safeName;
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower());
|
||||
auto editedLogoName = "atl_" + selected.safeName;
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1").arg(selected.safeName);
|
||||
listModel->getLogo(selected.safeName, url,
|
||||
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
|
||||
}
|
||||
|
@ -228,8 +228,7 @@ void FlamePage::suggestCurrent()
|
||||
extra_info.insert("pack_version_id", QString::number(version.fileId));
|
||||
|
||||
dialog->setSuggestedPack(current.name, new InstanceImportTask(version.downloadUrl, this, std::move(extra_info)));
|
||||
QString editedLogoName;
|
||||
editedLogoName = "curseforge_" + current.logoName;
|
||||
QString editedLogoName = "curseforge_" + current.logoName;
|
||||
listModel->getLogo(current.logoName, current.logoUrl,
|
||||
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ void ImportFTBPage::suggestCurrent()
|
||||
}
|
||||
|
||||
dialog->setSuggestedPack(selected.name, new PackInstallTask(selected));
|
||||
QString editedLogoName = QString("ftb_%1").arg(selected.id);
|
||||
QString editedLogoName = QString("ftb_%1_%2,jpg").arg(selected.name, selected.id);
|
||||
dialog->setSuggestedIconFromFile(FS::PathCombine(selected.path, "folder.jpg"), editedLogoName);
|
||||
}
|
||||
|
||||
|
@ -179,15 +179,11 @@ void Page::suggestCurrent()
|
||||
}
|
||||
|
||||
dialog->setSuggestedPack(selected.name, selectedVersion, new PackInstallTask(APPLICATION->network(), selected, selectedVersion));
|
||||
QString editedLogoName;
|
||||
if (selected.logo.toLower().startsWith("ftb")) {
|
||||
editedLogoName = selected.logo;
|
||||
} else {
|
||||
editedLogoName = "ftb_" + selected.logo;
|
||||
QString editedLogoName = selected.logo;
|
||||
if (!selected.logo.toLower().startsWith("ftb")) {
|
||||
editedLogoName = "ftb_" + editedLogoName;
|
||||
}
|
||||
|
||||
editedLogoName = editedLogoName.left(editedLogoName.lastIndexOf(".png"));
|
||||
|
||||
if (selected.type == PackType::Public) {
|
||||
publicListModel->getLogo(selected.logo,
|
||||
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
|
||||
|
@ -41,7 +41,9 @@
|
||||
#include "net/ApiDownload.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QIcon>
|
||||
#include <QUrl>
|
||||
|
||||
Technic::ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
|
||||
|
||||
@ -193,7 +195,7 @@ void Technic::ListModel::searchRequestFinished()
|
||||
pack.logoName = "null";
|
||||
} else {
|
||||
pack.logoUrl = rawURL;
|
||||
pack.logoName = rawURL.section(QLatin1Char('/'), -1);
|
||||
pack.logoName = pack.slug + "." + QFileInfo(QUrl(rawURL).fileName()).suffix();
|
||||
}
|
||||
pack.broken = false;
|
||||
newList.append(pack);
|
||||
@ -215,7 +217,7 @@ void Technic::ListModel::searchRequestFinished()
|
||||
auto iconUrl = Json::requireString(iconObj, "url");
|
||||
|
||||
pack.logoUrl = iconUrl;
|
||||
pack.logoName = iconUrl.section(QLatin1Char('/'), -1);
|
||||
pack.logoName = pack.slug + "." + QFileInfo(QUrl(iconUrl).fileName()).suffix();
|
||||
} else {
|
||||
pack.logoUrl = "null";
|
||||
pack.logoName = "null";
|
||||
|
354
launcher/updater/PrismExternalUpdater.cpp
Normal file
354
launcher/updater/PrismExternalUpdater.cpp
Normal 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();
|
||||
}
|
95
launcher/updater/PrismExternalUpdater.h
Normal file
95
launcher/updater/PrismExternalUpdater.h
Normal 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 property’s 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 property’s 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;
|
||||
};
|
93
launcher/updater/prismupdater/GitHubRelease.cpp
Normal file
93
launcher/updater/prismupdater/GitHubRelease.cpp
Normal 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;
|
||||
}
|
61
launcher/updater/prismupdater/GitHubRelease.h
Normal file
61
launcher/updater/prismupdater/GitHubRelease.h
Normal 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);
|
1401
launcher/updater/prismupdater/PrismUpdater.cpp
Normal file
1401
launcher/updater/prismupdater/PrismUpdater.cpp
Normal file
File diff suppressed because it is too large
Load Diff
143
launcher/updater/prismupdater/PrismUpdater.h
Normal file
143
launcher/updater/prismupdater/PrismUpdater.h
Normal 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
|
||||
};
|
89
launcher/updater/prismupdater/SelectReleaseDialog.ui
Normal file
89
launcher/updater/prismupdater/SelectReleaseDialog.ui
Normal 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>
|
168
launcher/updater/prismupdater/UpdaterDialogs.cpp
Normal file
168
launcher/updater/prismupdater/UpdaterDialogs.cpp
Normal 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;
|
||||
}
|
75
launcher/updater/prismupdater/UpdaterDialogs.h
Normal file
75
launcher/updater/prismupdater/UpdaterDialogs.h
Normal 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;
|
||||
};
|
26
launcher/updater/prismupdater/updater.exe.manifest
Normal file
26
launcher/updater/prismupdater/updater.exe.manifest
Normal 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>
|
40
launcher/updater/prismupdater/updater_main.cpp
Normal file
40
launcher/updater/prismupdater/updater_main.cpp
Normal 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;
|
||||
}
|
||||
}
|
@ -11,15 +11,33 @@ set(SRC
|
||||
org/prismlauncher/launcher/Launcher.java
|
||||
org/prismlauncher/launcher/impl/AbstractLauncher.java
|
||||
org/prismlauncher/launcher/impl/StandardLauncher.java
|
||||
org/prismlauncher/launcher/impl/legacy/LegacyLauncher.java
|
||||
org/prismlauncher/launcher/impl/legacy/LegacyFrame.java
|
||||
org/prismlauncher/exception/ParameterNotFoundException.java
|
||||
org/prismlauncher/exception/ParseException.java
|
||||
org/prismlauncher/utils/Parameters.java
|
||||
org/prismlauncher/utils/ReflectionUtils.java
|
||||
org/prismlauncher/utils/logging/Level.java
|
||||
org/prismlauncher/utils/logging/Log.java
|
||||
net/minecraft/Launcher.java
|
||||
org/prismlauncher/legacy/LegacyProxy.java
|
||||
)
|
||||
|
||||
set(LEGACY_SRC
|
||||
legacy/org/prismlauncher/legacy/LegacyFrame.java
|
||||
legacy/org/prismlauncher/legacy/LegacyLauncher.java
|
||||
legacy/org/prismlauncher/legacy/fix/online/Handler.java
|
||||
legacy/org/prismlauncher/legacy/fix/online/OnlineFixes.java
|
||||
legacy/org/prismlauncher/legacy/fix/online/SkinFix.java
|
||||
legacy/org/prismlauncher/legacy/utils/Base64.java
|
||||
legacy/org/prismlauncher/legacy/utils/api/MojangApi.java
|
||||
legacy/org/prismlauncher/legacy/utils/api/Texture.java
|
||||
legacy/org/prismlauncher/legacy/utils/json/JsonParseException.java
|
||||
legacy/org/prismlauncher/legacy/utils/json/JsonParser.java
|
||||
legacy/org/prismlauncher/legacy/utils/url/CustomUrlConnection.java
|
||||
legacy/org/prismlauncher/legacy/utils/url/UrlUtils.java
|
||||
legacy/net/minecraft/Launcher.java
|
||||
legacy/org/prismlauncher/legacy/LegacyProxy.java
|
||||
)
|
||||
|
||||
add_jar(NewLaunch ${SRC})
|
||||
add_jar(NewLaunchLegacy ${LEGACY_SRC} INCLUDE_JARS NewLaunch)
|
||||
install_jar(NewLaunch "${JARS_DEST_DIR}")
|
||||
install_jar(NewLaunchLegacy "${JARS_DEST_DIR}")
|
||||
|
@ -92,12 +92,11 @@ public final class Launcher extends Applet implements AppletStub {
|
||||
|
||||
try {
|
||||
if (documentBase == null) {
|
||||
if (applet.getClass().getPackage().getName().startsWith("com.mojang.")) {
|
||||
if (applet.getClass().getPackage().getName().startsWith("com.mojang"))
|
||||
// Special case only for Classic versions
|
||||
documentBase = new URL("http://www.minecraft.net:80/game/");
|
||||
} else {
|
||||
else
|
||||
documentBase = new URL("http://www.minecraft.net/game/");
|
||||
}
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
throw new AssertionError(e);
|
@ -52,7 +52,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.prismlauncher.launcher.impl.legacy;
|
||||
package org.prismlauncher.legacy;
|
||||
|
||||
import org.prismlauncher.utils.logging.Log;
|
||||
|
||||
@ -74,7 +74,7 @@ import javax.swing.JFrame;
|
||||
|
||||
import net.minecraft.Launcher;
|
||||
|
||||
public final class LegacyFrame extends JFrame {
|
||||
final class LegacyFrame extends JFrame {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Launcher launcher;
|
||||
@ -130,7 +130,7 @@ public final class LegacyFrame extends JFrame {
|
||||
|
||||
launcher.setParameter("username", user);
|
||||
launcher.setParameter("sessionid", session);
|
||||
launcher.setParameter("stand-alone", true); // Show the quit button. TODO: why won't this work?
|
||||
launcher.setParameter("stand-alone", true); // Show the quit button. This often doesn't seem to work.
|
||||
launcher.setParameter("haspaid", true); // Some old versions need this for world saves to work.
|
||||
launcher.setParameter("demo", demo);
|
||||
launcher.setParameter("fullscreen", false);
|
@ -53,23 +53,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.prismlauncher.launcher.impl.legacy;
|
||||
package org.prismlauncher.legacy;
|
||||
|
||||
import org.prismlauncher.launcher.impl.AbstractLauncher;
|
||||
import org.prismlauncher.utils.Parameters;
|
||||
import org.prismlauncher.utils.ReflectionUtils;
|
||||
import org.prismlauncher.utils.logging.Log;
|
||||
|
||||
import java.applet.Applet;
|
||||
import java.io.File;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Used to launch old versions that support applets.
|
||||
* Used to launch old versions which support applets.
|
||||
*/
|
||||
public final class LegacyLauncher extends AbstractLauncher {
|
||||
final class LegacyLauncher extends AbstractLauncher {
|
||||
private final String user, session;
|
||||
private final String title;
|
||||
private final String appletClass;
|
||||
@ -93,11 +97,9 @@ public final class LegacyLauncher extends AbstractLauncher {
|
||||
@Override
|
||||
public void launch() throws Throwable {
|
||||
Class<?> main = ClassLoader.getSystemClassLoader().loadClass(mainClassName);
|
||||
Field gameDirField = ReflectionUtils.findMinecraftGameDirField(main);
|
||||
Field gameDirField = findMinecraftGameDirField(main);
|
||||
|
||||
if (gameDirField == null)
|
||||
Log.warning("Could not find Minecraft folder field");
|
||||
else {
|
||||
if (gameDirField != null) {
|
||||
gameDirField.setAccessible(true);
|
||||
gameDirField.set(null, new File(gameDir));
|
||||
}
|
||||
@ -106,7 +108,7 @@ public final class LegacyLauncher extends AbstractLauncher {
|
||||
System.setProperty("minecraft.applet.TargetDirectory", gameDir);
|
||||
|
||||
try {
|
||||
LegacyFrame window = new LegacyFrame(title, ReflectionUtils.createAppletClass(appletClass));
|
||||
LegacyFrame window = new LegacyFrame(title, createAppletClass(appletClass));
|
||||
|
||||
window.start(user, session, width, height, maximize, serverAddress, serverPort, gameArgs.contains("--demo"));
|
||||
return;
|
||||
@ -115,9 +117,39 @@ public final class LegacyLauncher extends AbstractLauncher {
|
||||
}
|
||||
}
|
||||
|
||||
// find and invoke the main method, this time without size parameters
|
||||
// in all versions that support applets, these are ignored
|
||||
// find and invoke the main method, this time without size parameters - in all
|
||||
// versions that support applets, these are ignored
|
||||
MethodHandle method = ReflectionUtils.findMainMethod(main);
|
||||
method.invokeExact(gameArgs.toArray(new String[0]));
|
||||
}
|
||||
|
||||
private static Applet createAppletClass(String clazz) throws Throwable {
|
||||
Class<?> appletClass = ClassLoader.getSystemClassLoader().loadClass(clazz);
|
||||
|
||||
MethodHandle appletConstructor = MethodHandles.lookup().findConstructor(appletClass, MethodType.methodType(void.class));
|
||||
return (Applet) appletConstructor.invoke();
|
||||
}
|
||||
|
||||
private static Field findMinecraftGameDirField(Class<?> clazz) {
|
||||
// search for private static File
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getType() != File.class)
|
||||
continue;
|
||||
|
||||
int fieldModifiers = field.getModifiers();
|
||||
|
||||
if (!Modifier.isStatic(fieldModifiers))
|
||||
continue;
|
||||
|
||||
if (!Modifier.isPrivate(fieldModifiers))
|
||||
continue;
|
||||
|
||||
if (Modifier.isFinal(fieldModifiers))
|
||||
continue;
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Linking this library statically or dynamically with other modules is
|
||||
* making a combined work based on this library. Thus, the terms and
|
||||
* conditions of the GNU General Public License cover the whole
|
||||
* combination.
|
||||
*
|
||||
* As a special exception, the copyright holders of this library give
|
||||
* you permission to link this library with independent modules to
|
||||
* produce an executable, regardless of the license terms of these
|
||||
* independent modules, and to copy and distribute the resulting
|
||||
* executable under terms of your choice, provided that you also meet,
|
||||
* for each linked independent module, the terms and conditions of the
|
||||
* license of that module. An independent module is a module which is
|
||||
* not derived from or based on this library. If you modify this
|
||||
* library, you may extend this exception to your version of the
|
||||
* library, but you are not obliged to do so. If you do not wish to do
|
||||
* so, delete this exception statement from your version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.prismlauncher.legacy;
|
||||
|
||||
import org.prismlauncher.launcher.Launcher;
|
||||
import org.prismlauncher.legacy.fix.online.OnlineFixes;
|
||||
import org.prismlauncher.utils.Parameters;
|
||||
|
||||
// implementation of LegacyProxy
|
||||
public final class LegacyProxy {
|
||||
public static Launcher createLauncher(Parameters params) {
|
||||
return new LegacyLauncher(params);
|
||||
}
|
||||
|
||||
public static void applyOnlineFixes(Parameters parameters) {
|
||||
OnlineFixes.apply(parameters);
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Linking this library statically or dynamically with other modules is
|
||||
* making a combined work based on this library. Thus, the terms and
|
||||
* conditions of the GNU General Public License cover the whole
|
||||
* combination.
|
||||
*
|
||||
* As a special exception, the copyright holders of this library give
|
||||
* you permission to link this library with independent modules to
|
||||
* produce an executable, regardless of the license terms of these
|
||||
* independent modules, and to copy and distribute the resulting
|
||||
* executable under terms of your choice, provided that you also meet,
|
||||
* for each linked independent module, the terms and conditions of the
|
||||
* license of that module. An independent module is a module which is
|
||||
* not derived from or based on this library. If you modify this
|
||||
* library, you may extend this exception to your version of the
|
||||
* library, but you are not obliged to do so. If you do not wish to do
|
||||
* so, delete this exception statement from your version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.prismlauncher.legacy.fix.online;
|
||||
|
||||
import org.prismlauncher.legacy.utils.url.UrlUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
|
||||
final class Handler extends URLStreamHandler {
|
||||
@Override
|
||||
protected URLConnection openConnection(URL address) throws IOException {
|
||||
return openConnection(address, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URLConnection openConnection(URL address, Proxy proxy) throws IOException {
|
||||
URLConnection result;
|
||||
|
||||
// try skin fix
|
||||
result = SkinFix.openConnection(address, proxy);
|
||||
if (result != null)
|
||||
return result;
|
||||
|
||||
return UrlUtils.openConnection(address, proxy);
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Linking this library statically or dynamically with other modules is
|
||||
* making a combined work based on this library. Thus, the terms and
|
||||
* conditions of the GNU General Public License cover the whole
|
||||
* combination.
|
||||
*
|
||||
* As a special exception, the copyright holders of this library give
|
||||
* you permission to link this library with independent modules to
|
||||
* produce an executable, regardless of the license terms of these
|
||||
* independent modules, and to copy and distribute the resulting
|
||||
* executable under terms of your choice, provided that you also meet,
|
||||
* for each linked independent module, the terms and conditions of the
|
||||
* license of that module. An independent module is a module which is
|
||||
* not derived from or based on this library. If you modify this
|
||||
* library, you may extend this exception to your version of the
|
||||
* library, but you are not obliged to do so. If you do not wish to do
|
||||
* so, delete this exception statement from your version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.prismlauncher.legacy.fix.online;
|
||||
|
||||
import org.prismlauncher.legacy.utils.Base64;
|
||||
import org.prismlauncher.legacy.utils.url.UrlUtils;
|
||||
import org.prismlauncher.utils.Parameters;
|
||||
import org.prismlauncher.utils.logging.Log;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.net.URLStreamHandlerFactory;
|
||||
|
||||
/**
|
||||
* Fixes skins by redirecting to other URLs.
|
||||
*
|
||||
* @see {@link Handler}
|
||||
* @see {@link UrlUtils}
|
||||
*/
|
||||
public final class OnlineFixes implements URLStreamHandlerFactory {
|
||||
public static void apply(Parameters params) {
|
||||
if (!"true".equals(params.getString("onlineFixes", null)))
|
||||
return;
|
||||
|
||||
if (!UrlUtils.isSupported() || !Base64.isSupported()) {
|
||||
Log.warning("Cannot access the necessary Java internals for skin fix");
|
||||
Log.warning("Turning off online fixes in the settings will silence the warnings");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
URL.setURLStreamHandlerFactory(new OnlineFixes());
|
||||
} catch (Error e) {
|
||||
Log.warning("Cannot apply skin fix: URLStreamHandlerFactory is already set");
|
||||
Log.warning("Turning off online fixes in the settings will silence the warnings");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public URLStreamHandler createURLStreamHandler(String protocol) {
|
||||
if ("http".equals(protocol))
|
||||
return new Handler();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,179 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Linking this library statically or dynamically with other modules is
|
||||
* making a combined work based on this library. Thus, the terms and
|
||||
* conditions of the GNU General Public License cover the whole
|
||||
* combination.
|
||||
*
|
||||
* As a special exception, the copyright holders of this library give
|
||||
* you permission to link this library with independent modules to
|
||||
* produce an executable, regardless of the license terms of these
|
||||
* independent modules, and to copy and distribute the resulting
|
||||
* executable under terms of your choice, provided that you also meet,
|
||||
* for each linked independent module, the terms and conditions of the
|
||||
* license of that module. An independent module is a module which is
|
||||
* not derived from or based on this library. If you modify this
|
||||
* library, you may extend this exception to your version of the
|
||||
* library, but you are not obliged to do so. If you do not wish to do
|
||||
* so, delete this exception statement from your version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.prismlauncher.legacy.fix.online;
|
||||
|
||||
import org.prismlauncher.legacy.utils.api.MojangApi;
|
||||
import org.prismlauncher.legacy.utils.api.Texture;
|
||||
import org.prismlauncher.legacy.utils.url.CustomUrlConnection;
|
||||
import org.prismlauncher.legacy.utils.url.UrlUtils;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
final class SkinFix {
|
||||
static URLConnection openConnection(URL address, Proxy proxy) throws IOException {
|
||||
String skinOwner = findSkinOwner(address);
|
||||
if (skinOwner != null)
|
||||
// we need to correct the skin
|
||||
return getSkinConnection(skinOwner, proxy);
|
||||
|
||||
String capeOwner = findCapeOwner(address);
|
||||
if (capeOwner != null) {
|
||||
// since we do not need to process the image, open a direct connection bypassing
|
||||
// Handler
|
||||
Texture texture = MojangApi.getTexture(MojangApi.getUuid(capeOwner), "CAPE");
|
||||
if (texture == null)
|
||||
return null;
|
||||
|
||||
return UrlUtils.openConnection(texture.getUrl(), proxy);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static URLConnection getSkinConnection(String owner, Proxy proxy) throws IOException {
|
||||
Texture texture = MojangApi.getTexture(MojangApi.getUuid(owner), "SKIN");
|
||||
if (texture == null)
|
||||
return null;
|
||||
|
||||
URLConnection connection = UrlUtils.openConnection(texture.getUrl(), proxy);
|
||||
try (InputStream in = connection.getInputStream()) {
|
||||
// thank you craftycodie!
|
||||
// this is heavily based on
|
||||
// https://github.com/craftycodie/MineOnline/blob/4f4f86f9d051e0a6fd7ff0b95b2a05f7437683d7/src/main/java/gg/codie/mineonline/gui/textures/TextureHelper.java#L17
|
||||
BufferedImage image = ImageIO.read(in);
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
|
||||
|
||||
BufferedImage subimage;
|
||||
|
||||
if (image.getHeight() > 32) {
|
||||
// flatten second layers
|
||||
subimage = image.getSubimage(0, 32, 56, 16);
|
||||
graphics.drawImage(subimage, 0, 16, null);
|
||||
}
|
||||
|
||||
if (texture.isSlim()) {
|
||||
// convert slim to classic
|
||||
subimage = image.getSubimage(45, 16, 9, 16);
|
||||
graphics.drawImage(subimage, 46, 16, null);
|
||||
|
||||
subimage = image.getSubimage(49, 16, 2, 4);
|
||||
graphics.drawImage(subimage, 50, 16, null);
|
||||
|
||||
subimage = image.getSubimage(53, 20, 2, 12);
|
||||
graphics.drawImage(subimage, 54, 20, null);
|
||||
}
|
||||
|
||||
graphics.dispose();
|
||||
|
||||
// crop the image - old versions disregard all secondary layers besides the hat
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
image = image.getSubimage(0, 0, 64, 32);
|
||||
ImageIO.write(image, "png", out);
|
||||
|
||||
return new CustomUrlConnection(out.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
private static String findSkinOwner(URL address) {
|
||||
switch (address.getHost()) {
|
||||
case "www.minecraft.net":
|
||||
return stripIfPrefixed(address.getPath(), "/skin/");
|
||||
|
||||
case "s3.amazonaws.com":
|
||||
case "skins.minecraft.net":
|
||||
return stripIfPrefixed(address.getPath(), "/MinecraftSkins/");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String findCapeOwner(URL address) {
|
||||
switch (address.getHost()) {
|
||||
case "www.minecraft.net":
|
||||
if (!address.getPath().equals("/cloak/get.jsp"))
|
||||
return null;
|
||||
|
||||
return stripIfPrefixed(address.getQuery(), "user=");
|
||||
|
||||
case "s3.amazonaws.com":
|
||||
case "skins.minecraft.net":
|
||||
return stripIfPrefixed(address.getPath(), "/MinecraftCloaks/");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String stripIfPrefixed(String string, String prefix) {
|
||||
if (string != null && string.startsWith(prefix)) {
|
||||
string = string.substring(prefix.length());
|
||||
|
||||
if (string.endsWith(".png"))
|
||||
string = string.substring(0, string.lastIndexOf('.'));
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Linking this library statically or dynamically with other modules is
|
||||
* making a combined work based on this library. Thus, the terms and
|
||||
* conditions of the GNU General Public License cover the whole
|
||||
* combination.
|
||||
*
|
||||
* As a special exception, the copyright holders of this library give
|
||||
* you permission to link this library with independent modules to
|
||||
* produce an executable, regardless of the license terms of these
|
||||
* independent modules, and to copy and distribute the resulting
|
||||
* executable under terms of your choice, provided that you also meet,
|
||||
* for each linked independent module, the terms and conditions of the
|
||||
* license of that module. An independent module is a module which is
|
||||
* not derived from or based on this library. If you modify this
|
||||
* library, you may extend this exception to your version of the
|
||||
* library, but you are not obliged to do so. If you do not wish to do
|
||||
* so, delete this exception statement from your version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.prismlauncher.legacy.utils;
|
||||
|
||||
import org.prismlauncher.utils.logging.Log;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Uses Base64 with Java 8 or later, otherwise DatatypeConverter. In the latter
|
||||
* case, reflection is used to allow using newer compilers.
|
||||
*/
|
||||
public final class Base64 {
|
||||
private static boolean supported = true;
|
||||
private static MethodHandle legacy;
|
||||
|
||||
static {
|
||||
try {
|
||||
Class.forName("java.util.Base64");
|
||||
} catch (ClassNotFoundException e) {
|
||||
try {
|
||||
Class<?> datatypeConverter = Class.forName("javax.xml.bind.DatatypeConverter");
|
||||
legacy = MethodHandles.lookup().findStatic(
|
||||
datatypeConverter, "parseBase64Binary", MethodType.methodType(byte[].class, String.class));
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e1) {
|
||||
Log.error("Base64 not supported", e1);
|
||||
supported = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether base64 is supported.
|
||||
*
|
||||
* @return <code>true</code> if base64 can be parsed
|
||||
*/
|
||||
public static boolean isSupported() {
|
||||
return supported;
|
||||
}
|
||||
|
||||
public static byte[] decode(String input) {
|
||||
if (!isSupported())
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
if (legacy == null)
|
||||
return java.util.Base64.getDecoder().decode(input.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
try {
|
||||
return (byte[]) legacy.invokeExact(input);
|
||||
} catch (Error | RuntimeException e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Linking this library statically or dynamically with other modules is
|
||||
* making a combined work based on this library. Thus, the terms and
|
||||
* conditions of the GNU General Public License cover the whole
|
||||
* combination.
|
||||
*
|
||||
* As a special exception, the copyright holders of this library give
|
||||
* you permission to link this library with independent modules to
|
||||
* produce an executable, regardless of the license terms of these
|
||||
* independent modules, and to copy and distribute the resulting
|
||||
* executable under terms of your choice, provided that you also meet,
|
||||
* for each linked independent module, the terms and conditions of the
|
||||
* license of that module. An independent module is a module which is
|
||||
* not derived from or based on this library. If you modify this
|
||||
* library, you may extend this exception to your version of the
|
||||
* library, but you are not obliged to do so. If you do not wish to do
|
||||
* so, delete this exception statement from your version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.prismlauncher.legacy.utils.api;
|
||||
|
||||
import org.prismlauncher.legacy.utils.Base64;
|
||||
import org.prismlauncher.legacy.utils.json.JsonParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Basic wrapper for Mojang's Minecraft API.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public final class MojangApi {
|
||||
public static String getUuid(String username) throws IOException {
|
||||
try (InputStream in = new URL("https://api.mojang.com/users/profiles/minecraft/" + username).openStream()) {
|
||||
Map<String, Object> map = (Map<String, Object>) JsonParser.parse(in);
|
||||
return (String) map.get("id");
|
||||
}
|
||||
}
|
||||
|
||||
public static Texture getTexture(String player, String id) throws IOException {
|
||||
Map<String, Object> map = getTextures(player);
|
||||
|
||||
if (map != null) {
|
||||
map = (Map<String, Object>) map.get(id);
|
||||
if (map == null)
|
||||
return null;
|
||||
|
||||
URL url = new URL((String) map.get("url"));
|
||||
boolean slim = false;
|
||||
|
||||
if (id.equals("SKIN")) {
|
||||
map = (Map<String, Object>) map.get("metadata");
|
||||
if (map != null && "slim".equals(map.get("model")))
|
||||
slim = true;
|
||||
}
|
||||
|
||||
return new Texture(url, slim);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Map<String, Object> getTextures(String player) throws IOException {
|
||||
try (InputStream profileIn = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + player).openStream()) {
|
||||
Map<String, Object> profile = (Map<String, Object>) JsonParser.parse(profileIn);
|
||||
|
||||
for (Map<String, Object> property : (Iterable<Map<String, Object>>) profile.get("properties")) {
|
||||
if (property.get("name").equals("textures")) {
|
||||
Map<String, Object> result =
|
||||
(Map<String, Object>) JsonParser.parse(new String(Base64.decode((String) property.get("value"))));
|
||||
result = (Map<String, Object>) result.get("textures");
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Linking this library statically or dynamically with other modules is
|
||||
* making a combined work based on this library. Thus, the terms and
|
||||
* conditions of the GNU General Public License cover the whole
|
||||
* combination.
|
||||
*
|
||||
* As a special exception, the copyright holders of this library give
|
||||
* you permission to link this library with independent modules to
|
||||
* produce an executable, regardless of the license terms of these
|
||||
* independent modules, and to copy and distribute the resulting
|
||||
* executable under terms of your choice, provided that you also meet,
|
||||
* for each linked independent module, the terms and conditions of the
|
||||
* license of that module. An independent module is a module which is
|
||||
* not derived from or based on this library. If you modify this
|
||||
* library, you may extend this exception to your version of the
|
||||
* library, but you are not obliged to do so. If you do not wish to do
|
||||
* so, delete this exception statement from your version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.prismlauncher.legacy.utils.api;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Represents a texture from the Mojang API.
|
||||
*/
|
||||
public final class Texture {
|
||||
private final URL url;
|
||||
private final boolean slim;
|
||||
|
||||
public Texture(URL url, boolean slim) {
|
||||
this.url = url;
|
||||
this.slim = slim;
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public boolean isSlim() {
|
||||
return slim;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Linking this library statically or dynamically with other modules is
|
||||
* making a combined work based on this library. Thus, the terms and
|
||||
* conditions of the GNU General Public License cover the whole
|
||||
* combination.
|
||||
*
|
||||
* As a special exception, the copyright holders of this library give
|
||||
* you permission to link this library with independent modules to
|
||||
* produce an executable, regardless of the license terms of these
|
||||
* independent modules, and to copy and distribute the resulting
|
||||
* executable under terms of your choice, provided that you also meet,
|
||||
* for each linked independent module, the terms and conditions of the
|
||||
* license of that module. An independent module is a module which is
|
||||
* not derived from or based on this library. If you modify this
|
||||
* library, you may extend this exception to your version of the
|
||||
* library, but you are not obliged to do so. If you do not wish to do
|
||||
* so, delete this exception statement from your version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.prismlauncher.legacy.utils.json;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class JsonParseException extends IOException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public JsonParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,408 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Linking this library statically or dynamically with other modules is
|
||||
* making a combined work based on this library. Thus, the terms and
|
||||
* conditions of the GNU General Public License cover the whole
|
||||
* combination.
|
||||
*
|
||||
* As a special exception, the copyright holders of this library give
|
||||
* you permission to link this library with independent modules to
|
||||
* produce an executable, regardless of the license terms of these
|
||||
* independent modules, and to copy and distribute the resulting
|
||||
* executable under terms of your choice, provided that you also meet,
|
||||
* for each linked independent module, the terms and conditions of the
|
||||
* license of that module. An independent module is a module which is
|
||||
* not derived from or based on this library. If you modify this
|
||||
* library, you may extend this exception to your version of the
|
||||
* library, but you are not obliged to do so. If you do not wish to do
|
||||
* so, delete this exception statement from your version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.prismlauncher.legacy.utils.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A lightweight portable JSON parser used instead of GSON since it is not
|
||||
* available in a lot of versions.
|
||||
*/
|
||||
public final class JsonParser {
|
||||
private final Reader in;
|
||||
private char[] buffer;
|
||||
private int pos, length;
|
||||
|
||||
public static Object parse(String in) throws IOException {
|
||||
return parse(new StringReader(in));
|
||||
}
|
||||
|
||||
public static Object parse(InputStream in) throws IOException {
|
||||
return parse(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static Object parse(Reader in) throws IOException {
|
||||
return new JsonParser(in).readSingleValue();
|
||||
}
|
||||
|
||||
private JsonParser(Reader in) throws IOException {
|
||||
this.in = in;
|
||||
pos = length = 0;
|
||||
read();
|
||||
}
|
||||
|
||||
private int character() {
|
||||
if (length == -1)
|
||||
return -1;
|
||||
|
||||
return buffer[pos];
|
||||
}
|
||||
|
||||
private int read() throws IOException {
|
||||
if (length == -1)
|
||||
return -1;
|
||||
|
||||
if (buffer == null || pos++ == length - 1) {
|
||||
pos = 0;
|
||||
buffer = new char[8192];
|
||||
length = in.read(buffer);
|
||||
}
|
||||
|
||||
return character();
|
||||
}
|
||||
|
||||
private void assertCharacter(char character) throws JsonParseException {
|
||||
if (character() != character)
|
||||
throw new JsonParseException(
|
||||
"Expected '" + character + "' but got " + (character() != -1 ? ("'" + (char) character() + "'") : "EOF"));
|
||||
}
|
||||
|
||||
private void assertNoEOF(String expected) throws JsonParseException {
|
||||
if (character() == -1)
|
||||
throw new JsonParseException("Expected " + expected + " but got EOF");
|
||||
}
|
||||
|
||||
private void skipWhitespace() throws IOException {
|
||||
while (isWhitespace()) read();
|
||||
}
|
||||
|
||||
private boolean isWhitespace() {
|
||||
return character() == ' ' || character() == '\n' || character() == '\r' || character() == '\t';
|
||||
}
|
||||
|
||||
private Object readSingleValue() throws IOException {
|
||||
skipWhitespace();
|
||||
Object result = readValue();
|
||||
|
||||
if (!(result instanceof Double))
|
||||
read();
|
||||
|
||||
skipWhitespace();
|
||||
|
||||
if (character() != -1)
|
||||
throw new JsonParseException("Found trailing non-whitespace characters");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object readValue() throws IOException {
|
||||
assertNoEOF("a value");
|
||||
|
||||
int character = character();
|
||||
|
||||
switch (character) {
|
||||
case '{':
|
||||
return readObject();
|
||||
|
||||
case '[':
|
||||
return readArray();
|
||||
|
||||
case '"':
|
||||
return readString();
|
||||
|
||||
case 't':
|
||||
case 'f':
|
||||
// probably boolean
|
||||
Boolean bool = readBoolean();
|
||||
if (bool != null)
|
||||
return bool;
|
||||
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
// probably null
|
||||
if (readNull())
|
||||
return null;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (character == '-' || isDigit())
|
||||
// probably a number
|
||||
return readNumber();
|
||||
|
||||
throw new JsonParseException("Expected a JSON value but got '" + (char) character + "'");
|
||||
}
|
||||
|
||||
private Map<String, Object> readObject() throws IOException {
|
||||
assertCharacter('{');
|
||||
Map<String, Object> obj = new HashMap<>();
|
||||
boolean comma = false;
|
||||
|
||||
read();
|
||||
skipWhitespace();
|
||||
|
||||
while (character() != '}') {
|
||||
if (comma) {
|
||||
assertCharacter(',');
|
||||
read();
|
||||
skipWhitespace();
|
||||
}
|
||||
|
||||
String key = readString();
|
||||
read();
|
||||
skipWhitespace();
|
||||
assertCharacter(':');
|
||||
read();
|
||||
skipWhitespace();
|
||||
|
||||
Object value = readValue();
|
||||
obj.put(key, value);
|
||||
|
||||
if (!(value instanceof Double))
|
||||
read();
|
||||
|
||||
skipWhitespace();
|
||||
comma = true;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private List<Object> readArray() throws IOException {
|
||||
assertCharacter('[');
|
||||
List<Object> array = new ArrayList<>();
|
||||
boolean comma = false;
|
||||
|
||||
read();
|
||||
skipWhitespace();
|
||||
|
||||
while (character() != ']') {
|
||||
if (comma) {
|
||||
assertCharacter(',');
|
||||
read();
|
||||
skipWhitespace();
|
||||
}
|
||||
|
||||
Object value = readValue();
|
||||
array.add(value);
|
||||
|
||||
if (!(value instanceof Double))
|
||||
read();
|
||||
|
||||
skipWhitespace();
|
||||
comma = true;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
private String readString() throws IOException {
|
||||
assertCharacter('"');
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
while (read() != '"') {
|
||||
int character = character();
|
||||
|
||||
if (character >= '\u0000' && character <= '\u001F')
|
||||
throw new JsonParseException("Found unescaped control character within string");
|
||||
|
||||
switch (character) {
|
||||
case -1:
|
||||
throw new JsonParseException("Expected '\"' but got EOF");
|
||||
|
||||
case 0x7F:
|
||||
if (read() == '"') {
|
||||
return result.toString();
|
||||
}
|
||||
continue;
|
||||
|
||||
case '\\':
|
||||
int seq = read();
|
||||
|
||||
switch (seq) {
|
||||
case -1:
|
||||
throw new JsonParseException("Expected an escape sequence but got EOF");
|
||||
|
||||
case '\\':
|
||||
break;
|
||||
|
||||
case '/':
|
||||
case '\"':
|
||||
character = seq;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
character = '\b';
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
character = '\f';
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
character = '\n';
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
character = '\r';
|
||||
break;
|
||||
|
||||
case 't':
|
||||
character = '\t';
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
// char array to allow allocation in advance.
|
||||
char[] digits = new char[4];
|
||||
|
||||
for (int index = 0; index < digits.length; index++) {
|
||||
character = read();
|
||||
if (index == 0 && character() == '-') {
|
||||
throw new JsonParseException("Hex sequence may not be negative");
|
||||
} else if (character() == -1) {
|
||||
throw new JsonParseException("Expected a hex sequence but got EOF");
|
||||
}
|
||||
digits[index] = (char) character;
|
||||
}
|
||||
|
||||
String digitsString = new String(digits);
|
||||
|
||||
try {
|
||||
character = Integer.parseInt(digitsString, 16);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new JsonParseException("Could not parse hex sequence \"" + digitsString + "\"");
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new JsonParseException("Invalid escape sequence: \\" + (char) seq);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
result.append((char) character);
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private boolean isDigit() {
|
||||
return character() >= '0' && character() <= '9';
|
||||
}
|
||||
|
||||
private Double readNumber() throws IOException {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
if (character() == '-') {
|
||||
result.append((char) character());
|
||||
read();
|
||||
}
|
||||
|
||||
if (character() == '0') {
|
||||
result.append((char) character());
|
||||
read();
|
||||
|
||||
if (isDigit())
|
||||
throw new JsonParseException("Found superfluous leading zero");
|
||||
} else if (!isDigit())
|
||||
throw new JsonParseException("Expected digits");
|
||||
|
||||
while (character() != -1 && isDigit()) {
|
||||
result.append((char) character());
|
||||
read();
|
||||
}
|
||||
|
||||
if (character() == '.') {
|
||||
result.append('.');
|
||||
|
||||
read();
|
||||
assertNoEOF("digits");
|
||||
|
||||
if (!isDigit())
|
||||
throw new JsonParseException("Expected digits after decimal point");
|
||||
|
||||
while (character() != -1 && isDigit()) {
|
||||
result.append((char) character());
|
||||
read();
|
||||
}
|
||||
}
|
||||
|
||||
if (character() == 'e' || character() == 'E') {
|
||||
result.append('E');
|
||||
|
||||
read();
|
||||
assertNoEOF("digits");
|
||||
|
||||
if (character() == '+' || character() == '-') {
|
||||
result.append((char) character());
|
||||
read();
|
||||
}
|
||||
|
||||
if (!(character() == '+' || character() == '-' || isDigit()))
|
||||
throw new JsonParseException("Expected exponent digits");
|
||||
|
||||
while (character() != -1 && isDigit()) {
|
||||
result.append((char) character());
|
||||
read();
|
||||
}
|
||||
}
|
||||
|
||||
String resultStr = result.toString();
|
||||
|
||||
try {
|
||||
return Double.parseDouble(resultStr);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new JsonParseException("Failed to parse number '" + resultStr + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private Boolean readBoolean() throws IOException {
|
||||
if (character() == 't') {
|
||||
if (read() == 'r' && read() == 'u' && read() == 'e') {
|
||||
return true;
|
||||
}
|
||||
} else if (character() == 'f' && read() == 'a' && read() == 'l' && read() == 's' && read() == 'e') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean readNull() throws IOException {
|
||||
return character() == 'n' && read() == 'u' && read() == 'l' && read() == 'l';
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Linking this library statically or dynamically with other modules is
|
||||
* making a combined work based on this library. Thus, the terms and
|
||||
* conditions of the GNU General Public License cover the whole
|
||||
* combination.
|
||||
*
|
||||
* As a special exception, the copyright holders of this library give
|
||||
* you permission to link this library with independent modules to
|
||||
* produce an executable, regardless of the license terms of these
|
||||
* independent modules, and to copy and distribute the resulting
|
||||
* executable under terms of your choice, provided that you also meet,
|
||||
* for each linked independent module, the terms and conditions of the
|
||||
* license of that module. An independent module is a module which is
|
||||
* not derived from or based on this library. If you modify this
|
||||
* library, you may extend this exception to your version of the
|
||||
* library, but you are not obliged to do so. If you do not wish to do
|
||||
* so, delete this exception statement from your version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.prismlauncher.legacy.utils.url;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
public final class CustomUrlConnection extends HttpURLConnection {
|
||||
private final InputStream in;
|
||||
|
||||
public CustomUrlConnection(byte[] data) {
|
||||
this(new ByteArrayInputStream(data));
|
||||
}
|
||||
|
||||
public CustomUrlConnection(InputStream in) {
|
||||
super(null);
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() throws IOException {
|
||||
responseCode = 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean usingProxy() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Linking this library statically or dynamically with other modules is
|
||||
* making a combined work based on this library. Thus, the terms and
|
||||
* conditions of the GNU General Public License cover the whole
|
||||
* combination.
|
||||
*
|
||||
* As a special exception, the copyright holders of this library give
|
||||
* you permission to link this library with independent modules to
|
||||
* produce an executable, regardless of the license terms of these
|
||||
* independent modules, and to copy and distribute the resulting
|
||||
* executable under terms of your choice, provided that you also meet,
|
||||
* for each linked independent module, the terms and conditions of the
|
||||
* license of that module. An independent module is a module which is
|
||||
* not derived from or based on this library. If you modify this
|
||||
* library, you may extend this exception to your version of the
|
||||
* library, but you are not obliged to do so. If you do not wish to do
|
||||
* so, delete this exception statement from your version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.prismlauncher.legacy.utils.url;
|
||||
|
||||
import org.prismlauncher.utils.logging.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
|
||||
/**
|
||||
* A utility class for URLs which uses reflection to access constructors for
|
||||
* internal classes.
|
||||
*/
|
||||
public final class UrlUtils {
|
||||
private static URLStreamHandler http;
|
||||
private static MethodHandle openConnection;
|
||||
|
||||
static {
|
||||
try {
|
||||
// we first obtain the stock URLStreamHandler for http as we overwrite it later
|
||||
Method getURLStreamHandler = URL.class.getDeclaredMethod("getURLStreamHandler", String.class);
|
||||
getURLStreamHandler.setAccessible(true);
|
||||
http = (URLStreamHandler) getURLStreamHandler.invoke(null, "http");
|
||||
|
||||
// we next find the openConnection method
|
||||
Method openConnectionReflect = URLStreamHandler.class.getDeclaredMethod("openConnection", URL.class, Proxy.class);
|
||||
openConnectionReflect.setAccessible(true);
|
||||
openConnection = MethodHandles.lookup().unreflect(openConnectionReflect);
|
||||
} catch (Throwable e) {
|
||||
Log.error("URL reflection failed - some features may not work", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether all the features of this class are available.
|
||||
*
|
||||
* @return <code>true</code> if all features can be used
|
||||
*/
|
||||
public static boolean isSupported() {
|
||||
return http != null && openConnection != null;
|
||||
}
|
||||
|
||||
public static URLConnection openConnection(URL url, Proxy proxy) throws IOException {
|
||||
if (http == null)
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
if (url.getProtocol().equals("http"))
|
||||
return openConnection(http, url, proxy);
|
||||
|
||||
// fall back to Java's default method
|
||||
// at this point, this should not cause a StackOverflowError unless we've missed
|
||||
// a protocol out from the if statements
|
||||
return url.openConnection();
|
||||
}
|
||||
|
||||
public static URLConnection openConnection(URLStreamHandler handler, URL url, Proxy proxy) throws IOException {
|
||||
if (openConnection == null)
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
try {
|
||||
return (URLConnection) openConnection.invokeExact(handler, url, proxy);
|
||||
} catch (IOException | Error | RuntimeException e) {
|
||||
throw e; // rethrow if possible
|
||||
} catch (Throwable e) {
|
||||
throw new AssertionError(e); // oh dear! this isn't meant to happen
|
||||
}
|
||||
}
|
||||
}
|
@ -57,7 +57,7 @@ package org.prismlauncher;
|
||||
import org.prismlauncher.exception.ParseException;
|
||||
import org.prismlauncher.launcher.Launcher;
|
||||
import org.prismlauncher.launcher.impl.StandardLauncher;
|
||||
import org.prismlauncher.launcher.impl.legacy.LegacyLauncher;
|
||||
import org.prismlauncher.legacy.LegacyProxy;
|
||||
import org.prismlauncher.utils.Parameters;
|
||||
import org.prismlauncher.utils.logging.Log;
|
||||
|
||||
@ -106,6 +106,8 @@ public final class EntryPoint {
|
||||
}
|
||||
|
||||
try {
|
||||
LegacyProxy.applyOnlineFixes(params);
|
||||
|
||||
Launcher launcher;
|
||||
String type = params.getString("launcher");
|
||||
|
||||
@ -115,7 +117,7 @@ public final class EntryPoint {
|
||||
break;
|
||||
|
||||
case "legacy":
|
||||
launcher = new LegacyLauncher(params);
|
||||
launcher = LegacyProxy.createLauncher(params);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
66
libraries/launcher/org/prismlauncher/legacy/LegacyProxy.java
Normal file
66
libraries/launcher/org/prismlauncher/legacy/LegacyProxy.java
Normal file
@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Linking this library statically or dynamically with other modules is
|
||||
* making a combined work based on this library. Thus, the terms and
|
||||
* conditions of the GNU General Public License cover the whole
|
||||
* combination.
|
||||
*
|
||||
* As a special exception, the copyright holders of this library give
|
||||
* you permission to link this library with independent modules to
|
||||
* produce an executable, regardless of the license terms of these
|
||||
* independent modules, and to copy and distribute the resulting
|
||||
* executable under terms of your choice, provided that you also meet,
|
||||
* for each linked independent module, the terms and conditions of the
|
||||
* license of that module. An independent module is a module which is
|
||||
* not derived from or based on this library. If you modify this
|
||||
* library, you may extend this exception to your version of the
|
||||
* library, but you are not obliged to do so. If you do not wish to do
|
||||
* so, delete this exception statement from your version.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.prismlauncher.legacy;
|
||||
|
||||
import org.prismlauncher.launcher.Launcher;
|
||||
import org.prismlauncher.utils.Parameters;
|
||||
|
||||
// used as a fallback if NewLaunchLegacy is not on the classpath
|
||||
// if it is, this class will be replaced
|
||||
public final class LegacyProxy {
|
||||
public static Launcher createLauncher(Parameters params) {
|
||||
throw new AssertionError("NewLaunchLegacy is not loaded");
|
||||
}
|
||||
|
||||
public static void applyOnlineFixes(Parameters params) {}
|
||||
}
|
@ -68,61 +68,6 @@ public final class ReflectionUtils {
|
||||
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
|
||||
private static final ClassLoader LOADER = ClassLoader.getSystemClassLoader();
|
||||
|
||||
/**
|
||||
* Construct a Java applet by its class name.
|
||||
*
|
||||
* @param clazz The class name
|
||||
* @return The applet instance
|
||||
* @throws Throwable
|
||||
*/
|
||||
public static Applet createAppletClass(String clazz) throws Throwable {
|
||||
Class<?> appletClass = LOADER.loadClass(clazz);
|
||||
|
||||
MethodHandle appletConstructor = LOOKUP.findConstructor(appletClass, MethodType.methodType(void.class));
|
||||
return (Applet) appletConstructor.invoke();
|
||||
}
|
||||
|
||||
/**
|
||||
* Best guess of the game directory field within net.minecraft.client.Minecraft.
|
||||
* Designed for legacy versions - newer versions do not use a static field.
|
||||
*
|
||||
* @param clazz The class
|
||||
* @return The first field matching criteria
|
||||
*/
|
||||
public static Field findMinecraftGameDirField(Class<?> clazz) {
|
||||
Log.debug("Resolving minecraft game directory field");
|
||||
|
||||
// search for private static File
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getType() != File.class) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int fieldModifiers = field.getModifiers();
|
||||
|
||||
if (!Modifier.isStatic(fieldModifiers)) {
|
||||
Log.debug("Rejecting field " + field.getName() + " because it is not static");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Modifier.isPrivate(fieldModifiers)) {
|
||||
Log.debug("Rejecting field " + field.getName() + " because it is not private");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Modifier.isFinal(fieldModifiers)) {
|
||||
Log.debug("Rejecting field " + field.getName() + " because it is final");
|
||||
continue;
|
||||
}
|
||||
|
||||
Log.debug("Identified field " + field.getName() + " to match conditions for game directory field");
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the main method within a class.
|
||||
*
|
||||
|
@ -350,6 +350,7 @@ Section "@Launcher_DisplayName@"
|
||||
|
||||
File "@Launcher_APP_BINARY_NAME@.exe"
|
||||
File "@Launcher_APP_BINARY_NAME@_filelink.exe"
|
||||
File "@Launcher_APP_BINARY_NAME@_updater.exe"
|
||||
File "qt.conf"
|
||||
File "qtlogging.ini"
|
||||
File *.dll
|
||||
@ -435,6 +436,7 @@ Section "Uninstall"
|
||||
|
||||
Delete $INSTDIR\@Launcher_APP_BINARY_NAME@.exe
|
||||
Delete $INSTDIR\@Launcher_APP_BINARY_NAME@_filelink.exe
|
||||
Delete $INSTDIR\@Launcher_APP_BINARY_NAME@_updater.exe
|
||||
Delete $INSTDIR\qt.conf
|
||||
Delete $INSTDIR\*.dll
|
||||
|
||||
@ -472,7 +474,6 @@ Function .onInit
|
||||
${GetParameters} $R0
|
||||
${GetOptions} $R0 "/NoShortcuts" $R1
|
||||
${IfNot} ${Errors}
|
||||
${OrIf} ${FileExists} "$InstDir\@Launcher_APP_BINARY_NAME@.exe"
|
||||
!insertmacro UnselectSection ${SM_SHORTCUTS}
|
||||
!insertmacro UnselectSection ${DESKTOP_SHORTCUTS}
|
||||
${EndIf}
|
||||
|
Loading…
Reference in New Issue
Block a user