Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into netjob_retry
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
commit
b6f48f6fe0
124
.github/workflows/build.yml
vendored
124
.github/workflows/build.yml
vendored
@ -37,56 +37,43 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
qt_ver: 5
|
qt_ver: 5
|
||||||
|
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: linux
|
qt_host: linux
|
||||||
qt_arch: ''
|
qt_arch: ""
|
||||||
qt_version: '6.2.4'
|
qt_version: "6.2.4"
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: "qt5compat qtimageformats"
|
||||||
qt_tools: ''
|
qt_tools: ""
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MinGW-w64"
|
name: "Windows-MinGW-w64"
|
||||||
msystem: clang64
|
msystem: clang64
|
||||||
vcvars_arch: 'amd64_x86'
|
vcvars_arch: "amd64_x86"
|
||||||
|
|
||||||
- os: windows-2022
|
|
||||||
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'
|
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MSVC"
|
name: "Windows-MSVC"
|
||||||
msystem: ''
|
msystem: ""
|
||||||
architecture: 'x64'
|
architecture: "x64"
|
||||||
vcvars_arch: 'amd64'
|
vcvars_arch: "amd64"
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: windows
|
qt_host: windows
|
||||||
qt_arch: ''
|
qt_arch: ''
|
||||||
qt_version: '6.5.2'
|
qt_version: '6.6.0'
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_tools: ''
|
qt_tools: ''
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MSVC-arm64"
|
name: "Windows-MSVC-arm64"
|
||||||
msystem: ''
|
msystem: ""
|
||||||
architecture: 'arm64'
|
architecture: "arm64"
|
||||||
vcvars_arch: 'amd64_arm64'
|
vcvars_arch: "amd64_arm64"
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: windows
|
qt_host: windows
|
||||||
qt_arch: 'win64_msvc2019_arm64'
|
qt_arch: 'win64_msvc2019_arm64'
|
||||||
qt_version: '6.5.2'
|
qt_version: '6.6.0'
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_tools: ''
|
qt_tools: ''
|
||||||
|
|
||||||
@ -96,7 +83,7 @@ jobs:
|
|||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: mac
|
qt_host: mac
|
||||||
qt_arch: ''
|
qt_arch: ''
|
||||||
qt_version: '6.5.2'
|
qt_version: '6.6.0'
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_tools: ''
|
qt_tools: ''
|
||||||
|
|
||||||
@ -105,9 +92,9 @@ jobs:
|
|||||||
macosx_deployment_target: 10.13
|
macosx_deployment_target: 10.13
|
||||||
qt_ver: 5
|
qt_ver: 5
|
||||||
qt_host: mac
|
qt_host: mac
|
||||||
qt_version: '5.15.2'
|
qt_version: "5.15.2"
|
||||||
qt_modules: ''
|
qt_modules: ""
|
||||||
qt_tools: ''
|
qt_tools: ""
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
@ -127,9 +114,9 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: 'true'
|
submodules: "true"
|
||||||
|
|
||||||
- name: 'Setup MSYS2'
|
- name: "Setup MSYS2"
|
||||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||||
uses: msys2/setup-msys2@v2
|
uses: msys2/setup-msys2@v2
|
||||||
with:
|
with:
|
||||||
@ -169,7 +156,7 @@ jobs:
|
|||||||
path: '${{ github.workspace }}\.ccache'
|
path: '${{ github.workspace }}\.ccache'
|
||||||
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ matrix.os }}-mingw-w64-ccache
|
${{ matrix.os }}-mingw-w64-ccache
|
||||||
|
|
||||||
- name: Setup ccache (Windows MinGW-w64)
|
- name: Setup ccache (Windows MinGW-w64)
|
||||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||||
@ -214,35 +201,35 @@ jobs:
|
|||||||
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v3
|
||||||
with:
|
with:
|
||||||
aqtversion: '==3.1.*'
|
aqtversion: "==3.1.*"
|
||||||
py7zrversion: '>=0.20.2'
|
py7zrversion: ">=0.20.2"
|
||||||
version: ${{ matrix.qt_version }}
|
version: ${{ matrix.qt_version }}
|
||||||
host: 'windows'
|
host: "windows"
|
||||||
target: 'desktop'
|
target: "desktop"
|
||||||
arch: ''
|
arch: ""
|
||||||
modules: ${{ matrix.qt_modules }}
|
modules: ${{ matrix.qt_modules }}
|
||||||
tools: ${{ matrix.qt_tools }}
|
tools: ${{ matrix.qt_tools }}
|
||||||
cache: ${{ inputs.is_qt_cached }}
|
cache: ${{ inputs.is_qt_cached }}
|
||||||
cache-key-prefix: host-qt-arm64-windows
|
cache-key-prefix: host-qt-arm64-windows
|
||||||
dir: ${{ github.workspace }}\HostQt
|
dir: ${{ github.workspace }}\HostQt
|
||||||
set-env: false
|
set-env: false
|
||||||
|
|
||||||
- name: Install Qt (macOS, Linux, Qt 6 & Windows MSVC)
|
- name: Install Qt (macOS, Linux, Qt 6 & Windows MSVC)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '')
|
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '')
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v3
|
||||||
with:
|
with:
|
||||||
aqtversion: '==3.1.*'
|
aqtversion: "==3.1.*"
|
||||||
py7zrversion: '>=0.20.2'
|
py7zrversion: ">=0.20.2"
|
||||||
version: ${{ matrix.qt_version }}
|
version: ${{ matrix.qt_version }}
|
||||||
host: ${{ matrix.qt_host }}
|
host: ${{ matrix.qt_host }}
|
||||||
target: 'desktop'
|
target: "desktop"
|
||||||
arch: ${{ matrix.qt_arch }}
|
arch: ${{ matrix.qt_arch }}
|
||||||
modules: ${{ matrix.qt_modules }}
|
modules: ${{ matrix.qt_modules }}
|
||||||
tools: ${{ matrix.qt_tools }}
|
tools: ${{ matrix.qt_tools }}
|
||||||
cache: ${{ inputs.is_qt_cached }}
|
cache: ${{ inputs.is_qt_cached }}
|
||||||
|
|
||||||
- name: Install MSVC (Windows MSVC)
|
- name: Install MSVC (Windows MSVC)
|
||||||
if: runner.os == 'Windows' # We want this for MinGW builds as well, as we need SignTool
|
if: runner.os == 'Windows' # We want this for MinGW builds as well, as we need SignTool
|
||||||
uses: ilammy/msvc-dev-cmd@v1
|
uses: ilammy/msvc-dev-cmd@v1
|
||||||
with:
|
with:
|
||||||
vsversion: 2022
|
vsversion: 2022
|
||||||
@ -283,12 +270,12 @@ jobs:
|
|||||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -G Ninja
|
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja
|
||||||
|
|
||||||
- name: Configure CMake (Windows MSVC)
|
- name: Configure CMake (Windows MSVC)
|
||||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON
|
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }}
|
||||||
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
|
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
|
||||||
if ("${{ env.CCACHE_VAR }}")
|
if ("${{ env.CCACHE_VAR }}")
|
||||||
{
|
{
|
||||||
@ -303,7 +290,7 @@ jobs:
|
|||||||
- name: Configure CMake (Linux)
|
- name: Configure CMake (Linux)
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja
|
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -G Ninja
|
||||||
|
|
||||||
##
|
##
|
||||||
# BUILD
|
# BUILD
|
||||||
@ -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
|
Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
|
||||||
|
|
||||||
|
|
||||||
- name: Fetch codesign certificate (Windows)
|
- name: Fetch codesign certificate (Windows)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
shell: bash # yes, we are not using MSYS2 or PowerShell here
|
shell: bash # yes, we are not using MSYS2 or PowerShell here
|
||||||
run: |
|
run: |
|
||||||
echo '${{ secrets.WINDOWS_CODESIGN_CERT }}' | base64 --decode > codesign.pfx
|
echo '${{ secrets.WINDOWS_CODESIGN_CERT }}' | base64 --decode > codesign.pfx
|
||||||
|
|
||||||
@ -415,7 +401,7 @@ jobs:
|
|||||||
if (Get-Content ./codesign.pfx){
|
if (Get-Content ./codesign.pfx){
|
||||||
cd ${{ env.INSTALL_DIR }}
|
cd ${{ env.INSTALL_DIR }}
|
||||||
# We ship the exact same executable for portable and non-portable editions, so signing just once is fine
|
# We ship the exact same executable for portable and non-portable editions, so signing just once is fine
|
||||||
SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_filelink.exe
|
SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_updater.exe prismlauncher_filelink.exe
|
||||||
} else {
|
} else {
|
||||||
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
|
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
|
||||||
}
|
}
|
||||||
@ -507,15 +493,7 @@ jobs:
|
|||||||
export LD_LIBRARY_PATH
|
export LD_LIBRARY_PATH
|
||||||
|
|
||||||
chmod +x AppImageUpdate-x86_64.AppImage
|
chmod +x AppImageUpdate-x86_64.AppImage
|
||||||
./AppImageUpdate-x86_64.AppImage --appimage-extract
|
cp AppImageUpdate-x86_64.AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
|
||||||
|
|
||||||
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/optional
|
|
||||||
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins
|
|
||||||
|
|
||||||
cp -r squashfs-root/usr/bin/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
|
|
||||||
cp -r squashfs-root/usr/lib/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib
|
|
||||||
cp -r squashfs-root/usr/optional/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/optional
|
|
||||||
cp -r squashfs-root/usr/optional/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins
|
|
||||||
|
|
||||||
export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync"
|
export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync"
|
||||||
|
|
||||||
@ -569,14 +547,14 @@ jobs:
|
|||||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ runner.os }}-Qt5-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PrismLauncher.tar.gz
|
path: PrismLauncher.tar.gz
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux, portable, Qt 5)
|
- name: Upload binary tarball (Linux, portable, Qt 5)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: PrismLauncher-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PrismLauncher-portable.tar.gz
|
path: PrismLauncher-portable.tar.gz
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux, Qt 6)
|
- name: Upload binary tarball (Linux, Qt 6)
|
||||||
@ -623,7 +601,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
if: inputs.build_type == 'Debug'
|
if: inputs.build_type == 'Debug'
|
||||||
with:
|
with:
|
||||||
submodules: 'true'
|
submodules: "true"
|
||||||
- name: Build Flatpak (Linux)
|
- name: Build Flatpak (Linux)
|
||||||
if: inputs.build_type == 'Debug'
|
if: inputs.build_type == 'Debug'
|
||||||
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
||||||
|
29
.github/workflows/trigger_builds.yml
vendored
29
.github/workflows/trigger_builds.yml
vendored
@ -3,26 +3,25 @@ name: Build Application
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- 'renovate/**'
|
- "renovate/**"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- "**.md"
|
||||||
- '**/LICENSE'
|
- "**/LICENSE"
|
||||||
- 'flake.lock'
|
- "flake.lock"
|
||||||
- 'packages/**'
|
- "packages/**"
|
||||||
- '.github/ISSUE_TEMPLATE/**'
|
- ".github/ISSUE_TEMPLATE/**"
|
||||||
- '.markdownlint**'
|
- ".markdownlint**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- "**.md"
|
||||||
- '**/LICENSE'
|
- "**/LICENSE"
|
||||||
- 'flake.lock'
|
- "flake.lock"
|
||||||
- 'packages/**'
|
- "packages/**"
|
||||||
- '.github/ISSUE_TEMPLATE/**'
|
- ".github/ISSUE_TEMPLATE/**"
|
||||||
- '.markdownlint**'
|
- ".markdownlint**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
build_debug:
|
build_debug:
|
||||||
name: Build Debug
|
name: Build Debug
|
||||||
uses: ./.github/workflows/build.yml
|
uses: ./.github/workflows/build.yml
|
||||||
@ -34,3 +33,5 @@ jobs:
|
|||||||
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
||||||
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
|
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||||
|
20
.github/workflows/trigger_release.yml
vendored
20
.github/workflows/trigger_release.yml
vendored
@ -3,10 +3,9 @@ name: Build Application and Make Release
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '*'
|
- "*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
build_release:
|
build_release:
|
||||||
name: Build Release
|
name: Build Release
|
||||||
uses: ./.github/workflows/build.yml
|
uses: ./.github/workflows/build.yml
|
||||||
@ -18,6 +17,8 @@ jobs:
|
|||||||
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
||||||
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||||
|
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
|
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||||
|
|
||||||
create_release:
|
create_release:
|
||||||
needs: build_release
|
needs: build_release
|
||||||
@ -28,8 +29,8 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: 'true'
|
submodules: "true"
|
||||||
path: 'PrismLauncher-source'
|
path: "PrismLauncher-source"
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
- name: Grab and store version
|
- name: Grab and store version
|
||||||
@ -41,8 +42,8 @@ jobs:
|
|||||||
mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }}
|
mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }}
|
||||||
mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||||
mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||||
mv PrismLauncher-Linux-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
|
||||||
mv PrismLauncher-Linux*/PrismLauncher.tar.gz PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-Linux-Qt5*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz
|
||||||
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
|
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
|
||||||
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
|
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||||
mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
||||||
@ -86,8 +87,8 @@ jobs:
|
|||||||
draft: true
|
draft: true
|
||||||
prerelease: false
|
prerelease: false
|
||||||
files: |
|
files: |
|
||||||
PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
|
PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz
|
||||||
PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
|
PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
|
||||||
PrismLauncher-Linux-x86_64.AppImage
|
PrismLauncher-Linux-x86_64.AppImage
|
||||||
PrismLauncher-Linux-x86_64.AppImage.zsync
|
PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||||
PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||||
@ -95,9 +96,6 @@ jobs:
|
|||||||
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
|
||||||
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
|
||||||
PrismLauncher-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe
|
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-${{ env.VERSION }}.zip
|
||||||
PrismLauncher-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip
|
||||||
PrismLauncher-Windows-MSVC-arm64-Setup-${{ env.VERSION }}.exe
|
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.
|
# Build platform.
|
||||||
set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
|
set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
|
||||||
|
|
||||||
# Channel list URL
|
# Github repo URL with releases for updater
|
||||||
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
|
set(Launcher_UPDATER_GITHUB_REPO "https://github.com/PrismLauncher/PrismLauncher" CACHE STRING "Base github URL for the updater.")
|
||||||
|
|
||||||
|
# Name to help updater identify valid artifacts
|
||||||
|
set(Launcher_BUILD_ARTIFACT "" CACHE STRING "Artifact name to help the updater identify valid artifacts.")
|
||||||
|
|
||||||
# The metadata server
|
# The metadata server
|
||||||
set(Launcher_META_URL "https://meta.prismlauncher.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.")
|
set(Launcher_META_URL "https://meta.prismlauncher.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.")
|
||||||
@ -245,6 +248,11 @@ set(Launcher_MSA_CLIENT_ID "c36a9fb6-4f2a-41ff-90bd-ae7cc92031eb" CACHE STRING "
|
|||||||
# This key was issued specifically for Prism Launcher
|
# This key was issued specifically for Prism Launcher
|
||||||
set(Launcher_CURSEFORGE_API_KEY "$2a$10$wuAJuNZuted3NORVmpgUC.m8sI.pv1tOPKZyBgLFGjxFp/br0lZCC" CACHE STRING "API key for the CurseForge platform")
|
set(Launcher_CURSEFORGE_API_KEY "$2a$10$wuAJuNZuted3NORVmpgUC.m8sI.pv1tOPKZyBgLFGjxFp/br0lZCC" CACHE STRING "API key for the CurseForge platform")
|
||||||
|
|
||||||
|
set(Launcher_COMPILER_NAME ${CMAKE_CXX_COMPILER_ID})
|
||||||
|
set(Launcher_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION})
|
||||||
|
set(Launcher_COMPILER_TARGET_SYSTEM ${CMAKE_SYSTEM_NAME})
|
||||||
|
set(Launcher_COMPILER_TARGET_SYSTEM_VERSION ${CMAKE_SYSTEM_VERSION})
|
||||||
|
set(Launcher_COMPILER_TARGET_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR})
|
||||||
|
|
||||||
#### Check the current Git commit and branch
|
#### Check the current Git commit and branch
|
||||||
include(GetGitRevisionDescription)
|
include(GetGitRevisionDescription)
|
||||||
|
@ -50,7 +50,7 @@ Feel free to create a GitHub issue if you find a bug or want to suggest a new fe
|
|||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
The translation effort for Prism Launcher is hosted on [Weblate](https://hosted.weblate.org/projects/prismlauncher/launcher/) and information about translating Prism Launcher is available at <https://github.com/PrismLauncher/Translations>
|
The translation effort for Prism Launcher is hosted on [Weblate](https://hosted.weblate.org/projects/prismlauncher/launcher/) and information about translating Prism Launcher is available at <https://github.com/PrismLauncher/Translations>.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <qstringliteral.h>
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
@ -59,8 +60,16 @@ Config::Config()
|
|||||||
VERSION_MINOR = @Launcher_VERSION_MINOR@;
|
VERSION_MINOR = @Launcher_VERSION_MINOR@;
|
||||||
|
|
||||||
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
|
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
|
||||||
|
BUILD_ARTIFACT = "@Launcher_BUILD_ARTIFACT@";
|
||||||
BUILD_DATE = "@Launcher_BUILD_TIMESTAMP@";
|
BUILD_DATE = "@Launcher_BUILD_TIMESTAMP@";
|
||||||
UPDATER_BASE = "@Launcher_UPDATER_BASE@";
|
UPDATER_GITHUB_REPO = "@Launcher_UPDATER_GITHUB_REPO@";
|
||||||
|
|
||||||
|
COMPILER_NAME = "@Launcher_COMPILER_NAME@";
|
||||||
|
COMPILER_VERSION = "@Launcher_COMPILER_VERSION@";
|
||||||
|
|
||||||
|
COMPILER_TARGET_SYSTEM = "@Launcher_COMPILER_TARGET_SYSTEM@";
|
||||||
|
COMPILER_TARGET_SYSTEM_VERSION = "@Launcher_COMPILER_TARGET_SYSTEM_VERSION@";
|
||||||
|
COMPILER_TARGET_SYSTEM_PROCESSOR = "@Launcher_COMPILER_TARGET_PROCESSOR@";
|
||||||
|
|
||||||
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
|
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
|
||||||
MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@";
|
MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@";
|
||||||
@ -68,6 +77,8 @@ Config::Config()
|
|||||||
if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty())
|
if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty())
|
||||||
{
|
{
|
||||||
UPDATER_ENABLED = true;
|
UPDATER_ENABLED = true;
|
||||||
|
} else if(!UPDATER_GITHUB_REPO.isEmpty() && !BUILD_ARTIFACT.isEmpty()) {
|
||||||
|
UPDATER_ENABLED = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
GIT_COMMIT = "@Launcher_GIT_COMMIT@";
|
GIT_COMMIT = "@Launcher_GIT_COMMIT@";
|
||||||
@ -89,9 +100,6 @@ Config::Config()
|
|||||||
{
|
{
|
||||||
VERSION_CHANNEL = GIT_REFSPEC;
|
VERSION_CHANNEL = GIT_REFSPEC;
|
||||||
VERSION_CHANNEL.remove("refs/heads/");
|
VERSION_CHANNEL.remove("refs/heads/");
|
||||||
if(!UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty()) {
|
|
||||||
UPDATER_ENABLED = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (!GIT_COMMIT.isEmpty())
|
else if (!GIT_COMMIT.isEmpty())
|
||||||
{
|
{
|
||||||
@ -136,3 +144,16 @@ QString Config::printableVersionString() const
|
|||||||
}
|
}
|
||||||
return vstr;
|
return vstr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Config::compilerID() const
|
||||||
|
{
|
||||||
|
if (COMPILER_VERSION.isEmpty())
|
||||||
|
return COMPILER_NAME;
|
||||||
|
return QStringLiteral("%1 - %2").arg(COMPILER_NAME).arg(COMPILER_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Config::systemID() const
|
||||||
|
{
|
||||||
|
return QStringLiteral("%1 %2 %3").arg(COMPILER_TARGET_SYSTEM, COMPILER_TARGET_SYSTEM_VERSION, COMPILER_TARGET_SYSTEM_PROCESSOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -71,11 +71,29 @@ class Config {
|
|||||||
/// A short string identifying this build's platform or distribution.
|
/// A short string identifying this build's platform or distribution.
|
||||||
QString BUILD_PLATFORM;
|
QString BUILD_PLATFORM;
|
||||||
|
|
||||||
|
/// A short string identifying this build's valid artifacts int he updater. For example, "lin64" or "win32".
|
||||||
|
QString BUILD_ARTIFACT;
|
||||||
|
|
||||||
/// A string containing the build timestamp
|
/// A string containing the build timestamp
|
||||||
QString BUILD_DATE;
|
QString BUILD_DATE;
|
||||||
|
|
||||||
|
/// A string identifying the compiler use to build
|
||||||
|
QString COMPILER_NAME;
|
||||||
|
|
||||||
|
/// A string identifying the compiler version used to build
|
||||||
|
QString COMPILER_VERSION;
|
||||||
|
|
||||||
|
/// A string identifying the compiler target system os
|
||||||
|
QString COMPILER_TARGET_SYSTEM;
|
||||||
|
|
||||||
|
/// A String identifying the compiler target system version
|
||||||
|
QString COMPILER_TARGET_SYSTEM_VERSION;
|
||||||
|
|
||||||
|
/// A String identifying the compiler target processor
|
||||||
|
QString COMPILER_TARGET_SYSTEM_PROCESSOR;
|
||||||
|
|
||||||
/// URL for the updater's channel
|
/// URL for the updater's channel
|
||||||
QString UPDATER_BASE;
|
QString UPDATER_GITHUB_REPO;
|
||||||
|
|
||||||
/// The public key used to sign releases for the Sparkle updater appcast
|
/// The public key used to sign releases for the Sparkle updater appcast
|
||||||
QString MAC_SPARKLE_PUB_KEY;
|
QString MAC_SPARKLE_PUB_KEY;
|
||||||
@ -175,6 +193,18 @@ class Config {
|
|||||||
* \return The version number in string format (major.minor.revision.build).
|
* \return The version number in string format (major.minor.revision.build).
|
||||||
*/
|
*/
|
||||||
QString printableVersionString() const;
|
QString printableVersionString() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Compiler ID String
|
||||||
|
* \return a string of the form "Name - Version" of just "Name" if the version is empty
|
||||||
|
*/
|
||||||
|
QString compilerID() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief System ID String
|
||||||
|
* \return a string of the form "OS Verison Processor"
|
||||||
|
*/
|
||||||
|
QString systemID() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const Config BuildConfig;
|
extern const Config BuildConfig;
|
||||||
|
@ -89,6 +89,10 @@ function(
|
|||||||
-Wdouble-promotion # warn if float is implicit promoted to double
|
-Wdouble-promotion # warn if float is implicit promoted to double
|
||||||
-Wformat=2 # warn on security issues around functions that format output (ie printf)
|
-Wformat=2 # warn on security issues around functions that format output (ie printf)
|
||||||
-Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation
|
-Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation
|
||||||
|
# -Wgnu-zero-variadic-macro-arguments (part of -pedantic) is triggered by every qCDebug() call and therefore results
|
||||||
|
# in a lot of noise. This warning is only notifying us that clang is emulating the GCC behaviour
|
||||||
|
# instead of the exact standard wording so we can safely ignore it
|
||||||
|
-Wno-gnu-zero-variadic-macro-arguments
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
30
flake.lock
generated
30
flake.lock
generated
@ -3,11 +3,11 @@
|
|||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1673956053,
|
"lastModified": 1696426674,
|
||||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||||
"owner": "edolstra",
|
"owner": "edolstra",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -21,11 +21,11 @@
|
|||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1693611461,
|
"lastModified": 1696343447,
|
||||||
"narHash": "sha256-aPODl8vAgGQ0ZYFIRisxYG5MOGSkIczvu2Cd8Gb9+1Y=",
|
"narHash": "sha256-B2xAZKLkkeRFG5XcHHSXXcP7To9Xzr59KXeZiRf4vdQ=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "7f53fdb7bdc5bb237da7fefef12d099e4fd611ca",
|
"rev": "c9afaba3dfa4085dbd2ccb38dfade5141e33d9d4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -106,11 +106,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1695318763,
|
"lastModified": 1697009197,
|
||||||
"narHash": "sha256-FHVPDRP2AfvsxAdc+AsgFJevMz5VBmnZglFUMlxBkcY=",
|
"narHash": "sha256-viVRhBTFT8fPJTb1N3brQIpFZnttmwo3JVKNuWRVc3s=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "e12483116b3b51a185a33a272bf351e357ba9a99",
|
"rev": "01441e14af5e29c9d27ace398e6dd0b293e25a54",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -123,11 +123,11 @@
|
|||||||
"nixpkgs-lib": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"dir": "lib",
|
"dir": "lib",
|
||||||
"lastModified": 1693471703,
|
"lastModified": 1696019113,
|
||||||
"narHash": "sha256-0l03ZBL8P1P6z8MaSDS/MvuU8E75rVxe5eE1N6gxeTo=",
|
"narHash": "sha256-X3+DKYWJm93DRSdC5M6K5hLqzSya9BjibtBsuARoPco=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "3e52e76b70d5508f3cec70b882a29199f4d1ee85",
|
"rev": "f5892ddac112a1e9b3612c39af1b72987ee5783a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -153,11 +153,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1694364351,
|
"lastModified": 1696846637,
|
||||||
"narHash": "sha256-oadhSCqopYXxURwIA6/Anpe5IAG11q2LhvTJNP5zE6o=",
|
"narHash": "sha256-0hv4kbXxci2+pxhuXlVgftj/Jq79VSmtAyvfabCCtYk=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "pre-commit-hooks.nix",
|
"repo": "pre-commit-hooks.nix",
|
||||||
"rev": "4f883a76282bc28eb952570afc3d8a1bf6f481d7",
|
"rev": "42e1b6095ef80a51f79595d9951eb38e91c4e6ca",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -122,6 +122,7 @@
|
|||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
#include <LocalPeer.h>
|
#include <LocalPeer.h>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
#include <sys.h>
|
#include <sys.h>
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
@ -130,9 +131,13 @@
|
|||||||
#include "gamemode_client.h"
|
#include "gamemode_client.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
|
#if defined(Q_OS_MAC)
|
||||||
|
#if defined(SPARKLE_ENABLED)
|
||||||
#include "updater/MacSparkleUpdater.h"
|
#include "updater/MacSparkleUpdater.h"
|
||||||
#endif
|
#endif
|
||||||
|
#else
|
||||||
|
#include "updater/PrismExternalUpdater.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
#include "WindowsConsole.h"
|
#include "WindowsConsole.h"
|
||||||
@ -164,6 +169,34 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
std::tuple<QDateTime, QString, QString, QString, QString> read_lock_File(const QString& path)
|
||||||
|
{
|
||||||
|
auto contents = QString(FS::read(path));
|
||||||
|
auto lines = contents.split('\n');
|
||||||
|
|
||||||
|
QDateTime timestamp;
|
||||||
|
QString from, to, target, data_path;
|
||||||
|
for (auto line : lines) {
|
||||||
|
auto index = line.indexOf("=");
|
||||||
|
if (index < 0)
|
||||||
|
continue;
|
||||||
|
auto left = line.left(index);
|
||||||
|
auto right = line.mid(index + 1);
|
||||||
|
if (left.toLower() == "timestamp") {
|
||||||
|
timestamp = QDateTime::fromString(right, Qt::ISODate);
|
||||||
|
} else if (left.toLower() == "from") {
|
||||||
|
from = right;
|
||||||
|
} else if (left.toLower() == "to") {
|
||||||
|
to = right;
|
||||||
|
} else if (left.toLower() == "target") {
|
||||||
|
target = right;
|
||||||
|
} else if (left.toLower() == "data_path") {
|
||||||
|
data_path = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::make_tuple(timestamp, from, to, target, data_path);
|
||||||
|
}
|
||||||
|
|
||||||
Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||||
{
|
{
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
@ -296,6 +329,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
.arg(dataPath));
|
.arg(dataPath));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_dataPath = dataPath;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Establish the mechanism for communication with an already running PrismLauncher that uses the same data path.
|
* Establish the mechanism for communication with an already running PrismLauncher that uses the same data path.
|
||||||
@ -450,11 +484,16 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
|
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME) << ", (c) 2022-2023 "
|
||||||
|
<< qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
|
||||||
qDebug() << "Version : " << BuildConfig.printableVersionString();
|
qDebug() << "Version : " << BuildConfig.printableVersionString();
|
||||||
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
|
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
|
||||||
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
|
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
|
||||||
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
|
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
|
||||||
|
qDebug() << "Compiled for : " << BuildConfig.systemID();
|
||||||
|
qDebug() << "Compiled by : " << BuildConfig.compilerID();
|
||||||
|
qDebug() << "Build Artifact : " << BuildConfig.BUILD_ARTIFACT;
|
||||||
|
qDebug() << "Updates Enabled : " << (updaterEnabled() ? "Yes" : "No");
|
||||||
if (adjustedBy.size()) {
|
if (adjustedBy.size()) {
|
||||||
qDebug() << "Work dir before adjustment : " << origcwdPath;
|
qDebug() << "Work dir before adjustment : " << origcwdPath;
|
||||||
qDebug() << "Work dir after adjustment : " << QDir::currentPath();
|
qDebug() << "Work dir after adjustment : " << QDir::currentPath();
|
||||||
@ -582,6 +621,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
m_settings->registerSetting("IgnoreJavaCompatibility", false);
|
m_settings->registerSetting("IgnoreJavaCompatibility", false);
|
||||||
m_settings->registerSetting("IgnoreJavaWizard", false);
|
m_settings->registerSetting("IgnoreJavaWizard", false);
|
||||||
|
|
||||||
|
// Legacy settings
|
||||||
|
m_settings->registerSetting("OnlineFixes", false);
|
||||||
|
|
||||||
// Native library workarounds
|
// Native library workarounds
|
||||||
m_settings->registerSetting("UseNativeOpenAL", false);
|
m_settings->registerSetting("UseNativeOpenAL", false);
|
||||||
m_settings->registerSetting("CustomOpenALPath", "");
|
m_settings->registerSetting("CustomOpenALPath", "");
|
||||||
@ -738,15 +780,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
qDebug() << "<> Translations loaded.";
|
qDebug() << "<> Translations loaded.";
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize the updater
|
|
||||||
if (BuildConfig.UPDATER_ENABLED) {
|
|
||||||
qDebug() << "Initializing updater";
|
|
||||||
#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
|
|
||||||
m_updater.reset(new MacSparkleUpdater());
|
|
||||||
#endif
|
|
||||||
qDebug() << "<> Updater started.";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instance icons
|
// Instance icons
|
||||||
{
|
{
|
||||||
auto setting = APPLICATION->settings()->getSetting("IconsDir");
|
auto setting = APPLICATION->settings()->getSetting("IconsDir");
|
||||||
@ -849,6 +882,107 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
detectLibraries();
|
detectLibraries();
|
||||||
|
|
||||||
|
// check update locks
|
||||||
|
{
|
||||||
|
auto update_log_path = FS::PathCombine(m_dataPath, "logs", "prism_launcher_update.log");
|
||||||
|
|
||||||
|
auto update_lock = QFileInfo(FS::PathCombine(m_dataPath, ".prism_launcher_update.lock"));
|
||||||
|
if (update_lock.exists()) {
|
||||||
|
auto [timestamp, from, to, target, data_path] = read_lock_File(update_lock.absoluteFilePath());
|
||||||
|
auto infoMsg = tr("This installation has a update lock file present at: %1\n"
|
||||||
|
"\n"
|
||||||
|
"Timestamp: %2\n"
|
||||||
|
"Updating from version %3 to %4\n"
|
||||||
|
"Target install path: %5\n"
|
||||||
|
"Data Path: %6"
|
||||||
|
"\n"
|
||||||
|
"This likely means that a update attempt failed. Please ensure your installation is in working order before "
|
||||||
|
"proceeding.\n"
|
||||||
|
"Check the Prism Launcher updater log at: \n"
|
||||||
|
"%7\n"
|
||||||
|
"for details on the last update attempt.\n"
|
||||||
|
"\n"
|
||||||
|
"To delete this lock and proceed select \"Ignore\" below.")
|
||||||
|
.arg(update_lock.absoluteFilePath())
|
||||||
|
.arg(timestamp.toString(Qt::ISODate), from, to, target, data_path)
|
||||||
|
.arg(update_log_path);
|
||||||
|
auto msgBox = QMessageBox(QMessageBox::Warning, tr("Update In Progress"), infoMsg, QMessageBox::Ignore | QMessageBox::Abort);
|
||||||
|
msgBox.setDefaultButton(QMessageBox::Abort);
|
||||||
|
msgBox.setModal(true);
|
||||||
|
msgBox.setDetailedText(FS::read(update_log_path));
|
||||||
|
msgBox.setMinimumWidth(460);
|
||||||
|
msgBox.adjustSize();
|
||||||
|
auto res = msgBox.exec();
|
||||||
|
switch (res) {
|
||||||
|
case QMessageBox::Ignore: {
|
||||||
|
FS::deletePath(update_lock.absoluteFilePath());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case QMessageBox::Abort:
|
||||||
|
[[fallthrough]];
|
||||||
|
default: {
|
||||||
|
qDebug() << "Exiting because update lockfile is present";
|
||||||
|
QMetaObject::invokeMethod(
|
||||||
|
this, []() { exit(1); }, Qt::QueuedConnection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto update_fail_marker = QFileInfo(FS::PathCombine(m_dataPath, ".prism_launcher_update.fail"));
|
||||||
|
if (update_fail_marker.exists()) {
|
||||||
|
auto infoMsg = tr("An update attempt failed\n"
|
||||||
|
"\n"
|
||||||
|
"Please ensure your installation is in working order before "
|
||||||
|
"proceeding.\n"
|
||||||
|
"Check the Prism Launcher updater log at: \n"
|
||||||
|
"%1\n"
|
||||||
|
"for details on the last update attempt.")
|
||||||
|
.arg(update_log_path);
|
||||||
|
auto msgBox = QMessageBox(QMessageBox::Warning, tr("Update Failed"), infoMsg, QMessageBox::Ignore | QMessageBox::Abort);
|
||||||
|
msgBox.setDefaultButton(QMessageBox::Abort);
|
||||||
|
msgBox.setModal(true);
|
||||||
|
msgBox.setDetailedText(FS::read(update_log_path));
|
||||||
|
msgBox.setMinimumWidth(460);
|
||||||
|
msgBox.adjustSize();
|
||||||
|
auto res = msgBox.exec();
|
||||||
|
switch (res) {
|
||||||
|
case QMessageBox::Ignore: {
|
||||||
|
FS::deletePath(update_fail_marker.absoluteFilePath());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case QMessageBox::Abort:
|
||||||
|
[[fallthrough]];
|
||||||
|
default: {
|
||||||
|
qDebug() << "Exiting because update lockfile is present";
|
||||||
|
QMetaObject::invokeMethod(
|
||||||
|
this, []() { exit(1); }, Qt::QueuedConnection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto update_success_marker = QFileInfo(FS::PathCombine(m_dataPath, ".prism_launcher_update.success"));
|
||||||
|
if (update_success_marker.exists()) {
|
||||||
|
auto infoMsg = tr("Update succeeded\n"
|
||||||
|
"\n"
|
||||||
|
"You are now running %1 .\n"
|
||||||
|
"Check the Prism Launcher updater log at: \n"
|
||||||
|
"%1\n"
|
||||||
|
"for details.")
|
||||||
|
.arg(BuildConfig.printableVersionString())
|
||||||
|
.arg(update_log_path);
|
||||||
|
auto msgBox = new QMessageBox(QMessageBox::Information, tr("Update Succeeded"), infoMsg, QMessageBox::Ok);
|
||||||
|
msgBox->setDefaultButton(QMessageBox::Ok);
|
||||||
|
msgBox->setDetailedText(FS::read(update_log_path));
|
||||||
|
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
msgBox->setMinimumWidth(460);
|
||||||
|
msgBox->adjustSize();
|
||||||
|
msgBox->open();
|
||||||
|
FS::deletePath(update_success_marker.absoluteFilePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (createSetupWizard()) {
|
if (createSetupWizard()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -917,6 +1051,26 @@ bool Application::createSetupWizard()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Application::updaterEnabled()
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_MAC)
|
||||||
|
return BuildConfig.UPDATER_ENABLED;
|
||||||
|
#else
|
||||||
|
return BuildConfig.UPDATER_ENABLED && QFileInfo(FS::PathCombine(m_rootPath, updaterBinaryName())).isFile();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Application::updaterBinaryName()
|
||||||
|
{
|
||||||
|
auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME);
|
||||||
|
#if defined Q_OS_WIN32
|
||||||
|
exe_name.append(".exe");
|
||||||
|
#else
|
||||||
|
exe_name.prepend("bin/");
|
||||||
|
#endif
|
||||||
|
return exe_name;
|
||||||
|
}
|
||||||
|
|
||||||
bool Application::event(QEvent* event)
|
bool Application::event(QEvent* event)
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
@ -985,6 +1139,20 @@ void Application::performMainStartupAction()
|
|||||||
showMainWindow(false);
|
showMainWindow(false);
|
||||||
qDebug() << "<> Main window shown.";
|
qDebug() << "<> Main window shown.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialize the updater
|
||||||
|
if (updaterEnabled()) {
|
||||||
|
qDebug() << "Initializing updater";
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
#if defined(SPARKLE_ENABLED)
|
||||||
|
m_updater.reset(new MacSparkleUpdater());
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
m_updater.reset(new PrismExternalUpdater(m_mainWindow, m_rootPath, m_dataPath));
|
||||||
|
#endif
|
||||||
|
qDebug() << "<> Updater started.";
|
||||||
|
}
|
||||||
|
|
||||||
if (!m_urlsToImport.isEmpty()) {
|
if (!m_urlsToImport.isEmpty()) {
|
||||||
qDebug() << "<> Importing from url:" << m_urlsToImport;
|
qDebug() << "<> Importing from url:" << m_urlsToImport;
|
||||||
m_mainWindow->processURLs(m_urlsToImport);
|
m_mainWindow->processURLs(m_urlsToImport);
|
||||||
|
@ -159,6 +159,9 @@ class Application : public QApplication {
|
|||||||
/// this is the root of the 'installation'. Used for automatic updates
|
/// this is the root of the 'installation'. Used for automatic updates
|
||||||
const QString& root() { return m_rootPath; }
|
const QString& root() { return m_rootPath; }
|
||||||
|
|
||||||
|
/// the data path the application is using
|
||||||
|
const QString& dataRoot() { return m_dataPath; }
|
||||||
|
|
||||||
bool isPortable() { return m_portable; }
|
bool isPortable() { return m_portable; }
|
||||||
|
|
||||||
const Capabilities capabilities() { return m_capabilities; }
|
const Capabilities capabilities() { return m_capabilities; }
|
||||||
@ -179,6 +182,9 @@ class Application : public QApplication {
|
|||||||
|
|
||||||
int suitableMaxMem();
|
int suitableMaxMem();
|
||||||
|
|
||||||
|
bool updaterEnabled();
|
||||||
|
QString updaterBinaryName();
|
||||||
|
|
||||||
QUrl normalizeImportUrl(QString const& url);
|
QUrl normalizeImportUrl(QString const& url);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@ -244,6 +250,7 @@ class Application : public QApplication {
|
|||||||
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;
|
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;
|
||||||
|
|
||||||
QString m_rootPath;
|
QString m_rootPath;
|
||||||
|
QString m_dataPath;
|
||||||
Status m_status = Application::StartingUp;
|
Status m_status = Application::StartingUp;
|
||||||
Capabilities m_capabilities;
|
Capabilities m_capabilities;
|
||||||
bool m_portable = false;
|
bool m_portable = false;
|
||||||
|
@ -181,6 +181,11 @@ set(MAC_UPDATE_SOURCES
|
|||||||
updater/MacSparkleUpdater.mm
|
updater/MacSparkleUpdater.mm
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(PRISM_UPDATE_SOURCES
|
||||||
|
updater/PrismExternalUpdater.h
|
||||||
|
updater/PrismExternalUpdater.cpp
|
||||||
|
)
|
||||||
|
|
||||||
# Backend for the news bar... there's usually no news.
|
# Backend for the news bar... there's usually no news.
|
||||||
set(NEWS_SOURCES
|
set(NEWS_SOURCES
|
||||||
# News System
|
# News System
|
||||||
@ -216,13 +221,9 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/auth/MinecraftAccount.h
|
minecraft/auth/MinecraftAccount.h
|
||||||
minecraft/auth/Parsers.cpp
|
minecraft/auth/Parsers.cpp
|
||||||
minecraft/auth/Parsers.h
|
minecraft/auth/Parsers.h
|
||||||
minecraft/auth/Yggdrasil.cpp
|
|
||||||
minecraft/auth/Yggdrasil.h
|
|
||||||
|
|
||||||
minecraft/auth/flows/AuthFlow.cpp
|
minecraft/auth/flows/AuthFlow.cpp
|
||||||
minecraft/auth/flows/AuthFlow.h
|
minecraft/auth/flows/AuthFlow.h
|
||||||
minecraft/auth/flows/Mojang.cpp
|
|
||||||
minecraft/auth/flows/Mojang.h
|
|
||||||
minecraft/auth/flows/MSA.cpp
|
minecraft/auth/flows/MSA.cpp
|
||||||
minecraft/auth/flows/MSA.h
|
minecraft/auth/flows/MSA.h
|
||||||
minecraft/auth/flows/Offline.cpp
|
minecraft/auth/flows/Offline.cpp
|
||||||
@ -236,12 +237,8 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/auth/steps/GetSkinStep.h
|
minecraft/auth/steps/GetSkinStep.h
|
||||||
minecraft/auth/steps/LauncherLoginStep.cpp
|
minecraft/auth/steps/LauncherLoginStep.cpp
|
||||||
minecraft/auth/steps/LauncherLoginStep.h
|
minecraft/auth/steps/LauncherLoginStep.h
|
||||||
minecraft/auth/steps/MigrationEligibilityStep.cpp
|
|
||||||
minecraft/auth/steps/MigrationEligibilityStep.h
|
|
||||||
minecraft/auth/steps/MinecraftProfileStep.cpp
|
minecraft/auth/steps/MinecraftProfileStep.cpp
|
||||||
minecraft/auth/steps/MinecraftProfileStep.h
|
minecraft/auth/steps/MinecraftProfileStep.h
|
||||||
minecraft/auth/steps/MinecraftProfileStepMojang.cpp
|
|
||||||
minecraft/auth/steps/MinecraftProfileStepMojang.h
|
|
||||||
minecraft/auth/steps/MSAStep.cpp
|
minecraft/auth/steps/MSAStep.cpp
|
||||||
minecraft/auth/steps/MSAStep.h
|
minecraft/auth/steps/MSAStep.h
|
||||||
minecraft/auth/steps/XboxAuthorizationStep.cpp
|
minecraft/auth/steps/XboxAuthorizationStep.cpp
|
||||||
@ -250,8 +247,6 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/auth/steps/XboxProfileStep.h
|
minecraft/auth/steps/XboxProfileStep.h
|
||||||
minecraft/auth/steps/XboxUserStep.cpp
|
minecraft/auth/steps/XboxUserStep.cpp
|
||||||
minecraft/auth/steps/XboxUserStep.h
|
minecraft/auth/steps/XboxUserStep.h
|
||||||
minecraft/auth/steps/YggdrasilStep.cpp
|
|
||||||
minecraft/auth/steps/YggdrasilStep.h
|
|
||||||
|
|
||||||
minecraft/gameoptions/GameOptions.h
|
minecraft/gameoptions/GameOptions.h
|
||||||
minecraft/gameoptions/GameOptions.cpp
|
minecraft/gameoptions/GameOptions.cpp
|
||||||
@ -589,6 +584,63 @@ set(LINKEXE_SOURCES
|
|||||||
DesktopServices.cpp
|
DesktopServices.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(PRISMUPDATER_SOURCES
|
||||||
|
updater/prismupdater/PrismUpdater.h
|
||||||
|
updater/prismupdater/PrismUpdater.cpp
|
||||||
|
updater/prismupdater/UpdaterDialogs.h
|
||||||
|
updater/prismupdater/UpdaterDialogs.cpp
|
||||||
|
updater/prismupdater/GitHubRelease.h
|
||||||
|
updater/prismupdater/GitHubRelease.cpp
|
||||||
|
|
||||||
|
Json.h
|
||||||
|
Json.cpp
|
||||||
|
FileSystem.h
|
||||||
|
FileSystem.cpp
|
||||||
|
StringUtils.h
|
||||||
|
StringUtils.cpp
|
||||||
|
DesktopServices.h
|
||||||
|
DesktopServices.cpp
|
||||||
|
Version.h
|
||||||
|
Version.cpp
|
||||||
|
Markdown.h
|
||||||
|
Markdown.cpp
|
||||||
|
|
||||||
|
# Zip
|
||||||
|
MMCZip.h
|
||||||
|
MMCZip.cpp
|
||||||
|
|
||||||
|
# Time
|
||||||
|
MMCTime.h
|
||||||
|
MMCTime.cpp
|
||||||
|
|
||||||
|
net/ByteArraySink.h
|
||||||
|
net/ChecksumValidator.h
|
||||||
|
net/Download.cpp
|
||||||
|
net/Download.h
|
||||||
|
net/FileSink.cpp
|
||||||
|
net/FileSink.h
|
||||||
|
net/HttpMetaCache.cpp
|
||||||
|
net/HttpMetaCache.h
|
||||||
|
net/Logging.h
|
||||||
|
net/Logging.cpp
|
||||||
|
net/NetAction.h
|
||||||
|
net/NetRequest.cpp
|
||||||
|
net/NetRequest.h
|
||||||
|
net/NetJob.cpp
|
||||||
|
net/NetJob.h
|
||||||
|
net/NetUtils.h
|
||||||
|
net/Sink.h
|
||||||
|
net/Validator.h
|
||||||
|
net/HeaderProxy.h
|
||||||
|
net/RawHeaderProxy.h
|
||||||
|
|
||||||
|
ui/dialogs/ProgressDialog.cpp
|
||||||
|
ui/dialogs/ProgressDialog.h
|
||||||
|
ui/widgets/SubTaskProgressBar.h
|
||||||
|
ui/widgets/SubTaskProgressBar.cpp
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
######## Logging categories ########
|
######## Logging categories ########
|
||||||
|
|
||||||
ecm_qt_declare_logging_category(CORE_SOURCES
|
ecm_qt_declare_logging_category(CORE_SOURCES
|
||||||
@ -685,6 +737,8 @@ set(LOGIC_SOURCES
|
|||||||
|
|
||||||
if(APPLE AND Launcher_ENABLE_UPDATER)
|
if(APPLE AND Launcher_ENABLE_UPDATER)
|
||||||
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES})
|
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES})
|
||||||
|
else()
|
||||||
|
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${PRISM_UPDATE_SOURCES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
SET(LAUNCHER_SOURCES
|
SET(LAUNCHER_SOURCES
|
||||||
@ -916,6 +970,9 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/pages/modplatform/ImportPage.cpp
|
ui/pages/modplatform/ImportPage.cpp
|
||||||
ui/pages/modplatform/ImportPage.h
|
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.cpp
|
||||||
ui/pages/modplatform/modrinth/ModrinthResourceModels.h
|
ui/pages/modplatform/modrinth/ModrinthResourceModels.h
|
||||||
ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
|
ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
|
||||||
@ -944,8 +1001,6 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/dialogs/IconPickerDialog.h
|
ui/dialogs/IconPickerDialog.h
|
||||||
ui/dialogs/ImportResourceDialog.cpp
|
ui/dialogs/ImportResourceDialog.cpp
|
||||||
ui/dialogs/ImportResourceDialog.h
|
ui/dialogs/ImportResourceDialog.h
|
||||||
ui/dialogs/LoginDialog.cpp
|
|
||||||
ui/dialogs/LoginDialog.h
|
|
||||||
ui/dialogs/MSALoginDialog.cpp
|
ui/dialogs/MSALoginDialog.cpp
|
||||||
ui/dialogs/MSALoginDialog.h
|
ui/dialogs/MSALoginDialog.h
|
||||||
ui/dialogs/OfflineLoginDialog.cpp
|
ui/dialogs/OfflineLoginDialog.cpp
|
||||||
@ -1042,6 +1097,15 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/instanceview/VisualGroup.h
|
ui/instanceview/VisualGroup.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (NOT Apple)
|
||||||
|
set(LAUNCHER_SOURCES
|
||||||
|
${LAUNCHER_SOURCES}
|
||||||
|
|
||||||
|
ui/dialogs/UpdateAvailableDialog.h
|
||||||
|
ui/dialogs/UpdateAvailableDialog.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(LAUNCHER_SOURCES
|
set(LAUNCHER_SOURCES
|
||||||
WindowsConsole.cpp
|
WindowsConsole.cpp
|
||||||
@ -1080,6 +1144,7 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/pages/modplatform/legacy_ftb/Page.ui
|
ui/pages/modplatform/legacy_ftb/Page.ui
|
||||||
ui/pages/modplatform/import_ftb/ImportFTBPage.ui
|
ui/pages/modplatform/import_ftb/ImportFTBPage.ui
|
||||||
ui/pages/modplatform/ImportPage.ui
|
ui/pages/modplatform/ImportPage.ui
|
||||||
|
ui/pages/modplatform/OptionalModDialog.ui
|
||||||
ui/pages/modplatform/modrinth/ModrinthPage.ui
|
ui/pages/modplatform/modrinth/ModrinthPage.ui
|
||||||
ui/pages/modplatform/technic/TechnicPage.ui
|
ui/pages/modplatform/technic/TechnicPage.ui
|
||||||
ui/widgets/InstanceCardWidget.ui
|
ui/widgets/InstanceCardWidget.ui
|
||||||
@ -1104,7 +1169,6 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/dialogs/MSALoginDialog.ui
|
ui/dialogs/MSALoginDialog.ui
|
||||||
ui/dialogs/OfflineLoginDialog.ui
|
ui/dialogs/OfflineLoginDialog.ui
|
||||||
ui/dialogs/AboutDialog.ui
|
ui/dialogs/AboutDialog.ui
|
||||||
ui/dialogs/LoginDialog.ui
|
|
||||||
ui/dialogs/EditAccountDialog.ui
|
ui/dialogs/EditAccountDialog.ui
|
||||||
ui/dialogs/ReviewMessageBox.ui
|
ui/dialogs/ReviewMessageBox.ui
|
||||||
ui/dialogs/ScrollMessageBox.ui
|
ui/dialogs/ScrollMessageBox.ui
|
||||||
@ -1112,6 +1176,14 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/dialogs/ChooseProviderDialog.ui
|
ui/dialogs/ChooseProviderDialog.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
|
qt_wrap_ui(PRISM_UPDATE_UI
|
||||||
|
ui/dialogs/UpdateAvailableDialog.ui
|
||||||
|
)
|
||||||
|
|
||||||
|
if (NOT Apple)
|
||||||
|
set (LAUNCHER_UI ${LAUNCHER_UI} ${PRISM_UPDATE_UI})
|
||||||
|
endif()
|
||||||
|
|
||||||
qt_add_resources(LAUNCHER_RESOURCES
|
qt_add_resources(LAUNCHER_RESOURCES
|
||||||
resources/backgrounds/backgrounds.qrc
|
resources/backgrounds/backgrounds.qrc
|
||||||
resources/multimc/multimc.qrc
|
resources/multimc/multimc.qrc
|
||||||
@ -1128,6 +1200,12 @@ qt_add_resources(LAUNCHER_RESOURCES
|
|||||||
../${Launcher_Branding_LogoQRC}
|
../${Launcher_Branding_LogoQRC}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
qt_wrap_ui(PRISMUPDATER_UI
|
||||||
|
updater/prismupdater/SelectReleaseDialog.ui
|
||||||
|
ui/widgets/SubTaskProgressBar.ui
|
||||||
|
ui/dialogs/ProgressDialog.ui
|
||||||
|
)
|
||||||
|
|
||||||
######## Windows resource files ########
|
######## Windows resource files ########
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
|
set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
|
||||||
@ -1146,6 +1224,7 @@ set_project_warnings(Launcher_logic
|
|||||||
"${Launcher_GCC_WARNINGS}")
|
"${Launcher_GCC_WARNINGS}")
|
||||||
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
|
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
|
||||||
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
|
||||||
target_link_libraries(Launcher_logic
|
target_link_libraries(Launcher_logic
|
||||||
systeminfo
|
systeminfo
|
||||||
Launcher_murmur2
|
Launcher_murmur2
|
||||||
@ -1227,7 +1306,45 @@ install(TARGETS ${Launcher_Name}
|
|||||||
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
|
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WIN32)
|
if(NOT APPLE OR (DEFINED Launcher_BUILD_UPDATER AND Launcher_BUILD_UPDATER))
|
||||||
|
# Updater
|
||||||
|
add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI})
|
||||||
|
target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
target_link_libraries(prism_updater_logic
|
||||||
|
QuaZip::QuaZip
|
||||||
|
${ZLIB_LIBRARIES}
|
||||||
|
systeminfo
|
||||||
|
BuildConfig
|
||||||
|
ghcFilesystem::ghc_filesystem
|
||||||
|
Qt${QT_VERSION_MAJOR}::Widgets
|
||||||
|
Qt${QT_VERSION_MAJOR}::Core
|
||||||
|
Qt${QT_VERSION_MAJOR}::Network
|
||||||
|
${Launcher_QT_LIBS}
|
||||||
|
cmark::cmark
|
||||||
|
Katabasis
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
|
||||||
|
target_sources("${Launcher_Name}_updater" PRIVATE updater/prismupdater/updater.exe.manifest)
|
||||||
|
target_link_libraries("${Launcher_Name}_updater" prism_updater_logic)
|
||||||
|
|
||||||
|
if(DEFINED Launcher_APP_BINARY_NAME)
|
||||||
|
set_target_properties("${Launcher_Name}_updater" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_updater")
|
||||||
|
endif()
|
||||||
|
if(DEFINED Launcher_BINARY_RPATH)
|
||||||
|
SET_TARGET_PROPERTIES("${Launcher_Name}_updater" PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
install(TARGETS "${Launcher_Name}_updater"
|
||||||
|
BUNDLE DESTINATION "." COMPONENT Runtime
|
||||||
|
LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime
|
||||||
|
RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime
|
||||||
|
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER))
|
||||||
|
# File link
|
||||||
add_library(filelink_logic STATIC ${LINKEXE_SOURCES})
|
add_library(filelink_logic STATIC ${LINKEXE_SOURCES})
|
||||||
set_project_warnings(filelink_logic
|
set_project_warnings(filelink_logic
|
||||||
"${Launcher_MSVC_WARNINGS}"
|
"${Launcher_MSVC_WARNINGS}"
|
||||||
@ -1246,7 +1363,7 @@ if(WIN32)
|
|||||||
${Launcher_QT_LIBS}
|
${Launcher_QT_LIBS}
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable("${Launcher_Name}_filelink" WIN32 filelink/main.cpp)
|
add_executable("${Launcher_Name}_filelink" WIN32 filelink/filelink_main.cpp)
|
||||||
|
|
||||||
target_sources("${Launcher_Name}_filelink" PRIVATE filelink/filelink.exe.manifest)
|
target_sources("${Launcher_Name}_filelink" PRIVATE filelink/filelink.exe.manifest)
|
||||||
|
|
||||||
|
@ -194,6 +194,40 @@ void write(const QString& filename, const QByteArray& data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void appendSafe(const QString& filename, const QByteArray& data)
|
||||||
|
{
|
||||||
|
ensureExists(QFileInfo(filename).dir());
|
||||||
|
QByteArray buffer;
|
||||||
|
try {
|
||||||
|
buffer = read(filename);
|
||||||
|
} catch (FileSystemException&) {
|
||||||
|
buffer = QByteArray();
|
||||||
|
}
|
||||||
|
buffer.append(data);
|
||||||
|
QSaveFile file(filename);
|
||||||
|
if (!file.open(QSaveFile::WriteOnly)) {
|
||||||
|
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
||||||
|
}
|
||||||
|
if (buffer.size() != file.write(buffer)) {
|
||||||
|
throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
|
||||||
|
}
|
||||||
|
if (!file.commit()) {
|
||||||
|
throw FileSystemException("Error while committing data to " + filename + ": " + file.errorString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void append(const QString& filename, const QByteArray& data)
|
||||||
|
{
|
||||||
|
ensureExists(QFileInfo(filename).dir());
|
||||||
|
QFile file(filename);
|
||||||
|
if (!file.open(QFile::Append)) {
|
||||||
|
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
||||||
|
}
|
||||||
|
if (data.size() != file.write(data)) {
|
||||||
|
throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray read(const QString& filename)
|
QByteArray read(const QString& filename)
|
||||||
{
|
{
|
||||||
QFile file(filename);
|
QFile file(filename);
|
||||||
@ -238,6 +272,28 @@ bool ensureFolderPathExists(QString foldernamepath)
|
|||||||
return success;
|
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
|
* @brief Copies a directory and it's contents from src to dest
|
||||||
* @param offset subdirectory form src to copy 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)
|
if (!m_followSymlinks)
|
||||||
opt |= copy_opts::copy_symlinks;
|
opt |= copy_opts::copy_symlinks;
|
||||||
|
|
||||||
|
if (m_overwrite)
|
||||||
|
opt |= copy_opts::overwrite_existing;
|
||||||
|
|
||||||
// Function that'll do the actual copying
|
// Function that'll do the actual copying
|
||||||
auto copy_file = [&](QString src_path, QString relative_dst_path) {
|
auto copy_file = [&](QString src_path, QString relative_dst_path) {
|
||||||
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
|
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
|
||||||
@ -273,6 +332,9 @@ bool copy::operator()(const QString& offset, bool dryRun)
|
|||||||
auto dst_path = PathCombine(dst, relative_dst_path);
|
auto dst_path = PathCombine(dst, relative_dst_path);
|
||||||
if (!dryRun) {
|
if (!dryRun) {
|
||||||
ensureFilePathExists(dst_path);
|
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);
|
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
|
||||||
}
|
}
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -61,6 +61,16 @@ class FileSystemException : public ::Exception {
|
|||||||
*/
|
*/
|
||||||
void write(const QString& filename, const QByteArray& data);
|
void write(const QString& filename, const QByteArray& data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* append data to a file safely
|
||||||
|
*/
|
||||||
|
void appendSafe(const QString& filename, const QByteArray& data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* append data to a file
|
||||||
|
*/
|
||||||
|
void append(const QString& filename, const QByteArray& data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* read data from a file safely\
|
* read data from a file safely\
|
||||||
*/
|
*/
|
||||||
@ -109,6 +119,11 @@ class copy : public QObject {
|
|||||||
m_whitelist = whitelist;
|
m_whitelist = whitelist;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
copy& overwrite(const bool overwrite)
|
||||||
|
{
|
||||||
|
m_overwrite = overwrite;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
|
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
|
||||||
|
|
||||||
@ -128,6 +143,7 @@ class copy : public QObject {
|
|||||||
bool m_followSymlinks = true;
|
bool m_followSymlinks = true;
|
||||||
const IPathMatcher* m_matcher = nullptr;
|
const IPathMatcher* m_matcher = nullptr;
|
||||||
bool m_whitelist = false;
|
bool m_whitelist = false;
|
||||||
|
bool m_overwrite = false;
|
||||||
QDir m_src;
|
QDir m_src;
|
||||||
QDir m_dst;
|
QDir m_dst;
|
||||||
qsizetype m_copied;
|
qsizetype m_copied;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
/*
|
/*
|
||||||
* Prism Launcher - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* 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
|
* 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
|
* 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();
|
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);
|
auto inst = getInstanceById(id);
|
||||||
if (!inst) {
|
if (!inst) {
|
||||||
qDebug() << "Attempt to set a null instance's group";
|
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());
|
auto iter = m_instanceGroupIndex.find(inst->id());
|
||||||
if (iter != m_instanceGroupIndex.end()) {
|
if (iter != m_instanceGroupIndex.end()) {
|
||||||
if (*iter != name) {
|
if (*iter != name) {
|
||||||
|
decreaseGroupCount(*iter);
|
||||||
*iter = name;
|
*iter = name;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@ -258,7 +263,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
m_groupNameCache.insert(name);
|
increaseGroupCount(name);
|
||||||
auto idx = getInstIndex(inst.get());
|
auto idx = getInstIndex(inst.get());
|
||||||
emit dataChanged(index(idx), index(idx), { GroupRole });
|
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
@ -267,29 +272,55 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
|||||||
|
|
||||||
QStringList InstanceList::getGroups()
|
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;
|
bool removed = false;
|
||||||
qDebug() << "Delete group" << name;
|
qDebug() << "Delete group" << name;
|
||||||
for (auto& instance : m_instances) {
|
for (auto& instance : m_instances) {
|
||||||
const auto& instID = instance->id();
|
const QString& instID = instance->id();
|
||||||
auto instGroupName = getInstanceGroup(instID);
|
const QString instGroupName = getInstanceGroup(instID);
|
||||||
if (instGroupName == name) {
|
if (instGroupName == name) {
|
||||||
m_instanceGroupIndex.remove(instID);
|
m_instanceGroupIndex.remove(instID);
|
||||||
qDebug() << "Remove" << instID << "from group" << name;
|
qDebug() << "Remove" << instID << "from group" << name;
|
||||||
removed = true;
|
removed = true;
|
||||||
auto idx = getInstIndex(instance.get());
|
auto idx = getInstIndex(instance.get());
|
||||||
if (idx > 0) {
|
if (idx > 0)
|
||||||
emit dataChanged(index(idx), index(idx), { GroupRole });
|
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (removed) {
|
if (removed)
|
||||||
saveGroupList();
|
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)
|
bool InstanceList::isGroupCollapsed(const QString& group)
|
||||||
@ -305,12 +336,13 @@ bool InstanceList::trashInstance(const InstanceId& id)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto cachedGroupId = m_instanceGroupIndex[id];
|
QString cachedGroupId = m_instanceGroupIndex[id];
|
||||||
|
|
||||||
qDebug() << "Will trash instance" << id;
|
qDebug() << "Will trash instance" << id;
|
||||||
QString trashedLoc;
|
QString trashedLoc;
|
||||||
|
|
||||||
if (m_instanceGroupIndex.remove(id)) {
|
if (m_instanceGroupIndex.remove(id)) {
|
||||||
|
decreaseGroupCount(cachedGroupId);
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,7 +380,7 @@ void InstanceList::undoTrashInstance()
|
|||||||
QFile(top.trashPath).rename(top.polyPath);
|
QFile(top.trashPath).rename(top.polyPath);
|
||||||
|
|
||||||
m_instanceGroupIndex[top.id] = top.groupName;
|
m_instanceGroupIndex[top.id] = top.groupName;
|
||||||
m_groupNameCache.insert(top.groupName);
|
increaseGroupCount(top.groupName);
|
||||||
|
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
emit instancesChanged();
|
emit instancesChanged();
|
||||||
@ -362,7 +394,10 @@ void InstanceList::deleteInstance(const InstanceId& id)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString cachedGroupId = m_instanceGroupIndex[id];
|
||||||
|
|
||||||
if (m_instanceGroupIndex.remove(id)) {
|
if (m_instanceGroupIndex.remove(id)) {
|
||||||
|
decreaseGroupCount(cachedGroupId);
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -610,6 +645,25 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
|
|||||||
return inst;
|
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()
|
void InstanceList::saveGroupList()
|
||||||
{
|
{
|
||||||
qDebug() << "Will save group list now.";
|
qDebug() << "Will save group list now.";
|
||||||
@ -621,7 +675,7 @@ void InstanceList::saveGroupList()
|
|||||||
QString groupFileName = m_instDir + "/instgroups.json";
|
QString groupFileName = m_instDir + "/instgroups.json";
|
||||||
QMap<QString, QSet<QString>> reverseGroupMap;
|
QMap<QString, QSet<QString>> reverseGroupMap;
|
||||||
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) {
|
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) {
|
||||||
QString id = iter.key();
|
const QString& id = iter.key();
|
||||||
QString group = iter.value();
|
QString group = iter.value();
|
||||||
if (group.isEmpty())
|
if (group.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
@ -711,17 +765,22 @@ void InstanceList::loadGroupList()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSet<QString> groupSet;
|
|
||||||
m_instanceGroupIndex.clear();
|
m_instanceGroupIndex.clear();
|
||||||
|
m_groupNameCache.clear();
|
||||||
|
|
||||||
// Iterate through all the groups.
|
// Iterate through all the groups.
|
||||||
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
||||||
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) {
|
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) {
|
||||||
QString groupName = iter.key();
|
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 not an object, complain and skip to the next one.
|
||||||
if (!iter.value().isObject()) {
|
if (!iter.value().isObject()) {
|
||||||
qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
|
qWarning() << QString("Group '%1' in the group list should be an object").arg(groupName).toUtf8();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -733,23 +792,19 @@ void InstanceList::loadGroupList()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep a list/set of groups for choosing
|
|
||||||
groupSet.insert(groupName);
|
|
||||||
|
|
||||||
auto hidden = groupObj.value("hidden").toBool(false);
|
auto hidden = groupObj.value("hidden").toBool(false);
|
||||||
if (hidden) {
|
if (hidden)
|
||||||
m_collapsedGroups.insert(groupName);
|
m_collapsedGroups.insert(groupName);
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate through the list of instances in the group.
|
// Iterate through the list of instances in the group.
|
||||||
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
||||||
|
|
||||||
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++) {
|
for (auto value : instancesArray) {
|
||||||
m_instanceGroupIndex[(*iter2).toString()] = groupName;
|
m_instanceGroupIndex[value.toString()] = groupName;
|
||||||
|
increaseGroupCount(groupName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_groupsLoaded = true;
|
m_groupsLoaded = true;
|
||||||
m_groupNameCache.unite(groupSet);
|
|
||||||
qDebug() << "Group list loaded.";
|
qDebug() << "Group list loaded.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -925,7 +980,7 @@ bool InstanceList::commitStagedInstance(const QString& path,
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_instanceGroupIndex[instID] = groupName;
|
m_instanceGroupIndex[instID] = groupName;
|
||||||
m_groupNameCache.insert(groupName);
|
increaseGroupCount(groupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceSet.insert(instID);
|
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");
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* you may not use this file except in compliance with the License.
|
* it under the terms of the GNU General Public License as published by
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* You should have received a copy of the GNU General Public License
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
*
|
||||||
* See the License for the specific language governing permissions and
|
* This file incorporates work covered by the following copyright and
|
||||||
* limitations under the License.
|
* 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
|
#pragma once
|
||||||
@ -86,9 +106,10 @@ class InstanceList : public QAbstractListModel {
|
|||||||
bool isGroupCollapsed(const QString& groupName);
|
bool isGroupCollapsed(const QString& groupName);
|
||||||
|
|
||||||
GroupId getInstanceGroup(const InstanceId& id) const;
|
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 deleteGroup(const GroupId& name);
|
||||||
|
void renameGroup(const GroupId& src, const GroupId& dst);
|
||||||
bool trashInstance(const InstanceId& id);
|
bool trashInstance(const InstanceId& id);
|
||||||
bool trashedSomething();
|
bool trashedSomething();
|
||||||
void undoTrashInstance();
|
void undoTrashInstance();
|
||||||
@ -158,12 +179,16 @@ class InstanceList : public QAbstractListModel {
|
|||||||
QList<InstanceId> discoverInstances();
|
QList<InstanceId> discoverInstances();
|
||||||
InstancePtr loadInstance(const InstanceId& id);
|
InstancePtr loadInstance(const InstanceId& id);
|
||||||
|
|
||||||
|
void increaseGroupCount(const QString& group);
|
||||||
|
void decreaseGroupCount(const QString& group);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_watchLevel = 0;
|
int m_watchLevel = 0;
|
||||||
int totalPlayTime = 0;
|
int totalPlayTime = 0;
|
||||||
bool m_dirty = false;
|
bool m_dirty = false;
|
||||||
QList<InstancePtr> m_instances;
|
QList<InstancePtr> m_instances;
|
||||||
QSet<QString> m_groupNameCache;
|
// id -> refs
|
||||||
|
QMap<QString, int> m_groupNameCache;
|
||||||
|
|
||||||
SettingsObjectPtr m_globalSettings;
|
SettingsObjectPtr m_globalSettings;
|
||||||
QString m_instDir;
|
QString m_instDir;
|
||||||
|
@ -88,8 +88,8 @@ void LaunchController::decideAccount()
|
|||||||
if (accounts->count() <= 0) {
|
if (accounts->count() <= 0) {
|
||||||
// Tell the user they need to log in at least one account in order to play.
|
// Tell the user they need to log in at least one account in order to play.
|
||||||
auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"),
|
auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"),
|
||||||
tr("In order to play Minecraft, you must have at least one Microsoft or Mojang "
|
tr("In order to play Minecraft, you must have at least one Microsoft "
|
||||||
"account logged in. Mojang accounts can only be used offline. "
|
"account which owns Minecraft logged in."
|
||||||
"Would you like to open the account manager to add an account now?"),
|
"Would you like to open the account manager to add an account now?"),
|
||||||
QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)
|
QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)
|
||||||
->exec();
|
->exec();
|
||||||
|
@ -42,7 +42,11 @@
|
|||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
#include <QtConcurrentRun>
|
#include <QtConcurrentRun>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace MMCZip {
|
namespace MMCZip {
|
||||||
// ours
|
// ours
|
||||||
@ -132,6 +136,7 @@ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
// ours
|
// ours
|
||||||
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods)
|
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods)
|
||||||
{
|
{
|
||||||
@ -217,6 +222,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root)
|
QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root)
|
||||||
@ -422,6 +428,7 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
void ExportToZipTask::executeTask()
|
void ExportToZipTask::executeTask()
|
||||||
{
|
{
|
||||||
setStatus("Adding files...");
|
setStatus("Adding files...");
|
||||||
@ -500,5 +507,6 @@ bool ExportToZipTask::abort()
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace MMCZip
|
} // namespace MMCZip
|
@ -48,7 +48,10 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
#include "minecraft/mod/Mod.h"
|
#include "minecraft/mod/Mod.h"
|
||||||
|
#endif
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
namespace MMCZip {
|
namespace MMCZip {
|
||||||
@ -79,11 +82,12 @@ bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool follow
|
|||||||
*/
|
*/
|
||||||
bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false);
|
bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false);
|
||||||
|
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
/**
|
/**
|
||||||
* take a source jar, add mods to it, resulting in target jar
|
* take a source jar, add mods to it, resulting in target jar
|
||||||
*/
|
*/
|
||||||
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods);
|
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods);
|
||||||
|
#endif
|
||||||
/**
|
/**
|
||||||
* Find a single file in archive by file name (not path)
|
* Find a single file in archive by file name (not path)
|
||||||
*
|
*
|
||||||
@ -147,6 +151,7 @@ bool extractFile(QString fileCompressed, QString file, QString dir);
|
|||||||
*/
|
*/
|
||||||
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter);
|
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter);
|
||||||
|
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
class ExportToZipTask : public Task {
|
class ExportToZipTask : public Task {
|
||||||
public:
|
public:
|
||||||
ExportToZipTask(QString outputPath, QDir dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
|
ExportToZipTask(QString outputPath, QDir dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
|
||||||
@ -189,4 +194,5 @@ class ExportToZipTask : public Task {
|
|||||||
QFuture<ZipResult> m_build_zip_future;
|
QFuture<ZipResult> m_build_zip_future;
|
||||||
QFutureWatcher<ZipResult> m_build_zip_watcher;
|
QFutureWatcher<ZipResult> m_build_zip_watcher;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
} // namespace MMCZip
|
} // namespace MMCZip
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "StringUtils.h"
|
#include "StringUtils.h"
|
||||||
|
#include <qpair.h>
|
||||||
|
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
@ -149,7 +150,7 @@ QString StringUtils::truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((url_compact.length() >= max_len) && hard_limit) {
|
if ((url_compact.length() >= max_len) && hard_limit) {
|
||||||
// still too long, truncate normaly
|
// still too long, truncate normally
|
||||||
url_compact = QString(str_url);
|
url_compact = QString(str_url);
|
||||||
auto to_remove = url_compact.length() - max_len + 3;
|
auto to_remove = url_compact.length() - max_len + 3;
|
||||||
url_compact.remove(url_compact.length() - to_remove - 1, to_remove);
|
url_compact.remove(url_compact.length() - to_remove - 1, to_remove);
|
||||||
@ -182,3 +183,32 @@ QString StringUtils::getRandomAlphaNumeric()
|
|||||||
{
|
{
|
||||||
return QUuid::createUuid().toString(QUuid::Id128);
|
return QUuid::createUuid().toString(QUuid::Id128);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPair<QString, QString> StringUtils::splitFirst(const QString& s, const QString& sep, Qt::CaseSensitivity cs)
|
||||||
|
{
|
||||||
|
QString left, right;
|
||||||
|
auto index = s.indexOf(sep, 0, cs);
|
||||||
|
left = s.mid(0, index);
|
||||||
|
right = s.mid(index + sep.length());
|
||||||
|
return qMakePair(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPair<QString, QString> StringUtils::splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs)
|
||||||
|
{
|
||||||
|
QString left, right;
|
||||||
|
auto index = s.indexOf(sep, 0, cs);
|
||||||
|
left = s.mid(0, index);
|
||||||
|
right = s.mid(left.length() + 1);
|
||||||
|
return qMakePair(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPair<QString, QString> StringUtils::splitFirst(const QString& s, const QRegularExpression& re)
|
||||||
|
{
|
||||||
|
QString left, right;
|
||||||
|
QRegularExpressionMatch match;
|
||||||
|
auto index = s.indexOf(re, 0, &match);
|
||||||
|
left = s.mid(0, index);
|
||||||
|
auto end = match.hasMatch() ? left.length() + match.capturedLength() : left.length() + 1;
|
||||||
|
right = s.mid(end);
|
||||||
|
return qMakePair(left, right);
|
||||||
|
}
|
||||||
|
@ -36,8 +36,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QPair>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace StringUtils {
|
namespace StringUtils {
|
||||||
|
|
||||||
@ -70,12 +72,17 @@ int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs)
|
|||||||
/**
|
/**
|
||||||
* @brief Truncate a url while keeping its readability py placing the `...` in the middle of the path
|
* @brief Truncate a url while keeping its readability py placing the `...` in the middle of the path
|
||||||
* @param url Url to truncate
|
* @param url Url to truncate
|
||||||
* @param max_len max lenght of url in charaters
|
* @param max_len max length of url in characters
|
||||||
* @param hard_limit if truncating the path can't get the url short enough, truncate it normaly.
|
* @param hard_limit if truncating the path can't get the url short enough, truncate it normally.
|
||||||
*/
|
*/
|
||||||
QString truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_limit = false);
|
QString truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_limit = false);
|
||||||
|
|
||||||
QString humanReadableFileSize(double bytes, bool use_si = false, int decimal_points = 1);
|
QString humanReadableFileSize(double bytes, bool use_si = false, int decimal_points = 1);
|
||||||
|
|
||||||
QString getRandomAlphaNumeric();
|
QString getRandomAlphaNumeric();
|
||||||
|
|
||||||
|
QPair<QString, QString> splitFirst(const QString& s, const QString& sep, Qt::CaseSensitivity cs = Qt::CaseSensitive);
|
||||||
|
QPair<QString, QString> splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs = Qt::CaseSensitive);
|
||||||
|
QPair<QString, QString> splitFirst(const QString& s, const QRegularExpression& re);
|
||||||
|
|
||||||
} // namespace StringUtils
|
} // namespace StringUtils
|
||||||
|
@ -56,6 +56,7 @@ class Version {
|
|||||||
bool operator!=(const Version& other) const;
|
bool operator!=(const Version& other) const;
|
||||||
|
|
||||||
QString toString() const { return m_string; }
|
QString toString() const { return m_string; }
|
||||||
|
bool isEmpty() const { return m_string.isEmpty(); }
|
||||||
|
|
||||||
friend QDebug operator<<(QDebug debug, const Version& v);
|
friend QDebug operator<<(QDebug debug, const Version& v);
|
||||||
|
|
||||||
|
@ -93,6 +93,7 @@ FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv),
|
|||||||
joinServer(serverToJoin);
|
joinServer(serverToJoin);
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "no server to join";
|
qDebug() << "no server to join";
|
||||||
|
m_status = Failed;
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,6 +109,7 @@ void FileLinkApp::joinServer(QString server)
|
|||||||
connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
|
connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
|
||||||
|
|
||||||
connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) {
|
connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) {
|
||||||
|
m_status = Failed;
|
||||||
switch (socketError) {
|
switch (socketError) {
|
||||||
case QLocalSocket::ServerNotFoundError:
|
case QLocalSocket::ServerNotFoundError:
|
||||||
qDebug()
|
qDebug()
|
||||||
@ -132,6 +134,7 @@ void FileLinkApp::joinServer(QString server)
|
|||||||
|
|
||||||
connect(&socket, &QLocalSocket::disconnected, this, [&]() {
|
connect(&socket, &QLocalSocket::disconnected, this, [&]() {
|
||||||
qDebug() << "disconnected from server, should exit";
|
qDebug() << "disconnected from server, should exit";
|
||||||
|
m_status = Succeeded;
|
||||||
exit();
|
exit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -41,8 +41,10 @@ class FileLinkApp : public QCoreApplication {
|
|||||||
// friends for the purpose of limiting access to deprecated stuff
|
// friends for the purpose of limiting access to deprecated stuff
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
enum Status { Starting, Failed, Succeeded, Initialized };
|
||||||
FileLinkApp(int& argc, char** argv);
|
FileLinkApp(int& argc, char** argv);
|
||||||
virtual ~FileLinkApp();
|
virtual ~FileLinkApp();
|
||||||
|
Status status() const { return m_status; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void joinServer(QString server);
|
void joinServer(QString server);
|
||||||
@ -50,6 +52,8 @@ class FileLinkApp : public QCoreApplication {
|
|||||||
void runLink();
|
void runLink();
|
||||||
void sendResults();
|
void sendResults();
|
||||||
|
|
||||||
|
Status m_status = Status::Starting;
|
||||||
|
|
||||||
bool m_useHardLinks = false;
|
bool m_useHardLinks = false;
|
||||||
|
|
||||||
QDateTime m_startTime;
|
QDateTime m_startTime;
|
||||||
|
@ -26,5 +26,16 @@ int main(int argc, char* argv[])
|
|||||||
{
|
{
|
||||||
FileLinkApp ldh(argc, argv);
|
FileLinkApp ldh(argc, argv);
|
||||||
|
|
||||||
return ldh.exec();
|
switch (ldh.status()) {
|
||||||
|
case FileLinkApp::Starting:
|
||||||
|
case FileLinkApp::Initialized: {
|
||||||
|
return ldh.exec();
|
||||||
|
}
|
||||||
|
case FileLinkApp::Failed:
|
||||||
|
return 1;
|
||||||
|
case FileLinkApp::Succeeded:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
@ -24,11 +24,11 @@
|
|||||||
struct JavaInstall : public BaseVersion {
|
struct JavaInstall : public BaseVersion {
|
||||||
JavaInstall() {}
|
JavaInstall() {}
|
||||||
JavaInstall(QString id, QString arch, QString path) : id(id), arch(arch), path(path) {}
|
JavaInstall(QString id, QString arch, QString path) : id(id), arch(arch), path(path) {}
|
||||||
virtual QString descriptor() { return id.toString(); }
|
virtual QString descriptor() override { return id.toString(); }
|
||||||
|
|
||||||
virtual QString name() { return id.toString(); }
|
virtual QString name() override { return id.toString(); }
|
||||||
|
|
||||||
virtual QString typeString() const { return arch; }
|
virtual QString typeString() const override { return arch; }
|
||||||
|
|
||||||
virtual bool operator<(BaseVersion& a) override;
|
virtual bool operator<(BaseVersion& a) override;
|
||||||
virtual bool operator>(BaseVersion& a) override;
|
virtual bool operator>(BaseVersion& a) override;
|
||||||
|
@ -403,6 +403,14 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
scanJavaDirs("/opt/jdks");
|
scanJavaDirs("/opt/jdks");
|
||||||
// flatpak
|
// flatpak
|
||||||
scanJavaDirs("/app/jdk");
|
scanJavaDirs("/app/jdk");
|
||||||
|
|
||||||
|
auto home = qEnvironmentVariable("HOME");
|
||||||
|
|
||||||
|
// javas downloaded by IntelliJ
|
||||||
|
scanJavaDirs(FS::PathCombine(home, ".jdks"));
|
||||||
|
// javas downloaded by sdkman
|
||||||
|
scanJavaDirs(FS::PathCombine(home, ".sdkman/candidates/java"));
|
||||||
|
|
||||||
javas = addJavasFromEnv(javas);
|
javas = addJavasFromEnv(javas);
|
||||||
javas.removeDuplicates();
|
javas.removeDuplicates();
|
||||||
return javas;
|
return javas;
|
||||||
|
@ -45,10 +45,12 @@ QString JavaVersion::toString() const
|
|||||||
|
|
||||||
bool JavaVersion::requiresPermGen()
|
bool JavaVersion::requiresPermGen()
|
||||||
{
|
{
|
||||||
if (m_parseable) {
|
return !m_parseable || m_major < 8;
|
||||||
return m_major < 8;
|
}
|
||||||
}
|
|
||||||
return true;
|
bool JavaVersion::isModular()
|
||||||
|
{
|
||||||
|
return m_parseable && m_major >= 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JavaVersion::operator<(const JavaVersion& rhs)
|
bool JavaVersion::operator<(const JavaVersion& rhs)
|
||||||
|
@ -25,6 +25,8 @@ class JavaVersion {
|
|||||||
|
|
||||||
bool requiresPermGen();
|
bool requiresPermGen();
|
||||||
|
|
||||||
|
bool isModular();
|
||||||
|
|
||||||
QString toString() const;
|
QString toString() const;
|
||||||
|
|
||||||
int major() { return m_major; }
|
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("CloseAfterLaunch"), miscellaneousOverride);
|
||||||
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), 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");
|
m_settings->set("InstanceType", "OneSix");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -513,20 +517,28 @@ QStringList MinecraftInstance::javaArguments()
|
|||||||
|
|
||||||
args << "-Duser.language=en";
|
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;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MinecraftInstance::getLauncher()
|
QString MinecraftInstance::getLauncher()
|
||||||
{
|
{
|
||||||
auto profile = m_components->getProfile();
|
|
||||||
|
|
||||||
// use legacy launcher if the traits are set
|
// 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 "legacy";
|
||||||
|
|
||||||
return "standard";
|
return "standard";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MinecraftInstance::shouldApplyOnlineFixes()
|
||||||
|
{
|
||||||
|
return traits().contains("legacyServices") && settings()->get("OnlineFixes").toBool();
|
||||||
|
}
|
||||||
|
|
||||||
QMap<QString, QString> MinecraftInstance::getVariables()
|
QMap<QString, QString> MinecraftInstance::getVariables()
|
||||||
{
|
{
|
||||||
QMap<QString, QString> out;
|
QMap<QString, QString> out;
|
||||||
@ -716,6 +728,9 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
|
|||||||
launchScript += "traits " + trait + "\n";
|
launchScript += "traits " + trait + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldApplyOnlineFixes())
|
||||||
|
launchScript += "onlineFixes true\n";
|
||||||
|
|
||||||
launchScript += "launcher " + getLauncher() + "\n";
|
launchScript += "launcher " + getLauncher() + "\n";
|
||||||
|
|
||||||
// qDebug() << "Generated launch script:" << launchScript;
|
// qDebug() << "Generated launch script:" << launchScript;
|
||||||
@ -856,9 +871,6 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
|
|||||||
if (sessionRef.access_token != "0") {
|
if (sessionRef.access_token != "0") {
|
||||||
addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
|
addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
|
||||||
}
|
}
|
||||||
if (sessionRef.client_token.size()) {
|
|
||||||
addToFilter(sessionRef.client_token, tr("<CLIENT TOKEN>"));
|
|
||||||
}
|
|
||||||
addToFilter(sessionRef.uuid, tr("<PROFILE ID>"));
|
addToFilter(sessionRef.uuid, tr("<PROFILE ID>"));
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
|
@ -129,6 +129,7 @@ class MinecraftInstance : public BaseInstance {
|
|||||||
/// get arguments passed to java
|
/// get arguments passed to java
|
||||||
QStringList javaArguments();
|
QStringList javaArguments();
|
||||||
QString getLauncher();
|
QString getLauncher();
|
||||||
|
bool shouldApplyOnlineFixes();
|
||||||
|
|
||||||
/// get variables for launch command variable substitution/environment
|
/// get variables for launch command variable substitution/environment
|
||||||
QMap<QString, QString> getVariables() override;
|
QMap<QString, QString> getVariables() override;
|
||||||
|
@ -1018,8 +1018,7 @@ std::optional<ModPlatform::ModLoaderTypes> PackProfile::getSupportedModLoaders()
|
|||||||
// TODO: remove this or add version condition once Quilt drops official Fabric support
|
// TODO: remove this or add version condition once Quilt drops official Fabric support
|
||||||
if (loaders & ModPlatform::Quilt)
|
if (loaders & ModPlatform::Quilt)
|
||||||
loaders |= ModPlatform::Fabric;
|
loaders |= ModPlatform::Fabric;
|
||||||
// TODO: remove this or add version condition once NeoForge drops official Forge support
|
if (getComponentVersion("net.minecraft") == "1.20.1" && (loaders & ModPlatform::NeoForge))
|
||||||
if (loaders & ModPlatform::NeoForge)
|
|
||||||
loaders |= ModPlatform::Forge;
|
loaders |= ModPlatform::Forge;
|
||||||
return loaders;
|
return loaders;
|
||||||
}
|
}
|
||||||
|
@ -278,67 +278,6 @@ bool entitlementFromJSONV3(const QJsonObject& parent, MinecraftEntitlement& out)
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool AccountData::resumeStateFromV2(QJsonObject data)
|
|
||||||
{
|
|
||||||
// The JSON object must at least have a username for it to be valid.
|
|
||||||
if (!data.value("username").isString()) {
|
|
||||||
qCritical() << "Can't load Mojang account info from JSON object. Username field is missing or of the wrong type.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString userName = data.value("username").toString("");
|
|
||||||
QString clientToken = data.value("clientToken").toString("");
|
|
||||||
QString accessToken = data.value("accessToken").toString("");
|
|
||||||
|
|
||||||
QJsonArray profileArray = data.value("profiles").toArray();
|
|
||||||
if (profileArray.size() < 1) {
|
|
||||||
qCritical() << "Can't load Mojang account with username \"" << userName << "\". No profiles found.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AccountProfile {
|
|
||||||
QString id;
|
|
||||||
QString name;
|
|
||||||
bool legacy;
|
|
||||||
};
|
|
||||||
|
|
||||||
QList<AccountProfile> profiles;
|
|
||||||
int currentProfileIndex = 0;
|
|
||||||
int index = -1;
|
|
||||||
QString currentProfile = data.value("activeProfile").toString("");
|
|
||||||
for (QJsonValue profileVal : profileArray) {
|
|
||||||
index++;
|
|
||||||
QJsonObject profileObject = profileVal.toObject();
|
|
||||||
QString id = profileObject.value("id").toString("");
|
|
||||||
QString name = profileObject.value("name").toString("");
|
|
||||||
bool legacy_ = profileObject.value("legacy").toBool(false);
|
|
||||||
if (id.isEmpty() || name.isEmpty()) {
|
|
||||||
qWarning() << "Unable to load a profile" << name << "because it was missing an ID or a name.";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (id == currentProfile) {
|
|
||||||
currentProfileIndex = index;
|
|
||||||
}
|
|
||||||
profiles.append({ id, name, legacy_ });
|
|
||||||
}
|
|
||||||
auto& profile = profiles[currentProfileIndex];
|
|
||||||
|
|
||||||
type = AccountType::Mojang;
|
|
||||||
legacy = profile.legacy;
|
|
||||||
|
|
||||||
minecraftProfile.id = profile.id;
|
|
||||||
minecraftProfile.name = profile.name;
|
|
||||||
minecraftProfile.validity = Katabasis::Validity::Assumed;
|
|
||||||
|
|
||||||
yggdrasilToken.token = accessToken;
|
|
||||||
yggdrasilToken.extra["clientToken"] = clientToken;
|
|
||||||
yggdrasilToken.extra["userName"] = userName;
|
|
||||||
yggdrasilToken.validity = Katabasis::Validity::Assumed;
|
|
||||||
|
|
||||||
validity_ = minecraftProfile.validity;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AccountData::resumeStateFromV3(QJsonObject data)
|
bool AccountData::resumeStateFromV3(QJsonObject data)
|
||||||
{
|
{
|
||||||
auto typeV = data.value("type");
|
auto typeV = data.value("type");
|
||||||
@ -349,8 +288,6 @@ bool AccountData::resumeStateFromV3(QJsonObject data)
|
|||||||
auto typeS = typeV.toString();
|
auto typeS = typeV.toString();
|
||||||
if (typeS == "MSA") {
|
if (typeS == "MSA") {
|
||||||
type = AccountType::MSA;
|
type = AccountType::MSA;
|
||||||
} else if (typeS == "Mojang") {
|
|
||||||
type = AccountType::Mojang;
|
|
||||||
} else if (typeS == "Offline") {
|
} else if (typeS == "Offline") {
|
||||||
type = AccountType::Offline;
|
type = AccountType::Offline;
|
||||||
} else {
|
} else {
|
||||||
@ -358,11 +295,6 @@ bool AccountData::resumeStateFromV3(QJsonObject data)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == AccountType::Mojang) {
|
|
||||||
legacy = data.value("legacy").toBool(false);
|
|
||||||
canMigrateToMSA = data.value("canMigrateToMSA").toBool(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == AccountType::MSA) {
|
if (type == AccountType::MSA) {
|
||||||
auto clientIDV = data.value("msa-client-id");
|
auto clientIDV = data.value("msa-client-id");
|
||||||
if (clientIDV.isString()) {
|
if (clientIDV.isString()) {
|
||||||
@ -395,15 +327,7 @@ bool AccountData::resumeStateFromV3(QJsonObject data)
|
|||||||
QJsonObject AccountData::saveState() const
|
QJsonObject AccountData::saveState() const
|
||||||
{
|
{
|
||||||
QJsonObject output;
|
QJsonObject output;
|
||||||
if (type == AccountType::Mojang) {
|
if (type == AccountType::MSA) {
|
||||||
output["type"] = "Mojang";
|
|
||||||
if (legacy) {
|
|
||||||
output["legacy"] = true;
|
|
||||||
}
|
|
||||||
if (canMigrateToMSA) {
|
|
||||||
output["canMigrateToMSA"] = true;
|
|
||||||
}
|
|
||||||
} else if (type == AccountType::MSA) {
|
|
||||||
output["type"] = "MSA";
|
output["type"] = "MSA";
|
||||||
output["msa-client-id"] = msaClientID;
|
output["msa-client-id"] = msaClientID;
|
||||||
tokenToJSONV3(output, msaToken, "msa");
|
tokenToJSONV3(output, msaToken, "msa");
|
||||||
@ -420,51 +344,11 @@ QJsonObject AccountData::saveState() const
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AccountData::userName() const
|
|
||||||
{
|
|
||||||
if (type == AccountType::MSA) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
return yggdrasilToken.extra["userName"].toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AccountData::accessToken() const
|
QString AccountData::accessToken() const
|
||||||
{
|
{
|
||||||
return yggdrasilToken.token;
|
return yggdrasilToken.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AccountData::clientToken() const
|
|
||||||
{
|
|
||||||
if (type != AccountType::Mojang) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
return yggdrasilToken.extra["clientToken"].toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AccountData::setClientToken(QString clientToken)
|
|
||||||
{
|
|
||||||
if (type != AccountType::Mojang) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
yggdrasilToken.extra["clientToken"] = clientToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AccountData::generateClientTokenIfMissing()
|
|
||||||
{
|
|
||||||
if (yggdrasilToken.extra.contains("clientToken")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
invalidateClientToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AccountData::invalidateClientToken()
|
|
||||||
{
|
|
||||||
if (type != AccountType::Mojang) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]"));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AccountData::profileId() const
|
QString AccountData::profileId() const
|
||||||
{
|
{
|
||||||
return minecraftProfile.id;
|
return minecraftProfile.id;
|
||||||
@ -482,9 +366,6 @@ QString AccountData::profileName() const
|
|||||||
QString AccountData::accountDisplayString() const
|
QString AccountData::accountDisplayString() const
|
||||||
{
|
{
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case AccountType::Mojang: {
|
|
||||||
return userName();
|
|
||||||
}
|
|
||||||
case AccountType::Offline: {
|
case AccountType::Offline: {
|
||||||
return QObject::tr("<Offline>");
|
return QObject::tr("<Offline>");
|
||||||
}
|
}
|
||||||
|
@ -71,27 +71,17 @@ struct MinecraftProfile {
|
|||||||
Katabasis::Validity validity = Katabasis::Validity::None;
|
Katabasis::Validity validity = Katabasis::Validity::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class AccountType { MSA, Mojang, Offline };
|
enum class AccountType { MSA, Offline };
|
||||||
|
|
||||||
enum class AccountState { Unchecked, Offline, Working, Online, Disabled, Errored, Expired, Gone };
|
enum class AccountState { Unchecked, Offline, Working, Online, Disabled, Errored, Expired, Gone };
|
||||||
|
|
||||||
struct AccountData {
|
struct AccountData {
|
||||||
QJsonObject saveState() const;
|
QJsonObject saveState() const;
|
||||||
bool resumeStateFromV2(QJsonObject data);
|
|
||||||
bool resumeStateFromV3(QJsonObject data);
|
bool resumeStateFromV3(QJsonObject data);
|
||||||
|
|
||||||
//! userName for Mojang accounts, gamertag for MSA
|
//! userName for Mojang accounts, gamertag for MSA
|
||||||
QString accountDisplayString() const;
|
QString accountDisplayString() const;
|
||||||
|
|
||||||
//! Only valid for Mojang accounts. MSA does not preserve this information
|
|
||||||
QString userName() const;
|
|
||||||
|
|
||||||
//! Only valid for Mojang accounts.
|
|
||||||
QString clientToken() const;
|
|
||||||
void setClientToken(QString clientToken);
|
|
||||||
void invalidateClientToken();
|
|
||||||
void generateClientTokenIfMissing();
|
|
||||||
|
|
||||||
//! Yggdrasil access token, as passed to the game.
|
//! Yggdrasil access token, as passed to the game.
|
||||||
QString accessToken() const;
|
QString accessToken() const;
|
||||||
|
|
||||||
@ -101,8 +91,6 @@ struct AccountData {
|
|||||||
QString lastError() const;
|
QString lastError() const;
|
||||||
|
|
||||||
AccountType type = AccountType::MSA;
|
AccountType type = AccountType::MSA;
|
||||||
bool legacy = false;
|
|
||||||
bool canMigrateToMSA = false;
|
|
||||||
|
|
||||||
QString msaClientID;
|
QString msaClientID;
|
||||||
Katabasis::Token msaToken;
|
Katabasis::Token msaToken;
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
enum AccountListVersion { MojangOnly = 2, MojangMSA = 3 };
|
enum AccountListVersion { MojangMSA = 3 };
|
||||||
|
|
||||||
AccountList::AccountList(QObject* parent) : QAbstractListModel(parent)
|
AccountList::AccountList(QObject* parent) : QAbstractListModel(parent)
|
||||||
{
|
{
|
||||||
@ -320,17 +320,6 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case MigrationColumn: {
|
|
||||||
if (account->isMSA() || account->isOffline()) {
|
|
||||||
return tr("N/A", "Can Migrate");
|
|
||||||
}
|
|
||||||
if (account->canMigrate()) {
|
|
||||||
return tr("Yes", "Can Migrate");
|
|
||||||
} else {
|
|
||||||
return tr("No", "Can Migrate");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
@ -366,8 +355,6 @@ QVariant AccountList::headerData(int section, [[maybe_unused]] Qt::Orientation o
|
|||||||
return tr("Type");
|
return tr("Type");
|
||||||
case StatusColumn:
|
case StatusColumn:
|
||||||
return tr("Status");
|
return tr("Status");
|
||||||
case MigrationColumn:
|
|
||||||
return tr("Can Migrate?");
|
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
@ -379,11 +366,9 @@ QVariant AccountList::headerData(int section, [[maybe_unused]] Qt::Orientation o
|
|||||||
case NameColumn:
|
case NameColumn:
|
||||||
return tr("User name of the account.");
|
return tr("User name of the account.");
|
||||||
case TypeColumn:
|
case TypeColumn:
|
||||||
return tr("Type of the account - Mojang or MSA.");
|
return tr("Type of the account (MSA or Offline)");
|
||||||
case StatusColumn:
|
case StatusColumn:
|
||||||
return tr("Current status of the account.");
|
return tr("Current status of the account.");
|
||||||
case MigrationColumn:
|
|
||||||
return tr("Can this account migrate to a Microsoft account?");
|
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
@ -473,9 +458,6 @@ bool AccountList::loadList()
|
|||||||
// Make sure the format version matches.
|
// Make sure the format version matches.
|
||||||
auto listVersion = root.value("formatVersion").toVariant().toInt();
|
auto listVersion = root.value("formatVersion").toVariant().toInt();
|
||||||
switch (listVersion) {
|
switch (listVersion) {
|
||||||
case AccountListVersion::MojangOnly: {
|
|
||||||
return loadV2(root);
|
|
||||||
} break;
|
|
||||||
case AccountListVersion::MojangMSA: {
|
case AccountListVersion::MojangMSA: {
|
||||||
return loadV3(root);
|
return loadV3(root);
|
||||||
} break;
|
} break;
|
||||||
@ -489,36 +471,6 @@ bool AccountList::loadList()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AccountList::loadV2(QJsonObject& root)
|
|
||||||
{
|
|
||||||
beginResetModel();
|
|
||||||
auto defaultUserName = root.value("activeAccount").toString("");
|
|
||||||
QJsonArray accounts = root.value("accounts").toArray();
|
|
||||||
for (QJsonValue accountVal : accounts) {
|
|
||||||
QJsonObject accountObj = accountVal.toObject();
|
|
||||||
MinecraftAccountPtr account = MinecraftAccount::loadFromJsonV2(accountObj);
|
|
||||||
if (account.get() != nullptr) {
|
|
||||||
auto profileId = account->profileId();
|
|
||||||
if (!profileId.size()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (findAccountByProfileId(profileId) != -1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
connect(account.get(), &MinecraftAccount::changed, this, &AccountList::accountChanged);
|
|
||||||
connect(account.get(), &MinecraftAccount::activityChanged, this, &AccountList::accountActivityChanged);
|
|
||||||
m_accounts.append(account);
|
|
||||||
if (defaultUserName.size() && account->mojangUserName() == defaultUserName) {
|
|
||||||
m_defaultAccount = account;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qWarning() << "Failed to load an account.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
endResetModel();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AccountList::loadV3(QJsonObject& root)
|
bool AccountList::loadV3(QJsonObject& root)
|
||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
|
@ -55,7 +55,6 @@ class AccountList : public QAbstractListModel {
|
|||||||
// TODO: Add icon column.
|
// TODO: Add icon column.
|
||||||
ProfileNameColumn = 0,
|
ProfileNameColumn = 0,
|
||||||
NameColumn,
|
NameColumn,
|
||||||
MigrationColumn,
|
|
||||||
TypeColumn,
|
TypeColumn,
|
||||||
StatusColumn,
|
StatusColumn,
|
||||||
|
|
||||||
@ -97,7 +96,6 @@ class AccountList : public QAbstractListModel {
|
|||||||
void setListFilePath(QString path, bool autosave = false);
|
void setListFilePath(QString path, bool autosave = false);
|
||||||
|
|
||||||
bool loadList();
|
bool loadList();
|
||||||
bool loadV2(QJsonObject& root);
|
|
||||||
bool loadV3(QJsonObject& root);
|
bool loadV3(QJsonObject& root);
|
||||||
bool saveList();
|
bool saveList();
|
||||||
|
|
||||||
|
@ -24,10 +24,6 @@ struct AuthSession {
|
|||||||
GoneOrMigrated
|
GoneOrMigrated
|
||||||
} status = Undetermined;
|
} status = Undetermined;
|
||||||
|
|
||||||
// client token
|
|
||||||
QString client_token;
|
|
||||||
// account user name
|
|
||||||
QString username;
|
|
||||||
// combined session ID
|
// combined session ID
|
||||||
QString session;
|
QString session;
|
||||||
// volatile auth token
|
// volatile auth token
|
||||||
|
@ -51,7 +51,6 @@
|
|||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
#include "flows/MSA.h"
|
#include "flows/MSA.h"
|
||||||
#include "flows/Mojang.h"
|
|
||||||
#include "flows/Offline.h"
|
#include "flows/Offline.h"
|
||||||
|
|
||||||
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
|
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
|
||||||
@ -59,15 +58,6 @@ MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
|
|||||||
data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
MinecraftAccountPtr MinecraftAccount::loadFromJsonV2(const QJsonObject& json)
|
|
||||||
{
|
|
||||||
MinecraftAccountPtr account(new MinecraftAccount());
|
|
||||||
if (account->data.resumeStateFromV2(json)) {
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json)
|
MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json)
|
||||||
{
|
{
|
||||||
MinecraftAccountPtr account(new MinecraftAccount());
|
MinecraftAccountPtr account(new MinecraftAccount());
|
||||||
@ -77,15 +67,6 @@ MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString& username)
|
|
||||||
{
|
|
||||||
auto account = makeShared<MinecraftAccount>();
|
|
||||||
account->data.type = AccountType::Mojang;
|
|
||||||
account->data.yggdrasilToken.extra["userName"] = username;
|
|
||||||
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
|
|
||||||
MinecraftAccountPtr MinecraftAccount::createBlankMSA()
|
MinecraftAccountPtr MinecraftAccount::createBlankMSA()
|
||||||
{
|
{
|
||||||
MinecraftAccountPtr account(new MinecraftAccount());
|
MinecraftAccountPtr account(new MinecraftAccount());
|
||||||
@ -138,18 +119,6 @@ QPixmap MinecraftAccount::getFace() const
|
|||||||
return skin.scaled(64, 64, Qt::KeepAspectRatio);
|
return skin.scaled(64, 64, Qt::KeepAspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password)
|
|
||||||
{
|
|
||||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
|
||||||
|
|
||||||
m_currentTask.reset(new MojangLogin(&data, password));
|
|
||||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
|
||||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
|
||||||
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
|
||||||
emit activityChanged(true);
|
|
||||||
return m_currentTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA()
|
shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||||
@ -182,10 +151,8 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::refresh()
|
|||||||
|
|
||||||
if (data.type == AccountType::MSA) {
|
if (data.type == AccountType::MSA) {
|
||||||
m_currentTask.reset(new MSASilent(&data));
|
m_currentTask.reset(new MSASilent(&data));
|
||||||
} else if (data.type == AccountType::Offline) {
|
|
||||||
m_currentTask.reset(new OfflineRefresh(&data));
|
|
||||||
} else {
|
} else {
|
||||||
m_currentTask.reset(new MojangRefresh(&data));
|
m_currentTask.reset(new OfflineRefresh(&data));
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||||
@ -296,13 +263,8 @@ void MinecraftAccount::fillSession(AuthSessionPtr session)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the user name. you have to have an user name
|
|
||||||
// FIXME: not with MSA
|
|
||||||
session->username = data.userName();
|
|
||||||
// volatile auth token
|
// volatile auth token
|
||||||
session->access_token = data.accessToken();
|
session->access_token = data.accessToken();
|
||||||
// the semi-permanent client token
|
|
||||||
session->client_token = data.clientToken();
|
|
||||||
// profile name
|
// profile name
|
||||||
session->player_name = data.profileName();
|
session->player_name = data.profileName();
|
||||||
// profile ID
|
// profile ID
|
||||||
|
@ -85,13 +85,10 @@ class MinecraftAccount : public QObject, public Usable {
|
|||||||
//! Default constructor
|
//! Default constructor
|
||||||
explicit MinecraftAccount(QObject* parent = 0);
|
explicit MinecraftAccount(QObject* parent = 0);
|
||||||
|
|
||||||
static MinecraftAccountPtr createFromUsername(const QString& username);
|
|
||||||
|
|
||||||
static MinecraftAccountPtr createBlankMSA();
|
static MinecraftAccountPtr createBlankMSA();
|
||||||
|
|
||||||
static MinecraftAccountPtr createOffline(const QString& username);
|
static MinecraftAccountPtr createOffline(const QString& username);
|
||||||
|
|
||||||
static MinecraftAccountPtr loadFromJsonV2(const QJsonObject& json);
|
|
||||||
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject& json);
|
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject& json);
|
||||||
|
|
||||||
static QUuid uuidFromUsername(QString username);
|
static QUuid uuidFromUsername(QString username);
|
||||||
@ -100,12 +97,6 @@ class MinecraftAccount : public QObject, public Usable {
|
|||||||
QJsonObject saveToJson() const;
|
QJsonObject saveToJson() const;
|
||||||
|
|
||||||
public: /* manipulation */
|
public: /* manipulation */
|
||||||
/**
|
|
||||||
* Attempt to login. Empty password means we use the token.
|
|
||||||
* If the attempt fails because we already are performing some task, it returns false.
|
|
||||||
*/
|
|
||||||
shared_qobject_ptr<AccountTask> login(QString password);
|
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> loginMSA();
|
shared_qobject_ptr<AccountTask> loginMSA();
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> loginOffline();
|
shared_qobject_ptr<AccountTask> loginOffline();
|
||||||
@ -119,8 +110,6 @@ class MinecraftAccount : public QObject, public Usable {
|
|||||||
|
|
||||||
QString accountDisplayString() const { return data.accountDisplayString(); }
|
QString accountDisplayString() const { return data.accountDisplayString(); }
|
||||||
|
|
||||||
QString mojangUserName() const { return data.userName(); }
|
|
||||||
|
|
||||||
QString accessToken() const { return data.accessToken(); }
|
QString accessToken() const { return data.accessToken(); }
|
||||||
|
|
||||||
QString profileId() const { return data.profileId(); }
|
QString profileId() const { return data.profileId(); }
|
||||||
@ -129,8 +118,6 @@ class MinecraftAccount : public QObject, public Usable {
|
|||||||
|
|
||||||
bool isActive() const;
|
bool isActive() const;
|
||||||
|
|
||||||
bool canMigrate() const { return data.canMigrateToMSA; }
|
|
||||||
|
|
||||||
bool isMSA() const { return data.type == AccountType::MSA; }
|
bool isMSA() const { return data.type == AccountType::MSA; }
|
||||||
|
|
||||||
bool isOffline() const { return data.type == AccountType::Offline; }
|
bool isOffline() const { return data.type == AccountType::Offline; }
|
||||||
@ -142,12 +129,6 @@ class MinecraftAccount : public QObject, public Usable {
|
|||||||
QString typeString() const
|
QString typeString() const
|
||||||
{
|
{
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case AccountType::Mojang: {
|
|
||||||
if (data.legacy) {
|
|
||||||
return "legacy";
|
|
||||||
}
|
|
||||||
return "mojang";
|
|
||||||
} break;
|
|
||||||
case AccountType::MSA: {
|
case AccountType::MSA: {
|
||||||
return "msa";
|
return "msa";
|
||||||
} break;
|
} break;
|
||||||
|
@ -1,342 +0,0 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "Yggdrasil.h"
|
|
||||||
#include "AccountData.h"
|
|
||||||
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
|
|
||||||
Yggdrasil::Yggdrasil(AccountData* data, QObject* parent) : AccountTask(data, parent)
|
|
||||||
{
|
|
||||||
changeState(AccountTaskState::STATE_CREATED);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content)
|
|
||||||
{
|
|
||||||
changeState(AccountTaskState::STATE_WORKING);
|
|
||||||
|
|
||||||
QNetworkRequest netRequest(endpoint);
|
|
||||||
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
m_netReply = APPLICATION->network()->post(netRequest, content);
|
|
||||||
connect(m_netReply, &QNetworkReply::finished, this, &Yggdrasil::processReply);
|
|
||||||
connect(m_netReply, &QNetworkReply::uploadProgress, this, &Yggdrasil::refreshTimers);
|
|
||||||
connect(m_netReply, &QNetworkReply::downloadProgress, this, &Yggdrasil::refreshTimers);
|
|
||||||
connect(m_netReply, &QNetworkReply::sslErrors, this, &Yggdrasil::sslErrors);
|
|
||||||
timeout_keeper.setSingleShot(true);
|
|
||||||
timeout_keeper.start(timeout_max);
|
|
||||||
counter.setSingleShot(false);
|
|
||||||
counter.start(time_step);
|
|
||||||
progress(0, timeout_max);
|
|
||||||
connect(&timeout_keeper, &QTimer::timeout, this, &Yggdrasil::abortByTimeout);
|
|
||||||
connect(&counter, &QTimer::timeout, this, &Yggdrasil::heartbeat);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Yggdrasil::executeTask() {}
|
|
||||||
|
|
||||||
void Yggdrasil::refresh()
|
|
||||||
{
|
|
||||||
start();
|
|
||||||
/*
|
|
||||||
* {
|
|
||||||
* "clientToken": "client identifier"
|
|
||||||
* "accessToken": "current access token to be refreshed"
|
|
||||||
* "selectedProfile": // specifying this causes errors
|
|
||||||
* {
|
|
||||||
* "id": "profile ID"
|
|
||||||
* "name": "profile name"
|
|
||||||
* }
|
|
||||||
* "requestUser": true/false // request the user structure
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
QJsonObject req;
|
|
||||||
req.insert("clientToken", m_data->clientToken());
|
|
||||||
req.insert("accessToken", m_data->accessToken());
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
auto currentProfile = m_account->currentProfile();
|
|
||||||
QJsonObject profile;
|
|
||||||
profile.insert("id", currentProfile->id());
|
|
||||||
profile.insert("name", currentProfile->name());
|
|
||||||
req.insert("selectedProfile", profile);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
req.insert("requestUser", false);
|
|
||||||
QJsonDocument doc(req);
|
|
||||||
|
|
||||||
QUrl reqUrl("https://authserver.mojang.com/refresh");
|
|
||||||
QByteArray requestData = doc.toJson();
|
|
||||||
|
|
||||||
sendRequest(reqUrl, requestData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Yggdrasil::login(QString password)
|
|
||||||
{
|
|
||||||
start();
|
|
||||||
/*
|
|
||||||
* {
|
|
||||||
* "agent": { // optional
|
|
||||||
* "name": "Minecraft", // So far this is the only encountered value
|
|
||||||
* "version": 1 // This number might be increased
|
|
||||||
* // by the vanilla client in the future
|
|
||||||
* },
|
|
||||||
* "username": "mojang account name", // Can be an email address or player name for
|
|
||||||
* // unmigrated accounts
|
|
||||||
* "password": "mojang account password",
|
|
||||||
* "clientToken": "client identifier", // optional
|
|
||||||
* "requestUser": true/false // request the user structure
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
QJsonObject req;
|
|
||||||
|
|
||||||
{
|
|
||||||
QJsonObject agent;
|
|
||||||
// C++ makes string literals void* for some stupid reason, so we have to tell it
|
|
||||||
// QString... Thanks Obama.
|
|
||||||
agent.insert("name", QString("Minecraft"));
|
|
||||||
agent.insert("version", 1);
|
|
||||||
req.insert("agent", agent);
|
|
||||||
}
|
|
||||||
|
|
||||||
req.insert("username", m_data->userName());
|
|
||||||
req.insert("password", password);
|
|
||||||
req.insert("requestUser", false);
|
|
||||||
|
|
||||||
// If we already have a client token, give it to the server.
|
|
||||||
// Otherwise, let the server give us one.
|
|
||||||
|
|
||||||
m_data->generateClientTokenIfMissing();
|
|
||||||
req.insert("clientToken", m_data->clientToken());
|
|
||||||
|
|
||||||
QJsonDocument doc(req);
|
|
||||||
|
|
||||||
QUrl reqUrl("https://authserver.mojang.com/authenticate");
|
|
||||||
QNetworkRequest netRequest(reqUrl);
|
|
||||||
QByteArray requestData = doc.toJson();
|
|
||||||
|
|
||||||
sendRequest(reqUrl, requestData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Yggdrasil::refreshTimers(qint64, qint64)
|
|
||||||
{
|
|
||||||
timeout_keeper.stop();
|
|
||||||
timeout_keeper.start(timeout_max);
|
|
||||||
progress(count = 0, timeout_max);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Yggdrasil::heartbeat()
|
|
||||||
{
|
|
||||||
count += time_step;
|
|
||||||
progress(count, timeout_max);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Yggdrasil::abort()
|
|
||||||
{
|
|
||||||
progress(timeout_max, timeout_max);
|
|
||||||
// TODO: actually use this in a meaningful way
|
|
||||||
m_aborted = Yggdrasil::BY_USER;
|
|
||||||
m_netReply->abort();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Yggdrasil::abortByTimeout()
|
|
||||||
{
|
|
||||||
progress(timeout_max, timeout_max);
|
|
||||||
// TODO: actually use this in a meaningful way
|
|
||||||
m_aborted = Yggdrasil::BY_TIMEOUT;
|
|
||||||
m_netReply->abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Yggdrasil::sslErrors(QList<QSslError> errors)
|
|
||||||
{
|
|
||||||
int i = 1;
|
|
||||||
for (auto error : errors) {
|
|
||||||
qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
|
|
||||||
auto cert = error.certificate();
|
|
||||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Yggdrasil::processResponse(QJsonObject responseData)
|
|
||||||
{
|
|
||||||
// Read the response data. We need to get the client token, access token, and the selected
|
|
||||||
// profile.
|
|
||||||
qDebug() << "Processing authentication response.";
|
|
||||||
|
|
||||||
// qDebug() << responseData;
|
|
||||||
// If we already have a client token, make sure the one the server gave us matches our
|
|
||||||
// existing one.
|
|
||||||
QString clientToken = responseData.value("clientToken").toString("");
|
|
||||||
if (clientToken.isEmpty()) {
|
|
||||||
// Fail if the server gave us an empty client token
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (m_data->clientToken().isEmpty()) {
|
|
||||||
m_data->setClientToken(clientToken);
|
|
||||||
} else if (clientToken != m_data->clientToken()) {
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_HARD,
|
|
||||||
tr("Authentication server attempted to change the client token. This isn't supported."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, we set the access token.
|
|
||||||
qDebug() << "Getting access token.";
|
|
||||||
QString accessToken = responseData.value("accessToken").toString("");
|
|
||||||
if (accessToken.isEmpty()) {
|
|
||||||
// Fail if the server didn't give us an access token.
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Set the access token.
|
|
||||||
m_data->yggdrasilToken.token = accessToken;
|
|
||||||
m_data->yggdrasilToken.validity = Katabasis::Validity::Certain;
|
|
||||||
m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
|
|
||||||
|
|
||||||
// Get UUID here since we need it for later
|
|
||||||
auto profile = responseData.value("selectedProfile");
|
|
||||||
if (!profile.isObject()) {
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a selected profile."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto profileObj = profile.toObject();
|
|
||||||
for (auto i = profileObj.constBegin(); i != profileObj.constEnd(); ++i) {
|
|
||||||
if (i.key() == "name" && i.value().isString()) {
|
|
||||||
m_data->minecraftProfile.name = i->toString();
|
|
||||||
} else if (i.key() == "id" && i.value().isString()) {
|
|
||||||
m_data->minecraftProfile.id = i->toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_data->minecraftProfile.id.isEmpty()) {
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a UUID in selected profile."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We've made it through the minefield of possible errors. Return true to indicate that
|
|
||||||
// we've succeeded.
|
|
||||||
qDebug() << "Finished reading authentication response.";
|
|
||||||
changeState(AccountTaskState::STATE_SUCCEEDED);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Yggdrasil::processReply()
|
|
||||||
{
|
|
||||||
changeState(AccountTaskState::STATE_WORKING);
|
|
||||||
|
|
||||||
switch (m_netReply->error()) {
|
|
||||||
case QNetworkReply::NoError:
|
|
||||||
break;
|
|
||||||
case QNetworkReply::TimeoutError:
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation timed out."));
|
|
||||||
return;
|
|
||||||
case QNetworkReply::OperationCanceledError:
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
|
|
||||||
return;
|
|
||||||
case QNetworkReply::SslHandshakeFailedError:
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_SOFT,
|
|
||||||
tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
|
|
||||||
"<ul>"
|
|
||||||
"<li>You use Windows and need to update your root certificates, please install any outstanding updates.</li>"
|
|
||||||
"<li>Some device on your network is interfering with SSL traffic. In that case, "
|
|
||||||
"you have bigger worries than Minecraft not starting.</li>"
|
|
||||||
"<li>Possibly something else. Check the log file for details</li>"
|
|
||||||
"</ul>"));
|
|
||||||
return;
|
|
||||||
// used for invalid credentials and similar errors. Fall through.
|
|
||||||
case QNetworkReply::ContentAccessDenied:
|
|
||||||
case QNetworkReply::ContentOperationNotPermittedError:
|
|
||||||
break;
|
|
||||||
case QNetworkReply::ContentGoneError: {
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_GONE,
|
|
||||||
tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation failed due to a network error: %1 (%2)")
|
|
||||||
.arg(m_netReply->errorString())
|
|
||||||
.arg(m_netReply->error()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to parse the response regardless of the response code.
|
|
||||||
// Sometimes the auth server will give more information and an error code.
|
|
||||||
QJsonParseError jsonError;
|
|
||||||
QByteArray replyData = m_netReply->readAll();
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
|
|
||||||
// Check the response code.
|
|
||||||
int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
|
|
||||||
if (responseCode == 200) {
|
|
||||||
// If the response code was 200, then there shouldn't be an error. Make sure
|
|
||||||
// anyways.
|
|
||||||
// Also, sometimes an empty reply indicates success. If there was no data received,
|
|
||||||
// pass an empty json object to the processResponse function.
|
|
||||||
if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) {
|
|
||||||
processResponse(replyData.size() > 0 ? doc.object() : QJsonObject());
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_SOFT,
|
|
||||||
tr("Failed to parse authentication server response JSON response: %1 at offset %2.")
|
|
||||||
.arg(jsonError.errorString())
|
|
||||||
.arg(jsonError.offset));
|
|
||||||
qCritical() << replyData;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the response code was not 200, then Yggdrasil may have given us information
|
|
||||||
// about the error.
|
|
||||||
// If we can parse the response, then get information from it. Otherwise just say
|
|
||||||
// there was an unknown error.
|
|
||||||
if (jsonError.error == QJsonParseError::NoError) {
|
|
||||||
// We were able to parse the server's response. Woo!
|
|
||||||
// Call processError. If a subclass has overridden it then they'll handle their
|
|
||||||
// stuff there.
|
|
||||||
qDebug() << "The request failed, but the server gave us an error message. Processing error.";
|
|
||||||
processError(doc.object());
|
|
||||||
} else {
|
|
||||||
// The server didn't say anything regarding the error. Give the user an unknown
|
|
||||||
// error.
|
|
||||||
qDebug() << "The request failed and the server gave no error message. Unknown error.";
|
|
||||||
changeState(
|
|
||||||
AccountTaskState::STATE_FAILED_SOFT,
|
|
||||||
tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(m_netReply->errorString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Yggdrasil::processError(QJsonObject responseData)
|
|
||||||
{
|
|
||||||
QJsonValue errorVal = responseData.value("error");
|
|
||||||
QJsonValue errorMessageValue = responseData.value("errorMessage");
|
|
||||||
QJsonValue causeVal = responseData.value("cause");
|
|
||||||
|
|
||||||
if (errorVal.isString() && errorMessageValue.isString()) {
|
|
||||||
m_error = std::shared_ptr<Error>(new Error{ errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("") });
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
|
|
||||||
} else {
|
|
||||||
// Error is not in standard format. Don't set m_error and return unknown error.
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "AccountTask.h"
|
|
||||||
|
|
||||||
#include <qsslerror.h>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QString>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include "MinecraftAccount.h"
|
|
||||||
|
|
||||||
class QNetworkAccessManager;
|
|
||||||
class QNetworkReply;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Yggdrasil task is a task that performs an operation on a given mojang account.
|
|
||||||
*/
|
|
||||||
class Yggdrasil : public AccountTask {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit Yggdrasil(AccountData* data, QObject* parent = 0);
|
|
||||||
virtual ~Yggdrasil() = default;
|
|
||||||
|
|
||||||
void refresh();
|
|
||||||
void login(QString password);
|
|
||||||
|
|
||||||
struct Error {
|
|
||||||
QString m_errorMessageShort;
|
|
||||||
QString m_errorMessageVerbose;
|
|
||||||
QString m_cause;
|
|
||||||
};
|
|
||||||
std::shared_ptr<Error> m_error;
|
|
||||||
|
|
||||||
enum AbortedBy { BY_NOTHING, BY_USER, BY_TIMEOUT } m_aborted = BY_NOTHING;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void executeTask() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes the response received from the server.
|
|
||||||
* If an error occurred, this should emit a failed signal.
|
|
||||||
* If Yggdrasil gave an error response, it should call setError() first, and then return false.
|
|
||||||
* Otherwise, it should return true.
|
|
||||||
* Note: If the response from the server was blank, and the HTTP code was 200, this function is called with
|
|
||||||
* an empty QJsonObject.
|
|
||||||
*/
|
|
||||||
void processResponse(QJsonObject responseData);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes an error response received from the server.
|
|
||||||
* The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error.
|
|
||||||
* \returns a QString error message that will be passed to emitFailed.
|
|
||||||
*/
|
|
||||||
virtual void processError(QJsonObject responseData);
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
void processReply();
|
|
||||||
void refreshTimers(qint64, qint64);
|
|
||||||
void heartbeat();
|
|
||||||
void sslErrors(QList<QSslError>);
|
|
||||||
void abortByTimeout();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
virtual bool abort() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void sendRequest(QUrl endpoint, QByteArray content);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QNetworkReply* m_netReply = nullptr;
|
|
||||||
QTimer timeout_keeper;
|
|
||||||
QTimer counter;
|
|
||||||
int count = 0; // num msec since time reset
|
|
||||||
|
|
||||||
const int timeout_max = 30000;
|
|
||||||
const int time_step = 50;
|
|
||||||
};
|
|
@ -12,7 +12,6 @@
|
|||||||
#include "minecraft/auth/AccountData.h"
|
#include "minecraft/auth/AccountData.h"
|
||||||
#include "minecraft/auth/AccountTask.h"
|
#include "minecraft/auth/AccountTask.h"
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
#include "minecraft/auth/Yggdrasil.h"
|
|
||||||
|
|
||||||
class AuthFlow : public AccountTask {
|
class AuthFlow : public AccountTask {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
#include "Mojang.h"
|
|
||||||
|
|
||||||
#include "minecraft/auth/steps/GetSkinStep.h"
|
|
||||||
#include "minecraft/auth/steps/MigrationEligibilityStep.h"
|
|
||||||
#include "minecraft/auth/steps/MinecraftProfileStepMojang.h"
|
|
||||||
#include "minecraft/auth/steps/YggdrasilStep.h"
|
|
||||||
|
|
||||||
MojangRefresh::MojangRefresh(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
|
||||||
{
|
|
||||||
m_steps.append(makeShared<YggdrasilStep>(m_data, QString()));
|
|
||||||
m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
|
|
||||||
m_steps.append(makeShared<MigrationEligibilityStep>(m_data));
|
|
||||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
|
||||||
}
|
|
||||||
|
|
||||||
MojangLogin::MojangLogin(AccountData* data, QString password, QObject* parent) : AuthFlow(data, parent), m_password(password)
|
|
||||||
{
|
|
||||||
m_steps.append(makeShared<YggdrasilStep>(m_data, m_password));
|
|
||||||
m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
|
|
||||||
m_steps.append(makeShared<MigrationEligibilityStep>(m_data));
|
|
||||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "AuthFlow.h"
|
|
||||||
|
|
||||||
class MojangRefresh : public AuthFlow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit MojangRefresh(AccountData* data, QObject* parent = 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
class MojangLogin : public AuthFlow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit MojangLogin(AccountData* data, QString password, QObject* parent = 0);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString m_password;
|
|
||||||
};
|
|
@ -1,45 +0,0 @@
|
|||||||
#include "MigrationEligibilityStep.h"
|
|
||||||
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
|
||||||
|
|
||||||
MigrationEligibilityStep::MigrationEligibilityStep(AccountData* data) : AuthStep(data) {}
|
|
||||||
|
|
||||||
MigrationEligibilityStep::~MigrationEligibilityStep() noexcept = default;
|
|
||||||
|
|
||||||
QString MigrationEligibilityStep::describe()
|
|
||||||
{
|
|
||||||
return tr("Checking for migration eligibility.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void MigrationEligibilityStep::perform()
|
|
||||||
{
|
|
||||||
auto url = QUrl("https://api.minecraftservices.com/rollout/v1/msamigration");
|
|
||||||
QNetworkRequest request = QNetworkRequest(url);
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
|
|
||||||
|
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
|
||||||
connect(requestor, &AuthRequest::finished, this, &MigrationEligibilityStep::onRequestDone);
|
|
||||||
requestor->get(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MigrationEligibilityStep::rehydrate()
|
|
||||||
{
|
|
||||||
// NOOP, for now. We only save bools and there's nothing to check.
|
|
||||||
}
|
|
||||||
|
|
||||||
void MigrationEligibilityStep::onRequestDone(QNetworkReply::NetworkError error,
|
|
||||||
QByteArray data,
|
|
||||||
QList<QNetworkReply::RawHeaderPair> headers)
|
|
||||||
{
|
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
|
||||||
requestor->deleteLater();
|
|
||||||
|
|
||||||
if (error == QNetworkReply::NoError) {
|
|
||||||
Parsers::parseRolloutResponse(data, m_data->canMigrateToMSA);
|
|
||||||
}
|
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got migration flags"));
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
|
||||||
|
|
||||||
class MigrationEligibilityStep : public AuthStep {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit MigrationEligibilityStep(AccountData* data);
|
|
||||||
virtual ~MigrationEligibilityStep() noexcept;
|
|
||||||
|
|
||||||
void perform() override;
|
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
|
||||||
};
|
|
@ -41,10 +41,6 @@ void MinecraftProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByt
|
|||||||
qCDebug(authCredentials()) << data;
|
qCDebug(authCredentials()) << data;
|
||||||
if (error == QNetworkReply::ContentNotFoundError) {
|
if (error == QNetworkReply::ContentNotFoundError) {
|
||||||
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
|
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
|
||||||
if (m_data->type == AccountType::Mojang) {
|
|
||||||
m_data->minecraftEntitlement.canPlayMinecraft = false;
|
|
||||||
m_data->minecraftEntitlement.ownsMinecraft = false;
|
|
||||||
}
|
|
||||||
m_data->minecraftProfile = MinecraftProfile();
|
m_data->minecraftProfile = MinecraftProfile();
|
||||||
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
|
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
|
||||||
return;
|
return;
|
||||||
@ -73,10 +69,5 @@ void MinecraftProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByt
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_data->type == AccountType::Mojang) {
|
|
||||||
auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
|
|
||||||
m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
|
|
||||||
m_data->minecraftEntitlement.ownsMinecraft = validProfile;
|
|
||||||
}
|
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded."));
|
emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded."));
|
||||||
}
|
}
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
#include "MinecraftProfileStepMojang.h"
|
|
||||||
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
|
|
||||||
#include "Logging.h"
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
|
||||||
#include "net/NetUtils.h"
|
|
||||||
|
|
||||||
MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {}
|
|
||||||
|
|
||||||
MinecraftProfileStepMojang::~MinecraftProfileStepMojang() noexcept = default;
|
|
||||||
|
|
||||||
QString MinecraftProfileStepMojang::describe()
|
|
||||||
{
|
|
||||||
return tr("Fetching the Minecraft profile.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void MinecraftProfileStepMojang::perform()
|
|
||||||
{
|
|
||||||
if (m_data->minecraftProfile.id.isEmpty()) {
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("A UUID is required to get the profile."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// use session server instead of profile due to profile endpoint being locked for locked Mojang accounts
|
|
||||||
QUrl url = QUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + m_data->minecraftProfile.id);
|
|
||||||
QNetworkRequest req = QNetworkRequest(url);
|
|
||||||
AuthRequest* request = new AuthRequest(this);
|
|
||||||
connect(request, &AuthRequest::finished, this, &MinecraftProfileStepMojang::onRequestDone);
|
|
||||||
request->get(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MinecraftProfileStepMojang::rehydrate()
|
|
||||||
{
|
|
||||||
// NOOP, for now. We only save bools and there's nothing to check.
|
|
||||||
}
|
|
||||||
|
|
||||||
void MinecraftProfileStepMojang::onRequestDone(QNetworkReply::NetworkError error,
|
|
||||||
QByteArray data,
|
|
||||||
QList<QNetworkReply::RawHeaderPair> headers)
|
|
||||||
{
|
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
|
||||||
requestor->deleteLater();
|
|
||||||
|
|
||||||
qCDebug(authCredentials()) << data;
|
|
||||||
if (error == QNetworkReply::ContentNotFoundError) {
|
|
||||||
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
|
|
||||||
if (m_data->type == AccountType::Mojang) {
|
|
||||||
m_data->minecraftEntitlement.canPlayMinecraft = false;
|
|
||||||
m_data->minecraftEntitlement.ownsMinecraft = false;
|
|
||||||
}
|
|
||||||
m_data->minecraftProfile = MinecraftProfile();
|
|
||||||
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (error != QNetworkReply::NoError) {
|
|
||||||
qWarning() << "Error getting profile:";
|
|
||||||
qWarning() << " HTTP Status: " << requestor->httpStatus_;
|
|
||||||
qWarning() << " Internal error no.: " << error;
|
|
||||||
qWarning() << " Error string: " << requestor->errorString_;
|
|
||||||
|
|
||||||
qWarning() << " Response:";
|
|
||||||
qWarning() << QString::fromUtf8(data);
|
|
||||||
|
|
||||||
if (Net::isApplicationError(error)) {
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
|
||||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
|
||||||
} else {
|
|
||||||
emit finished(AccountTaskState::STATE_OFFLINE,
|
|
||||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) {
|
|
||||||
m_data->minecraftProfile = MinecraftProfile();
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_data->type == AccountType::Mojang) {
|
|
||||||
auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
|
|
||||||
m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
|
|
||||||
m_data->minecraftEntitlement.ownsMinecraft = validProfile;
|
|
||||||
}
|
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded."));
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
|
||||||
|
|
||||||
class MinecraftProfileStepMojang : public AuthStep {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit MinecraftProfileStepMojang(AccountData* data);
|
|
||||||
virtual ~MinecraftProfileStepMojang() noexcept;
|
|
||||||
|
|
||||||
void perform() override;
|
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
|
||||||
};
|
|
@ -38,7 +38,7 @@ void XboxUserStep::perform()
|
|||||||
QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate"));
|
QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate"));
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
request.setRawHeader("Accept", "application/json");
|
request.setRawHeader("Accept", "application/json");
|
||||||
// set contract-verison header (prevent err 400 bad-request?)
|
// set contract-version header (prevent err 400 bad-request?)
|
||||||
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders
|
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders
|
||||||
request.setRawHeader("x-xbl-contract-version", "1");
|
request.setRawHeader("x-xbl-contract-version", "1");
|
||||||
|
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
#include "YggdrasilStep.h"
|
|
||||||
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
|
||||||
#include "minecraft/auth/Yggdrasil.h"
|
|
||||||
|
|
||||||
YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(data), m_password(password)
|
|
||||||
{
|
|
||||||
m_yggdrasil = new Yggdrasil(m_data, this);
|
|
||||||
|
|
||||||
connect(m_yggdrasil, &Task::failed, this, &YggdrasilStep::onAuthFailed);
|
|
||||||
connect(m_yggdrasil, &Task::succeeded, this, &YggdrasilStep::onAuthSucceeded);
|
|
||||||
connect(m_yggdrasil, &Task::aborted, this, &YggdrasilStep::onAuthFailed);
|
|
||||||
}
|
|
||||||
|
|
||||||
YggdrasilStep::~YggdrasilStep() noexcept = default;
|
|
||||||
|
|
||||||
QString YggdrasilStep::describe()
|
|
||||||
{
|
|
||||||
return tr("Logging in with Mojang account.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void YggdrasilStep::rehydrate()
|
|
||||||
{
|
|
||||||
// NOOP, for now.
|
|
||||||
}
|
|
||||||
|
|
||||||
void YggdrasilStep::perform()
|
|
||||||
{
|
|
||||||
if (m_password.size()) {
|
|
||||||
m_yggdrasil->login(m_password);
|
|
||||||
} else {
|
|
||||||
m_yggdrasil->refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void YggdrasilStep::onAuthSucceeded()
|
|
||||||
{
|
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Logged in with Mojang"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void YggdrasilStep::onAuthFailed()
|
|
||||||
{
|
|
||||||
// TODO: hook these in again, expand to MSA
|
|
||||||
// m_error = m_yggdrasil->m_error;
|
|
||||||
// m_aborted = m_yggdrasil->m_aborted;
|
|
||||||
|
|
||||||
auto state = m_yggdrasil->taskState();
|
|
||||||
QString errorMessage = tr("Mojang user authentication failed.");
|
|
||||||
|
|
||||||
// NOTE: soft error in the first step means 'offline'
|
|
||||||
if (state == AccountTaskState::STATE_FAILED_SOFT) {
|
|
||||||
state = AccountTaskState::STATE_OFFLINE;
|
|
||||||
errorMessage = tr("Mojang user authentication ended with a network error.");
|
|
||||||
}
|
|
||||||
emit finished(state, errorMessage);
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
|
||||||
|
|
||||||
class Yggdrasil;
|
|
||||||
|
|
||||||
class YggdrasilStep : public AuthStep {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit YggdrasilStep(AccountData* data, QString password);
|
|
||||||
virtual ~YggdrasilStep() noexcept;
|
|
||||||
|
|
||||||
void perform() override;
|
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onAuthSucceeded();
|
|
||||||
void onAuthFailed();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Yggdrasil* m_yggdrasil = nullptr;
|
|
||||||
QString m_password;
|
|
||||||
};
|
|
@ -105,6 +105,17 @@ void LauncherPartLaunch::executeTask()
|
|||||||
auto instance = m_parent->instance();
|
auto instance = m_parent->instance();
|
||||||
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(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);
|
m_launchScript = minecraftInstance->createLaunchScript(m_session, m_serverToJoin);
|
||||||
QStringList args = minecraftInstance->javaArguments();
|
QStringList args = minecraftInstance->javaArguments();
|
||||||
QString allArgs = args.join(", ");
|
QString allArgs = args.join(", ");
|
||||||
@ -120,6 +131,9 @@ void LauncherPartLaunch::executeTask()
|
|||||||
auto classPath = minecraftInstance->getClassPath();
|
auto classPath = minecraftInstance->getClassPath();
|
||||||
classPath.prepend(jarPath);
|
classPath.prepend(jarPath);
|
||||||
|
|
||||||
|
if (!legacyJarPath.isEmpty())
|
||||||
|
classPath.prepend(legacyJarPath);
|
||||||
|
|
||||||
auto natPath = minecraftInstance->getNativePath();
|
auto natPath = minecraftInstance->getNativePath();
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
if (!fitsInLocal8bit(natPath)) {
|
if (!fitsInLocal8bit(natPath)) {
|
||||||
|
@ -31,6 +31,7 @@ class Mod;
|
|||||||
class Metadata {
|
class Metadata {
|
||||||
public:
|
public:
|
||||||
using ModStruct = Packwiz::V1::Mod;
|
using ModStruct = Packwiz::V1::Mod;
|
||||||
|
using ModSide = Packwiz::V1::Side;
|
||||||
|
|
||||||
static auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct
|
static auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct
|
||||||
{
|
{
|
||||||
|
@ -330,7 +330,8 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>
|
|||||||
|
|
||||||
// When you have a Qt build with assertions turned on, proceeding here will abort the application
|
// When you have a Qt build with assertions turned on, proceeding here will abort the application
|
||||||
if (added_set.size() > 0) {
|
if (added_set.size() > 0) {
|
||||||
beginInsertRows(QModelIndex(), m_resources.size(), m_resources.size() + added_set.size() - 1);
|
beginInsertRows(QModelIndex(), static_cast<int>(m_resources.size()),
|
||||||
|
static_cast<int>(m_resources.size() + added_set.size() - 1));
|
||||||
|
|
||||||
for (auto& added : added_set) {
|
for (auto& added : added_set) {
|
||||||
auto res = new_resources[added];
|
auto res = new_resources[added];
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
#include "ShaderPack.h"
|
#include "ShaderPack.h"
|
||||||
|
|
||||||
#include "minecraft/mod/tasks/LocalShaderPackParseTask.h"
|
#include <QRegularExpression>
|
||||||
|
|
||||||
void ShaderPack::setPackFormat(ShaderPackFormat new_format)
|
void ShaderPack::setPackFormat(ShaderPackFormat new_format)
|
||||||
{
|
{
|
||||||
@ -35,3 +35,8 @@ bool ShaderPack::valid() const
|
|||||||
{
|
{
|
||||||
return m_pack_format != ShaderPackFormat::INVALID;
|
return m_pack_format != ShaderPackFormat::INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ShaderPack::applyFilter(QRegularExpression filter) const
|
||||||
|
{
|
||||||
|
return valid() && Resource::applyFilter(filter);
|
||||||
|
}
|
||||||
|
@ -54,6 +54,7 @@ class ShaderPack : public Resource {
|
|||||||
void setPackFormat(ShaderPackFormat new_format);
|
void setPackFormat(ShaderPackFormat new_format);
|
||||||
|
|
||||||
bool valid() const override;
|
bool valid() const override;
|
||||||
|
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
mutable QMutex m_data_lock;
|
mutable QMutex m_data_lock;
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ResourceFolderModel.h"
|
#include "ResourceFolderModel.h"
|
||||||
|
#include "minecraft/mod/ShaderPack.h"
|
||||||
|
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||||
|
#include "minecraft/mod/tasks/LocalShaderPackParseTask.h"
|
||||||
|
|
||||||
class ShaderPackFolderModel : public ResourceFolderModel {
|
class ShaderPackFolderModel : public ResourceFolderModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -9,4 +12,14 @@ class ShaderPackFolderModel : public ResourceFolderModel {
|
|||||||
explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) {}
|
explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) {}
|
||||||
|
|
||||||
virtual QString id() const override { return "shaderpacks"; }
|
virtual QString id() const override { return "shaderpacks"; }
|
||||||
|
|
||||||
|
[[nodiscard]] Task* createUpdateTask() override
|
||||||
|
{
|
||||||
|
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<ShaderPack>(entry); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Task* createParseTask(Resource& resource) override
|
||||||
|
{
|
||||||
|
return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast<ShaderPack&>(resource));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -94,7 +94,7 @@ QList<ModPlatform::Dependency> GetModDependenciesTask::getDependenciesForVersion
|
|||||||
for (auto ver_dep : version.dependencies) {
|
for (auto ver_dep : version.dependencies) {
|
||||||
if (ver_dep.type != ModPlatform::DependencyType::REQUIRED)
|
if (ver_dep.type != ModPlatform::DependencyType::REQUIRED)
|
||||||
continue;
|
continue;
|
||||||
|
ver_dep = getOverride(ver_dep, providerName);
|
||||||
auto isOnlyVersion = providerName == ModPlatform::ResourceProvider::MODRINTH && ver_dep.addonId.toString().isEmpty();
|
auto isOnlyVersion = providerName == ModPlatform::ResourceProvider::MODRINTH && ver_dep.addonId.toString().isEmpty();
|
||||||
if (auto dep = std::find_if(c_dependencies.begin(), c_dependencies.end(),
|
if (auto dep = std::find_if(c_dependencies.begin(), c_dependencies.end(),
|
||||||
[&ver_dep, isOnlyVersion](const ModPlatform::Dependency& i) {
|
[&ver_dep, isOnlyVersion](const ModPlatform::Dependency& i) {
|
||||||
@ -127,7 +127,7 @@ QList<ModPlatform::Dependency> GetModDependenciesTask::getDependenciesForVersion
|
|||||||
dep != m_pack_dependencies.end()) // check loaded dependencies
|
dep != m_pack_dependencies.end()) // check loaded dependencies
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
c_dependencies.append(getOverride(ver_dep, providerName));
|
c_dependencies.append(ver_dep);
|
||||||
}
|
}
|
||||||
return c_dependencies;
|
return c_dependencies;
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ void ModFolderLoadTask::getFromMetadata()
|
|||||||
auto metadata = Metadata::get(m_index_dir, entry);
|
auto metadata = Metadata::get(m_index_dir, entry);
|
||||||
|
|
||||||
if (!metadata.isValid()) {
|
if (!metadata.isValid()) {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* mod = new Mod(m_mods_dir, metadata);
|
auto* mod = new Mod(m_mods_dir, metadata);
|
||||||
|
@ -24,6 +24,7 @@ class CheckUpdateTask : public Task {
|
|||||||
QString old_hash;
|
QString old_hash;
|
||||||
QString old_version;
|
QString old_version;
|
||||||
QString new_version;
|
QString new_version;
|
||||||
|
std::optional<ModPlatform::IndexedVersionType> new_version_type;
|
||||||
QString changelog;
|
QString changelog;
|
||||||
ModPlatform::ResourceProvider provider;
|
ModPlatform::ResourceProvider provider;
|
||||||
shared_qobject_ptr<ResourceDownloadTask> download;
|
shared_qobject_ptr<ResourceDownloadTask> download;
|
||||||
@ -33,10 +34,18 @@ class CheckUpdateTask : public Task {
|
|||||||
QString old_h,
|
QString old_h,
|
||||||
QString old_v,
|
QString old_v,
|
||||||
QString new_v,
|
QString new_v,
|
||||||
|
std::optional<ModPlatform::IndexedVersionType> new_v_type,
|
||||||
QString changelog,
|
QString changelog,
|
||||||
ModPlatform::ResourceProvider p,
|
ModPlatform::ResourceProvider p,
|
||||||
shared_qobject_ptr<ResourceDownloadTask> t)
|
shared_qobject_ptr<ResourceDownloadTask> t)
|
||||||
: name(name), old_hash(old_h), old_version(old_v), new_version(new_v), changelog(changelog), provider(p), download(t)
|
: name(name)
|
||||||
|
, old_hash(old_h)
|
||||||
|
, old_version(old_v)
|
||||||
|
, new_version(new_v)
|
||||||
|
, new_version_type(new_v_type)
|
||||||
|
, changelog(changelog)
|
||||||
|
, provider(p)
|
||||||
|
, download(t)
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,6 +24,40 @@
|
|||||||
|
|
||||||
namespace ModPlatform {
|
namespace ModPlatform {
|
||||||
|
|
||||||
|
static const QMap<QString, IndexedVersionType::VersionType> s_indexed_version_type_names = {
|
||||||
|
{ "release", IndexedVersionType::VersionType::Release },
|
||||||
|
{ "beta", IndexedVersionType::VersionType::Beta },
|
||||||
|
{ "alpha", IndexedVersionType::VersionType::Alpha }
|
||||||
|
};
|
||||||
|
|
||||||
|
IndexedVersionType::IndexedVersionType(const QString& type) : IndexedVersionType(enumFromString(type)) {}
|
||||||
|
|
||||||
|
IndexedVersionType::IndexedVersionType(const IndexedVersionType::VersionType& type)
|
||||||
|
{
|
||||||
|
m_type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexedVersionType::IndexedVersionType(const IndexedVersionType& other)
|
||||||
|
{
|
||||||
|
m_type = other.m_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexedVersionType& IndexedVersionType::operator=(const IndexedVersionType& other)
|
||||||
|
{
|
||||||
|
m_type = other.m_type;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString IndexedVersionType::toString(const IndexedVersionType::VersionType& type)
|
||||||
|
{
|
||||||
|
return s_indexed_version_type_names.key(type, "unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexedVersionType::VersionType IndexedVersionType::enumFromString(const QString& type)
|
||||||
|
{
|
||||||
|
return s_indexed_version_type_names.value(type, IndexedVersionType::VersionType::Unknown);
|
||||||
|
}
|
||||||
|
|
||||||
auto ProviderCapabilities::name(ResourceProvider p) -> const char*
|
auto ProviderCapabilities::name(ResourceProvider p) -> const char*
|
||||||
{
|
{
|
||||||
switch (p) {
|
switch (p) {
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
class QIODevice;
|
class QIODevice;
|
||||||
|
|
||||||
@ -58,6 +59,34 @@ struct DonationData {
|
|||||||
QString url;
|
QString url;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct IndexedVersionType {
|
||||||
|
enum class VersionType { Release = 1, Beta, Alpha, Unknown };
|
||||||
|
IndexedVersionType(const QString& type);
|
||||||
|
IndexedVersionType(const IndexedVersionType::VersionType& type);
|
||||||
|
IndexedVersionType(const IndexedVersionType& type);
|
||||||
|
IndexedVersionType() : IndexedVersionType(IndexedVersionType::VersionType::Unknown) {}
|
||||||
|
static const QString toString(const IndexedVersionType::VersionType& type);
|
||||||
|
static IndexedVersionType::VersionType enumFromString(const QString& type);
|
||||||
|
bool isValid() const { return m_type != IndexedVersionType::VersionType::Unknown; }
|
||||||
|
IndexedVersionType& operator=(const IndexedVersionType& other);
|
||||||
|
bool operator==(const IndexedVersionType& other) const { return m_type == other.m_type; }
|
||||||
|
bool operator==(const IndexedVersionType::VersionType& type) const { return m_type == type; }
|
||||||
|
bool operator!=(const IndexedVersionType& other) const { return m_type != other.m_type; }
|
||||||
|
bool operator!=(const IndexedVersionType::VersionType& type) const { return m_type != type; }
|
||||||
|
bool operator<(const IndexedVersionType& other) const { return m_type < other.m_type; }
|
||||||
|
bool operator<(const IndexedVersionType::VersionType& type) const { return m_type < type; }
|
||||||
|
bool operator<=(const IndexedVersionType& other) const { return m_type <= other.m_type; }
|
||||||
|
bool operator<=(const IndexedVersionType::VersionType& type) const { return m_type <= type; }
|
||||||
|
bool operator>(const IndexedVersionType& other) const { return m_type > other.m_type; }
|
||||||
|
bool operator>(const IndexedVersionType::VersionType& type) const { return m_type > type; }
|
||||||
|
bool operator>=(const IndexedVersionType& other) const { return m_type >= other.m_type; }
|
||||||
|
bool operator>=(const IndexedVersionType::VersionType& type) const { return m_type >= type; }
|
||||||
|
|
||||||
|
QString toString() const { return toString(m_type); }
|
||||||
|
|
||||||
|
IndexedVersionType::VersionType m_type;
|
||||||
|
};
|
||||||
|
|
||||||
struct Dependency {
|
struct Dependency {
|
||||||
QVariant addonId;
|
QVariant addonId;
|
||||||
DependencyType type;
|
DependencyType type;
|
||||||
@ -69,6 +98,7 @@ struct IndexedVersion {
|
|||||||
QVariant fileId;
|
QVariant fileId;
|
||||||
QString version;
|
QString version;
|
||||||
QString version_number = {};
|
QString version_number = {};
|
||||||
|
IndexedVersionType version_type;
|
||||||
QStringList mcVersion;
|
QStringList mcVersion;
|
||||||
QString downloadUrl;
|
QString downloadUrl;
|
||||||
QString date;
|
QString date;
|
||||||
@ -107,6 +137,7 @@ struct IndexedPack {
|
|||||||
QString logoName;
|
QString logoName;
|
||||||
QString logoUrl;
|
QString logoUrl;
|
||||||
QString websiteUrl;
|
QString websiteUrl;
|
||||||
|
QString side;
|
||||||
|
|
||||||
bool versionsLoaded = false;
|
bool versionsLoaded = false;
|
||||||
QVector<IndexedVersion> versions;
|
QVector<IndexedVersion> versions;
|
||||||
|
@ -43,5 +43,5 @@ void ATLauncher::loadIndexedPack(ATLauncher::IndexedPack& m, QJsonObject& obj)
|
|||||||
m.system = Json::ensureBoolean(obj, QString("system"), false);
|
m.system = Json::ensureBoolean(obj, QString("system"), false);
|
||||||
m.description = Json::ensureString(obj, "description", "");
|
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 "ATLPackInstallTask.h"
|
||||||
|
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <quazip/quazip.h>
|
#include <quazip/quazip.h>
|
||||||
|
|
||||||
@ -50,6 +51,7 @@
|
|||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
#include "minecraft/OneSixVersionFormat.h"
|
#include "minecraft/OneSixVersionFormat.h"
|
||||||
#include "minecraft/PackProfile.h"
|
#include "minecraft/PackProfile.h"
|
||||||
|
#include "modplatform/atlauncher/ATLPackManifest.h"
|
||||||
#include "net/ChecksumValidator.h"
|
#include "net/ChecksumValidator.h"
|
||||||
#include "settings/INISettingsObject.h"
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
@ -57,6 +59,7 @@
|
|||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
|
#include "ui/dialogs/BlockedModsDialog.h"
|
||||||
|
|
||||||
namespace ATLauncher {
|
namespace ATLauncher {
|
||||||
|
|
||||||
@ -717,6 +720,8 @@ void PackInstallTask::downloadMods()
|
|||||||
|
|
||||||
jarmods.clear();
|
jarmods.clear();
|
||||||
jobPtr.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
|
jobPtr.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
|
||||||
|
|
||||||
|
QList<VersionMod> blocked_mods;
|
||||||
for (const auto& mod : m_version.mods) {
|
for (const auto& mod : m_version.mods) {
|
||||||
// skip non-client mods
|
// skip non-client mods
|
||||||
if (!mod.client)
|
if (!mod.client)
|
||||||
@ -731,9 +736,10 @@ void PackInstallTask::downloadMods()
|
|||||||
case DownloadType::Server:
|
case DownloadType::Server:
|
||||||
url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url;
|
url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url;
|
||||||
break;
|
break;
|
||||||
case DownloadType::Browser:
|
case DownloadType::Browser: {
|
||||||
emitFailed(tr("Unsupported download type: %1").arg(mod.download_raw));
|
blocked_mods.append(mod);
|
||||||
return;
|
continue;
|
||||||
|
}
|
||||||
case DownloadType::Direct:
|
case DownloadType::Direct:
|
||||||
url = mod.url;
|
url = mod.url;
|
||||||
break;
|
break;
|
||||||
@ -805,24 +811,86 @@ void PackInstallTask::downloadMods()
|
|||||||
modsToCopy[entry->getFullPath()] = path;
|
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::succeeded, this, &PackInstallTask::onModsDownloaded);
|
||||||
connect(jobPtr.get(), &NetJob::failed, [&](QString reason) {
|
connect(jobPtr.get(), &NetJob::progress, [this](qint64 current, qint64 total) {
|
||||||
abortable = false;
|
|
||||||
jobPtr.reset();
|
|
||||||
emitFailed(reason);
|
|
||||||
});
|
|
||||||
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
|
|
||||||
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
||||||
abortable = true;
|
abortable = true;
|
||||||
setProgress(current, total);
|
setProgress(current, total);
|
||||||
});
|
});
|
||||||
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
|
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
|
||||||
connect(jobPtr.get(), &NetJob::aborted, [&] {
|
connect(jobPtr.get(), &NetJob::aborted, &PackInstallTask::emitAborted);
|
||||||
abortable = false;
|
connect(jobPtr.get(), &NetJob::failed, &PackInstallTask::emitFailed);
|
||||||
jobPtr.reset();
|
|
||||||
emitAborted();
|
|
||||||
});
|
|
||||||
|
|
||||||
jobPtr->start();
|
jobPtr->start();
|
||||||
}
|
}
|
||||||
@ -843,7 +911,7 @@ void PackInstallTask::onModsDownloaded()
|
|||||||
QtConcurrent::run(QThreadPool::globalInstance(), this, &PackInstallTask::extractMods, modsToExtract, modsToDecomp, modsToCopy);
|
QtConcurrent::run(QThreadPool::globalInstance(), this, &PackInstallTask::extractMods, modsToExtract, modsToDecomp, modsToCopy);
|
||||||
#endif
|
#endif
|
||||||
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onModsExtracted);
|
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);
|
m_modExtractFutureWatcher.setFuture(m_modExtractFuture);
|
||||||
} else {
|
} else {
|
||||||
install();
|
install();
|
||||||
|
@ -173,7 +173,7 @@ void FlameCheckUpdate::executeTask()
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver, m_mods_folder);
|
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver, m_mods_folder);
|
||||||
m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver.version,
|
m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver.version, latest_ver.version_type,
|
||||||
api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()),
|
api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()),
|
||||||
ModPlatform::ResourceProvider::FLAME, download_task);
|
ModPlatform::ResourceProvider::FLAME, download_task);
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
#include "minecraft/World.h"
|
#include "minecraft/World.h"
|
||||||
#include "minecraft/mod/tasks/LocalResourceParse.h"
|
#include "minecraft/mod/tasks/LocalResourceParse.h"
|
||||||
#include "net/ApiDownload.h"
|
#include "net/ApiDownload.h"
|
||||||
|
#include "ui/pages/modplatform/OptionalModDialog.h"
|
||||||
|
|
||||||
static const FlameAPI api;
|
static const FlameAPI api;
|
||||||
|
|
||||||
@ -509,13 +510,33 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
|||||||
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||||
{
|
{
|
||||||
m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
|
m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
|
||||||
for (const auto& result : m_mod_id_resolver->getResults().files) {
|
auto results = m_mod_id_resolver->getResults().files;
|
||||||
QString filename = result.fileName;
|
|
||||||
|
QStringList optionalFiles;
|
||||||
|
for (auto& result : results) {
|
||||||
if (!result.required) {
|
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);
|
auto path = FS::PathCombine(m_stagingPath, relpath);
|
||||||
|
|
||||||
switch (result.type) {
|
switch (result.type) {
|
||||||
@ -547,7 +568,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
|||||||
m_mod_id_resolver.reset();
|
m_mod_id_resolver.reset();
|
||||||
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() {
|
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() {
|
||||||
m_files_job.reset();
|
m_files_job.reset();
|
||||||
validateZIPResouces();
|
validateZIPResources();
|
||||||
});
|
});
|
||||||
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
|
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
|
||||||
m_files_job.reset();
|
m_files_job.reset();
|
||||||
@ -596,7 +617,7 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
|
|||||||
setAbortable(true);
|
setAbortable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlameCreationTask::validateZIPResouces()
|
void FlameCreationTask::validateZIPResources()
|
||||||
{
|
{
|
||||||
qDebug() << "Validating whether resources stored as .zip are in the right place";
|
qDebug() << "Validating whether resources stored as .zip are in the right place";
|
||||||
for (auto [fileName, targetFolder] : m_ZIP_resources) {
|
for (auto [fileName, targetFolder] : m_ZIP_resources) {
|
||||||
@ -649,8 +670,8 @@ void FlameCreationTask::validateZIPResouces()
|
|||||||
validatePath(fileName, targetFolder, "datapacks");
|
validatePath(fileName, targetFolder, "datapacks");
|
||||||
break;
|
break;
|
||||||
case PackedResourceType::ShaderPack:
|
case PackedResourceType::ShaderPack:
|
||||||
// in theroy flame API can't do this but who knows, that *may* change ?
|
// in theory flame API can't do this but who knows, that *may* change ?
|
||||||
// better to handle it if it *does* occure in the future
|
// better to handle it if it *does* occur in the future
|
||||||
validatePath(fileName, targetFolder, "shaderpacks");
|
validatePath(fileName, targetFolder, "shaderpacks");
|
||||||
break;
|
break;
|
||||||
case PackedResourceType::WorldSave:
|
case PackedResourceType::WorldSave:
|
||||||
|
@ -74,7 +74,7 @@ class FlameCreationTask final : public InstanceCreationTask {
|
|||||||
void idResolverSucceeded(QEventLoop&);
|
void idResolverSucceeded(QEventLoop&);
|
||||||
void setupDownloadJob(QEventLoop&);
|
void setupDownloadJob(QEventLoop&);
|
||||||
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
|
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
|
||||||
void validateZIPResouces();
|
void validateZIPResources();
|
||||||
QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion);
|
QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -139,6 +139,22 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
|
|||||||
file.downloadUrl = Json::ensureString(obj, "downloadUrl");
|
file.downloadUrl = Json::ensureString(obj, "downloadUrl");
|
||||||
file.fileName = Json::requireString(obj, "fileName");
|
file.fileName = Json::requireString(obj, "fileName");
|
||||||
|
|
||||||
|
ModPlatform::IndexedVersionType::VersionType ver_type;
|
||||||
|
switch (Json::requireInteger(obj, "releaseType")) {
|
||||||
|
case 1:
|
||||||
|
ver_type = ModPlatform::IndexedVersionType::VersionType::Release;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
ver_type = ModPlatform::IndexedVersionType::VersionType::Beta;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
ver_type = ModPlatform::IndexedVersionType::VersionType::Alpha;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ver_type = ModPlatform::IndexedVersionType::VersionType::Unknown;
|
||||||
|
}
|
||||||
|
file.version_type = ModPlatform::IndexedVersionType(ver_type);
|
||||||
|
|
||||||
auto hash_list = Json::ensureArray(obj, "hashes");
|
auto hash_list = Json::ensureArray(obj, "hashes");
|
||||||
for (auto h : hash_list) {
|
for (auto h : hash_list) {
|
||||||
auto hash_entry = Json::ensureObject(h);
|
auto hash_entry = Json::ensureObject(h);
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
#include "FlamePackIndex.h"
|
#include "FlamePackIndex.h"
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
|
||||||
@ -9,8 +11,8 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)
|
|||||||
pack.description = Json::ensureString(obj, "summary", "");
|
pack.description = Json::ensureString(obj, "summary", "");
|
||||||
|
|
||||||
auto logo = Json::requireObject(obj, "logo");
|
auto logo = Json::requireObject(obj, "logo");
|
||||||
pack.logoName = Json::requireString(logo, "title");
|
|
||||||
pack.logoUrl = Json::requireString(logo, "thumbnailUrl");
|
pack.logoUrl = Json::requireString(logo, "thumbnailUrl");
|
||||||
|
pack.logoName = Json::requireString(obj, "slug") + "." + QFileInfo(QUrl(pack.logoUrl).fileName()).suffix();
|
||||||
|
|
||||||
auto authors = Json::requireArray(obj, "authors");
|
auto authors = Json::requireArray(obj, "authors");
|
||||||
for (auto authorIter : authors) {
|
for (auto authorIter : authors) {
|
||||||
@ -89,6 +91,22 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr)
|
|||||||
// pick the latest version supported
|
// pick the latest version supported
|
||||||
file.mcVersion = versionArray[0].toString();
|
file.mcVersion = versionArray[0].toString();
|
||||||
file.version = Json::requireString(version, "displayName");
|
file.version = Json::requireString(version, "displayName");
|
||||||
|
|
||||||
|
ModPlatform::IndexedVersionType::VersionType ver_type;
|
||||||
|
switch (Json::requireInteger(version, "releaseType")) {
|
||||||
|
case 1:
|
||||||
|
ver_type = ModPlatform::IndexedVersionType::VersionType::Release;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
ver_type = ModPlatform::IndexedVersionType::VersionType::Beta;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
ver_type = ModPlatform::IndexedVersionType::VersionType::Alpha;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ver_type = ModPlatform::IndexedVersionType::VersionType::Unknown;
|
||||||
|
}
|
||||||
|
file.version_type = ModPlatform::IndexedVersionType(ver_type);
|
||||||
file.downloadUrl = Json::ensureString(version, "downloadUrl");
|
file.downloadUrl = Json::ensureString(version, "downloadUrl");
|
||||||
|
|
||||||
// only add if we have a download URL (third party distribution is enabled)
|
// only add if we have a download URL (third party distribution is enabled)
|
||||||
|
@ -17,6 +17,7 @@ struct IndexedVersion {
|
|||||||
int addonId;
|
int addonId;
|
||||||
int fileId;
|
int fileId;
|
||||||
QString version;
|
QString version;
|
||||||
|
ModPlatform::IndexedVersionType version_type;
|
||||||
QString mcVersion;
|
QString mcVersion;
|
||||||
QString downloadUrl;
|
QString downloadUrl;
|
||||||
};
|
};
|
||||||
|
@ -48,7 +48,7 @@ struct File {
|
|||||||
|
|
||||||
int projectId = 0;
|
int projectId = 0;
|
||||||
int fileId = 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;
|
bool required = true;
|
||||||
QString hash;
|
QString hash;
|
||||||
// NOTE: only set on blocked files ! Empty otherwise.
|
// NOTE: only set on blocked files ! Empty otherwise.
|
||||||
|
@ -70,16 +70,18 @@ void PackInstallTask::downloadPack()
|
|||||||
setProgress(1, 4);
|
setProgress(1, 4);
|
||||||
setAbortable(false);
|
setAbortable(false);
|
||||||
|
|
||||||
archivePath = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file);
|
auto path = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file);
|
||||||
|
auto entry = APPLICATION->metacache()->resolveEntry("FTBPacks", path);
|
||||||
|
entry->setStale(true);
|
||||||
|
archivePath = entry->getFullPath();
|
||||||
netJobContainer.reset(new NetJob("Download FTB Pack", m_network));
|
netJobContainer.reset(new NetJob("Download FTB Pack", m_network));
|
||||||
QString url;
|
QString url;
|
||||||
if (m_pack.type == PackType::Private) {
|
if (m_pack.type == PackType::Private) {
|
||||||
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "privatepacks/%1").arg(archivePath);
|
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "privatepacks/%1").arg(path);
|
||||||
} else {
|
} else {
|
||||||
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(archivePath);
|
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(path);
|
||||||
}
|
}
|
||||||
netJobContainer->addNetAction(Net::ApiDownload::makeFile(url, archivePath));
|
netJobContainer->addNetAction(Net::ApiDownload::makeCached(url, entry));
|
||||||
|
|
||||||
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip);
|
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip);
|
||||||
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed);
|
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed);
|
||||||
|
@ -161,8 +161,8 @@ void ModrinthCheckUpdate::executeTask()
|
|||||||
|
|
||||||
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder);
|
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder);
|
||||||
|
|
||||||
m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.changelog,
|
m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.version_type,
|
||||||
ModPlatform::ResourceProvider::MODRINTH, download_task);
|
project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task);
|
||||||
}
|
}
|
||||||
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
|
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "modplatform/helpers/OverrideUtils.h"
|
#include "modplatform/helpers/OverrideUtils.h"
|
||||||
|
|
||||||
|
#include "modplatform/modrinth/ModrinthPackManifest.h"
|
||||||
#include "net/ChecksumValidator.h"
|
#include "net/ChecksumValidator.h"
|
||||||
|
|
||||||
#include "net/ApiDownload.h"
|
#include "net/ApiDownload.h"
|
||||||
@ -16,8 +17,10 @@
|
|||||||
#include "settings/INISettingsObject.h"
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
|
#include "ui/pages/modplatform/OptionalModDialog.h"
|
||||||
|
|
||||||
#include <QAbstractButton>
|
#include <QAbstractButton>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
bool ModrinthCreationTask::abort()
|
bool ModrinthCreationTask::abort()
|
||||||
{
|
{
|
||||||
@ -319,10 +322,10 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
|
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
|
||||||
bool had_optional = false;
|
std::vector<Modrinth::File> optionalFiles;
|
||||||
for (const auto& modInfo : jsonFiles) {
|
for (const auto& modInfo : jsonFiles) {
|
||||||
Modrinth::File file;
|
Modrinth::File file;
|
||||||
file.path = Json::requireString(modInfo, "path");
|
file.path = Json::requireString(modInfo, "path").replace("\\", "/");
|
||||||
|
|
||||||
auto env = Json::ensureObject(modInfo, "env");
|
auto env = Json::ensureObject(modInfo, "env");
|
||||||
// 'env' field is optional
|
// 'env' field is optional
|
||||||
@ -331,18 +334,7 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
|
|||||||
if (support == "unsupported") {
|
if (support == "unsupported") {
|
||||||
continue;
|
continue;
|
||||||
} else if (support == "optional") {
|
} else if (support == "optional") {
|
||||||
// TODO: Make a review dialog for choosing which ones the user wants!
|
file.required = false;
|
||||||
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";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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) {
|
if (set_internal_data) {
|
||||||
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
||||||
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
|
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
#include "MMCZip.h"
|
#include "MMCZip.h"
|
||||||
#include "minecraft/PackProfile.h"
|
#include "minecraft/PackProfile.h"
|
||||||
|
#include "minecraft/mod/MetadataHandler.h"
|
||||||
#include "minecraft/mod/ModFolderModel.h"
|
#include "minecraft/mod/ModFolderModel.h"
|
||||||
|
|
||||||
const QStringList ModrinthPackExportTask::PREFIXES({ "mods/", "coremods/", "resourcepacks/", "texturepacks/", "shaderpacks/" });
|
const QStringList ModrinthPackExportTask::PREFIXES({ "mods/", "coremods/", "resourcepacks/", "texturepacks/", "shaderpacks/" });
|
||||||
@ -129,7 +130,8 @@ void ModrinthPackExportTask::collectHashes()
|
|||||||
QCryptographicHash sha1(QCryptographicHash::Algorithm::Sha1);
|
QCryptographicHash sha1(QCryptographicHash::Algorithm::Sha1);
|
||||||
sha1.addData(data);
|
sha1.addData(data);
|
||||||
|
|
||||||
ResolvedFile resolvedFile{ sha1.result().toHex(), sha512.result().toHex(), url.toEncoded(), openFile.size() };
|
ResolvedFile resolvedFile{ sha1.result().toHex(), sha512.result().toHex(), url.toEncoded(), openFile.size(),
|
||||||
|
mod->metadata()->side };
|
||||||
resolvedFiles[relative] = resolvedFile;
|
resolvedFiles[relative] = resolvedFile;
|
||||||
|
|
||||||
// nice! we've managed to resolve based on local metadata!
|
// nice! we've managed to resolve based on local metadata!
|
||||||
@ -272,22 +274,33 @@ QByteArray ModrinthPackExportTask::generateIndex()
|
|||||||
QString path = iterator.key();
|
QString path = iterator.key();
|
||||||
const ResolvedFile& value = iterator.value();
|
const ResolvedFile& value = iterator.value();
|
||||||
|
|
||||||
if (optionalFiles) {
|
QJsonObject env;
|
||||||
// detect disabled mod
|
|
||||||
const QFileInfo pathInfo(path);
|
// detect disabled mod
|
||||||
if (pathInfo.suffix() == "disabled") {
|
const QFileInfo pathInfo(path);
|
||||||
// rename it
|
if (optionalFiles && pathInfo.suffix() == "disabled") {
|
||||||
path = pathInfo.dir().filePath(pathInfo.completeBaseName());
|
// rename it
|
||||||
// ...and make it optional
|
path = pathInfo.dir().filePath(pathInfo.completeBaseName());
|
||||||
QJsonObject env;
|
env["client"] = "optional";
|
||||||
env["client"] = "optional";
|
env["server"] = "optional";
|
||||||
env["server"] = "optional";
|
} else {
|
||||||
fileOut["env"] = env;
|
env["client"] = "required";
|
||||||
}
|
env["server"] = "required";
|
||||||
}
|
}
|
||||||
|
switch (iterator->side) {
|
||||||
|
case Metadata::ModSide::ClientSide:
|
||||||
|
env["server"] = "unsupported";
|
||||||
|
break;
|
||||||
|
case Metadata::ModSide::ServerSide:
|
||||||
|
env["client"] = "unsupported";
|
||||||
|
break;
|
||||||
|
case Metadata::ModSide::UniversalSide:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fileOut["env"] = env;
|
||||||
|
|
||||||
fileOut["path"] = path;
|
fileOut["path"] = path;
|
||||||
fileOut["downloads"] = QJsonArray{ iterator.value().url };
|
fileOut["downloads"] = QJsonArray{ iterator->url };
|
||||||
|
|
||||||
QJsonObject hashes;
|
QJsonObject hashes;
|
||||||
hashes["sha1"] = value.sha1;
|
hashes["sha1"] = value.sha1;
|
||||||
|
@ -44,6 +44,7 @@ class ModrinthPackExportTask : public Task {
|
|||||||
struct ResolvedFile {
|
struct ResolvedFile {
|
||||||
QString sha1, sha512, url;
|
QString sha1, sha512, url;
|
||||||
qint64 size;
|
qint64 size;
|
||||||
|
Metadata::ModSide side;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const QStringList PREFIXES;
|
static const QStringList PREFIXES;
|
||||||
|
@ -27,6 +27,11 @@
|
|||||||
static ModrinthAPI api;
|
static ModrinthAPI api;
|
||||||
static ModPlatform::ProviderCapabilities ProviderCaps;
|
static ModPlatform::ProviderCapabilities ProviderCaps;
|
||||||
|
|
||||||
|
bool shouldDownloadOnSide(QString side)
|
||||||
|
{
|
||||||
|
return side == "required" || side == "optional";
|
||||||
|
}
|
||||||
|
|
||||||
// https://docs.modrinth.com/api-spec/#tag/projects/operation/getProject
|
// https://docs.modrinth.com/api-spec/#tag/projects/operation/getProject
|
||||||
void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
||||||
{
|
{
|
||||||
@ -53,6 +58,17 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
|||||||
modAuthor.url = api.getAuthorURL(modAuthor.name);
|
modAuthor.url = api.getAuthorURL(modAuthor.name);
|
||||||
pack.authors.append(modAuthor);
|
pack.authors.append(modAuthor);
|
||||||
|
|
||||||
|
auto client = shouldDownloadOnSide(Json::ensureString(obj, "client_side"));
|
||||||
|
auto server = shouldDownloadOnSide(Json::ensureString(obj, "server_side"));
|
||||||
|
|
||||||
|
if (server && client) {
|
||||||
|
pack.side = "both";
|
||||||
|
} else if (server) {
|
||||||
|
pack.side = "server";
|
||||||
|
} else if (client) {
|
||||||
|
pack.side = "client";
|
||||||
|
}
|
||||||
|
|
||||||
// Modrinth can have more data than what's provided by the basic search :)
|
// Modrinth can have more data than what's provided by the basic search :)
|
||||||
pack.extraDataLoaded = false;
|
pack.extraDataLoaded = false;
|
||||||
}
|
}
|
||||||
@ -149,6 +165,8 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
|
|||||||
}
|
}
|
||||||
file.version = Json::requireString(obj, "name");
|
file.version = Json::requireString(obj, "name");
|
||||||
file.version_number = Json::requireString(obj, "version_number");
|
file.version_number = Json::requireString(obj, "version_number");
|
||||||
|
file.version_type = ModPlatform::IndexedVersionType(Json::requireString(obj, "version_type"));
|
||||||
|
|
||||||
file.changelog = Json::requireString(obj, "changelog");
|
file.changelog = Json::requireString(obj, "changelog");
|
||||||
|
|
||||||
auto dependencies = Json::ensureArray(obj, "dependencies");
|
auto dependencies = Json::ensureArray(obj, "dependencies");
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ModrinthPackManifest.h"
|
#include "ModrinthPackManifest.h"
|
||||||
|
#include <QFileInfo>
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
|
||||||
#include "modplatform/modrinth/ModrinthAPI.h"
|
#include "modplatform/modrinth/ModrinthAPI.h"
|
||||||
@ -56,8 +57,8 @@ void loadIndexedPack(Modpack& pack, QJsonObject& obj)
|
|||||||
pack.description = Json::ensureString(obj, "description");
|
pack.description = Json::ensureString(obj, "description");
|
||||||
auto temp_author_name = Json::ensureString(obj, "author");
|
auto temp_author_name = Json::ensureString(obj, "author");
|
||||||
pack.author = std::make_tuple(temp_author_name, api.getAuthorURL(temp_author_name));
|
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.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)
|
void loadIndexedInfo(Modpack& pack, QJsonObject& obj)
|
||||||
@ -128,6 +129,7 @@ auto loadIndexedVersion(QJsonObject& obj) -> ModpackVersion
|
|||||||
|
|
||||||
file.name = Json::requireString(obj, "name");
|
file.name = Json::requireString(obj, "name");
|
||||||
file.version = Json::requireString(obj, "version_number");
|
file.version = Json::requireString(obj, "version_number");
|
||||||
|
file.version_type = ModPlatform::IndexedVersionType(Json::requireString(obj, "version_type"));
|
||||||
file.changelog = Json::ensureString(obj, "changelog");
|
file.changelog = Json::ensureString(obj, "changelog");
|
||||||
|
|
||||||
file.id = Json::requireString(obj, "id");
|
file.id = Json::requireString(obj, "id");
|
||||||
|
@ -45,6 +45,8 @@
|
|||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
|
#include "modplatform/ModIndex.h"
|
||||||
|
|
||||||
class MinecraftInstance;
|
class MinecraftInstance;
|
||||||
|
|
||||||
namespace Modrinth {
|
namespace Modrinth {
|
||||||
@ -55,6 +57,7 @@ struct File {
|
|||||||
QCryptographicHash::Algorithm hashAlgorithm;
|
QCryptographicHash::Algorithm hashAlgorithm;
|
||||||
QByteArray hash;
|
QByteArray hash;
|
||||||
QQueue<QUrl> downloads;
|
QQueue<QUrl> downloads;
|
||||||
|
bool required = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DonationData {
|
struct DonationData {
|
||||||
@ -79,6 +82,7 @@ struct ModpackExtra {
|
|||||||
struct ModpackVersion {
|
struct ModpackVersion {
|
||||||
QString name;
|
QString name;
|
||||||
QString version;
|
QString version;
|
||||||
|
ModPlatform::IndexedVersionType version_type;
|
||||||
QString changelog;
|
QString changelog;
|
||||||
|
|
||||||
QString id;
|
QString id;
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "StringUtils.h"
|
#include "StringUtils.h"
|
||||||
@ -111,6 +113,7 @@ auto V1::createModFormat([[maybe_unused]] QDir& index_dir, ModPlatform::IndexedP
|
|||||||
mod.provider = mod_pack.provider;
|
mod.provider = mod_pack.provider;
|
||||||
mod.file_id = mod_version.fileId;
|
mod.file_id = mod_version.fileId;
|
||||||
mod.project_id = mod_pack.addonId;
|
mod.project_id = mod_pack.addonId;
|
||||||
|
mod.side = stringToSide(mod_pack.side);
|
||||||
|
|
||||||
return mod;
|
return mod;
|
||||||
}
|
}
|
||||||
@ -154,38 +157,52 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod)
|
|||||||
FS::ensureFilePathExists(index_file.fileName());
|
FS::ensureFilePathExists(index_file.fileName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toml::table update;
|
||||||
|
switch (mod.provider) {
|
||||||
|
case (ModPlatform::ResourceProvider::FLAME):
|
||||||
|
if (mod.file_id.toInt() == 0 || mod.project_id.toInt() == 0) {
|
||||||
|
qCritical() << QString("Did not write file %1 because missing information!").arg(normalized_fname);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
update = toml::table{
|
||||||
|
{ "file-id", mod.file_id.toInt() },
|
||||||
|
{ "project-id", mod.project_id.toInt() },
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case (ModPlatform::ResourceProvider::MODRINTH):
|
||||||
|
if (mod.mod_id().toString().isEmpty() || mod.version().toString().isEmpty()) {
|
||||||
|
qCritical() << QString("Did not write file %1 because missing information!").arg(normalized_fname);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
update = toml::table{
|
||||||
|
{ "mod-id", mod.mod_id().toString().toStdString() },
|
||||||
|
{ "version", mod.version().toString().toStdString() },
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (!index_file.open(QIODevice::ReadWrite)) {
|
if (!index_file.open(QIODevice::ReadWrite)) {
|
||||||
qCritical() << QString("Could not open file %1!").arg(indexFileName(mod.name));
|
qCritical() << QString("Could not open file %1!").arg(normalized_fname);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put TOML data into the file
|
// Put TOML data into the file
|
||||||
QTextStream in_stream(&index_file);
|
QTextStream in_stream(&index_file);
|
||||||
auto addToStream = [&in_stream](QString&& key, QString value) { in_stream << QString("%1 = \"%2\"\n").arg(key, value); };
|
|
||||||
|
|
||||||
{
|
{
|
||||||
addToStream("name", mod.name);
|
auto tbl = toml::table{ { "name", mod.name.toStdString() },
|
||||||
addToStream("filename", mod.filename);
|
{ "filename", mod.filename.toStdString() },
|
||||||
addToStream("side", mod.side);
|
{ "side", sideToString(mod.side).toStdString() },
|
||||||
|
{ "download",
|
||||||
in_stream << QString("\n[download]\n");
|
toml::table{
|
||||||
addToStream("mode", mod.mode);
|
{ "mode", mod.mode.toStdString() },
|
||||||
addToStream("url", mod.url.toString());
|
{ "url", mod.url.toString().toStdString() },
|
||||||
addToStream("hash-format", mod.hash_format);
|
{ "hash-format", mod.hash_format.toStdString() },
|
||||||
addToStream("hash", mod.hash);
|
{ "hash", mod.hash.toStdString() },
|
||||||
|
} },
|
||||||
in_stream << QString("\n[update]\n");
|
{ "update", toml::table{ { ProviderCaps.name(mod.provider), update } } } };
|
||||||
in_stream << QString("[update.%1]\n").arg(ProviderCaps.name(mod.provider));
|
std::stringstream ss;
|
||||||
switch (mod.provider) {
|
ss << tbl;
|
||||||
case (ModPlatform::ResourceProvider::FLAME):
|
in_stream << QString::fromStdString(ss.str());
|
||||||
in_stream << QString("file-id = %1\n").arg(mod.file_id.toString());
|
|
||||||
in_stream << QString("project-id = %1\n").arg(mod.project_id.toString());
|
|
||||||
break;
|
|
||||||
case (ModPlatform::ResourceProvider::MODRINTH):
|
|
||||||
addToStream("mod-id", mod.mod_id().toString());
|
|
||||||
addToStream("version", mod.version().toString());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
index_file.flush();
|
index_file.flush();
|
||||||
@ -258,7 +275,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
|
|||||||
{ // Basic info
|
{ // Basic info
|
||||||
mod.name = stringEntry(table, "name");
|
mod.name = stringEntry(table, "name");
|
||||||
mod.filename = stringEntry(table, "filename");
|
mod.filename = stringEntry(table, "filename");
|
||||||
mod.side = stringEntry(table, "side");
|
mod.side = stringToSide(stringEntry(table, "side"));
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // [download] info
|
{ // [download] info
|
||||||
@ -313,4 +330,28 @@ auto V1::getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto V1::sideToString(Side side) -> QString
|
||||||
|
{
|
||||||
|
switch (side) {
|
||||||
|
case Side::ClientSide:
|
||||||
|
return "client";
|
||||||
|
case Side::ServerSide:
|
||||||
|
return "server";
|
||||||
|
case Side::UniversalSide:
|
||||||
|
return "both";
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto V1::stringToSide(QString side) -> Side
|
||||||
|
{
|
||||||
|
if (side == "client")
|
||||||
|
return Side::ClientSide;
|
||||||
|
if (side == "server")
|
||||||
|
return Side::ServerSide;
|
||||||
|
if (side == "both")
|
||||||
|
return Side::UniversalSide;
|
||||||
|
return Side::UniversalSide;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Packwiz
|
} // namespace Packwiz
|
||||||
|
@ -35,12 +35,12 @@ auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool shoul
|
|||||||
|
|
||||||
class V1 {
|
class V1 {
|
||||||
public:
|
public:
|
||||||
|
enum class Side { ClientSide = 1 << 0, ServerSide = 1 << 1, UniversalSide = ClientSide | ServerSide };
|
||||||
struct Mod {
|
struct Mod {
|
||||||
QString slug{};
|
QString slug{};
|
||||||
QString name{};
|
QString name{};
|
||||||
QString filename{};
|
QString filename{};
|
||||||
// FIXME: make side an enum
|
Side side{ Side::UniversalSide };
|
||||||
QString side{ "both" };
|
|
||||||
|
|
||||||
// [download]
|
// [download]
|
||||||
QString mode{};
|
QString mode{};
|
||||||
@ -93,6 +93,9 @@ class V1 {
|
|||||||
* If the mod doesn't have a metadata, it simply returns an empty Mod object.
|
* If the mod doesn't have a metadata, it simply returns an empty Mod object.
|
||||||
* */
|
* */
|
||||||
static auto getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod;
|
static auto getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod;
|
||||||
|
|
||||||
|
static auto sideToString(Side side) -> QString;
|
||||||
|
static auto stringToSide(QString side) -> Side;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Packwiz
|
} // namespace Packwiz
|
||||||
|
@ -36,13 +36,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "NetJob.h"
|
#include "NetJob.h"
|
||||||
#include "Application.h"
|
|
||||||
#include "tasks/ConcurrentTask.h"
|
#include "tasks/ConcurrentTask.h"
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
|
#include "Application.h"
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network)
|
NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network) : ConcurrentTask(nullptr, job_name), m_network(network)
|
||||||
: ConcurrentTask(nullptr, job_name, APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()), m_network(network)
|
{
|
||||||
{}
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
|
setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
auto NetJob::addNetAction(NetAction::Ptr action) -> bool
|
auto NetJob::addNetAction(NetAction::Ptr action) -> bool
|
||||||
{
|
{
|
||||||
@ -140,6 +145,7 @@ void NetJob::updateState()
|
|||||||
|
|
||||||
void NetJob::emitFailed(QString reason)
|
void NetJob::emitFailed(QString reason)
|
||||||
{
|
{
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
auto response = CustomMessageBox::selectable(nullptr, "Confirm retry",
|
auto response = CustomMessageBox::selectable(nullptr, "Confirm retry",
|
||||||
"The tasks failed\n"
|
"The tasks failed\n"
|
||||||
"Failed urls\n" +
|
"Failed urls\n" +
|
||||||
@ -155,5 +161,6 @@ void NetJob::emitFailed(QString reason)
|
|||||||
startNext();
|
startNext();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
ConcurrentTask::emitFailed(reason);
|
ConcurrentTask::emitFailed(reason);
|
||||||
}
|
}
|
@ -46,6 +46,7 @@
|
|||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "BuildConfig.h"
|
||||||
|
|
||||||
#include "net/NetAction.h"
|
#include "net/NetAction.h"
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ void TranslationsModel::indexReceived()
|
|||||||
reloadLocalFiles();
|
reloadLocalFiles();
|
||||||
|
|
||||||
auto language = d->m_system_locale;
|
auto language = d->m_system_locale;
|
||||||
if (!findLanguage(language)) {
|
if (!findLanguageAsOptional(language).has_value()) {
|
||||||
language = d->m_system_language;
|
language = d->m_system_language;
|
||||||
}
|
}
|
||||||
selectLanguage(language);
|
selectLanguage(language);
|
||||||
@ -417,14 +417,17 @@ int TranslationsModel::columnCount([[maybe_unused]] const QModelIndex& parent) c
|
|||||||
return 2;
|
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; });
|
return 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 {
|
std::optional<Language> TranslationsModel::findLanguageAsOptional(const QString& key)
|
||||||
return found;
|
{
|
||||||
}
|
auto found = findLanguage(key);
|
||||||
|
if (found != d->m_languages.end())
|
||||||
|
return *found;
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void TranslationsModel::setUseSystemLocale(bool useSystemLocale)
|
void TranslationsModel::setUseSystemLocale(bool useSystemLocale)
|
||||||
@ -436,13 +439,13 @@ void TranslationsModel::setUseSystemLocale(bool useSystemLocale)
|
|||||||
bool TranslationsModel::selectLanguage(QString key)
|
bool TranslationsModel::selectLanguage(QString key)
|
||||||
{
|
{
|
||||||
QString& langCode = key;
|
QString& langCode = key;
|
||||||
auto langPtr = findLanguage(key);
|
auto langPtr = findLanguageAsOptional(key);
|
||||||
|
|
||||||
if (langCode.isEmpty()) {
|
if (langCode.isEmpty()) {
|
||||||
d->no_language_set = true;
|
d->no_language_set = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!langPtr) {
|
if (!langPtr.has_value()) {
|
||||||
qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode;
|
qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode;
|
||||||
langCode = defaultLangCode;
|
langCode = defaultLangCode;
|
||||||
} else {
|
} else {
|
||||||
@ -527,9 +530,8 @@ bool TranslationsModel::selectLanguage(QString key)
|
|||||||
QModelIndex TranslationsModel::selectedIndex()
|
QModelIndex TranslationsModel::selectedIndex()
|
||||||
{
|
{
|
||||||
auto found = findLanguage(d->m_selectedLanguage);
|
auto found = findLanguage(d->m_selectedLanguage);
|
||||||
if (found) {
|
if (found != d->m_languages.end()) {
|
||||||
// QVector iterator freely converts to pointer to contained type
|
return index(std::distance(d->m_languages.begin(), found), 0, QModelIndex());
|
||||||
return index(found - d->m_languages.begin(), 0, QModelIndex());
|
|
||||||
}
|
}
|
||||||
return QModelIndex();
|
return QModelIndex();
|
||||||
}
|
}
|
||||||
@ -562,8 +564,8 @@ void TranslationsModel::updateLanguage(QString key)
|
|||||||
qWarning() << "Cannot update builtin language" << key;
|
qWarning() << "Cannot update builtin language" << key;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto found = findLanguage(key);
|
auto found = findLanguageAsOptional(key);
|
||||||
if (!found) {
|
if (!found.has_value()) {
|
||||||
qWarning() << "Cannot update invalid language" << key;
|
qWarning() << "Cannot update invalid language" << key;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -578,8 +580,8 @@ void TranslationsModel::downloadTranslation(QString key)
|
|||||||
d->m_nextDownload = key;
|
d->m_nextDownload = key;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto lang = findLanguage(key);
|
auto lang = findLanguageAsOptional(key);
|
||||||
if (!lang) {
|
if (!lang.has_value()) {
|
||||||
qWarning() << "Will not download an unknown translation" << key;
|
qWarning() << "Will not download an unknown translation" << key;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
struct Language;
|
struct Language;
|
||||||
|
|
||||||
@ -40,7 +41,8 @@ class TranslationsModel : public QAbstractListModel {
|
|||||||
void setUseSystemLocale(bool useSystemLocale);
|
void setUseSystemLocale(bool useSystemLocale);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Language* findLanguage(const QString& key);
|
QVector<Language>::Iterator findLanguage(const QString& key);
|
||||||
|
std::optional<Language> findLanguageAsOptional(const QString& key);
|
||||||
void reloadLocalFiles();
|
void reloadLocalFiles();
|
||||||
void downloadTranslation(QString key);
|
void downloadTranslation(QString key);
|
||||||
void downloadNext();
|
void downloadNext();
|
||||||
|
@ -219,7 +219,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||||||
ui->actionDISCORD->setVisible(!BuildConfig.DISCORD_URL.isEmpty());
|
ui->actionDISCORD->setVisible(!BuildConfig.DISCORD_URL.isEmpty());
|
||||||
ui->actionREDDIT->setVisible(!BuildConfig.SUBREDDIT_URL.isEmpty());
|
ui->actionREDDIT->setVisible(!BuildConfig.SUBREDDIT_URL.isEmpty());
|
||||||
|
|
||||||
ui->actionCheckUpdate->setVisible(BuildConfig.UPDATER_ENABLED);
|
ui->actionCheckUpdate->setVisible(APPLICATION->updaterEnabled());
|
||||||
|
|
||||||
#ifndef Q_OS_MAC
|
#ifndef Q_OS_MAC
|
||||||
ui->actionAddToPATH->setVisible(false);
|
ui->actionAddToPATH->setVisible(false);
|
||||||
@ -363,7 +363,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||||||
// Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit.
|
// Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit.
|
||||||
// Template hell sucks...
|
// Template hell sucks...
|
||||||
connect(APPLICATION->accounts().get(), &AccountList::defaultAccountChanged, [this] { defaultAccountChanged(); });
|
connect(APPLICATION->accounts().get(), &AccountList::defaultAccountChanged, [this] { defaultAccountChanged(); });
|
||||||
connect(APPLICATION->accounts().get(), &AccountList::listChanged, [this] { repopulateAccountsMenu(); });
|
connect(APPLICATION->accounts().get(), &AccountList::listChanged, [this] { defaultAccountChanged(); });
|
||||||
|
|
||||||
// Show initial account
|
// Show initial account
|
||||||
defaultAccountChanged();
|
defaultAccountChanged();
|
||||||
@ -377,7 +377,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||||||
updateNewsLabel();
|
updateNewsLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BuildConfig.UPDATER_ENABLED) {
|
if (APPLICATION->updaterEnabled()) {
|
||||||
bool updatesAllowed = APPLICATION->updatesAreAllowed();
|
bool updatesAllowed = APPLICATION->updatesAreAllowed();
|
||||||
updatesAllowedChanged(updatesAllowed);
|
updatesAllowedChanged(updatesAllowed);
|
||||||
|
|
||||||
@ -514,10 +514,10 @@ void MainWindow::showInstanceContextMenu(const QPoint& pos)
|
|||||||
} else {
|
} else {
|
||||||
auto group = view->groupNameAt(pos);
|
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);
|
actionVoid->setEnabled(false);
|
||||||
|
|
||||||
QAction* actionCreateInstance = new QAction(tr("Create instance"), this);
|
QAction* actionCreateInstance = new QAction(tr("&Create instance"), this);
|
||||||
actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip());
|
actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip());
|
||||||
if (!group.isNull()) {
|
if (!group.isNull()) {
|
||||||
QVariantMap instance_action_data;
|
QVariantMap instance_action_data;
|
||||||
@ -531,12 +531,13 @@ void MainWindow::showInstanceContextMenu(const QPoint& pos)
|
|||||||
actions.prepend(actionVoid);
|
actions.prepend(actionVoid);
|
||||||
actions.append(actionCreateInstance);
|
actions.append(actionCreateInstance);
|
||||||
if (!group.isNull()) {
|
if (!group.isNull()) {
|
||||||
QAction* actionDeleteGroup = new QAction(tr("Delete group '%1'").arg(group), this);
|
QAction* actionDeleteGroup = new QAction(tr("&Delete group"), this);
|
||||||
QVariantMap delete_group_action_data;
|
connect(actionDeleteGroup, &QAction::triggered, this, [this, group] { deleteGroup(group); });
|
||||||
delete_group_action_data["group"] = group;
|
|
||||||
actionDeleteGroup->setData(delete_group_action_data);
|
|
||||||
connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup()));
|
|
||||||
actions.append(actionDeleteGroup);
|
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;
|
QMenu myMenu;
|
||||||
@ -676,7 +677,7 @@ void MainWindow::repopulateAccountsMenu()
|
|||||||
|
|
||||||
void MainWindow::updatesAllowedChanged(bool allowed)
|
void MainWindow::updatesAllowedChanged(bool allowed)
|
||||||
{
|
{
|
||||||
if (!BuildConfig.UPDATER_ENABLED) {
|
if (!APPLICATION->updaterEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ui->actionCheckUpdate->setEnabled(allowed);
|
ui->actionCheckUpdate->setEnabled(allowed);
|
||||||
@ -872,7 +873,7 @@ void MainWindow::finalizeInstance(InstancePtr inst)
|
|||||||
} else {
|
} else {
|
||||||
CustomMessageBox::selectable(this, tr("Error"),
|
CustomMessageBox::selectable(this, tr("Error"),
|
||||||
tr("The launcher cannot download Minecraft or update instances unless you have at least "
|
tr("The launcher cannot download Minecraft or update instances unless you have at least "
|
||||||
"one account added.\nPlease add your Microsoft or Mojang account."),
|
"one account added.\nPlease add a Microsoft account."),
|
||||||
QMessageBox::Warning)
|
QMessageBox::Warning)
|
||||||
->show();
|
->show();
|
||||||
}
|
}
|
||||||
@ -1128,40 +1129,49 @@ void MainWindow::on_actionChangeInstGroup_triggered()
|
|||||||
if (!m_selectedInstance)
|
if (!m_selectedInstance)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool ok = false;
|
|
||||||
InstanceId instId = m_selectedInstance->id();
|
InstanceId instId = m_selectedInstance->id();
|
||||||
QString name(APPLICATION->instances()->getInstanceGroup(instId));
|
QString src(APPLICATION->instances()->getInstanceGroup(instId));
|
||||||
auto groups = APPLICATION->instances()->getGroups();
|
|
||||||
groups.insert(0, "");
|
QStringList groups = APPLICATION->instances()->getGroups();
|
||||||
groups.sort(Qt::CaseInsensitive);
|
groups.prepend("");
|
||||||
int foo = groups.indexOf(name);
|
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) {
|
if (ok) {
|
||||||
APPLICATION->instances()->setInstanceGroup(instId, name);
|
APPLICATION->instances()->setInstanceGroup(instId, dst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::deleteGroup()
|
void MainWindow::deleteGroup(QString group)
|
||||||
{
|
{
|
||||||
QObject* obj = sender();
|
Q_ASSERT(!group.isEmpty());
|
||||||
if (!obj)
|
|
||||||
|
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;
|
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;
|
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()
|
void MainWindow::undoTrashInstance()
|
||||||
@ -1208,7 +1218,7 @@ void MainWindow::refreshInstances()
|
|||||||
|
|
||||||
void MainWindow::checkForUpdates()
|
void MainWindow::checkForUpdates()
|
||||||
{
|
{
|
||||||
if (BuildConfig.UPDATER_ENABLED) {
|
if (APPLICATION->updaterEnabled()) {
|
||||||
APPLICATION->triggerUpdateCheck();
|
APPLICATION->triggerUpdateCheck();
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Updater not set up. Cannot check for updates.";
|
qWarning() << "Updater not set up. Cannot check for updates.";
|
||||||
|
@ -148,7 +148,8 @@ class MainWindow : public QMainWindow {
|
|||||||
|
|
||||||
void on_actionDeleteInstance_triggered();
|
void on_actionDeleteInstance_triggered();
|
||||||
|
|
||||||
void deleteGroup();
|
void deleteGroup(QString group);
|
||||||
|
void renameGroup(QString group);
|
||||||
void undoTrashInstance();
|
void undoTrashInstance();
|
||||||
|
|
||||||
inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); }
|
inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); }
|
||||||
|
@ -41,8 +41,8 @@
|
|||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& 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)
|
: QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods), m_hash_type(hash_type)
|
||||||
{
|
{
|
||||||
m_hashing_task = shared_qobject_ptr<ConcurrentTask>(
|
m_hashing_task = shared_qobject_ptr<ConcurrentTask>(
|
||||||
new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
|
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
|
/// @param path the path to the local file being hashed
|
||||||
void BlockedModsDialog::buildHashTask(QString path)
|
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;
|
qDebug() << "[Blocked Mods Dialog] Creating Hash task for path: " << path;
|
||||||
|
|
||||||
@ -335,6 +335,13 @@ bool BlockedModsDialog::checkValidPath(QString path)
|
|||||||
|
|
||||||
for (auto& mod : m_mods) {
|
for (auto& mod : m_mods) {
|
||||||
if (compare(filename, mod.name)) {
|
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;
|
qDebug() << "[Blocked Mods Dialog] Name match found:" << mod.name << "| From path:" << path;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ class BlockedModsDialog : public QDialog {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
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;
|
~BlockedModsDialog() override;
|
||||||
|
|
||||||
@ -73,6 +73,7 @@ class BlockedModsDialog : public QDialog {
|
|||||||
QSet<QString> m_pending_hash_paths;
|
QSet<QString> m_pending_hash_paths;
|
||||||
bool m_rehash_pending;
|
bool m_rehash_pending;
|
||||||
QPushButton* m_openMissingButton;
|
QPushButton* m_openMissingButton;
|
||||||
|
QString m_hash_type;
|
||||||
|
|
||||||
void openAll(bool missingOnly);
|
void openAll(bool missingOnly);
|
||||||
void addDownloadFolder();
|
void addDownloadFolder();
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
/*
|
/*
|
||||||
* Prism Launcher - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* 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
|
* 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
|
* 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->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
|
||||||
ui->instNameTextBox->setText(original->name());
|
ui->instNameTextBox->setText(original->name());
|
||||||
ui->instNameTextBox->setFocus();
|
ui->instNameTextBox->setFocus();
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
||||||
auto groupList = APPLICATION->instances()->getGroups();
|
QStringList groups = APPLICATION->instances()->getGroups();
|
||||||
QSet<QString> groups(groupList.begin(), groupList.end());
|
groups.prepend("");
|
||||||
groupList = QStringList(groups.values());
|
ui->groupBox->addItems(groups);
|
||||||
#else
|
int index = groups.indexOf(APPLICATION->instances()->getInstanceGroup(m_original->id()));
|
||||||
auto groups = APPLICATION->instances()->getGroups().toSet();
|
if (index == -1)
|
||||||
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) {
|
|
||||||
index = 0;
|
index = 0;
|
||||||
}
|
|
||||||
ui->groupBox->setCurrentIndex(index);
|
ui->groupBox->setCurrentIndex(index);
|
||||||
ui->groupBox->lineEdit()->setPlaceholderText(tr("No group"));
|
ui->groupBox->lineEdit()->setPlaceholderText(tr("No group"));
|
||||||
ui->copySavesCheckbox->setChecked(m_selectedOptions.isCopySavesEnabled());
|
ui->copySavesCheckbox->setChecked(m_selectedOptions.isCopySavesEnabled());
|
||||||
|
@ -1,115 +0,0 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "LoginDialog.h"
|
|
||||||
#include "ui_LoginDialog.h"
|
|
||||||
|
|
||||||
#include "minecraft/auth/AccountTask.h"
|
|
||||||
|
|
||||||
#include <QtWidgets/QPushButton>
|
|
||||||
|
|
||||||
LoginDialog::LoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::LoginDialog)
|
|
||||||
{
|
|
||||||
ui->setupUi(this);
|
|
||||||
ui->progressBar->setVisible(false);
|
|
||||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
|
||||||
|
|
||||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
|
||||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
|
||||||
}
|
|
||||||
|
|
||||||
LoginDialog::~LoginDialog()
|
|
||||||
{
|
|
||||||
delete ui;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stage 1: User interaction
|
|
||||||
void LoginDialog::accept()
|
|
||||||
{
|
|
||||||
setUserInputsEnabled(false);
|
|
||||||
ui->progressBar->setVisible(true);
|
|
||||||
|
|
||||||
// Setup the login task and start it
|
|
||||||
m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text());
|
|
||||||
m_loginTask = m_account->login(ui->passTextBox->text());
|
|
||||||
connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed);
|
|
||||||
connect(m_loginTask.get(), &Task::succeeded, this, &LoginDialog::onTaskSucceeded);
|
|
||||||
connect(m_loginTask.get(), &Task::status, this, &LoginDialog::onTaskStatus);
|
|
||||||
connect(m_loginTask.get(), &Task::progress, this, &LoginDialog::onTaskProgress);
|
|
||||||
m_loginTask->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoginDialog::setUserInputsEnabled(bool enable)
|
|
||||||
{
|
|
||||||
ui->userTextBox->setEnabled(enable);
|
|
||||||
ui->passTextBox->setEnabled(enable);
|
|
||||||
ui->buttonBox->setEnabled(enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable the OK button only when both textboxes contain something.
|
|
||||||
void LoginDialog::on_userTextBox_textEdited(const QString& newText)
|
|
||||||
{
|
|
||||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty());
|
|
||||||
}
|
|
||||||
void LoginDialog::on_passTextBox_textEdited(const QString& newText)
|
|
||||||
{
|
|
||||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoginDialog::onTaskFailed(const QString& reason)
|
|
||||||
{
|
|
||||||
// Set message
|
|
||||||
auto lines = reason.split('\n');
|
|
||||||
QString processed;
|
|
||||||
for (auto line : lines) {
|
|
||||||
if (line.size()) {
|
|
||||||
processed += "<font color='red'>" + line + "</font><br />";
|
|
||||||
} else {
|
|
||||||
processed += "<br />";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ui->label->setText(processed);
|
|
||||||
|
|
||||||
// Re-enable user-interaction
|
|
||||||
setUserInputsEnabled(true);
|
|
||||||
ui->progressBar->setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoginDialog::onTaskSucceeded()
|
|
||||||
{
|
|
||||||
QDialog::accept();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoginDialog::onTaskStatus(const QString& status)
|
|
||||||
{
|
|
||||||
ui->label->setText(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoginDialog::onTaskProgress(qint64 current, qint64 total)
|
|
||||||
{
|
|
||||||
ui->progressBar->setMaximum(total);
|
|
||||||
ui->progressBar->setValue(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public interface
|
|
||||||
MinecraftAccountPtr LoginDialog::newAccount(QWidget* parent, QString msg)
|
|
||||||
{
|
|
||||||
LoginDialog dlg(parent);
|
|
||||||
dlg.ui->label->setText(msg);
|
|
||||||
if (dlg.exec() == QDialog::Accepted) {
|
|
||||||
return dlg.m_account;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QtCore/QEventLoop>
|
|
||||||
#include <QtWidgets/QDialog>
|
|
||||||
|
|
||||||
#include "minecraft/auth/MinecraftAccount.h"
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
class LoginDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoginDialog : public QDialog {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
~LoginDialog();
|
|
||||||
|
|
||||||
static MinecraftAccountPtr newAccount(QWidget* parent, QString message);
|
|
||||||
|
|
||||||
private:
|
|
||||||
explicit LoginDialog(QWidget* parent = 0);
|
|
||||||
|
|
||||||
void setUserInputsEnabled(bool enable);
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
void accept();
|
|
||||||
|
|
||||||
void onTaskFailed(const QString& reason);
|
|
||||||
void onTaskSucceeded();
|
|
||||||
void onTaskStatus(const QString& status);
|
|
||||||
void onTaskProgress(qint64 current, qint64 total);
|
|
||||||
|
|
||||||
void on_userTextBox_textEdited(const QString& newText);
|
|
||||||
void on_passTextBox_textEdited(const QString& newText);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ui::LoginDialog* ui;
|
|
||||||
MinecraftAccountPtr m_account;
|
|
||||||
Task::Ptr m_loginTask;
|
|
||||||
};
|
|
@ -1,77 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>LoginDialog</class>
|
|
||||||
<widget class="QDialog" name="LoginDialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>421</width>
|
|
||||||
<height>198</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Add Account</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="text">
|
|
||||||
<string notr="true">Message label placeholder.</string>
|
|
||||||
</property>
|
|
||||||
<property name="textFormat">
|
|
||||||
<enum>Qt::RichText</enum>
|
|
||||||
</property>
|
|
||||||
<property name="textInteractionFlags">
|
|
||||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="userTextBox">
|
|
||||||
<property name="placeholderText">
|
|
||||||
<string>Email</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="passTextBox">
|
|
||||||
<property name="echoMode">
|
|
||||||
<enum>QLineEdit::Password</enum>
|
|
||||||
</property>
|
|
||||||
<property name="placeholderText">
|
|
||||||
<string>Password</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QProgressBar" name="progressBar">
|
|
||||||
<property name="value">
|
|
||||||
<number>24</number>
|
|
||||||
</property>
|
|
||||||
<property name="textVisible">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</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/>
|
|
||||||
</ui>
|
|
@ -219,8 +219,10 @@ void ModUpdateDialog::checkCandidates()
|
|||||||
if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME)
|
if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME)
|
||||||
changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt());
|
changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt());
|
||||||
auto download_task = makeShared<ResourceDownloadTask>(dep->pack, dep->version, m_mod_model);
|
auto download_task = makeShared<ResourceDownloadTask>(dep->pack, dep->version, m_mod_model);
|
||||||
CheckUpdateTask::UpdatableMod updatable = { dep->pack->name, dep->version.hash, "", dep->version.version,
|
CheckUpdateTask::UpdatableMod updatable = {
|
||||||
changelog, dep->pack->provider, download_task };
|
dep->pack->name, dep->version.hash, "", dep->version.version, dep->version.version_type,
|
||||||
|
changelog, dep->pack->provider, download_task
|
||||||
|
};
|
||||||
|
|
||||||
appendMod(updatable, getRequiredBy.value(dep->version.addonId.toString()));
|
appendMod(updatable, getRequiredBy.value(dep->version.addonId.toString()));
|
||||||
m_tasks.insert(updatable.name, updatable.download);
|
m_tasks.insert(updatable.name, updatable.download);
|
||||||
@ -415,6 +417,11 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri
|
|||||||
auto new_version_item = new QTreeWidgetItem(item_top);
|
auto new_version_item = new QTreeWidgetItem(item_top);
|
||||||
new_version_item->setText(0, tr("New version: %1").arg(info.new_version));
|
new_version_item->setText(0, tr("New version: %1").arg(info.new_version));
|
||||||
|
|
||||||
|
if (info.new_version_type.has_value()) {
|
||||||
|
auto new_version_type_itme = new QTreeWidgetItem(item_top);
|
||||||
|
new_version_type_itme->setText(0, tr("New Version Type: %1").arg(info.new_version_type.value().toString()));
|
||||||
|
}
|
||||||
|
|
||||||
if (!requiredBy.isEmpty()) {
|
if (!requiredBy.isEmpty()) {
|
||||||
auto requiredByItem = new QTreeWidgetItem(item_top);
|
auto requiredByItem = new QTreeWidgetItem(item_top);
|
||||||
if (requiredBy.length() == 1) {
|
if (requiredBy.length() == 1) {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
/*
|
/*
|
||||||
* Prism Launcher - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -75,23 +76,14 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
|
|||||||
InstIconKey = "default";
|
InstIconKey = "default";
|
||||||
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
|
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
QStringList groups = APPLICATION->instances()->getGroups();
|
||||||
auto groupList = APPLICATION->instances()->getGroups();
|
groups.prepend("");
|
||||||
auto groups = QSet<QString>(groupList.begin(), groupList.end());
|
int index = groups.indexOf(initialGroup);
|
||||||
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);
|
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
index = 0;
|
index = 1;
|
||||||
|
groups.insert(index, initialGroup);
|
||||||
}
|
}
|
||||||
|
ui->groupBox->addItems(groups);
|
||||||
ui->groupBox->setCurrentIndex(index);
|
ui->groupBox->setCurrentIndex(index);
|
||||||
ui->groupBox->lineEdit()->setPlaceholderText(tr("No group"));
|
ui->groupBox->lineEdit()->setPlaceholderText(tr("No group"));
|
||||||
|
|
||||||
@ -237,8 +229,7 @@ void NewInstanceDialog::setSuggestedIcon(const QString& key)
|
|||||||
|
|
||||||
InstanceTask* NewInstanceDialog::extractTask()
|
InstanceTask* NewInstanceDialog::extractTask()
|
||||||
{
|
{
|
||||||
InstanceTask* extracted = creationTask.get();
|
InstanceTask* extracted = creationTask.release();
|
||||||
creationTask.release();
|
|
||||||
|
|
||||||
InstanceName inst_name(ui->instNameTextBox->placeholderText().trimmed(), importVersion);
|
InstanceName inst_name(ui->instNameTextBox->placeholderText().trimmed(), importVersion);
|
||||||
inst_name.setName(ui->instNameTextBox->text().trimmed());
|
inst_name.setName(ui->instNameTextBox->text().trimmed());
|
||||||
|
@ -48,6 +48,9 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Global Task Status...</string>
|
<string>Global Task Status...</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -109,8 +112,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>464</width>
|
<width>460</width>
|
||||||
<height>96</height>
|
<height>108</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="taskProgressLayout">
|
<layout class="QVBoxLayout" name="taskProgressLayout">
|
||||||
|
@ -167,8 +167,8 @@ void ResourceDownloadDialog::confirm()
|
|||||||
});
|
});
|
||||||
for (auto& task : selected) {
|
for (auto& task : selected) {
|
||||||
confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(),
|
confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(),
|
||||||
ProviderCaps.name(task->getProvider()),
|
ProviderCaps.name(task->getProvider()), getRequiredBy.value(task->getPack()->addonId.toString()),
|
||||||
getRequiredBy.value(task->getPack()->addonId.toString()) });
|
task->getVersion().version_type.toString() });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (confirm_dialog->exec()) {
|
if (confirm_dialog->exec()) {
|
||||||
|
@ -77,6 +77,10 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
|
|||||||
itemTop->insertChildren(childIndx++, { requiredByItem });
|
itemTop->insertChildren(childIndx++, { requiredByItem });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto versionTypeItem = new QTreeWidgetItem(itemTop);
|
||||||
|
versionTypeItem->setText(0, tr("Version Type: %1").arg(info.version_type));
|
||||||
|
itemTop->insertChildren(childIndx++, { versionTypeItem });
|
||||||
|
|
||||||
ui->modTreeWidget->addTopLevelItem(itemTop);
|
ui->modTreeWidget->addTopLevelItem(itemTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ class ReviewMessageBox : public QDialog {
|
|||||||
QString custom_file_path{};
|
QString custom_file_path{};
|
||||||
QString provider;
|
QString provider;
|
||||||
QStringList required_by;
|
QStringList required_by;
|
||||||
|
QString version_type;
|
||||||
};
|
};
|
||||||
|
|
||||||
void appendResource(ResourceInformation&& info);
|
void appendResource(ResourceInformation&& info);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user