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:
kumquat-ir 2022-07-31 14:54:50 -04:00
commit 579582740e
60 changed files with 1019 additions and 1299 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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
View File

@ -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": {

View File

@ -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, ... }:

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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,

View File

@ -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;
} }

View File

@ -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;
}; };

View File

@ -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 }'`

View File

@ -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);
} }

View File

@ -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

View File

@ -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;
} }

View File

@ -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);

View File

@ -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

View File

@ -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;
} }

View File

@ -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 */

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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)";
} }
} }

View File

@ -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) {

View File

@ -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

View File

@ -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();
} }

View File

@ -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;
} }

View File

@ -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)) {

View File

@ -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)) {

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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;

View File

@ -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..."));

View File

@ -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;

View File

@ -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;
} }

View File

@ -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;

View File

@ -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

View File

@ -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;
}; };
} }

View File

@ -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)

View File

@ -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

View File

@ -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
View 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

View File

@ -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!"),

View File

@ -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();

View File

@ -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});
} }

View File

@ -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();
} }

View File

@ -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)

View File

@ -155,7 +155,7 @@
<string>Check for &amp;Updates</string> <string>Check for &amp;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>

View File

@ -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

View File

@ -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;
} }

View File

@ -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;

View File

@ -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());
} }

View File

@ -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.

View File

@ -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")

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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 ];
}; };
} }