Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into instance_copy_progress
This commit is contained in:
commit
e43a557a0b
5
.clang-tidy
Normal file
5
.clang-tidy
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Checks:
|
||||||
|
- modernize-use-using
|
||||||
|
- readability-avoid-const-params-in-decls
|
||||||
|
|
||||||
|
SystemHeaders: false
|
4
.github/workflows/backport.yml
vendored
4
.github/workflows/backport.yml
vendored
@ -20,11 +20,11 @@ jobs:
|
|||||||
if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name))
|
if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name))
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
- name: Create backport PRs
|
- name: Create backport PRs
|
||||||
uses: korthout/backport-action@v1.4.0
|
uses: korthout/backport-action@v2.1.1
|
||||||
with:
|
with:
|
||||||
# Config README: https://github.com/korthout/backport-action#backport-action
|
# Config README: https://github.com/korthout/backport-action#backport-action
|
||||||
pull_description: |-
|
pull_description: |-
|
||||||
|
102
.github/workflows/build.yml
vendored
102
.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 }}
|
||||||
|
|
||||||
@ -125,11 +112,11 @@ jobs:
|
|||||||
# PREPARE
|
# PREPARE
|
||||||
##
|
##
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
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:
|
||||||
@ -164,7 +151,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve ccache cache (Windows MinGW-w64)
|
- name: Retrieve ccache cache (Windows MinGW-w64)
|
||||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||||
uses: actions/cache@v3.3.1
|
uses: actions/cache@v3.3.2
|
||||||
with:
|
with:
|
||||||
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 }}
|
||||||
@ -214,12 +201,12 @@ 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 }}
|
||||||
@ -231,11 +218,11 @@ jobs:
|
|||||||
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 }}
|
||||||
@ -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,7 +389,6 @@ 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
|
||||||
@ -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"
|
||||||
|
|
||||||
@ -523,7 +501,7 @@ jobs:
|
|||||||
export SIGN=1
|
export SIGN=1
|
||||||
export SIGN_KEY=${{ secrets.GPG_PRIVATE_KEY_ID }}
|
export SIGN_KEY=${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||||
mkdir -p ~/.gnupg/
|
mkdir -p ~/.gnupg/
|
||||||
printf "$GPG_PRIVATE_KEY" | base64 --decode > ~/.gnupg/private.key
|
echo "$GPG_PRIVATE_KEY" > ~/.gnupg/private.key
|
||||||
gpg --import ~/.gnupg/private.key
|
gpg --import ~/.gnupg/private.key
|
||||||
else
|
else
|
||||||
echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY
|
echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY
|
||||||
@ -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)
|
||||||
@ -616,14 +594,14 @@ jobs:
|
|||||||
flatpak:
|
flatpak:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: bilelmoussaoui/flatpak-github-actions:kde-5.15-22.08
|
image: bilelmoussaoui/flatpak-github-actions:kde-5.15-23.08
|
||||||
options: --privileged
|
options: --privileged
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
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
|
||||||
|
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@ -8,7 +8,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
|
|
||||||
|
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 }}
|
||||||
|
22
.github/workflows/trigger_release.yml
vendored
22
.github/workflows/trigger_release.yml
vendored
@ -3,10 +3,9 @@ name: Build Application and Make Release
|
|||||||
on:
|
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
|
||||||
@ -26,10 +27,10 @@ jobs:
|
|||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
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
|
||||||
|
6
.github/workflows/update-flake.yml
vendored
6
.github/workflows/update-flake.yml
vendored
@ -16,10 +16,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@v22
|
- uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23
|
||||||
|
|
||||||
- uses: DeterminateSystems/update-flake-lock@v19
|
- uses: DeterminateSystems/update-flake-lock@v20
|
||||||
with:
|
with:
|
||||||
commit-msg: "chore(nix): update lockfile"
|
commit-msg: "chore(nix): update lockfile"
|
||||||
pr-title: "chore(nix): update lockfile"
|
pr-title: "chore(nix): update lockfile"
|
||||||
|
@ -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)
|
||||||
@ -339,6 +347,11 @@ add_subdirectory(program_info)
|
|||||||
####################################### Install layout #######################################
|
####################################### Install layout #######################################
|
||||||
|
|
||||||
set(Launcher_ENABLE_UPDATER NO)
|
set(Launcher_ENABLE_UPDATER NO)
|
||||||
|
set(Launcher_BUILD_UPDATER NO)
|
||||||
|
|
||||||
|
if (NOT APPLE AND (NOT Launcher_UPDATER_GITHUB_REPO STREQUAL "" AND NOT Launcher_BUILD_ARTIFACT STREQUAL ""))
|
||||||
|
set(Launcher_BUILD_UPDATER YES)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(NOT (UNIX AND APPLE))
|
if(NOT (UNIX AND APPLE))
|
||||||
# Install "portable.txt" if selected component is "portable"
|
# Install "portable.txt" if selected component is "portable"
|
||||||
|
15
README.md
15
README.md
@ -18,11 +18,18 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
- All downloads and instructions for Prism Launcher can be found on our [Website](https://prismlauncher.org/download).
|
- All downloads and instructions for Prism Launcher can be found on our [Website](https://prismlauncher.org/download).
|
||||||
- Last build status can be found in the [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions).
|
- Last build status can be found in the [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions) tab (this also includes the pull requests status).
|
||||||
|
|
||||||
### Development Builds
|
### Development Builds
|
||||||
|
|
||||||
There are development builds available [here](https://github.com/PrismLauncher/PrismLauncher/actions). These have debug information in the binaries, so their file sizes are relatively larger.
|
Please understand that these builds are not intended for most users. There may be bugs, and other instabilities. You have been warned.
|
||||||
|
|
||||||
|
There are development builds available through:
|
||||||
|
|
||||||
|
- [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions) (includes builds from pull requests opened by contribuitors)
|
||||||
|
- [nightly.link](https://nightly.link/PrismLauncher/PrismLauncher/workflows/trigger_builds/develop) (this will always point only to the latest version of develop)
|
||||||
|
|
||||||
|
These have debug information in the binaries, so their file sizes are relatively larger.
|
||||||
|
|
||||||
Prebuilt Development builds are provided for **Linux**, **Windows** and **macOS**.
|
Prebuilt Development builds are provided for **Linux**, **Windows** and **macOS**.
|
||||||
|
|
||||||
@ -30,7 +37,7 @@ For **Arch**, **Debian**, **Fedora**, **OpenSUSE (Tumbleweed)** and **Gentoo**,
|
|||||||
|
|
||||||
[](https://aur.archlinux.org/packages/prismlauncher-git) [](https://aur.archlinux.org/packages/prismlauncher-qt5-git) [](https://mpr.makedeb.org/packages/prismlauncher-git)<br />[](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [](https://build.opensuse.org/project/show/home:getchoo) [](https://packages.gentoo.org/packages/games-action/prismlauncher)
|
[](https://aur.archlinux.org/packages/prismlauncher-git) [](https://aur.archlinux.org/packages/prismlauncher-qt5-git) [](https://mpr.makedeb.org/packages/prismlauncher-git)<br />[](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [](https://build.opensuse.org/project/show/home:getchoo) [](https://packages.gentoo.org/packages/games-action/prismlauncher)
|
||||||
|
|
||||||
These packages are also availiable to all the distributions based on the ones mentioned above.
|
These packages are also available to all the distributions based on the ones mentioned above.
|
||||||
|
|
||||||
## Community & Support
|
## Community & Support
|
||||||
|
|
||||||
@ -50,7 +57,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;
|
||||||
|
@ -68,6 +68,8 @@ function(
|
|||||||
/w14906 # string literal cast to 'LPWSTR'
|
/w14906 # string literal cast to 'LPWSTR'
|
||||||
/w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied
|
/w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied
|
||||||
/permissive- # standards conformance mode for MSVC compiler.
|
/permissive- # standards conformance mode for MSVC compiler.
|
||||||
|
|
||||||
|
/we4062 # forbid omitting a possible value of an enum in a switch statement
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -75,7 +77,6 @@ function(
|
|||||||
set(CLANG_WARNINGS
|
set(CLANG_WARNINGS
|
||||||
-Wall
|
-Wall
|
||||||
-Wextra # reasonable and standard
|
-Wextra # reasonable and standard
|
||||||
-Wextra-semi # Warn about semicolon after in-class function definition.
|
|
||||||
-Wshadow # warn the user if a variable declaration shadows one from a parent context
|
-Wshadow # warn the user if a variable declaration shadows one from a parent context
|
||||||
-Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps
|
-Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps
|
||||||
# catch hard to track down memory errors
|
# catch hard to track down memory errors
|
||||||
@ -90,6 +91,12 @@ 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
|
||||||
|
|
||||||
|
-Werror=switch # forbid omitting a possible value of an enum in a switch statement
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -101,6 +108,8 @@ function(
|
|||||||
-Wduplicated-branches # warn if if / else branches have duplicated code
|
-Wduplicated-branches # warn if if / else branches have duplicated code
|
||||||
-Wlogical-op # warn about logical operations being used where bitwise were probably wanted
|
-Wlogical-op # warn about logical operations being used where bitwise were probably wanted
|
||||||
-Wuseless-cast # warn if you perform a cast to the same type
|
-Wuseless-cast # warn if you perform a cast to the same type
|
||||||
|
|
||||||
|
-Werror=switch # forbid omitting a possible value of an enum in a switch statement
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -125,6 +134,8 @@ function(
|
|||||||
-Woverloaded-virtual
|
-Woverloaded-virtual
|
||||||
-Wuseless-cast
|
-Wuseless-cast
|
||||||
-Wextra-semi
|
-Wextra-semi
|
||||||
|
|
||||||
|
-Werror=switch # forbid omitting a possible value of an enum in a switch statement
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_options(
|
target_compile_options(
|
||||||
|
52
flake.lock
generated
52
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": 1690933134,
|
"lastModified": 1698882062,
|
||||||
"narHash": "sha256-ab989mN63fQZBFrkk4Q8bYxQCktuHmBIBqUG1jl6/FQ=",
|
"narHash": "sha256-HkhafUayIqxXyHH1X8d9RDl1M2CkFgZLjKD3MzabiEo=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "59cf3f1447cfc75087e7273b04b31e689a8599fb",
|
"rev": "8c9fa2545007b49a5db5f650ae91f227672c3877",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -76,11 +76,11 @@
|
|||||||
"libnbtplusplus": {
|
"libnbtplusplus": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1690036783,
|
"lastModified": 1699286814,
|
||||||
"narHash": "sha256-A5kTgICnx+Qdq3Fir/bKTfdTt/T1NQP2SC+nhN1ENug=",
|
"narHash": "sha256-yy0q+bky80LtK1GWzz7qpM+aAGrOqLuewbid8WT1ilk=",
|
||||||
"owner": "PrismLauncher",
|
"owner": "PrismLauncher",
|
||||||
"repo": "libnbtplusplus",
|
"repo": "libnbtplusplus",
|
||||||
"rev": "a5e8fd52b8bf4ab5d5bcc042b2a247867589985f",
|
"rev": "23b955121b8217c1c348a9ed2483167a6f3ff4ad",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -89,13 +89,28 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nix-filter": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1694857738,
|
||||||
|
"narHash": "sha256-bxxNyLHjhu0N8T3REINXQ2ZkJco0ABFPn6PIe2QUfqo=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "nix-filter",
|
||||||
|
"rev": "41fd48e00c22b4ced525af521ead8792402de0ea",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "nix-filter",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1692463654,
|
"lastModified": 1700108881,
|
||||||
"narHash": "sha256-F8hZmsQINI+S6UROM4jyxAMbQLtzE44pI8Nk6NtMdao=",
|
"narHash": "sha256-+Lqybl8kj0+nD/IlAWPPG/RDTa47gff9nbei0u7BntE=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ca3c9ac9f4cdd4bea19f592b32bb59b74ab7d783",
|
"rev": "7414e9ee0b3e9903c24d3379f577a417f0aae5f1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -108,11 +123,11 @@
|
|||||||
"nixpkgs-lib": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"dir": "lib",
|
"dir": "lib",
|
||||||
"lastModified": 1690881714,
|
"lastModified": 1698611440,
|
||||||
"narHash": "sha256-h/nXluEqdiQHs1oSgkOOWF+j8gcJMWhwnZ9PFabN6q0=",
|
"narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "9e1960bc196baf6881340d53dccb203a951745a2",
|
"rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -138,11 +153,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1692274144,
|
"lastModified": 1700064067,
|
||||||
"narHash": "sha256-BxTQuRUANQ81u8DJznQyPmRsg63t4Yc+0kcyq6OLz8s=",
|
"narHash": "sha256-1ZWNDzhu8UlVCK7+DUN9dVQfiHX1bv6OQP9VxstY/gs=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "pre-commit-hooks.nix",
|
"repo": "pre-commit-hooks.nix",
|
||||||
"rev": "7e3517c03d46159fdbf8c0e5c97f82d5d4b0c8fa",
|
"rev": "e558068cba67b23b4fbc5537173dbb43748a17e8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -156,6 +171,7 @@
|
|||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts",
|
||||||
"libnbtplusplus": "libnbtplusplus",
|
"libnbtplusplus": "libnbtplusplus",
|
||||||
|
"nix-filter": "nix-filter",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"pre-commit-hooks": "pre-commit-hooks"
|
"pre-commit-hooks": "pre-commit-hooks"
|
||||||
}
|
}
|
||||||
|
25
flake.nix
25
flake.nix
@ -4,6 +4,7 @@
|
|||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
|
nix-filter.url = "github:numtide/nix-filter";
|
||||||
pre-commit-hooks = {
|
pre-commit-hooks = {
|
||||||
url = "github:cachix/pre-commit-hooks.nix";
|
url = "github:cachix/pre-commit-hooks.nix";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
@ -20,8 +21,24 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs:
|
outputs = {
|
||||||
inputs.flake-parts.lib.mkFlake
|
flake-parts,
|
||||||
{inherit inputs;}
|
pre-commit-hooks,
|
||||||
{imports = [./nix];};
|
...
|
||||||
|
} @ inputs:
|
||||||
|
flake-parts.lib.mkFlake {inherit inputs;} {
|
||||||
|
imports = [
|
||||||
|
pre-commit-hooks.flakeModule
|
||||||
|
|
||||||
|
./nix/dev.nix
|
||||||
|
./nix/distribution.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
systems = [
|
||||||
|
"x86_64-linux"
|
||||||
|
"aarch64-linux"
|
||||||
|
"x86_64-darwin"
|
||||||
|
"aarch64-darwin"
|
||||||
|
];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
id: org.prismlauncher.PrismLauncher
|
id: org.prismlauncher.PrismLauncher
|
||||||
runtime: org.kde.Platform
|
runtime: org.kde.Platform
|
||||||
runtime-version: "5.15-22.08"
|
runtime-version: "5.15-23.08"
|
||||||
sdk: org.kde.Sdk
|
sdk: org.kde.Sdk
|
||||||
sdk-extensions:
|
sdk-extensions:
|
||||||
- org.freedesktop.Sdk.Extension.openjdk17
|
- org.freedesktop.Sdk.Extension.openjdk17
|
||||||
@ -113,6 +113,9 @@ modules:
|
|||||||
version-query: .tag_name
|
version-query: .tag_name
|
||||||
url-query: .tarball_url
|
url-query: .tarball_url
|
||||||
timestamp-query: .published_at
|
timestamp-query: .published_at
|
||||||
|
# from https://github.com/flathub/net.gaijin.WarThunder/blob/7ea6f7a9f84b9c77150c003a7059dc03f8dcbc7f/gamemode.patch
|
||||||
|
- type: patch
|
||||||
|
path: patches/gamemode.patch
|
||||||
cleanup:
|
cleanup:
|
||||||
- /include
|
- /include
|
||||||
- /lib/pkgconfig
|
- /lib/pkgconfig
|
||||||
|
12
flatpak/patches/gamemode.patch
Normal file
12
flatpak/patches/gamemode.patch
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
diff -ruN a/common/common-pidfds.c b/common/common-pidfds.c
|
||||||
|
--- a/common/common-pidfds.c 2021-02-18 20:00:12.000000000 +0100
|
||||||
|
+++ b/common/common-pidfds.c 2023-09-07 08:57:42.954362763 +0200
|
||||||
|
@@ -58,6 +58,8 @@
|
||||||
|
{
|
||||||
|
return (int)syscall(__NR_pidfd_open, pid, flags);
|
||||||
|
}
|
||||||
|
+#else
|
||||||
|
+#include <sys/pidfd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* pidfd functions */
|
@ -1,5 +1,6 @@
|
|||||||
builds:
|
builds:
|
||||||
exclude: []
|
exclude:
|
||||||
|
- "*.x86_64-darwin.*"
|
||||||
include:
|
include:
|
||||||
- "checks.x86_64-linux.*"
|
- "checks.x86_64-linux.*"
|
||||||
- "devShells.*.*"
|
- "devShells.*.*"
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
#include "ui/pages/global/APIPage.h"
|
#include "ui/pages/global/APIPage.h"
|
||||||
#include "ui/pages/global/AccountListPage.h"
|
#include "ui/pages/global/AccountListPage.h"
|
||||||
#include "ui/pages/global/CustomCommandsPage.h"
|
#include "ui/pages/global/CustomCommandsPage.h"
|
||||||
|
#include "ui/pages/global/EnvironmentVariablesPage.h"
|
||||||
#include "ui/pages/global/ExternalToolsPage.h"
|
#include "ui/pages/global/ExternalToolsPage.h"
|
||||||
#include "ui/pages/global/JavaPage.h"
|
#include "ui/pages/global/JavaPage.h"
|
||||||
#include "ui/pages/global/LanguagePage.h"
|
#include "ui/pages/global/LanguagePage.h"
|
||||||
@ -122,6 +123,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 +132,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 +170,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 +330,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 +485,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();
|
||||||
@ -503,6 +543,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
m_settings->registerSetting("MenuBarInsteadOfToolBar", false);
|
m_settings->registerSetting("MenuBarInsteadOfToolBar", false);
|
||||||
|
|
||||||
|
m_settings->registerSetting("NumberOfConcurrentTasks", 10);
|
||||||
|
m_settings->registerSetting("NumberOfConcurrentDownloads", 6);
|
||||||
|
|
||||||
QString defaultMonospace;
|
QString defaultMonospace;
|
||||||
int defaultSize = 11;
|
int defaultSize = 11;
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
@ -579,6 +622,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", "");
|
||||||
@ -594,9 +640,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
m_settings->registerSetting("ShowGameTime", true);
|
m_settings->registerSetting("ShowGameTime", true);
|
||||||
m_settings->registerSetting("ShowGlobalGameTime", true);
|
m_settings->registerSetting("ShowGlobalGameTime", true);
|
||||||
m_settings->registerSetting("RecordGameTime", true);
|
m_settings->registerSetting("RecordGameTime", true);
|
||||||
|
m_settings->registerSetting("ShowGameTimeWithoutDays", false);
|
||||||
|
|
||||||
// Minecraft mods
|
// Minecraft mods
|
||||||
m_settings->registerSetting("ModMetadataDisabled", false);
|
m_settings->registerSetting("ModMetadataDisabled", false);
|
||||||
|
m_settings->registerSetting("ModDependenciesDisabled", false);
|
||||||
|
|
||||||
// Minecraft offline player name
|
// Minecraft offline player name
|
||||||
m_settings->registerSetting("LastOfflinePlayerName", "");
|
m_settings->registerSetting("LastOfflinePlayerName", "");
|
||||||
@ -673,6 +721,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
m_settings->registerSetting("CloseAfterLaunch", false);
|
m_settings->registerSetting("CloseAfterLaunch", false);
|
||||||
m_settings->registerSetting("QuitAfterGameStop", false);
|
m_settings->registerSetting("QuitAfterGameStop", false);
|
||||||
|
|
||||||
|
m_settings->registerSetting("Env", QVariant(QMap<QString, QVariant>()));
|
||||||
|
|
||||||
// Custom Microsoft Authentication Client ID
|
// Custom Microsoft Authentication Client ID
|
||||||
m_settings->registerSetting("MSAClientIDOverride", "");
|
m_settings->registerSetting("MSAClientIDOverride", "");
|
||||||
|
|
||||||
@ -698,6 +748,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
m_globalSettingsProvider->addPage<JavaPage>();
|
m_globalSettingsProvider->addPage<JavaPage>();
|
||||||
m_globalSettingsProvider->addPage<LanguagePage>();
|
m_globalSettingsProvider->addPage<LanguagePage>();
|
||||||
m_globalSettingsProvider->addPage<CustomCommandsPage>();
|
m_globalSettingsProvider->addPage<CustomCommandsPage>();
|
||||||
|
m_globalSettingsProvider->addPage<EnvironmentVariablesPage>();
|
||||||
m_globalSettingsProvider->addPage<ProxyPage>();
|
m_globalSettingsProvider->addPage<ProxyPage>();
|
||||||
m_globalSettingsProvider->addPage<ExternalToolsPage>();
|
m_globalSettingsProvider->addPage<ExternalToolsPage>();
|
||||||
m_globalSettingsProvider->addPage<AccountListPage>();
|
m_globalSettingsProvider->addPage<AccountListPage>();
|
||||||
@ -734,15 +785,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");
|
||||||
@ -845,6 +887,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;
|
||||||
}
|
}
|
||||||
@ -913,6 +1056,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
|
||||||
@ -981,6 +1144,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;
|
||||||
|
@ -388,7 +388,7 @@ QString BaseInstance::name() const
|
|||||||
|
|
||||||
QString BaseInstance::windowTitle() const
|
QString BaseInstance::windowTitle() const
|
||||||
{
|
{
|
||||||
return BuildConfig.LAUNCHER_DISPLAYNAME + ": " + name().replace(QRegularExpression("\\s+"), " ");
|
return BuildConfig.LAUNCHER_DISPLAYNAME + ": " + name();
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: why is this here? move it to MinecraftInstance!!!
|
// FIXME: why is this here? move it to MinecraftInstance!!!
|
||||||
|
@ -64,7 +64,7 @@ class LaunchTask;
|
|||||||
class BaseInstance;
|
class BaseInstance;
|
||||||
|
|
||||||
// pointer for lazy people
|
// pointer for lazy people
|
||||||
typedef std::shared_ptr<BaseInstance> InstancePtr;
|
using InstancePtr = std::shared_ptr<BaseInstance>;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Base class for instances.
|
* \brief Base class for instances.
|
||||||
|
@ -51,7 +51,7 @@ class BaseVersionList : public QAbstractListModel {
|
|||||||
ArchitectureRole,
|
ArchitectureRole,
|
||||||
SortRole
|
SortRole
|
||||||
};
|
};
|
||||||
typedef QList<int> RoleList;
|
using RoleList = QList<int>;
|
||||||
|
|
||||||
explicit BaseVersionList(QObject* parent = 0);
|
explicit BaseVersionList(QObject* parent = 0);
|
||||||
|
|
||||||
|
@ -139,6 +139,7 @@ set(NET_SOURCES
|
|||||||
net/HeaderProxy.h
|
net/HeaderProxy.h
|
||||||
net/RawHeaderProxy.h
|
net/RawHeaderProxy.h
|
||||||
net/ApiHeaderProxy.h
|
net/ApiHeaderProxy.h
|
||||||
|
net/StaticHeaderProxy.h
|
||||||
net/ApiDownload.h
|
net/ApiDownload.h
|
||||||
net/ApiDownload.cpp
|
net/ApiDownload.cpp
|
||||||
net/ApiUpload.cpp
|
net/ApiUpload.cpp
|
||||||
@ -181,6 +182,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 +222,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 +238,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 +248,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 +585,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 +738,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
|
||||||
@ -834,6 +889,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/pages/global/AccountListPage.h
|
ui/pages/global/AccountListPage.h
|
||||||
ui/pages/global/CustomCommandsPage.cpp
|
ui/pages/global/CustomCommandsPage.cpp
|
||||||
ui/pages/global/CustomCommandsPage.h
|
ui/pages/global/CustomCommandsPage.h
|
||||||
|
ui/pages/global/EnvironmentVariablesPage.cpp
|
||||||
|
ui/pages/global/EnvironmentVariablesPage.h
|
||||||
ui/pages/global/ExternalToolsPage.cpp
|
ui/pages/global/ExternalToolsPage.cpp
|
||||||
ui/pages/global/ExternalToolsPage.h
|
ui/pages/global/ExternalToolsPage.h
|
||||||
ui/pages/global/JavaPage.cpp
|
ui/pages/global/JavaPage.cpp
|
||||||
@ -916,6 +973,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 +1004,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
|
||||||
@ -984,6 +1042,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/widgets/Common.h
|
ui/widgets/Common.h
|
||||||
ui/widgets/CustomCommands.cpp
|
ui/widgets/CustomCommands.cpp
|
||||||
ui/widgets/CustomCommands.h
|
ui/widgets/CustomCommands.h
|
||||||
|
ui/widgets/EnvironmentVariables.cpp
|
||||||
|
ui/widgets/EnvironmentVariables.h
|
||||||
ui/widgets/DropLabel.cpp
|
ui/widgets/DropLabel.cpp
|
||||||
ui/widgets/DropLabel.h
|
ui/widgets/DropLabel.h
|
||||||
ui/widgets/FocusLineEdit.cpp
|
ui/widgets/FocusLineEdit.cpp
|
||||||
@ -1042,6 +1102,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,10 +1149,12 @@ 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
|
||||||
ui/widgets/CustomCommands.ui
|
ui/widgets/CustomCommands.ui
|
||||||
|
ui/widgets/EnvironmentVariables.ui
|
||||||
ui/widgets/InfoFrame.ui
|
ui/widgets/InfoFrame.ui
|
||||||
ui/widgets/ModFilterWidget.ui
|
ui/widgets/ModFilterWidget.ui
|
||||||
ui/widgets/SubTaskProgressBar.ui
|
ui/widgets/SubTaskProgressBar.ui
|
||||||
@ -1104,7 +1175,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 +1182,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 +1206,12 @@ qt_add_resources(LAUNCHER_RESOURCES
|
|||||||
../${Launcher_Branding_LogoQRC}
|
../${Launcher_Branding_LogoQRC}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
qt_wrap_ui(PRISMUPDATER_UI
|
||||||
|
updater/prismupdater/SelectReleaseDialog.ui
|
||||||
|
ui/widgets/SubTaskProgressBar.ui
|
||||||
|
ui/dialogs/ProgressDialog.ui
|
||||||
|
)
|
||||||
|
|
||||||
######## Windows resource files ########
|
######## Windows resource files ########
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
|
set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
|
||||||
@ -1137,12 +1221,16 @@ include(CompilerWarnings)
|
|||||||
|
|
||||||
# Add executable
|
# Add executable
|
||||||
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
|
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_TEST)
|
||||||
|
endif()
|
||||||
set_project_warnings(Launcher_logic
|
set_project_warnings(Launcher_logic
|
||||||
"${Launcher_MSVC_WARNINGS}"
|
"${Launcher_MSVC_WARNINGS}"
|
||||||
"${Launcher_CLANG_WARNINGS}"
|
"${Launcher_CLANG_WARNINGS}"
|
||||||
"${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
|
||||||
@ -1224,7 +1312,45 @@ install(TARGETS ${Launcher_Name}
|
|||||||
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
|
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WIN32)
|
if(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}"
|
||||||
@ -1243,7 +1369,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)
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
class DataMigrationTask : public Task {
|
class DataMigrationTask : public Task {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathmatcher);
|
explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
|
||||||
~DataMigrationTask() override = default;
|
~DataMigrationTask() override = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -108,12 +108,9 @@ bool openDirectory(const QString& path, [[maybe_unused]] bool ensureExists)
|
|||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||||
if (!isSandbox()) {
|
if (!isSandbox()) {
|
||||||
return IndirectOpen(f);
|
return IndirectOpen(f);
|
||||||
} else {
|
|
||||||
return f();
|
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
return f();
|
|
||||||
#endif
|
#endif
|
||||||
|
return f();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool openFile(const QString& path)
|
bool openFile(const QString& path)
|
||||||
|
@ -267,10 +267,7 @@ bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceP
|
|||||||
|
|
||||||
bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
|
bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
|
||||||
{
|
{
|
||||||
auto fileName = fileInfo.fileName();
|
return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()));
|
||||||
auto path = relPath(fileInfo.absoluteFilePath());
|
|
||||||
return std::any_of(m_ignoreFiles.cbegin(), m_ignoreFiles.cend(), [fileName](auto iFileName) { return fileName == iFileName; }) ||
|
|
||||||
m_ignoreFilePaths.covers(path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileIgnoreProxy::filterFile(const QString& fileName) const
|
bool FileIgnoreProxy::filterFile(const QString& fileName) const
|
||||||
|
@ -123,26 +123,35 @@ namespace fs = ghc::filesystem;
|
|||||||
|
|
||||||
#if defined(__MINGW32__)
|
#if defined(__MINGW32__)
|
||||||
|
|
||||||
typedef struct _DUPLICATE_EXTENTS_DATA {
|
struct _DUPLICATE_EXTENTS_DATA {
|
||||||
HANDLE FileHandle;
|
HANDLE FileHandle;
|
||||||
LARGE_INTEGER SourceFileOffset;
|
LARGE_INTEGER SourceFileOffset;
|
||||||
LARGE_INTEGER TargetFileOffset;
|
LARGE_INTEGER TargetFileOffset;
|
||||||
LARGE_INTEGER ByteCount;
|
LARGE_INTEGER ByteCount;
|
||||||
} DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA;
|
};
|
||||||
|
|
||||||
typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
|
using DUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA;
|
||||||
|
using PDUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA*;
|
||||||
|
|
||||||
|
struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
|
||||||
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
|
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
|
||||||
WORD Reserved; // Must be 0
|
WORD Reserved; // Must be 0
|
||||||
DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
|
DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
|
||||||
DWORD ChecksumChunkSizeInBytes;
|
DWORD ChecksumChunkSizeInBytes;
|
||||||
DWORD ClusterSizeInBytes;
|
DWORD ClusterSizeInBytes;
|
||||||
} FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER;
|
};
|
||||||
|
|
||||||
typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER {
|
using FSCTL_GET_INTEGRITY_INFORMATION_BUFFER = _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER;
|
||||||
|
using PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER = _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER*;
|
||||||
|
|
||||||
|
struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER {
|
||||||
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
|
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
|
||||||
WORD Reserved; // Must be 0
|
WORD Reserved; // Must be 0
|
||||||
DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
|
DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
|
||||||
} FSCTL_SET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER;
|
};
|
||||||
|
|
||||||
|
using FSCTL_SET_INTEGRITY_INFORMATION_BUFFER = _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER;
|
||||||
|
using PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER = _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER*;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -194,6 +203,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 +281,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 +330,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 +341,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) {
|
||||||
@ -872,6 +943,8 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
|
|||||||
<< "\n";
|
<< "\n";
|
||||||
stream << "Type=Application"
|
stream << "Type=Application"
|
||||||
<< "\n";
|
<< "\n";
|
||||||
|
stream << "Categories=Game;ActionGame;AdventureGame;Simulation"
|
||||||
|
<< "\n";
|
||||||
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
|
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
|
||||||
stream << "Name=" << name.toLocal8Bit() << "\n";
|
stream << "Name=" << name.toLocal8Bit() << "\n";
|
||||||
if (!icon.isEmpty()) {
|
if (!icon.isEmpty()) {
|
||||||
|
@ -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;
|
||||||
|
@ -59,7 +59,7 @@
|
|||||||
|
|
||||||
#include <quazip/quazipdir.h>
|
#include <quazip/quazipdir.h>
|
||||||
|
|
||||||
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent, QMap<QString, QString>&& extra_info)
|
InstanceImportTask::InstanceImportTask(const QUrl& sourceUrl, QWidget* parent, QMap<QString, QString>&& extra_info)
|
||||||
: m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent)
|
: m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ class FileResolvingTask;
|
|||||||
class InstanceImportTask : public InstanceTask {
|
class InstanceImportTask : public InstanceTask {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
|
explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
|
||||||
|
|
||||||
bool abort() override;
|
bool abort() override;
|
||||||
const QVector<Flame::File>& getBlockedFiles() const { return m_blockedMods; }
|
const QVector<Flame::File>& getBlockedFiles() const { return m_blockedMods; }
|
||||||
|
@ -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.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -768,6 +823,9 @@ void InstanceList::on_InstFolderChanged([[maybe_unused]] const Setting& setting,
|
|||||||
}
|
}
|
||||||
m_instDir = newInstDir;
|
m_instDir = newInstDir;
|
||||||
m_groupsLoaded = false;
|
m_groupsLoaded = false;
|
||||||
|
beginRemoveRows(QModelIndex(), 0, count());
|
||||||
|
m_instances.erase(m_instances.begin(), m_instances.end());
|
||||||
|
endRemoveRows();
|
||||||
emit instancesChanged();
|
emit instancesChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -892,9 +950,12 @@ QString InstanceList::getStagedInstancePath()
|
|||||||
|
|
||||||
bool InstanceList::commitStagedInstance(const QString& path,
|
bool InstanceList::commitStagedInstance(const QString& path,
|
||||||
InstanceName const& instanceName,
|
InstanceName const& instanceName,
|
||||||
const QString& groupName,
|
QString groupName,
|
||||||
InstanceTask const& commiting)
|
InstanceTask const& commiting)
|
||||||
{
|
{
|
||||||
|
if (groupName.isEmpty() && !groupName.isNull())
|
||||||
|
groupName = QString();
|
||||||
|
|
||||||
QDir dir;
|
QDir dir;
|
||||||
QString instID;
|
QString instID;
|
||||||
InstancePtr inst;
|
InstancePtr inst;
|
||||||
@ -925,7 +986,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,4 +1,24 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -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();
|
||||||
@ -109,7 +130,7 @@ class InstanceList : public QAbstractListModel {
|
|||||||
* should_override is used when another similar instance already exists, and we want to override it
|
* should_override is used when another similar instance already exists, and we want to override it
|
||||||
* - for instance, when updating it.
|
* - for instance, when updating it.
|
||||||
*/
|
*/
|
||||||
bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, const QString& groupName, const InstanceTask&);
|
bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, QString groupName, const InstanceTask&);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy a previously created staging area given by @keyPath - used when creation fails.
|
* Destroy a previously created staging area given by @keyPath - used when creation fails.
|
||||||
@ -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;
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
#include "LaunchController.h"
|
#include "LaunchController.h"
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "minecraft/auth/AccountData.h"
|
||||||
#include "minecraft/auth/AccountList.h"
|
#include "minecraft/auth/AccountList.h"
|
||||||
|
|
||||||
#include "ui/InstanceWindow.h"
|
#include "ui/InstanceWindow.h"
|
||||||
@ -88,8 +89,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();
|
||||||
@ -106,7 +107,7 @@ void LaunchController::decideAccount()
|
|||||||
// Select the account to use. If the instance has a specific account set, that will be used. Otherwise, the default account will be used
|
// Select the account to use. If the instance has a specific account set, that will be used. Otherwise, the default account will be used
|
||||||
auto instanceAccountId = m_instance->settings()->get("InstanceAccountId").toString();
|
auto instanceAccountId = m_instance->settings()->get("InstanceAccountId").toString();
|
||||||
auto instanceAccountIndex = accounts->findAccountByProfileId(instanceAccountId);
|
auto instanceAccountIndex = accounts->findAccountByProfileId(instanceAccountId);
|
||||||
if (instanceAccountIndex == -1) {
|
if (instanceAccountIndex == -1 || instanceAccountId.isEmpty()) {
|
||||||
m_accountToUse = accounts->defaultAccount();
|
m_accountToUse = accounts->defaultAccount();
|
||||||
} else {
|
} else {
|
||||||
m_accountToUse = accounts->at(instanceAccountIndex);
|
m_accountToUse = accounts->at(instanceAccountIndex);
|
||||||
@ -161,7 +162,7 @@ void LaunchController::login()
|
|||||||
m_accountToUse->fillSession(m_session);
|
m_accountToUse->fillSession(m_session);
|
||||||
|
|
||||||
// Launch immediately in true offline mode
|
// Launch immediately in true offline mode
|
||||||
if (m_accountToUse->isOffline()) {
|
if (m_accountToUse->accountType() == AccountType::Offline) {
|
||||||
launchInstance();
|
launchInstance();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -16,19 +16,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <MMCTime.h>
|
#include <MMCTime.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
||||||
QString Time::prettifyDuration(int64_t duration)
|
QString Time::prettifyDuration(int64_t duration, bool noDays)
|
||||||
{
|
{
|
||||||
int seconds = (int)(duration % 60);
|
int seconds = (int)(duration % 60);
|
||||||
duration /= 60;
|
duration /= 60;
|
||||||
int minutes = (int)(duration % 60);
|
int minutes = (int)(duration % 60);
|
||||||
duration /= 60;
|
duration /= 60;
|
||||||
int hours = (int)(duration % 24);
|
int hours = (int)(noDays ? duration : (duration % 24));
|
||||||
int days = (int)(duration / 24);
|
int days = (int)(noDays ? 0 : (duration / 24));
|
||||||
if ((hours == 0) && (days == 0)) {
|
if ((hours == 0) && (days == 0)) {
|
||||||
return QObject::tr("%1min %2s").arg(minutes).arg(seconds);
|
return QObject::tr("%1min %2s").arg(minutes).arg(seconds);
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
namespace Time {
|
namespace Time {
|
||||||
|
|
||||||
QString prettifyDuration(int64_t duration);
|
QString prettifyDuration(int64_t duration, bool noDays = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`.
|
* @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`.
|
||||||
|
@ -42,11 +42,15 @@
|
|||||||
|
|
||||||
#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
|
||||||
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction filter)
|
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction& filter)
|
||||||
{
|
{
|
||||||
QuaZip modZip(from.filePath());
|
QuaZip modZip(from.filePath());
|
||||||
modZip.open(QuaZip::mdUnzip);
|
modZip.open(QuaZip::mdUnzip);
|
||||||
@ -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 {
|
||||||
@ -57,7 +60,7 @@ using FilterFunction = std::function<bool(const QString&)>;
|
|||||||
/**
|
/**
|
||||||
* Merge two zip files, using a filter function
|
* Merge two zip files, using a filter function
|
||||||
*/
|
*/
|
||||||
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction filter = nullptr);
|
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction& filter = nullptr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compress directory, by providing a list of files to compress
|
* Compress directory, by providing a list of files to compress
|
||||||
@ -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)
|
||||||
@ -167,7 +172,7 @@ class ExportToZipTask : public Task {
|
|||||||
void setExcludeFiles(QStringList excludeFiles) { m_exclude_files = excludeFiles; }
|
void setExcludeFiles(QStringList excludeFiles) { m_exclude_files = excludeFiles; }
|
||||||
void addExtraFile(QString fileName, QByteArray data) { m_extra_files.insert(fileName, data); }
|
void addExtraFile(QString fileName, QByteArray data) { m_extra_files.insert(fileName, data); }
|
||||||
|
|
||||||
typedef std::optional<QString> ZipResult;
|
using ZipResult = std::optional<QString>;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void executeTask() override;
|
virtual void executeTask() override;
|
||||||
@ -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
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <QPixmapCache>
|
#include <QPixmapCache>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QTime>
|
#include <QTime>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
#define GET_TYPE() \
|
#define GET_TYPE() \
|
||||||
Qt::ConnectionType type; \
|
Qt::ConnectionType type; \
|
||||||
@ -100,10 +101,14 @@ class PixmapCache final : public QObject {
|
|||||||
*/
|
*/
|
||||||
bool _markCacheMissByEviciton()
|
bool _markCacheMissByEviciton()
|
||||||
{
|
{
|
||||||
|
static constexpr uint maxInt = static_cast<uint>(std::numeric_limits<int>::max());
|
||||||
|
static constexpr uint step = 10240;
|
||||||
|
static constexpr int oneSecond = 1000;
|
||||||
|
|
||||||
auto now = QTime::currentTime();
|
auto now = QTime::currentTime();
|
||||||
if (!m_last_cache_miss_by_eviciton.isNull()) {
|
if (!m_last_cache_miss_by_eviciton.isNull()) {
|
||||||
auto diff = m_last_cache_miss_by_eviciton.msecsTo(now);
|
auto diff = m_last_cache_miss_by_eviciton.msecsTo(now);
|
||||||
if (diff < 1000) { // less than a second ago
|
if (diff < oneSecond) { // less than a second ago
|
||||||
++m_consecutive_fast_evicitons;
|
++m_consecutive_fast_evicitons;
|
||||||
} else {
|
} else {
|
||||||
m_consecutive_fast_evicitons = 0;
|
m_consecutive_fast_evicitons = 0;
|
||||||
@ -111,11 +116,17 @@ class PixmapCache final : public QObject {
|
|||||||
}
|
}
|
||||||
m_last_cache_miss_by_eviciton = now;
|
m_last_cache_miss_by_eviciton = now;
|
||||||
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
|
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
|
||||||
// double the cache size
|
// increase the cache size
|
||||||
auto newSize = _cacheLimit() * 2;
|
uint newSize = _cacheLimit() + step;
|
||||||
qDebug() << m_consecutive_fast_evicitons << "pixmap cache misses by eviction happened too fast, doubling cache size to"
|
if (newSize >= maxInt) { // increase it until you overflow :D
|
||||||
<< newSize;
|
newSize = maxInt;
|
||||||
_setCacheLimit(newSize);
|
qDebug() << m_consecutive_fast_evicitons
|
||||||
|
<< tr("pixmap cache misses by eviction happened too fast, doing nothing as the cache size reached it's limit");
|
||||||
|
} else {
|
||||||
|
qDebug() << m_consecutive_fast_evicitons
|
||||||
|
<< tr("pixmap cache misses by eviction happened too fast, increasing cache size to") << static_cast<int>(newSize);
|
||||||
|
}
|
||||||
|
_setCacheLimit(static_cast<int>(newSize));
|
||||||
m_consecutive_fast_evicitons = 0;
|
m_consecutive_fast_evicitons = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class RecursiveFileSystemWatcher : public QObject {
|
|||||||
QDir rootDir() const { return m_root; }
|
QDir rootDir() const { return m_root; }
|
||||||
|
|
||||||
// WARNING: setting this to true may be bad for performance
|
// WARNING: setting this to true may be bad for performance
|
||||||
void setWatchFiles(const bool watchFiles);
|
void setWatchFiles(bool watchFiles);
|
||||||
bool watchFiles() const { return m_watchFiles; }
|
bool watchFiles() const { return m_watchFiles; }
|
||||||
|
|
||||||
void setMatcher(IPathMatcher::Ptr matcher) { m_matcher = matcher; }
|
void setMatcher(IPathMatcher::Ptr matcher) { m_matcher = matcher; }
|
||||||
|
@ -32,7 +32,7 @@ class ResourceDownloadTask : public SequentialTask {
|
|||||||
public:
|
public:
|
||||||
explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
||||||
ModPlatform::IndexedVersion version,
|
ModPlatform::IndexedVersion version,
|
||||||
const std::shared_ptr<ResourceFolderModel> packs,
|
std::shared_ptr<ResourceFolderModel> packs,
|
||||||
bool is_indexed = true,
|
bool is_indexed = true,
|
||||||
QString custom_target_folder = {});
|
QString custom_target_folder = {});
|
||||||
const QString& getFilename() const { return m_pack_version.fileName; }
|
const QString& getFilename() const { return m_pack_version.fileName; }
|
||||||
|
@ -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);
|
||||||
|
|
||||||
@ -103,14 +104,8 @@ class Version {
|
|||||||
|
|
||||||
QString m_fullString;
|
QString m_fullString;
|
||||||
|
|
||||||
[[nodiscard]] inline bool isAppendix() const
|
[[nodiscard]] inline bool isAppendix() const { return m_stringPart.startsWith('+'); }
|
||||||
{
|
[[nodiscard]] inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; }
|
||||||
return m_stringPart.startsWith('+');
|
|
||||||
}
|
|
||||||
[[nodiscard]] inline bool isPreRelease() const
|
|
||||||
{
|
|
||||||
return m_stringPart.startsWith('-') && m_stringPart.length() > 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool operator==(const Section& other) const
|
inline bool operator==(const Section& other) const
|
||||||
{
|
{
|
||||||
@ -156,14 +151,8 @@ class Version {
|
|||||||
return m_fullString < other.m_fullString;
|
return m_fullString < other.m_fullString;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool operator!=(const Section& other) const
|
inline bool operator!=(const Section& other) const { return !(*this == other); }
|
||||||
{
|
inline bool operator>(const Section& other) const { return !(*this < other || *this == other); }
|
||||||
return !(*this == other);
|
|
||||||
}
|
|
||||||
inline bool operator>(const Section& other) const
|
|
||||||
{
|
|
||||||
return !(*this < other || *this == other);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -10,7 +10,7 @@ class VersionProxyModel : public QAbstractProxyModel {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum Column { Name, ParentVersion, Branch, Type, Architecture, Path, Time };
|
enum Column { Name, ParentVersion, Branch, Type, Architecture, Path, Time };
|
||||||
typedef QHash<BaseVersionList::ModelRoles, std::shared_ptr<Filter>> FilterMap;
|
using FilterMap = QHash<BaseVersionList::ModelRoles, std::shared_ptr<Filter>>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VersionProxyModel(QObject* parent = 0);
|
VersionProxyModel(QObject* parent = 0);
|
||||||
@ -28,7 +28,7 @@ class VersionProxyModel : public QAbstractProxyModel {
|
|||||||
|
|
||||||
const FilterMap& filters() const;
|
const FilterMap& filters() const;
|
||||||
const QString& search() const;
|
const QString& search() const;
|
||||||
void setFilter(const BaseVersionList::ModelRoles column, Filter* filter);
|
void setFilter(BaseVersionList::ModelRoles column, Filter* filter);
|
||||||
void setSearch(const QString& search);
|
void setSearch(const QString& search);
|
||||||
void clearFilters();
|
void clearFilters();
|
||||||
QModelIndex getRecommended() const;
|
QModelIndex getRecommended() const;
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
switch (ldh.status()) {
|
||||||
|
case FileLinkApp::Starting:
|
||||||
|
case FileLinkApp::Initialized: {
|
||||||
return ldh.exec();
|
return ldh.exec();
|
||||||
|
}
|
||||||
|
case FileLinkApp::Failed:
|
||||||
|
return 1;
|
||||||
|
case FileLinkApp::Succeeded:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
@ -67,7 +67,7 @@ class IconList : public QAbstractListModel {
|
|||||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
|
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||||
|
|
||||||
bool addThemeIcon(const QString& key);
|
bool addThemeIcon(const QString& key);
|
||||||
bool addIcon(const QString& key, const QString& name, const QString& path, const IconType type);
|
bool addIcon(const QString& key, const QString& name, const QString& path, IconType type);
|
||||||
void saveIcon(const QString& key, const QString& path, const char* format) const;
|
void saveIcon(const QString& key, const QString& path, const char* format) const;
|
||||||
bool deleteIcon(const QString& key);
|
bool deleteIcon(const QString& key);
|
||||||
bool trashIcon(const QString& key);
|
bool trashIcon(const QString& key);
|
||||||
|
@ -22,8 +22,8 @@ struct JavaCheckResult {
|
|||||||
enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
|
enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef shared_qobject_ptr<QProcess> QProcessPtr;
|
using QProcessPtr = shared_qobject_ptr<QProcess>;
|
||||||
typedef shared_qobject_ptr<JavaChecker> JavaCheckerPtr;
|
using JavaCheckerPtr = shared_qobject_ptr<JavaChecker>;
|
||||||
class JavaChecker : public QObject {
|
class JavaChecker : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
class JavaCheckerJob;
|
class JavaCheckerJob;
|
||||||
typedef shared_qobject_ptr<JavaCheckerJob> JavaCheckerJobPtr;
|
using JavaCheckerJobPtr = shared_qobject_ptr<JavaCheckerJob>;
|
||||||
|
|
||||||
// FIXME: this just seems horribly redundant
|
// FIXME: this just seems horribly redundant
|
||||||
class JavaCheckerJob : public Task {
|
class JavaCheckerJob : public Task {
|
||||||
|
@ -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;
|
||||||
@ -42,4 +42,4 @@ struct JavaInstall : public BaseVersion {
|
|||||||
bool recommended = false;
|
bool recommended = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::shared_ptr<JavaInstall> JavaInstallPtr;
|
using JavaInstallPtr = std::shared_ptr<JavaInstall>;
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
@ -335,6 +336,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
candidates.append(getMinecraftJavaBundle());
|
||||||
candidates = addJavasFromEnv(candidates);
|
candidates = addJavasFromEnv(candidates);
|
||||||
candidates.removeDuplicates();
|
candidates.removeDuplicates();
|
||||||
return candidates;
|
return candidates;
|
||||||
@ -360,6 +362,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
||||||
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
|
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
|
||||||
}
|
}
|
||||||
|
javas.append(getMinecraftJavaBundle());
|
||||||
javas = addJavasFromEnv(javas);
|
javas = addJavasFromEnv(javas);
|
||||||
javas.removeDuplicates();
|
javas.removeDuplicates();
|
||||||
return javas;
|
return javas;
|
||||||
@ -403,6 +406,15 @@ 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.append(getMinecraftJavaBundle());
|
||||||
javas = addJavasFromEnv(javas);
|
javas = addJavasFromEnv(javas);
|
||||||
javas.removeDuplicates();
|
javas.removeDuplicates();
|
||||||
return javas;
|
return javas;
|
||||||
@ -415,6 +427,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
QList<QString> javas;
|
QList<QString> javas;
|
||||||
javas.append(this->GetDefaultJava()->path);
|
javas.append(this->GetDefaultJava()->path);
|
||||||
|
|
||||||
|
javas.append(getMinecraftJavaBundle());
|
||||||
return addJavasFromEnv(javas);
|
return addJavasFromEnv(javas);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -423,3 +436,50 @@ QString JavaUtils::getJavaCheckPath()
|
|||||||
{
|
{
|
||||||
return APPLICATION->getJarPath("JavaCheck.jar");
|
return APPLICATION->getJarPath("JavaCheck.jar");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList getMinecraftJavaBundle()
|
||||||
|
{
|
||||||
|
QString partialPath;
|
||||||
|
QString executable = "java";
|
||||||
|
QStringList processpaths;
|
||||||
|
#if defined(Q_OS_OSX)
|
||||||
|
partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support");
|
||||||
|
#elif defined(Q_OS_WIN32)
|
||||||
|
partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", "");
|
||||||
|
executable += "w.exe";
|
||||||
|
|
||||||
|
// add the microsoft store version of the launcher to the search. the current path is:
|
||||||
|
// C:\Users\USERNAME\AppData\Local\Packages\Microsoft.4297127D64EC6_8wekyb3d8bbwe\LocalCache\Local\runtime
|
||||||
|
auto minecraftMSStorePath =
|
||||||
|
FS::PathCombine(QFileInfo(partialPath).absolutePath(), "Local", "Packages", "Microsoft.4297127D64EC6_8wekyb3d8bbwe");
|
||||||
|
minecraftMSStorePath = FS::PathCombine(minecraftMSStorePath, "LocalCache", "Local", "runtime");
|
||||||
|
processpaths << minecraftMSStorePath;
|
||||||
|
#else
|
||||||
|
partialPath = QDir::homePath();
|
||||||
|
#endif
|
||||||
|
auto minecraftDataPath = FS::PathCombine(partialPath, ".minecraft", "runtime");
|
||||||
|
processpaths << minecraftDataPath;
|
||||||
|
|
||||||
|
QStringList javas;
|
||||||
|
while (!processpaths.isEmpty()) {
|
||||||
|
auto dirPath = processpaths.takeFirst();
|
||||||
|
QDir dir(dirPath);
|
||||||
|
if (!dir.exists())
|
||||||
|
continue;
|
||||||
|
auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
auto binFound = false;
|
||||||
|
for (auto& entry : entries) {
|
||||||
|
if (entry.baseName() == "bin") {
|
||||||
|
javas.append(FS::PathCombine(entry.canonicalFilePath(), executable));
|
||||||
|
binFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!binFound) {
|
||||||
|
for (auto& entry : entries) {
|
||||||
|
processpaths << entry.canonicalFilePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return javas;
|
||||||
|
}
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
QString stripVariableEntries(QString name, QString target, QString remove);
|
QString stripVariableEntries(QString name, QString target, QString remove);
|
||||||
QProcessEnvironment CleanEnviroment();
|
QProcessEnvironment CleanEnviroment();
|
||||||
|
QStringList getMinecraftJavaBundle();
|
||||||
|
|
||||||
class JavaUtils : public QObject {
|
class JavaUtils : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -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; }
|
||||||
|
@ -55,6 +55,6 @@ class Index : public QAbstractListModel, public BaseEntity {
|
|||||||
QVector<VersionList::Ptr> m_lists;
|
QVector<VersionList::Ptr> m_lists;
|
||||||
QHash<QString, VersionList::Ptr> m_uids;
|
QHash<QString, VersionList::Ptr> m_uids;
|
||||||
|
|
||||||
void connectVersionList(const int row, const VersionList::Ptr& list);
|
void connectVersionList(int row, const VersionList::Ptr& list);
|
||||||
};
|
};
|
||||||
} // namespace Meta
|
} // namespace Meta
|
||||||
|
@ -64,7 +64,7 @@ class Version : public QObject, public BaseVersion, public BaseEntity {
|
|||||||
|
|
||||||
public: // for usage by format parsers only
|
public: // for usage by format parsers only
|
||||||
void setType(const QString& type);
|
void setType(const QString& type);
|
||||||
void setTime(const qint64 time);
|
void setTime(qint64 time);
|
||||||
void setRequires(const Meta::RequireSet& reqs, const Meta::RequireSet& conflicts);
|
void setRequires(const Meta::RequireSet& reqs, const Meta::RequireSet& conflicts);
|
||||||
void setVolatile(bool volatile_);
|
void setVolatile(bool volatile_);
|
||||||
void setRecommended(bool recommended);
|
void setRecommended(bool recommended);
|
||||||
|
@ -79,7 +79,7 @@ class VersionList : public BaseVersionList, public BaseEntity {
|
|||||||
|
|
||||||
Version::Ptr m_recommended;
|
Version::Ptr m_recommended;
|
||||||
|
|
||||||
void setupAddedVersion(const int row, const Version::Ptr& version);
|
void setupAddedVersion(int row, const Version::Ptr& version);
|
||||||
};
|
};
|
||||||
} // namespace Meta
|
} // namespace Meta
|
||||||
Q_DECLARE_METATYPE(Meta::VersionList::Ptr)
|
Q_DECLARE_METATYPE(Meta::VersionList::Ptr)
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
class Agent;
|
class Agent;
|
||||||
|
|
||||||
typedef std::shared_ptr<Agent> AgentPtr;
|
using AgentPtr = std::shared_ptr<Agent>;
|
||||||
|
|
||||||
class Agent {
|
class Agent {
|
||||||
public:
|
public:
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "net/ApiDownload.h"
|
#include "net/ApiDownload.h"
|
||||||
#include "net/ChecksumValidator.h"
|
#include "net/ChecksumValidator.h"
|
||||||
|
#include "net/Download.h"
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
|
@ -105,4 +105,4 @@ class Component : public QObject, public ProblemProvider {
|
|||||||
bool m_loaded = false;
|
bool m_loaded = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef shared_qobject_ptr<Component> ComponentPtr;
|
using ComponentPtr = shared_qobject_ptr<Component>;
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
#include "Component.h"
|
#include "Component.h"
|
||||||
#include "ComponentUpdateTask_p.h"
|
#include "ComponentUpdateTask_p.h"
|
||||||
#include "OneSixVersionFormat.h"
|
|
||||||
#include "PackProfile.h"
|
#include "PackProfile.h"
|
||||||
#include "PackProfile_p.h"
|
#include "PackProfile_p.h"
|
||||||
#include "Version.h"
|
#include "Version.h"
|
||||||
#include "cassert"
|
#include "cassert"
|
||||||
#include "meta/Index.h"
|
#include "meta/Index.h"
|
||||||
#include "meta/Version.h"
|
#include "meta/Version.h"
|
||||||
#include "meta/VersionList.h"
|
#include "minecraft/OneSixVersionFormat.h"
|
||||||
|
#include "minecraft/ProfileUtils.h"
|
||||||
#include "net/Mode.h"
|
#include "net/Mode.h"
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
class Library;
|
class Library;
|
||||||
class MinecraftInstance;
|
class MinecraftInstance;
|
||||||
|
|
||||||
typedef std::shared_ptr<Library> LibraryPtr;
|
using LibraryPtr = std::shared_ptr<Library>;
|
||||||
|
|
||||||
class Library {
|
class Library {
|
||||||
friend class OneSixVersionFormat;
|
friend class OneSixVersionFormat;
|
||||||
|
@ -184,6 +184,13 @@ 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);
|
||||||
|
|
||||||
|
auto envSetting = m_settings->registerSetting("OverrideEnv", false);
|
||||||
|
m_settings->registerOverride(global_settings->getSetting("Env"), envSetting);
|
||||||
|
|
||||||
m_settings->set("InstanceType", "OneSix");
|
m_settings->set("InstanceType", "OneSix");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,6 +202,12 @@ void MinecraftInstance::loadSpecificSettings()
|
|||||||
m_settings->registerSetting("UseAccountForInstance", false);
|
m_settings->registerSetting("UseAccountForInstance", false);
|
||||||
m_settings->registerSetting("InstanceAccountId", "");
|
m_settings->registerSetting("InstanceAccountId", "");
|
||||||
|
|
||||||
|
m_settings->registerSetting("ExportName", "");
|
||||||
|
m_settings->registerSetting("ExportVersion", "1.0.0");
|
||||||
|
m_settings->registerSetting("ExportSummary", "");
|
||||||
|
m_settings->registerSetting("ExportAuthor", "");
|
||||||
|
m_settings->registerSetting("ExportOptionalFiles", true);
|
||||||
|
|
||||||
qDebug() << "Instance-type specific settings were loaded!";
|
qDebug() << "Instance-type specific settings were loaded!";
|
||||||
|
|
||||||
setSpecificSettingsLoaded(true);
|
setSpecificSettingsLoaded(true);
|
||||||
@ -305,7 +318,7 @@ QString MinecraftInstance::getLocalLibraryPath() const
|
|||||||
bool MinecraftInstance::supportsDemo() const
|
bool MinecraftInstance::supportsDemo() const
|
||||||
{
|
{
|
||||||
Version instance_ver{ getPackProfile()->getComponentVersion("net.minecraft") };
|
Version instance_ver{ getPackProfile()->getComponentVersion("net.minecraft") };
|
||||||
// Demo mode was introduced in 1.3.1: https://minecraft.fandom.com/wiki/Demo_mode#History
|
// Demo mode was introduced in 1.3.1: https://minecraft.wiki/w/Demo_mode#History
|
||||||
// FIXME: Due to Version constraints atm, this can't handle well non-release versions
|
// FIXME: Due to Version constraints atm, this can't handle well non-release versions
|
||||||
return instance_ver >= Version("1.3.1");
|
return instance_ver >= Version("1.3.1");
|
||||||
}
|
}
|
||||||
@ -507,20 +520,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;
|
||||||
@ -530,6 +551,7 @@ QMap<QString, QString> MinecraftInstance::getVariables()
|
|||||||
out.insert("INST_MC_DIR", QDir::toNativeSeparators(QDir(gameRoot()).absolutePath()));
|
out.insert("INST_MC_DIR", QDir::toNativeSeparators(QDir(gameRoot()).absolutePath()));
|
||||||
out.insert("INST_JAVA", settings()->get("JavaPath").toString());
|
out.insert("INST_JAVA", settings()->get("JavaPath").toString());
|
||||||
out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
|
out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
|
||||||
|
out.insert("NO_COLOR", "1");
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -543,6 +565,22 @@ QProcessEnvironment MinecraftInstance::createEnvironment()
|
|||||||
for (auto it = variables.begin(); it != variables.end(); ++it) {
|
for (auto it = variables.begin(); it != variables.end(); ++it) {
|
||||||
env.insert(it.key(), it.value());
|
env.insert(it.key(), it.value());
|
||||||
}
|
}
|
||||||
|
// custom env
|
||||||
|
|
||||||
|
auto insertEnv = [&env](QMap<QString, QVariant> envMap) {
|
||||||
|
if (envMap.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (auto iter = envMap.begin(); iter != envMap.end(); iter++)
|
||||||
|
env.insert(iter.key(), iter.value().toString());
|
||||||
|
};
|
||||||
|
|
||||||
|
bool overrideEnv = settings()->get("OverrideEnv").toBool();
|
||||||
|
|
||||||
|
if (!overrideEnv)
|
||||||
|
insertEnv(APPLICATION->settings()->get("Env").toMap());
|
||||||
|
else
|
||||||
|
insertEnv(settings()->get("Env").toMap());
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,16 +591,27 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
|||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud) {
|
if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud) {
|
||||||
auto preloadList = env.value("LD_PRELOAD").split(QLatin1String(":"));
|
QStringList preloadList;
|
||||||
auto libPaths = env.value("LD_LIBRARY_PATH").split(QLatin1String(":"));
|
if (auto value = env.value("LD_PRELOAD"); !value.isEmpty())
|
||||||
|
preloadList = value.split(QLatin1String(":"));
|
||||||
|
QStringList libPaths;
|
||||||
|
if (auto value = env.value("LD_LIBRARY_PATH"); !value.isEmpty())
|
||||||
|
libPaths = value.split(QLatin1String(":"));
|
||||||
|
|
||||||
auto mangoHudLibString = MangoHud::getLibraryString();
|
auto mangoHudLibString = MangoHud::getLibraryString();
|
||||||
if (!mangoHudLibString.isEmpty()) {
|
if (!mangoHudLibString.isEmpty()) {
|
||||||
QFileInfo mangoHudLib(mangoHudLibString);
|
QFileInfo mangoHudLib(mangoHudLibString);
|
||||||
|
QString libPath = mangoHudLib.absolutePath();
|
||||||
|
auto appendLib = [libPath, &preloadList](QString fileName) {
|
||||||
|
if (QFileInfo(FS::PathCombine(libPath, fileName)).exists())
|
||||||
|
preloadList << fileName;
|
||||||
|
};
|
||||||
|
|
||||||
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
|
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
|
||||||
preloadList << "libMangoHud_dlsym.so" << mangoHudLib.fileName();
|
appendLib("libMangoHud_dlsym.so");
|
||||||
libPaths << mangoHudLib.absolutePath();
|
appendLib("libMangoHud_opengl.so");
|
||||||
|
appendLib(mangoHudLib.fileName());
|
||||||
|
libPaths << libPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":")));
|
env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":")));
|
||||||
@ -579,7 +628,6 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
|||||||
env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
|
env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -692,7 +740,7 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
|
|||||||
{
|
{
|
||||||
QString windowParams;
|
QString windowParams;
|
||||||
if (settings()->get("LaunchMaximized").toBool())
|
if (settings()->get("LaunchMaximized").toBool())
|
||||||
windowParams = "max";
|
windowParams = "maximized";
|
||||||
else
|
else
|
||||||
windowParams =
|
windowParams =
|
||||||
QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt());
|
QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt());
|
||||||
@ -700,6 +748,19 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
|
|||||||
launchScript += "windowParams " + windowParams + "\n";
|
launchScript += "windowParams " + windowParams + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// launcher info
|
||||||
|
{
|
||||||
|
launchScript += "launcherBrand " + BuildConfig.LAUNCHER_NAME + "\n";
|
||||||
|
launchScript += "launcherVersion " + BuildConfig.printableVersionString() + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// instance info
|
||||||
|
{
|
||||||
|
launchScript += "instanceName " + name() + "\n";
|
||||||
|
launchScript += "instanceIconKey " + name() + "\n";
|
||||||
|
launchScript += "instanceIconPath icon.png\n"; // we already save a copy here
|
||||||
|
}
|
||||||
|
|
||||||
// legacy auth
|
// legacy auth
|
||||||
if (session) {
|
if (session) {
|
||||||
launchScript += "userName " + session->player_name + "\n";
|
launchScript += "userName " + session->player_name + "\n";
|
||||||
@ -710,6 +771,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;
|
||||||
@ -850,9 +914,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;
|
||||||
@ -934,13 +995,16 @@ QString MinecraftInstance::getStatusbarDescription()
|
|||||||
if (m_settings->get("ShowGameTime").toBool()) {
|
if (m_settings->get("ShowGameTime").toBool()) {
|
||||||
if (lastTimePlayed() > 0) {
|
if (lastTimePlayed() > 0) {
|
||||||
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
|
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
|
||||||
description.append(tr(", last played on %1 for %2")
|
description.append(
|
||||||
|
tr(", last played on %1 for %2")
|
||||||
.arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
|
.arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
|
||||||
.arg(Time::prettifyDuration(lastTimePlayed())));
|
.arg(Time::prettifyDuration(lastTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalTimePlayed() > 0) {
|
if (totalTimePlayed() > 0) {
|
||||||
description.append(tr(", total played for %1").arg(Time::prettifyDuration(totalTimePlayed())));
|
description.append(
|
||||||
|
tr(", total played for %1")
|
||||||
|
.arg(Time::prettifyDuration(totalTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasCrashed()) {
|
if (hasCrashed()) {
|
||||||
|
@ -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;
|
||||||
@ -173,4 +174,4 @@ class MinecraftInstance : public BaseInstance {
|
|||||||
mutable std::shared_ptr<GameOptions> m_game_options;
|
mutable std::shared_ptr<GameOptions> m_game_options;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::shared_ptr<MinecraftInstance> MinecraftInstancePtr;
|
using MinecraftInstancePtr = std::shared_ptr<MinecraftInstance>;
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
struct MojangDownloadInfo {
|
struct MojangDownloadInfo {
|
||||||
// types
|
// types
|
||||||
typedef std::shared_ptr<MojangDownloadInfo> Ptr;
|
using Ptr = std::shared_ptr<MojangDownloadInfo>;
|
||||||
|
|
||||||
// data
|
// data
|
||||||
/// Local filesystem path. WARNING: not used, only here so we can pass through mojang files unmolested!
|
/// Local filesystem path. WARNING: not used, only here so we can pass through mojang files unmolested!
|
||||||
@ -23,7 +23,7 @@ struct MojangLibraryDownloadInfo {
|
|||||||
MojangLibraryDownloadInfo() {}
|
MojangLibraryDownloadInfo() {}
|
||||||
|
|
||||||
// types
|
// types
|
||||||
typedef std::shared_ptr<MojangLibraryDownloadInfo> Ptr;
|
using Ptr = std::shared_ptr<MojangLibraryDownloadInfo>;
|
||||||
|
|
||||||
// methods
|
// methods
|
||||||
MojangDownloadInfo* getDownloadInfo(QString classifier)
|
MojangDownloadInfo* getDownloadInfo(QString classifier)
|
||||||
@ -42,7 +42,7 @@ struct MojangLibraryDownloadInfo {
|
|||||||
|
|
||||||
struct MojangAssetIndexInfo : public MojangDownloadInfo {
|
struct MojangAssetIndexInfo : public MojangDownloadInfo {
|
||||||
// types
|
// types
|
||||||
typedef std::shared_ptr<MojangAssetIndexInfo> Ptr;
|
using Ptr = std::shared_ptr<MojangAssetIndexInfo>;
|
||||||
|
|
||||||
// methods
|
// methods
|
||||||
MojangAssetIndexInfo() {}
|
MojangAssetIndexInfo() {}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
class OneSixVersionFormat {
|
class OneSixVersionFormat {
|
||||||
public:
|
public:
|
||||||
// version files / profile patches
|
// version files / profile patches
|
||||||
static VersionFilePtr versionFileFromJson(const QJsonDocument& doc, const QString& filename, const bool requireOrder);
|
static VersionFilePtr versionFileFromJson(const QJsonDocument& doc, const QString& filename, bool requireOrder);
|
||||||
static QJsonDocument versionFileToJson(const VersionFilePtr& patch);
|
static QJsonDocument versionFileToJson(const VersionFilePtr& patch);
|
||||||
|
|
||||||
// libraries
|
// libraries
|
||||||
|
@ -58,15 +58,14 @@
|
|||||||
#include "ComponentUpdateTask.h"
|
#include "ComponentUpdateTask.h"
|
||||||
#include "PackProfile.h"
|
#include "PackProfile.h"
|
||||||
#include "PackProfile_p.h"
|
#include "PackProfile_p.h"
|
||||||
|
#include "minecraft/mod/Mod.h"
|
||||||
|
#include "modplatform/ModIndex.h"
|
||||||
|
|
||||||
#include "Application.h"
|
static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
|
||||||
#include "modplatform/ResourceAPI.h"
|
{ "net.minecraftforge", ModPlatform::Forge },
|
||||||
|
{ "net.fabricmc.fabric-loader", ModPlatform::Fabric },
|
||||||
static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ { "net.neoforged", ResourceAPI::NeoForge },
|
{ "org.quiltmc.quilt-loader", ModPlatform::Quilt },
|
||||||
{ "net.minecraftforge", ResourceAPI::Forge },
|
{ "com.mumfrey.liteloader", ModPlatform::LiteLoader } };
|
||||||
{ "net.fabricmc.fabric-loader", ResourceAPI::Fabric },
|
|
||||||
{ "org.quiltmc.quilt-loader", ResourceAPI::Quilt },
|
|
||||||
{ "com.mumfrey.liteloader", ResourceAPI::LiteLoader } };
|
|
||||||
|
|
||||||
PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel()
|
PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel()
|
||||||
{
|
{
|
||||||
@ -431,7 +430,7 @@ bool PackProfile::remove(const int index)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PackProfile::remove(const QString id)
|
bool PackProfile::remove(const QString& id)
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (auto patch : d->components) {
|
for (auto patch : d->components) {
|
||||||
@ -990,12 +989,12 @@ void PackProfile::disableInteraction(bool disable)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
|
std::optional<ModPlatform::ModLoaderTypes> PackProfile::getModLoaders()
|
||||||
{
|
{
|
||||||
ResourceAPI::ModLoaderTypes result;
|
ModPlatform::ModLoaderTypes result;
|
||||||
bool has_any_loader = false;
|
bool has_any_loader = false;
|
||||||
|
|
||||||
QMapIterator<QString, ResourceAPI::ModLoaderType> i(modloaderMapping);
|
QMapIterator<QString, ModPlatform::ModLoaderType> i(modloaderMapping);
|
||||||
|
|
||||||
while (i.hasNext()) {
|
while (i.hasNext()) {
|
||||||
i.next();
|
i.next();
|
||||||
@ -1009,3 +1008,17 @@ std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
|
|||||||
return {};
|
return {};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<ModPlatform::ModLoaderTypes> PackProfile::getSupportedModLoaders()
|
||||||
|
{
|
||||||
|
auto loadersOpt = getModLoaders();
|
||||||
|
if (!loadersOpt.has_value())
|
||||||
|
return loadersOpt;
|
||||||
|
auto loaders = loadersOpt.value();
|
||||||
|
// TODO: remove this or add version condition once Quilt drops official Fabric support
|
||||||
|
if (loaders & ModPlatform::Quilt)
|
||||||
|
loaders |= ModPlatform::Fabric;
|
||||||
|
if (getComponentVersion("net.minecraft") == "1.20.1" && (loaders & ModPlatform::NeoForge))
|
||||||
|
loaders |= ModPlatform::Forge;
|
||||||
|
return loaders;
|
||||||
|
}
|
||||||
|
@ -44,14 +44,11 @@
|
|||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "BaseVersion.h"
|
|
||||||
#include "Component.h"
|
#include "Component.h"
|
||||||
#include "LaunchProfile.h"
|
#include "LaunchProfile.h"
|
||||||
#include "Library.h"
|
#include "modplatform/ModIndex.h"
|
||||||
#include "MojangDownloadInfo.h"
|
|
||||||
#include "ProfileUtils.h"
|
|
||||||
#include "modplatform/ResourceAPI.h"
|
|
||||||
#include "net/Mode.h"
|
#include "net/Mode.h"
|
||||||
|
|
||||||
class MinecraftInstance;
|
class MinecraftInstance;
|
||||||
@ -92,13 +89,13 @@ class PackProfile : public QAbstractListModel {
|
|||||||
|
|
||||||
enum MoveDirection { MoveUp, MoveDown };
|
enum MoveDirection { MoveUp, MoveDown };
|
||||||
/// move component file # up or down the list
|
/// move component file # up or down the list
|
||||||
void move(const int index, const MoveDirection direction);
|
void move(int index, MoveDirection direction);
|
||||||
|
|
||||||
/// remove component file # - including files/records
|
/// remove component file # - including files/records
|
||||||
bool remove(const int index);
|
bool remove(int index);
|
||||||
|
|
||||||
/// remove component file by id - including files/records
|
/// remove component file by id - including files/records
|
||||||
bool remove(const QString id);
|
bool remove(const QString& id);
|
||||||
|
|
||||||
bool customize(int index);
|
bool customize(int index);
|
||||||
|
|
||||||
@ -146,7 +143,9 @@ class PackProfile : public QAbstractListModel {
|
|||||||
// todo(merged): is this the best approach
|
// todo(merged): is this the best approach
|
||||||
void appendComponent(ComponentPtr component);
|
void appendComponent(ComponentPtr component);
|
||||||
|
|
||||||
std::optional<ResourceAPI::ModLoaderTypes> getModLoaders();
|
std::optional<ModPlatform::ModLoaderTypes> getModLoaders();
|
||||||
|
// this returns aditional loaders(Quilt supports fabric and NeoForge supports Forge)
|
||||||
|
std::optional<ModPlatform::ModLoaderTypes> getSupportedModLoaders();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void scheduleSave();
|
void scheduleSave();
|
||||||
|
@ -140,7 +140,7 @@ VersionFilePtr parseJsonFile(const QFileInfo& fileInfo, const bool requireOrder)
|
|||||||
return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder);
|
return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool saveJsonFile(const QJsonDocument doc, const QString& filename)
|
bool saveJsonFile(const QJsonDocument& doc, const QString& filename)
|
||||||
{
|
{
|
||||||
auto data = doc.toJson();
|
auto data = doc.toJson();
|
||||||
QSaveFile jsonFile(filename);
|
QSaveFile jsonFile(filename);
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
#include "VersionFile.h"
|
#include "VersionFile.h"
|
||||||
|
|
||||||
namespace ProfileUtils {
|
namespace ProfileUtils {
|
||||||
typedef QStringList PatchOrder;
|
using PatchOrder = QStringList;
|
||||||
|
|
||||||
/// Read and parse a OneSix format order file
|
/// Read and parse a OneSix format order file
|
||||||
bool readOverrideOrders(QString path, PatchOrder& order);
|
bool readOverrideOrders(QString path, PatchOrder& order);
|
||||||
@ -47,10 +47,10 @@ bool readOverrideOrders(QString path, PatchOrder& order);
|
|||||||
bool writeOverrideOrders(QString path, const PatchOrder& order);
|
bool writeOverrideOrders(QString path, const PatchOrder& order);
|
||||||
|
|
||||||
/// Parse a version file in JSON format
|
/// Parse a version file in JSON format
|
||||||
VersionFilePtr parseJsonFile(const QFileInfo& fileInfo, const bool requireOrder);
|
VersionFilePtr parseJsonFile(const QFileInfo& fileInfo, bool requireOrder);
|
||||||
|
|
||||||
/// Save a JSON file (in any format)
|
/// Save a JSON file (in any format)
|
||||||
bool saveJsonFile(const QJsonDocument doc, const QString& filename);
|
bool saveJsonFile(const QJsonDocument& doc, const QString& filename);
|
||||||
|
|
||||||
/// Remove LWJGL from a patch file. This is applied to all Mojang-like profile files.
|
/// Remove LWJGL from a patch file. This is applied to all Mojang-like profile files.
|
||||||
void removeLwjglFromPatch(VersionFilePtr patch);
|
void removeLwjglFromPatch(VersionFilePtr patch);
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
@ -283,9 +283,15 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
|
|||||||
return account->accountDisplayString();
|
return account->accountDisplayString();
|
||||||
|
|
||||||
case TypeColumn: {
|
case TypeColumn: {
|
||||||
auto typeStr = account->typeString();
|
switch (account->accountType()) {
|
||||||
typeStr[0] = typeStr[0].toUpper();
|
case AccountType::MSA: {
|
||||||
return typeStr;
|
return tr("MSA", "Account type");
|
||||||
|
}
|
||||||
|
case AccountType::Offline: {
|
||||||
|
return tr("Offline", "Account type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tr("Unknown", "Account type");
|
||||||
}
|
}
|
||||||
|
|
||||||
case StatusColumn: {
|
case StatusColumn: {
|
||||||
@ -320,17 +326,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 +361,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 +372,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();
|
||||||
}
|
}
|
||||||
@ -415,7 +406,7 @@ Qt::ItemFlags AccountList::flags(const QModelIndex& index) const
|
|||||||
|
|
||||||
bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int role)
|
bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int role)
|
||||||
{
|
{
|
||||||
if (idx.row() < 0 || idx.row() >= rowCount(idx) || !idx.isValid()) {
|
if (idx.row() < 0 || idx.row() >= rowCount(idx.parent()) || !idx.isValid()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -423,7 +414,8 @@ bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int rol
|
|||||||
if (value == Qt::Checked) {
|
if (value == Qt::Checked) {
|
||||||
MinecraftAccountPtr account = at(idx.row());
|
MinecraftAccountPtr account = at(idx.row());
|
||||||
setDefaultAccount(account);
|
setDefaultAccount(account);
|
||||||
}
|
} else if (m_defaultAccount == at(idx.row()))
|
||||||
|
setDefaultAccount(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit dataChanged(idx, index(idx.row(), columnCount(QModelIndex()) - 1));
|
emit dataChanged(idx, index(idx.row(), columnCount(QModelIndex()) - 1));
|
||||||
@ -472,9 +464,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;
|
||||||
@ -488,36 +477,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,
|
||||||
|
|
||||||
@ -76,7 +75,7 @@ class AccountList : public QAbstractListModel {
|
|||||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
|
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||||
virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override;
|
virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override;
|
||||||
|
|
||||||
void addAccount(const MinecraftAccountPtr account);
|
void addAccount(MinecraftAccountPtr account);
|
||||||
void removeAccount(QModelIndex index);
|
void removeAccount(QModelIndex index);
|
||||||
int findAccountByProfileId(const QString& profileId) const;
|
int findAccountByProfileId(const QString& profileId) const;
|
||||||
MinecraftAccountPtr getAccountByProfileName(const QString& profileName) const;
|
MinecraftAccountPtr getAccountByProfileName(const QString& profileName) const;
|
||||||
@ -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
|
||||||
@ -47,4 +43,4 @@ struct AuthSession {
|
|||||||
bool demo = false;
|
bool demo = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::shared_ptr<AuthSession> AuthSessionPtr;
|
using AuthSessionPtr = std::shared_ptr<AuthSession>;
|
||||||
|
@ -51,23 +51,14 @@
|
|||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
#include "flows/MSA.h"
|
#include "flows/MSA.h"
|
||||||
#include "flows/Mojang.h"
|
|
||||||
#include "flows/Offline.h"
|
#include "flows/Offline.h"
|
||||||
|
#include "minecraft/auth/AccountData.h"
|
||||||
|
|
||||||
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
|
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 +68,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 +120,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 +152,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);
|
||||||
@ -218,7 +186,7 @@ void MinecraftAccount::authFailed(QString reason)
|
|||||||
// NOTE: this doesn't do much. There was an error of some sort.
|
// NOTE: this doesn't do much. There was an error of some sort.
|
||||||
} break;
|
} break;
|
||||||
case AccountTaskState::STATE_FAILED_HARD: {
|
case AccountTaskState::STATE_FAILED_HARD: {
|
||||||
if (isMSA()) {
|
if (accountType() == AccountType::MSA) {
|
||||||
data.msaToken.token = QString();
|
data.msaToken.token = QString();
|
||||||
data.msaToken.refresh_token = QString();
|
data.msaToken.refresh_token = QString();
|
||||||
data.msaToken.validity = Katabasis::Validity::None;
|
data.msaToken.validity = Katabasis::Validity::None;
|
||||||
@ -296,13 +264,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
|
||||||
|
@ -54,7 +54,7 @@ class Task;
|
|||||||
class AccountTask;
|
class AccountTask;
|
||||||
class MinecraftAccount;
|
class MinecraftAccount;
|
||||||
|
|
||||||
typedef shared_qobject_ptr<MinecraftAccount> MinecraftAccountPtr;
|
using MinecraftAccountPtr = shared_qobject_ptr<MinecraftAccount>;
|
||||||
Q_DECLARE_METATYPE(MinecraftAccountPtr)
|
Q_DECLARE_METATYPE(MinecraftAccountPtr)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -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,11 +118,7 @@ class MinecraftAccount : public QObject, public Usable {
|
|||||||
|
|
||||||
bool isActive() const;
|
bool isActive() const;
|
||||||
|
|
||||||
bool canMigrate() const { return data.canMigrateToMSA; }
|
[[nodiscard]] AccountType accountType() const noexcept { return data.type; }
|
||||||
|
|
||||||
bool isMSA() const { return data.type == AccountType::MSA; }
|
|
||||||
|
|
||||||
bool isOffline() const { return data.type == AccountType::Offline; }
|
|
||||||
|
|
||||||
bool ownsMinecraft() const { return data.minecraftEntitlement.ownsMinecraft; }
|
bool ownsMinecraft() const { return data.minecraftEntitlement.ownsMinecraft; }
|
||||||
|
|
||||||
@ -142,12 +127,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)) {
|
||||||
|
@ -26,4 +26,4 @@ struct MinecraftServerTarget {
|
|||||||
static MinecraftServerTarget parse(const QString& fullAddress);
|
static MinecraftServerTarget parse(const QString& fullAddress);
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::shared_ptr<MinecraftServerTarget> MinecraftServerTargetPtr;
|
using MinecraftServerTargetPtr = std::shared_ptr<MinecraftServerTarget>;
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
#include "Version.h"
|
#include "Version.h"
|
||||||
|
|
||||||
// Values taken from:
|
// Values taken from:
|
||||||
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_data_pack#%22pack_format%22
|
// https://minecraft.wiki/w/Tutorials/Creating_a_data_pack#%22pack_format%22
|
||||||
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
|
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
|
||||||
{ 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } },
|
{ 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } },
|
||||||
{ 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } },
|
{ 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } },
|
||||||
|
@ -63,7 +63,7 @@ class DataPack : public Resource {
|
|||||||
mutable QMutex m_data_lock;
|
mutable QMutex m_data_lock;
|
||||||
|
|
||||||
/* The 'version' of a data pack, as defined in the pack.mcmeta file.
|
/* The 'version' of a data pack, as defined in the pack.mcmeta file.
|
||||||
* See https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
|
* See https://minecraft.wiki/w/Data_pack#pack.mcmeta
|
||||||
*/
|
*/
|
||||||
int m_pack_format = 0;
|
int m_pack_format = 0;
|
||||||
|
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -132,15 +132,21 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -
|
|||||||
if (!preserve_metadata) {
|
if (!preserve_metadata) {
|
||||||
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
|
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
|
||||||
|
|
||||||
|
destroyMetadata(index_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resource::destroy(attempt_trash);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mod::destroyMetadata(QDir& index_dir)
|
||||||
|
{
|
||||||
if (metadata()) {
|
if (metadata()) {
|
||||||
Metadata::remove(index_dir, metadata()->slug);
|
Metadata::remove(index_dir, metadata()->slug);
|
||||||
} else {
|
} else {
|
||||||
auto n = name();
|
auto n = name();
|
||||||
Metadata::remove(index_dir, n);
|
Metadata::remove(index_dir, n);
|
||||||
}
|
}
|
||||||
}
|
m_local_details.metadata = nullptr;
|
||||||
|
|
||||||
return Resource::destroy(attempt_trash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mod::details() const -> const ModDetails&
|
auto Mod::details() const -> const ModDetails&
|
||||||
@ -246,7 +252,8 @@ void Mod::setIcon(QImage new_image) const
|
|||||||
PixmapCache::remove(m_pack_image_cache_key.key);
|
PixmapCache::remove(m_pack_image_cache_key.key);
|
||||||
|
|
||||||
// scale the image to avoid flooding the pixmapcache
|
// scale the image to avoid flooding the pixmapcache
|
||||||
auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
|
auto pixmap =
|
||||||
|
QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
|
||||||
|
|
||||||
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
|
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
|
||||||
m_pack_image_cache_key.was_ever_used = true;
|
m_pack_image_cache_key.was_ever_used = true;
|
||||||
@ -259,7 +266,7 @@ QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
|
|||||||
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
|
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
|
||||||
if (size.isNull())
|
if (size.isNull())
|
||||||
return cached_image;
|
return cached_image;
|
||||||
return cached_image.scaled(size, mode);
|
return cached_image.scaled(size, mode, Qt::SmoothTransformation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// No valid image we can get
|
// No valid image we can get
|
||||||
|
@ -93,6 +93,8 @@ class Mod : public Resource {
|
|||||||
|
|
||||||
// Delete all the files of this mod
|
// Delete all the files of this mod
|
||||||
auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
|
auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
|
||||||
|
// Delete the metadata only
|
||||||
|
void destroyMetadata(QDir& index_dir);
|
||||||
|
|
||||||
void finishResolvingWithDetails(ModDetails&& details);
|
void finishResolvingWithDetails(ModDetails&& details);
|
||||||
|
|
||||||
|
@ -37,9 +37,9 @@
|
|||||||
#include "ModFolderModel.h"
|
#include "ModFolderModel.h"
|
||||||
|
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
#include <qheaderview.h>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
|
#include <QHeaderView>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
@ -51,8 +51,13 @@
|
|||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
|
#include "Json.h"
|
||||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||||
|
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
|
||||||
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
||||||
|
#include "modplatform/ModIndex.h"
|
||||||
|
#include "modplatform/flame/FlameAPI.h"
|
||||||
|
#include "modplatform/flame/FlameModIndex.h"
|
||||||
|
|
||||||
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
|
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
|
||||||
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
|
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
|
||||||
@ -60,8 +65,8 @@ ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool
|
|||||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider" });
|
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider" });
|
||||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") });
|
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") });
|
||||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER };
|
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER };
|
||||||
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch,
|
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch,
|
||||||
QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents };
|
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
|
||||||
m_columnsHideable = { false, true, false, true, true, true };
|
m_columnsHideable = { false, true, false, true, true, true };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +131,11 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
|
|||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
case Qt::SizeHintRole:
|
||||||
|
if (column == ImageColumn) {
|
||||||
|
return QSize(32, 32);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
case Qt::CheckStateRole:
|
case Qt::CheckStateRole:
|
||||||
switch (column) {
|
switch (column) {
|
||||||
case ActiveColumn:
|
case ActiveColumn:
|
||||||
@ -228,6 +238,25 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ModFolderModel::deleteModsMetadata(const QModelIndexList& indexes)
|
||||||
|
{
|
||||||
|
if (indexes.isEmpty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto i : indexes) {
|
||||||
|
if (i.column() != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto m = at(i.row());
|
||||||
|
auto index_dir = indexDir();
|
||||||
|
m->destroyMetadata(index_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool ModFolderModel::isValid()
|
bool ModFolderModel::isValid()
|
||||||
{
|
{
|
||||||
return m_dir.exists() && m_dir.isReadable();
|
return m_dir.exists() && m_dir.isReadable();
|
||||||
@ -309,3 +338,47 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
|
|||||||
|
|
||||||
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const FlameAPI flameAPI;
|
||||||
|
bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers)
|
||||||
|
{
|
||||||
|
if (vers.addonId.isValid()) {
|
||||||
|
ModPlatform::IndexedPack pack{
|
||||||
|
vers.addonId,
|
||||||
|
ModPlatform::ResourceProvider::FLAME,
|
||||||
|
};
|
||||||
|
|
||||||
|
QEventLoop loop;
|
||||||
|
|
||||||
|
auto response = std::make_shared<QByteArray>();
|
||||||
|
auto job = flameAPI.getProject(vers.addonId.toString(), response);
|
||||||
|
|
||||||
|
QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
|
||||||
|
QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
|
||||||
|
QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
|
||||||
|
QJsonParseError parse_error{};
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||||
|
if (parse_error.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
|
||||||
|
<< " reason: " << parse_error.errorString();
|
||||||
|
qDebug() << *response;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto obj = Json::requireObject(Json::requireObject(doc), "data");
|
||||||
|
FlameMod::loadIndexedPack(pack, obj);
|
||||||
|
} catch (const JSONValidationError& e) {
|
||||||
|
qDebug() << doc;
|
||||||
|
qWarning() << "Error while reading mod info: " << e.cause();
|
||||||
|
}
|
||||||
|
LocalModUpdateTask update_metadata(indexDir(), pack, vers);
|
||||||
|
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
|
||||||
|
update_metadata.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
job->start();
|
||||||
|
|
||||||
|
loop.exec();
|
||||||
|
}
|
||||||
|
return ResourceFolderModel::installResource(file_path);
|
||||||
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user