Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into import_zip
This commit is contained in:
commit
ba947993c2
106
.github/workflows/build.yml
vendored
106
.github/workflows/build.yml
vendored
@ -54,14 +54,17 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
qt_ver: 5
|
qt_ver: 5
|
||||||
|
qt_host: linux
|
||||||
|
qt_arch: ""
|
||||||
|
qt_version: "5.12.8"
|
||||||
|
qt_modules: "qtnetworkauth"
|
||||||
|
|
||||||
- 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 qtnetworkauth"
|
||||||
qt_tools: ""
|
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MinGW-w64"
|
name: "Windows-MinGW-w64"
|
||||||
@ -75,10 +78,9 @@ jobs:
|
|||||||
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.7.0'
|
qt_version: "6.7.0"
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
qt_tools: ''
|
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MSVC-arm64"
|
name: "Windows-MSVC-arm64"
|
||||||
@ -87,20 +89,18 @@ jobs:
|
|||||||
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.7.0'
|
qt_version: "6.7.0"
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
qt_tools: ''
|
|
||||||
|
|
||||||
- os: macos-12
|
- os: macos-12
|
||||||
name: macOS
|
name: macOS
|
||||||
macosx_deployment_target: 11.0
|
macosx_deployment_target: 11.0
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: mac
|
qt_host: mac
|
||||||
qt_arch: ''
|
qt_arch: ""
|
||||||
qt_version: '6.7.0'
|
qt_version: "6.7.0"
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
qt_tools: ''
|
|
||||||
|
|
||||||
- os: macos-12
|
- os: macos-12
|
||||||
name: macOS-Legacy
|
name: macOS-Legacy
|
||||||
@ -108,8 +108,7 @@ jobs:
|
|||||||
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: "qtnetworkauth"
|
||||||
qt_tools: ""
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
@ -151,6 +150,7 @@ jobs:
|
|||||||
quazip-qt6:p
|
quazip-qt6:p
|
||||||
ccache:p
|
ccache:p
|
||||||
qt6-5compat:p
|
qt6-5compat:p
|
||||||
|
qt6-networkauth:p
|
||||||
cmark:p
|
cmark:p
|
||||||
|
|
||||||
- name: Force newer ccache
|
- name: Force newer ccache
|
||||||
@ -160,7 +160,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup ccache
|
- name: Setup ccache
|
||||||
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
|
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.12
|
uses: hendrikmuhs/ccache-action@v1.2.13
|
||||||
with:
|
with:
|
||||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
|
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
|
||||||
|
|
||||||
@ -207,11 +207,6 @@ jobs:
|
|||||||
brew update
|
brew update
|
||||||
brew install ninja extra-cmake-modules
|
brew install ninja extra-cmake-modules
|
||||||
|
|
||||||
- name: Install Qt (Linux)
|
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
|
||||||
run: |
|
|
||||||
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
|
|
||||||
|
|
||||||
- name: Install host Qt (Windows MSVC arm64)
|
- name: Install host Qt (Windows MSVC arm64)
|
||||||
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
|
||||||
@ -223,20 +218,18 @@ jobs:
|
|||||||
target: "desktop"
|
target: "desktop"
|
||||||
arch: ""
|
arch: ""
|
||||||
modules: ${{ matrix.qt_modules }}
|
modules: ${{ matrix.qt_modules }}
|
||||||
tools: ${{ matrix.qt_tools }}
|
|
||||||
cache: ${{ inputs.is_qt_cached }}
|
cache: ${{ inputs.is_qt_cached }}
|
||||||
cache-key-prefix: host-qt-arm64-windows
|
cache-key-prefix: host-qt-arm64-windows
|
||||||
dir: ${{ github.workspace }}\HostQt
|
dir: ${{ github.workspace }}\HostQt
|
||||||
set-env: false
|
set-env: false
|
||||||
|
|
||||||
- name: Install Qt (macOS, Linux, Qt 6 & Windows MSVC)
|
- name: Install Qt (macOS, Linux & Windows MSVC)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '')
|
if: 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 }}
|
|
||||||
target: "desktop"
|
target: "desktop"
|
||||||
arch: ${{ matrix.qt_arch }}
|
arch: ${{ matrix.qt_arch }}
|
||||||
modules: ${{ matrix.qt_modules }}
|
modules: ${{ matrix.qt_modules }}
|
||||||
@ -432,12 +425,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
|
cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
|
||||||
|
|
||||||
cd ${{ env.INSTALL_DIR }}
|
|
||||||
if ("${{ matrix.qt_ver }}" -eq "5")
|
|
||||||
{
|
|
||||||
Copy-Item ${{ runner.workspace }}/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll
|
|
||||||
Copy-Item ${{ runner.workspace }}/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll
|
|
||||||
}
|
|
||||||
cd ${{ github.workspace }}
|
cd ${{ github.workspace }}
|
||||||
|
|
||||||
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
|
||||||
@ -490,28 +477,6 @@ jobs:
|
|||||||
":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
|
||||||
}
|
}
|
||||||
|
|
||||||
- name: Package (Linux)
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}
|
|
||||||
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_DIR }}/manifest.txt
|
|
||||||
cd ${{ env.INSTALL_DIR }}
|
|
||||||
tar --owner root --group root -czf ../PrismLauncher.tar.gz *
|
|
||||||
|
|
||||||
- name: Package (Linux, portable)
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }}
|
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
|
||||||
|
|
||||||
# workaround to make portable installs to work on fedora
|
|
||||||
mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
|
||||||
cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
|
||||||
|
|
||||||
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
|
||||||
cd ${{ env.INSTALL_PORTABLE_DIR }}
|
|
||||||
tar -czf ../PrismLauncher-portable.tar.gz *
|
|
||||||
|
|
||||||
- name: Package AppImage (Linux)
|
- name: Package AppImage (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
shell: bash
|
shell: bash
|
||||||
@ -558,6 +523,25 @@ jobs:
|
|||||||
|
|
||||||
mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
|
mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
|
||||||
|
|
||||||
|
- name: Package (Linux, portable)
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
run: |
|
||||||
|
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_PORTABLE_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=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -DINSTALL_BUNDLE=full -G Ninja
|
||||||
|
cmake --install ${{ env.BUILD_DIR }}
|
||||||
|
cmake --install ${{ env.BUILD_DIR }} --component portable
|
||||||
|
|
||||||
|
mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libffi.so.7 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
|
||||||
|
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
||||||
|
cd ${{ env.INSTALL_PORTABLE_DIR }}
|
||||||
|
tar -czf ../PrismLauncher-portable.tar.gz *
|
||||||
|
|
||||||
##
|
##
|
||||||
# UPLOAD BUILDS
|
# UPLOAD BUILDS
|
||||||
##
|
##
|
||||||
@ -590,13 +574,6 @@ jobs:
|
|||||||
name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PrismLauncher-Setup.exe
|
path: PrismLauncher-Setup.exe
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux, Qt 5)
|
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: PrismLauncher-${{ runner.os }}-Qt5-${{ env.VERSION }}-${{ inputs.build_type }}
|
|
||||||
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@v4
|
uses: actions/upload-artifact@v4
|
||||||
@ -604,13 +581,6 @@ jobs:
|
|||||||
name: PrismLauncher-${{ runner.os }}-Qt5-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)
|
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver !=5
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: PrismLauncher-${{ runner.os }}-Qt6-${{ env.VERSION }}-${{ inputs.build_type }}
|
|
||||||
path: PrismLauncher.tar.gz
|
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux, portable, Qt 6)
|
- name: Upload binary tarball (Linux, portable, Qt 6)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
sudo apt-get -y update
|
sudo apt-get -y update
|
||||||
|
|
||||||
sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
|
sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 libqt5networkauth5 libqt5networkauth5-dev
|
||||||
|
|
||||||
- name: Configure and Build
|
- name: Configure and Build
|
||||||
run: |
|
run: |
|
||||||
|
4
.github/workflows/trigger_release.yml
vendored
4
.github/workflows/trigger_release.yml
vendored
@ -46,9 +46,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }}
|
mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }}
|
||||||
mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||||
mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
|
||||||
mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-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-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.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
|
mv PrismLauncher-macOS-Legacy*/PrismLauncher.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
|
||||||
@ -92,11 +90,9 @@ jobs:
|
|||||||
draft: true
|
draft: true
|
||||||
prerelease: false
|
prerelease: false
|
||||||
files: |
|
files: |
|
||||||
PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz
|
|
||||||
PrismLauncher-Linux-Qt5-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-Portable-${{ env.VERSION }}.tar.gz
|
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||||
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
|
||||||
|
@ -282,7 +282,7 @@ endif()
|
|||||||
include(QtVersionlessBackport)
|
include(QtVersionlessBackport)
|
||||||
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
||||||
set(QT_VERSION_MAJOR 5)
|
set(QT_VERSION_MAJOR 5)
|
||||||
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml)
|
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth)
|
||||||
|
|
||||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||||
find_package(QuaZip-Qt5 1.3 QUIET)
|
find_package(QuaZip-Qt5 1.3 QUIET)
|
||||||
@ -296,7 +296,7 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
|||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE")
|
||||||
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
||||||
set(QT_VERSION_MAJOR 6)
|
set(QT_VERSION_MAJOR 6)
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat)
|
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth)
|
||||||
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
|
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
|
||||||
|
|
||||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||||
@ -417,7 +417,19 @@ elseif(UNIX)
|
|||||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR})
|
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR})
|
||||||
|
|
||||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "share/${Launcher_Name}")
|
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "share/${Launcher_Name}")
|
||||||
|
|
||||||
|
if (INSTALL_BUNDLE STREQUAL full)
|
||||||
|
set(PLUGIN_DEST_DIR "plugins")
|
||||||
|
set(BUNDLE_DEST_DIR ".")
|
||||||
|
set(RESOURCES_DEST_DIR ".")
|
||||||
|
|
||||||
|
# Apps to bundle
|
||||||
|
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${Launcher_APP_BINARY_NAME}")
|
||||||
|
|
||||||
|
# directories to look for dependencies
|
||||||
|
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||||
|
endif()
|
||||||
|
|
||||||
if(Launcher_ManPage)
|
if(Launcher_ManPage)
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6")
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6")
|
||||||
endif()
|
endif()
|
||||||
@ -511,7 +523,6 @@ if(NOT cmark_FOUND)
|
|||||||
else()
|
else()
|
||||||
message(STATUS "Using system cmark")
|
message(STATUS "Using system cmark")
|
||||||
endif()
|
endif()
|
||||||
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
|
|
||||||
add_subdirectory(libraries/gamemode)
|
add_subdirectory(libraries/gamemode)
|
||||||
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
|
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
|
||||||
if (NOT ghc_filesystem_FOUND)
|
if (NOT ghc_filesystem_FOUND)
|
||||||
|
26
COPYING.md
26
COPYING.md
@ -333,32 +333,6 @@
|
|||||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
## O2 (Katabasis fork)
|
|
||||||
|
|
||||||
Copyright (c) 2012, Akos Polster
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
## Gamemode
|
## Gamemode
|
||||||
|
|
||||||
Copyright (c) 2017-2022, Feral Interactive
|
Copyright (c) 2017-2022, Feral Interactive
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
<string>A Minecraft mod wants to access your camera.</string>
|
<string>A Minecraft mod wants to access your camera.</string>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>A Minecraft mod wants to access your microphone.</string>
|
<string>A Minecraft mod wants to access your microphone.</string>
|
||||||
|
<key>NSDownloadsFolderUsageDescription</key>
|
||||||
|
<string>Prism uses access to your Downloads folder to help you more quickly add mods that can't be automatically downloaded to your instance. You can change where Prism scans for downloaded mods in Settings or the prompt that appears.</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
|
18
flake.lock
generated
18
flake.lock
generated
@ -23,11 +23,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1712014858,
|
"lastModified": 1714641030,
|
||||||
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
|
"narHash": "sha256-yzcRNDoyVP7+SCNX0wmuDju1NUCt8Dz9+lyUXEI0dbI=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
|
"rev": "e5d10a24b66c3ea8f150e47dfdb0416ab7c3390e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -93,11 +93,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1713596654,
|
"lastModified": 1715413075,
|
||||||
"narHash": "sha256-LJbHQQ5aX1LVth2ST+Kkse/DRzgxlVhTL1rxthvyhZc=",
|
"narHash": "sha256-FCi3R1MeS5bVp0M0xTheveP6hhcCYfW/aghSTPebYL4=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "fd16bb6d3bcca96039b11aa52038fafeb6e4f4be",
|
"rev": "e4e7a43a9db7e22613accfeb1005cca1b2b1ee0d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -122,11 +122,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1712897695,
|
"lastModified": 1714478972,
|
||||||
"narHash": "sha256-nMirxrGteNAl9sWiOhoN5tIHyjBbVi5e2tgZUgZlK3Y=",
|
"narHash": "sha256-q//cgb52vv81uOuwz1LaXElp3XAe1TqrABXODAEF6Sk=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "pre-commit-hooks.nix",
|
"repo": "pre-commit-hooks.nix",
|
||||||
"rev": "40e6053ecb65fcbf12863338a6dcefb3f55f1bf8",
|
"rev": "2849da033884f54822af194400f8dff435ada242",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -126,7 +126,6 @@ set(NET_SOURCES
|
|||||||
net/MetaCacheSink.h
|
net/MetaCacheSink.h
|
||||||
net/Logging.h
|
net/Logging.h
|
||||||
net/Logging.cpp
|
net/Logging.cpp
|
||||||
net/NetAction.h
|
|
||||||
net/NetJob.cpp
|
net/NetJob.cpp
|
||||||
net/NetJob.h
|
net/NetJob.h
|
||||||
net/NetUtils.h
|
net/NetUtils.h
|
||||||
@ -210,28 +209,17 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/auth/AccountData.h
|
minecraft/auth/AccountData.h
|
||||||
minecraft/auth/AccountList.cpp
|
minecraft/auth/AccountList.cpp
|
||||||
minecraft/auth/AccountList.h
|
minecraft/auth/AccountList.h
|
||||||
minecraft/auth/AccountTask.cpp
|
|
||||||
minecraft/auth/AccountTask.h
|
|
||||||
minecraft/auth/AuthRequest.cpp
|
|
||||||
minecraft/auth/AuthRequest.h
|
|
||||||
minecraft/auth/AuthSession.cpp
|
minecraft/auth/AuthSession.cpp
|
||||||
minecraft/auth/AuthSession.h
|
minecraft/auth/AuthSession.h
|
||||||
minecraft/auth/AuthStep.cpp
|
|
||||||
minecraft/auth/AuthStep.h
|
minecraft/auth/AuthStep.h
|
||||||
minecraft/auth/MinecraftAccount.cpp
|
minecraft/auth/MinecraftAccount.cpp
|
||||||
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/flows/AuthFlow.cpp
|
minecraft/auth/AuthFlow.cpp
|
||||||
minecraft/auth/flows/AuthFlow.h
|
minecraft/auth/AuthFlow.h
|
||||||
minecraft/auth/flows/MSA.cpp
|
|
||||||
minecraft/auth/flows/MSA.h
|
|
||||||
minecraft/auth/flows/Offline.cpp
|
|
||||||
minecraft/auth/flows/Offline.h
|
|
||||||
|
|
||||||
minecraft/auth/steps/OfflineStep.cpp
|
|
||||||
minecraft/auth/steps/OfflineStep.h
|
|
||||||
minecraft/auth/steps/EntitlementsStep.cpp
|
minecraft/auth/steps/EntitlementsStep.cpp
|
||||||
minecraft/auth/steps/EntitlementsStep.h
|
minecraft/auth/steps/EntitlementsStep.h
|
||||||
minecraft/auth/steps/GetSkinStep.cpp
|
minecraft/auth/steps/GetSkinStep.cpp
|
||||||
@ -240,6 +228,8 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/auth/steps/LauncherLoginStep.h
|
minecraft/auth/steps/LauncherLoginStep.h
|
||||||
minecraft/auth/steps/MinecraftProfileStep.cpp
|
minecraft/auth/steps/MinecraftProfileStep.cpp
|
||||||
minecraft/auth/steps/MinecraftProfileStep.h
|
minecraft/auth/steps/MinecraftProfileStep.h
|
||||||
|
minecraft/auth/steps/MSADeviceCodeStep.cpp
|
||||||
|
minecraft/auth/steps/MSADeviceCodeStep.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
|
||||||
@ -624,7 +614,6 @@ set(PRISMUPDATER_SOURCES
|
|||||||
net/HttpMetaCache.h
|
net/HttpMetaCache.h
|
||||||
net/Logging.h
|
net/Logging.h
|
||||||
net/Logging.cpp
|
net/Logging.cpp
|
||||||
net/NetAction.h
|
|
||||||
net/NetRequest.cpp
|
net/NetRequest.cpp
|
||||||
net/NetRequest.h
|
net/NetRequest.h
|
||||||
net/NetJob.cpp
|
net/NetJob.cpp
|
||||||
@ -827,6 +816,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/themes/DarkTheme.h
|
ui/themes/DarkTheme.h
|
||||||
ui/themes/ITheme.cpp
|
ui/themes/ITheme.cpp
|
||||||
ui/themes/ITheme.h
|
ui/themes/ITheme.h
|
||||||
|
ui/themes/HintOverrideProxyStyle.cpp
|
||||||
|
ui/themes/HintOverrideProxyStyle.h
|
||||||
ui/themes/SystemTheme.cpp
|
ui/themes/SystemTheme.cpp
|
||||||
ui/themes/SystemTheme.h
|
ui/themes/SystemTheme.h
|
||||||
ui/themes/IconTheme.cpp
|
ui/themes/IconTheme.cpp
|
||||||
@ -1239,7 +1230,6 @@ target_link_libraries(Launcher_logic
|
|||||||
tomlplusplus::tomlplusplus
|
tomlplusplus::tomlplusplus
|
||||||
qdcss
|
qdcss
|
||||||
BuildConfig
|
BuildConfig
|
||||||
Katabasis
|
|
||||||
Qt${QT_VERSION_MAJOR}::Widgets
|
Qt${QT_VERSION_MAJOR}::Widgets
|
||||||
ghcFilesystem::ghc_filesystem
|
ghcFilesystem::ghc_filesystem
|
||||||
)
|
)
|
||||||
@ -1257,6 +1247,7 @@ target_link_libraries(Launcher_logic
|
|||||||
Qt${QT_VERSION_MAJOR}::Concurrent
|
Qt${QT_VERSION_MAJOR}::Concurrent
|
||||||
Qt${QT_VERSION_MAJOR}::Gui
|
Qt${QT_VERSION_MAJOR}::Gui
|
||||||
Qt${QT_VERSION_MAJOR}::Widgets
|
Qt${QT_VERSION_MAJOR}::Widgets
|
||||||
|
Qt${QT_VERSION_MAJOR}::NetworkAuth
|
||||||
${Launcher_QT_LIBS}
|
${Launcher_QT_LIBS}
|
||||||
)
|
)
|
||||||
target_link_libraries(Launcher_logic
|
target_link_libraries(Launcher_logic
|
||||||
@ -1327,7 +1318,6 @@ if(Launcher_BUILD_UPDATER)
|
|||||||
Qt${QT_VERSION_MAJOR}::Network
|
Qt${QT_VERSION_MAJOR}::Network
|
||||||
${Launcher_QT_LIBS}
|
${Launcher_QT_LIBS}
|
||||||
cmark::cmark
|
cmark::cmark
|
||||||
Katabasis
|
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
|
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
|
||||||
@ -1492,7 +1482,6 @@ if(INSTALL_BUNDLE STREQUAL "full")
|
|||||||
CONFIGURATIONS Debug RelWithDebInfo ""
|
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||||
DESTINATION ${PLUGIN_DEST_DIR}
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
COMPONENT Runtime
|
COMPONENT Runtime
|
||||||
PATTERN "*qopensslbackend*" EXCLUDE
|
|
||||||
PATTERN "*qcertonlybackend*" EXCLUDE
|
PATTERN "*qcertonlybackend*" EXCLUDE
|
||||||
)
|
)
|
||||||
install(
|
install(
|
||||||
@ -1503,10 +1492,78 @@ if(INSTALL_BUNDLE STREQUAL "full")
|
|||||||
REGEX "dd\\." EXCLUDE
|
REGEX "dd\\." EXCLUDE
|
||||||
REGEX "_debug\\." EXCLUDE
|
REGEX "_debug\\." EXCLUDE
|
||||||
REGEX "\\.dSYM" EXCLUDE
|
REGEX "\\.dSYM" EXCLUDE
|
||||||
PATTERN "*qopensslbackend*" EXCLUDE
|
|
||||||
PATTERN "*qcertonlybackend*" EXCLUDE
|
PATTERN "*qcertonlybackend*" EXCLUDE
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
# Wayland support
|
||||||
|
if(EXISTS "${QT_PLUGINS_DIR}/wayland-graphics-integration-client")
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-client"
|
||||||
|
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
)
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-client"
|
||||||
|
CONFIGURATIONS Release MinSizeRel
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
REGEX "dd\\." EXCLUDE
|
||||||
|
REGEX "_debug\\." EXCLUDE
|
||||||
|
REGEX "\\.dSYM" EXCLUDE
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
if(EXISTS "${QT_PLUGINS_DIR}/wayland-graphics-integration-server")
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-server"
|
||||||
|
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
)
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-server"
|
||||||
|
CONFIGURATIONS Release MinSizeRel
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
REGEX "dd\\." EXCLUDE
|
||||||
|
REGEX "_debug\\." EXCLUDE
|
||||||
|
REGEX "\\.dSYM" EXCLUDE
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
if(EXISTS "${QT_PLUGINS_DIR}/wayland-decoration-client")
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-decoration-client"
|
||||||
|
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
)
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-decoration-client"
|
||||||
|
CONFIGURATIONS Release MinSizeRel
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
REGEX "dd\\." EXCLUDE
|
||||||
|
REGEX "_debug\\." EXCLUDE
|
||||||
|
REGEX "\\.dSYM" EXCLUDE
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
if(EXISTS "${QT_PLUGINS_DIR}/wayland-shell-integration")
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-shell-integration"
|
||||||
|
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
)
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-shell-integration"
|
||||||
|
CONFIGURATIONS Release MinSizeRel
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
REGEX "dd\\." EXCLUDE
|
||||||
|
REGEX "_debug\\." EXCLUDE
|
||||||
|
REGEX "\\.dSYM" EXCLUDE
|
||||||
|
)
|
||||||
|
endif()
|
||||||
configure_file(
|
configure_file(
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"
|
"${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake"
|
"${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake"
|
||||||
|
@ -4,8 +4,6 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
|
||||||
InstanceCreationTask::InstanceCreationTask() = default;
|
|
||||||
|
|
||||||
void InstanceCreationTask::executeTask()
|
void InstanceCreationTask::executeTask()
|
||||||
{
|
{
|
||||||
setAbortable(true);
|
setAbortable(true);
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
class InstanceCreationTask : public InstanceTask {
|
class InstanceCreationTask : public InstanceTask {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
InstanceCreationTask();
|
InstanceCreationTask() = default;
|
||||||
virtual ~InstanceCreationTask() = default;
|
virtual ~InstanceCreationTask() = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -57,7 +57,6 @@
|
|||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include "JavaCommon.h"
|
#include "JavaCommon.h"
|
||||||
#include "launch/steps/TextPrint.h"
|
#include "launch/steps/TextPrint.h"
|
||||||
#include "minecraft/auth/AccountTask.h"
|
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
LaunchController::LaunchController(QObject* parent) : Task(parent) {}
|
LaunchController::LaunchController(QObject* parent) : Task(parent) {}
|
||||||
|
@ -154,7 +154,12 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q
|
|||||||
#if defined(LAUNCHER_APPLICATION)
|
#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,
|
||||||
|
bool utf8Enabled = false)
|
||||||
: m_output_path(outputPath)
|
: m_output_path(outputPath)
|
||||||
, m_output(outputPath)
|
, m_output(outputPath)
|
||||||
, m_dir(dir)
|
, m_dir(dir)
|
||||||
@ -163,10 +168,15 @@ class ExportToZipTask : public Task {
|
|||||||
, m_follow_symlinks(followSymlinks)
|
, m_follow_symlinks(followSymlinks)
|
||||||
{
|
{
|
||||||
setAbortable(true);
|
setAbortable(true);
|
||||||
m_output.setUtf8Enabled(true);
|
m_output.setUtf8Enabled(utf8Enabled);
|
||||||
};
|
};
|
||||||
ExportToZipTask(QString outputPath, QString dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
|
ExportToZipTask(QString outputPath,
|
||||||
: ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks){};
|
QString dir,
|
||||||
|
QFileInfoList files,
|
||||||
|
QString destinationPrefix = "",
|
||||||
|
bool followSymlinks = false,
|
||||||
|
bool utf8Enabled = false)
|
||||||
|
: ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks, utf8Enabled){};
|
||||||
|
|
||||||
virtual ~ExportToZipTask() = default;
|
virtual ~ExportToZipTask() = default;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
set(CMAKE_MODULE_PATH "@CMAKE_MODULE_PATH@")
|
set(CMAKE_MODULE_PATH "@CMAKE_MODULE_PATH@")
|
||||||
|
|
||||||
file(GLOB_RECURSE QTPLUGINS "${CMAKE_INSTALL_PREFIX}/@PLUGIN_DEST_DIR@/*@CMAKE_SHARED_LIBRARY_SUFFIX@")
|
file(GLOB_RECURSE QTPLUGINS "${CMAKE_INSTALL_PREFIX}/@PLUGIN_DEST_DIR@/*@CMAKE_SHARED_LIBRARY_SUFFIX@")
|
||||||
function(gp_resolved_file_type_override resolved_file type_var)
|
function(gp_resolved_file_type_override resolved_file type_var)
|
||||||
if(resolved_file MATCHES "^/(usr/)?lib/libQt")
|
if(resolved_file MATCHES "^/(usr/)?lib/libQt")
|
||||||
|
@ -449,13 +449,13 @@ QStringList getMinecraftJavaBundle()
|
|||||||
executable += "w.exe";
|
executable += "w.exe";
|
||||||
|
|
||||||
auto appDataPath = QProcessEnvironment::systemEnvironment().value("APPDATA", "");
|
auto appDataPath = QProcessEnvironment::systemEnvironment().value("APPDATA", "");
|
||||||
processpaths << FS::PathCombine(QFileInfo(appDataPath).absolutePath(), ".minecraft", "runtime");
|
processpaths << FS::PathCombine(QFileInfo(appDataPath).absoluteFilePath(), ".minecraft", "runtime");
|
||||||
|
|
||||||
// add the microsoft store version of the launcher to the search. the current path is:
|
// 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
|
// C:\Users\USERNAME\AppData\Local\Packages\Microsoft.4297127D64EC6_8wekyb3d8bbwe\LocalCache\Local\runtime
|
||||||
auto localAppDataPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", "");
|
auto localAppDataPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", "");
|
||||||
auto minecraftMSStorePath =
|
auto minecraftMSStorePath =
|
||||||
FS::PathCombine(QFileInfo(localAppDataPath).absolutePath(), "Packages", "Microsoft.4297127D64EC6_8wekyb3d8bbwe");
|
FS::PathCombine(QFileInfo(localAppDataPath).absoluteFilePath(), "Packages", "Microsoft.4297127D64EC6_8wekyb3d8bbwe");
|
||||||
processpaths << FS::PathCombine(minecraftMSStorePath, "LocalCache", "Local", "runtime");
|
processpaths << FS::PathCombine(minecraftMSStorePath, "LocalCache", "Local", "runtime");
|
||||||
#else
|
#else
|
||||||
processpaths << FS::PathCombine(QDir::homePath(), ".minecraft", "runtime");
|
processpaths << FS::PathCombine(QDir::homePath(), ".minecraft", "runtime");
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
#include "net/Download.h"
|
#include "net/Download.h"
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
QSet<QString> collectPathsFromDir(QString dirPath)
|
QSet<QString> collectPathsFromDir(QString dirPath)
|
||||||
@ -276,7 +277,7 @@ bool reconstructAssets(QString assetsId, QString resourcesFolder)
|
|||||||
|
|
||||||
} // namespace AssetsUtils
|
} // namespace AssetsUtils
|
||||||
|
|
||||||
NetAction::Ptr AssetObject::getDownloadAction()
|
Net::NetRequest::Ptr AssetObject::getDownloadAction()
|
||||||
{
|
{
|
||||||
QFileInfo objectFile(getLocalPath());
|
QFileInfo objectFile(getLocalPath());
|
||||||
if ((!objectFile.isFile()) || (objectFile.size() != size)) {
|
if ((!objectFile.isFile()) || (objectFile.size() != size)) {
|
||||||
|
@ -17,14 +17,14 @@
|
|||||||
|
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include "net/NetAction.h"
|
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
struct AssetObject {
|
struct AssetObject {
|
||||||
QString getRelPath();
|
QString getRelPath();
|
||||||
QUrl getUrl();
|
QUrl getUrl();
|
||||||
QString getLocalPath();
|
QString getLocalPath();
|
||||||
NetAction::Ptr getDownloadAction();
|
Net::NetRequest::Ptr getDownloadAction();
|
||||||
|
|
||||||
QString hash;
|
QString hash;
|
||||||
qint64 size;
|
qint64 size;
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
|
|
||||||
#include "Library.h"
|
#include "Library.h"
|
||||||
#include "MinecraftInstance.h"
|
#include "MinecraftInstance.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
#include <BuildConfig.h>
|
#include <BuildConfig.h>
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
@ -74,12 +75,12 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<NetAction::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext,
|
QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext,
|
||||||
class HttpMetaCache* cache,
|
class HttpMetaCache* cache,
|
||||||
QStringList& failedLocalFiles,
|
QStringList& failedLocalFiles,
|
||||||
const QString& overridePath) const
|
const QString& overridePath) const
|
||||||
{
|
{
|
||||||
QList<NetAction::Ptr> out;
|
QList<Net::NetRequest::Ptr> out;
|
||||||
bool stale = isAlwaysStale();
|
bool stale = isAlwaysStale();
|
||||||
bool local = isLocal();
|
bool local = isLocal();
|
||||||
|
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <net/NetAction.h>
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
@ -48,6 +47,7 @@
|
|||||||
#include "MojangDownloadInfo.h"
|
#include "MojangDownloadInfo.h"
|
||||||
#include "Rule.h"
|
#include "Rule.h"
|
||||||
#include "RuntimeContext.h"
|
#include "RuntimeContext.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
class Library;
|
class Library;
|
||||||
class MinecraftInstance;
|
class MinecraftInstance;
|
||||||
@ -144,10 +144,10 @@ class Library {
|
|||||||
bool isForge() const;
|
bool isForge() const;
|
||||||
|
|
||||||
// Get a list of downloads for this library
|
// Get a list of downloads for this library
|
||||||
QList<NetAction::Ptr> getDownloads(const RuntimeContext& runtimeContext,
|
QList<Net::NetRequest::Ptr> getDownloads(const RuntimeContext& runtimeContext,
|
||||||
class HttpMetaCache* cache,
|
class HttpMetaCache* cache,
|
||||||
QStringList& failedLocalFiles,
|
QStringList& failedLocalFiles,
|
||||||
const QString& overridePath) const;
|
const QString& overridePath) const;
|
||||||
|
|
||||||
QString getCompatibleNative(const RuntimeContext& runtimeContext) const;
|
QString getCompatibleNative(const RuntimeContext& runtimeContext) const;
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void tokenToJSONV3(QJsonObject& parent, Katabasis::Token t, const char* tokenName)
|
void tokenToJSONV3(QJsonObject& parent, Token t, const char* tokenName)
|
||||||
{
|
{
|
||||||
if (!t.persistent) {
|
if (!t.persistent) {
|
||||||
return;
|
return;
|
||||||
@ -74,9 +74,9 @@ void tokenToJSONV3(QJsonObject& parent, Katabasis::Token t, const char* tokenNam
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Katabasis::Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenName)
|
Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenName)
|
||||||
{
|
{
|
||||||
Katabasis::Token out;
|
Token out;
|
||||||
auto tokenObject = parent.value(tokenName).toObject();
|
auto tokenObject = parent.value(tokenName).toObject();
|
||||||
if (tokenObject.isEmpty()) {
|
if (tokenObject.isEmpty()) {
|
||||||
return out;
|
return out;
|
||||||
@ -94,7 +94,7 @@ Katabasis::Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenNam
|
|||||||
auto token = tokenObject.value("token");
|
auto token = tokenObject.value("token");
|
||||||
if (token.isString()) {
|
if (token.isString()) {
|
||||||
out.token = token.toString();
|
out.token = token.toString();
|
||||||
out.validity = Katabasis::Validity::Assumed;
|
out.validity = Validity::Assumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto refresh_token = tokenObject.value("refresh_token");
|
auto refresh_token = tokenObject.value("refresh_token");
|
||||||
@ -241,13 +241,13 @@ MinecraftProfile profileFromJSONV3(const QJsonObject& parent, const char* tokenN
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.validity = Katabasis::Validity::Assumed;
|
out.validity = Validity::Assumed;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void entitlementToJSONV3(QJsonObject& parent, MinecraftEntitlement p)
|
void entitlementToJSONV3(QJsonObject& parent, MinecraftEntitlement p)
|
||||||
{
|
{
|
||||||
if (p.validity == Katabasis::Validity::None) {
|
if (p.validity == Validity::None) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QJsonObject out;
|
QJsonObject out;
|
||||||
@ -271,7 +271,7 @@ bool entitlementFromJSONV3(const QJsonObject& parent, MinecraftEntitlement& out)
|
|||||||
}
|
}
|
||||||
out.canPlayMinecraft = canPlayMinecraftV.toBool(false);
|
out.canPlayMinecraft = canPlayMinecraftV.toBool(false);
|
||||||
out.ownsMinecraft = ownsMinecraftV.toBool(false);
|
out.ownsMinecraft = ownsMinecraftV.toBool(false);
|
||||||
out.validity = Katabasis::Validity::Assumed;
|
out.validity = Validity::Assumed;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -313,10 +313,10 @@ bool AccountData::resumeStateFromV3(QJsonObject data)
|
|||||||
|
|
||||||
minecraftProfile = profileFromJSONV3(data, "profile");
|
minecraftProfile = profileFromJSONV3(data, "profile");
|
||||||
if (!entitlementFromJSONV3(data, minecraftEntitlement)) {
|
if (!entitlementFromJSONV3(data, minecraftEntitlement)) {
|
||||||
if (minecraftProfile.validity != Katabasis::Validity::None) {
|
if (minecraftProfile.validity != Validity::None) {
|
||||||
minecraftEntitlement.canPlayMinecraft = true;
|
minecraftEntitlement.canPlayMinecraft = true;
|
||||||
minecraftEntitlement.ownsMinecraft = true;
|
minecraftEntitlement.ownsMinecraft = true;
|
||||||
minecraftEntitlement.validity = Katabasis::Validity::Assumed;
|
minecraftEntitlement.validity = Validity::Assumed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,12 +34,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <katabasis/Bits.h>
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVariantMap>
|
||||||
|
|
||||||
|
enum class Validity { None, Assumed, Certain };
|
||||||
|
|
||||||
|
struct Token {
|
||||||
|
QDateTime issueInstant;
|
||||||
|
QDateTime notAfter;
|
||||||
|
QString token;
|
||||||
|
QString refresh_token;
|
||||||
|
QVariantMap extra;
|
||||||
|
|
||||||
|
Validity validity = Validity::None;
|
||||||
|
bool persistent = true;
|
||||||
|
};
|
||||||
|
|
||||||
struct Skin {
|
struct Skin {
|
||||||
QString id;
|
QString id;
|
||||||
QString url;
|
QString url;
|
||||||
@ -59,7 +76,7 @@ struct Cape {
|
|||||||
struct MinecraftEntitlement {
|
struct MinecraftEntitlement {
|
||||||
bool ownsMinecraft = false;
|
bool ownsMinecraft = false;
|
||||||
bool canPlayMinecraft = false;
|
bool canPlayMinecraft = false;
|
||||||
Katabasis::Validity validity = Katabasis::Validity::None;
|
Validity validity = Validity::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MinecraftProfile {
|
struct MinecraftProfile {
|
||||||
@ -68,7 +85,7 @@ struct MinecraftProfile {
|
|||||||
Skin skin;
|
Skin skin;
|
||||||
QString currentCape;
|
QString currentCape;
|
||||||
QMap<QString, Cape> capes;
|
QMap<QString, Cape> capes;
|
||||||
Katabasis::Validity validity = Katabasis::Validity::None;
|
Validity validity = Validity::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class AccountType { MSA, Offline };
|
enum class AccountType { MSA, Offline };
|
||||||
@ -93,15 +110,15 @@ struct AccountData {
|
|||||||
AccountType type = AccountType::MSA;
|
AccountType type = AccountType::MSA;
|
||||||
|
|
||||||
QString msaClientID;
|
QString msaClientID;
|
||||||
Katabasis::Token msaToken;
|
Token msaToken;
|
||||||
Katabasis::Token userToken;
|
Token userToken;
|
||||||
Katabasis::Token xboxApiToken;
|
Token xboxApiToken;
|
||||||
Katabasis::Token mojangservicesToken;
|
Token mojangservicesToken;
|
||||||
|
|
||||||
Katabasis::Token yggdrasilToken;
|
Token yggdrasilToken;
|
||||||
MinecraftProfile minecraftProfile;
|
MinecraftProfile minecraftProfile;
|
||||||
MinecraftEntitlement minecraftEntitlement;
|
MinecraftEntitlement minecraftEntitlement;
|
||||||
Katabasis::Validity validity_ = Katabasis::Validity::None;
|
Validity validity_ = Validity::None;
|
||||||
|
|
||||||
// runtime only information (not saved with the account)
|
// runtime only information (not saved with the account)
|
||||||
QString internalId;
|
QString internalId;
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
#include "AccountList.h"
|
#include "AccountList.h"
|
||||||
#include "AccountData.h"
|
#include "AccountData.h"
|
||||||
#include "AccountTask.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
@ -639,8 +639,8 @@ void AccountList::tryNext()
|
|||||||
if (account->internalId() == accountId) {
|
if (account->internalId() == accountId) {
|
||||||
m_currentTask = account->refresh();
|
m_currentTask = account->refresh();
|
||||||
if (m_currentTask) {
|
if (m_currentTask) {
|
||||||
connect(m_currentTask.get(), &AccountTask::succeeded, this, &AccountList::authSucceeded);
|
connect(m_currentTask.get(), &Task::succeeded, this, &AccountList::authSucceeded);
|
||||||
connect(m_currentTask.get(), &AccountTask::failed, this, &AccountList::authFailed);
|
connect(m_currentTask.get(), &Task::failed, this, &AccountList::authFailed);
|
||||||
m_currentTask->start();
|
m_currentTask->start();
|
||||||
qDebug() << "RefreshSchedule: Processing account " << account->accountDisplayString() << " with internal ID "
|
qDebug() << "RefreshSchedule: Processing account " << account->accountDisplayString() << " with internal ID "
|
||||||
<< accountId;
|
<< accountId;
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "MinecraftAccount.h"
|
#include "MinecraftAccount.h"
|
||||||
|
#include "minecraft/auth/AuthFlow.h"
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@ -144,7 +145,7 @@ class AccountList : public QAbstractListModel {
|
|||||||
QList<QString> m_refreshQueue;
|
QList<QString> m_refreshQueue;
|
||||||
QTimer* m_refreshTimer;
|
QTimer* m_refreshTimer;
|
||||||
QTimer* m_nextTimer;
|
QTimer* m_nextTimer;
|
||||||
shared_qobject_ptr<AccountTask> m_currentTask;
|
shared_qobject_ptr<AuthFlow> m_currentTask;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Called whenever the list changes.
|
* Called whenever the list changes.
|
||||||
|
@ -1,134 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
||||||
*
|
|
||||||
* 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");
|
|
||||||
* 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 "AccountTask.h"
|
|
||||||
#include "MinecraftAccount.h"
|
|
||||||
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
AccountTask::AccountTask(AccountData* data, QObject* parent) : Task(parent), m_data(data)
|
|
||||||
{
|
|
||||||
changeState(AccountTaskState::STATE_CREATED);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AccountTask::getStateMessage() const
|
|
||||||
{
|
|
||||||
switch (m_taskState) {
|
|
||||||
case AccountTaskState::STATE_CREATED:
|
|
||||||
return "Waiting...";
|
|
||||||
case AccountTaskState::STATE_WORKING:
|
|
||||||
return tr("Sending request to auth servers...");
|
|
||||||
case AccountTaskState::STATE_SUCCEEDED:
|
|
||||||
return tr("Authentication task succeeded.");
|
|
||||||
case AccountTaskState::STATE_OFFLINE:
|
|
||||||
return tr("Failed to contact the authentication server.");
|
|
||||||
case AccountTaskState::STATE_DISABLED:
|
|
||||||
return tr("Client ID has changed. New session needs to be created.");
|
|
||||||
case AccountTaskState::STATE_FAILED_SOFT:
|
|
||||||
return tr("Encountered an error during authentication.");
|
|
||||||
case AccountTaskState::STATE_FAILED_HARD:
|
|
||||||
return tr("Failed to authenticate. The session has expired.");
|
|
||||||
case AccountTaskState::STATE_FAILED_GONE:
|
|
||||||
return tr("Failed to authenticate. The account no longer exists.");
|
|
||||||
default:
|
|
||||||
return tr("...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AccountTask::changeState(AccountTaskState newState, QString reason)
|
|
||||||
{
|
|
||||||
m_taskState = newState;
|
|
||||||
// FIXME: virtual method invoked in constructor.
|
|
||||||
// We want that behavior, but maybe make it less weird?
|
|
||||||
setStatus(getStateMessage());
|
|
||||||
switch (newState) {
|
|
||||||
case AccountTaskState::STATE_CREATED: {
|
|
||||||
m_data->errorString.clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case AccountTaskState::STATE_WORKING: {
|
|
||||||
m_data->accountState = AccountState::Working;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case AccountTaskState::STATE_SUCCEEDED: {
|
|
||||||
m_data->accountState = AccountState::Online;
|
|
||||||
emitSucceeded();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
case AccountTaskState::STATE_OFFLINE: {
|
|
||||||
m_data->errorString = reason;
|
|
||||||
m_data->accountState = AccountState::Offline;
|
|
||||||
emitFailed(reason);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
case AccountTaskState::STATE_DISABLED: {
|
|
||||||
m_data->errorString = reason;
|
|
||||||
m_data->accountState = AccountState::Disabled;
|
|
||||||
emitFailed(reason);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
case AccountTaskState::STATE_FAILED_SOFT: {
|
|
||||||
m_data->errorString = reason;
|
|
||||||
m_data->accountState = AccountState::Errored;
|
|
||||||
emitFailed(reason);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
case AccountTaskState::STATE_FAILED_HARD: {
|
|
||||||
m_data->errorString = reason;
|
|
||||||
m_data->accountState = AccountState::Expired;
|
|
||||||
emitFailed(reason);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
case AccountTaskState::STATE_FAILED_GONE: {
|
|
||||||
m_data->errorString = reason;
|
|
||||||
m_data->accountState = AccountState::Gone;
|
|
||||||
emitFailed(reason);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
QString error = tr("Unknown account task state: %1").arg(int(newState));
|
|
||||||
m_data->accountState = AccountState::Errored;
|
|
||||||
emitFailed(error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
||||||
*
|
|
||||||
* 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");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <tasks/Task.h>
|
|
||||||
|
|
||||||
#include <qsslerror.h>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QString>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include "MinecraftAccount.h"
|
|
||||||
|
|
||||||
class QNetworkReply;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum for describing the state of the current task.
|
|
||||||
* Used by the getStateMessage function to determine what the status message should be.
|
|
||||||
*/
|
|
||||||
enum class AccountTaskState {
|
|
||||||
STATE_CREATED,
|
|
||||||
STATE_WORKING,
|
|
||||||
STATE_SUCCEEDED,
|
|
||||||
STATE_DISABLED, //!< MSA Client ID has changed. Tell user to reloginn
|
|
||||||
STATE_FAILED_SOFT, //!< soft failure. authentication went through partially
|
|
||||||
STATE_FAILED_HARD, //!< hard failure. main tokens are invalid
|
|
||||||
STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists
|
|
||||||
STATE_OFFLINE //!< soft failure. authentication failed in the first step in a 'soft' way
|
|
||||||
};
|
|
||||||
|
|
||||||
class AccountTask : public Task {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit AccountTask(AccountData* data, QObject* parent = 0);
|
|
||||||
virtual ~AccountTask(){};
|
|
||||||
|
|
||||||
AccountTaskState m_taskState = AccountTaskState::STATE_CREATED;
|
|
||||||
|
|
||||||
AccountTaskState taskState() { return m_taskState; }
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
|
||||||
void hideVerificationUriAndCode();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* Returns the state message for the given state.
|
|
||||||
* Used to set the status message for the task.
|
|
||||||
* Should be overridden by subclasses that want to change messages for a given state.
|
|
||||||
*/
|
|
||||||
virtual QString getStateMessage() const;
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
// NOTE: true -> non-terminal state, false -> terminal state
|
|
||||||
bool changeState(AccountTaskState newState, QString reason = QString());
|
|
||||||
|
|
||||||
protected:
|
|
||||||
AccountData* m_data = nullptr;
|
|
||||||
};
|
|
146
launcher/minecraft/auth/AuthFlow.cpp
Normal file
146
launcher/minecraft/auth/AuthFlow.cpp
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
#include <QDebug>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
|
#include "minecraft/auth/AccountData.h"
|
||||||
|
#include "minecraft/auth/steps/EntitlementsStep.h"
|
||||||
|
#include "minecraft/auth/steps/GetSkinStep.h"
|
||||||
|
#include "minecraft/auth/steps/LauncherLoginStep.h"
|
||||||
|
#include "minecraft/auth/steps/MSADeviceCodeStep.h"
|
||||||
|
#include "minecraft/auth/steps/MSAStep.h"
|
||||||
|
#include "minecraft/auth/steps/MinecraftProfileStep.h"
|
||||||
|
#include "minecraft/auth/steps/XboxAuthorizationStep.h"
|
||||||
|
#include "minecraft/auth/steps/XboxProfileStep.h"
|
||||||
|
#include "minecraft/auth/steps/XboxUserStep.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
#include "AuthFlow.h"
|
||||||
|
|
||||||
|
#include <Application.h>
|
||||||
|
|
||||||
|
AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent) : Task(parent), m_data(data)
|
||||||
|
{
|
||||||
|
if (data->type == AccountType::MSA) {
|
||||||
|
if (action == Action::DeviceCode) {
|
||||||
|
auto oauthStep = makeShared<MSADeviceCodeStep>(m_data);
|
||||||
|
connect(oauthStep.get(), &MSADeviceCodeStep::authorizeWithBrowser, this, &AuthFlow::authorizeWithBrowserWithExtra);
|
||||||
|
connect(this, &Task::aborted, oauthStep.get(), &MSADeviceCodeStep::abort);
|
||||||
|
m_steps.append(oauthStep);
|
||||||
|
} else {
|
||||||
|
auto oauthStep = makeShared<MSAStep>(m_data, action == Action::Refresh);
|
||||||
|
connect(oauthStep.get(), &MSAStep::authorizeWithBrowser, this, &AuthFlow::authorizeWithBrowser);
|
||||||
|
m_steps.append(oauthStep);
|
||||||
|
}
|
||||||
|
m_steps.append(makeShared<XboxUserStep>(m_data));
|
||||||
|
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
|
||||||
|
m_steps.append(
|
||||||
|
makeShared<XboxAuthorizationStep>(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang"));
|
||||||
|
m_steps.append(makeShared<LauncherLoginStep>(m_data));
|
||||||
|
m_steps.append(makeShared<XboxProfileStep>(m_data));
|
||||||
|
m_steps.append(makeShared<EntitlementsStep>(m_data));
|
||||||
|
m_steps.append(makeShared<MinecraftProfileStep>(m_data));
|
||||||
|
m_steps.append(makeShared<GetSkinStep>(m_data));
|
||||||
|
}
|
||||||
|
changeState(AccountTaskState::STATE_CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthFlow::succeed()
|
||||||
|
{
|
||||||
|
m_data->validity_ = Validity::Certain;
|
||||||
|
changeState(AccountTaskState::STATE_SUCCEEDED, tr("Finished all authentication steps"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthFlow::executeTask()
|
||||||
|
{
|
||||||
|
changeState(AccountTaskState::STATE_WORKING, tr("Initializing"));
|
||||||
|
nextStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthFlow::nextStep()
|
||||||
|
{
|
||||||
|
if (m_steps.size() == 0) {
|
||||||
|
// we got to the end without an incident... assume this is all.
|
||||||
|
m_currentStep.reset();
|
||||||
|
succeed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_currentStep = m_steps.front();
|
||||||
|
qDebug() << "AuthFlow:" << m_currentStep->describe();
|
||||||
|
m_steps.pop_front();
|
||||||
|
connect(m_currentStep.get(), &AuthStep::finished, this, &AuthFlow::stepFinished);
|
||||||
|
|
||||||
|
m_currentStep->perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthFlow::stepFinished(AccountTaskState resultingState, QString message)
|
||||||
|
{
|
||||||
|
if (changeState(resultingState, message))
|
||||||
|
nextStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AuthFlow::changeState(AccountTaskState newState, QString reason)
|
||||||
|
{
|
||||||
|
m_taskState = newState;
|
||||||
|
setDetails(reason);
|
||||||
|
switch (newState) {
|
||||||
|
case AccountTaskState::STATE_CREATED: {
|
||||||
|
setStatus(tr("Waiting..."));
|
||||||
|
m_data->errorString.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case AccountTaskState::STATE_WORKING: {
|
||||||
|
setStatus(m_currentStep ? m_currentStep->describe() : tr("Working..."));
|
||||||
|
m_data->accountState = AccountState::Working;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case AccountTaskState::STATE_SUCCEEDED: {
|
||||||
|
setStatus(tr("Authentication task succeeded."));
|
||||||
|
m_data->accountState = AccountState::Online;
|
||||||
|
emitSucceeded();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case AccountTaskState::STATE_OFFLINE: {
|
||||||
|
setStatus(tr("Failed to contact the authentication server."));
|
||||||
|
m_data->errorString = reason;
|
||||||
|
m_data->accountState = AccountState::Offline;
|
||||||
|
emitFailed(reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case AccountTaskState::STATE_DISABLED: {
|
||||||
|
setStatus(tr("Client ID has changed. New session needs to be created."));
|
||||||
|
m_data->errorString = reason;
|
||||||
|
m_data->accountState = AccountState::Disabled;
|
||||||
|
emitFailed(reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case AccountTaskState::STATE_FAILED_SOFT: {
|
||||||
|
setStatus(tr("Encountered an error during authentication."));
|
||||||
|
m_data->errorString = reason;
|
||||||
|
m_data->accountState = AccountState::Errored;
|
||||||
|
emitFailed(reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case AccountTaskState::STATE_FAILED_HARD: {
|
||||||
|
setStatus(tr("Failed to authenticate. The session has expired."));
|
||||||
|
m_data->errorString = reason;
|
||||||
|
m_data->accountState = AccountState::Expired;
|
||||||
|
emitFailed(reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case AccountTaskState::STATE_FAILED_GONE: {
|
||||||
|
setStatus(tr("Failed to authenticate. The account no longer exists."));
|
||||||
|
m_data->errorString = reason;
|
||||||
|
m_data->accountState = AccountState::Gone;
|
||||||
|
emitFailed(reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
setStatus(tr("..."));
|
||||||
|
QString error = tr("Unknown account task state: %1").arg(int(newState));
|
||||||
|
m_data->accountState = AccountState::Errored;
|
||||||
|
emitFailed(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
launcher/minecraft/auth/AuthFlow.h
Normal file
45
launcher/minecraft/auth/AuthFlow.h
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QImage>
|
||||||
|
#include <QList>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
#include "minecraft/auth/AccountData.h"
|
||||||
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
class AuthFlow : public Task {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum class Action { Refresh, Login, DeviceCode };
|
||||||
|
|
||||||
|
explicit AuthFlow(AccountData* data, Action action = Action::Refresh, QObject* parent = 0);
|
||||||
|
virtual ~AuthFlow() = default;
|
||||||
|
|
||||||
|
void executeTask() override;
|
||||||
|
|
||||||
|
AccountTaskState taskState() { return m_taskState; }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void authorizeWithBrowser(const QUrl& url);
|
||||||
|
void authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void succeed();
|
||||||
|
void nextStep();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
// NOTE: true -> non-terminal state, false -> terminal state
|
||||||
|
bool changeState(AccountTaskState newState, QString reason = QString());
|
||||||
|
void stepFinished(AccountTaskState resultingState, QString message);
|
||||||
|
|
||||||
|
private:
|
||||||
|
AccountTaskState m_taskState = AccountTaskState::STATE_CREATED;
|
||||||
|
QList<AuthStep::Ptr> m_steps;
|
||||||
|
AuthStep::Ptr m_currentStep;
|
||||||
|
AccountData* m_data = nullptr;
|
||||||
|
};
|
@ -1,175 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
||||||
*
|
|
||||||
* 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");
|
|
||||||
* 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 <cassert>
|
|
||||||
|
|
||||||
#include <QBuffer>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QUrlQuery>
|
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
#include "AuthRequest.h"
|
|
||||||
#include "katabasis/Globals.h"
|
|
||||||
|
|
||||||
AuthRequest::AuthRequest(QObject* parent) : QObject(parent) {}
|
|
||||||
|
|
||||||
AuthRequest::~AuthRequest() {}
|
|
||||||
|
|
||||||
void AuthRequest::get(const QNetworkRequest& req, int timeout /* = 60*1000*/)
|
|
||||||
{
|
|
||||||
setup(req, QNetworkAccessManager::GetOperation);
|
|
||||||
reply_ = APPLICATION->network()->get(request_);
|
|
||||||
status_ = Requesting;
|
|
||||||
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
|
||||||
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
|
|
||||||
#else // &QNetworkReply::error SIGNAL depricated
|
|
||||||
connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
|
|
||||||
#endif
|
|
||||||
connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
|
|
||||||
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthRequest::post(const QNetworkRequest& req, const QByteArray& data, int timeout /* = 60*1000*/)
|
|
||||||
{
|
|
||||||
setup(req, QNetworkAccessManager::PostOperation);
|
|
||||||
data_ = data;
|
|
||||||
status_ = Requesting;
|
|
||||||
reply_ = APPLICATION->network()->post(request_, data_);
|
|
||||||
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
|
||||||
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
|
|
||||||
#else // &QNetworkReply::error SIGNAL depricated
|
|
||||||
connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
|
|
||||||
#endif
|
|
||||||
connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
|
|
||||||
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
|
|
||||||
connect(reply_, &QNetworkReply::uploadProgress, this, &AuthRequest::onUploadProgress);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthRequest::onRequestFinished()
|
|
||||||
{
|
|
||||||
if (status_ == Idle) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthRequest::onRequestError(QNetworkReply::NetworkError error)
|
|
||||||
{
|
|
||||||
qWarning() << "AuthRequest::onRequestError: Error" << (int)error;
|
|
||||||
if (status_ == Idle) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
errorString_ = reply_->errorString();
|
|
||||||
httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
error_ = error;
|
|
||||||
qWarning() << "AuthRequest::onRequestError: Error string: " << errorString_;
|
|
||||||
qWarning() << "AuthRequest::onRequestError: HTTP status" << httpStatus_
|
|
||||||
<< reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
|
||||||
|
|
||||||
// QTimer::singleShot(10, this, SLOT(finish()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthRequest::onSslErrors(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 AuthRequest::onUploadProgress(qint64 uploaded, qint64 total)
|
|
||||||
{
|
|
||||||
if (status_ == Idle) {
|
|
||||||
qWarning() << "AuthRequest::onUploadProgress: No pending request";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Restart timeout because request in progress
|
|
||||||
Katabasis::Reply* o2Reply = timedReplies_.find(reply_);
|
|
||||||
if (o2Reply) {
|
|
||||||
o2Reply->start();
|
|
||||||
}
|
|
||||||
emit uploadProgress(uploaded, total);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthRequest::setup(const QNetworkRequest& req, QNetworkAccessManager::Operation operation, const QByteArray& verb)
|
|
||||||
{
|
|
||||||
request_ = req;
|
|
||||||
operation_ = operation;
|
|
||||||
url_ = req.url();
|
|
||||||
|
|
||||||
QUrl url = url_;
|
|
||||||
request_.setUrl(url);
|
|
||||||
|
|
||||||
if (!verb.isEmpty()) {
|
|
||||||
request_.setRawHeader(Katabasis::HTTP_HTTP_HEADER, verb);
|
|
||||||
}
|
|
||||||
|
|
||||||
status_ = Requesting;
|
|
||||||
error_ = QNetworkReply::NoError;
|
|
||||||
errorString_.clear();
|
|
||||||
httpStatus_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthRequest::finish()
|
|
||||||
{
|
|
||||||
QByteArray data;
|
|
||||||
if (status_ == Idle) {
|
|
||||||
qWarning() << "AuthRequest::finish: No pending request";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
data = reply_->readAll();
|
|
||||||
status_ = Idle;
|
|
||||||
timedReplies_.remove(reply_);
|
|
||||||
reply_->disconnect(this);
|
|
||||||
reply_->deleteLater();
|
|
||||||
QList<QNetworkReply::RawHeaderPair> headers = reply_->rawHeaderPairs();
|
|
||||||
emit finished(error_, data, headers);
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QUrl>
|
|
||||||
|
|
||||||
#include "katabasis/Reply.h"
|
|
||||||
|
|
||||||
/// Makes authentication requests.
|
|
||||||
class AuthRequest : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit AuthRequest(QObject* parent = 0);
|
|
||||||
~AuthRequest();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void get(const QNetworkRequest& req, int timeout = 60 * 1000);
|
|
||||||
void post(const QNetworkRequest& req, const QByteArray& data, int timeout = 60 * 1000);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
|
|
||||||
/// Emitted when a request has been completed or failed.
|
|
||||||
void finished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
|
|
||||||
|
|
||||||
/// Emitted when an upload has progressed.
|
|
||||||
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
|
|
||||||
/// Handle request finished.
|
|
||||||
void onRequestFinished();
|
|
||||||
|
|
||||||
/// Handle request error.
|
|
||||||
void onRequestError(QNetworkReply::NetworkError error);
|
|
||||||
|
|
||||||
/// Handle ssl errors.
|
|
||||||
void onSslErrors(QList<QSslError> errors);
|
|
||||||
|
|
||||||
/// Finish the request, emit finished() signal.
|
|
||||||
void finish();
|
|
||||||
|
|
||||||
/// Handle upload progress.
|
|
||||||
void onUploadProgress(qint64 uploaded, qint64 total);
|
|
||||||
|
|
||||||
public:
|
|
||||||
QNetworkReply::NetworkError error_;
|
|
||||||
int httpStatus_ = 0;
|
|
||||||
QString errorString_;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void setup(const QNetworkRequest& request, QNetworkAccessManager::Operation operation, const QByteArray& verb = QByteArray());
|
|
||||||
|
|
||||||
enum Status { Idle, Requesting, ReRequesting };
|
|
||||||
|
|
||||||
QNetworkRequest request_;
|
|
||||||
QByteArray data_;
|
|
||||||
QNetworkReply* reply_;
|
|
||||||
Status status_;
|
|
||||||
QNetworkAccessManager::Operation operation_;
|
|
||||||
QUrl url_;
|
|
||||||
Katabasis::ReplyList timedReplies_;
|
|
||||||
|
|
||||||
QTimer* timer_;
|
|
||||||
};
|
|
@ -1,5 +0,0 @@
|
|||||||
#include "AuthStep.h"
|
|
||||||
|
|
||||||
AuthStep::AuthStep(AccountData* data) : QObject(nullptr), m_data(data) {}
|
|
||||||
|
|
||||||
AuthStep::~AuthStep() noexcept = default;
|
|
@ -3,30 +3,40 @@
|
|||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "AccountTask.h"
|
|
||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
#include "minecraft/auth/AccountData.h"
|
#include "minecraft/auth/AccountData.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for describing the state of the current task.
|
||||||
|
* Used by the getStateMessage function to determine what the status message should be.
|
||||||
|
*/
|
||||||
|
enum class AccountTaskState {
|
||||||
|
STATE_CREATED,
|
||||||
|
STATE_WORKING,
|
||||||
|
STATE_SUCCEEDED,
|
||||||
|
STATE_DISABLED, //!< MSA Client ID has changed. Tell user to reloginn
|
||||||
|
STATE_FAILED_SOFT, //!< soft failure. authentication went through partially
|
||||||
|
STATE_FAILED_HARD, //!< hard failure. main tokens are invalid
|
||||||
|
STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists
|
||||||
|
STATE_OFFLINE //!< soft failure. authentication failed in the first step in a 'soft' way
|
||||||
|
};
|
||||||
|
|
||||||
class AuthStep : public QObject {
|
class AuthStep : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using Ptr = shared_qobject_ptr<AuthStep>;
|
using Ptr = shared_qobject_ptr<AuthStep>;
|
||||||
|
|
||||||
public:
|
explicit AuthStep(AccountData* data) : QObject(nullptr), m_data(data){};
|
||||||
explicit AuthStep(AccountData* data);
|
virtual ~AuthStep() noexcept = default;
|
||||||
virtual ~AuthStep() noexcept;
|
|
||||||
|
|
||||||
virtual QString describe() = 0;
|
virtual QString describe() = 0;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual void perform() = 0;
|
virtual void perform() = 0;
|
||||||
virtual void rehydrate() = 0;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void finished(AccountTaskState resultingState, QString message);
|
void finished(AccountTaskState resultingState, QString message);
|
||||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
|
||||||
void hideVerificationUriAndCode();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AccountData* m_data;
|
AccountData* m_data;
|
||||||
|
@ -50,9 +50,8 @@
|
|||||||
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
#include "flows/MSA.h"
|
|
||||||
#include "flows/Offline.h"
|
|
||||||
#include "minecraft/auth/AccountData.h"
|
#include "minecraft/auth/AccountData.h"
|
||||||
|
#include "minecraft/auth/AuthFlow.h"
|
||||||
|
|
||||||
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
|
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
@ -80,7 +79,7 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString& username)
|
|||||||
auto account = makeShared<MinecraftAccount>();
|
auto account = makeShared<MinecraftAccount>();
|
||||||
account->data.type = AccountType::Offline;
|
account->data.type = AccountType::Offline;
|
||||||
account->data.yggdrasilToken.token = "0";
|
account->data.yggdrasilToken.token = "0";
|
||||||
account->data.yggdrasilToken.validity = Katabasis::Validity::Certain;
|
account->data.yggdrasilToken.validity = Validity::Certain;
|
||||||
account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
|
account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
|
||||||
account->data.yggdrasilToken.extra["userName"] = username;
|
account->data.yggdrasilToken.extra["userName"] = username;
|
||||||
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||||
@ -88,7 +87,7 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString& username)
|
|||||||
account->data.minecraftEntitlement.canPlayMinecraft = true;
|
account->data.minecraftEntitlement.canPlayMinecraft = true;
|
||||||
account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(QRegularExpression("[{}-]"));
|
account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(QRegularExpression("[{}-]"));
|
||||||
account->data.minecraftProfile.name = username;
|
account->data.minecraftProfile.name = username;
|
||||||
account->data.minecraftProfile.validity = Katabasis::Validity::Certain;
|
account->data.minecraftProfile.validity = Validity::Certain;
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,11 +119,11 @@ QPixmap MinecraftAccount::getFace() const
|
|||||||
return skin.scaled(64, 64, Qt::KeepAspectRatio);
|
return skin.scaled(64, 64, Qt::KeepAspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA()
|
shared_qobject_ptr<AuthFlow> MinecraftAccount::login(bool useDeviceCode)
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||||
|
|
||||||
m_currentTask.reset(new MSAInteractive(&data));
|
m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login, this));
|
||||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||||
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
||||||
@ -132,29 +131,13 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA()
|
|||||||
return m_currentTask;
|
return m_currentTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginOffline()
|
shared_qobject_ptr<AuthFlow> MinecraftAccount::refresh()
|
||||||
{
|
|
||||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
|
||||||
|
|
||||||
m_currentTask.reset(new OfflineLogin(&data));
|
|
||||||
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::refresh()
|
|
||||||
{
|
{
|
||||||
if (m_currentTask) {
|
if (m_currentTask) {
|
||||||
return m_currentTask;
|
return m_currentTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.type == AccountType::MSA) {
|
m_currentTask.reset(new AuthFlow(&data, AuthFlow::Action::Refresh, this));
|
||||||
m_currentTask.reset(new MSASilent(&data));
|
|
||||||
} else {
|
|
||||||
m_currentTask.reset(new OfflineRefresh(&data));
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||||
@ -163,7 +146,7 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::refresh()
|
|||||||
return m_currentTask;
|
return m_currentTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> MinecraftAccount::currentTask()
|
shared_qobject_ptr<AuthFlow> MinecraftAccount::currentTask()
|
||||||
{
|
{
|
||||||
return m_currentTask;
|
return m_currentTask;
|
||||||
}
|
}
|
||||||
@ -189,17 +172,17 @@ void MinecraftAccount::authFailed(QString reason)
|
|||||||
if (accountType() == AccountType::MSA) {
|
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 = Validity::None;
|
||||||
data.validity_ = Katabasis::Validity::None;
|
data.validity_ = Validity::None;
|
||||||
} else {
|
} else {
|
||||||
data.yggdrasilToken.token = QString();
|
data.yggdrasilToken.token = QString();
|
||||||
data.yggdrasilToken.validity = Katabasis::Validity::None;
|
data.yggdrasilToken.validity = Validity::None;
|
||||||
data.validity_ = Katabasis::Validity::None;
|
data.validity_ = Validity::None;
|
||||||
}
|
}
|
||||||
emit changed();
|
emit changed();
|
||||||
} break;
|
} break;
|
||||||
case AccountTaskState::STATE_FAILED_GONE: {
|
case AccountTaskState::STATE_FAILED_GONE: {
|
||||||
data.validity_ = Katabasis::Validity::None;
|
data.validity_ = Validity::None;
|
||||||
emit changed();
|
emit changed();
|
||||||
} break;
|
} break;
|
||||||
case AccountTaskState::STATE_CREATED:
|
case AccountTaskState::STATE_CREATED:
|
||||||
@ -229,13 +212,13 @@ bool MinecraftAccount::shouldRefresh() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
switch (data.validity_) {
|
switch (data.validity_) {
|
||||||
case Katabasis::Validity::Certain: {
|
case Validity::Certain: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Katabasis::Validity::None: {
|
case Validity::None: {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case Katabasis::Validity::Assumed: {
|
case Validity::Assumed: {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,15 +43,13 @@
|
|||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "AccountData.h"
|
#include "AccountData.h"
|
||||||
#include "AuthSession.h"
|
#include "AuthSession.h"
|
||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
#include "Usable.h"
|
#include "Usable.h"
|
||||||
|
#include "minecraft/auth/AuthFlow.h"
|
||||||
|
|
||||||
class Task;
|
class Task;
|
||||||
class AccountTask;
|
|
||||||
class MinecraftAccount;
|
class MinecraftAccount;
|
||||||
|
|
||||||
using MinecraftAccountPtr = shared_qobject_ptr<MinecraftAccount>;
|
using MinecraftAccountPtr = shared_qobject_ptr<MinecraftAccount>;
|
||||||
@ -97,13 +95,11 @@ class MinecraftAccount : public QObject, public Usable {
|
|||||||
QJsonObject saveToJson() const;
|
QJsonObject saveToJson() const;
|
||||||
|
|
||||||
public: /* manipulation */
|
public: /* manipulation */
|
||||||
shared_qobject_ptr<AccountTask> loginMSA();
|
shared_qobject_ptr<AuthFlow> login(bool useDeviceCode = false);
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> loginOffline();
|
shared_qobject_ptr<AuthFlow> refresh();
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> refresh();
|
shared_qobject_ptr<AuthFlow> currentTask();
|
||||||
|
|
||||||
shared_qobject_ptr<AccountTask> currentTask();
|
|
||||||
|
|
||||||
public: /* queries */
|
public: /* queries */
|
||||||
QString internalId() const { return data.internalId; }
|
QString internalId() const { return data.internalId; }
|
||||||
@ -166,7 +162,7 @@ class MinecraftAccount : public QObject, public Usable {
|
|||||||
AccountData data;
|
AccountData data;
|
||||||
|
|
||||||
// current task we are executing here
|
// current task we are executing here
|
||||||
shared_qobject_ptr<AccountTask> m_currentTask;
|
shared_qobject_ptr<AuthFlow> m_currentTask;
|
||||||
|
|
||||||
protected: /* methods */
|
protected: /* methods */
|
||||||
void incrementUses() override;
|
void incrementUses() override;
|
||||||
|
@ -79,7 +79,7 @@ bool getBool(QJsonValue value, bool& out)
|
|||||||
// 2148916238 = child account not linked to a family
|
// 2148916238 = child account not linked to a family
|
||||||
*/
|
*/
|
||||||
|
|
||||||
bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString name)
|
bool parseXTokenResponse(QByteArray& data, Token& output, QString name)
|
||||||
{
|
{
|
||||||
qDebug() << "Parsing" << name << ":";
|
qDebug() << "Parsing" << name << ":";
|
||||||
qCDebug(authCredentials()) << data;
|
qCDebug(authCredentials()) << data;
|
||||||
@ -135,7 +135,7 @@ bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString nam
|
|||||||
qWarning() << "Missing uhs";
|
qWarning() << "Missing uhs";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
output.validity = Katabasis::Validity::Certain;
|
output.validity = Validity::Certain;
|
||||||
qDebug() << name << "is valid.";
|
qDebug() << name << "is valid.";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -213,7 +213,7 @@ bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output)
|
|||||||
output.capes[capeOut.id] = capeOut;
|
output.capes[capeOut.id] = capeOut;
|
||||||
}
|
}
|
||||||
output.currentCape = currentCape;
|
output.currentCape = currentCape;
|
||||||
output.validity = Katabasis::Validity::Certain;
|
output.validity = Validity::Certain;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,7 +388,7 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output)
|
|||||||
output.currentCape = capeOut.alias;
|
output.currentCape = capeOut.alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
output.validity = Katabasis::Validity::Certain;
|
output.validity = Validity::Certain;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,7 +422,7 @@ bool parseMinecraftEntitlements(QByteArray& data, MinecraftEntitlement& output)
|
|||||||
output.ownsMinecraft = true;
|
output.ownsMinecraft = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
output.validity = Katabasis::Validity::Certain;
|
output.validity = Validity::Certain;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,7 +456,7 @@ bool parseRolloutResponse(QByteArray& data, bool& result)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool parseMojangResponse(QByteArray& data, Katabasis::Token& output)
|
bool parseMojangResponse(QByteArray& data, Token& output)
|
||||||
{
|
{
|
||||||
QJsonParseError jsonError;
|
QJsonParseError jsonError;
|
||||||
qDebug() << "Parsing Mojang response...";
|
qDebug() << "Parsing Mojang response...";
|
||||||
@ -488,7 +488,7 @@ bool parseMojangResponse(QByteArray& data, Katabasis::Token& output)
|
|||||||
qWarning() << "access_token is not valid";
|
qWarning() << "access_token is not valid";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
output.validity = Katabasis::Validity::Certain;
|
output.validity = Validity::Certain;
|
||||||
qDebug() << "Mojang response is valid.";
|
qDebug() << "Mojang response is valid.";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@ bool getNumber(QJsonValue value, double& out);
|
|||||||
bool getNumber(QJsonValue value, int64_t& out);
|
bool getNumber(QJsonValue value, int64_t& out);
|
||||||
bool getBool(QJsonValue value, bool& out);
|
bool getBool(QJsonValue value, bool& out);
|
||||||
|
|
||||||
bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString name);
|
bool parseXTokenResponse(QByteArray& data, Token& output, QString name);
|
||||||
bool parseMojangResponse(QByteArray& data, Katabasis::Token& output);
|
bool parseMojangResponse(QByteArray& data, Token& output);
|
||||||
|
|
||||||
bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output);
|
bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output);
|
||||||
bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output);
|
bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output);
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
#include <QDebug>
|
|
||||||
#include <QNetworkAccessManager>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
|
|
||||||
#include "AuthFlow.h"
|
|
||||||
#include "katabasis/Globals.h"
|
|
||||||
|
|
||||||
#include <Application.h>
|
|
||||||
|
|
||||||
AuthFlow::AuthFlow(AccountData* data, QObject* parent) : AccountTask(data, parent) {}
|
|
||||||
|
|
||||||
void AuthFlow::succeed()
|
|
||||||
{
|
|
||||||
m_data->validity_ = Katabasis::Validity::Certain;
|
|
||||||
changeState(AccountTaskState::STATE_SUCCEEDED, tr("Finished all authentication steps"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthFlow::executeTask()
|
|
||||||
{
|
|
||||||
if (m_currentStep) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
changeState(AccountTaskState::STATE_WORKING, tr("Initializing"));
|
|
||||||
nextStep();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthFlow::nextStep()
|
|
||||||
{
|
|
||||||
if (m_steps.size() == 0) {
|
|
||||||
// we got to the end without an incident... assume this is all.
|
|
||||||
m_currentStep.reset();
|
|
||||||
succeed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_currentStep = m_steps.front();
|
|
||||||
qDebug() << "AuthFlow:" << m_currentStep->describe();
|
|
||||||
m_steps.pop_front();
|
|
||||||
connect(m_currentStep.get(), &AuthStep::finished, this, &AuthFlow::stepFinished);
|
|
||||||
connect(m_currentStep.get(), &AuthStep::showVerificationUriAndCode, this, &AuthFlow::showVerificationUriAndCode);
|
|
||||||
connect(m_currentStep.get(), &AuthStep::hideVerificationUriAndCode, this, &AuthFlow::hideVerificationUriAndCode);
|
|
||||||
|
|
||||||
m_currentStep->perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AuthFlow::getStateMessage() const
|
|
||||||
{
|
|
||||||
switch (m_taskState) {
|
|
||||||
case AccountTaskState::STATE_WORKING: {
|
|
||||||
if (m_currentStep) {
|
|
||||||
return m_currentStep->describe();
|
|
||||||
} else {
|
|
||||||
return tr("Working...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return AccountTask::getStateMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AuthFlow::stepFinished(AccountTaskState resultingState, QString message)
|
|
||||||
{
|
|
||||||
if (changeState(resultingState, message)) {
|
|
||||||
nextStep();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QImage>
|
|
||||||
#include <QList>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QSet>
|
|
||||||
#include <QVector>
|
|
||||||
|
|
||||||
#include <katabasis/DeviceFlow.h>
|
|
||||||
|
|
||||||
#include "minecraft/auth/AccountData.h"
|
|
||||||
#include "minecraft/auth/AccountTask.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
|
||||||
|
|
||||||
class AuthFlow : public AccountTask {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit AuthFlow(AccountData* data, QObject* parent = 0);
|
|
||||||
|
|
||||||
Katabasis::Validity validity() { return m_data->validity_; };
|
|
||||||
|
|
||||||
QString getStateMessage() const override;
|
|
||||||
|
|
||||||
void executeTask() override;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void activityChanged(Katabasis::Activity activity);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void stepFinished(AccountTaskState resultingState, QString message);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void succeed();
|
|
||||||
void nextStep();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QList<AuthStep::Ptr> m_steps;
|
|
||||||
AuthStep::Ptr m_currentStep;
|
|
||||||
};
|
|
@ -1,36 +0,0 @@
|
|||||||
#include "MSA.h"
|
|
||||||
|
|
||||||
#include "minecraft/auth/steps/EntitlementsStep.h"
|
|
||||||
#include "minecraft/auth/steps/GetSkinStep.h"
|
|
||||||
#include "minecraft/auth/steps/LauncherLoginStep.h"
|
|
||||||
#include "minecraft/auth/steps/MSAStep.h"
|
|
||||||
#include "minecraft/auth/steps/MinecraftProfileStep.h"
|
|
||||||
#include "minecraft/auth/steps/XboxAuthorizationStep.h"
|
|
||||||
#include "minecraft/auth/steps/XboxProfileStep.h"
|
|
||||||
#include "minecraft/auth/steps/XboxUserStep.h"
|
|
||||||
|
|
||||||
MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
|
||||||
{
|
|
||||||
m_steps.append(makeShared<MSAStep>(m_data, MSAStep::Action::Refresh));
|
|
||||||
m_steps.append(makeShared<XboxUserStep>(m_data));
|
|
||||||
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
|
|
||||||
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang"));
|
|
||||||
m_steps.append(makeShared<LauncherLoginStep>(m_data));
|
|
||||||
m_steps.append(makeShared<XboxProfileStep>(m_data));
|
|
||||||
m_steps.append(makeShared<EntitlementsStep>(m_data));
|
|
||||||
m_steps.append(makeShared<MinecraftProfileStep>(m_data));
|
|
||||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
|
||||||
}
|
|
||||||
|
|
||||||
MSAInteractive::MSAInteractive(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
|
||||||
{
|
|
||||||
m_steps.append(makeShared<MSAStep>(m_data, MSAStep::Action::Login));
|
|
||||||
m_steps.append(makeShared<XboxUserStep>(m_data));
|
|
||||||
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
|
|
||||||
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang"));
|
|
||||||
m_steps.append(makeShared<LauncherLoginStep>(m_data));
|
|
||||||
m_steps.append(makeShared<XboxProfileStep>(m_data));
|
|
||||||
m_steps.append(makeShared<EntitlementsStep>(m_data));
|
|
||||||
m_steps.append(makeShared<MinecraftProfileStep>(m_data));
|
|
||||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "AuthFlow.h"
|
|
||||||
|
|
||||||
class MSAInteractive : public AuthFlow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit MSAInteractive(AccountData* data, QObject* parent = 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
class MSASilent : public AuthFlow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit MSASilent(AccountData* data, QObject* parent = 0);
|
|
||||||
};
|
|
@ -1,13 +0,0 @@
|
|||||||
#include "Offline.h"
|
|
||||||
|
|
||||||
#include "minecraft/auth/steps/OfflineStep.h"
|
|
||||||
|
|
||||||
OfflineRefresh::OfflineRefresh(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
|
||||||
{
|
|
||||||
m_steps.append(makeShared<OfflineStep>(m_data));
|
|
||||||
}
|
|
||||||
|
|
||||||
OfflineLogin::OfflineLogin(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
|
||||||
{
|
|
||||||
m_steps.append(makeShared<OfflineStep>(m_data));
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "AuthFlow.h"
|
|
||||||
|
|
||||||
class OfflineRefresh : public AuthFlow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit OfflineRefresh(AccountData* data, QObject* parent = 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
class OfflineLogin : public AuthFlow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit OfflineLogin(AccountData* data, QObject* parent = 0);
|
|
||||||
};
|
|
@ -1,16 +1,20 @@
|
|||||||
#include "EntitlementsStep.h"
|
#include "EntitlementsStep.h"
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
#include <QUrl>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
|
#include "net/Download.h"
|
||||||
|
#include "net/StaticHeaderProxy.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
EntitlementsStep::EntitlementsStep(AccountData* data) : AuthStep(data) {}
|
EntitlementsStep::EntitlementsStep(AccountData* data) : AuthStep(data) {}
|
||||||
|
|
||||||
EntitlementsStep::~EntitlementsStep() noexcept = default;
|
|
||||||
|
|
||||||
QString EntitlementsStep::describe()
|
QString EntitlementsStep::describe()
|
||||||
{
|
{
|
||||||
return tr("Determining game ownership.");
|
return tr("Determining game ownership.");
|
||||||
@ -19,35 +23,31 @@ QString EntitlementsStep::describe()
|
|||||||
void EntitlementsStep::perform()
|
void EntitlementsStep::perform()
|
||||||
{
|
{
|
||||||
auto uuid = QUuid::createUuid();
|
auto uuid = QUuid::createUuid();
|
||||||
m_entitlementsRequestId = uuid.toString().remove('{').remove('}');
|
m_entitlements_request_id = uuid.toString().remove('{').remove('}');
|
||||||
auto url = "https://api.minecraftservices.com/entitlements/license?requestId=" + m_entitlementsRequestId;
|
|
||||||
QNetworkRequest request = QNetworkRequest(url);
|
QUrl url("https://api.minecraftservices.com/entitlements/license?requestId=" + m_entitlements_request_id);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
|
||||||
request.setRawHeader("Accept", "application/json");
|
{ "Accept", "application/json" },
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
|
{ "Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8() } };
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
|
||||||
connect(requestor, &AuthRequest::finished, this, &EntitlementsStep::onRequestDone);
|
m_response.reset(new QByteArray());
|
||||||
requestor->get(request);
|
m_task = Net::Download::makeByteArray(url, m_response);
|
||||||
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &EntitlementsStep::onRequestDone);
|
||||||
|
|
||||||
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
qDebug() << "Getting entitlements...";
|
qDebug() << "Getting entitlements...";
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntitlementsStep::rehydrate()
|
void EntitlementsStep::onRequestDone()
|
||||||
{
|
{
|
||||||
// NOOP, for now. We only save bools and there's nothing to check.
|
qCDebug(authCredentials()) << *m_response;
|
||||||
}
|
|
||||||
|
|
||||||
void EntitlementsStep::onRequestDone([[maybe_unused]] QNetworkReply::NetworkError error,
|
|
||||||
QByteArray data,
|
|
||||||
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
|
|
||||||
{
|
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
|
||||||
requestor->deleteLater();
|
|
||||||
|
|
||||||
qCDebug(authCredentials()) << data;
|
|
||||||
|
|
||||||
// TODO: check presence of same entitlementsRequestId?
|
// TODO: check presence of same entitlementsRequestId?
|
||||||
// TODO: validate JWTs?
|
// TODO: validate JWTs?
|
||||||
Parsers::parseMinecraftEntitlements(data, m_data->minecraftEntitlement);
|
Parsers::parseMinecraftEntitlements(*m_response, m_data->minecraftEntitlement);
|
||||||
|
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got entitlements"));
|
emit finished(AccountTaskState::STATE_WORKING, tr("Got entitlements"));
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Download.h"
|
||||||
|
|
||||||
class EntitlementsStep : public AuthStep {
|
class EntitlementsStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EntitlementsStep(AccountData* data);
|
explicit EntitlementsStep(AccountData* data);
|
||||||
virtual ~EntitlementsStep() noexcept;
|
virtual ~EntitlementsStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_entitlementsRequestId;
|
QString m_entitlements_request_id;
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Download::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
@ -3,13 +3,10 @@
|
|||||||
|
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
#include "Application.h"
|
||||||
#include "minecraft/auth/Parsers.h"
|
|
||||||
|
|
||||||
GetSkinStep::GetSkinStep(AccountData* data) : AuthStep(data) {}
|
GetSkinStep::GetSkinStep(AccountData* data) : AuthStep(data) {}
|
||||||
|
|
||||||
GetSkinStep::~GetSkinStep() noexcept = default;
|
|
||||||
|
|
||||||
QString GetSkinStep::describe()
|
QString GetSkinStep::describe()
|
||||||
{
|
{
|
||||||
return tr("Getting skin.");
|
return tr("Getting skin.");
|
||||||
@ -17,25 +14,20 @@ QString GetSkinStep::describe()
|
|||||||
|
|
||||||
void GetSkinStep::perform()
|
void GetSkinStep::perform()
|
||||||
{
|
{
|
||||||
auto url = QUrl(m_data->minecraftProfile.skin.url);
|
QUrl url(m_data->minecraftProfile.skin.url);
|
||||||
QNetworkRequest request = QNetworkRequest(url);
|
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
m_response.reset(new QByteArray());
|
||||||
connect(requestor, &AuthRequest::finished, this, &GetSkinStep::onRequestDone);
|
m_task = Net::Download::makeByteArray(url, m_response);
|
||||||
requestor->get(request);
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &GetSkinStep::onRequestDone);
|
||||||
|
|
||||||
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetSkinStep::rehydrate()
|
void GetSkinStep::onRequestDone()
|
||||||
{
|
{
|
||||||
// NOOP, for now.
|
if (m_task->error() == QNetworkReply::NoError)
|
||||||
}
|
m_data->minecraftProfile.skin.data = *m_response;
|
||||||
|
|
||||||
void GetSkinStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
|
||||||
{
|
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
|
||||||
requestor->deleteLater();
|
|
||||||
|
|
||||||
if (error == QNetworkReply::NoError) {
|
|
||||||
m_data->minecraftProfile.skin.data = data;
|
|
||||||
}
|
|
||||||
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Got skin"));
|
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Got skin"));
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Download.h"
|
||||||
|
|
||||||
class GetSkinStep : public AuthStep {
|
class GetSkinStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GetSkinStep(AccountData* data);
|
explicit GetSkinStep(AccountData* data);
|
||||||
virtual ~GetSkinStep() noexcept;
|
virtual ~GetSkinStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Download::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
#include "LauncherLoginStep.h"
|
#include "LauncherLoginStep.h"
|
||||||
|
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "minecraft/auth/AccountTask.h"
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
#include "net/NetUtils.h"
|
#include "net/NetUtils.h"
|
||||||
|
#include "net/StaticHeaderProxy.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {}
|
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {}
|
||||||
|
|
||||||
LauncherLoginStep::~LauncherLoginStep() noexcept = default;
|
|
||||||
|
|
||||||
QString LauncherLoginStep::describe()
|
QString LauncherLoginStep::describe()
|
||||||
{
|
{
|
||||||
return tr("Accessing Mojang services.");
|
return tr("Accessing Mojang services.");
|
||||||
@ -19,7 +19,7 @@ QString LauncherLoginStep::describe()
|
|||||||
|
|
||||||
void LauncherLoginStep::perform()
|
void LauncherLoginStep::perform()
|
||||||
{
|
{
|
||||||
auto requestURL = "https://api.minecraftservices.com/launcher/login";
|
QUrl url("https://api.minecraftservices.com/launcher/login");
|
||||||
auto uhs = m_data->mojangservicesToken.extra["uhs"].toString();
|
auto uhs = m_data->mojangservicesToken.extra["uhs"].toString();
|
||||||
auto xToken = m_data->mojangservicesToken.token;
|
auto xToken = m_data->mojangservicesToken.token;
|
||||||
|
|
||||||
@ -31,40 +31,37 @@ void LauncherLoginStep::perform()
|
|||||||
)XXX";
|
)XXX";
|
||||||
auto requestBody = mc_auth_template.arg(uhs, xToken);
|
auto requestBody = mc_auth_template.arg(uhs, xToken);
|
||||||
|
|
||||||
QNetworkRequest request = QNetworkRequest(QUrl(requestURL));
|
auto headers = QList<Net::HeaderPair>{
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
{ "Content-Type", "application/json" },
|
||||||
request.setRawHeader("Accept", "application/json");
|
{ "Accept", "application/json" },
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
};
|
||||||
connect(requestor, &AuthRequest::finished, this, &LauncherLoginStep::onRequestDone);
|
|
||||||
requestor->post(request, requestBody.toUtf8());
|
m_response.reset(new QByteArray());
|
||||||
|
m_task = Net::Upload::makeByteArray(url, m_response, requestBody.toUtf8());
|
||||||
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &LauncherLoginStep::onRequestDone);
|
||||||
|
|
||||||
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
qDebug() << "Getting Minecraft access token...";
|
qDebug() << "Getting Minecraft access token...";
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherLoginStep::rehydrate()
|
void LauncherLoginStep::onRequestDone()
|
||||||
{
|
{
|
||||||
// TODO: check the token validity
|
qCDebug(authCredentials()) << *m_response;
|
||||||
}
|
if (m_task->error() != QNetworkReply::NoError) {
|
||||||
|
qWarning() << "Reply error:" << m_task->error();
|
||||||
void LauncherLoginStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
if (Net::isApplicationError(m_task->error())) {
|
||||||
{
|
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(m_task->errorString()));
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
|
||||||
requestor->deleteLater();
|
|
||||||
|
|
||||||
qCDebug(authCredentials()) << data;
|
|
||||||
if (error != QNetworkReply::NoError) {
|
|
||||||
qWarning() << "Reply error:" << error;
|
|
||||||
qCDebug(authCredentials()) << data;
|
|
||||||
if (Net::isApplicationError(error)) {
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_));
|
|
||||||
} else {
|
} else {
|
||||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_));
|
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(m_task->errorString()));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) {
|
if (!Parsers::parseMojangResponse(*m_response, m_data->yggdrasilToken)) {
|
||||||
qWarning() << "Could not parse login_with_xbox response...";
|
qWarning() << "Could not parse login_with_xbox response...";
|
||||||
qCDebug(authCredentials()) << data;
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to parse the Minecraft access token response."));
|
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to parse the Minecraft access token response."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
class LauncherLoginStep : public AuthStep {
|
class LauncherLoginStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit LauncherLoginStep(AccountData* data);
|
explicit LauncherLoginStep(AccountData* data);
|
||||||
virtual ~LauncherLoginStep() noexcept;
|
virtual ~LauncherLoginStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Upload::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
270
launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp
Normal file
270
launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2024 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "MSADeviceCodeStep.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include "Json.h"
|
||||||
|
#include "net/StaticHeaderProxy.h"
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code
|
||||||
|
MSADeviceCodeStep::MSADeviceCodeStep(AccountData* data) : AuthStep(data)
|
||||||
|
{
|
||||||
|
m_clientId = APPLICATION->getMSAClientID();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MSADeviceCodeStep::describe()
|
||||||
|
{
|
||||||
|
return tr("Logging in with Microsoft account(device code).");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSADeviceCodeStep::perform()
|
||||||
|
{
|
||||||
|
QUrlQuery data;
|
||||||
|
data.addQueryItem("client_id", m_clientId);
|
||||||
|
data.addQueryItem("scope", "XboxLive.SignIn XboxLive.offline_access");
|
||||||
|
auto payload = data.query(QUrl::FullyEncoded).toUtf8();
|
||||||
|
QUrl url("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode");
|
||||||
|
auto headers = QList<Net::HeaderPair>{
|
||||||
|
{ "Content-Type", "application/x-www-form-urlencoded" },
|
||||||
|
{ "Accept", "application/json" },
|
||||||
|
};
|
||||||
|
m_response.reset(new QByteArray());
|
||||||
|
m_task = Net::Upload::makeByteArray(url, m_response, payload);
|
||||||
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAutorizationFinished);
|
||||||
|
|
||||||
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeviceAutorizationResponse {
|
||||||
|
QString device_code;
|
||||||
|
QString user_code;
|
||||||
|
QString verification_uri;
|
||||||
|
int expires_in;
|
||||||
|
int interval;
|
||||||
|
|
||||||
|
QString error;
|
||||||
|
QString error_description;
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& data)
|
||||||
|
{
|
||||||
|
QJsonParseError err;
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
|
||||||
|
if (err.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "Failed to parse device autorization response due to err:" << err.errorString();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doc.isObject()) {
|
||||||
|
qWarning() << "Device autorization response is not an object";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto obj = doc.object();
|
||||||
|
return {
|
||||||
|
Json::ensureString(obj, "device_code"), Json::ensureString(obj, "user_code"), Json::ensureString(obj, "verification_uri"),
|
||||||
|
Json::ensureInteger(obj, "expires_in"), Json::ensureInteger(obj, "interval"), Json::ensureString(obj, "error"),
|
||||||
|
Json::ensureString(obj, "error_description"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSADeviceCodeStep::deviceAutorizationFinished()
|
||||||
|
{
|
||||||
|
auto rsp = parseDeviceAutorizationResponse(*m_response);
|
||||||
|
if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) {
|
||||||
|
qWarning() << "Device authorization failed:" << rsp.error;
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD,
|
||||||
|
tr("Device authorization failed: %1").arg(rsp.error_description.isEmpty() ? rsp.error : rsp.error_description));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!m_task->wasSuccessful() || m_task->error() != QNetworkReply::NoError) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Failed to retrieve device authorization"));
|
||||||
|
qDebug() << *m_response;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rsp.device_code.isEmpty() || rsp.user_code.isEmpty() || rsp.verification_uri.isEmpty() || rsp.expires_in == 0) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Device authorization failed: required fields missing"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rsp.interval != 0) {
|
||||||
|
interval = rsp.interval;
|
||||||
|
}
|
||||||
|
m_device_code = rsp.device_code;
|
||||||
|
emit authorizeWithBrowser(rsp.verification_uri, rsp.user_code, rsp.expires_in);
|
||||||
|
m_expiration_timer.setTimerType(Qt::VeryCoarseTimer);
|
||||||
|
m_expiration_timer.setInterval(rsp.expires_in * 1000);
|
||||||
|
m_expiration_timer.setSingleShot(true);
|
||||||
|
connect(&m_expiration_timer, &QTimer::timeout, this, &MSADeviceCodeStep::abort);
|
||||||
|
m_expiration_timer.start();
|
||||||
|
|
||||||
|
m_pool_timer.setTimerType(Qt::VeryCoarseTimer);
|
||||||
|
m_pool_timer.setSingleShot(true);
|
||||||
|
startPoolTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSADeviceCodeStep::abort()
|
||||||
|
{
|
||||||
|
m_expiration_timer.stop();
|
||||||
|
m_pool_timer.stop();
|
||||||
|
if (m_task) {
|
||||||
|
m_task->abort();
|
||||||
|
}
|
||||||
|
m_is_aborted = true;
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Task aborted"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSADeviceCodeStep::startPoolTimer()
|
||||||
|
{
|
||||||
|
if (m_is_aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_pool_timer.setInterval(interval * 1000);
|
||||||
|
connect(&m_pool_timer, &QTimer::timeout, this, &MSADeviceCodeStep::authenticateUser);
|
||||||
|
m_pool_timer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSADeviceCodeStep::authenticateUser()
|
||||||
|
{
|
||||||
|
QUrlQuery data;
|
||||||
|
data.addQueryItem("client_id", m_clientId);
|
||||||
|
data.addQueryItem("grant_type", "urn:ietf:params:oauth:grant-type:device_code");
|
||||||
|
data.addQueryItem("device_code", m_device_code);
|
||||||
|
auto payload = data.query(QUrl::FullyEncoded).toUtf8();
|
||||||
|
QUrl url("https://login.microsoftonline.com/consumers/oauth2/v2.0/token");
|
||||||
|
auto headers = QList<Net::HeaderPair>{
|
||||||
|
{ "Content-Type", "application/x-www-form-urlencoded" },
|
||||||
|
{ "Accept", "application/json" },
|
||||||
|
};
|
||||||
|
m_response.reset(new QByteArray());
|
||||||
|
m_task = Net::Upload::makeByteArray(url, m_response, payload);
|
||||||
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::authenticationFinished);
|
||||||
|
|
||||||
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AuthenticationResponse {
|
||||||
|
QString access_token;
|
||||||
|
QString token_type;
|
||||||
|
QString refresh_token;
|
||||||
|
int expires_in;
|
||||||
|
|
||||||
|
QString error;
|
||||||
|
QString error_description;
|
||||||
|
|
||||||
|
QVariantMap extra;
|
||||||
|
};
|
||||||
|
|
||||||
|
AuthenticationResponse parseAuthenticationResponse(const QByteArray& data)
|
||||||
|
{
|
||||||
|
QJsonParseError err;
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
|
||||||
|
if (err.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "Failed to parse device autorization response due to err:" << err.errorString();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doc.isObject()) {
|
||||||
|
qWarning() << "Device autorization response is not an object";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto obj = doc.object();
|
||||||
|
return { Json::ensureString(obj, "access_token"),
|
||||||
|
Json::ensureString(obj, "token_type"),
|
||||||
|
Json::ensureString(obj, "refresh_token"),
|
||||||
|
Json::ensureInteger(obj, "expires_in"),
|
||||||
|
Json::ensureString(obj, "error"),
|
||||||
|
Json::ensureString(obj, "error_description"),
|
||||||
|
obj.toVariantMap() };
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSADeviceCodeStep::authenticationFinished()
|
||||||
|
{
|
||||||
|
if (m_task->error() == QNetworkReply::TimeoutError) {
|
||||||
|
// rfc8628#section-3.5
|
||||||
|
// "On encountering a connection timeout, clients MUST unilaterally
|
||||||
|
// reduce their polling frequency before retrying. The use of an
|
||||||
|
// exponential backoff algorithm to achieve this, such as doubling the
|
||||||
|
// polling interval on each such connection timeout, is RECOMMENDED."
|
||||||
|
interval *= 2;
|
||||||
|
startPoolTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto rsp = parseAuthenticationResponse(*m_response);
|
||||||
|
if (rsp.error == "slow_down") {
|
||||||
|
// rfc8628#section-3.5
|
||||||
|
// "A variant of 'authorization_pending', the authorization request is
|
||||||
|
// still pending and polling should continue, but the interval MUST
|
||||||
|
// be increased by 5 seconds for this and all subsequent requests."
|
||||||
|
interval += 5;
|
||||||
|
startPoolTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rsp.error == "authorization_pending") {
|
||||||
|
// keep trying - rfc8628#section-3.5
|
||||||
|
// "The authorization request is still pending as the end user hasn't
|
||||||
|
// yet completed the user-interaction steps (Section 3.3)."
|
||||||
|
startPoolTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) {
|
||||||
|
qWarning() << "Device Access failed:" << rsp.error;
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD,
|
||||||
|
tr("Device Access failed: %1").arg(rsp.error_description.isEmpty() ? rsp.error : rsp.error_description));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!m_task->wasSuccessful() || m_task->error() != QNetworkReply::NoError) {
|
||||||
|
startPoolTimer(); // it failed so just try again without increasing the interval
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_expiration_timer.stop();
|
||||||
|
m_data->msaClientID = m_clientId;
|
||||||
|
m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc();
|
||||||
|
m_data->msaToken.notAfter = QDateTime::currentDateTime().addSecs(rsp.expires_in);
|
||||||
|
m_data->msaToken.extra = rsp.extra;
|
||||||
|
m_data->msaToken.refresh_token = rsp.refresh_token;
|
||||||
|
m_data->msaToken.token = rsp.access_token;
|
||||||
|
emit finished(AccountTaskState::STATE_WORKING, tr("Got"));
|
||||||
|
}
|
76
launcher/minecraft/auth/steps/MSADeviceCodeStep.h
Normal file
76
launcher/minecraft/auth/steps/MSADeviceCodeStep.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2024 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
|
class MSADeviceCodeStep : public AuthStep {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit MSADeviceCodeStep(AccountData* data);
|
||||||
|
virtual ~MSADeviceCodeStep() noexcept = default;
|
||||||
|
|
||||||
|
void perform() override;
|
||||||
|
|
||||||
|
QString describe() override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void abort();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void authorizeWithBrowser(QString url, QString code, int expiresIn);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void deviceAutorizationFinished();
|
||||||
|
void startPoolTimer();
|
||||||
|
void authenticateUser();
|
||||||
|
void authenticationFinished();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_clientId;
|
||||||
|
QString m_device_code;
|
||||||
|
bool m_is_aborted = false;
|
||||||
|
int interval = 5;
|
||||||
|
|
||||||
|
QTimer m_pool_timer;
|
||||||
|
QTimer m_expiration_timer;
|
||||||
|
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Upload::Ptr m_task;
|
||||||
|
};
|
@ -35,123 +35,74 @@
|
|||||||
|
|
||||||
#include "MSAStep.h"
|
#include "MSAStep.h"
|
||||||
|
|
||||||
|
#include <QtNetworkAuth/qoauthhttpserverreplyhandler.h>
|
||||||
|
#include <QAbstractOAuth2>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
#include "BuildConfig.h"
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "Logging.h"
|
|
||||||
|
|
||||||
using OAuth2 = Katabasis::DeviceFlow;
|
MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent)
|
||||||
using Activity = Katabasis::Activity;
|
|
||||||
|
|
||||||
MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action)
|
|
||||||
{
|
{
|
||||||
m_clientId = APPLICATION->getMSAClientID();
|
m_clientId = APPLICATION->getMSAClientID();
|
||||||
OAuth2::Options opts;
|
|
||||||
opts.scope = "XboxLive.signin offline_access";
|
|
||||||
opts.clientIdentifier = m_clientId;
|
|
||||||
opts.authorizationUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode";
|
|
||||||
opts.accessTokenUrl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
|
|
||||||
|
|
||||||
// FIXME: OAuth2 is not aware of our fancy shared pointers
|
auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this);
|
||||||
m_oauth2 = new OAuth2(opts, m_data->msaToken, this, APPLICATION->network().get());
|
replyHandler->setCallbackText(
|
||||||
|
" <iframe src=\"https://prismlauncher.org/successful-login\" title=\"PrismLauncher Microsoft login\" style=\"position:fixed; "
|
||||||
|
"top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden; "
|
||||||
|
"z-index:999999;\"/> ");
|
||||||
|
oauth2.setReplyHandler(replyHandler);
|
||||||
|
oauth2.setAuthorizationUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize"));
|
||||||
|
oauth2.setAccessTokenUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/token"));
|
||||||
|
oauth2.setScope("XboxLive.SignIn XboxLive.offline_access");
|
||||||
|
oauth2.setClientIdentifier(m_clientId);
|
||||||
|
oauth2.setNetworkAccessManager(APPLICATION->network().get());
|
||||||
|
|
||||||
connect(m_oauth2, &OAuth2::activityChanged, this, &MSAStep::onOAuthActivityChanged);
|
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::granted, this, [this] {
|
||||||
connect(m_oauth2, &OAuth2::showVerificationUriAndCode, this, &MSAStep::showVerificationUriAndCode);
|
m_data->msaClientID = oauth2.clientIdentifier();
|
||||||
|
m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc();
|
||||||
|
m_data->msaToken.notAfter = oauth2.expirationAt();
|
||||||
|
m_data->msaToken.extra = oauth2.extraTokens();
|
||||||
|
m_data->msaToken.refresh_token = oauth2.refreshToken();
|
||||||
|
m_data->msaToken.token = oauth2.token();
|
||||||
|
emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
|
||||||
|
});
|
||||||
|
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &MSAStep::authorizeWithBrowser);
|
||||||
|
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this](const QAbstractOAuth2::Error err) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication failed."));
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::extraTokensChanged, this,
|
||||||
|
[this](const QVariantMap& tokens) { m_data->msaToken.extra = tokens; });
|
||||||
|
|
||||||
|
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::clientIdentifierChanged, this,
|
||||||
|
[this](const QString& clientIdentifier) { m_data->msaClientID = clientIdentifier; });
|
||||||
}
|
}
|
||||||
|
|
||||||
MSAStep::~MSAStep() noexcept = default;
|
|
||||||
|
|
||||||
QString MSAStep::describe()
|
QString MSAStep::describe()
|
||||||
{
|
{
|
||||||
return tr("Logging in with Microsoft account.");
|
return tr("Logging in with Microsoft account.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void MSAStep::rehydrate()
|
|
||||||
{
|
|
||||||
switch (m_action) {
|
|
||||||
case Refresh: {
|
|
||||||
// TODO: check the tokens and see if they are old (older than a day)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case Login: {
|
|
||||||
// NOOP
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSAStep::perform()
|
void MSAStep::perform()
|
||||||
{
|
{
|
||||||
switch (m_action) {
|
if (m_silent) {
|
||||||
case Refresh: {
|
if (m_data->msaClientID != m_clientId) {
|
||||||
if (m_data->msaClientID != m_clientId) {
|
emit finished(AccountTaskState::STATE_DISABLED,
|
||||||
emit hideVerificationUriAndCode();
|
tr("Microsoft user authentication failed - client identification has changed."));
|
||||||
emit finished(AccountTaskState::STATE_DISABLED,
|
|
||||||
tr("Microsoft user authentication failed - client identification has changed."));
|
|
||||||
}
|
|
||||||
m_oauth2->refresh();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case Login: {
|
oauth2.setRefreshToken(m_data->msaToken.refresh_token);
|
||||||
QVariantMap extraOpts;
|
oauth2.refreshAccessToken();
|
||||||
extraOpts["prompt"] = "select_account";
|
} else {
|
||||||
m_oauth2->setExtraRequestParams(extraOpts);
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0
|
||||||
|
oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* map) {
|
||||||
|
#else
|
||||||
|
oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMap<QString, QVariant>* map) {
|
||||||
|
#endif
|
||||||
|
map->insert("prompt", "select_account");
|
||||||
|
});
|
||||||
|
|
||||||
*m_data = AccountData();
|
*m_data = AccountData();
|
||||||
m_data->msaClientID = m_clientId;
|
m_data->msaClientID = m_clientId;
|
||||||
m_oauth2->login();
|
oauth2.grant();
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity)
|
|
||||||
{
|
|
||||||
switch (activity) {
|
|
||||||
case Katabasis::Activity::Idle:
|
|
||||||
case Katabasis::Activity::LoggingIn:
|
|
||||||
case Katabasis::Activity::Refreshing:
|
|
||||||
case Katabasis::Activity::LoggingOut: {
|
|
||||||
// We asked it to do something, it's doing it. Nothing to act upon.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case Katabasis::Activity::Succeeded: {
|
|
||||||
// Succeeded or did not invalidate tokens
|
|
||||||
emit hideVerificationUriAndCode();
|
|
||||||
QVariantMap extraTokens = m_oauth2->extraTokens();
|
|
||||||
if (!extraTokens.isEmpty()) {
|
|
||||||
qCDebug(authCredentials()) << "Extra tokens in response:";
|
|
||||||
foreach (QString key, extraTokens.keys()) {
|
|
||||||
qCDebug(authCredentials()) << "\t" << key << ":" << extraTokens.value(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case Katabasis::Activity::FailedSoft: {
|
|
||||||
// NOTE: soft error in the first step means 'offline'
|
|
||||||
emit hideVerificationUriAndCode();
|
|
||||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("Microsoft user authentication ended with a network error."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case Katabasis::Activity::FailedGone: {
|
|
||||||
emit hideVerificationUriAndCode();
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_GONE, tr("Microsoft user authentication failed - user no longer exists."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case Katabasis::Activity::FailedHard: {
|
|
||||||
emit hideVerificationUriAndCode();
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication failed."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
emit hideVerificationUriAndCode();
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication completed with an unrecognized result."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,30 +36,24 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
|
||||||
#include <katabasis/DeviceFlow.h>
|
#include <QtNetworkAuth/qoauth2authorizationcodeflow.h>
|
||||||
|
|
||||||
class MSAStep : public AuthStep {
|
class MSAStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum Action { Refresh, Login };
|
explicit MSAStep(AccountData* data, bool silent = false);
|
||||||
|
virtual ~MSAStep() noexcept = default;
|
||||||
public:
|
|
||||||
explicit MSAStep(AccountData* data, Action action);
|
|
||||||
virtual ~MSAStep() noexcept;
|
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
signals:
|
||||||
void onOAuthActivityChanged(Katabasis::Activity activity);
|
void authorizeWithBrowser(const QUrl& url);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Katabasis::DeviceFlow* m_oauth2 = nullptr;
|
bool m_silent;
|
||||||
Action m_action;
|
|
||||||
QString m_clientId;
|
QString m_clientId;
|
||||||
|
QOAuth2AuthorizationCodeFlow oauth2;
|
||||||
};
|
};
|
||||||
|
@ -2,15 +2,13 @@
|
|||||||
|
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
#include "Logging.h"
|
#include "Application.h"
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
#include "net/NetUtils.h"
|
#include "net/NetUtils.h"
|
||||||
|
#include "net/StaticHeaderProxy.h"
|
||||||
|
|
||||||
MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {}
|
MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {}
|
||||||
|
|
||||||
MinecraftProfileStep::~MinecraftProfileStep() noexcept = default;
|
|
||||||
|
|
||||||
QString MinecraftProfileStep::describe()
|
QString MinecraftProfileStep::describe()
|
||||||
{
|
{
|
||||||
return tr("Fetching the Minecraft profile.");
|
return tr("Fetching the Minecraft profile.");
|
||||||
@ -18,52 +16,47 @@ QString MinecraftProfileStep::describe()
|
|||||||
|
|
||||||
void MinecraftProfileStep::perform()
|
void MinecraftProfileStep::perform()
|
||||||
{
|
{
|
||||||
auto url = QUrl("https://api.minecraftservices.com/minecraft/profile");
|
QUrl url("https://api.minecraftservices.com/minecraft/profile");
|
||||||
QNetworkRequest request = QNetworkRequest(url);
|
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
{ "Accept", "application/json" },
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
|
{ "Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8() } };
|
||||||
|
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
m_response.reset(new QByteArray());
|
||||||
connect(requestor, &AuthRequest::finished, this, &MinecraftProfileStep::onRequestDone);
|
m_task = Net::Download::makeByteArray(url, m_response);
|
||||||
requestor->get(request);
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &MinecraftProfileStep::onRequestDone);
|
||||||
|
|
||||||
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MinecraftProfileStep::rehydrate()
|
void MinecraftProfileStep::onRequestDone()
|
||||||
{
|
{
|
||||||
// NOOP, for now. We only save bools and there's nothing to check.
|
if (m_task->error() == QNetworkReply::ContentNotFoundError) {
|
||||||
}
|
|
||||||
|
|
||||||
void MinecraftProfileStep::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.
|
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
|
||||||
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;
|
||||||
}
|
}
|
||||||
if (error != QNetworkReply::NoError) {
|
if (m_task->error() != QNetworkReply::NoError) {
|
||||||
qWarning() << "Error getting profile:";
|
qWarning() << "Error getting profile:";
|
||||||
qWarning() << " HTTP Status: " << requestor->httpStatus_;
|
qWarning() << " HTTP Status: " << m_task->replyStatusCode();
|
||||||
qWarning() << " Internal error no.: " << error;
|
qWarning() << " Internal error no.: " << m_task->error();
|
||||||
qWarning() << " Error string: " << requestor->errorString_;
|
qWarning() << " Error string: " << m_task->errorString();
|
||||||
|
|
||||||
qWarning() << " Response:";
|
qWarning() << " Response:";
|
||||||
qWarning() << QString::fromUtf8(data);
|
qWarning() << QString::fromUtf8(*m_response);
|
||||||
|
|
||||||
if (Net::isApplicationError(error)) {
|
if (Net::isApplicationError(m_task->error())) {
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
tr("Minecraft Java profile acquisition failed: %1").arg(m_task->errorString()));
|
||||||
} else {
|
} else {
|
||||||
emit finished(AccountTaskState::STATE_OFFLINE,
|
emit finished(AccountTaskState::STATE_OFFLINE, tr("Minecraft Java profile acquisition failed: %1").arg(m_task->errorString()));
|
||||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) {
|
if (!Parsers::parseMinecraftProfile(*m_response, m_data->minecraftProfile)) {
|
||||||
m_data->minecraftProfile = MinecraftProfile();
|
m_data->minecraftProfile = MinecraftProfile();
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed"));
|
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed"));
|
||||||
return;
|
return;
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Download.h"
|
||||||
|
|
||||||
class MinecraftProfileStep : public AuthStep {
|
class MinecraftProfileStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MinecraftProfileStep(AccountData* data);
|
explicit MinecraftProfileStep(AccountData* data);
|
||||||
virtual ~MinecraftProfileStep() noexcept;
|
virtual ~MinecraftProfileStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Download::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
#include "OfflineStep.h"
|
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
|
|
||||||
OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {}
|
|
||||||
OfflineStep::~OfflineStep() noexcept = default;
|
|
||||||
|
|
||||||
QString OfflineStep::describe()
|
|
||||||
{
|
|
||||||
return tr("Creating offline account.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void OfflineStep::rehydrate()
|
|
||||||
{
|
|
||||||
// NOOP
|
|
||||||
}
|
|
||||||
|
|
||||||
void OfflineStep::perform()
|
|
||||||
{
|
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Created offline account."));
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
|
||||||
|
|
||||||
#include <katabasis/DeviceFlow.h>
|
|
||||||
|
|
||||||
class OfflineStep : public AuthStep {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit OfflineStep(AccountData* data);
|
|
||||||
virtual ~OfflineStep() noexcept;
|
|
||||||
|
|
||||||
void perform() override;
|
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
|
||||||
};
|
|
@ -4,27 +4,22 @@
|
|||||||
#include <QJsonParseError>
|
#include <QJsonParseError>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
#include "net/NetUtils.h"
|
#include "net/NetUtils.h"
|
||||||
|
#include "net/StaticHeaderProxy.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind)
|
XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind)
|
||||||
: AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind)
|
: AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
XboxAuthorizationStep::~XboxAuthorizationStep() noexcept = default;
|
|
||||||
|
|
||||||
QString XboxAuthorizationStep::describe()
|
QString XboxAuthorizationStep::describe()
|
||||||
{
|
{
|
||||||
return tr("Getting authorization to access %1 services.").arg(m_authorizationKind);
|
return tr("Getting authorization to access %1 services.").arg(m_authorizationKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
void XboxAuthorizationStep::rehydrate()
|
|
||||||
{
|
|
||||||
// FIXME: check if the tokens are good?
|
|
||||||
}
|
|
||||||
|
|
||||||
void XboxAuthorizationStep::perform()
|
void XboxAuthorizationStep::perform()
|
||||||
{
|
{
|
||||||
QString xbox_auth_template = R"XXX(
|
QString xbox_auth_template = R"XXX(
|
||||||
@ -41,40 +36,44 @@ void XboxAuthorizationStep::perform()
|
|||||||
)XXX";
|
)XXX";
|
||||||
auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty);
|
auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty);
|
||||||
// http://xboxlive.com
|
// http://xboxlive.com
|
||||||
QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize"));
|
QUrl url("https://xsts.auth.xboxlive.com/xsts/authorize");
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
auto headers = QList<Net::HeaderPair>{
|
||||||
request.setRawHeader("Accept", "application/json");
|
{ "Content-Type", "application/json" },
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
{ "Accept", "application/json" },
|
||||||
connect(requestor, &AuthRequest::finished, this, &XboxAuthorizationStep::onRequestDone);
|
};
|
||||||
requestor->post(request, xbox_auth_data.toUtf8());
|
m_response.reset(new QByteArray());
|
||||||
|
m_task = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8());
|
||||||
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &XboxAuthorizationStep::onRequestDone);
|
||||||
|
|
||||||
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
qDebug() << "Getting authorization token for " << m_relyingParty;
|
qDebug() << "Getting authorization token for " << m_relyingParty;
|
||||||
}
|
}
|
||||||
|
|
||||||
void XboxAuthorizationStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
void XboxAuthorizationStep::onRequestDone()
|
||||||
{
|
{
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
qCDebug(authCredentials()) << *m_response;
|
||||||
requestor->deleteLater();
|
if (m_task->error() != QNetworkReply::NoError) {
|
||||||
|
qWarning() << "Reply error:" << m_task->error();
|
||||||
qCDebug(authCredentials()) << data;
|
if (Net::isApplicationError(m_task->error())) {
|
||||||
if (error != QNetworkReply::NoError) {
|
if (!processSTSError()) {
|
||||||
qWarning() << "Reply error:" << error;
|
|
||||||
if (Net::isApplicationError(error)) {
|
|
||||||
if (!processSTSError(error, data, headers)) {
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, error));
|
tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, m_task->error()));
|
||||||
} else {
|
} else {
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, requestor->errorString_));
|
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_task->errorString()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
emit finished(AccountTaskState::STATE_OFFLINE,
|
emit finished(AccountTaskState::STATE_OFFLINE,
|
||||||
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, requestor->errorString_));
|
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_task->errorString()));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Katabasis::Token temp;
|
Token temp;
|
||||||
if (!Parsers::parseXTokenResponse(data, temp, m_authorizationKind)) {
|
if (!Parsers::parseXTokenResponse(*m_response, temp, m_authorizationKind)) {
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind));
|
tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind));
|
||||||
return;
|
return;
|
||||||
@ -91,11 +90,11 @@ void XboxAuthorizationStep::onRequestDone(QNetworkReply::NetworkError error, QBy
|
|||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got authorization to access %1").arg(m_relyingParty));
|
emit finished(AccountTaskState::STATE_WORKING, tr("Got authorization to access %1").arg(m_relyingParty));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool XboxAuthorizationStep::processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
bool XboxAuthorizationStep::processSTSError()
|
||||||
{
|
{
|
||||||
if (error == QNetworkReply::AuthenticationRequiredError) {
|
if (m_task->error() == QNetworkReply::AuthenticationRequiredError) {
|
||||||
QJsonParseError jsonError;
|
QJsonParseError jsonError;
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
QJsonDocument doc = QJsonDocument::fromJson(*m_response, &jsonError);
|
||||||
if (jsonError.error) {
|
if (jsonError.error) {
|
||||||
qWarning() << "Cannot parse error XSTS response as JSON: " << jsonError.errorString();
|
qWarning() << "Cannot parse error XSTS response as JSON: " << jsonError.errorString();
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
|
@ -1,29 +1,32 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
class XboxAuthorizationStep : public AuthStep {
|
class XboxAuthorizationStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind);
|
explicit XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind);
|
||||||
virtual ~XboxAuthorizationStep() noexcept;
|
virtual ~XboxAuthorizationStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
|
bool processSTSError();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Katabasis::Token* m_token;
|
Token* m_token;
|
||||||
QString m_relyingParty;
|
QString m_relyingParty;
|
||||||
QString m_authorizationKind;
|
QString m_authorizationKind;
|
||||||
|
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Upload::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
@ -3,28 +3,21 @@
|
|||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
#include <QUrlQuery>
|
#include <QUrlQuery>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
|
||||||
#include "net/NetUtils.h"
|
#include "net/NetUtils.h"
|
||||||
|
#include "net/StaticHeaderProxy.h"
|
||||||
|
|
||||||
XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {}
|
XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {}
|
||||||
|
|
||||||
XboxProfileStep::~XboxProfileStep() noexcept = default;
|
|
||||||
|
|
||||||
QString XboxProfileStep::describe()
|
QString XboxProfileStep::describe()
|
||||||
{
|
{
|
||||||
return tr("Fetching Xbox profile.");
|
return tr("Fetching Xbox profile.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void XboxProfileStep::rehydrate()
|
|
||||||
{
|
|
||||||
// NOOP, for now. We only save bools and there's nothing to check.
|
|
||||||
}
|
|
||||||
|
|
||||||
void XboxProfileStep::perform()
|
void XboxProfileStep::perform()
|
||||||
{
|
{
|
||||||
auto url = QUrl("https://profile.xboxlive.com/users/me/profile/settings");
|
QUrl url("https://profile.xboxlive.com/users/me/profile/settings");
|
||||||
QUrlQuery q;
|
QUrlQuery q;
|
||||||
q.addQueryItem("settings",
|
q.addQueryItem("settings",
|
||||||
"GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw,"
|
"GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw,"
|
||||||
@ -33,36 +26,38 @@ void XboxProfileStep::perform()
|
|||||||
"PreferredColor,Location,Bio,Watermarks,"
|
"PreferredColor,Location,Bio,Watermarks,"
|
||||||
"RealName,RealNameOverride,IsQuarantined");
|
"RealName,RealNameOverride,IsQuarantined");
|
||||||
url.setQuery(q);
|
url.setQuery(q);
|
||||||
|
auto headers = QList<Net::HeaderPair>{
|
||||||
|
{ "Content-Type", "application/json" },
|
||||||
|
{ "Accept", "application/json" },
|
||||||
|
{ "x-xbl-contract-version", "3" },
|
||||||
|
{ "Authorization", QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8() }
|
||||||
|
};
|
||||||
|
|
||||||
QNetworkRequest request = QNetworkRequest(url);
|
m_response.reset(new QByteArray());
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
m_task = Net::Download::makeByteArray(url, m_response);
|
||||||
request.setRawHeader("Accept", "application/json");
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
request.setRawHeader("x-xbl-contract-version", "3");
|
|
||||||
request.setRawHeader("Authorization",
|
connect(m_task.get(), &Task::finished, this, &XboxProfileStep::onRequestDone);
|
||||||
QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8());
|
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
m_task->setNetwork(APPLICATION->network());
|
||||||
connect(requestor, &AuthRequest::finished, this, &XboxProfileStep::onRequestDone);
|
m_task->start();
|
||||||
requestor->get(request);
|
|
||||||
qDebug() << "Getting Xbox profile...";
|
qDebug() << "Getting Xbox profile...";
|
||||||
}
|
}
|
||||||
|
|
||||||
void XboxProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
void XboxProfileStep::onRequestDone()
|
||||||
{
|
{
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
if (m_task->error() != QNetworkReply::NoError) {
|
||||||
requestor->deleteLater();
|
qWarning() << "Reply error:" << m_task->error();
|
||||||
|
qCDebug(authCredentials()) << *m_response;
|
||||||
if (error != QNetworkReply::NoError) {
|
if (Net::isApplicationError(m_task->error())) {
|
||||||
qWarning() << "Reply error:" << error;
|
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to retrieve the Xbox profile: %1").arg(m_task->errorString()));
|
||||||
qCDebug(authCredentials()) << data;
|
|
||||||
if (Net::isApplicationError(error)) {
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_));
|
|
||||||
} else {
|
} else {
|
||||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_));
|
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to retrieve the Xbox profile: %1").arg(m_task->errorString()));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(authCredentials()) << "XBox profile: " << data;
|
qCDebug(authCredentials()) << "XBox profile: " << *m_response;
|
||||||
|
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile"));
|
emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile"));
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Download.h"
|
||||||
|
|
||||||
class XboxProfileStep : public AuthStep {
|
class XboxProfileStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit XboxProfileStep(AccountData* data);
|
explicit XboxProfileStep(AccountData* data);
|
||||||
virtual ~XboxProfileStep() noexcept;
|
virtual ~XboxProfileStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Download::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
@ -2,24 +2,18 @@
|
|||||||
|
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
#include "Application.h"
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
#include "net/NetUtils.h"
|
#include "net/NetUtils.h"
|
||||||
|
#include "net/StaticHeaderProxy.h"
|
||||||
|
|
||||||
XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {}
|
XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {}
|
||||||
|
|
||||||
XboxUserStep::~XboxUserStep() noexcept = default;
|
|
||||||
|
|
||||||
QString XboxUserStep::describe()
|
QString XboxUserStep::describe()
|
||||||
{
|
{
|
||||||
return tr("Logging in as an Xbox user.");
|
return tr("Logging in as an Xbox user.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void XboxUserStep::rehydrate()
|
|
||||||
{
|
|
||||||
// NOOP, for now. We only save bools and there's nothing to check.
|
|
||||||
}
|
|
||||||
|
|
||||||
void XboxUserStep::perform()
|
void XboxUserStep::perform()
|
||||||
{
|
{
|
||||||
QString xbox_auth_template = R"XXX(
|
QString xbox_auth_template = R"XXX(
|
||||||
@ -35,36 +29,39 @@ void XboxUserStep::perform()
|
|||||||
)XXX";
|
)XXX";
|
||||||
auto xbox_auth_data = xbox_auth_template.arg(m_data->msaToken.token);
|
auto xbox_auth_data = xbox_auth_template.arg(m_data->msaToken.token);
|
||||||
|
|
||||||
QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate"));
|
QUrl url("https://user.auth.xboxlive.com/user/authenticate");
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
auto headers = QList<Net::HeaderPair>{
|
||||||
request.setRawHeader("Accept", "application/json");
|
{ "Content-Type", "application/json" },
|
||||||
// set contract-version header (prevent err 400 bad-request?)
|
{ "Accept", "application/json" },
|
||||||
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders
|
// set contract-version header (prevent err 400 bad-request?)
|
||||||
request.setRawHeader("x-xbl-contract-version", "1");
|
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders
|
||||||
|
{ "x-xbl-contract-version", "1" }
|
||||||
|
};
|
||||||
|
m_response.reset(new QByteArray());
|
||||||
|
m_task = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8());
|
||||||
|
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
auto* requestor = new AuthRequest(this);
|
connect(m_task.get(), &Task::finished, this, &XboxUserStep::onRequestDone);
|
||||||
connect(requestor, &AuthRequest::finished, this, &XboxUserStep::onRequestDone);
|
|
||||||
requestor->post(request, xbox_auth_data.toUtf8());
|
m_task->setNetwork(APPLICATION->network());
|
||||||
|
m_task->start();
|
||||||
qDebug() << "First layer of XBox auth ... commencing.";
|
qDebug() << "First layer of XBox auth ... commencing.";
|
||||||
}
|
}
|
||||||
|
|
||||||
void XboxUserStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
void XboxUserStep::onRequestDone()
|
||||||
{
|
{
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
if (m_task->error() != QNetworkReply::NoError) {
|
||||||
requestor->deleteLater();
|
qWarning() << "Reply error:" << m_task->error();
|
||||||
|
if (Net::isApplicationError(m_task->error())) {
|
||||||
if (error != QNetworkReply::NoError) {
|
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed: %1").arg(m_task->errorString()));
|
||||||
qWarning() << "Reply error:" << error;
|
|
||||||
if (Net::isApplicationError(error)) {
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed: %1").arg(requestor->errorString_));
|
|
||||||
} else {
|
} else {
|
||||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("XBox user authentication failed: %1").arg(requestor->errorString_));
|
emit finished(AccountTaskState::STATE_OFFLINE, tr("XBox user authentication failed: %1").arg(m_task->errorString()));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Katabasis::Token temp;
|
Token temp;
|
||||||
if (!Parsers::parseXTokenResponse(data, temp, "UToken")) {
|
if (!Parsers::parseXTokenResponse(*m_response, temp, "UToken")) {
|
||||||
qWarning() << "Could not parse user authentication response...";
|
qWarning() << "Could not parse user authentication response...";
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication response could not be understood."));
|
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication response could not be understood."));
|
||||||
return;
|
return;
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
class XboxUserStep : public AuthStep {
|
class XboxUserStep : public AuthStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit XboxUserStep(AccountData* data);
|
explicit XboxUserStep(AccountData* data);
|
||||||
virtual ~XboxUserStep() noexcept;
|
virtual ~XboxUserStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
void onRequestDone();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Upload::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
@ -274,7 +274,7 @@ QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
|
|||||||
return {};
|
return {};
|
||||||
|
|
||||||
if (m_pack_image_cache_key.was_ever_used) {
|
if (m_pack_image_cache_key.was_ever_used) {
|
||||||
qDebug() << "Mod" << name() << "Had it's icon evicted form the cache. reloading...";
|
qDebug() << "Mod" << name() << "Had it's icon evicted from the cache. reloading...";
|
||||||
PixmapCache::markCacheMissByEviciton();
|
PixmapCache::markCacheMissByEviciton();
|
||||||
}
|
}
|
||||||
// Image got evicted from the cache or an attempt to load it has not been made. load it and retry.
|
// Image got evicted from the cache or an attempt to load it has not been made. load it and retry.
|
||||||
|
@ -284,7 +284,12 @@ void ResourceFolderModel::resolveResource(Resource* res)
|
|||||||
connect(
|
connect(
|
||||||
task.get(), &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
task.get(), &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
||||||
connect(
|
connect(
|
||||||
task.get(), &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection);
|
task.get(), &Task::finished, this,
|
||||||
|
[=] {
|
||||||
|
m_active_parse_tasks.remove(ticket);
|
||||||
|
emit parseFinished();
|
||||||
|
},
|
||||||
|
Qt::ConnectionType::QueuedConnection);
|
||||||
|
|
||||||
m_helper_thread_task.addTask(task);
|
m_helper_thread_task.addTask(task);
|
||||||
|
|
||||||
@ -617,3 +622,26 @@ QString ResourceFolderModel::instDirPath() const
|
|||||||
{
|
{
|
||||||
return QFileInfo(m_instance->instanceRoot()).absoluteFilePath();
|
return QFileInfo(m_instance->instanceRoot()).absoluteFilePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ResourceFolderModel::onParseFailed(int ticket, QString resource_id)
|
||||||
|
{
|
||||||
|
auto iter = m_active_parse_tasks.constFind(ticket);
|
||||||
|
if (iter == m_active_parse_tasks.constEnd())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto removed_index = m_resources_index[resource_id];
|
||||||
|
auto removed_it = m_resources.begin() + removed_index;
|
||||||
|
Q_ASSERT(removed_it != m_resources.end());
|
||||||
|
|
||||||
|
beginRemoveRows(QModelIndex(), removed_index, removed_index);
|
||||||
|
m_resources.erase(removed_it);
|
||||||
|
|
||||||
|
// update index
|
||||||
|
m_resources_index.clear();
|
||||||
|
int idx = 0;
|
||||||
|
for (auto const& mod : qAsConst(m_resources)) {
|
||||||
|
m_resources_index[mod->internal_id()] = idx;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
@ -143,6 +143,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void updateFinished();
|
void updateFinished();
|
||||||
|
void parseFinished();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/** This creates a new update task to be executed by update().
|
/** This creates a new update task to be executed by update().
|
||||||
@ -189,11 +190,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||||||
* if the resource is complex and has more stuff to parse.
|
* if the resource is complex and has more stuff to parse.
|
||||||
*/
|
*/
|
||||||
virtual void onParseSucceeded(int ticket, QString resource_id);
|
virtual void onParseSucceeded(int ticket, QString resource_id);
|
||||||
virtual void onParseFailed(int ticket, QString resource_id)
|
virtual void onParseFailed(int ticket, QString resource_id);
|
||||||
{
|
|
||||||
Q_UNUSED(ticket);
|
|
||||||
Q_UNUSED(resource_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
|
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
|
||||||
|
@ -35,8 +35,3 @@ bool ShaderPack::valid() const
|
|||||||
{
|
{
|
||||||
return m_pack_format != ShaderPackFormat::INVALID;
|
return m_pack_format != ShaderPackFormat::INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderPack::applyFilter(QRegularExpression filter) const
|
|
||||||
{
|
|
||||||
return valid() && Resource::applyFilter(filter);
|
|
||||||
}
|
|
||||||
|
@ -54,7 +54,6 @@ class ShaderPack : public Resource {
|
|||||||
void setPackFormat(ShaderPackFormat new_format);
|
void setPackFormat(ShaderPackFormat new_format);
|
||||||
|
|
||||||
bool valid() const override;
|
bool valid() const override;
|
||||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
mutable QMutex m_data_lock;
|
mutable QMutex m_data_lock;
|
||||||
|
@ -746,7 +746,7 @@ void LocalModParseTask::executeTask()
|
|||||||
m_result->details = mod.details();
|
m_result->details = mod.details();
|
||||||
|
|
||||||
if (m_aborted)
|
if (m_aborted)
|
||||||
emit finished();
|
emitAborted();
|
||||||
else
|
else
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
@ -286,8 +286,10 @@ bool LocalResourcePackParseTask::abort()
|
|||||||
|
|
||||||
void LocalResourcePackParseTask::executeTask()
|
void LocalResourcePackParseTask::executeTask()
|
||||||
{
|
{
|
||||||
if (!ResourcePackUtils::process(m_resource_pack))
|
if (!ResourcePackUtils::process(m_resource_pack)) {
|
||||||
|
emitFailed("this is not a resource pack");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_aborted)
|
if (m_aborted)
|
||||||
emitAborted();
|
emitAborted();
|
||||||
|
@ -103,8 +103,10 @@ bool LocalShaderPackParseTask::abort()
|
|||||||
|
|
||||||
void LocalShaderPackParseTask::executeTask()
|
void LocalShaderPackParseTask::executeTask()
|
||||||
{
|
{
|
||||||
if (!ShaderPackUtils::process(m_shader_pack))
|
if (!ShaderPackUtils::process(m_shader_pack)) {
|
||||||
|
emitFailed("this is not a shader pack");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_aborted)
|
if (m_aborted)
|
||||||
emitAborted();
|
emitAborted();
|
||||||
|
@ -241,8 +241,10 @@ bool LocalTexturePackParseTask::abort()
|
|||||||
|
|
||||||
void LocalTexturePackParseTask::executeTask()
|
void LocalTexturePackParseTask::executeTask()
|
||||||
{
|
{
|
||||||
if (!TexturePackUtils::process(m_texture_pack))
|
if (!TexturePackUtils::process(m_texture_pack)) {
|
||||||
|
emitFailed("this is not a texture pack");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_aborted)
|
if (m_aborted)
|
||||||
emitAborted();
|
emitAborted();
|
||||||
|
@ -1031,6 +1031,12 @@ void PackInstallTask::install()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
components->setComponentVersion("net.minecraftforge", version);
|
components->setComponentVersion("net.minecraftforge", version);
|
||||||
|
} else if (m_version.loader.type == QString("neoforge")) {
|
||||||
|
auto version = getVersionForLoader("net.neoforged");
|
||||||
|
if (version == Q_NULLPTR)
|
||||||
|
return;
|
||||||
|
|
||||||
|
components->setComponentVersion("net.neoforged", version);
|
||||||
} else if (m_version.loader.type == QString("fabric")) {
|
} else if (m_version.loader.type == QString("fabric")) {
|
||||||
auto version = getVersionForLoader("net.fabricmc.fabric-loader");
|
auto version = getVersionForLoader("net.fabricmc.fabric-loader");
|
||||||
if (version == Q_NULLPTR)
|
if (version == Q_NULLPTR)
|
||||||
|
@ -537,7 +537,12 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
|||||||
selectedOptionalMods = optionalModDialog.getResult();
|
selectedOptionalMods = optionalModDialog.getResult();
|
||||||
}
|
}
|
||||||
for (const auto& result : results) {
|
for (const auto& result : results) {
|
||||||
auto relpath = FS::PathCombine(result.targetFolder, result.fileName);
|
auto fileName = result.fileName;
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
fileName = FS::RemoveInvalidPathChars(fileName);
|
||||||
|
#endif
|
||||||
|
auto relpath = FS::PathCombine(result.targetFolder, fileName);
|
||||||
|
|
||||||
if (!result.required && !selectedOptionalMods.contains(relpath)) {
|
if (!result.required && !selectedOptionalMods.contains(relpath)) {
|
||||||
relpath += ".disabled";
|
relpath += ".disabled";
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "FlameModIndex.h"
|
#include "FlameModIndex.h"
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
#include "minecraft/PackProfile.h"
|
#include "minecraft/PackProfile.h"
|
||||||
@ -138,6 +139,9 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
|
|||||||
file.version = Json::requireString(obj, "displayName");
|
file.version = Json::requireString(obj, "displayName");
|
||||||
file.downloadUrl = Json::ensureString(obj, "downloadUrl");
|
file.downloadUrl = Json::ensureString(obj, "downloadUrl");
|
||||||
file.fileName = Json::requireString(obj, "fileName");
|
file.fileName = Json::requireString(obj, "fileName");
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
file.fileName = FS::RemoveInvalidPathChars(file.fileName);
|
||||||
|
#endif
|
||||||
|
|
||||||
ModPlatform::IndexedVersionType::VersionType ver_type;
|
ModPlatform::IndexedVersionType::VersionType ver_type;
|
||||||
switch (Json::requireInteger(obj, "releaseType")) {
|
switch (Json::requireInteger(obj, "releaseType")) {
|
||||||
|
@ -201,7 +201,7 @@ void FlamePackExportTask::makeApiRequest()
|
|||||||
<< " reason: " << parseError.errorString();
|
<< " reason: " << parseError.errorString();
|
||||||
qWarning() << *response;
|
qWarning() << *response;
|
||||||
|
|
||||||
failed(parseError.errorString());
|
emitFailed(parseError.errorString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,6 +213,7 @@ void FlamePackExportTask::makeApiRequest()
|
|||||||
if (dataArr.isEmpty()) {
|
if (dataArr.isEmpty()) {
|
||||||
qWarning() << "No matches found for fingerprint search!";
|
qWarning() << "No matches found for fingerprint search!";
|
||||||
|
|
||||||
|
getProjectsInfo();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (auto match : dataArr) {
|
for (auto match : dataArr) {
|
||||||
@ -243,9 +244,9 @@ void FlamePackExportTask::makeApiRequest()
|
|||||||
qDebug() << doc;
|
qDebug() << doc;
|
||||||
}
|
}
|
||||||
pendingHashes.clear();
|
pendingHashes.clear();
|
||||||
|
getProjectsInfo();
|
||||||
});
|
});
|
||||||
connect(task.get(), &Task::finished, this, &FlamePackExportTask::getProjectsInfo);
|
connect(task.get(), &NetJob::failed, this, &FlamePackExportTask::getProjectsInfo);
|
||||||
connect(task.get(), &NetJob::failed, this, &FlamePackExportTask::emitFailed);
|
|
||||||
task->start();
|
task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,7 +280,7 @@ void FlamePackExportTask::getProjectsInfo()
|
|||||||
qWarning() << "Error while parsing JSON response from CurseForge projects task at " << parseError.offset
|
qWarning() << "Error while parsing JSON response from CurseForge projects task at " << parseError.offset
|
||||||
<< " reason: " << parseError.errorString();
|
<< " reason: " << parseError.errorString();
|
||||||
qWarning() << *response;
|
qWarning() << *response;
|
||||||
failed(parseError.errorString());
|
emitFailed(parseError.errorString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,7 +334,7 @@ void FlamePackExportTask::buildZip()
|
|||||||
setStatus(tr("Adding files..."));
|
setStatus(tr("Adding files..."));
|
||||||
setProgress(4, 5);
|
setProgress(4, 5);
|
||||||
|
|
||||||
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
|
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true, false);
|
||||||
zipTask->addExtraFile("manifest.json", generateIndex());
|
zipTask->addExtraFile("manifest.json", generateIndex());
|
||||||
zipTask->addExtraFile("modlist.html", generateHTML());
|
zipTask->addExtraFile("modlist.html", generateHTML());
|
||||||
|
|
||||||
|
@ -45,8 +45,8 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
|
|||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
||||||
int network_error_code = -1;
|
int network_error_code = -1;
|
||||||
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action)
|
||||||
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
network_error_code = failed_action->replyStatusCode();
|
||||||
|
|
||||||
callbacks.on_fail(reason, network_error_code);
|
callbacks.on_fail(reason, network_error_code);
|
||||||
});
|
});
|
||||||
@ -104,8 +104,8 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
|
|||||||
});
|
});
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
||||||
int network_error_code = -1;
|
int network_error_code = -1;
|
||||||
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action)
|
||||||
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
network_error_code = failed_action->replyStatusCode();
|
||||||
|
|
||||||
callbacks.on_fail(reason, network_error_code);
|
callbacks.on_fail(reason, network_error_code);
|
||||||
});
|
});
|
||||||
@ -155,8 +155,8 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
|
|||||||
});
|
});
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
||||||
int network_error_code = -1;
|
int network_error_code = -1;
|
||||||
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action)
|
||||||
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
network_error_code = failed_action->replyStatusCode();
|
||||||
|
|
||||||
callbacks.on_fail(reason, network_error_code);
|
callbacks.on_fail(reason, network_error_code);
|
||||||
});
|
});
|
||||||
|
@ -240,11 +240,15 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
|
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
|
||||||
|
|
||||||
for (auto file : m_files) {
|
for (auto file : m_files) {
|
||||||
auto file_path = FS::PathCombine(root_modpack_path, file.path);
|
auto fileName = file.path;
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
fileName = FS::RemoveInvalidPathChars(fileName);
|
||||||
|
#endif
|
||||||
|
auto file_path = FS::PathCombine(root_modpack_path, fileName);
|
||||||
if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) {
|
if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) {
|
||||||
// This means we somehow got out of the root folder, so abort here to prevent exploits
|
// This means we somehow got out of the root folder, so abort here to prevent exploits
|
||||||
setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.")
|
setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.")
|
||||||
.arg(file.path));
|
.arg(fileName));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +261,7 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
// FIXME: This really needs to be put into a ConcurrentTask of
|
// FIXME: This really needs to be put into a ConcurrentTask of
|
||||||
// MultipleOptionsTask's , once those exist :)
|
// MultipleOptionsTask's , once those exist :)
|
||||||
auto param = dl.toWeakRef();
|
auto param = dl.toWeakRef();
|
||||||
connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] {
|
connect(dl.get(), &Task::failed, [this, &file, file_path, param] {
|
||||||
auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
|
auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
|
||||||
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
||||||
m_files_job->addNetAction(ndl);
|
m_files_job->addNetAction(ndl);
|
||||||
|
@ -200,7 +200,7 @@ void ModrinthPackExportTask::buildZip()
|
|||||||
{
|
{
|
||||||
setStatus(tr("Adding files..."));
|
setStatus(tr("Adding files..."));
|
||||||
|
|
||||||
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
|
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true, true);
|
||||||
zipTask->addExtraFile("modrinth.index.json", generateIndex());
|
zipTask->addExtraFile("modrinth.index.json", generateIndex());
|
||||||
|
|
||||||
zipTask->setExcludeFiles(resolvedFiles.keys());
|
zipTask->setExcludeFiles(resolvedFiles.keys());
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ModrinthPackIndex.h"
|
#include "ModrinthPackIndex.h"
|
||||||
|
#include "FileSystem.h"
|
||||||
#include "ModrinthAPI.h"
|
#include "ModrinthAPI.h"
|
||||||
|
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
@ -226,6 +227,9 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
|
|||||||
if (parent.contains("url")) {
|
if (parent.contains("url")) {
|
||||||
file.downloadUrl = Json::requireString(parent, "url");
|
file.downloadUrl = Json::requireString(parent, "url");
|
||||||
file.fileName = Json::requireString(parent, "filename");
|
file.fileName = Json::requireString(parent, "filename");
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
file.fileName = FS::RemoveInvalidPathChars(file.fileName);
|
||||||
|
#endif
|
||||||
file.is_preferred = Json::requireBoolean(parent, "primary") || (files.count() == 1);
|
file.is_preferred = Json::requireBoolean(parent, "primary") || (files.count() == 1);
|
||||||
auto hash_list = Json::requireObject(parent, "hashes");
|
auto hash_list = Json::requireObject(parent, "hashes");
|
||||||
|
|
||||||
|
@ -155,8 +155,26 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings,
|
|||||||
auto libraryObject = Json::ensureObject(library, {}, "");
|
auto libraryObject = Json::ensureObject(library, {}, "");
|
||||||
auto libraryName = Json::ensureString(libraryObject, "name", "", "");
|
auto libraryName = Json::ensureString(libraryObject, "name", "", "");
|
||||||
|
|
||||||
if ((libraryName.startsWith("net.minecraftforge:forge:") || libraryName.startsWith("net.minecraftforge:fmlloader:")) &&
|
if (libraryName.startsWith("net.neoforged.fancymodloader:")) { // it is neoforge
|
||||||
libraryName.contains('-')) {
|
// no easy way to get the version from the libs so use the arguments
|
||||||
|
auto arguments = Json::ensureObject(root, "arguments", {});
|
||||||
|
bool isVersionArg = false;
|
||||||
|
QString neoforgeVersion;
|
||||||
|
for (auto arg : Json::ensureArray(arguments, "game", {})) {
|
||||||
|
auto argument = Json::ensureString(arg, "");
|
||||||
|
if (isVersionArg) {
|
||||||
|
neoforgeVersion = argument;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
isVersionArg = "--fml.neoForgeVersion" == argument || "--fml.forgeVersion" == argument;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!neoforgeVersion.isEmpty()) {
|
||||||
|
components->setComponentVersion("net.neoforged", neoforgeVersion);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if ((libraryName.startsWith("net.minecraftforge:forge:") || libraryName.startsWith("net.minecraftforge:fmlloader:")) &&
|
||||||
|
libraryName.contains('-')) {
|
||||||
QString libraryVersion = libraryName.section(':', 2);
|
QString libraryVersion = libraryName.section(':', 2);
|
||||||
if (!libraryVersion.startsWith("1.7.10-")) {
|
if (!libraryVersion.startsWith("1.7.10-")) {
|
||||||
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1));
|
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1));
|
||||||
@ -164,6 +182,7 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings,
|
|||||||
// 1.7.10 versions sometimes look like 1.7.10-10.13.4.1614-1.7.10, this filters out the 10.13.4.1614 part
|
// 1.7.10 versions sometimes look like 1.7.10-10.13.4.1614-1.7.10, this filters out the 10.13.4.1614 part
|
||||||
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1));
|
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1));
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
} else {
|
} else {
|
||||||
// <Technic library name prefix> -> <our component name>
|
// <Technic library name prefix> -> <our component name>
|
||||||
static QMap<QString, QString> loaderMap{ { "net.minecraftforge:minecraftforge:", "net.minecraftforge" },
|
static QMap<QString, QString> loaderMap{ { "net.minecraftforge:minecraftforge:", "net.minecraftforge" },
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
#include "ByteArraySink.h"
|
#include "ByteArraySink.h"
|
||||||
#include "ChecksumValidator.h"
|
#include "ChecksumValidator.h"
|
||||||
#include "MetaCacheSink.h"
|
#include "MetaCacheSink.h"
|
||||||
#include "net/NetAction.h"
|
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
|
@ -19,9 +19,6 @@
|
|||||||
|
|
||||||
#include "net/ApiUpload.h"
|
#include "net/ApiUpload.h"
|
||||||
#include "ByteArraySink.h"
|
#include "ByteArraySink.h"
|
||||||
#include "ChecksumValidator.h"
|
|
||||||
#include "MetaCacheSink.h"
|
|
||||||
#include "net/NetAction.h"
|
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
|
@ -74,10 +74,6 @@ class ByteArraySink : public Sink {
|
|||||||
|
|
||||||
auto abort() -> Task::State override
|
auto abort() -> Task::State override
|
||||||
{
|
{
|
||||||
if (m_output)
|
|
||||||
m_output->clear();
|
|
||||||
else
|
|
||||||
qWarning() << "ByteArraySink did not clear the buffer because it's not addressable";
|
|
||||||
failAllValidators();
|
failAllValidators();
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,6 @@
|
|||||||
#include "ChecksumValidator.h"
|
#include "ChecksumValidator.h"
|
||||||
#include "MetaCacheSink.h"
|
#include "MetaCacheSink.h"
|
||||||
|
|
||||||
#include "net/NetAction.h"
|
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
|
@ -84,6 +84,9 @@ auto HttpMetaCache::getEntry(QString base, QString resource_path) -> MetaEntryPt
|
|||||||
|
|
||||||
auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag) -> MetaEntryPtr
|
auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag) -> MetaEntryPtr
|
||||||
{
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
resource_path = FS::RemoveInvalidPathChars(resource_path);
|
||||||
|
#endif
|
||||||
auto entry = getEntry(base, resource_path);
|
auto entry = getEntry(base, resource_path);
|
||||||
// it's not present? generate a default stale entry
|
// it's not present? generate a default stale entry
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
|
@ -1,100 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
|
||||||
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* This file incorporates work covered by the following copyright and
|
|
||||||
* permission notice:
|
|
||||||
*
|
|
||||||
* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QUrl>
|
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
|
|
||||||
#include "HeaderProxy.h"
|
|
||||||
|
|
||||||
class NetAction : public Task {
|
|
||||||
Q_OBJECT
|
|
||||||
protected:
|
|
||||||
explicit NetAction() : Task() {}
|
|
||||||
|
|
||||||
public:
|
|
||||||
using Ptr = shared_qobject_ptr<NetAction>;
|
|
||||||
|
|
||||||
virtual ~NetAction() = default;
|
|
||||||
|
|
||||||
QUrl url() { return m_url; }
|
|
||||||
|
|
||||||
void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; }
|
|
||||||
|
|
||||||
void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr<Net::HeaderProxy>(proxy)); }
|
|
||||||
virtual void init() = 0;
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0;
|
|
||||||
virtual void downloadError(QNetworkReply::NetworkError error) = 0;
|
|
||||||
virtual void downloadFinished() = 0;
|
|
||||||
virtual void downloadReadyRead() = 0;
|
|
||||||
|
|
||||||
virtual void sslErrors(const QList<QSslError>& errors)
|
|
||||||
{
|
|
||||||
int i = 1;
|
|
||||||
for (auto error : errors) {
|
|
||||||
qCritical() << "Network SSL Error #" << i << " : " << error.errorString();
|
|
||||||
auto cert = error.certificate();
|
|
||||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void startAction(shared_qobject_ptr<QNetworkAccessManager> network)
|
|
||||||
{
|
|
||||||
m_network = network;
|
|
||||||
executeTask();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void executeTask() override {}
|
|
||||||
|
|
||||||
public:
|
|
||||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
|
||||||
|
|
||||||
/// the network reply
|
|
||||||
unique_qobject_ptr<QNetworkReply> m_reply;
|
|
||||||
|
|
||||||
/// source URL
|
|
||||||
QUrl m_url;
|
|
||||||
std::vector<std::shared_ptr<Net::HeaderProxy>> m_headerProxies;
|
|
||||||
};
|
|
@ -36,6 +36,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "NetJob.h"
|
#include "NetJob.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
#include "tasks/ConcurrentTask.h"
|
#include "tasks/ConcurrentTask.h"
|
||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
@ -48,7 +49,7 @@ NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> netwo
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
auto NetJob::addNetAction(NetAction::Ptr action) -> bool
|
auto NetJob::addNetAction(Net::NetRequest::Ptr action) -> bool
|
||||||
{
|
{
|
||||||
action->setNetwork(m_network);
|
action->setNetwork(m_network);
|
||||||
|
|
||||||
@ -111,11 +112,11 @@ auto NetJob::abort() -> bool
|
|||||||
return fullyAborted;
|
return fullyAborted;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto NetJob::getFailedActions() -> QList<NetAction*>
|
auto NetJob::getFailedActions() -> QList<Net::NetRequest*>
|
||||||
{
|
{
|
||||||
QList<NetAction*> failed;
|
QList<Net::NetRequest*> failed;
|
||||||
for (auto index : m_failed) {
|
for (auto index : m_failed) {
|
||||||
failed.push_back(dynamic_cast<NetAction*>(index.get()));
|
failed.push_back(dynamic_cast<Net::NetRequest*>(index.get()));
|
||||||
}
|
}
|
||||||
return failed;
|
return failed;
|
||||||
}
|
}
|
||||||
@ -124,7 +125,7 @@ auto NetJob::getFailedFiles() -> QList<QString>
|
|||||||
{
|
{
|
||||||
QList<QString> failed;
|
QList<QString> failed;
|
||||||
for (auto index : m_failed) {
|
for (auto index : m_failed) {
|
||||||
failed.append(static_cast<NetAction*>(index.get())->url().toString());
|
failed.append(static_cast<Net::NetRequest*>(index.get())->url().toString());
|
||||||
}
|
}
|
||||||
return failed;
|
return failed;
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
#include <QtNetwork>
|
#include <QtNetwork>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include "NetAction.h"
|
#include "net/NetRequest.h"
|
||||||
#include "tasks/ConcurrentTask.h"
|
#include "tasks/ConcurrentTask.h"
|
||||||
|
|
||||||
// Those are included so that they are also included by anyone using NetJob
|
// Those are included so that they are also included by anyone using NetJob
|
||||||
@ -58,9 +58,9 @@ class NetJob : public ConcurrentTask {
|
|||||||
auto size() const -> int;
|
auto size() const -> int;
|
||||||
|
|
||||||
auto canAbort() const -> bool override;
|
auto canAbort() const -> bool override;
|
||||||
auto addNetAction(NetAction::Ptr action) -> bool;
|
auto addNetAction(Net::NetRequest::Ptr action) -> bool;
|
||||||
|
|
||||||
auto getFailedActions() -> QList<NetAction*>;
|
auto getFailedActions() -> QList<Net::NetRequest*>;
|
||||||
auto getFailedFiles() -> QList<QString>;
|
auto getFailedFiles() -> QList<QString>;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -37,10 +37,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "NetRequest.h"
|
#include "NetRequest.h"
|
||||||
#include <QUrl>
|
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QUrl>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
@ -48,8 +49,6 @@
|
|||||||
#endif
|
#endif
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
|
|
||||||
#include "net/NetAction.h"
|
|
||||||
|
|
||||||
#include "MMCTime.h"
|
#include "MMCTime.h"
|
||||||
#include "StringUtils.h"
|
#include "StringUtils.h"
|
||||||
|
|
||||||
@ -68,7 +67,8 @@ void NetRequest::executeTask()
|
|||||||
|
|
||||||
if (getState() == Task::State::AbortedByUser) {
|
if (getState() == Task::State::AbortedByUser) {
|
||||||
qCWarning(logCat) << getUid().toString() << "Attempt to start an aborted Request:" << m_url.toString();
|
qCWarning(logCat) << getUid().toString() << "Attempt to start an aborted Request:" << m_url.toString();
|
||||||
emitAborted();
|
emit aborted();
|
||||||
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,10 +85,12 @@ void NetRequest::executeTask()
|
|||||||
break;
|
break;
|
||||||
case State::Inactive:
|
case State::Inactive:
|
||||||
case State::Failed:
|
case State::Failed:
|
||||||
emitFailed();
|
emit failed("Failed to initilize sink");
|
||||||
|
emit finished();
|
||||||
return;
|
return;
|
||||||
case State::AbortedByUser:
|
case State::AbortedByUser:
|
||||||
emitAborted();
|
emit aborted();
|
||||||
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +104,6 @@ void NetRequest::executeTask()
|
|||||||
for (auto& header_proxy : m_headerProxies) {
|
for (auto& header_proxy : m_headerProxies) {
|
||||||
header_proxy->writeHeaders(request);
|
header_proxy->writeHeaders(request);
|
||||||
}
|
}
|
||||||
// TODO remove duplication
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
request.setTransferTimeout();
|
request.setTransferTimeout();
|
||||||
@ -111,11 +112,12 @@ void NetRequest::executeTask()
|
|||||||
m_last_progress_time = m_clock.now();
|
m_last_progress_time = m_clock.now();
|
||||||
m_last_progress_bytes = 0;
|
m_last_progress_bytes = 0;
|
||||||
|
|
||||||
QNetworkReply* rep = getReply(request);
|
auto rep = getReply(request);
|
||||||
if (rep == nullptr) // it failed
|
if (rep == nullptr) // it failed
|
||||||
return;
|
return;
|
||||||
m_reply.reset(rep);
|
m_reply.reset(rep);
|
||||||
connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::downloadProgress);
|
connect(rep, &QNetworkReply::uploadProgress, this, &NetRequest::onProgress);
|
||||||
|
connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::onProgress);
|
||||||
connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished);
|
connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished);
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||||
connect(rep, &QNetworkReply::errorOccurred, this, &NetRequest::downloadError);
|
connect(rep, &QNetworkReply::errorOccurred, this, &NetRequest::downloadError);
|
||||||
@ -126,7 +128,7 @@ void NetRequest::executeTask()
|
|||||||
connect(rep, &QNetworkReply::readyRead, this, &NetRequest::downloadReadyRead);
|
connect(rep, &QNetworkReply::readyRead, this, &NetRequest::downloadReadyRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetRequest::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
void NetRequest::onProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||||
{
|
{
|
||||||
auto now = m_clock.now();
|
auto now = m_clock.now();
|
||||||
auto elapsed = now - m_last_progress_time;
|
auto elapsed = now - m_last_progress_time;
|
||||||
@ -169,7 +171,9 @@ void NetRequest::downloadError(QNetworkReply::NetworkError error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// error happened during download.
|
// error happened during download.
|
||||||
qCCritical(logCat) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
|
qCCritical(logCat) << getUid().toString() << "Failed" << m_url.toString() << "with reason" << error;
|
||||||
|
if (m_reply)
|
||||||
|
qCCritical(logCat) << getUid().toString() << "HTTP Status" << replyStatusCode() << ";error" << errorString();
|
||||||
m_state = State::Failed;
|
m_state = State::Failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -234,7 +238,7 @@ auto NetRequest::handleRedirect() -> bool
|
|||||||
|
|
||||||
m_url = QUrl(redirect.toString());
|
m_url = QUrl(redirect.toString());
|
||||||
qCDebug(logCat) << getUid().toString() << "Following redirect to " << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Following redirect to " << m_url.toString();
|
||||||
startAction(m_network);
|
executeTask();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -252,21 +256,18 @@ void NetRequest::downloadFinished()
|
|||||||
{
|
{
|
||||||
qCDebug(logCat) << getUid().toString() << "Request failed but we are allowed to proceed:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request failed but we are allowed to proceed:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
|
||||||
emit succeeded();
|
emit succeeded();
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
} else if (m_state == State::Failed) {
|
} else if (m_state == State::Failed) {
|
||||||
qCDebug(logCat) << getUid().toString() << "Request failed in previous step:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request failed in previous step:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
emit failed(m_reply->errorString());
|
||||||
emit failed("");
|
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
} else if (m_state == State::AbortedByUser) {
|
} else if (m_state == State::AbortedByUser) {
|
||||||
qCDebug(logCat) << getUid().toString() << "Request aborted in previous step:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request aborted in previous step:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
|
||||||
emit aborted();
|
emit aborted();
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
@ -280,7 +281,7 @@ void NetRequest::downloadFinished()
|
|||||||
if (m_state != State::Succeeded) {
|
if (m_state != State::Succeeded) {
|
||||||
qCDebug(logCat) << getUid().toString() << "Request failed to write:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request failed to write:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
emit failed("");
|
emit failed("failed to write in sink");
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -291,13 +292,11 @@ void NetRequest::downloadFinished()
|
|||||||
if (m_state != State::Succeeded) {
|
if (m_state != State::Succeeded) {
|
||||||
qCDebug(logCat) << getUid().toString() << "Request failed to finalize:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request failed to finalize:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
emit failed("failed to finalize the request");
|
||||||
emit failed("");
|
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_reply.reset();
|
|
||||||
qCDebug(logCat) << getUid().toString() << "Request succeeded:" << m_url.toString();
|
qCDebug(logCat) << getUid().toString() << "Request succeeded:" << m_url.toString();
|
||||||
emit succeeded();
|
emit succeeded();
|
||||||
emit finished();
|
emit finished();
|
||||||
@ -331,4 +330,23 @@ auto NetRequest::abort() -> bool
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int NetRequest::replyStatusCode() const
|
||||||
|
{
|
||||||
|
return m_reply ? m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply::NetworkError NetRequest::error() const
|
||||||
|
{
|
||||||
|
return m_reply ? m_reply->error() : QNetworkReply::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl NetRequest::url() const
|
||||||
|
{
|
||||||
|
return m_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NetRequest::errorString() const
|
||||||
|
{
|
||||||
|
return m_reply ? m_reply->errorString() : "";
|
||||||
|
}
|
||||||
} // namespace Net
|
} // namespace Net
|
||||||
|
@ -39,20 +39,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QUrl>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
#include "NetAction.h"
|
#include "HeaderProxy.h"
|
||||||
#include "Sink.h"
|
#include "Sink.h"
|
||||||
#include "Validator.h"
|
#include "Validator.h"
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
#include "net/Logging.h"
|
#include "net/Logging.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
class NetRequest : public NetAction {
|
class NetRequest : public Task {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
protected:
|
protected:
|
||||||
explicit NetRequest() : NetAction() {}
|
explicit NetRequest() : Task() {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using Ptr = shared_qobject_ptr<class NetRequest>;
|
using Ptr = shared_qobject_ptr<class NetRequest>;
|
||||||
@ -61,26 +64,30 @@ class NetRequest : public NetAction {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
~NetRequest() override = default;
|
~NetRequest() override = default;
|
||||||
|
|
||||||
void init() override {}
|
|
||||||
|
|
||||||
public:
|
|
||||||
void addValidator(Validator* v);
|
void addValidator(Validator* v);
|
||||||
auto abort() -> bool override;
|
auto abort() -> bool override;
|
||||||
auto canAbort() const -> bool override { return true; }
|
auto canAbort() const -> bool override { return true; }
|
||||||
|
|
||||||
|
void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; }
|
||||||
|
void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr<Net::HeaderProxy>(proxy)); }
|
||||||
|
|
||||||
|
virtual void init() {}
|
||||||
|
|
||||||
|
QUrl url() const;
|
||||||
|
int replyStatusCode() const;
|
||||||
|
QNetworkReply::NetworkError error() const;
|
||||||
|
QString errorString() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto handleRedirect() -> bool;
|
auto handleRedirect() -> bool;
|
||||||
virtual QNetworkReply* getReply(QNetworkRequest&) = 0;
|
virtual QNetworkReply* getReply(QNetworkRequest&) = 0;
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
|
void onProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||||
void downloadError(QNetworkReply::NetworkError error) override;
|
void downloadError(QNetworkReply::NetworkError error);
|
||||||
void sslErrors(const QList<QSslError>& errors) override;
|
void sslErrors(const QList<QSslError>& errors);
|
||||||
void downloadFinished() override;
|
void downloadFinished();
|
||||||
void downloadReadyRead() override;
|
void downloadReadyRead();
|
||||||
|
|
||||||
public slots:
|
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -93,6 +100,15 @@ class NetRequest : public NetAction {
|
|||||||
std::chrono::steady_clock m_clock;
|
std::chrono::steady_clock m_clock;
|
||||||
std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time;
|
std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time;
|
||||||
qint64 m_last_progress_bytes;
|
qint64 m_last_progress_bytes;
|
||||||
|
|
||||||
|
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
||||||
|
|
||||||
|
/// the network reply
|
||||||
|
unique_qobject_ptr<QNetworkReply> m_reply;
|
||||||
|
|
||||||
|
/// source URL
|
||||||
|
QUrl m_url;
|
||||||
|
std::vector<std::shared_ptr<Net::HeaderProxy>> m_headerProxies;
|
||||||
};
|
};
|
||||||
} // namespace Net
|
} // namespace Net
|
||||||
|
|
||||||
|
@ -35,9 +35,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "net/NetAction.h"
|
|
||||||
|
|
||||||
#include "Validator.h"
|
#include "Validator.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
class Sink {
|
class Sink {
|
||||||
|
@ -46,7 +46,8 @@ namespace Net {
|
|||||||
|
|
||||||
QNetworkReply* Upload::getReply(QNetworkRequest& request)
|
QNetworkReply* Upload::getReply(QNetworkRequest& request)
|
||||||
{
|
{
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
if (!request.hasRawHeader("Content-Type"))
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
return m_network->post(request, m_post_data);
|
return m_network->post(request, m_post_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "net/NetAction.h"
|
#include <QNetworkReply>
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
class Validator {
|
class Validator {
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
qt.*.debug=false
|
qt.*.debug=false
|
||||||
# don't log credentials by default
|
# don't log credentials by default
|
||||||
launcher.auth.credentials.debug=false
|
launcher.auth.credentials.debug=false
|
||||||
katabasis.*.debug=false
|
|
||||||
# remove the debug lines, other log levels still get through
|
# remove the debug lines, other log levels still get through
|
||||||
launcher.task.net.download.debug=false
|
launcher.task.net.download.debug=false
|
||||||
# enable or disable whole catageries
|
# enable or disable whole catageries
|
||||||
|
@ -231,7 +231,8 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||||||
setInstanceActionsEnabled(false);
|
setInstanceActionsEnabled(false);
|
||||||
|
|
||||||
// add a close button at the end of the main toolbar when running on gamescope / steam deck
|
// add a close button at the end of the main toolbar when running on gamescope / steam deck
|
||||||
// FIXME: detect if we don't have server side decorations instead
|
// this is only needed on gamescope because it defaults to an X11/XWayland session and
|
||||||
|
// does not implement decorations
|
||||||
if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") {
|
if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") {
|
||||||
ui->mainToolBar->addAction(ui->actionCloseWindow);
|
ui->mainToolBar->addAction(ui->actionCloseWindow);
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods, QString hash_type)
|
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods, QString hash_type)
|
||||||
: QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods), m_hash_type(hash_type)
|
: QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods), m_hash_type(hash_type)
|
||||||
@ -60,8 +61,13 @@ BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, cons
|
|||||||
|
|
||||||
qDebug() << "[Blocked Mods Dialog] Mods List: " << mods;
|
qDebug() << "[Blocked Mods Dialog] Mods List: " << mods;
|
||||||
|
|
||||||
setupWatch();
|
// defer setup of file system watchers until after the dialog is shown
|
||||||
scanPaths();
|
// this allows OS (namely macOS) permission prompts to show after the relevant dialog appears
|
||||||
|
QTimer::singleShot(0, this, [this] {
|
||||||
|
setupWatch();
|
||||||
|
scanPaths();
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
this->setWindowTitle(title);
|
this->setWindowTitle(title);
|
||||||
ui->labelDescription->setText(text);
|
ui->labelDescription->setText(text);
|
||||||
@ -158,7 +164,8 @@ void BlockedModsDialog::update()
|
|||||||
|
|
||||||
QString watching;
|
QString watching;
|
||||||
for (auto& dir : m_watcher.directories()) {
|
for (auto& dir : m_watcher.directories()) {
|
||||||
watching += QString("<a href=\"%1\">%1</a><br/>").arg(dir);
|
QUrl fileURL = QUrl::fromLocalFile(dir);
|
||||||
|
watching += QString("<a href=\"%1\">%2</a><br/>").arg(fileURL.toString(), dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui->textBrowserWatched->setText(watching);
|
ui->textBrowserWatched->setText(watching);
|
||||||
@ -194,6 +201,10 @@ void BlockedModsDialog::setupWatch()
|
|||||||
void BlockedModsDialog::watchPath(QString path, bool watch_recursive)
|
void BlockedModsDialog::watchPath(QString path, bool watch_recursive)
|
||||||
{
|
{
|
||||||
auto to_watch = QFileInfo(path);
|
auto to_watch = QFileInfo(path);
|
||||||
|
if (!to_watch.isReadable()) {
|
||||||
|
qWarning() << "[Blocked Mods Dialog] Failed to add Watch Path (unable to read):" << path;
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto to_watch_path = to_watch.canonicalFilePath();
|
auto to_watch_path = to_watch.canonicalFilePath();
|
||||||
if (m_watcher.directories().contains(to_watch_path))
|
if (m_watcher.directories().contains(to_watch_path))
|
||||||
return; // don't watch the same path twice (no loops!)
|
return; // don't watch the same path twice (no loops!)
|
||||||
|
@ -146,7 +146,7 @@ void ExportInstanceDialog::doExport()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto task = makeShared<MMCZip::ExportToZipTask>(output, m_instance->instanceRoot(), files, "", true);
|
auto task = makeShared<MMCZip::ExportToZipTask>(output, m_instance->instanceRoot(), files, "", true, true);
|
||||||
|
|
||||||
connect(task.get(), &Task::failed, this,
|
connect(task.get(), &Task::failed, this,
|
||||||
[this, output](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
[this, output](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
#include "ui_MSALoginDialog.h"
|
#include "ui_MSALoginDialog.h"
|
||||||
|
|
||||||
#include "DesktopServices.h"
|
#include "DesktopServices.h"
|
||||||
#include "minecraft/auth/AccountTask.h"
|
#include "minecraft/auth/AuthFlow.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
@ -47,30 +47,29 @@
|
|||||||
MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MSALoginDialog)
|
MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MSALoginDialog)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
ui->progressBar->setVisible(false);
|
|
||||||
ui->actionButton->setVisible(false);
|
|
||||||
// ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
|
|
||||||
|
|
||||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
ui->cancel->setEnabled(false);
|
||||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
ui->link->setVisible(false);
|
||||||
|
ui->copy->setVisible(false);
|
||||||
|
ui->progressBar->setVisible(false);
|
||||||
|
|
||||||
|
connect(ui->cancel, &QPushButton::pressed, this, &QDialog::reject);
|
||||||
|
connect(ui->copy, &QPushButton::pressed, this, &MSALoginDialog::copyUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MSALoginDialog::exec()
|
int MSALoginDialog::exec()
|
||||||
{
|
{
|
||||||
setUserInputsEnabled(false);
|
|
||||||
ui->progressBar->setVisible(true);
|
|
||||||
|
|
||||||
// Setup the login task and start it
|
// Setup the login task and start it
|
||||||
m_account = MinecraftAccount::createBlankMSA();
|
m_account = MinecraftAccount::createBlankMSA();
|
||||||
m_loginTask = m_account->loginMSA();
|
m_task = m_account->login(m_using_device_code);
|
||||||
connect(m_loginTask.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
|
connect(m_task.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
|
||||||
connect(m_loginTask.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded);
|
connect(m_task.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded);
|
||||||
connect(m_loginTask.get(), &Task::status, this, &MSALoginDialog::onTaskStatus);
|
connect(m_task.get(), &Task::status, this, &MSALoginDialog::onTaskStatus);
|
||||||
connect(m_loginTask.get(), &Task::progress, this, &MSALoginDialog::onTaskProgress);
|
connect(m_task.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser);
|
||||||
connect(m_loginTask.get(), &AccountTask::showVerificationUriAndCode, this, &MSALoginDialog::showVerificationUriAndCode);
|
connect(m_task.get(), &AuthFlow::authorizeWithBrowserWithExtra, this, &MSALoginDialog::authorizeWithBrowserWithExtra);
|
||||||
connect(m_loginTask.get(), &AccountTask::hideVerificationUriAndCode, this, &MSALoginDialog::hideVerificationUriAndCode);
|
connect(ui->cancel, &QPushButton::pressed, m_task.get(), &Task::abort);
|
||||||
connect(&m_externalLoginTimer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick);
|
connect(&m_external_timer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick);
|
||||||
m_loginTask->start();
|
m_task->start();
|
||||||
|
|
||||||
return QDialog::exec();
|
return QDialog::exec();
|
||||||
}
|
}
|
||||||
@ -80,60 +79,6 @@ MSALoginDialog::~MSALoginDialog()
|
|||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MSALoginDialog::externalLoginTick()
|
|
||||||
{
|
|
||||||
m_externalLoginElapsed++;
|
|
||||||
ui->progressBar->setValue(m_externalLoginElapsed);
|
|
||||||
ui->progressBar->repaint();
|
|
||||||
|
|
||||||
if (m_externalLoginElapsed >= m_externalLoginTimeout) {
|
|
||||||
m_externalLoginTimer.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSALoginDialog::showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn)
|
|
||||||
{
|
|
||||||
m_externalLoginElapsed = 0;
|
|
||||||
m_externalLoginTimeout = expiresIn;
|
|
||||||
|
|
||||||
m_externalLoginTimer.setInterval(1000);
|
|
||||||
m_externalLoginTimer.setSingleShot(false);
|
|
||||||
m_externalLoginTimer.start();
|
|
||||||
|
|
||||||
ui->progressBar->setMaximum(expiresIn);
|
|
||||||
ui->progressBar->setValue(m_externalLoginElapsed);
|
|
||||||
|
|
||||||
QString urlString = uri.toString();
|
|
||||||
QString linkString = QString("<a href=\"%1\">%2</a>").arg(urlString, urlString);
|
|
||||||
if (urlString == "https://www.microsoft.com/link" && !code.isEmpty()) {
|
|
||||||
urlString += QString("?otc=%1").arg(code);
|
|
||||||
DesktopServices::openUrl(urlString);
|
|
||||||
ui->label->setText(tr("<p>Please login in the opened browser. If no browser was opened, please open up %1 in "
|
|
||||||
"a browser and put in the code <b>%2</b> to proceed with login.</p>")
|
|
||||||
.arg(linkString, code));
|
|
||||||
} else {
|
|
||||||
ui->label->setText(
|
|
||||||
tr("<p>Please open up %1 in a browser and put in the code <b>%2</b> to proceed with login.</p>").arg(linkString, code));
|
|
||||||
}
|
|
||||||
ui->actionButton->setVisible(true);
|
|
||||||
connect(ui->actionButton, &QPushButton::clicked, [=]() {
|
|
||||||
DesktopServices::openUrl(uri);
|
|
||||||
QClipboard* cb = QApplication::clipboard();
|
|
||||||
cb->setText(code);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSALoginDialog::hideVerificationUriAndCode()
|
|
||||||
{
|
|
||||||
m_externalLoginTimer.stop();
|
|
||||||
ui->actionButton->setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSALoginDialog::setUserInputsEnabled(bool enable)
|
|
||||||
{
|
|
||||||
ui->buttonBox->setEnabled(enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MSALoginDialog::onTaskFailed(const QString& reason)
|
void MSALoginDialog::onTaskFailed(const QString& reason)
|
||||||
{
|
{
|
||||||
// Set message
|
// Set message
|
||||||
@ -146,12 +91,7 @@ void MSALoginDialog::onTaskFailed(const QString& reason)
|
|||||||
processed += "<br />";
|
processed += "<br />";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ui->label->setText(processed);
|
ui->message->setText(processed);
|
||||||
|
|
||||||
// Re-enable user-interaction
|
|
||||||
setUserInputsEnabled(true);
|
|
||||||
ui->progressBar->setVisible(false);
|
|
||||||
ui->actionButton->setVisible(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MSALoginDialog::onTaskSucceeded()
|
void MSALoginDialog::onTaskSucceeded()
|
||||||
@ -161,22 +101,81 @@ void MSALoginDialog::onTaskSucceeded()
|
|||||||
|
|
||||||
void MSALoginDialog::onTaskStatus(const QString& status)
|
void MSALoginDialog::onTaskStatus(const QString& status)
|
||||||
{
|
{
|
||||||
ui->label->setText(status);
|
ui->message->setText(status);
|
||||||
}
|
ui->cancel->setEnabled(false);
|
||||||
|
ui->link->setVisible(false);
|
||||||
void MSALoginDialog::onTaskProgress(qint64 current, qint64 total)
|
ui->copy->setVisible(false);
|
||||||
{
|
ui->progressBar->setVisible(false);
|
||||||
ui->progressBar->setMaximum(total);
|
|
||||||
ui->progressBar->setValue(current);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public interface
|
// Public interface
|
||||||
MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent, QString msg)
|
MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent, QString msg, bool usingDeviceCode)
|
||||||
{
|
{
|
||||||
MSALoginDialog dlg(parent);
|
MSALoginDialog dlg(parent);
|
||||||
dlg.ui->label->setText(msg);
|
dlg.m_using_device_code = usingDeviceCode;
|
||||||
|
dlg.ui->message->setText(msg);
|
||||||
if (dlg.exec() == QDialog::Accepted) {
|
if (dlg.exec() == QDialog::Accepted) {
|
||||||
return dlg.m_account;
|
return dlg.m_account;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MSALoginDialog::authorizeWithBrowser(const QUrl& url)
|
||||||
|
{
|
||||||
|
ui->cancel->setEnabled(true);
|
||||||
|
ui->link->setVisible(true);
|
||||||
|
ui->copy->setVisible(true);
|
||||||
|
DesktopServices::openUrl(url);
|
||||||
|
ui->link->setText(url.toDisplayString());
|
||||||
|
ui->message->setText(
|
||||||
|
tr("Browser opened to complete the login process."
|
||||||
|
"<br /><br />"
|
||||||
|
"If your browser hasn't opened, please manually open the below link in your browser:"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSALoginDialog::copyUrl()
|
||||||
|
{
|
||||||
|
QClipboard* cb = QApplication::clipboard();
|
||||||
|
cb->setText(ui->link->text());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn)
|
||||||
|
{
|
||||||
|
m_external_elapsed = 0;
|
||||||
|
m_external_timeout = expiresIn;
|
||||||
|
|
||||||
|
m_external_timer.setInterval(1000);
|
||||||
|
m_external_timer.setSingleShot(false);
|
||||||
|
m_external_timer.start();
|
||||||
|
|
||||||
|
ui->progressBar->setMaximum(expiresIn);
|
||||||
|
ui->progressBar->setValue(m_external_elapsed);
|
||||||
|
|
||||||
|
QString linkString = QString("<a href=\"%1\">%2</a>").arg(url, url);
|
||||||
|
if (url == "https://www.microsoft.com/link" && !code.isEmpty()) {
|
||||||
|
url += QString("?otc=%1").arg(code);
|
||||||
|
ui->message->setText(tr("<p>Please login in the opened browser. If no browser was opened, please open up %1 in "
|
||||||
|
"a browser and put in the code <b>%2</b> to proceed with login.</p>")
|
||||||
|
.arg(linkString, code));
|
||||||
|
} else {
|
||||||
|
ui->message->setText(
|
||||||
|
tr("<p>Please open up %1 in a browser and put in the code <b>%2</b> to proceed with login.</p>").arg(linkString, code));
|
||||||
|
}
|
||||||
|
ui->cancel->setEnabled(true);
|
||||||
|
ui->link->setVisible(true);
|
||||||
|
ui->copy->setVisible(true);
|
||||||
|
ui->progressBar->setVisible(true);
|
||||||
|
DesktopServices::openUrl(url);
|
||||||
|
ui->link->setText(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSALoginDialog::externalLoginTick()
|
||||||
|
{
|
||||||
|
m_external_elapsed++;
|
||||||
|
ui->progressBar->setValue(m_external_elapsed);
|
||||||
|
ui->progressBar->repaint();
|
||||||
|
|
||||||
|
if (m_external_elapsed >= m_external_timeout) {
|
||||||
|
m_external_timer.stop();
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@
|
|||||||
#include <QtCore/QEventLoop>
|
#include <QtCore/QEventLoop>
|
||||||
#include <QtWidgets/QDialog>
|
#include <QtWidgets/QDialog>
|
||||||
|
|
||||||
|
#include "minecraft/auth/AuthFlow.h"
|
||||||
#include "minecraft/auth/MinecraftAccount.h"
|
#include "minecraft/auth/MinecraftAccount.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
@ -31,29 +32,29 @@ class MSALoginDialog : public QDialog {
|
|||||||
public:
|
public:
|
||||||
~MSALoginDialog();
|
~MSALoginDialog();
|
||||||
|
|
||||||
static MinecraftAccountPtr newAccount(QWidget* parent, QString message);
|
static MinecraftAccountPtr newAccount(QWidget* parent, QString message, bool usingDeviceCode = false);
|
||||||
int exec() override;
|
int exec() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit MSALoginDialog(QWidget* parent = 0);
|
explicit MSALoginDialog(QWidget* parent = 0);
|
||||||
|
|
||||||
void setUserInputsEnabled(bool enable);
|
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void onTaskFailed(const QString& reason);
|
void onTaskFailed(const QString& reason);
|
||||||
void onTaskSucceeded();
|
void onTaskSucceeded();
|
||||||
void onTaskStatus(const QString& status);
|
void onTaskStatus(const QString& status);
|
||||||
void onTaskProgress(qint64 current, qint64 total);
|
void authorizeWithBrowser(const QUrl& url);
|
||||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
void authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn);
|
||||||
void hideVerificationUriAndCode();
|
void copyUrl();
|
||||||
|
|
||||||
void externalLoginTick();
|
void externalLoginTick();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::MSALoginDialog* ui;
|
Ui::MSALoginDialog* ui;
|
||||||
MinecraftAccountPtr m_account;
|
MinecraftAccountPtr m_account;
|
||||||
shared_qobject_ptr<AccountTask> m_loginTask;
|
shared_qobject_ptr<AuthFlow> m_task;
|
||||||
QTimer m_externalLoginTimer;
|
|
||||||
int m_externalLoginElapsed = 0;
|
int m_external_elapsed;
|
||||||
int m_externalLoginTimeout = 0;
|
int m_external_timeout;
|
||||||
|
QTimer m_external_timer;
|
||||||
|
|
||||||
|
bool m_using_device_code = false;
|
||||||
};
|
};
|
||||||
|
@ -7,11 +7,11 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>491</width>
|
<width>491</width>
|
||||||
<height>143</height>
|
<height>208</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
<sizepolicy hsizetype="Fixed" vsizetype="MinimumExpanding">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
@ -21,15 +21,28 @@
|
|||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="message">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>500</width>
|
||||||
|
<height>500</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string notr="true">Message label placeholder.
|
<string notr="true"/>
|
||||||
|
|
||||||
aaaaa</string>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="textFormat">
|
<property name="textFormat">
|
||||||
<enum>Qt::RichText</enum>
|
<enum>Qt::RichText</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<property name="openExternalLinks">
|
<property name="openExternalLinks">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -38,6 +51,28 @@ aaaaa</string>
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="linkLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="link">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="copy">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="copy">
|
||||||
|
<normaloff>.</normaloff>.</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QProgressBar" name="progressBar">
|
<widget class="QProgressBar" name="progressBar">
|
||||||
<property name="value">
|
<property name="value">
|
||||||
@ -49,25 +84,11 @@ aaaaa</string>
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<widget class="QPushButton" name="cancel">
|
||||||
<item>
|
<property name="text">
|
||||||
<widget class="QPushButton" name="actionButton">
|
<string>Cancel</string>
|
||||||
<property name="text">
|
</property>
|
||||||
<string>Open page and copy code</string>
|
</widget>
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="standardButtons">
|
|
||||||
<set>QDialogButtonBox::Cancel</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
#include "OfflineLoginDialog.h"
|
#include "OfflineLoginDialog.h"
|
||||||
#include "ui_OfflineLoginDialog.h"
|
#include "ui_OfflineLoginDialog.h"
|
||||||
|
|
||||||
#include "minecraft/auth/AccountTask.h"
|
|
||||||
|
|
||||||
#include <QtWidgets/QPushButton>
|
#include <QtWidgets/QPushButton>
|
||||||
|
|
||||||
OfflineLoginDialog::OfflineLoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::OfflineLoginDialog)
|
OfflineLoginDialog::OfflineLoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::OfflineLoginDialog)
|
||||||
@ -28,7 +26,7 @@ void OfflineLoginDialog::accept()
|
|||||||
|
|
||||||
// Setup the login task and start it
|
// Setup the login task and start it
|
||||||
m_account = MinecraftAccount::createOffline(ui->userTextBox->text());
|
m_account = MinecraftAccount::createOffline(ui->userTextBox->text());
|
||||||
m_loginTask = m_account->loginOffline();
|
m_loginTask = m_account->login();
|
||||||
connect(m_loginTask.get(), &Task::failed, this, &OfflineLoginDialog::onTaskFailed);
|
connect(m_loginTask.get(), &Task::failed, this, &OfflineLoginDialog::onTaskFailed);
|
||||||
connect(m_loginTask.get(), &Task::succeeded, this, &OfflineLoginDialog::onTaskSucceeded);
|
connect(m_loginTask.get(), &Task::succeeded, this, &OfflineLoginDialog::onTaskSucceeded);
|
||||||
connect(m_loginTask.get(), &Task::status, this, &OfflineLoginDialog::onTaskStatus);
|
connect(m_loginTask.get(), &Task::status, this, &OfflineLoginDialog::onTaskStatus);
|
||||||
|
@ -45,8 +45,9 @@
|
|||||||
#include "ui/dialogs/ProgressDialog.h"
|
#include "ui/dialogs/ProgressDialog.h"
|
||||||
|
|
||||||
#include <Application.h>
|
#include <Application.h>
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
|
#include "net/StaticHeaderProxy.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget* parent)
|
ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget* parent)
|
||||||
: QDialog(parent), m_accountToSetup(accountToSetup), ui(new Ui::ProfileSetupDialog)
|
: QDialog(parent), m_accountToSetup(accountToSetup), ui(new Ui::ProfileSetupDialog)
|
||||||
@ -150,28 +151,27 @@ void ProfileSetupDialog::checkName(const QString& name)
|
|||||||
currentCheck = name;
|
currentCheck = name;
|
||||||
isChecking = true;
|
isChecking = true;
|
||||||
|
|
||||||
auto token = m_accountToSetup->accessToken();
|
QUrl url(QString("https://api.minecraftservices.com/minecraft/profile/name/%1/available").arg(name));
|
||||||
|
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
|
||||||
|
{ "Accept", "application/json" },
|
||||||
|
{ "Authorization", QString("Bearer %1").arg(m_accountToSetup->accessToken()).toUtf8() } };
|
||||||
|
|
||||||
auto url = QString("https://api.minecraftservices.com/minecraft/profile/name/%1/available").arg(name);
|
m_check_response.reset(new QByteArray());
|
||||||
QNetworkRequest request = QNetworkRequest(url);
|
if (m_check_task)
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
disconnect(m_check_task.get(), nullptr, this, nullptr);
|
||||||
request.setRawHeader("Accept", "application/json");
|
m_check_task = Net::Download::makeByteArray(url, m_check_response);
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());
|
m_check_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
connect(m_check_task.get(), &Task::finished, this, &ProfileSetupDialog::checkFinished);
|
||||||
connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::checkFinished);
|
|
||||||
requestor->get(request);
|
m_check_task->setNetwork(APPLICATION->network());
|
||||||
|
m_check_task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProfileSetupDialog::checkFinished(QNetworkReply::NetworkError error,
|
void ProfileSetupDialog::checkFinished()
|
||||||
QByteArray profileData,
|
|
||||||
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
|
|
||||||
{
|
{
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
if (m_check_task->error() == QNetworkReply::NoError) {
|
||||||
requestor->deleteLater();
|
auto doc = QJsonDocument::fromJson(*m_check_response);
|
||||||
|
|
||||||
if (error == QNetworkReply::NoError) {
|
|
||||||
auto doc = QJsonDocument::fromJson(profileData);
|
|
||||||
auto root = doc.object();
|
auto root = doc.object();
|
||||||
auto statusValue = root.value("status").toString("INVALID");
|
auto statusValue = root.value("status").toString("INVALID");
|
||||||
if (statusValue == "AVAILABLE") {
|
if (statusValue == "AVAILABLE") {
|
||||||
@ -195,20 +195,22 @@ void ProfileSetupDialog::setupProfile(const QString& profileName)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto token = m_accountToSetup->accessToken();
|
|
||||||
|
|
||||||
auto url = QString("https://api.minecraftservices.com/minecraft/profile");
|
|
||||||
QNetworkRequest request = QNetworkRequest(url);
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
request.setRawHeader("Accept", "application/json");
|
|
||||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());
|
|
||||||
|
|
||||||
QString payloadTemplate("{\"profileName\":\"%1\"}");
|
QString payloadTemplate("{\"profileName\":\"%1\"}");
|
||||||
auto profileData = payloadTemplate.arg(profileName).toUtf8();
|
|
||||||
|
|
||||||
AuthRequest* requestor = new AuthRequest(this);
|
QUrl url("https://api.minecraftservices.com/minecraft/profile");
|
||||||
connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::setupProfileFinished);
|
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
|
||||||
requestor->post(request, profileData);
|
{ "Accept", "application/json" },
|
||||||
|
{ "Authorization", QString("Bearer %1").arg(m_accountToSetup->accessToken()).toUtf8() } };
|
||||||
|
|
||||||
|
m_profile_response.reset(new QByteArray());
|
||||||
|
m_profile_task = Net::Upload::makeByteArray(url, m_profile_response, payloadTemplate.arg(profileName).toUtf8());
|
||||||
|
m_profile_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||||
|
|
||||||
|
connect(m_profile_task.get(), &Task::finished, this, &ProfileSetupDialog::setupProfileFinished);
|
||||||
|
|
||||||
|
m_profile_task->setNetwork(APPLICATION->network());
|
||||||
|
m_profile_task->start();
|
||||||
|
|
||||||
isWorking = true;
|
isWorking = true;
|
||||||
|
|
||||||
auto button = ui->buttonBox->button(QDialogButtonBox::Cancel);
|
auto button = ui->buttonBox->button(QDialogButtonBox::Cancel);
|
||||||
@ -244,22 +246,17 @@ struct MojangError {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void ProfileSetupDialog::setupProfileFinished(QNetworkReply::NetworkError error,
|
void ProfileSetupDialog::setupProfileFinished()
|
||||||
QByteArray errorData,
|
|
||||||
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
|
|
||||||
{
|
{
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
|
||||||
requestor->deleteLater();
|
|
||||||
|
|
||||||
isWorking = false;
|
isWorking = false;
|
||||||
if (error == QNetworkReply::NoError) {
|
if (m_profile_task->error() == QNetworkReply::NoError) {
|
||||||
/*
|
/*
|
||||||
* data contains the profile in the response
|
* data contains the profile in the response
|
||||||
* ... we could parse it and update the account, but let's just return back to the normal login flow instead...
|
* ... we could parse it and update the account, but let's just return back to the normal login flow instead...
|
||||||
*/
|
*/
|
||||||
accept();
|
accept();
|
||||||
} else {
|
} else {
|
||||||
auto parsedError = MojangError::fromJSON(errorData);
|
auto parsedError = MojangError::fromJSON(*m_profile_response);
|
||||||
ui->errorLabel->setVisible(true);
|
ui->errorLabel->setVisible(true);
|
||||||
ui->errorLabel->setText(tr("The server returned the following error:") + "\n\n" + parsedError.errorMessage);
|
ui->errorLabel->setText(tr("The server returned the following error:") + "\n\n" + parsedError.errorMessage);
|
||||||
qDebug() << parsedError.rawError;
|
qDebug() << parsedError.rawError;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user