Merge remote-tracking branch 'origin/develop' into download-all-blocked
Signed-off-by: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com>
This commit is contained in:
commit
579582740e
43
.github/workflows/build.yml
vendored
43
.github/workflows/build.yml
vendored
@ -23,10 +23,9 @@ jobs:
|
|||||||
qt_ver: 5
|
qt_ver: 5
|
||||||
|
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
appimage: true
|
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: linux
|
qt_host: linux
|
||||||
qt_version: '6.3.1'
|
qt_version: '6.2.4'
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_path: /home/runner/work/PolyMC/Qt
|
qt_path: /home/runner/work/PolyMC/Qt
|
||||||
|
|
||||||
@ -92,7 +91,7 @@ jobs:
|
|||||||
if: runner.os != 'Windows' && inputs.build_type == 'Debug'
|
if: runner.os != 'Windows' && inputs.build_type == 'Debug'
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.1
|
uses: hendrikmuhs/ccache-action@v1.2.1
|
||||||
with:
|
with:
|
||||||
key: ${{ matrix.os }}-${{ matrix.appimage }}
|
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
|
||||||
|
|
||||||
- name: Setup ccache (Windows)
|
- name: Setup ccache (Windows)
|
||||||
if: runner.os == 'Windows' && inputs.build_type == 'Debug'
|
if: runner.os == 'Windows' && inputs.build_type == 'Debug'
|
||||||
@ -115,9 +114,9 @@ jobs:
|
|||||||
uses: actions/cache@v3.0.2
|
uses: actions/cache@v3.0.2
|
||||||
with:
|
with:
|
||||||
path: '${{ github.workspace }}\.ccache'
|
path: '${{ github.workspace }}\.ccache'
|
||||||
key: ${{ matrix.os }}--qt${{ matrix.qt_ver }}
|
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ matrix.os }}--qt${{ matrix.qt_ver }}
|
${{ matrix.os }}-qt${{ matrix.qt_ver }}
|
||||||
|
|
||||||
- name: Set short version
|
- name: Set short version
|
||||||
shell: bash
|
shell: bash
|
||||||
@ -138,7 +137,7 @@ jobs:
|
|||||||
brew install ninja extra-cmake-modules
|
brew install ninja extra-cmake-modules
|
||||||
|
|
||||||
- name: Install Qt (Linux)
|
- name: Install Qt (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.appimage != true
|
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
|
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
|
||||||
|
|
||||||
@ -162,7 +161,7 @@ jobs:
|
|||||||
aqtversion: ==2.1.*
|
aqtversion: ==2.1.*
|
||||||
|
|
||||||
- name: Prepare AppImage (Linux)
|
- name: Prepare AppImage (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.appimage == true
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
run: |
|
run: |
|
||||||
wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
|
wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
|
||||||
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage"
|
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage"
|
||||||
@ -281,7 +280,7 @@ jobs:
|
|||||||
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
|
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
|
||||||
|
|
||||||
- name: Package (Linux)
|
- name: Package (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.appimage != true
|
if: runner.os == 'Linux'
|
||||||
run: |
|
run: |
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}
|
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}
|
||||||
|
|
||||||
@ -289,7 +288,7 @@ jobs:
|
|||||||
tar --owner root --group root -czf ../PolyMC.tar.gz *
|
tar --owner root --group root -czf ../PolyMC.tar.gz *
|
||||||
|
|
||||||
- name: Package (Linux, portable)
|
- name: Package (Linux, portable)
|
||||||
if: runner.os == 'Linux' && matrix.appimage != true
|
if: runner.os == 'Linux'
|
||||||
run: |
|
run: |
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }}
|
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }}
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
||||||
@ -298,7 +297,7 @@ jobs:
|
|||||||
tar -czf ../PolyMC-portable.tar.gz *
|
tar -czf ../PolyMC-portable.tar.gz *
|
||||||
|
|
||||||
- name: Package AppImage (Linux)
|
- name: Package AppImage (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.appimage == true
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
|
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
|
||||||
@ -357,22 +356,36 @@ jobs:
|
|||||||
name: PolyMC-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PolyMC-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PolyMC-Setup.exe
|
path: PolyMC-Setup.exe
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux)
|
- name: Upload binary tarball (Linux, Qt 5)
|
||||||
if: runner.os == 'Linux' && matrix.appimage != true
|
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PolyMC.tar.gz
|
path: PolyMC.tar.gz
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux, portable)
|
- name: Upload binary tarball (Linux, portable, Qt 5)
|
||||||
if: runner.os == 'Linux' && matrix.appimage != true
|
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: PolyMC-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PolyMC-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PolyMC-portable.tar.gz
|
path: PolyMC-portable.tar.gz
|
||||||
|
|
||||||
|
- name: Upload binary tarball (Linux, Qt 6)
|
||||||
|
if: runner.os == 'Linux' && matrix.qt_ver !=5
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: PolyMC-${{ runner.os }}-Qt6-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
|
path: PolyMC.tar.gz
|
||||||
|
|
||||||
|
- name: Upload binary tarball (Linux, portable, Qt 6)
|
||||||
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: PolyMC-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
|
path: PolyMC-portable.tar.gz
|
||||||
|
|
||||||
- name: Upload AppImage (Linux)
|
- name: Upload AppImage (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.appimage == true
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
||||||
|
4
.github/workflows/trigger_release.yml
vendored
4
.github/workflows/trigger_release.yml
vendored
@ -35,6 +35,8 @@ jobs:
|
|||||||
- name: Package artifacts properly
|
- name: Package artifacts properly
|
||||||
run: |
|
run: |
|
||||||
mv ${{ github.workspace }}/PolyMC-source PolyMC-${{ env.VERSION }}
|
mv ${{ github.workspace }}/PolyMC-source PolyMC-${{ env.VERSION }}
|
||||||
|
mv PolyMC-Linux-Qt6-Portable*/PolyMC-portable.tar.gz PolyMC-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||||
|
mv PolyMC-Linux-Qt6*/PolyMC.tar.gz PolyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||||
mv PolyMC-Linux-Portable*/PolyMC-portable.tar.gz PolyMC-Linux-Portable-${{ env.VERSION }}.tar.gz
|
mv PolyMC-Linux-Portable*/PolyMC-portable.tar.gz PolyMC-Linux-Portable-${{ env.VERSION }}.tar.gz
|
||||||
mv PolyMC-Linux*/PolyMC.tar.gz PolyMC-Linux-${{ env.VERSION }}.tar.gz
|
mv PolyMC-Linux*/PolyMC.tar.gz PolyMC-Linux-${{ env.VERSION }}.tar.gz
|
||||||
mv PolyMC-*.AppImage/PolyMC-*.AppImage PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
|
mv PolyMC-*.AppImage/PolyMC-*.AppImage PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
|
||||||
@ -70,6 +72,8 @@ jobs:
|
|||||||
PolyMC-Linux-Portable-${{ env.VERSION }}.tar.gz
|
PolyMC-Linux-Portable-${{ env.VERSION }}.tar.gz
|
||||||
PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
|
PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
|
||||||
PolyMC-Windows-Legacy-${{ env.VERSION }}.zip
|
PolyMC-Windows-Legacy-${{ env.VERSION }}.zip
|
||||||
|
PolyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||||
|
PolyMC-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||||
PolyMC-Windows-Legacy-Portable-${{ env.VERSION }}.zip
|
PolyMC-Windows-Legacy-Portable-${{ env.VERSION }}.zip
|
||||||
PolyMC-Windows-Legacy-Setup-${{ env.VERSION }}.exe
|
PolyMC-Windows-Legacy-Setup-${{ env.VERSION }}.exe
|
||||||
PolyMC-Windows-${{ env.VERSION }}.zip
|
PolyMC-Windows-${{ env.VERSION }}.zip
|
||||||
|
@ -29,10 +29,10 @@ set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/jars)
|
|||||||
######## Set compiler flags ########
|
######## Set compiler flags ########
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED true)
|
set(CMAKE_CXX_STANDARD_REQUIRED true)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED true)
|
set(CMAKE_C_STANDARD_REQUIRED true)
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
include(GenerateExportHeader)
|
include(GenerateExportHeader)
|
||||||
set(CMAKE_CXX_FLAGS "-Wall -pedantic -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
|
set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
|
||||||
if(UNIX AND APPLE)
|
if(UNIX AND APPLE)
|
||||||
set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}")
|
set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}")
|
||||||
endif()
|
endif()
|
||||||
@ -319,7 +319,6 @@ endif()
|
|||||||
add_subdirectory(libraries/rainbow) # Qt extension for colors
|
add_subdirectory(libraries/rainbow) # Qt extension for colors
|
||||||
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
|
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
|
||||||
add_subdirectory(libraries/classparser) # class parser library
|
add_subdirectory(libraries/classparser) # class parser library
|
||||||
add_subdirectory(libraries/optional-bare)
|
|
||||||
add_subdirectory(libraries/tomlc99) # toml parser
|
add_subdirectory(libraries/tomlc99) # toml parser
|
||||||
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
|
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
|
||||||
add_subdirectory(libraries/gamemode)
|
add_subdirectory(libraries/gamemode)
|
||||||
|
28
COPYING.md
28
COPYING.md
@ -295,34 +295,6 @@
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
# optional-bare
|
|
||||||
|
|
||||||
Code from https://github.com/martinmoene/optional-bare/
|
|
||||||
|
|
||||||
Boost Software License - Version 1.0 - August 17th, 2003
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
obtaining a copy of the software and accompanying documentation covered by
|
|
||||||
this license (the "Software") to use, reproduce, display, distribute,
|
|
||||||
execute, and transmit the Software, and to prepare derivative works of the
|
|
||||||
Software, and to permit third-parties to whom the Software is furnished to
|
|
||||||
do so, all subject to the following:
|
|
||||||
|
|
||||||
The copyright notices in the Software and this entire statement, including
|
|
||||||
the above license grant, this restriction and the following disclaimer,
|
|
||||||
must be included in all copies of the Software, in whole or in part, and
|
|
||||||
all derivative works of the Software, unless such copies or derivative
|
|
||||||
works are solely in the form of machine-executable object code generated by
|
|
||||||
a source language processor.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
|
||||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
|
||||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
|
||||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
# tomlc99
|
# tomlc99
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
@ -64,7 +64,7 @@ The translation effort for PolyMC is hosted on [Weblate](https://hosted.weblate.
|
|||||||
|
|
||||||
## Download information
|
## Download information
|
||||||
|
|
||||||
To modify download information or change packaging information send a pull request or issue to the website [Here](https://github.com/PolyMC/polymc.github.io/blob/master/src/download.md)
|
To modify download information or change packaging information send a pull request or issue to the website [here](https://github.com/PolyMC/polymc.github.io/tree/master/src/download).
|
||||||
|
|
||||||
## Forking/Redistributing/Custom builds policy
|
## Forking/Redistributing/Custom builds policy
|
||||||
|
|
||||||
|
16
flake.lock
generated
16
flake.lock
generated
@ -19,26 +19,26 @@
|
|||||||
"libnbtplusplus": {
|
"libnbtplusplus": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1591558203,
|
"lastModified": 1650031308,
|
||||||
"narHash": "sha256-QgvNvaoFflCXEPCCFBCeZvYTpuiwScBG7EosUgFwFNQ=",
|
"narHash": "sha256-TvVOjkUobYJD9itQYueELJX3wmecvEdCbJ0FinW2mL4=",
|
||||||
"owner": "multimc",
|
"owner": "PolyMC",
|
||||||
"repo": "libnbtplusplus",
|
"repo": "libnbtplusplus",
|
||||||
"rev": "dc72a20b7efd304d12af2025223fad07b4b78464",
|
"rev": "2203af7eeb48c45398139b583615134efd8d407f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "multimc",
|
"owner": "PolyMC",
|
||||||
"repo": "libnbtplusplus",
|
"repo": "libnbtplusplus",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1654665288,
|
"lastModified": 1658119717,
|
||||||
"narHash": "sha256-7blJpfoZEu7GKb84uh3io/5eSJNdaagXD9d15P9iQMs=",
|
"narHash": "sha256-4upOZIQQ7Bc4CprqnHsKnqYfw+arJeAuU+QcpjYBXW0=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "43ecbe7840d155fa933ee8a500fb00dbbc651fc8",
|
"rev": "9eb60f25aff0d2218c848dd4574a0ab5e296cabe",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||||
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
|
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
|
||||||
libnbtplusplus = { url = "github:multimc/libnbtplusplus"; flake = false; };
|
libnbtplusplus = { url = "github:PolyMC/libnbtplusplus"; flake = false; };
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, libnbtplusplus, ... }:
|
outputs = { self, nixpkgs, libnbtplusplus, ... }:
|
||||||
|
@ -117,6 +117,7 @@ set(NET_SOURCES
|
|||||||
net/NetAction.h
|
net/NetAction.h
|
||||||
net/NetJob.cpp
|
net/NetJob.cpp
|
||||||
net/NetJob.h
|
net/NetJob.h
|
||||||
|
net/NetUtils.h
|
||||||
net/PasteUpload.cpp
|
net/PasteUpload.cpp
|
||||||
net/PasteUpload.h
|
net/PasteUpload.h
|
||||||
net/Sink.h
|
net/Sink.h
|
||||||
@ -990,7 +991,6 @@ target_link_libraries(Launcher_logic
|
|||||||
Launcher_murmur2
|
Launcher_murmur2
|
||||||
nbt++
|
nbt++
|
||||||
${ZLIB_LIBRARIES}
|
${ZLIB_LIBRARIES}
|
||||||
optional-bare
|
|
||||||
tomlc99
|
tomlc99
|
||||||
BuildConfig
|
BuildConfig
|
||||||
Katabasis
|
Katabasis
|
||||||
|
@ -35,25 +35,25 @@
|
|||||||
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QSaveFile>
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QDebug>
|
#include <QSaveFile>
|
||||||
#include <QUrl>
|
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
#include <windows.h>
|
|
||||||
#include <string>
|
|
||||||
#include <sys/utime.h>
|
|
||||||
#include <winnls.h>
|
|
||||||
#include <shobjidl.h>
|
|
||||||
#include <objbase.h>
|
#include <objbase.h>
|
||||||
#include <objidl.h>
|
#include <objidl.h>
|
||||||
#include <shlguid.h>
|
#include <shlguid.h>
|
||||||
#include <shlobj.h>
|
#include <shlobj.h>
|
||||||
|
#include <shobjidl.h>
|
||||||
|
#include <sys/utime.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winnls.h>
|
||||||
|
#include <string>
|
||||||
#else
|
#else
|
||||||
#include <utime.h>
|
#include <utime.h>
|
||||||
#endif
|
#endif
|
||||||
@ -62,10 +62,8 @@ namespace FS {
|
|||||||
|
|
||||||
void ensureExists(const QDir& dir)
|
void ensureExists(const QDir& dir)
|
||||||
{
|
{
|
||||||
if (!QDir().mkpath(dir.absolutePath()))
|
if (!QDir().mkpath(dir.absolutePath())) {
|
||||||
{
|
throw FileSystemException("Unable to create folder " + dir.dirName() + " (" + dir.absolutePath() + ")");
|
||||||
throw FileSystemException("Unable to create folder " + dir.dirName() + " (" +
|
|
||||||
dir.absolutePath() + ")");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,38 +71,28 @@ void write(const QString &filename, const QByteArray &data)
|
|||||||
{
|
{
|
||||||
ensureExists(QFileInfo(filename).dir());
|
ensureExists(QFileInfo(filename).dir());
|
||||||
QSaveFile file(filename);
|
QSaveFile file(filename);
|
||||||
if (!file.open(QSaveFile::WriteOnly))
|
if (!file.open(QSaveFile::WriteOnly)) {
|
||||||
{
|
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
||||||
throw FileSystemException("Couldn't open " + filename + " for writing: " +
|
|
||||||
file.errorString());
|
|
||||||
}
|
}
|
||||||
if (data.size() != file.write(data))
|
if (data.size() != file.write(data)) {
|
||||||
{
|
throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
|
||||||
throw FileSystemException("Error writing data to " + filename + ": " +
|
|
||||||
file.errorString());
|
|
||||||
}
|
}
|
||||||
if (!file.commit())
|
if (!file.commit()) {
|
||||||
{
|
throw FileSystemException("Error while committing data to " + filename + ": " + file.errorString());
|
||||||
throw FileSystemException("Error while committing data to " + filename + ": " +
|
|
||||||
file.errorString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray read(const QString& filename)
|
QByteArray read(const QString& filename)
|
||||||
{
|
{
|
||||||
QFile file(filename);
|
QFile file(filename);
|
||||||
if (!file.open(QFile::ReadOnly))
|
if (!file.open(QFile::ReadOnly)) {
|
||||||
{
|
throw FileSystemException("Unable to open " + filename + " for reading: " + file.errorString());
|
||||||
throw FileSystemException("Unable to open " + filename + " for reading: " +
|
|
||||||
file.errorString());
|
|
||||||
}
|
}
|
||||||
const qint64 size = file.size();
|
const qint64 size = file.size();
|
||||||
QByteArray data(int(size), 0);
|
QByteArray data(int(size), 0);
|
||||||
const qint64 ret = file.read(data.data(), size);
|
const qint64 ret = file.read(data.data(), size);
|
||||||
if (ret == -1 || ret != size)
|
if (ret == -1 || ret != size) {
|
||||||
{
|
throw FileSystemException("Error reading data from " + filename + ": " + file.errorString());
|
||||||
throw FileSystemException("Error reading data from " + filename + ": " +
|
|
||||||
file.errorString());
|
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -152,52 +140,39 @@ bool copy::operator()(const QString &offset)
|
|||||||
if (!currentSrc.exists())
|
if (!currentSrc.exists())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(!m_followSymlinks && currentSrc.isSymLink())
|
if (!m_followSymlinks && currentSrc.isSymLink()) {
|
||||||
{
|
|
||||||
qDebug() << "creating symlink" << src << " - " << dst;
|
qDebug() << "creating symlink" << src << " - " << dst;
|
||||||
if (!ensureFilePathExists(dst))
|
if (!ensureFilePathExists(dst)) {
|
||||||
{
|
|
||||||
qWarning() << "Cannot create path!";
|
qWarning() << "Cannot create path!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return QFile::link(currentSrc.symLinkTarget(), dst);
|
return QFile::link(currentSrc.symLinkTarget(), dst);
|
||||||
}
|
} else if (currentSrc.isFile()) {
|
||||||
else if(currentSrc.isFile())
|
|
||||||
{
|
|
||||||
qDebug() << "copying file" << src << " - " << dst;
|
qDebug() << "copying file" << src << " - " << dst;
|
||||||
if (!ensureFilePathExists(dst))
|
if (!ensureFilePathExists(dst)) {
|
||||||
{
|
|
||||||
qWarning() << "Cannot create path!";
|
qWarning() << "Cannot create path!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return QFile::copy(src, dst);
|
return QFile::copy(src, dst);
|
||||||
}
|
} else if (currentSrc.isDir()) {
|
||||||
else if(currentSrc.isDir())
|
|
||||||
{
|
|
||||||
qDebug() << "recursing" << offset;
|
qDebug() << "recursing" << offset;
|
||||||
if (!ensureFolderPathExists(dst))
|
if (!ensureFolderPathExists(dst)) {
|
||||||
{
|
|
||||||
qWarning() << "Cannot create path!";
|
qWarning() << "Cannot create path!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QDir currentDir(src);
|
QDir currentDir(src);
|
||||||
for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System))
|
for (auto& f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) {
|
||||||
{
|
|
||||||
auto inner_offset = PathCombine(offset, f);
|
auto inner_offset = PathCombine(offset, f);
|
||||||
// ignore and skip stuff that matches the blacklist.
|
// ignore and skip stuff that matches the blacklist.
|
||||||
if(m_blacklist && m_blacklist->matches(inner_offset))
|
if (m_blacklist && m_blacklist->matches(inner_offset)) {
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(!operator()(inner_offset))
|
if (!operator()(inner_offset)) {
|
||||||
{
|
|
||||||
qWarning() << "Failed to copy" << inner_offset;
|
qWarning() << "Failed to copy" << inner_offset;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
qCritical() << "Copy ERROR: Unknown filesystem object:" << src;
|
qCritical() << "Copy ERROR: Unknown filesystem object:" << src;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -214,49 +189,35 @@ bool deletePath(QString path)
|
|||||||
|
|
||||||
QDir dir(path);
|
QDir dir(path);
|
||||||
|
|
||||||
if (!dir.exists())
|
if (!dir.exists()) {
|
||||||
{
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
|
auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst);
|
||||||
QDir::AllDirs | QDir::Files,
|
|
||||||
QDir::DirsFirst);
|
|
||||||
|
|
||||||
for(auto & info: allEntries)
|
for (auto& info : allEntries) {
|
||||||
{
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath());
|
QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath());
|
||||||
auto wString = nativePath.toStdWString();
|
auto wString = nativePath.toStdWString();
|
||||||
DWORD dwAttrs = GetFileAttributesW(wString.c_str());
|
DWORD dwAttrs = GetFileAttributesW(wString.c_str());
|
||||||
// Windows: check for junctions, reparse points and other nasty things of that sort
|
// Windows: check for junctions, reparse points and other nasty things of that sort
|
||||||
if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT)
|
if (dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||||
{
|
if (info.isFile()) {
|
||||||
if (info.isFile())
|
|
||||||
{
|
|
||||||
OK &= QFile::remove(info.absoluteFilePath());
|
OK &= QFile::remove(info.absoluteFilePath());
|
||||||
}
|
} else if (info.isDir()) {
|
||||||
else if (info.isDir())
|
|
||||||
{
|
|
||||||
OK &= dir.rmdir(info.absoluteFilePath());
|
OK &= dir.rmdir(info.absoluteFilePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// We do not trust Qt with reparse points, but do trust it with unix symlinks.
|
// We do not trust Qt with reparse points, but do trust it with unix symlinks.
|
||||||
if(info.isSymLink())
|
if (info.isSymLink()) {
|
||||||
{
|
|
||||||
OK &= QFile::remove(info.absoluteFilePath());
|
OK &= QFile::remove(info.absoluteFilePath());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
else if (info.isDir())
|
else if (info.isDir()) {
|
||||||
{
|
|
||||||
OK &= deletePath(info.absoluteFilePath());
|
OK &= deletePath(info.absoluteFilePath());
|
||||||
}
|
} else if (info.isFile()) {
|
||||||
else if (info.isFile())
|
|
||||||
{
|
|
||||||
OK &= QFile::remove(info.absoluteFilePath());
|
OK &= QFile::remove(info.absoluteFilePath());
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
OK = false;
|
OK = false;
|
||||||
qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath();
|
qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath();
|
||||||
}
|
}
|
||||||
@ -265,6 +226,14 @@ bool deletePath(QString path)
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool trash(QString path, QString *pathInTrash = nullptr)
|
||||||
|
{
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
return QFile::moveToTrash(path, pathInTrash);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
QString PathCombine(const QString& path1, const QString& path2)
|
QString PathCombine(const QString& path1, const QString& path2)
|
||||||
{
|
{
|
||||||
@ -292,17 +261,14 @@ QString AbsolutePath(QString path)
|
|||||||
|
|
||||||
QString ResolveExecutable(QString path)
|
QString ResolveExecutable(QString path)
|
||||||
{
|
{
|
||||||
if (path.isEmpty())
|
if (path.isEmpty()) {
|
||||||
{
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if(!path.contains('/'))
|
if (!path.contains('/')) {
|
||||||
{
|
|
||||||
path = QStandardPaths::findExecutable(path);
|
path = QStandardPaths::findExecutable(path);
|
||||||
}
|
}
|
||||||
QFileInfo pathInfo(path);
|
QFileInfo pathInfo(path);
|
||||||
if(!pathInfo.exists() || !pathInfo.isExecutable())
|
if (!pathInfo.exists() || !pathInfo.isExecutable()) {
|
||||||
{
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
return pathInfo.absoluteFilePath();
|
return pathInfo.absoluteFilePath();
|
||||||
@ -322,12 +288,9 @@ QString NormalizePath(QString path)
|
|||||||
QDir b(path);
|
QDir b(path);
|
||||||
QString newAbsolute = b.absolutePath();
|
QString newAbsolute = b.absolutePath();
|
||||||
|
|
||||||
if (newAbsolute.startsWith(currentAbsolute))
|
if (newAbsolute.startsWith(currentAbsolute)) {
|
||||||
{
|
|
||||||
return a.relativeFilePath(newAbsolute);
|
return a.relativeFilePath(newAbsolute);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return newAbsolute;
|
return newAbsolute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,10 +299,8 @@ QString badFilenameChars = "\"\\/?<>:;*|!+\r\n";
|
|||||||
|
|
||||||
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
|
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < string.length(); i++)
|
for (int i = 0; i < string.length(); i++) {
|
||||||
{
|
if (badFilenameChars.contains(string[i])) {
|
||||||
if (badFilenameChars.contains(string[i]))
|
|
||||||
{
|
|
||||||
string[i] = replaceWith;
|
string[i] = replaceWith;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -351,15 +312,12 @@ QString DirNameFromString(QString string, QString inDir)
|
|||||||
int num = 0;
|
int num = 0;
|
||||||
QString baseName = RemoveInvalidFilenameChars(string, '-');
|
QString baseName = RemoveInvalidFilenameChars(string, '-');
|
||||||
QString dirName;
|
QString dirName;
|
||||||
do
|
do {
|
||||||
{
|
if (num == 0) {
|
||||||
if(num == 0)
|
|
||||||
{
|
|
||||||
dirName = baseName;
|
dirName = baseName;
|
||||||
}
|
} else {
|
||||||
else
|
dirName = baseName + QString::number(num);
|
||||||
{
|
;
|
||||||
dirName = baseName + QString::number(num);;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's over 9000
|
// If it's over 9000
|
||||||
@ -383,36 +341,31 @@ bool checkProblemticPathJava(QDir folder)
|
|||||||
|
|
||||||
bool called_coinit = false;
|
bool called_coinit = false;
|
||||||
|
|
||||||
HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args)
|
HRESULT CreateLink(LPCCH linkPath, LPCWSTR targetPath, LPCWSTR args)
|
||||||
{
|
{
|
||||||
HRESULT hres;
|
HRESULT hres;
|
||||||
|
|
||||||
if (!called_coinit)
|
if (!called_coinit) {
|
||||||
{
|
|
||||||
hres = CoInitialize(NULL);
|
hres = CoInitialize(NULL);
|
||||||
called_coinit = true;
|
called_coinit = true;
|
||||||
|
|
||||||
if (!SUCCEEDED(hres))
|
if (!SUCCEEDED(hres)) {
|
||||||
{
|
|
||||||
qWarning("Failed to initialize COM. Error 0x%08lX", hres);
|
qWarning("Failed to initialize COM. Error 0x%08lX", hres);
|
||||||
return hres;
|
return hres;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IShellLinkA *link;
|
IShellLink* link;
|
||||||
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
|
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&link);
|
||||||
(LPVOID *)&link);
|
|
||||||
|
|
||||||
if (SUCCEEDED(hres))
|
if (SUCCEEDED(hres)) {
|
||||||
{
|
|
||||||
IPersistFile* persistFile;
|
IPersistFile* persistFile;
|
||||||
|
|
||||||
link->SetPath(targetPath);
|
link->SetPath(targetPath);
|
||||||
link->SetArguments(args);
|
link->SetArguments(args);
|
||||||
|
|
||||||
hres = link->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile);
|
hres = link->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile);
|
||||||
if (SUCCEEDED(hres))
|
if (SUCCEEDED(hres)) {
|
||||||
{
|
|
||||||
WCHAR wstr[MAX_PATH];
|
WCHAR wstr[MAX_PATH];
|
||||||
|
|
||||||
MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH);
|
MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH);
|
||||||
@ -433,8 +386,7 @@ QString getDesktopDir()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cross-platform Shortcut creation
|
// Cross-platform Shortcut creation
|
||||||
bool createShortCut(QString location, QString dest, QStringList args, QString name,
|
bool createShortCut(QString location, QString dest, QStringList args, QString name, QString icon)
|
||||||
QString icon)
|
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||||
location = PathCombine(location, name + ".desktop");
|
location = PathCombine(location, name + ".desktop");
|
||||||
@ -459,8 +411,7 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
|
|||||||
stream.flush();
|
stream.flush();
|
||||||
f.close();
|
f.close();
|
||||||
|
|
||||||
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup |
|
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
|
||||||
QFileDevice::ExeOther);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
#elif defined Q_OS_WIN
|
#elif defined Q_OS_WIN
|
||||||
|
@ -41,11 +41,9 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFlags>
|
#include <QFlags>
|
||||||
|
|
||||||
namespace FS
|
namespace FS {
|
||||||
{
|
|
||||||
|
|
||||||
class FileSystemException : public ::Exception
|
class FileSystemException : public ::Exception {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
FileSystemException(const QString& message) : Exception(message) {}
|
FileSystemException(const QString& message) : Exception(message) {}
|
||||||
};
|
};
|
||||||
@ -77,8 +75,7 @@ bool ensureFilePathExists(QString filenamepath);
|
|||||||
*/
|
*/
|
||||||
bool ensureFolderPathExists(QString filenamepath);
|
bool ensureFolderPathExists(QString filenamepath);
|
||||||
|
|
||||||
class copy
|
class copy {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
copy(const QString& src, const QString& dst)
|
copy(const QString& src, const QString& dst)
|
||||||
{
|
{
|
||||||
@ -95,10 +92,7 @@ public:
|
|||||||
m_blacklist = filter;
|
m_blacklist = filter;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
bool operator()()
|
bool operator()() { return operator()(QString()); }
|
||||||
{
|
|
||||||
return operator()(QString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool operator()(const QString& offset);
|
bool operator()(const QString& offset);
|
||||||
@ -115,6 +109,11 @@ private:
|
|||||||
*/
|
*/
|
||||||
bool deletePath(QString path);
|
bool deletePath(QString path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trash a folder / file
|
||||||
|
*/
|
||||||
|
bool trash(QString path, QString *pathInTrash);
|
||||||
|
|
||||||
QString PathCombine(const QString& path1, const QString& path2);
|
QString PathCombine(const QString& path1, const QString& path2);
|
||||||
QString PathCombine(const QString& path1, const QString& path2, const QString& path3);
|
QString PathCombine(const QString& path1, const QString& path2, const QString& path3);
|
||||||
QString PathCombine(const QString& path1, const QString& path2, const QString& path3, const QString& path4);
|
QString PathCombine(const QString& path1, const QString& path2, const QString& path3, const QString& path4);
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
#include "modplatform/flame/PackManifest.h"
|
#include "modplatform/flame/PackManifest.h"
|
||||||
|
|
||||||
#include <nonstd/optional>
|
#include <optional>
|
||||||
|
|
||||||
class QuaZip;
|
class QuaZip;
|
||||||
namespace Flame
|
namespace Flame
|
||||||
@ -90,8 +90,8 @@ private: /* data */
|
|||||||
QString m_archivePath;
|
QString m_archivePath;
|
||||||
bool m_downloadRequired = false;
|
bool m_downloadRequired = false;
|
||||||
std::unique_ptr<QuaZip> m_packZip;
|
std::unique_ptr<QuaZip> m_packZip;
|
||||||
QFuture<nonstd::optional<QStringList>> m_extractFuture;
|
QFuture<std::optional<QStringList>> m_extractFuture;
|
||||||
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
|
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
||||||
QVector<Flame::File> m_blockedMods;
|
QVector<Flame::File> m_blockedMods;
|
||||||
enum class ModpackType{
|
enum class ModpackType{
|
||||||
Unknown,
|
Unknown,
|
||||||
|
@ -33,30 +33,32 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QSet>
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QThread>
|
|
||||||
#include <QTextStream>
|
|
||||||
#include <QXmlStreamReader>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
#include <QUuid>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QStack>
|
||||||
|
#include <QPair>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QUuid>
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
|
||||||
#include "InstanceList.h"
|
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include "InstanceTask.h"
|
|
||||||
#include "settings/INISettingsObject.h"
|
|
||||||
#include "NullInstance.h"
|
|
||||||
#include "minecraft/MinecraftInstance.h"
|
|
||||||
#include "FileSystem.h"
|
|
||||||
#include "ExponentialSeries.h"
|
#include "ExponentialSeries.h"
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "InstanceList.h"
|
||||||
|
#include "InstanceTask.h"
|
||||||
|
#include "NullInstance.h"
|
||||||
#include "WatchLock.h"
|
#include "WatchLock.h"
|
||||||
|
#include "minecraft/MinecraftInstance.h"
|
||||||
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
@ -69,8 +71,7 @@ InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir,
|
|||||||
{
|
{
|
||||||
resumeWatch();
|
resumeWatch();
|
||||||
// Create aand normalize path
|
// Create aand normalize path
|
||||||
if (!QDir::current().exists(instDir))
|
if (!QDir::current().exists(instDir)) {
|
||||||
{
|
|
||||||
QDir::current().mkpath(instDir);
|
QDir::current().mkpath(instDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,9 +84,7 @@ InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir,
|
|||||||
m_watcher->addPath(m_instDir);
|
m_watcher->addPath(m_instDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
InstanceList::~InstanceList()
|
InstanceList::~InstanceList() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Qt::DropActions InstanceList::supportedDragActions() const
|
Qt::DropActions InstanceList::supportedDragActions() const
|
||||||
{
|
{
|
||||||
@ -130,7 +129,6 @@ QMimeData * InstanceList::mimeData(const QModelIndexList& indexes) const
|
|||||||
return mimeData;
|
return mimeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int InstanceList::rowCount(const QModelIndex& parent) const
|
int InstanceList::rowCount(const QModelIndex& parent) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(parent);
|
Q_UNUSED(parent);
|
||||||
@ -147,8 +145,7 @@ QModelIndex InstanceList::index(int row, int column, const QModelIndex &parent)
|
|||||||
|
|
||||||
QVariant InstanceList::data(const QModelIndex& index, int role) const
|
QVariant InstanceList::data(const QModelIndex& index, int role) const
|
||||||
{
|
{
|
||||||
if (!index.isValid())
|
if (!index.isValid()) {
|
||||||
{
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
|
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
|
||||||
@ -193,18 +190,15 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
|
|||||||
|
|
||||||
bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role)
|
bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||||
{
|
{
|
||||||
if (!index.isValid())
|
if (!index.isValid()) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(role != Qt::EditRole)
|
if (role != Qt::EditRole) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BaseInstance* pdata = static_cast<BaseInstance*>(index.internalPointer());
|
BaseInstance* pdata = static_cast<BaseInstance*>(index.internalPointer());
|
||||||
auto newName = value.toString();
|
auto newName = value.toString();
|
||||||
if(pdata->name() == newName)
|
if (pdata->name() == newName) {
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
pdata->setName(newName);
|
pdata->setName(newName);
|
||||||
@ -214,8 +208,7 @@ bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int
|
|||||||
Qt::ItemFlags InstanceList::flags(const QModelIndex& index) const
|
Qt::ItemFlags InstanceList::flags(const QModelIndex& index) const
|
||||||
{
|
{
|
||||||
Qt::ItemFlags f;
|
Qt::ItemFlags f;
|
||||||
if (index.isValid())
|
if (index.isValid()) {
|
||||||
{
|
|
||||||
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
||||||
}
|
}
|
||||||
return f;
|
return f;
|
||||||
@ -224,13 +217,11 @@ Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
|
|||||||
GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
|
GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
|
||||||
{
|
{
|
||||||
auto inst = getInstanceById(id);
|
auto inst = getInstanceById(id);
|
||||||
if(!inst)
|
if (!inst) {
|
||||||
{
|
|
||||||
return GroupId();
|
return GroupId();
|
||||||
}
|
}
|
||||||
auto iter = m_instanceGroupIndex.find(inst->id());
|
auto iter = m_instanceGroupIndex.find(inst->id());
|
||||||
if(iter != m_instanceGroupIndex.end())
|
if (iter != m_instanceGroupIndex.end()) {
|
||||||
{
|
|
||||||
return *iter;
|
return *iter;
|
||||||
}
|
}
|
||||||
return GroupId();
|
return GroupId();
|
||||||
@ -239,30 +230,24 @@ GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
|
|||||||
void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||||
{
|
{
|
||||||
auto inst = getInstanceById(id);
|
auto inst = getInstanceById(id);
|
||||||
if(!inst)
|
if (!inst) {
|
||||||
{
|
|
||||||
qDebug() << "Attempt to set a null instance's group";
|
qDebug() << "Attempt to set a null instance's group";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
auto iter = m_instanceGroupIndex.find(inst->id());
|
auto iter = m_instanceGroupIndex.find(inst->id());
|
||||||
if(iter != m_instanceGroupIndex.end())
|
if (iter != m_instanceGroupIndex.end()) {
|
||||||
{
|
if (*iter != name) {
|
||||||
if(*iter != name)
|
|
||||||
{
|
|
||||||
*iter = name;
|
*iter = name;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
changed = true;
|
changed = true;
|
||||||
m_instanceGroupIndex[id] = name;
|
m_instanceGroupIndex[id] = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(changed)
|
if (changed) {
|
||||||
{
|
|
||||||
m_groupNameCache.insert(name);
|
m_groupNameCache.insert(name);
|
||||||
auto idx = getInstIndex(inst.get());
|
auto idx = getInstIndex(inst.get());
|
||||||
emit dataChanged(index(idx), index(idx), { GroupRole });
|
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||||
@ -279,24 +264,20 @@ void InstanceList::deleteGroup(const QString& name)
|
|||||||
{
|
{
|
||||||
bool removed = false;
|
bool removed = false;
|
||||||
qDebug() << "Delete group" << name;
|
qDebug() << "Delete group" << name;
|
||||||
for(auto & instance: m_instances)
|
for (auto& instance : m_instances) {
|
||||||
{
|
|
||||||
const auto& instID = instance->id();
|
const auto& instID = instance->id();
|
||||||
auto instGroupName = getInstanceGroup(instID);
|
auto instGroupName = getInstanceGroup(instID);
|
||||||
if(instGroupName == name)
|
if (instGroupName == name) {
|
||||||
{
|
|
||||||
m_instanceGroupIndex.remove(instID);
|
m_instanceGroupIndex.remove(instID);
|
||||||
qDebug() << "Remove" << instID << "from group" << name;
|
qDebug() << "Remove" << instID << "from group" << name;
|
||||||
removed = true;
|
removed = true;
|
||||||
auto idx = getInstIndex(instance.get());
|
auto idx = getInstIndex(instance.get());
|
||||||
if(idx > 0)
|
if (idx > 0) {
|
||||||
{
|
|
||||||
emit dataChanged(index(idx), index(idx), { GroupRole });
|
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(removed)
|
if (removed) {
|
||||||
{
|
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -306,23 +287,75 @@ bool InstanceList::isGroupCollapsed(const QString& group)
|
|||||||
return m_collapsedGroups.contains(group);
|
return m_collapsedGroups.contains(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InstanceList::trashInstance(const InstanceId& id)
|
||||||
|
{
|
||||||
|
auto inst = getInstanceById(id);
|
||||||
|
if (!inst) {
|
||||||
|
qDebug() << "Cannot trash instance" << id << ". No such instance is present (deleted externally?).";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cachedGroupId = m_instanceGroupIndex[id];
|
||||||
|
|
||||||
|
qDebug() << "Will trash instance" << id;
|
||||||
|
QString trashedLoc;
|
||||||
|
|
||||||
|
if (m_instanceGroupIndex.remove(id)) {
|
||||||
|
saveGroupList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FS::trash(inst->instanceRoot(), &trashedLoc)) {
|
||||||
|
qDebug() << "Trash of instance" << id << "has not been completely successfully...";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Instance" << id << "has been trashed by the launcher.";
|
||||||
|
m_trashHistory.push({id, inst->instanceRoot(), trashedLoc, cachedGroupId});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InstanceList::trashedSomething() {
|
||||||
|
return !m_trashHistory.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceList::undoTrashInstance() {
|
||||||
|
if (m_trashHistory.empty()) {
|
||||||
|
qWarning() << "Nothing to recover from trash.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto top = m_trashHistory.pop();
|
||||||
|
|
||||||
|
while (QDir(top.polyPath).exists()) {
|
||||||
|
top.id += "1";
|
||||||
|
top.polyPath += "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Moving" << top.trashPath << "back to" << top.polyPath;
|
||||||
|
QFile(top.trashPath).rename(top.polyPath);
|
||||||
|
|
||||||
|
m_instanceGroupIndex[top.id] = top.groupName;
|
||||||
|
m_groupNameCache.insert(top.groupName);
|
||||||
|
|
||||||
|
saveGroupList();
|
||||||
|
emit instancesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
void InstanceList::deleteInstance(const InstanceId& id)
|
void InstanceList::deleteInstance(const InstanceId& id)
|
||||||
{
|
{
|
||||||
auto inst = getInstanceById(id);
|
auto inst = getInstanceById(id);
|
||||||
if(!inst)
|
if (!inst) {
|
||||||
{
|
|
||||||
qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
|
qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(m_instanceGroupIndex.remove(id))
|
if (m_instanceGroupIndex.remove(id)) {
|
||||||
{
|
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Will delete instance" << id;
|
qDebug() << "Will delete instance" << id;
|
||||||
if(!FS::deletePath(inst->instanceRoot()))
|
if (!FS::deletePath(inst->instanceRoot())) {
|
||||||
{
|
|
||||||
qWarning() << "Deletion of instance" << id << "has not been completely successful ...";
|
qWarning() << "Deletion of instance" << id << "has not been completely successful ...";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -334,11 +367,9 @@ static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &
|
|||||||
{
|
{
|
||||||
QMap<InstanceId, InstanceLocator> out;
|
QMap<InstanceId, InstanceLocator> out;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for(auto & item: list)
|
for (auto& item : list) {
|
||||||
{
|
|
||||||
auto id = item->id();
|
auto id = item->id();
|
||||||
if(out.contains(id))
|
if (out.contains(id)) {
|
||||||
{
|
|
||||||
qWarning() << "Duplicate ID" << id << "in instance list";
|
qWarning() << "Duplicate ID" << id << "in instance list";
|
||||||
}
|
}
|
||||||
out[id] = std::make_pair(item, i);
|
out[id] = std::make_pair(item, i);
|
||||||
@ -352,19 +383,16 @@ QList< InstanceId > InstanceList::discoverInstances()
|
|||||||
qDebug() << "Discovering instances in" << m_instDir;
|
qDebug() << "Discovering instances in" << m_instDir;
|
||||||
QList<InstanceId> out;
|
QList<InstanceId> out;
|
||||||
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks);
|
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks);
|
||||||
while (iter.hasNext())
|
while (iter.hasNext()) {
|
||||||
{
|
|
||||||
QString subDir = iter.next();
|
QString subDir = iter.next();
|
||||||
QFileInfo dirInfo(subDir);
|
QFileInfo dirInfo(subDir);
|
||||||
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
|
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
|
||||||
continue;
|
continue;
|
||||||
// if it is a symlink, ignore it if it goes to the instance folder
|
// if it is a symlink, ignore it if it goes to the instance folder
|
||||||
if(dirInfo.isSymLink())
|
if (dirInfo.isSymLink()) {
|
||||||
{
|
|
||||||
QFileInfo targetInfo(dirInfo.symLinkTarget());
|
QFileInfo targetInfo(dirInfo.symLinkTarget());
|
||||||
QFileInfo instDirInfo(m_instDir);
|
QFileInfo instDirInfo(m_instDir);
|
||||||
if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath())
|
if (targetInfo.canonicalPath() == instDirInfo.canonicalFilePath()) {
|
||||||
{
|
|
||||||
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
|
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -388,74 +416,56 @@ InstanceList::InstListError InstanceList::loadList()
|
|||||||
|
|
||||||
QList<InstancePtr> newList;
|
QList<InstancePtr> newList;
|
||||||
|
|
||||||
for(auto & id: discoverInstances())
|
for (auto& id : discoverInstances()) {
|
||||||
{
|
if (existingIds.contains(id)) {
|
||||||
if(existingIds.contains(id))
|
|
||||||
{
|
|
||||||
auto instPair = existingIds[id];
|
auto instPair = existingIds[id];
|
||||||
existingIds.remove(id);
|
existingIds.remove(id);
|
||||||
qDebug() << "Should keep and soft-reload" << id;
|
qDebug() << "Should keep and soft-reload" << id;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
InstancePtr instPtr = loadInstance(id);
|
InstancePtr instPtr = loadInstance(id);
|
||||||
if(instPtr)
|
if (instPtr) {
|
||||||
{
|
|
||||||
newList.append(instPtr);
|
newList.append(instPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
|
// TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
|
||||||
if(!existingIds.isEmpty())
|
if (!existingIds.isEmpty()) {
|
||||||
{
|
|
||||||
// get the list of removed instances and sort it by their original index, from last to first
|
// get the list of removed instances and sort it by their original index, from last to first
|
||||||
auto deadList = existingIds.values();
|
auto deadList = existingIds.values();
|
||||||
auto orderSortPredicate = [](const InstanceLocator & a, const InstanceLocator & b) -> bool
|
auto orderSortPredicate = [](const InstanceLocator& a, const InstanceLocator& b) -> bool { return a.second > b.second; };
|
||||||
{
|
|
||||||
return a.second > b.second;
|
|
||||||
};
|
|
||||||
std::sort(deadList.begin(), deadList.end(), orderSortPredicate);
|
std::sort(deadList.begin(), deadList.end(), orderSortPredicate);
|
||||||
// remove the contiguous ranges of rows
|
// remove the contiguous ranges of rows
|
||||||
int front_bookmark = -1;
|
int front_bookmark = -1;
|
||||||
int back_bookmark = -1;
|
int back_bookmark = -1;
|
||||||
int currentItem = -1;
|
int currentItem = -1;
|
||||||
auto removeNow = [&]()
|
auto removeNow = [&]() {
|
||||||
{
|
|
||||||
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
|
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
|
||||||
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
|
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
front_bookmark = -1;
|
front_bookmark = -1;
|
||||||
back_bookmark = currentItem;
|
back_bookmark = currentItem;
|
||||||
};
|
};
|
||||||
for(auto & removedItem: deadList)
|
for (auto& removedItem : deadList) {
|
||||||
{
|
|
||||||
auto instPtr = removedItem.first;
|
auto instPtr = removedItem.first;
|
||||||
instPtr->invalidate();
|
instPtr->invalidate();
|
||||||
currentItem = removedItem.second;
|
currentItem = removedItem.second;
|
||||||
if(back_bookmark == -1)
|
if (back_bookmark == -1) {
|
||||||
{
|
|
||||||
// no bookmark yet
|
// no bookmark yet
|
||||||
back_bookmark = currentItem;
|
back_bookmark = currentItem;
|
||||||
}
|
} else if (currentItem == front_bookmark - 1) {
|
||||||
else if(currentItem == front_bookmark - 1)
|
|
||||||
{
|
|
||||||
// part of contiguous sequence, continue
|
// part of contiguous sequence, continue
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// seam between previous and current item
|
// seam between previous and current item
|
||||||
removeNow();
|
removeNow();
|
||||||
}
|
}
|
||||||
front_bookmark = currentItem;
|
front_bookmark = currentItem;
|
||||||
}
|
}
|
||||||
if(back_bookmark != -1)
|
if (back_bookmark != -1) {
|
||||||
{
|
|
||||||
removeNow();
|
removeNow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(newList.size())
|
if (newList.size()) {
|
||||||
{
|
|
||||||
add(newList);
|
add(newList);
|
||||||
}
|
}
|
||||||
m_dirty = false;
|
m_dirty = false;
|
||||||
@ -466,16 +476,14 @@ InstanceList::InstListError InstanceList::loadList()
|
|||||||
void InstanceList::updateTotalPlayTime()
|
void InstanceList::updateTotalPlayTime()
|
||||||
{
|
{
|
||||||
totalPlayTime = 0;
|
totalPlayTime = 0;
|
||||||
for(auto const& itr : m_instances)
|
for (auto const& itr : m_instances) {
|
||||||
{
|
|
||||||
totalPlayTime += itr.get()->totalTimePlayed();
|
totalPlayTime += itr.get()->totalTimePlayed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceList::saveNow()
|
void InstanceList::saveNow()
|
||||||
{
|
{
|
||||||
for(auto & item: m_instances)
|
for (auto& item : m_instances) {
|
||||||
{
|
|
||||||
item->saveNow();
|
item->saveNow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -484,8 +492,7 @@ void InstanceList::add(const QList<InstancePtr> &t)
|
|||||||
{
|
{
|
||||||
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
|
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
|
||||||
m_instances.append(t);
|
m_instances.append(t);
|
||||||
for(auto & ptr : t)
|
for (auto& ptr : t) {
|
||||||
{
|
|
||||||
connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged);
|
connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged);
|
||||||
}
|
}
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
@ -493,14 +500,12 @@ void InstanceList::add(const QList<InstancePtr> &t)
|
|||||||
|
|
||||||
void InstanceList::resumeWatch()
|
void InstanceList::resumeWatch()
|
||||||
{
|
{
|
||||||
if(m_watchLevel > 0)
|
if (m_watchLevel > 0) {
|
||||||
{
|
|
||||||
qWarning() << "Bad suspend level resume in instance list";
|
qWarning() << "Bad suspend level resume in instance list";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_watchLevel++;
|
m_watchLevel++;
|
||||||
if(m_watchLevel > 0 && m_dirty)
|
if (m_watchLevel > 0 && m_dirty) {
|
||||||
{
|
|
||||||
loadList();
|
loadList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -513,8 +518,7 @@ void InstanceList::suspendWatch()
|
|||||||
void InstanceList::providerUpdated()
|
void InstanceList::providerUpdated()
|
||||||
{
|
{
|
||||||
m_dirty = true;
|
m_dirty = true;
|
||||||
if(m_watchLevel == 1)
|
if (m_watchLevel == 1) {
|
||||||
{
|
|
||||||
loadList();
|
loadList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -523,10 +527,8 @@ InstancePtr InstanceList::getInstanceById(QString instId) const
|
|||||||
{
|
{
|
||||||
if (instId.isEmpty())
|
if (instId.isEmpty())
|
||||||
return InstancePtr();
|
return InstancePtr();
|
||||||
for(auto & inst: m_instances)
|
for (auto& inst : m_instances) {
|
||||||
{
|
if (inst->id() == instId) {
|
||||||
if (inst->id() == instId)
|
|
||||||
{
|
|
||||||
return inst;
|
return inst;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -541,10 +543,8 @@ QModelIndex InstanceList::getInstanceIndexById(const QString &id) const
|
|||||||
int InstanceList::getInstIndex(BaseInstance* inst) const
|
int InstanceList::getInstIndex(BaseInstance* inst) const
|
||||||
{
|
{
|
||||||
int count = m_instances.count();
|
int count = m_instances.count();
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++) {
|
||||||
{
|
if (inst == m_instances[i].get()) {
|
||||||
if (inst == m_instances[i].get())
|
|
||||||
{
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -554,8 +554,7 @@ int InstanceList::getInstIndex(BaseInstance *inst) const
|
|||||||
void InstanceList::propertiesChanged(BaseInstance* inst)
|
void InstanceList::propertiesChanged(BaseInstance* inst)
|
||||||
{
|
{
|
||||||
int i = getInstIndex(inst);
|
int i = getInstIndex(inst);
|
||||||
if (i != -1)
|
if (i != -1) {
|
||||||
{
|
|
||||||
emit dataChanged(index(i), index(i));
|
emit dataChanged(index(i), index(i));
|
||||||
updateTotalPlayTime();
|
updateTotalPlayTime();
|
||||||
}
|
}
|
||||||
@ -563,8 +562,7 @@ void InstanceList::propertiesChanged(BaseInstance *inst)
|
|||||||
|
|
||||||
InstancePtr InstanceList::loadInstance(const InstanceId& id)
|
InstancePtr InstanceList::loadInstance(const InstanceId& id)
|
||||||
{
|
{
|
||||||
if(!m_groupsLoaded)
|
if (!m_groupsLoaded) {
|
||||||
{
|
|
||||||
loadGroupList();
|
loadGroupList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -592,34 +590,28 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
|
|||||||
void InstanceList::saveGroupList()
|
void InstanceList::saveGroupList()
|
||||||
{
|
{
|
||||||
qDebug() << "Will save group list now.";
|
qDebug() << "Will save group list now.";
|
||||||
if(!m_instancesProbed)
|
if (!m_instancesProbed) {
|
||||||
{
|
|
||||||
qDebug() << "Group saving prevented because we don't know the full list of instances yet.";
|
qDebug() << "Group saving prevented because we don't know the full list of instances yet.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
WatchLock foo(m_watcher, m_instDir);
|
WatchLock foo(m_watcher, m_instDir);
|
||||||
QString groupFileName = m_instDir + "/instgroups.json";
|
QString groupFileName = m_instDir + "/instgroups.json";
|
||||||
QMap<QString, QSet<QString>> reverseGroupMap;
|
QMap<QString, QSet<QString>> reverseGroupMap;
|
||||||
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++)
|
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) {
|
||||||
{
|
|
||||||
QString id = iter.key();
|
QString id = iter.key();
|
||||||
QString group = iter.value();
|
QString group = iter.value();
|
||||||
if (group.isEmpty())
|
if (group.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
if(!instanceSet.contains(id))
|
if (!instanceSet.contains(id)) {
|
||||||
{
|
|
||||||
qDebug() << "Skipping saving missing instance" << id << "to groups list.";
|
qDebug() << "Skipping saving missing instance" << id << "to groups list.";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reverseGroupMap.count(group))
|
if (!reverseGroupMap.count(group)) {
|
||||||
{
|
|
||||||
QSet<QString> set;
|
QSet<QString> set;
|
||||||
set.insert(id);
|
set.insert(id);
|
||||||
reverseGroupMap[group] = set;
|
reverseGroupMap[group] = set;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
QSet<QString>& set = reverseGroupMap[group];
|
QSet<QString>& set = reverseGroupMap[group];
|
||||||
set.insert(id);
|
set.insert(id);
|
||||||
}
|
}
|
||||||
@ -627,15 +619,13 @@ void InstanceList::saveGroupList()
|
|||||||
QJsonObject toplevel;
|
QJsonObject toplevel;
|
||||||
toplevel.insert("formatVersion", QJsonValue(QString("1")));
|
toplevel.insert("formatVersion", QJsonValue(QString("1")));
|
||||||
QJsonObject groupsArr;
|
QJsonObject groupsArr;
|
||||||
for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++)
|
for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++) {
|
||||||
{
|
|
||||||
auto list = iter.value();
|
auto list = iter.value();
|
||||||
auto name = iter.key();
|
auto name = iter.key();
|
||||||
QJsonObject groupObj;
|
QJsonObject groupObj;
|
||||||
QJsonArray instanceArr;
|
QJsonArray instanceArr;
|
||||||
groupObj.insert("hidden", QJsonValue(m_collapsedGroups.contains(name)));
|
groupObj.insert("hidden", QJsonValue(m_collapsedGroups.contains(name)));
|
||||||
for (auto item : list)
|
for (auto item : list) {
|
||||||
{
|
|
||||||
instanceArr.append(QJsonValue(item));
|
instanceArr.append(QJsonValue(item));
|
||||||
}
|
}
|
||||||
groupObj.insert("instances", instanceArr);
|
groupObj.insert("instances", instanceArr);
|
||||||
@ -643,13 +633,10 @@ void InstanceList::saveGroupList()
|
|||||||
}
|
}
|
||||||
toplevel.insert("groups", groupsArr);
|
toplevel.insert("groups", groupsArr);
|
||||||
QJsonDocument doc(toplevel);
|
QJsonDocument doc(toplevel);
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
FS::write(groupFileName, doc.toJson());
|
FS::write(groupFileName, doc.toJson());
|
||||||
qDebug() << "Group list saved.";
|
qDebug() << "Group list saved.";
|
||||||
}
|
} catch (const FS::FileSystemException& e) {
|
||||||
catch (const FS::FileSystemException &e)
|
|
||||||
{
|
|
||||||
qCritical() << "Failed to write instance group file :" << e.cause();
|
qCritical() << "Failed to write instance group file :" << e.cause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -665,12 +652,9 @@ void InstanceList::loadGroupList()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
QByteArray jsonData;
|
QByteArray jsonData;
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
jsonData = FS::read(groupFileName);
|
jsonData = FS::read(groupFileName);
|
||||||
}
|
} catch (const FS::FileSystemException& e) {
|
||||||
catch (const FS::FileSystemException &e)
|
|
||||||
{
|
|
||||||
qCritical() << "Failed to read instance group file :" << e.cause();
|
qCritical() << "Failed to read instance group file :" << e.cause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -679,8 +663,7 @@ void InstanceList::loadGroupList()
|
|||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
|
||||||
|
|
||||||
// if the json was bad, fail
|
// if the json was bad, fail
|
||||||
if (error.error != QJsonParseError::NoError)
|
if (error.error != QJsonParseError::NoError) {
|
||||||
{
|
|
||||||
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
|
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
|
||||||
.arg(error.errorString(), QString::number(error.offset))
|
.arg(error.errorString(), QString::number(error.offset))
|
||||||
.toUtf8();
|
.toUtf8();
|
||||||
@ -688,8 +671,7 @@ void InstanceList::loadGroupList()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if the root of the json wasn't an object, fail
|
// if the root of the json wasn't an object, fail
|
||||||
if (!jsonDoc.isObject())
|
if (!jsonDoc.isObject()) {
|
||||||
{
|
|
||||||
qWarning() << "Invalid group file. Root entry should be an object.";
|
qWarning() << "Invalid group file. Root entry should be an object.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -701,8 +683,7 @@ void InstanceList::loadGroupList()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Get the groups. if it's not an object, fail
|
// Get the groups. if it's not an object, fail
|
||||||
if (!rootObj.value("groups").isObject())
|
if (!rootObj.value("groups").isObject()) {
|
||||||
{
|
|
||||||
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
|
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -712,21 +693,20 @@ void InstanceList::loadGroupList()
|
|||||||
|
|
||||||
// Iterate through all the groups.
|
// Iterate through all the groups.
|
||||||
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
||||||
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
|
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) {
|
||||||
{
|
|
||||||
QString groupName = iter.key();
|
QString groupName = iter.key();
|
||||||
|
|
||||||
// If not an object, complain and skip to the next one.
|
// If not an object, complain and skip to the next one.
|
||||||
if (!iter.value().isObject())
|
if (!iter.value().isObject()) {
|
||||||
{
|
|
||||||
qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
|
qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject groupObj = iter.value().toObject();
|
QJsonObject groupObj = iter.value().toObject();
|
||||||
if (!groupObj.value("instances").isArray())
|
if (!groupObj.value("instances").isArray()) {
|
||||||
{
|
qWarning() << QString("Group '%1' in the group list is invalid. It should contain an array called 'instances'.")
|
||||||
qWarning() << QString("Group '%1' in the group list is invalid. It should contain an array called 'instances'.").arg(groupName).toUtf8();
|
.arg(groupName)
|
||||||
|
.toUtf8();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -741,8 +721,7 @@ void InstanceList::loadGroupList()
|
|||||||
// Iterate through the list of instances in the group.
|
// Iterate through the list of instances in the group.
|
||||||
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
||||||
|
|
||||||
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++)
|
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++) {
|
||||||
{
|
|
||||||
m_instanceGroupIndex[(*iter2).toString()] = groupName;
|
m_instanceGroupIndex[(*iter2).toString()] = groupName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -760,10 +739,8 @@ void InstanceList::instanceDirContentsChanged(const QString& path)
|
|||||||
void InstanceList::on_InstFolderChanged(const Setting& setting, QVariant value)
|
void InstanceList::on_InstFolderChanged(const Setting& setting, QVariant value)
|
||||||
{
|
{
|
||||||
QString newInstDir = QDir(value.toString()).canonicalPath();
|
QString newInstDir = QDir(value.toString()).canonicalPath();
|
||||||
if(newInstDir != m_instDir)
|
if (newInstDir != m_instDir) {
|
||||||
{
|
if (m_groupsLoaded) {
|
||||||
if(m_groupsLoaded)
|
|
||||||
{
|
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
m_instDir = newInstDir;
|
m_instDir = newInstDir;
|
||||||
@ -783,18 +760,13 @@ void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed)
|
|||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
|
|
||||||
class InstanceStaging : public Task
|
class InstanceStaging : public Task {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
const unsigned minBackoff = 1;
|
const unsigned minBackoff = 1;
|
||||||
const unsigned maxBackoff = 16;
|
const unsigned maxBackoff = 16;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
InstanceStaging (
|
InstanceStaging(InstanceList* parent, Task* child, const QString& stagingPath, const QString& instanceName, const QString& groupName)
|
||||||
InstanceList * parent,
|
|
||||||
Task * child,
|
|
||||||
const QString & stagingPath,
|
|
||||||
const QString& instanceName,
|
|
||||||
const QString& groupName )
|
|
||||||
: backoff(minBackoff, maxBackoff)
|
: backoff(minBackoff, maxBackoff)
|
||||||
{
|
{
|
||||||
m_parent = parent;
|
m_parent = parent;
|
||||||
@ -812,47 +784,36 @@ public:
|
|||||||
|
|
||||||
virtual ~InstanceStaging(){};
|
virtual ~InstanceStaging(){};
|
||||||
|
|
||||||
|
|
||||||
// FIXME/TODO: add ability to abort during instance commit retries
|
// FIXME/TODO: add ability to abort during instance commit retries
|
||||||
bool abort() override
|
bool abort() override
|
||||||
{
|
{
|
||||||
if(m_child && m_child->canAbort())
|
if (m_child && m_child->canAbort()) {
|
||||||
{
|
|
||||||
return m_child->abort();
|
return m_child->abort();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool canAbort() const override
|
bool canAbort() const override
|
||||||
{
|
{
|
||||||
if(m_child && m_child->canAbort())
|
if (m_child && m_child->canAbort()) {
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void executeTask() override
|
virtual void executeTask() override { m_child->start(); }
|
||||||
{
|
QStringList warnings() const override { return m_child->warnings(); }
|
||||||
m_child->start();
|
|
||||||
}
|
|
||||||
QStringList warnings() const override
|
|
||||||
{
|
|
||||||
return m_child->warnings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void childSucceded()
|
void childSucceded()
|
||||||
{
|
{
|
||||||
unsigned sleepTime = backoff();
|
unsigned sleepTime = backoff();
|
||||||
if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName))
|
if (m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) {
|
||||||
{
|
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// we actually failed, retry?
|
// we actually failed, retry?
|
||||||
if(sleepTime == maxBackoff)
|
if (sleepTime == maxBackoff) {
|
||||||
{
|
|
||||||
emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something."));
|
emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -895,8 +856,7 @@ QString InstanceList::getStagedInstancePath()
|
|||||||
QString relPath = FS::PathCombine(tempDir, key);
|
QString relPath = FS::PathCombine(tempDir, key);
|
||||||
QDir rootPath(m_instDir);
|
QDir rootPath(m_instDir);
|
||||||
auto path = FS::PathCombine(m_instDir, relPath);
|
auto path = FS::PathCombine(m_instDir, relPath);
|
||||||
if(!rootPath.mkpath(relPath))
|
if (!rootPath.mkpath(relPath)) {
|
||||||
{
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
@ -913,8 +873,7 @@ bool InstanceList::commitStagedInstance(const QString& path, const QString& inst
|
|||||||
{
|
{
|
||||||
WatchLock lock(m_watcher, m_instDir);
|
WatchLock lock(m_watcher, m_instDir);
|
||||||
QString destination = FS::PathCombine(m_instDir, instID);
|
QString destination = FS::PathCombine(m_instDir, instID);
|
||||||
if(!dir.rename(path, destination))
|
if (!dir.rename(path, destination)) {
|
||||||
{
|
|
||||||
qWarning() << "Failed to move" << path << "to" << destination;
|
qWarning() << "Failed to move" << path << "to" << destination;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -933,7 +892,8 @@ bool InstanceList::destroyStagingPath(const QString& keyPath)
|
|||||||
return FS::deletePath(keyPath);
|
return FS::deletePath(keyPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
int InstanceList::getTotalPlayTime() {
|
int InstanceList::getTotalPlayTime()
|
||||||
|
{
|
||||||
updateTotalPlayTime();
|
updateTotalPlayTime();
|
||||||
return totalPlayTime;
|
return totalPlayTime;
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QStack>
|
||||||
|
#include <QPair>
|
||||||
|
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
|
|
||||||
@ -46,6 +48,12 @@ enum class GroupsState
|
|||||||
Dirty
|
Dirty
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TrashHistoryItem {
|
||||||
|
QString id;
|
||||||
|
QString polyPath;
|
||||||
|
QString trashPath;
|
||||||
|
QString groupName;
|
||||||
|
};
|
||||||
|
|
||||||
class InstanceList : public QAbstractListModel
|
class InstanceList : public QAbstractListModel
|
||||||
{
|
{
|
||||||
@ -102,6 +110,9 @@ public:
|
|||||||
void setInstanceGroup(const InstanceId & id, const GroupId& name);
|
void setInstanceGroup(const InstanceId & id, const GroupId& name);
|
||||||
|
|
||||||
void deleteGroup(const GroupId & name);
|
void deleteGroup(const GroupId & name);
|
||||||
|
bool trashInstance(const InstanceId &id);
|
||||||
|
bool trashedSomething();
|
||||||
|
void undoTrashInstance();
|
||||||
void deleteInstance(const InstanceId & id);
|
void deleteInstance(const InstanceId & id);
|
||||||
|
|
||||||
// Wrap an instance creation task in some more task machinery and make it ready to be used
|
// Wrap an instance creation task in some more task machinery and make it ready to be used
|
||||||
@ -180,4 +191,6 @@ private:
|
|||||||
QSet<InstanceId> instanceSet;
|
QSet<InstanceId> instanceSet;
|
||||||
bool m_groupsLoaded = false;
|
bool m_groupsLoaded = false;
|
||||||
bool m_instancesProbed = false;
|
bool m_instancesProbed = false;
|
||||||
|
|
||||||
|
QStack<TrashHistoryItem> m_trashHistory;
|
||||||
};
|
};
|
||||||
|
@ -18,13 +18,17 @@ LAUNCHER_NAME=@Launcher_APP_BINARY_NAME@
|
|||||||
LAUNCHER_DIR="$(dirname "$(readlink -f "$0")")"
|
LAUNCHER_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
echo "Launcher Dir: ${LAUNCHER_DIR}"
|
echo "Launcher Dir: ${LAUNCHER_DIR}"
|
||||||
|
|
||||||
# Set up env - filter out input LD_ variables but pass them in under different names
|
# Set up env.
|
||||||
export GAME_LIBRARY_PATH=${GAME_LIBRARY_PATH-${LD_LIBRARY_PATH}}
|
# Pass our custom variables separately so that the launcher can remove them for child processes
|
||||||
export GAME_PRELOAD=${GAME_PRELOAD-${LD_PRELOAD}}
|
export LAUNCHER_LD_LIBRARY_PATH="${LAUNCHER_DIR}/lib@LIB_SUFFIX@"
|
||||||
export LD_LIBRARY_PATH="${LAUNCHER_DIR}/lib@LIB_SUFFIX@":$LAUNCHER_LIBRARY_PATH
|
export LAUNCHER_LD_PRELOAD=""
|
||||||
export LD_PRELOAD=$LAUNCHER_PRELOAD
|
export LAUNCHER_QT_PLUGIN_PATH="${LAUNCHER_DIR}/plugins"
|
||||||
export QT_PLUGIN_PATH="${LAUNCHER_DIR}/plugins"
|
export LAUNCHER_QT_FONTPATH="${LAUNCHER_DIR}/fonts"
|
||||||
export QT_FONTPATH="${LAUNCHER_DIR}/fonts"
|
|
||||||
|
export LD_LIBRARY_PATH="$LAUNCHER_LD_LIBRARY_PATH:$LD_LIBRARY_PATH"
|
||||||
|
export LD_PRELOAD="$LAUNCHER_LD_PRELOAD:$LD_PRELOAD"
|
||||||
|
export QT_PLUGIN_PATH="$LAUNCHER_QT_PLUGIN_PATH:$QT_PLUGIN_PATH"
|
||||||
|
export QT_FONTPATH="$LAUNCHER_QT_FONTPATH:$QT_FONTPATH"
|
||||||
|
|
||||||
# Detect missing dependencies...
|
# Detect missing dependencies...
|
||||||
DEPS_LIST=`ldd "${LAUNCHER_DIR}"/plugins/*/*.so 2>/dev/null | grep "not found" | sort -u | awk -vORS=", " '{ print $1 }'`
|
DEPS_LIST=`ldd "${LAUNCHER_DIR}"/plugins/*/*.so 2>/dev/null | grep "not found" | sort -u | awk -vORS=", " '{ print $1 }'`
|
||||||
|
@ -141,9 +141,10 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
|||||||
QSet<QString> addedFiles;
|
QSet<QString> addedFiles;
|
||||||
|
|
||||||
// Modify the jar
|
// Modify the jar
|
||||||
for (auto i = mods.constEnd(); i != mods.constBegin(); --i)
|
// This needs to be done in reverse-order to ensure we respect the loading order of components
|
||||||
|
for (auto i = mods.crbegin(); i != mods.crend(); i++)
|
||||||
{
|
{
|
||||||
const Mod* mod = *i;
|
const auto* mod = *i;
|
||||||
// do not merge disabled mods.
|
// do not merge disabled mods.
|
||||||
if (!mod->enabled())
|
if (!mod->enabled())
|
||||||
continue;
|
continue;
|
||||||
@ -267,7 +268,7 @@ bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & re
|
|||||||
|
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
|
std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
|
||||||
{
|
{
|
||||||
QDir directory(target);
|
QDir directory(target);
|
||||||
QStringList extracted;
|
QStringList extracted;
|
||||||
@ -276,7 +277,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
|
|||||||
auto numEntries = zip->getEntriesCount();
|
auto numEntries = zip->getEntriesCount();
|
||||||
if(numEntries < 0) {
|
if(numEntries < 0) {
|
||||||
qWarning() << "Failed to enumerate files in archive";
|
qWarning() << "Failed to enumerate files in archive";
|
||||||
return nonstd::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
else if(numEntries == 0) {
|
else if(numEntries == 0) {
|
||||||
qDebug() << "Extracting empty archives seems odd...";
|
qDebug() << "Extracting empty archives seems odd...";
|
||||||
@ -285,7 +286,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
|
|||||||
else if (!zip->goToFirstFile())
|
else if (!zip->goToFirstFile())
|
||||||
{
|
{
|
||||||
qWarning() << "Failed to seek to first file in zip";
|
qWarning() << "Failed to seek to first file in zip";
|
||||||
return nonstd::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
do
|
do
|
||||||
@ -322,7 +323,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
|
|||||||
{
|
{
|
||||||
qWarning() << "Failed to extract file" << original_name << "to" << absFilePath;
|
qWarning() << "Failed to extract file" << original_name << "to" << absFilePath;
|
||||||
JlCompress::removeFile(extracted);
|
JlCompress::removeFile(extracted);
|
||||||
return nonstd::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
extracted.append(absFilePath);
|
extracted.append(absFilePath);
|
||||||
@ -340,7 +341,7 @@ bool MMCZip::extractRelFile(QuaZip *zip, const QString &file, const QString &tar
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir)
|
std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir)
|
||||||
{
|
{
|
||||||
QuaZip zip(fileCompressed);
|
QuaZip zip(fileCompressed);
|
||||||
if (!zip.open(QuaZip::mdUnzip))
|
if (!zip.open(QuaZip::mdUnzip))
|
||||||
@ -351,13 +352,13 @@ nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString
|
|||||||
return QStringList();
|
return QStringList();
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
|
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
|
||||||
return nonstd::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return MMCZip::extractSubDir(&zip, "", dir);
|
return MMCZip::extractSubDir(&zip, "", dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir)
|
std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir)
|
||||||
{
|
{
|
||||||
QuaZip zip(fileCompressed);
|
QuaZip zip(fileCompressed);
|
||||||
if (!zip.open(QuaZip::mdUnzip))
|
if (!zip.open(QuaZip::mdUnzip))
|
||||||
@ -368,7 +369,7 @@ nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString
|
|||||||
return QStringList();
|
return QStringList();
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
|
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
|
||||||
return nonstd::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return MMCZip::extractSubDir(&zip, subdir, dir);
|
return MMCZip::extractSubDir(&zip, subdir, dir);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include <quazip/JlCompress.h>
|
#include <quazip/JlCompress.h>
|
||||||
#include <nonstd/optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace MMCZip
|
namespace MMCZip
|
||||||
{
|
{
|
||||||
@ -95,7 +95,7 @@ namespace MMCZip
|
|||||||
/**
|
/**
|
||||||
* Extract a subdirectory from an archive
|
* Extract a subdirectory from an archive
|
||||||
*/
|
*/
|
||||||
nonstd::optional<QStringList> extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
|
std::optional<QStringList> extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
|
||||||
|
|
||||||
bool extractRelFile(QuaZip *zip, const QString & file, const QString &target);
|
bool extractRelFile(QuaZip *zip, const QString & file, const QString &target);
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ namespace MMCZip
|
|||||||
* \param dir The directory to extract to, the current directory if left empty.
|
* \param dir The directory to extract to, the current directory if left empty.
|
||||||
* \return The list of the full paths of the files extracted, empty on failure.
|
* \return The list of the full paths of the files extracted, empty on failure.
|
||||||
*/
|
*/
|
||||||
nonstd::optional<QStringList> extractDir(QString fileCompressed, QString dir);
|
std::optional<QStringList> extractDir(QString fileCompressed, QString dir);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract a subdirectory from an archive
|
* Extract a subdirectory from an archive
|
||||||
@ -116,7 +116,7 @@ namespace MMCZip
|
|||||||
* \param dir The directory to extract to, the current directory if left empty.
|
* \param dir The directory to extract to, the current directory if left empty.
|
||||||
* \return The list of the full paths of the files extracted, empty on failure.
|
* \return The list of the full paths of the files extracted, empty on failure.
|
||||||
*/
|
*/
|
||||||
nonstd::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir);
|
std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract a single file from an archive into a directory
|
* Extract a single file from an archive into a directory
|
||||||
|
@ -77,10 +77,12 @@ public:
|
|||||||
{
|
{
|
||||||
return m_ptr;
|
return m_ptr;
|
||||||
}
|
}
|
||||||
bool operator==(const shared_qobject_ptr<T>& other) {
|
template<typename U>
|
||||||
|
bool operator==(const shared_qobject_ptr<U>& other) const {
|
||||||
return m_ptr == other.m_ptr;
|
return m_ptr == other.m_ptr;
|
||||||
}
|
}
|
||||||
bool operator!=(const shared_qobject_ptr<T>& other) {
|
template<typename U>
|
||||||
|
bool operator!=(const shared_qobject_ptr<U>& other) const {
|
||||||
return m_ptr != other.m_ptr;
|
return m_ptr != other.m_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,26 +52,25 @@ JavaUtils::JavaUtils()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
QString stripVariableEntries(QString name, QString target, QString remove)
|
||||||
static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH)
|
|
||||||
{
|
{
|
||||||
QDir mmcBin(QCoreApplication::applicationDirPath());
|
char delimiter = ':';
|
||||||
auto items = LD_LIBRARY_PATH.split(':');
|
#ifdef Q_OS_WIN32
|
||||||
QStringList final;
|
delimiter = ';';
|
||||||
for(auto & item: items)
|
|
||||||
{
|
|
||||||
QDir test(item);
|
|
||||||
if(test == mmcBin)
|
|
||||||
{
|
|
||||||
qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final.append(item);
|
|
||||||
}
|
|
||||||
return final.join(':');
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
auto targetItems = target.split(delimiter);
|
||||||
|
auto toRemove = remove.split(delimiter);
|
||||||
|
|
||||||
|
for (QString item : toRemove) {
|
||||||
|
bool removed = targetItems.removeOne(item);
|
||||||
|
if (!removed)
|
||||||
|
qWarning() << "Entry" << item
|
||||||
|
<< "could not be stripped from variable" << name;
|
||||||
|
}
|
||||||
|
return targetItems.join(delimiter);
|
||||||
|
}
|
||||||
|
|
||||||
QProcessEnvironment CleanEnviroment()
|
QProcessEnvironment CleanEnviroment()
|
||||||
{
|
{
|
||||||
// prepare the process environment
|
// prepare the process environment
|
||||||
@ -89,6 +88,16 @@ QProcessEnvironment CleanEnviroment()
|
|||||||
"JAVA_OPTIONS",
|
"JAVA_OPTIONS",
|
||||||
"JAVA_TOOL_OPTIONS"
|
"JAVA_TOOL_OPTIONS"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QStringList stripped =
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
|
"LD_LIBRARY_PATH",
|
||||||
|
"LD_PRELOAD",
|
||||||
|
#endif
|
||||||
|
"QT_PLUGIN_PATH",
|
||||||
|
"QT_FONTPATH"
|
||||||
|
};
|
||||||
for(auto key: rawenv.keys())
|
for(auto key: rawenv.keys())
|
||||||
{
|
{
|
||||||
auto value = rawenv.value(key);
|
auto value = rawenv.value(key);
|
||||||
@ -98,19 +107,22 @@ QProcessEnvironment CleanEnviroment()
|
|||||||
qDebug() << "Env: ignoring" << key << value;
|
qDebug() << "Env: ignoring" << key << value;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// filter PolyMC-related things
|
|
||||||
if(key.startsWith("QT_"))
|
// These are used to strip the original variables
|
||||||
|
// If there is "LD_LIBRARY_PATH" and "LAUNCHER_LD_LIBRARY_PATH", we want to
|
||||||
|
// remove all values in "LAUNCHER_LD_LIBRARY_PATH" from "LD_LIBRARY_PATH"
|
||||||
|
if(key.startsWith("LAUNCHER_"))
|
||||||
{
|
{
|
||||||
qDebug() << "Env: ignoring" << key << value;
|
qDebug() << "Env: ignoring" << key << value;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
if(stripped.contains(key))
|
||||||
// Do not pass LD_* variables to java. They were intended for PolyMC
|
|
||||||
if(key.startsWith("LD_"))
|
|
||||||
{
|
{
|
||||||
qDebug() << "Env: ignoring" << key << value;
|
QString newValue = stripVariableEntries(key, value, rawenv.value("LAUNCHER_" + key));
|
||||||
continue;
|
|
||||||
|
qDebug() << "Env: stripped" << key << value << "to" << newValue;
|
||||||
}
|
}
|
||||||
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
// Strip IBus
|
// Strip IBus
|
||||||
// IBus is a Linux IME framework. For some reason, it breaks MC?
|
// IBus is a Linux IME framework. For some reason, it breaks MC?
|
||||||
if (key == "XMODIFIERS" && value.contains(IBUS))
|
if (key == "XMODIFIERS" && value.contains(IBUS))
|
||||||
@ -119,22 +131,12 @@ QProcessEnvironment CleanEnviroment()
|
|||||||
value.replace(IBUS, "");
|
value.replace(IBUS, "");
|
||||||
qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value;
|
qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value;
|
||||||
}
|
}
|
||||||
if(key == "GAME_PRELOAD")
|
|
||||||
{
|
|
||||||
env.insert("LD_PRELOAD", value);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(key == "GAME_LIBRARY_PATH")
|
|
||||||
{
|
|
||||||
env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
// qDebug() << "Env: " << key << value;
|
// qDebug() << "Env: " << key << value;
|
||||||
env.insert(key, value);
|
env.insert(key, value);
|
||||||
}
|
}
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
// HACK: Workaround for QTBUG42500
|
// HACK: Workaround for QTBUG-42500
|
||||||
if(!env.contains("LD_LIBRARY_PATH"))
|
if(!env.contains("LD_LIBRARY_PATH"))
|
||||||
{
|
{
|
||||||
env.insert("LD_LIBRARY_PATH", "");
|
env.insert("LD_LIBRARY_PATH", "");
|
||||||
@ -203,7 +205,7 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
|
|||||||
// Read the current type version from the registry.
|
// Read the current type version from the registry.
|
||||||
// This will be used to find any key that contains the JavaHome value.
|
// This will be used to find any key that contains the JavaHome value.
|
||||||
|
|
||||||
TCHAR subKeyName[255];
|
WCHAR subKeyName[255];
|
||||||
DWORD subKeyNameSize, numSubKeys, retCode;
|
DWORD subKeyNameSize, numSubKeys, retCode;
|
||||||
|
|
||||||
// Get the number of subkeys
|
// Get the number of subkeys
|
||||||
@ -229,12 +231,11 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
|
|||||||
KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS)
|
KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
// Read the JavaHome value to find where Java is installed.
|
// Read the JavaHome value to find where Java is installed.
|
||||||
TCHAR *value = NULL;
|
|
||||||
DWORD valueSz = 0;
|
DWORD valueSz = 0;
|
||||||
if (RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE *)value,
|
if (RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, NULL,
|
||||||
&valueSz) == ERROR_MORE_DATA)
|
&valueSz) == ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
value = new TCHAR[valueSz];
|
WCHAR *value = new WCHAR[valueSz];
|
||||||
RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE *)value,
|
RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE *)value,
|
||||||
&valueSz);
|
&valueSz);
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
QString stripVariableEntries(QString name, QString target, QString remove);
|
||||||
QProcessEnvironment CleanEnviroment();
|
QProcessEnvironment CleanEnviroment();
|
||||||
|
|
||||||
class JavaUtils : public QObject
|
class JavaUtils : public QObject
|
||||||
|
@ -282,35 +282,22 @@ void LaunchTask::emitFailed(QString reason)
|
|||||||
Task::emitFailed(reason);
|
Task::emitFailed(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LaunchTask::substituteVariables(const QStringList &args) const
|
void LaunchTask::substituteVariables(QStringList &args) const
|
||||||
{
|
{
|
||||||
auto variables = m_instance->getVariables();
|
auto env = m_instance->createEnvironment();
|
||||||
auto envVariables = QProcessEnvironment::systemEnvironment();
|
|
||||||
|
|
||||||
for (auto arg : args) {
|
for (auto key : env.keys())
|
||||||
for (auto key : variables)
|
|
||||||
{
|
{
|
||||||
arg.replace("$" + key, variables.value(key));
|
args.replaceInStrings("$" + key, env.value(key));
|
||||||
}
|
|
||||||
for (auto env : envVariables.keys())
|
|
||||||
{
|
|
||||||
arg.replace("$" + env, envVariables.value(env));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString LaunchTask::substituteVariables(const QString &cmd) const
|
void LaunchTask::substituteVariables(QString &cmd) const
|
||||||
{
|
{
|
||||||
QString out = cmd;
|
auto env = m_instance->createEnvironment();
|
||||||
auto variables = m_instance->getVariables();
|
|
||||||
for (auto it = variables.begin(); it != variables.end(); ++it)
|
for (auto key : env.keys())
|
||||||
{
|
{
|
||||||
out.replace("$" + it.key(), it.value());
|
cmd.replace("$" + key, env.value(key));
|
||||||
}
|
}
|
||||||
auto env = QProcessEnvironment::systemEnvironment();
|
|
||||||
for (auto var : env.keys())
|
|
||||||
{
|
|
||||||
out.replace("$" + var, env.value(var));
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
@ -105,8 +105,8 @@ public: /* methods */
|
|||||||
shared_qobject_ptr<LogModel> getLogModel();
|
shared_qobject_ptr<LogModel> getLogModel();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void substituteVariables(const QStringList &args) const;
|
void substituteVariables(QStringList &args) const;
|
||||||
QString substituteVariables(const QString &cmd) const;
|
void substituteVariables(QString &cmd) const;
|
||||||
QString censorPrivateInfo(QString in);
|
QString censorPrivateInfo(QString in);
|
||||||
|
|
||||||
protected: /* methods */
|
protected: /* methods */
|
||||||
|
@ -56,9 +56,10 @@ void PostLaunchCommand::executeTask()
|
|||||||
const QString program = args.takeFirst();
|
const QString program = args.takeFirst();
|
||||||
m_process.start(program, args);
|
m_process.start(program, args);
|
||||||
#else
|
#else
|
||||||
QString postlaunch_cmd = m_parent->substituteVariables(m_command);
|
m_parent->substituteVariables(m_command);
|
||||||
emit logLine(tr("Running Post-Launch command: %1").arg(postlaunch_cmd), MessageLevel::Launcher);
|
|
||||||
m_process.start(postlaunch_cmd);
|
emit logLine(tr("Running Post-Launch command: %1").arg(m_command), MessageLevel::Launcher);
|
||||||
|
m_process.start(m_command);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,9 +56,10 @@ void PreLaunchCommand::executeTask()
|
|||||||
const QString program = args.takeFirst();
|
const QString program = args.takeFirst();
|
||||||
m_process.start(program, args);
|
m_process.start(program, args);
|
||||||
#else
|
#else
|
||||||
QString prelaunch_cmd = m_parent->substituteVariables(m_command);
|
m_parent->substituteVariables(m_command);
|
||||||
emit logLine(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd), MessageLevel::Launcher);
|
|
||||||
m_process.start(prelaunch_cmd);
|
emit logLine(tr("Running Pre-Launch command: %1").arg(m_command), MessageLevel::Launcher);
|
||||||
|
m_process.start(m_command);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -709,15 +709,15 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
|||||||
{
|
{
|
||||||
if(mod->type() == Mod::MOD_FOLDER)
|
if(mod->type() == Mod::MOD_FOLDER)
|
||||||
{
|
{
|
||||||
out << u8" [📁] " + mod->fileinfo().completeBaseName() + " (folder)";
|
out << u8" [🖿] " + mod->fileinfo().completeBaseName() + " (folder)";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mod->enabled()) {
|
if(mod->enabled()) {
|
||||||
out << u8" [✔️]" + mod->fileinfo().completeBaseName();
|
out << u8" [✔] " + mod->fileinfo().completeBaseName();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
out << u8" [❌] " + mod->fileinfo().completeBaseName() + " (disabled)";
|
out << u8" [✘] " + mod->fileinfo().completeBaseName() + " (disabled)";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -53,12 +53,12 @@
|
|||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
|
||||||
#include <nonstd/optional>
|
#include <optional>
|
||||||
|
|
||||||
using nonstd::optional;
|
using std::optional;
|
||||||
using nonstd::nullopt;
|
using std::nullopt;
|
||||||
|
|
||||||
GameType::GameType(nonstd::optional<int> original):
|
GameType::GameType(std::optional<int> original):
|
||||||
original(original)
|
original(original)
|
||||||
{
|
{
|
||||||
if(!original) {
|
if(!original) {
|
||||||
|
@ -16,11 +16,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <nonstd/optional>
|
#include <optional>
|
||||||
|
|
||||||
struct GameType {
|
struct GameType {
|
||||||
GameType() = default;
|
GameType() = default;
|
||||||
GameType (nonstd::optional<int> original);
|
GameType (std::optional<int> original);
|
||||||
|
|
||||||
QString toTranslatedString() const;
|
QString toTranslatedString() const;
|
||||||
QString toLogString() const;
|
QString toLogString() const;
|
||||||
@ -33,7 +33,7 @@ struct GameType {
|
|||||||
Adventure,
|
Adventure,
|
||||||
Spectator
|
Spectator
|
||||||
} type = Unknown;
|
} type = Unknown;
|
||||||
nonstd::optional<int> original;
|
std::optional<int> original;
|
||||||
};
|
};
|
||||||
|
|
||||||
class World
|
class World
|
||||||
|
@ -109,8 +109,10 @@ QStringList AccountList::profileNames() const {
|
|||||||
|
|
||||||
void AccountList::addAccount(const MinecraftAccountPtr account)
|
void AccountList::addAccount(const MinecraftAccountPtr account)
|
||||||
{
|
{
|
||||||
// NOTE: Do not allow adding something that's already there
|
// NOTE: Do not allow adding something that's already there. We shouldn't let it continue
|
||||||
|
// because of the signal / slot connections after this.
|
||||||
if (m_accounts.contains(account)) {
|
if (m_accounts.contains(account)) {
|
||||||
|
qDebug() << "Tried to add account that's already on the accounts list!";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +125,8 @@ void AccountList::addAccount(const MinecraftAccountPtr account)
|
|||||||
if(profileId.size()) {
|
if(profileId.size()) {
|
||||||
auto existingAccount = findAccountByProfileId(profileId);
|
auto existingAccount = findAccountByProfileId(profileId);
|
||||||
if(existingAccount != -1) {
|
if(existingAccount != -1) {
|
||||||
|
qDebug() << "Replacing old account with a new one with the same profile ID!";
|
||||||
|
|
||||||
MinecraftAccountPtr existingAccountPtr = m_accounts[existingAccount];
|
MinecraftAccountPtr existingAccountPtr = m_accounts[existingAccount];
|
||||||
m_accounts[existingAccount] = account;
|
m_accounts[existingAccount] = account;
|
||||||
if(m_defaultAccount == existingAccountPtr) {
|
if(m_defaultAccount == existingAccountPtr) {
|
||||||
@ -138,9 +142,12 @@ void AccountList::addAccount(const MinecraftAccountPtr account)
|
|||||||
|
|
||||||
// if we don't have this profileId yet, add the account to the end
|
// if we don't have this profileId yet, add the account to the end
|
||||||
int row = m_accounts.count();
|
int row = m_accounts.count();
|
||||||
|
qDebug() << "Inserting account at index" << row;
|
||||||
|
|
||||||
beginInsertRows(QModelIndex(), row, row);
|
beginInsertRows(QModelIndex(), row, row);
|
||||||
m_accounts.append(account);
|
m_accounts.append(account);
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
|
|
||||||
onListChanged();
|
onListChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "minecraft/auth/AuthRequest.h"
|
#include "minecraft/auth/AuthRequest.h"
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
#include "minecraft/auth/AccountTask.h"
|
#include "minecraft/auth/AccountTask.h"
|
||||||
|
#include "net/NetUtils.h"
|
||||||
|
|
||||||
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {
|
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {
|
||||||
|
|
||||||
@ -58,10 +59,18 @@ void LauncherLoginStep::onRequestDone(
|
|||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
qDebug() << data;
|
qDebug() << data;
|
||||||
#endif
|
#endif
|
||||||
|
if (Net::isApplicationError(error)) {
|
||||||
emit finished(
|
emit finished(
|
||||||
AccountTaskState::STATE_FAILED_SOFT,
|
AccountTaskState::STATE_FAILED_SOFT,
|
||||||
tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_)
|
tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit finished(
|
||||||
|
AccountTaskState::STATE_OFFLINE,
|
||||||
|
tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_)
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
#include "minecraft/auth/AuthRequest.h"
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
|
#include "net/NetUtils.h"
|
||||||
|
|
||||||
MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {
|
MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {
|
||||||
|
|
||||||
@ -64,10 +65,18 @@ void MinecraftProfileStep::onRequestDone(
|
|||||||
qWarning() << " Response:";
|
qWarning() << " Response:";
|
||||||
qWarning() << QString::fromUtf8(data);
|
qWarning() << QString::fromUtf8(data);
|
||||||
|
|
||||||
|
if (Net::isApplicationError(error)) {
|
||||||
emit finished(
|
emit finished(
|
||||||
AccountTaskState::STATE_FAILED_SOFT,
|
AccountTaskState::STATE_FAILED_SOFT,
|
||||||
tr("Minecraft Java profile acquisition failed.")
|
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit finished(
|
||||||
|
AccountTaskState::STATE_OFFLINE,
|
||||||
|
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) {
|
if(!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
#include "minecraft/auth/AuthRequest.h"
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
|
#include "net/NetUtils.h"
|
||||||
|
|
||||||
MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {
|
MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {
|
||||||
|
|
||||||
@ -67,10 +68,18 @@ void MinecraftProfileStepMojang::onRequestDone(
|
|||||||
qWarning() << " Response:";
|
qWarning() << " Response:";
|
||||||
qWarning() << QString::fromUtf8(data);
|
qWarning() << QString::fromUtf8(data);
|
||||||
|
|
||||||
|
if (Net::isApplicationError(error)) {
|
||||||
emit finished(
|
emit finished(
|
||||||
AccountTaskState::STATE_FAILED_SOFT,
|
AccountTaskState::STATE_FAILED_SOFT,
|
||||||
tr("Minecraft Java profile acquisition failed.")
|
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit finished(
|
||||||
|
AccountTaskState::STATE_OFFLINE,
|
||||||
|
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) {
|
if(!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
#include "minecraft/auth/AuthRequest.h"
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
|
#include "net/NetUtils.h"
|
||||||
|
|
||||||
XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token *token, QString relyingParty, QString authorizationKind):
|
XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token *token, QString relyingParty, QString authorizationKind):
|
||||||
AuthStep(data),
|
AuthStep(data),
|
||||||
@ -62,12 +63,26 @@ void XboxAuthorizationStep::onRequestDone(
|
|||||||
#endif
|
#endif
|
||||||
if (error != QNetworkReply::NoError) {
|
if (error != QNetworkReply::NoError) {
|
||||||
qWarning() << "Reply error:" << error;
|
qWarning() << "Reply error:" << error;
|
||||||
|
if (Net::isApplicationError(error)) {
|
||||||
if(!processSTSError(error, data, headers)) {
|
if(!processSTSError(error, data, headers)) {
|
||||||
emit finished(
|
emit finished(
|
||||||
AccountTaskState::STATE_FAILED_SOFT,
|
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, error)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
emit finished(
|
||||||
|
AccountTaskState::STATE_FAILED_SOFT,
|
||||||
|
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, requestor->errorString_)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit finished(
|
||||||
|
AccountTaskState::STATE_OFFLINE,
|
||||||
|
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, requestor->errorString_)
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
#include "minecraft/auth/AuthRequest.h"
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
|
#include "net/NetUtils.h"
|
||||||
|
|
||||||
XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {
|
XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {
|
||||||
|
|
||||||
@ -58,10 +59,18 @@ void XboxProfileStep::onRequestDone(
|
|||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
qDebug() << data;
|
qDebug() << data;
|
||||||
#endif
|
#endif
|
||||||
finished(
|
if (Net::isApplicationError(error)) {
|
||||||
|
emit finished(
|
||||||
AccountTaskState::STATE_FAILED_SOFT,
|
AccountTaskState::STATE_FAILED_SOFT,
|
||||||
tr("Failed to retrieve the Xbox profile.")
|
tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit finished(
|
||||||
|
AccountTaskState::STATE_OFFLINE,
|
||||||
|
tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_)
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
#include "minecraft/auth/AuthRequest.h"
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "minecraft/auth/Parsers.h"
|
||||||
|
#include "net/NetUtils.h"
|
||||||
|
|
||||||
XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {
|
XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {
|
||||||
|
|
||||||
@ -53,7 +54,17 @@ void XboxUserStep::onRequestDone(
|
|||||||
|
|
||||||
if (error != QNetworkReply::NoError) {
|
if (error != QNetworkReply::NoError) {
|
||||||
qWarning() << "Reply error:" << error;
|
qWarning() << "Reply error:" << error;
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed."));
|
if (Net::isApplicationError(error)) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
|
tr("XBox user authentication failed: %1").arg(requestor->errorString_)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emit finished(
|
||||||
|
AccountTaskState::STATE_OFFLINE,
|
||||||
|
tr("XBox user authentication failed: %1").arg(requestor->errorString_)
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
#include "minecraft/PackProfile.h"
|
#include "minecraft/PackProfile.h"
|
||||||
#include "meta/Version.h"
|
#include "meta/Version.h"
|
||||||
|
|
||||||
#include <nonstd/optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace ATLauncher {
|
namespace ATLauncher {
|
||||||
|
|
||||||
@ -131,8 +131,8 @@ private:
|
|||||||
Meta::VersionPtr minecraftVersion;
|
Meta::VersionPtr minecraftVersion;
|
||||||
QMap<QString, Meta::VersionPtr> componentsToInstall;
|
QMap<QString, Meta::VersionPtr> componentsToInstall;
|
||||||
|
|
||||||
QFuture<nonstd::optional<QStringList>> m_extractFuture;
|
QFuture<std::optional<QStringList>> m_extractFuture;
|
||||||
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
|
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
||||||
|
|
||||||
QFuture<bool> m_modExtractFuture;
|
QFuture<bool> m_modExtractFuture;
|
||||||
QFutureWatcher<bool> m_modExtractFutureWatcher;
|
QFutureWatcher<bool> m_modExtractFutureWatcher;
|
||||||
|
@ -7,6 +7,13 @@ Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAcc
|
|||||||
: m_network(network), m_toProcess(toProcess)
|
: m_network(network), m_toProcess(toProcess)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
bool Flame::FileResolvingTask::abort()
|
||||||
|
{
|
||||||
|
if (m_dljob)
|
||||||
|
return m_dljob->abort();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Flame::FileResolvingTask::executeTask()
|
void Flame::FileResolvingTask::executeTask()
|
||||||
{
|
{
|
||||||
setStatus(tr("Resolving mod IDs..."));
|
setStatus(tr("Resolving mod IDs..."));
|
||||||
|
@ -13,6 +13,9 @@ public:
|
|||||||
explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest &toProcess);
|
explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest &toProcess);
|
||||||
virtual ~FileResolvingTask() {};
|
virtual ~FileResolvingTask() {};
|
||||||
|
|
||||||
|
bool canAbort() const override { return true; }
|
||||||
|
bool abort() override;
|
||||||
|
|
||||||
const Flame::Manifest &getResults() const
|
const Flame::Manifest &getResults() const
|
||||||
{
|
{
|
||||||
return m_toProcess;
|
return m_toProcess;
|
||||||
|
@ -123,7 +123,7 @@ void FlameCheckUpdate::executeTask()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus(tr("Getting API response from CurseForge for '%1'").arg(mod->name()));
|
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name()));
|
||||||
setProgress(i++, m_mods.size());
|
setProgress(i++, m_mods.size());
|
||||||
|
|
||||||
auto latest_ver = api.getLatestVersion({ mod->metadata()->project_id.toString(), m_game_versions, m_loaders });
|
auto latest_ver = api.getLatestVersion({ mod->metadata()->project_id.toString(), m_game_versions, m_loaders });
|
||||||
@ -145,7 +145,7 @@ void FlameCheckUpdate::executeTask()
|
|||||||
if (latest_ver.downloadUrl.isEmpty() && latest_ver.fileId != mod->metadata()->file_id) {
|
if (latest_ver.downloadUrl.isEmpty() && latest_ver.fileId != mod->metadata()->file_id) {
|
||||||
auto pack = getProjectInfo(latest_ver);
|
auto pack = getProjectInfo(latest_ver);
|
||||||
auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver.fileId.toString());
|
auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver.fileId.toString());
|
||||||
emit checkFailed(mod, tr("Mod has a new update available, but is opted-out on CurseForge"), recover_url);
|
emit checkFailed(mod, tr("Mod has a new update available, but is not downloadable using CurseForge."), recover_url);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
|
|
||||||
#include <nonstd/optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace LegacyFTB {
|
namespace LegacyFTB {
|
||||||
|
|
||||||
@ -46,8 +46,8 @@ private: /* data */
|
|||||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
||||||
bool abortable = false;
|
bool abortable = false;
|
||||||
std::unique_ptr<QuaZip> m_packZip;
|
std::unique_ptr<QuaZip> m_packZip;
|
||||||
QFuture<nonstd::optional<QStringList>> m_extractFuture;
|
QFuture<std::optional<QStringList>> m_extractFuture;
|
||||||
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
|
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
||||||
NetJob::Ptr netJobContainer;
|
NetJob::Ptr netJobContainer;
|
||||||
QString archivePath;
|
QString archivePath;
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -40,103 +42,190 @@
|
|||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
#include "minecraft/PackProfile.h"
|
#include "minecraft/PackProfile.h"
|
||||||
|
#include "modplatform/flame/PackManifest.h"
|
||||||
#include "net/ChecksumValidator.h"
|
#include "net/ChecksumValidator.h"
|
||||||
#include "settings/INISettingsObject.h"
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
#include "BuildConfig.h"
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "BuildConfig.h"
|
||||||
|
#include "ui/dialogs/ScrollMessageBox.h"
|
||||||
|
|
||||||
namespace ModpacksCH {
|
namespace ModpacksCH {
|
||||||
|
|
||||||
PackInstallTask::PackInstallTask(Modpack pack, QString version)
|
PackInstallTask::PackInstallTask(Modpack pack, QString version, QWidget* parent)
|
||||||
{
|
: m_pack(std::move(pack)), m_version_name(std::move(version)), m_parent(parent)
|
||||||
m_pack = pack;
|
{}
|
||||||
m_version_name = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PackInstallTask::abort()
|
bool PackInstallTask::abort()
|
||||||
{
|
{
|
||||||
if(abortable)
|
bool aborted = true;
|
||||||
{
|
|
||||||
return jobPtr->abort();
|
if (m_net_job)
|
||||||
}
|
aborted &= m_net_job->abort();
|
||||||
return false;
|
if (m_mod_id_resolver_task)
|
||||||
|
aborted &= m_mod_id_resolver_task->abort();
|
||||||
|
|
||||||
|
// FIXME: This should be 'emitAborted()', but InstanceStaging doesn't connect to the abort signal yet...
|
||||||
|
if (aborted)
|
||||||
|
emitFailed(tr("Aborted"));
|
||||||
|
|
||||||
|
return aborted;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PackInstallTask::executeTask()
|
void PackInstallTask::executeTask()
|
||||||
{
|
{
|
||||||
|
setStatus(tr("Getting the manifest..."));
|
||||||
|
|
||||||
// Find pack version
|
// Find pack version
|
||||||
bool found = false;
|
auto version_it = std::find_if(m_pack.versions.constBegin(), m_pack.versions.constEnd(),
|
||||||
VersionInfo version;
|
[this](ModpacksCH::VersionInfo const& a) { return a.name == m_version_name; });
|
||||||
|
|
||||||
for(auto vInfo : m_pack.versions) {
|
if (version_it == m_pack.versions.constEnd()) {
|
||||||
if (vInfo.name == m_version_name) {
|
|
||||||
found = true;
|
|
||||||
version = vInfo;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!found) {
|
|
||||||
emitFailed(tr("Failed to find pack version %1").arg(m_version_name));
|
emitFailed(tr("Failed to find pack version %1").arg(m_version_name));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *netJob = new NetJob("ModpacksCH::VersionFetch", APPLICATION->network());
|
auto version = *version_it;
|
||||||
auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2").arg(m_pack.id).arg(version.id);
|
|
||||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
|
|
||||||
jobPtr = netJob;
|
|
||||||
jobPtr->start();
|
|
||||||
|
|
||||||
QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
|
auto* netJob = new NetJob("ModpacksCH::VersionFetch", APPLICATION->network());
|
||||||
QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
|
|
||||||
|
auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2").arg(m_pack.id).arg(version.id);
|
||||||
|
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &m_response));
|
||||||
|
|
||||||
|
QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onManifestDownloadSucceeded);
|
||||||
|
QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onManifestDownloadFailed);
|
||||||
|
QObject::connect(netJob, &NetJob::progress, this, &PackInstallTask::setProgress);
|
||||||
|
|
||||||
|
m_net_job = netJob;
|
||||||
|
|
||||||
|
netJob->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PackInstallTask::onDownloadSucceeded()
|
void PackInstallTask::onManifestDownloadSucceeded()
|
||||||
{
|
{
|
||||||
jobPtr.reset();
|
m_net_job.reset();
|
||||||
|
|
||||||
QJsonParseError parse_error;
|
QJsonParseError parse_error{};
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
|
QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error);
|
||||||
if (parse_error.error != QJsonParseError::NoError) {
|
if (parse_error.error != QJsonParseError::NoError) {
|
||||||
qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
|
qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset
|
||||||
qWarning() << response;
|
<< " reason: " << parse_error.errorString();
|
||||||
|
qWarning() << m_response;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto obj = doc.object();
|
|
||||||
|
|
||||||
ModpacksCH::Version version;
|
ModpacksCH::Version version;
|
||||||
try
|
try {
|
||||||
{
|
auto obj = Json::requireObject(doc);
|
||||||
ModpacksCH::loadVersion(version, obj);
|
ModpacksCH::loadVersion(version, obj);
|
||||||
}
|
} catch (const JSONValidationError& e) {
|
||||||
catch (const JSONValidationError &e)
|
|
||||||
{
|
|
||||||
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
|
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_version = version;
|
m_version = version;
|
||||||
|
|
||||||
downloadPack();
|
resolveMods();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PackInstallTask::onDownloadFailed(QString reason)
|
void PackInstallTask::resolveMods()
|
||||||
{
|
{
|
||||||
jobPtr.reset();
|
setStatus(tr("Resolving mods..."));
|
||||||
emitFailed(reason);
|
setProgress(0, 100);
|
||||||
|
|
||||||
|
m_file_id_map.clear();
|
||||||
|
|
||||||
|
Flame::Manifest manifest;
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
for (auto const& file : m_version.files) {
|
||||||
|
if (!file.serverOnly && file.url.isEmpty()) {
|
||||||
|
if (file.curseforge.file_id <= 0) {
|
||||||
|
emitFailed(tr("Invalid manifest: There's no information available to download the file '%1'!").arg(file.name));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Flame::File flame_file;
|
||||||
|
flame_file.projectId = file.curseforge.project_id;
|
||||||
|
flame_file.fileId = file.curseforge.file_id;
|
||||||
|
flame_file.hash = file.sha1;
|
||||||
|
|
||||||
|
manifest.files.insert(flame_file.fileId, flame_file);
|
||||||
|
m_file_id_map.append(flame_file.fileId);
|
||||||
|
} else {
|
||||||
|
m_file_id_map.append(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_mod_id_resolver_task = new Flame::FileResolvingTask(APPLICATION->network(), manifest);
|
||||||
|
|
||||||
|
connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::succeeded, this, &PackInstallTask::onResolveModsSucceeded);
|
||||||
|
connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onResolveModsFailed);
|
||||||
|
connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::progress, this, &PackInstallTask::setProgress);
|
||||||
|
|
||||||
|
m_mod_id_resolver_task->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PackInstallTask::onResolveModsSucceeded()
|
||||||
|
{
|
||||||
|
m_abortable = false;
|
||||||
|
|
||||||
|
QString text;
|
||||||
|
auto anyBlocked = false;
|
||||||
|
|
||||||
|
Flame::Manifest results = m_mod_id_resolver_task->getResults();
|
||||||
|
for (int index = 0; index < m_file_id_map.size(); index++) {
|
||||||
|
auto const file_id = m_file_id_map.at(index);
|
||||||
|
if (file_id < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Flame::File results_file = results.files[file_id];
|
||||||
|
VersionFile& local_file = m_version.files[index];
|
||||||
|
|
||||||
|
// First check for blocked mods
|
||||||
|
if (!results_file.resolved || results_file.url.isEmpty()) {
|
||||||
|
QString type(local_file.type);
|
||||||
|
|
||||||
|
type[0] = type[0].toUpper();
|
||||||
|
text += QString("%1: %2 - <a href='%3'>%3</a><br/>").arg(type, local_file.name, results_file.websiteUrl);
|
||||||
|
anyBlocked = true;
|
||||||
|
} else {
|
||||||
|
local_file.url = results_file.url.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_mod_id_resolver_task.reset();
|
||||||
|
|
||||||
|
if (anyBlocked) {
|
||||||
|
qDebug() << "Blocked files found, displaying file list";
|
||||||
|
|
||||||
|
auto message_dialog = new ScrollMessageBox(m_parent, tr("Blocked files found"),
|
||||||
|
tr("The following files are not available for download in third party launchers.<br/>"
|
||||||
|
"You will need to manually download them and add them to the instance."),
|
||||||
|
text);
|
||||||
|
|
||||||
|
if (message_dialog->exec() == QDialog::Accepted)
|
||||||
|
downloadPack();
|
||||||
|
else
|
||||||
|
abort();
|
||||||
|
} else {
|
||||||
|
downloadPack();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PackInstallTask::downloadPack()
|
void PackInstallTask::downloadPack()
|
||||||
{
|
{
|
||||||
setStatus(tr("Downloading mods..."));
|
setStatus(tr("Downloading mods..."));
|
||||||
|
|
||||||
jobPtr = new NetJob(tr("Mod download"), APPLICATION->network());
|
auto* jobPtr = new NetJob(tr("Mod download"), APPLICATION->network());
|
||||||
for(auto file : m_version.files) {
|
for (auto const& file : m_version.files) {
|
||||||
if(file.serverOnly) continue;
|
if (file.serverOnly || file.url.isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
QFileInfo fileName(file.name);
|
QFileInfo file_info(file.name);
|
||||||
auto cacheName = fileName.completeBaseName() + "-" + file.sha1 + "." + fileName.suffix();
|
auto cacheName = file_info.completeBaseName() + "-" + file.sha1 + "." + file_info.suffix();
|
||||||
|
|
||||||
auto entry = APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", cacheName);
|
auto entry = APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", cacheName);
|
||||||
entry->setStale(true);
|
entry->setStale(true);
|
||||||
@ -144,47 +233,49 @@ void PackInstallTask::downloadPack()
|
|||||||
auto relpath = FS::PathCombine("minecraft", file.path, file.name);
|
auto relpath = FS::PathCombine("minecraft", file.path, file.name);
|
||||||
auto path = FS::PathCombine(m_stagingPath, relpath);
|
auto path = FS::PathCombine(m_stagingPath, relpath);
|
||||||
|
|
||||||
if (filesToCopy.contains(path)) {
|
if (m_files_to_copy.contains(path)) {
|
||||||
qWarning() << "Ignoring" << file.url << "as a file of that path is already downloading.";
|
qWarning() << "Ignoring" << file.url << "as a file of that path is already downloading.";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Will download" << file.url << "to" << path;
|
qDebug() << "Will download" << file.url << "to" << path;
|
||||||
filesToCopy[path] = entry->getFullPath();
|
m_files_to_copy[path] = entry->getFullPath();
|
||||||
|
|
||||||
auto dl = Net::Download::makeCached(file.url, entry);
|
auto dl = Net::Download::makeCached(file.url, entry);
|
||||||
if (!file.sha1.isEmpty()) {
|
if (!file.sha1.isEmpty()) {
|
||||||
auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1());
|
auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1());
|
||||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
||||||
}
|
}
|
||||||
|
|
||||||
jobPtr->addNetAction(dl);
|
jobPtr->addNetAction(dl);
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(jobPtr.get(), &NetJob::succeeded, this, [&]()
|
connect(jobPtr, &NetJob::succeeded, this, &PackInstallTask::onModDownloadSucceeded);
|
||||||
{
|
connect(jobPtr, &NetJob::failed, this, &PackInstallTask::onModDownloadFailed);
|
||||||
abortable = false;
|
connect(jobPtr, &NetJob::progress, this, &PackInstallTask::setProgress);
|
||||||
jobPtr.reset();
|
|
||||||
install();
|
|
||||||
});
|
|
||||||
connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
|
|
||||||
{
|
|
||||||
abortable = false;
|
|
||||||
jobPtr.reset();
|
|
||||||
emitFailed(reason);
|
|
||||||
});
|
|
||||||
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
|
|
||||||
{
|
|
||||||
abortable = true;
|
|
||||||
setProgress(current, total);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
m_net_job = jobPtr;
|
||||||
jobPtr->start();
|
jobPtr->start();
|
||||||
|
|
||||||
|
m_abortable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PackInstallTask::onModDownloadSucceeded()
|
||||||
|
{
|
||||||
|
m_net_job.reset();
|
||||||
|
install();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PackInstallTask::install()
|
void PackInstallTask::install()
|
||||||
{
|
{
|
||||||
setStatus(tr("Copying modpack files"));
|
setStatus(tr("Copying modpack files..."));
|
||||||
|
setProgress(0, m_files_to_copy.size());
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
for (auto iter = filesToCopy.begin(); iter != filesToCopy.end(); iter++) {
|
m_abortable = false;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (auto iter = m_files_to_copy.constBegin(); iter != m_files_to_copy.constEnd(); iter++) {
|
||||||
auto& to = iter.key();
|
auto& to = iter.key();
|
||||||
auto& from = iter.value();
|
auto& from = iter.value();
|
||||||
FS::copy fileCopyOperation(from, to);
|
FS::copy fileCopyOperation(from, to);
|
||||||
@ -193,9 +284,13 @@ void PackInstallTask::install()
|
|||||||
emitFailed(tr("Failed to copy files"));
|
emitFailed(tr("Failed to copy files"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setProgress(i++, m_files_to_copy.size());
|
||||||
|
QCoreApplication::processEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus(tr("Installing modpack"));
|
setStatus(tr("Installing modpack..."));
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
||||||
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
|
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
|
||||||
@ -213,12 +308,12 @@ void PackInstallTask::install()
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto target : m_version.targets) {
|
for (auto target : m_version.targets) {
|
||||||
if(target.type != "modloader") continue;
|
if (target.type != "modloader")
|
||||||
|
continue;
|
||||||
|
|
||||||
if (target.name == "forge") {
|
if (target.name == "forge") {
|
||||||
components->setComponentVersion("net.minecraftforge", target.version);
|
components->setComponentVersion("net.minecraftforge", target.version);
|
||||||
}
|
} else if (target.name == "fabric") {
|
||||||
else if(target.name == "fabric") {
|
|
||||||
components->setComponentVersion("net.fabricmc.fabric-loader", target.version);
|
components->setComponentVersion("net.fabricmc.fabric-loader", target.version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -245,4 +340,20 @@ void PackInstallTask::install()
|
|||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PackInstallTask::onManifestDownloadFailed(QString reason)
|
||||||
|
{
|
||||||
|
m_net_job.reset();
|
||||||
|
emitFailed(reason);
|
||||||
}
|
}
|
||||||
|
void PackInstallTask::onResolveModsFailed(QString reason)
|
||||||
|
{
|
||||||
|
m_net_job.reset();
|
||||||
|
emitFailed(reason);
|
||||||
|
}
|
||||||
|
void PackInstallTask::onModDownloadFailed(QString reason)
|
||||||
|
{
|
||||||
|
m_net_job.reset();
|
||||||
|
emitFailed(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ModpacksCH
|
||||||
|
@ -1,4 +1,24 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
* 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 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
|
* Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
* Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
|
* Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
|
||||||
*
|
*
|
||||||
@ -20,44 +40,60 @@
|
|||||||
#include "FTBPackManifest.h"
|
#include "FTBPackManifest.h"
|
||||||
|
|
||||||
#include "InstanceTask.h"
|
#include "InstanceTask.h"
|
||||||
|
#include "QObjectPtr.h"
|
||||||
|
#include "modplatform/flame/FileResolvingTask.h"
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
namespace ModpacksCH {
|
namespace ModpacksCH {
|
||||||
|
|
||||||
class PackInstallTask : public InstanceTask
|
class PackInstallTask final : public InstanceTask
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit PackInstallTask(Modpack pack, QString version);
|
explicit PackInstallTask(Modpack pack, QString version, QWidget* parent = nullptr);
|
||||||
virtual ~PackInstallTask(){}
|
~PackInstallTask() override = default;
|
||||||
|
|
||||||
bool canAbort() const override { return true; }
|
bool canAbort() const override { return m_abortable; }
|
||||||
bool abort() override;
|
bool abort() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void executeTask() override;
|
void executeTask() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onDownloadSucceeded();
|
void onManifestDownloadSucceeded();
|
||||||
void onDownloadFailed(QString reason);
|
void onResolveModsSucceeded();
|
||||||
|
void onModDownloadSucceeded();
|
||||||
|
|
||||||
|
void onManifestDownloadFailed(QString reason);
|
||||||
|
void onResolveModsFailed(QString reason);
|
||||||
|
void onModDownloadFailed(QString reason);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void resolveMods();
|
||||||
void downloadPack();
|
void downloadPack();
|
||||||
void install();
|
void install();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool abortable = false;
|
bool m_abortable = true;
|
||||||
|
|
||||||
NetJob::Ptr jobPtr;
|
NetJob::Ptr m_net_job = nullptr;
|
||||||
QByteArray response;
|
shared_qobject_ptr<Flame::FileResolvingTask> m_mod_id_resolver_task = nullptr;
|
||||||
|
|
||||||
|
QList<int> m_file_id_map;
|
||||||
|
|
||||||
|
QByteArray m_response;
|
||||||
|
|
||||||
Modpack m_pack;
|
Modpack m_pack;
|
||||||
QString m_version_name;
|
QString m_version_name;
|
||||||
Version m_version;
|
Version m_version;
|
||||||
|
|
||||||
QMap<QString, QString> filesToCopy;
|
QMap<QString, QString> m_files_to_copy;
|
||||||
|
|
||||||
|
//FIXME: nuke
|
||||||
|
QWidget* m_parent;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,23 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
|
* PolyMC - 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 2020 Jamie Mansfield <jmansfield@cadixdev.org>
|
* Copyright 2020 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
* Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
|
* Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
|
||||||
*
|
*
|
||||||
@ -127,13 +146,16 @@ static void loadVersionFile(ModpacksCH::VersionFile & a, QJsonObject & obj)
|
|||||||
a.path = Json::requireString(obj, "path");
|
a.path = Json::requireString(obj, "path");
|
||||||
a.name = Json::requireString(obj, "name");
|
a.name = Json::requireString(obj, "name");
|
||||||
a.version = Json::requireString(obj, "version");
|
a.version = Json::requireString(obj, "version");
|
||||||
a.url = Json::requireString(obj, "url");
|
a.url = Json::ensureString(obj, "url"); // optional
|
||||||
a.sha1 = Json::requireString(obj, "sha1");
|
a.sha1 = Json::requireString(obj, "sha1");
|
||||||
a.size = Json::requireInteger(obj, "size");
|
a.size = Json::requireInteger(obj, "size");
|
||||||
a.clientOnly = Json::requireBoolean(obj, "clientonly");
|
a.clientOnly = Json::requireBoolean(obj, "clientonly");
|
||||||
a.serverOnly = Json::requireBoolean(obj, "serveronly");
|
a.serverOnly = Json::requireBoolean(obj, "serveronly");
|
||||||
a.optional = Json::requireBoolean(obj, "optional");
|
a.optional = Json::requireBoolean(obj, "optional");
|
||||||
a.updated = Json::requireInteger(obj, "updated");
|
a.updated = Json::requireInteger(obj, "updated");
|
||||||
|
auto curseforgeObj = Json::ensureObject(obj, "curseforge"); // optional
|
||||||
|
a.curseforge.project_id = Json::ensureInteger(curseforgeObj, "project");
|
||||||
|
a.curseforge.file_id = Json::ensureInteger(curseforgeObj, "file");
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj)
|
void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj)
|
||||||
|
@ -1,4 +1,23 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
|
* PolyMC - 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 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
|
* Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
* Copyright 2020 Petr Mrazek <peterix@gmail.com>
|
* Copyright 2020 Petr Mrazek <peterix@gmail.com>
|
||||||
*
|
*
|
||||||
@ -97,6 +116,12 @@ struct VersionTarget
|
|||||||
int64_t updated;
|
int64_t updated;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct VersionFileCurseForge
|
||||||
|
{
|
||||||
|
int project_id;
|
||||||
|
int file_id;
|
||||||
|
};
|
||||||
|
|
||||||
struct VersionFile
|
struct VersionFile
|
||||||
{
|
{
|
||||||
int id;
|
int id;
|
||||||
@ -111,6 +136,7 @@ struct VersionFile
|
|||||||
bool serverOnly;
|
bool serverOnly;
|
||||||
bool optional;
|
bool optional;
|
||||||
int64_t updated;
|
int64_t updated;
|
||||||
|
VersionFileCurseForge curseforge;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Version
|
struct Version
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include <nonstd/optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace Technic {
|
namespace Technic {
|
||||||
|
|
||||||
@ -57,8 +57,8 @@ private:
|
|||||||
QString m_archivePath;
|
QString m_archivePath;
|
||||||
NetJob::Ptr m_filesNetJob;
|
NetJob::Ptr m_filesNetJob;
|
||||||
std::unique_ptr<QuaZip> m_packZip;
|
std::unique_ptr<QuaZip> m_packZip;
|
||||||
QFuture<nonstd::optional<QStringList>> m_extractFuture;
|
QFuture<std::optional<QStringList>> m_extractFuture;
|
||||||
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
|
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Technic
|
} // namespace Technic
|
||||||
|
44
launcher/net/NetUtils.h
Normal file
44
launcher/net/NetUtils.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
|
namespace Net {
|
||||||
|
inline bool isApplicationError(QNetworkReply::NetworkError x) {
|
||||||
|
// Mainly taken from https://github.com/qt/qtbase/blob/dev/src/network/access/qhttpthreaddelegate.cpp
|
||||||
|
static QSet<QNetworkReply::NetworkError> errors = {
|
||||||
|
QNetworkReply::ProtocolInvalidOperationError,
|
||||||
|
QNetworkReply::AuthenticationRequiredError,
|
||||||
|
QNetworkReply::ContentAccessDenied,
|
||||||
|
QNetworkReply::ContentNotFoundError,
|
||||||
|
QNetworkReply::ContentOperationNotPermittedError,
|
||||||
|
QNetworkReply::ProxyAuthenticationRequiredError,
|
||||||
|
QNetworkReply::ContentConflictError,
|
||||||
|
QNetworkReply::ContentGoneError,
|
||||||
|
QNetworkReply::InternalServerError,
|
||||||
|
QNetworkReply::OperationNotImplementedError,
|
||||||
|
QNetworkReply::ServiceUnavailableError,
|
||||||
|
QNetworkReply::UnknownServerError,
|
||||||
|
QNetworkReply::UnknownContentError
|
||||||
|
};
|
||||||
|
return errors.contains(x);
|
||||||
|
}
|
||||||
|
} // namespace Net
|
@ -252,6 +252,9 @@ public:
|
|||||||
TranslatedAction actionViewInstanceFolder;
|
TranslatedAction actionViewInstanceFolder;
|
||||||
TranslatedAction actionViewCentralModsFolder;
|
TranslatedAction actionViewCentralModsFolder;
|
||||||
|
|
||||||
|
QMenu * editMenu = nullptr;
|
||||||
|
TranslatedAction actionUndoTrashInstance;
|
||||||
|
|
||||||
QMenu * helpMenu = nullptr;
|
QMenu * helpMenu = nullptr;
|
||||||
TranslatedToolButton helpMenuButton;
|
TranslatedToolButton helpMenuButton;
|
||||||
TranslatedAction actionReportBug;
|
TranslatedAction actionReportBug;
|
||||||
@ -335,6 +338,14 @@ public:
|
|||||||
actionSettings->setShortcut(QKeySequence::Preferences);
|
actionSettings->setShortcut(QKeySequence::Preferences);
|
||||||
all_actions.append(&actionSettings);
|
all_actions.append(&actionSettings);
|
||||||
|
|
||||||
|
actionUndoTrashInstance = TranslatedAction(MainWindow);
|
||||||
|
connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), MainWindow, SLOT(undoTrashInstance()));
|
||||||
|
actionUndoTrashInstance->setObjectName(QStringLiteral("actionUndoTrashInstance"));
|
||||||
|
actionUndoTrashInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Undo Last Instance Deletion"));
|
||||||
|
actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
|
||||||
|
actionUndoTrashInstance->setShortcut(QKeySequence("Ctrl+Z"));
|
||||||
|
all_actions.append(&actionUndoTrashInstance);
|
||||||
|
|
||||||
if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
|
if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
|
||||||
actionReportBug = TranslatedAction(MainWindow);
|
actionReportBug = TranslatedAction(MainWindow);
|
||||||
actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
|
actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
|
||||||
@ -508,6 +519,9 @@ public:
|
|||||||
fileMenu->addSeparator();
|
fileMenu->addSeparator();
|
||||||
fileMenu->addAction(actionSettings);
|
fileMenu->addAction(actionSettings);
|
||||||
|
|
||||||
|
editMenu = menuBar->addMenu(tr("&Edit"));
|
||||||
|
editMenu->addAction(actionUndoTrashInstance);
|
||||||
|
|
||||||
viewMenu = menuBar->addMenu(tr("&View"));
|
viewMenu = menuBar->addMenu(tr("&View"));
|
||||||
viewMenu->setSeparatorsCollapsible(false);
|
viewMenu->setSeparatorsCollapsible(false);
|
||||||
viewMenu->addAction(actionCAT);
|
viewMenu->addAction(actionCAT);
|
||||||
@ -732,9 +746,10 @@ public:
|
|||||||
|
|
||||||
actionDeleteInstance = TranslatedAction(MainWindow);
|
actionDeleteInstance = TranslatedAction(MainWindow);
|
||||||
actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance"));
|
actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance"));
|
||||||
actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Dele&te Instance..."));
|
actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Dele&te Instance"));
|
||||||
actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance."));
|
actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance."));
|
||||||
actionDeleteInstance->setShortcuts({QKeySequence(tr("Backspace")), QKeySequence::Delete});
|
actionDeleteInstance->setShortcuts({QKeySequence(tr("Backspace")), QKeySequence::Delete});
|
||||||
|
actionDeleteInstance->setAutoRepeat(false);
|
||||||
all_actions.append(&actionDeleteInstance);
|
all_actions.append(&actionDeleteInstance);
|
||||||
|
|
||||||
actionCopyInstance = TranslatedAction(MainWindow);
|
actionCopyInstance = TranslatedAction(MainWindow);
|
||||||
@ -1150,6 +1165,11 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
|
|||||||
connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup()));
|
connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup()));
|
||||||
actions.append(actionDeleteGroup);
|
actions.append(actionDeleteGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QAction *actionUndoTrashInstance = new QAction("Undo last trash instance", this);
|
||||||
|
connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), SLOT(undoTrashInstance()));
|
||||||
|
actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
|
||||||
|
actions.append(actionUndoTrashInstance);
|
||||||
}
|
}
|
||||||
QMenu myMenu;
|
QMenu myMenu;
|
||||||
myMenu.addActions(actions);
|
myMenu.addActions(actions);
|
||||||
@ -1832,6 +1852,11 @@ void MainWindow::deleteGroup()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::undoTrashInstance()
|
||||||
|
{
|
||||||
|
APPLICATION->instances()->undoTrashInstance();
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionViewInstanceFolder_triggered()
|
void MainWindow::on_actionViewInstanceFolder_triggered()
|
||||||
{
|
{
|
||||||
QString str = APPLICATION->settings()->get("InstanceDir").toString();
|
QString str = APPLICATION->settings()->get("InstanceDir").toString();
|
||||||
@ -1957,7 +1982,12 @@ void MainWindow::on_actionDeleteInstance_triggered()
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto id = m_selectedInstance->id();
|
auto id = m_selectedInstance->id();
|
||||||
|
if (APPLICATION->instances()->trashInstance(id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto response = CustomMessageBox::selectable(
|
auto response = CustomMessageBox::selectable(
|
||||||
this,
|
this,
|
||||||
tr("CAREFUL!"),
|
tr("CAREFUL!"),
|
||||||
|
@ -145,6 +145,7 @@ private slots:
|
|||||||
void on_actionDeleteInstance_triggered();
|
void on_actionDeleteInstance_triggered();
|
||||||
|
|
||||||
void deleteGroup();
|
void deleteGroup();
|
||||||
|
void undoTrashInstance();
|
||||||
|
|
||||||
void on_actionExportInstance_triggered();
|
void on_actionExportInstance_triggered();
|
||||||
|
|
||||||
|
@ -158,8 +158,9 @@ void ModUpdateDialog::checkCandidates()
|
|||||||
if (!reason.isEmpty())
|
if (!reason.isEmpty())
|
||||||
text += tr("Reason: %1").arg(reason) + "<br>";
|
text += tr("Reason: %1").arg(reason) + "<br>";
|
||||||
if (!recover_url.isEmpty())
|
if (!recover_url.isEmpty())
|
||||||
text += tr("Possible solution: ") + tr("Getting the latest version manually:") + "<br>" +
|
//: %1 is the link to download it manually
|
||||||
QString("<a href='%1'>").arg(recover_url.toString()) + recover_url.toString() + "</a><br>";
|
text += tr("Possible solution: Getting the latest version manually:<br>%1<br>")
|
||||||
|
.arg(QString("<a href='%1'>%1</a>").arg(recover_url.toString()));
|
||||||
text += "<br>";
|
text += "<br>";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,9 +242,9 @@ auto ModUpdateDialog::ensureMetadata() -> bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
ChooseProviderDialog chooser(this);
|
ChooseProviderDialog chooser(this);
|
||||||
chooser.setDescription(tr("This mod (%1) does not have a metadata yet. We need to create one in order to keep relevant "
|
chooser.setDescription(tr("The mod '%1' does not have a metadata yet. We need to generate it in order to track relevant "
|
||||||
"information on how to update this "
|
"information on how to update this mod. "
|
||||||
"mod. To do this, please select a mod provider from which we can search for updates for %1.")
|
"To do this, please select a mod provider which we can use to check for updates for this mod.")
|
||||||
.arg(candidate->name()));
|
.arg(candidate->name()));
|
||||||
auto confirmed = chooser.exec() == QDialog::DialogCode::Accepted;
|
auto confirmed = chooser.exec() == QDialog::DialogCode::Accepted;
|
||||||
|
|
||||||
@ -330,7 +331,7 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::P
|
|||||||
|
|
||||||
m_second_try_metadata->addTask(task);
|
m_second_try_metadata->addTask(task);
|
||||||
} else {
|
} else {
|
||||||
QString reason{ tr("Didn't find a valid version on the selected mod provider(s)") };
|
QString reason{ tr("Couldn't find a valid version on the selected mod provider(s)") };
|
||||||
|
|
||||||
m_failed_metadata.append({mod, reason});
|
m_failed_metadata.append({mod, reason});
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ void ProgressDialog::setSkipButton(bool present, QString label)
|
|||||||
void ProgressDialog::on_skipButton_clicked(bool checked)
|
void ProgressDialog::on_skipButton_clicked(bool checked)
|
||||||
{
|
{
|
||||||
Q_UNUSED(checked);
|
Q_UNUSED(checked);
|
||||||
task->abort();
|
if (task->abort())
|
||||||
QDialog::reject();
|
QDialog::reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,13 +32,13 @@ class SortProxy : public QSortFilterProxyModel {
|
|||||||
|
|
||||||
const auto& mod = model->at(source_row);
|
const auto& mod = model->at(source_row);
|
||||||
|
|
||||||
if (mod.name().contains(filterRegularExpression()))
|
if (filterRegularExpression().match(mod.name()).hasMatch())
|
||||||
return true;
|
return true;
|
||||||
if (mod.description().contains(filterRegularExpression()))
|
if (filterRegularExpression().match(mod.description()).hasMatch())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
for (auto& author : mod.authors()) {
|
for (auto& author : mod.authors()) {
|
||||||
if (author.contains(filterRegularExpression())) {
|
if (filterRegularExpression().match(author).hasMatch()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,7 +182,7 @@ void ExternalResourcesPage::retranslate()
|
|||||||
void ExternalResourcesPage::filterTextChanged(const QString& newContents)
|
void ExternalResourcesPage::filterTextChanged(const QString& newContents)
|
||||||
{
|
{
|
||||||
m_viewFilter = newContents;
|
m_viewFilter = newContents;
|
||||||
m_filterModel->setFilterFixedString(m_viewFilter);
|
m_filterModel->setFilterRegularExpression(m_viewFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExternalResourcesPage::runningStateChanged(bool running)
|
void ExternalResourcesPage::runningStateChanged(bool running)
|
||||||
|
@ -155,7 +155,7 @@
|
|||||||
<string>Check for &Updates</string>
|
<string>Check for &Updates</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>"Tries to find / update all selected resources (all resources if none is selected)"</string>
|
<string>Try to check or update all selected resources (all resources if none are selected)</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -349,7 +349,7 @@ void InstanceSettingsPage::loadSettings()
|
|||||||
ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool());
|
ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool());
|
||||||
|
|
||||||
#if !defined(Q_OS_LINUX)
|
#if !defined(Q_OS_LINUX)
|
||||||
ui->perfomanceGroupBox->setVisible(false);
|
ui->settingsTabs->setTabVisible(ui->settingsTabs->indexOf(ui->performancePage), false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Miscellanous
|
// Miscellanous
|
||||||
|
@ -80,7 +80,7 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
|
|||||||
|
|
||||||
connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::installMods);
|
connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::installMods);
|
||||||
|
|
||||||
ui->actionUpdateItem->setToolTip(tr("Tries to find / update all selected mods (all mods if none is selected)"));
|
ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)"));
|
||||||
ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem);
|
ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem);
|
||||||
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
|
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
|
||||||
|
|
||||||
@ -190,10 +190,15 @@ void ModFolderPage::updateMods()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (update_dialog.noUpdates()) {
|
if (update_dialog.noUpdates()) {
|
||||||
CustomMessageBox::selectable(this, tr("Update checker"),
|
QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) };
|
||||||
(mods_list.size() == 1)
|
if (mods_list.size() > 1) {
|
||||||
? tr("'%1' is up-to-date! :)").arg(mods_list.front()->name())
|
if (use_all) {
|
||||||
: tr("All %1mods are up-to-date! :)").arg(use_all ? "" : (tr("selected") + " ")))
|
message = tr("All mods are up-to-date! :)");
|
||||||
|
} else {
|
||||||
|
message = tr("All selected mods are up-to-date! :)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CustomMessageBox::selectable(this, tr("Update checker"), message)
|
||||||
->exec();
|
->exec();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* PolyMC - Minecraft Launcher
|
||||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -126,7 +127,7 @@ void FtbPage::suggestCurrent()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion));
|
dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion, this));
|
||||||
for(auto art : selected.art) {
|
for(auto art : selected.art) {
|
||||||
if(art.type == "square") {
|
if(art.type == "square") {
|
||||||
QString editedLogoName;
|
QString editedLogoName;
|
||||||
|
@ -31,6 +31,7 @@ QPalette DarkTheme::colorScheme()
|
|||||||
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
|
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
|
||||||
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
|
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
|
||||||
darkPalette.setColor(QPalette::HighlightedText, Qt::black);
|
darkPalette.setColor(QPalette::HighlightedText, Qt::black);
|
||||||
|
darkPalette.setColor(QPalette::PlaceholderText, Qt::darkGray);
|
||||||
return fadeInactive(darkPalette, fadeAmount(), fadeColor());
|
return fadeInactive(darkPalette, fadeAmount(), fadeColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,14 +155,6 @@ Canonical implementation of the murmur2 hash, taken from [SMHasher](https://gith
|
|||||||
|
|
||||||
Public domain (the author disclaimed the copyright).
|
Public domain (the author disclaimed the copyright).
|
||||||
|
|
||||||
## optional-bare
|
|
||||||
|
|
||||||
A simple single-file header-only version of a C++17-like optional for default-constructible, copyable types, for C++98 and later.
|
|
||||||
|
|
||||||
Imported from: https://github.com/martinmoene/optional-bare/commit/0bb1d183bcee1e854c4ea196b533252c51f98b81
|
|
||||||
|
|
||||||
Boost Software License - Version 1.0
|
|
||||||
|
|
||||||
## quazip
|
## quazip
|
||||||
|
|
||||||
A zip manipulation library, forked for MultiMC's use.
|
A zip manipulation library, forked for MultiMC's use.
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.9.4)
|
|
||||||
project(optional-bare)
|
|
||||||
|
|
||||||
add_library(optional-bare INTERFACE)
|
|
||||||
target_include_directories(optional-bare INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
|
@ -1,23 +0,0 @@
|
|||||||
Boost Software License - Version 1.0 - August 17th, 2003
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
obtaining a copy of the software and accompanying documentation covered by
|
|
||||||
this license (the "Software") to use, reproduce, display, distribute,
|
|
||||||
execute, and transmit the Software, and to prepare derivative works of the
|
|
||||||
Software, and to permit third-parties to whom the Software is furnished to
|
|
||||||
do so, all subject to the following:
|
|
||||||
|
|
||||||
The copyright notices in the Software and this entire statement, including
|
|
||||||
the above license grant, this restriction and the following disclaimer,
|
|
||||||
must be included in all copies of the Software, in whole or in part, and
|
|
||||||
all derivative works of the Software, unless such copies or derivative
|
|
||||||
works are solely in the form of machine-executable object code generated by
|
|
||||||
a source language processor.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
|
||||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
|
||||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
|
||||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
DEALINGS IN THE SOFTWARE.
|
|
@ -1,5 +0,0 @@
|
|||||||
# optional bare
|
|
||||||
|
|
||||||
A simple single-file header-only version of a C++17-like optional for default-constructible, copyable types, for C++98 and later.
|
|
||||||
|
|
||||||
Imported from: https://github.com/martinmoene/optional-bare/commit/0bb1d183bcee1e854c4ea196b533252c51f98b81
|
|
@ -1,508 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2017-2019 by Martin Moene
|
|
||||||
//
|
|
||||||
// https://github.com/martinmoene/optional-bare
|
|
||||||
//
|
|
||||||
// Distributed under the Boost Software License, Version 1.0.
|
|
||||||
// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
||||||
|
|
||||||
#ifndef NONSTD_OPTIONAL_BARE_HPP
|
|
||||||
#define NONSTD_OPTIONAL_BARE_HPP
|
|
||||||
|
|
||||||
#define optional_bare_MAJOR 1
|
|
||||||
#define optional_bare_MINOR 1
|
|
||||||
#define optional_bare_PATCH 0
|
|
||||||
|
|
||||||
#define optional_bare_VERSION optional_STRINGIFY(optional_bare_MAJOR) "." optional_STRINGIFY(optional_bare_MINOR) "." optional_STRINGIFY(optional_bare_PATCH)
|
|
||||||
|
|
||||||
#define optional_STRINGIFY( x ) optional_STRINGIFY_( x )
|
|
||||||
#define optional_STRINGIFY_( x ) #x
|
|
||||||
|
|
||||||
// optional-bare configuration:
|
|
||||||
|
|
||||||
#define optional_OPTIONAL_DEFAULT 0
|
|
||||||
#define optional_OPTIONAL_NONSTD 1
|
|
||||||
#define optional_OPTIONAL_STD 2
|
|
||||||
|
|
||||||
#if !defined( optional_CONFIG_SELECT_OPTIONAL )
|
|
||||||
# define optional_CONFIG_SELECT_OPTIONAL ( optional_HAVE_STD_OPTIONAL ? optional_OPTIONAL_STD : optional_OPTIONAL_NONSTD )
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Control presence of exception handling (try and auto discover):
|
|
||||||
|
|
||||||
#ifndef optional_CONFIG_NO_EXCEPTIONS
|
|
||||||
# if _MSC_VER
|
|
||||||
# include <cstddef> // for _HAS_EXCEPTIONS
|
|
||||||
# endif
|
|
||||||
# if _MSC_VER
|
|
||||||
# include <cstddef> // for _HAS_EXCEPTIONS
|
|
||||||
# endif
|
|
||||||
# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS)
|
|
||||||
# define optional_CONFIG_NO_EXCEPTIONS 0
|
|
||||||
# else
|
|
||||||
# define optional_CONFIG_NO_EXCEPTIONS 1
|
|
||||||
# endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// C++ language version detection (C++20 is speculative):
|
|
||||||
// Note: VC14.0/1900 (VS2015) lacks too much from C++14.
|
|
||||||
|
|
||||||
#ifndef optional_CPLUSPLUS
|
|
||||||
# if defined(_MSVC_LANG ) && !defined(__clang__)
|
|
||||||
# define optional_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG )
|
|
||||||
# else
|
|
||||||
# define optional_CPLUSPLUS __cplusplus
|
|
||||||
# endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define optional_CPP98_OR_GREATER ( optional_CPLUSPLUS >= 199711L )
|
|
||||||
#define optional_CPP11_OR_GREATER ( optional_CPLUSPLUS >= 201103L )
|
|
||||||
#define optional_CPP14_OR_GREATER ( optional_CPLUSPLUS >= 201402L )
|
|
||||||
#define optional_CPP17_OR_GREATER ( optional_CPLUSPLUS >= 201703L )
|
|
||||||
#define optional_CPP20_OR_GREATER ( optional_CPLUSPLUS >= 202000L )
|
|
||||||
|
|
||||||
// C++ language version (represent 98 as 3):
|
|
||||||
|
|
||||||
#define optional_CPLUSPLUS_V ( optional_CPLUSPLUS / 100 - (optional_CPLUSPLUS > 200000 ? 2000 : 1994) )
|
|
||||||
|
|
||||||
// Use C++17 std::optional if available and requested:
|
|
||||||
|
|
||||||
#if optional_CPP17_OR_GREATER && defined(__has_include )
|
|
||||||
# if __has_include( <optional> )
|
|
||||||
# define optional_HAVE_STD_OPTIONAL 1
|
|
||||||
# else
|
|
||||||
# define optional_HAVE_STD_OPTIONAL 0
|
|
||||||
# endif
|
|
||||||
#else
|
|
||||||
# define optional_HAVE_STD_OPTIONAL 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define optional_USES_STD_OPTIONAL ( (optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_STD) || ((optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_DEFAULT) && optional_HAVE_STD_OPTIONAL) )
|
|
||||||
|
|
||||||
//
|
|
||||||
// Using std::optional:
|
|
||||||
//
|
|
||||||
|
|
||||||
#if optional_USES_STD_OPTIONAL
|
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace nonstd {
|
|
||||||
|
|
||||||
using std::in_place;
|
|
||||||
using std::in_place_type;
|
|
||||||
using std::in_place_index;
|
|
||||||
using std::in_place_t;
|
|
||||||
using std::in_place_type_t;
|
|
||||||
using std::in_place_index_t;
|
|
||||||
|
|
||||||
using std::optional;
|
|
||||||
using std::bad_optional_access;
|
|
||||||
using std::hash;
|
|
||||||
|
|
||||||
using std::nullopt;
|
|
||||||
using std::nullopt_t;
|
|
||||||
|
|
||||||
using std::operator==;
|
|
||||||
using std::operator!=;
|
|
||||||
using std::operator<;
|
|
||||||
using std::operator<=;
|
|
||||||
using std::operator>;
|
|
||||||
using std::operator>=;
|
|
||||||
using std::make_optional;
|
|
||||||
using std::swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else // optional_USES_STD_OPTIONAL
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
#if ! optional_CONFIG_NO_EXCEPTIONS
|
|
||||||
# include <stdexcept>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace nonstd { namespace optional_bare {
|
|
||||||
|
|
||||||
// type for nullopt
|
|
||||||
|
|
||||||
struct nullopt_t
|
|
||||||
{
|
|
||||||
struct init{};
|
|
||||||
nullopt_t( init ) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// extra parenthesis to prevent the most vexing parse:
|
|
||||||
|
|
||||||
const nullopt_t nullopt(( nullopt_t::init() ));
|
|
||||||
|
|
||||||
// optional access error.
|
|
||||||
|
|
||||||
#if ! optional_CONFIG_NO_EXCEPTIONS
|
|
||||||
|
|
||||||
class bad_optional_access : public std::logic_error
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit bad_optional_access()
|
|
||||||
: logic_error( "bad optional access" ) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // optional_CONFIG_NO_EXCEPTIONS
|
|
||||||
|
|
||||||
// Simplistic optional: requires T to be default constructible, copyable.
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
class optional
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
typedef void (optional::*safe_bool)() const;
|
|
||||||
|
|
||||||
public:
|
|
||||||
typedef T value_type;
|
|
||||||
|
|
||||||
optional()
|
|
||||||
: has_value_( false )
|
|
||||||
{}
|
|
||||||
|
|
||||||
optional( nullopt_t )
|
|
||||||
: has_value_( false )
|
|
||||||
{}
|
|
||||||
|
|
||||||
optional( T const & arg )
|
|
||||||
: has_value_( true )
|
|
||||||
, value_ ( arg )
|
|
||||||
{}
|
|
||||||
|
|
||||||
template< class U >
|
|
||||||
optional( optional<U> const & other )
|
|
||||||
: has_value_( other.has_value() )
|
|
||||||
, value_ ( other.value() )
|
|
||||||
{}
|
|
||||||
|
|
||||||
optional & operator=( nullopt_t )
|
|
||||||
{
|
|
||||||
reset();
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< class U >
|
|
||||||
optional & operator=( optional<U> const & other )
|
|
||||||
{
|
|
||||||
has_value_ = other.has_value();
|
|
||||||
value_ = other.value();
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void swap( optional & rhs )
|
|
||||||
{
|
|
||||||
using std::swap;
|
|
||||||
if ( has_value() == true && rhs.has_value() == true ) { swap( **this, *rhs ); }
|
|
||||||
else if ( has_value() == false && rhs.has_value() == true ) { initialize( *rhs ); rhs.reset(); }
|
|
||||||
else if ( has_value() == true && rhs.has_value() == false ) { rhs.initialize( **this ); reset(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// observers
|
|
||||||
|
|
||||||
value_type const * operator->() const
|
|
||||||
{
|
|
||||||
return assert( has_value() ),
|
|
||||||
&value_;
|
|
||||||
}
|
|
||||||
|
|
||||||
value_type * operator->()
|
|
||||||
{
|
|
||||||
return assert( has_value() ),
|
|
||||||
&value_;
|
|
||||||
}
|
|
||||||
|
|
||||||
value_type const & operator*() const
|
|
||||||
{
|
|
||||||
return assert( has_value() ),
|
|
||||||
value_;
|
|
||||||
}
|
|
||||||
|
|
||||||
value_type & operator*()
|
|
||||||
{
|
|
||||||
return assert( has_value() ),
|
|
||||||
value_;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if optional_CPP11_OR_GREATER
|
|
||||||
explicit operator bool() const
|
|
||||||
{
|
|
||||||
return has_value();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
operator safe_bool() const
|
|
||||||
{
|
|
||||||
return has_value() ? &optional::this_type_does_not_support_comparisons : 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool has_value() const
|
|
||||||
{
|
|
||||||
return has_value_;
|
|
||||||
}
|
|
||||||
|
|
||||||
value_type const & value() const
|
|
||||||
{
|
|
||||||
#if optional_CONFIG_NO_EXCEPTIONS
|
|
||||||
assert( has_value() );
|
|
||||||
#else
|
|
||||||
if ( ! has_value() )
|
|
||||||
throw bad_optional_access();
|
|
||||||
#endif
|
|
||||||
return value_;
|
|
||||||
}
|
|
||||||
|
|
||||||
value_type & value()
|
|
||||||
{
|
|
||||||
#if optional_CONFIG_NO_EXCEPTIONS
|
|
||||||
assert( has_value() );
|
|
||||||
#else
|
|
||||||
if ( ! has_value() )
|
|
||||||
throw bad_optional_access();
|
|
||||||
#endif
|
|
||||||
return value_;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< class U >
|
|
||||||
value_type value_or( U const & v ) const
|
|
||||||
{
|
|
||||||
return has_value() ? value() : static_cast<value_type>( v );
|
|
||||||
}
|
|
||||||
|
|
||||||
// modifiers
|
|
||||||
|
|
||||||
void reset()
|
|
||||||
{
|
|
||||||
has_value_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void this_type_does_not_support_comparisons() const {}
|
|
||||||
|
|
||||||
template< typename V >
|
|
||||||
void initialize( V const & value )
|
|
||||||
{
|
|
||||||
assert( ! has_value() );
|
|
||||||
value_ = value;
|
|
||||||
has_value_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool has_value_;
|
|
||||||
value_type value_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Relational operators
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator==( optional<T> const & x, optional<U> const & y )
|
|
||||||
{
|
|
||||||
return bool(x) != bool(y) ? false : bool(x) == false ? true : *x == *y;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator!=( optional<T> const & x, optional<U> const & y )
|
|
||||||
{
|
|
||||||
return !(x == y);
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator<( optional<T> const & x, optional<U> const & y )
|
|
||||||
{
|
|
||||||
return (!y) ? false : (!x) ? true : *x < *y;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator>( optional<T> const & x, optional<U> const & y )
|
|
||||||
{
|
|
||||||
return (y < x);
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator<=( optional<T> const & x, optional<U> const & y )
|
|
||||||
{
|
|
||||||
return !(y < x);
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator>=( optional<T> const & x, optional<U> const & y )
|
|
||||||
{
|
|
||||||
return !(x < y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comparison with nullopt
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
inline bool operator==( optional<T> const & x, nullopt_t )
|
|
||||||
{
|
|
||||||
return (!x);
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
inline bool operator==( nullopt_t, optional<T> const & x )
|
|
||||||
{
|
|
||||||
return (!x);
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
inline bool operator!=( optional<T> const & x, nullopt_t )
|
|
||||||
{
|
|
||||||
return bool(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
inline bool operator!=( nullopt_t, optional<T> const & x )
|
|
||||||
{
|
|
||||||
return bool(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
inline bool operator<( optional<T> const &, nullopt_t )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
inline bool operator<( nullopt_t, optional<T> const & x )
|
|
||||||
{
|
|
||||||
return bool(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
inline bool operator<=( optional<T> const & x, nullopt_t )
|
|
||||||
{
|
|
||||||
return (!x);
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
inline bool operator<=( nullopt_t, optional<T> const & )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
inline bool operator>( optional<T> const & x, nullopt_t )
|
|
||||||
{
|
|
||||||
return bool(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
inline bool operator>( nullopt_t, optional<T> const & )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
inline bool operator>=( optional<T> const &, nullopt_t )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
inline bool operator>=( nullopt_t, optional<T> const & x )
|
|
||||||
{
|
|
||||||
return (!x);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comparison with T
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator==( optional<T> const & x, U const & v )
|
|
||||||
{
|
|
||||||
return bool(x) ? *x == v : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator==( U const & v, optional<T> const & x )
|
|
||||||
{
|
|
||||||
return bool(x) ? v == *x : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator!=( optional<T> const & x, U const & v )
|
|
||||||
{
|
|
||||||
return bool(x) ? *x != v : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator!=( U const & v, optional<T> const & x )
|
|
||||||
{
|
|
||||||
return bool(x) ? v != *x : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator<( optional<T> const & x, U const & v )
|
|
||||||
{
|
|
||||||
return bool(x) ? *x < v : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator<( U const & v, optional<T> const & x )
|
|
||||||
{
|
|
||||||
return bool(x) ? v < *x : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator<=( optional<T> const & x, U const & v )
|
|
||||||
{
|
|
||||||
return bool(x) ? *x <= v : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator<=( U const & v, optional<T> const & x )
|
|
||||||
{
|
|
||||||
return bool(x) ? v <= *x : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator>( optional<T> const & x, U const & v )
|
|
||||||
{
|
|
||||||
return bool(x) ? *x > v : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator>( U const & v, optional<T> const & x )
|
|
||||||
{
|
|
||||||
return bool(x) ? v > *x : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator>=( optional<T> const & x, U const & v )
|
|
||||||
{
|
|
||||||
return bool(x) ? *x >= v : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename T, typename U >
|
|
||||||
inline bool operator>=( U const & v, optional<T> const & x )
|
|
||||||
{
|
|
||||||
return bool(x) ? v >= *x : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Specialized algorithms
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
void swap( optional<T> & x, optional<T> & y )
|
|
||||||
{
|
|
||||||
x.swap( y );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenience function to create an optional.
|
|
||||||
|
|
||||||
template< typename T >
|
|
||||||
inline optional<T> make_optional( T const & v )
|
|
||||||
{
|
|
||||||
return optional<T>( v );
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace optional-bare
|
|
||||||
|
|
||||||
using namespace optional_bare;
|
|
||||||
|
|
||||||
} // namespace nonstd
|
|
||||||
|
|
||||||
#endif // optional_USES_STD_OPTIONAL
|
|
||||||
|
|
||||||
#endif // NONSTD_OPTIONAL_BARE_HPP
|
|
@ -34,7 +34,6 @@ let
|
|||||||
libXxf86vm
|
libXxf86vm
|
||||||
libpulseaudio
|
libpulseaudio
|
||||||
libGL
|
libGL
|
||||||
stdenv.cc.cc.lib
|
|
||||||
];
|
];
|
||||||
|
|
||||||
# This variable will be passed to Minecraft by PolyMC
|
# This variable will be passed to Minecraft by PolyMC
|
||||||
@ -68,16 +67,20 @@ stdenv.mkDerivation rec {
|
|||||||
] ++ lib.optionals enableLTO [ "-DENABLE_LTO=on" ]
|
] ++ lib.optionals enableLTO [ "-DENABLE_LTO=on" ]
|
||||||
++ lib.optionals (msaClientID != "") [ "-DLauncher_MSA_CLIENT_ID=${msaClientID}" ];
|
++ lib.optionals (msaClientID != "") [ "-DLauncher_MSA_CLIENT_ID=${msaClientID}" ];
|
||||||
|
|
||||||
|
# we have to check if the system is NixOS before adding stdenv.cc.cc.lib (#923)
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
# xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128
|
# xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128
|
||||||
wrapQtApp $out/bin/polymc \
|
wrapQtApp $out/bin/polymc \
|
||||||
--set GAME_LIBRARY_PATH ${gameLibraryPath} \
|
--run '[ -f /etc/NIXOS ] && export LD_LIBRARY_PATH="${stdenv.cc.cc.lib}/lib:$LD_LIBRARY_PATH"' \
|
||||||
|
--prefix LD_LIBRARY_PATH : ${gameLibraryPath} \
|
||||||
--prefix POLYMC_JAVA_PATHS : ${javaPaths} \
|
--prefix POLYMC_JAVA_PATHS : ${javaPaths} \
|
||||||
--prefix PATH : ${lib.makeBinPath [ xorg.xrandr ]}
|
--prefix PATH : ${lib.makeBinPath [ xorg.xrandr ]}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
meta = with lib; {
|
meta = with lib; {
|
||||||
homepage = "https://polymc.org/";
|
homepage = "https://polymc.org/";
|
||||||
|
downloadPage = "https://polymc.org/download/";
|
||||||
|
changelog = "https://github.com/PolyMC/PolyMC/releases";
|
||||||
description = "A free, open source launcher for Minecraft";
|
description = "A free, open source launcher for Minecraft";
|
||||||
longDescription = ''
|
longDescription = ''
|
||||||
Allows you to have multiple, separate instances of Minecraft (each with
|
Allows you to have multiple, separate instances of Minecraft (each with
|
||||||
@ -85,7 +88,7 @@ stdenv.mkDerivation rec {
|
|||||||
their associated options with a simple interface.
|
their associated options with a simple interface.
|
||||||
'';
|
'';
|
||||||
platforms = platforms.unix;
|
platforms = platforms.unix;
|
||||||
license = licenses.gpl3Plus;
|
license = licenses.gpl3Only;
|
||||||
maintainers = with maintainers; [ starcraft66 kloenk ];
|
maintainers = with maintainers; [ starcraft66 kloenk ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user