Compare commits
41 Commits
6.0
...
release-6.
Author | SHA1 | Date | |
---|---|---|---|
39bba9cbfa | |||
104c231740 | |||
bc376b22c7 | |||
54e03d602f | |||
d8a84d2aa3 | |||
a7ff74365d | |||
68c884f20d | |||
b7e96bdf62 | |||
16b48866f4 | |||
4827f7e317 | |||
92f6a34624 | |||
c5ce8bfb3e | |||
6429088472 | |||
bf9885dd7e | |||
d16b6fe634 | |||
4539d58d7d | |||
a8611a56fc | |||
c3d64aa984 | |||
3fc63fa196 | |||
4438684ce6 | |||
699fce4482 | |||
49060beae7 | |||
de561e4fd3 | |||
1de301752f | |||
cb8c389303 | |||
e1e0166c95 | |||
6f8e1ccf89 | |||
3751856a4e | |||
5203e72199 | |||
3b1ab3c974 | |||
3476bbebd9 | |||
45870497c6 | |||
bcf506488f | |||
040774d67b | |||
94410352f5 | |||
2a819f1ca0 | |||
25c63dd1e0 | |||
d7223972d8 | |||
0a6c1238eb | |||
c11575f5f5 | |||
783761ca2e |
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@ -61,7 +61,7 @@ jobs:
|
||||
qt_ver: 6
|
||||
qt_host: windows
|
||||
qt_arch: ''
|
||||
qt_version: '6.4.0'
|
||||
qt_version: '6.4.2'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
@ -73,7 +73,7 @@ jobs:
|
||||
qt_ver: 6
|
||||
qt_host: windows
|
||||
qt_arch: 'win64_msvc2019_arm64'
|
||||
qt_version: '6.4.0'
|
||||
qt_version: '6.4.2'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
@ -105,6 +105,7 @@ jobs:
|
||||
INSTALL_APPIMAGE_DIR: "install-appdir"
|
||||
BUILD_DIR: "build"
|
||||
CCACHE_VAR: ""
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
|
||||
|
||||
steps:
|
||||
##
|
||||
|
2
.github/workflows/winget.yml
vendored
2
.github/workflows/winget.yml
vendored
@ -11,5 +11,5 @@ jobs:
|
||||
with:
|
||||
identifier: PrismLauncher.PrismLauncher
|
||||
version: ${{ github.event.release.tag_name }}
|
||||
installers-regex: 'PrismLauncher-Windows-Setup-.+\.exe$'
|
||||
installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$'
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
|
@ -28,19 +28,28 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
include(GenerateExportHeader)
|
||||
if(MSVC)
|
||||
# Use /W4 as /Wall includes unnesserey warnings such as added padding to structs
|
||||
# /permissive- specify standards-conforming compiler behavior, also enabled by Qt6, default on with std:c++20
|
||||
# /GS Adds buffer security checks, default on but incuded anyway to mirror gcc's fstack-protector flag
|
||||
set(CMAKE_CXX_FLAGS "/W4 /permissive- /GS ${CMAKE_CXX_FLAGS}")
|
||||
# /permissive- specify standards-conforming compiler behavior, also enabled by Qt6, default on with std:c++20
|
||||
# Use /W4 as /Wall includes unnesserey warnings such as added padding to structs
|
||||
set(CMAKE_CXX_FLAGS "/GS /permissive- /W4 ${CMAKE_CXX_FLAGS}")
|
||||
|
||||
# LINK accepts /SUBSYSTEM whics sets if we are a WINDOWS (gui) or a CONSOLE programs
|
||||
# This implicitly selects an entrypoint specific to the subsystem selected
|
||||
# qtmain/QtEntryPointLib provides the correct entrypoint (wWinMain) for gui programs
|
||||
# Additinaly LINK autodetects we use a GUI so we can omit /SUBSYSTEM
|
||||
# This allows tests to still use have console without using seperate linker flags
|
||||
# /LTCG allows for linking wholy optimizated programs
|
||||
# /MANIFEST:NO disables generating a manifest file, we instead provide our own
|
||||
# /STACK sets the stack reserve size, ATL's pack list needs 3-4 MiB as of November 2022, provide 8 MiB
|
||||
set(CMAKE_EXE_LINKER_FLAGS "/MANIFEST:NO /STACK:8388608 ${CMAKE_EXE_LINKER_FLAGS}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "/LTCG /MANIFEST:NO /STACK:8388608 ${CMAKE_EXE_LINKER_FLAGS}")
|
||||
|
||||
# /GL enables whole program optimizations
|
||||
# /Gw helps reduce binary size
|
||||
# /Gy allows the compiler to package individual functions
|
||||
# /guard:cf enables control flow guard
|
||||
foreach(lang C CXX)
|
||||
set("CMAKE_${lang}_FLAGS_RELEASE" "/GL /Gw /Gy /guard:cf")
|
||||
endforeach()
|
||||
|
||||
# See https://github.com/ccache/ccache/issues/1040
|
||||
# Note, CMake 3.25 replaces this with CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
|
||||
@ -130,7 +139,7 @@ set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRIN
|
||||
|
||||
######## Set version numbers ########
|
||||
set(Launcher_VERSION_MAJOR 6)
|
||||
set(Launcher_VERSION_MINOR 0)
|
||||
set(Launcher_VERSION_MINOR 3)
|
||||
|
||||
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
|
||||
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0")
|
||||
@ -199,9 +208,15 @@ set(Launcher_BUILD_TIMESTAMP "${TODAY}")
|
||||
|
||||
################################ 3rd Party Libs ################################
|
||||
|
||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||
# Successive configurations of cmake without cleaning the build dir will cause zlib fallback to fail due to cached values
|
||||
# Record when fallback triggered and skip this find_package
|
||||
if(NOT Launcher_FORCE_BUNDLED_LIBS AND NOT FORCE_BUNDLED_ZLIB)
|
||||
find_package(ZLIB QUIET)
|
||||
endif()
|
||||
if(NOT ZLIB_FOUND)
|
||||
set(FORCE_BUNDLED_ZLIB TRUE CACHE BOOL "")
|
||||
mark_as_advanced(FORCE_BUNDLED_ZLIB)
|
||||
endif()
|
||||
|
||||
# Find the required Qt parts
|
||||
include(QtVersionlessBackport)
|
||||
@ -259,6 +274,8 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||
find_package(ghc_filesystem QUIET)
|
||||
endif()
|
||||
|
||||
include(ECMQtDeclareLoggingCategory)
|
||||
|
||||
####################################### Program Info #######################################
|
||||
|
||||
set(Launcher_APP_BINARY_NAME "prismlauncher" CACHE STRING "Name of the Launcher binary")
|
||||
@ -366,13 +383,24 @@ add_subdirectory(libraries/systeminfo) # system information library
|
||||
add_subdirectory(libraries/hoedown) # markdown parser
|
||||
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
|
||||
add_subdirectory(libraries/javacheck) # java compatibility checker
|
||||
if(NOT ZLIB_FOUND)
|
||||
if(FORCE_BUNDLED_ZLIB)
|
||||
message(STATUS "Using bundled zlib")
|
||||
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Suppress cmake warnings and allow INTERPROCEDURAL_OPTIMIZATION for zlib
|
||||
set(SKIP_INSTALL_ALL ON)
|
||||
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL)
|
||||
|
||||
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib" CACHE STRING "")
|
||||
# On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not.
|
||||
# We cannot safely undo the rename on those systems, and they generally have packages for zlib anyway.
|
||||
check_include_file(unistd.h NEED_GENERATED_ZCONF)
|
||||
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" AND NOT NEED_GENERATED_ZCONF)
|
||||
# zlib's cmake script renames a file, dirtying the submodule, see https://github.com/madler/zlib/issues/162
|
||||
message(STATUS "Undoing Rename")
|
||||
message(STATUS " ${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h")
|
||||
file(RENAME "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h")
|
||||
endif()
|
||||
|
||||
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib" "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" CACHE STRING "" FORCE)
|
||||
set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}")
|
||||
add_library(ZLIB::ZLIB ALIAS zlibstatic)
|
||||
set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "zlib library name")
|
||||
|
@ -1,7 +1,7 @@
|
||||
## Prism Launcher
|
||||
|
||||
Prism Launcher - Minecraft Launcher
|
||||
Copyright (C) 2022 Prism Launcher Contributors
|
||||
Copyright (C) 2022-2023 Prism Launcher Contributors
|
||||
|
||||
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
|
||||
|
@ -76,7 +76,9 @@ Config::Config()
|
||||
|
||||
// Assume that builds outside of Git repos are "stable"
|
||||
if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND")
|
||||
|| GIT_TAG == QStringLiteral("GITDIR-NOTFOUND"))
|
||||
|| GIT_TAG == QStringLiteral("GITDIR-NOTFOUND")
|
||||
|| GIT_REFSPEC == QStringLiteral("")
|
||||
|| GIT_TAG == QStringLiteral("GIT-NOTFOUND"))
|
||||
{
|
||||
GIT_REFSPEC = "refs/heads/stable";
|
||||
GIT_TAG = versionString();
|
||||
|
31
flake.lock
generated
31
flake.lock
generated
@ -3,11 +3,11 @@
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1650374568,
|
||||
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
|
||||
"lastModified": 1668681692,
|
||||
"narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
|
||||
"rev": "009399224d5e398d03b22badca40a37ac85412a1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -34,11 +34,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1666057921,
|
||||
"narHash": "sha256-VpQqtXdj6G7cH//SvoprjR7XT3KS7p+tCVebGK1N6tE=",
|
||||
"lastModified": 1671417167,
|
||||
"narHash": "sha256-JkHam6WQOwZN1t2C2sbp1TqMv3TVRjzrdoejqfefwrM=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "88eab1e431cabd0ed621428d8b40d425a07af39f",
|
||||
"rev": "bb31220cca6d044baa6dc2715b07497a2a7c4bc7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -52,24 +52,7 @@
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"libnbtplusplus": "libnbtplusplus",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"tomlplusplus": "tomlplusplus"
|
||||
}
|
||||
},
|
||||
"tomlplusplus": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1666091090,
|
||||
"narHash": "sha256-djpMCFPvkJcfynV8WnsYdtwLq+J7jpV1iM4C6TojiyM=",
|
||||
"owner": "marzer",
|
||||
"repo": "tomlplusplus",
|
||||
"rev": "1e4a3833d013aee08f58c5b31c69f709afc69f73",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "marzer",
|
||||
"repo": "tomlplusplus",
|
||||
"type": "github"
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -5,10 +5,9 @@
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
|
||||
libnbtplusplus = { url = "github:PrismLauncher/libnbtplusplus"; flake = false; };
|
||||
tomlplusplus = { url = "github:marzer/tomlplusplus"; flake = false; };
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, libnbtplusplus, tomlplusplus, ... }:
|
||||
outputs = { self, nixpkgs, libnbtplusplus, ... }:
|
||||
let
|
||||
# User-friendly version number.
|
||||
version = builtins.substring 0 8 self.lastModifiedDate;
|
||||
@ -23,8 +22,8 @@
|
||||
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
|
||||
|
||||
packagesFn = pkgs: rec {
|
||||
prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
|
||||
prismlauncher = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
|
||||
prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; };
|
||||
prismlauncher = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; };
|
||||
};
|
||||
in
|
||||
{
|
||||
|
@ -62,11 +62,6 @@
|
||||
#include "ui/pages/global/APIPage.h"
|
||||
#include "ui/pages/global/CustomCommandsPage.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "ui/WinDarkmode.h"
|
||||
#include <versionhelpers.h>
|
||||
#endif
|
||||
|
||||
#include "ui/setupwizard/SetupWizard.h"
|
||||
#include "ui/setupwizard/LanguageWizardPage.h"
|
||||
#include "ui/setupwizard/JavaWizardPage.h"
|
||||
@ -146,19 +141,12 @@ static const QLatin1String liveCheckFile("live.check");
|
||||
PixmapCache* PixmapCache::s_instance = nullptr;
|
||||
|
||||
namespace {
|
||||
|
||||
/** This is used so that we can output to the log file in addition to the CLI. */
|
||||
void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
const char *levels = "DWCFIS";
|
||||
const QString format("%1 %2 %3\n");
|
||||
|
||||
qint64 msecstotal = APPLICATION->timeSinceStart();
|
||||
qint64 seconds = msecstotal / 1000;
|
||||
qint64 msecs = msecstotal % 1000;
|
||||
QString foo;
|
||||
char buf[1025] = {0};
|
||||
::snprintf(buf, 1024, "%5lld.%03lld", seconds, msecs);
|
||||
|
||||
QString out = format.arg(buf).arg(levels[type]).arg(msg);
|
||||
QString out = qFormatLogMessage(type, context, msg);
|
||||
out += QChar::LineFeed;
|
||||
|
||||
APPLICATION->logFile->write(out.toUtf8());
|
||||
APPLICATION->logFile->flush();
|
||||
@ -431,6 +419,14 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
return;
|
||||
}
|
||||
qInstallMessageHandler(appDebugOutput);
|
||||
|
||||
qSetMessagePattern(
|
||||
"%{time process}" " "
|
||||
"%{if-debug}D%{endif}" "%{if-info}I%{endif}" "%{if-warning}W%{endif}" "%{if-critical}C%{endif}" "%{if-fatal}F%{endif}"
|
||||
" " "|" " "
|
||||
"%{if-category}[%{category}]: %{endif}"
|
||||
"%{message}");
|
||||
|
||||
qDebug() << "<> Log initialized.";
|
||||
}
|
||||
|
||||
@ -1352,16 +1348,7 @@ MainWindow* Application::showMainWindow(bool minimized)
|
||||
m_mainWindow = new MainWindow();
|
||||
m_mainWindow->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").toByteArray()));
|
||||
m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").toByteArray()));
|
||||
#ifdef Q_OS_WIN
|
||||
if (IsWindows10OrGreater())
|
||||
{
|
||||
if (QString::compare(settings()->get("ApplicationTheme").toString(), "dark") == 0) {
|
||||
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
|
||||
} else {
|
||||
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(minimized)
|
||||
{
|
||||
m_mainWindow->showMinimized();
|
||||
@ -1538,7 +1525,8 @@ QString Application::getJarPath(QString jarFile)
|
||||
FS::PathCombine(m_rootPath, "share/" + BuildConfig.LAUNCHER_APP_BINARY_NAME),
|
||||
#endif
|
||||
FS::PathCombine(m_rootPath, "jars"),
|
||||
FS::PathCombine(applicationDirPath(), "jars")
|
||||
FS::PathCombine(applicationDirPath(), "jars"),
|
||||
FS::PathCombine(applicationDirPath(), "..", "jars") // from inside build dir, for debuging
|
||||
};
|
||||
for(QString p : potentialPaths)
|
||||
{
|
||||
|
@ -47,8 +47,8 @@ void ApplicationMessage::parse(const QByteArray & input) {
|
||||
args.clear();
|
||||
|
||||
auto parsedArgs = root.value("args").toObject();
|
||||
for(auto iter = parsedArgs.begin(); iter != parsedArgs.end(); iter++) {
|
||||
args[iter.key()] = iter.value().toString();
|
||||
for(auto iter = parsedArgs.constBegin(); iter != parsedArgs.constEnd(); iter++) {
|
||||
args.insert(iter.key(), iter.value().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,8 +56,8 @@ QByteArray ApplicationMessage::serialize() {
|
||||
QJsonObject root;
|
||||
root.insert("command", command);
|
||||
QJsonObject outArgs;
|
||||
for (auto iter = args.begin(); iter != args.end(); iter++) {
|
||||
outArgs[iter.key()] = iter.value();
|
||||
for (auto iter = args.constBegin(); iter != args.constEnd(); iter++) {
|
||||
outArgs.insert(iter.key(), iter.value());
|
||||
}
|
||||
root.insert("args", outArgs);
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QHash>
|
||||
#include <QByteArray>
|
||||
|
||||
struct ApplicationMessage {
|
||||
QString command;
|
||||
QMap<QString, QString> args;
|
||||
QHash<QString, QString> args;
|
||||
|
||||
QByteArray serialize();
|
||||
void parse(const QByteArray & input);
|
||||
|
@ -551,6 +551,24 @@ set(ATLAUNCHER_SOURCES
|
||||
modplatform/atlauncher/ATLShareCode.h
|
||||
)
|
||||
|
||||
######## Logging categories ########
|
||||
|
||||
ecm_qt_declare_logging_category(CORE_SOURCES
|
||||
HEADER Logging.h
|
||||
IDENTIFIER authCredentials
|
||||
CATEGORY_NAME "launcher.auth.credentials"
|
||||
DEFAULT_SEVERITY Warning
|
||||
DESCRIPTION "Secrets and credentials for debugging purposes"
|
||||
EXPORT "${Launcher_Name}"
|
||||
)
|
||||
|
||||
if(KDE_INSTALL_LOGGINGCATEGORIESDIR) # only install if there is a standard path for this
|
||||
ecm_qt_install_logging_categories(
|
||||
EXPORT "${Launcher_Name}"
|
||||
DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
################################ COMPILE ################################
|
||||
|
||||
set(LOGIC_SOURCES
|
||||
@ -905,16 +923,6 @@ SET(LAUNCHER_SOURCES
|
||||
ui/instanceview/VisualGroup.h
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
set(LAUNCHER_SOURCES
|
||||
${LAUNCHER_SOURCES}
|
||||
|
||||
# GUI - dark titlebar for Windows 10/11
|
||||
ui/WinDarkmode.h
|
||||
ui/WinDarkmode.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
qt_wrap_ui(LAUNCHER_UI
|
||||
ui/setupwizard/PasteWizardPage.ui
|
||||
ui/pages/global/AccountListPage.ui
|
||||
@ -1166,6 +1174,8 @@ if(INSTALL_BUNDLE STREQUAL "full")
|
||||
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
PATTERN "*qopensslbackend*" EXCLUDE
|
||||
PATTERN "*qcertonlybackend*" EXCLUDE
|
||||
)
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/tls"
|
||||
@ -1175,6 +1185,8 @@ if(INSTALL_BUNDLE STREQUAL "full")
|
||||
REGEX "dd\\." EXCLUDE
|
||||
REGEX "_debug\\." EXCLUDE
|
||||
REGEX "\\.dSYM" EXCLUDE
|
||||
PATTERN "*qopensslbackend*" EXCLUDE
|
||||
PATTERN "*qcertonlybackend*" EXCLUDE
|
||||
)
|
||||
endif()
|
||||
configure_file(
|
||||
|
@ -56,6 +56,7 @@
|
||||
#include <shlobj.h>
|
||||
#include <shobjidl.h>
|
||||
#include <sys/utime.h>
|
||||
#include <versionhelpers.h>
|
||||
#include <windows.h>
|
||||
#include <winnls.h>
|
||||
#include <string>
|
||||
@ -234,6 +235,10 @@ bool trash(QString path, QString *pathInTrash = nullptr)
|
||||
// FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal
|
||||
if (DesktopServices::isFlatpak())
|
||||
return false;
|
||||
#if defined Q_OS_WIN32
|
||||
if (IsWindowsServer())
|
||||
return false;
|
||||
#endif
|
||||
return QFile::moveToTrash(path, pathInTrash);
|
||||
#endif
|
||||
}
|
||||
|
@ -257,20 +257,26 @@ void InstanceImportTask::extractAborted()
|
||||
|
||||
void InstanceImportTask::processFlame()
|
||||
{
|
||||
auto pack_id_it = m_extra_info.constFind("pack_id");
|
||||
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
|
||||
auto pack_id = pack_id_it.value();
|
||||
FlameCreationTask* inst_creation_task = nullptr;
|
||||
if (!m_extra_info.isEmpty()) {
|
||||
auto pack_id_it = m_extra_info.constFind("pack_id");
|
||||
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
|
||||
auto pack_id = pack_id_it.value();
|
||||
|
||||
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
|
||||
Q_ASSERT(pack_version_id_it != m_extra_info.constEnd());
|
||||
auto pack_version_id = pack_version_id_it.value();
|
||||
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
|
||||
Q_ASSERT(pack_version_id_it != m_extra_info.constEnd());
|
||||
auto pack_version_id = pack_version_id_it.value();
|
||||
|
||||
QString original_instance_id;
|
||||
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
|
||||
if (original_instance_id_it != m_extra_info.constEnd())
|
||||
original_instance_id = original_instance_id_it.value();
|
||||
QString original_instance_id;
|
||||
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
|
||||
if (original_instance_id_it != m_extra_info.constEnd())
|
||||
original_instance_id = original_instance_id_it.value();
|
||||
|
||||
auto* inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
|
||||
inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
|
||||
} else {
|
||||
// FIXME: Find a way to get IDs in directly imported ZIPs
|
||||
inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, {}, {});
|
||||
}
|
||||
|
||||
inst_creation_task->setName(*this);
|
||||
inst_creation_task->setIcon(m_instIcon);
|
||||
@ -335,21 +341,33 @@ void InstanceImportTask::processMultiMC()
|
||||
|
||||
void InstanceImportTask::processModrinth()
|
||||
{
|
||||
auto pack_id_it = m_extra_info.constFind("pack_id");
|
||||
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
|
||||
auto pack_id = pack_id_it.value();
|
||||
ModrinthCreationTask* inst_creation_task = nullptr;
|
||||
if (!m_extra_info.isEmpty()) {
|
||||
auto pack_id_it = m_extra_info.constFind("pack_id");
|
||||
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
|
||||
auto pack_id = pack_id_it.value();
|
||||
|
||||
QString pack_version_id;
|
||||
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
|
||||
if (pack_version_id_it != m_extra_info.constEnd())
|
||||
pack_version_id = pack_version_id_it.value();
|
||||
QString pack_version_id;
|
||||
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
|
||||
if (pack_version_id_it != m_extra_info.constEnd())
|
||||
pack_version_id = pack_version_id_it.value();
|
||||
|
||||
QString original_instance_id;
|
||||
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
|
||||
if (original_instance_id_it != m_extra_info.constEnd())
|
||||
original_instance_id = original_instance_id_it.value();
|
||||
QString original_instance_id;
|
||||
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
|
||||
if (original_instance_id_it != m_extra_info.constEnd())
|
||||
original_instance_id = original_instance_id_it.value();
|
||||
|
||||
auto* inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
|
||||
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
|
||||
} else {
|
||||
QString pack_id;
|
||||
if (!m_sourceUrl.isEmpty()) {
|
||||
QRegularExpression regex(R"(data\/(.*)\/versions)");
|
||||
pack_id = regex.match(m_sourceUrl.toString()).captured(1);
|
||||
}
|
||||
|
||||
// FIXME: Find a way to get the ID in directly imported ZIPs
|
||||
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id);
|
||||
}
|
||||
|
||||
inst_creation_task->setName(*this);
|
||||
inst_creation_task->setIcon(m_instIcon);
|
||||
|
@ -39,6 +39,8 @@
|
||||
#include "minecraft/ParseUtils.h"
|
||||
#include <minecraft/MojangVersionFormat.h>
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
using namespace Json;
|
||||
|
||||
static void readString(const QJsonObject &root, const QString &key, QString &variable)
|
||||
@ -121,6 +123,15 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
out->uid = root.value("fileId").toString();
|
||||
}
|
||||
|
||||
const QRegularExpression valid_uid_regex{ QRegularExpression::anchoredPattern(QStringLiteral(R"([a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]+)*)")) };
|
||||
if (!valid_uid_regex.match(out->uid).hasMatch()) {
|
||||
qCritical() << "The component's 'uid' contains illegal characters! UID:" << out->uid;
|
||||
out->addProblem(
|
||||
ProblemSeverity::Error,
|
||||
QObject::tr("The component's 'uid' contains illegal characters! This can cause security issues.")
|
||||
);
|
||||
}
|
||||
|
||||
out->version = root.value("version").toString();
|
||||
|
||||
MojangVersionFormat::readVersionProperties(root, out.get());
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "Parsers.h"
|
||||
#include "Json.h"
|
||||
#include "Logging.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
@ -75,9 +76,7 @@ bool getBool(QJsonValue value, bool & out) {
|
||||
|
||||
bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString name) {
|
||||
qDebug() << "Parsing" << name <<":";
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << data;
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
@ -137,9 +136,7 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString na
|
||||
|
||||
bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
|
||||
qDebug() << "Parsing Minecraft profile...";
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
@ -275,9 +272,7 @@ decoded base64 "value":
|
||||
|
||||
bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
|
||||
qDebug() << "Parsing Minecraft profile...";
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
@ -389,9 +384,7 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
|
||||
|
||||
bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) {
|
||||
qDebug() << "Parsing Minecraft entitlements...";
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
@ -424,9 +417,7 @@ bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output)
|
||||
|
||||
bool parseRolloutResponse(QByteArray & data, bool& result) {
|
||||
qDebug() << "Parsing Rollout response...";
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
@ -455,9 +446,7 @@ bool parseRolloutResponse(QByteArray & data, bool& result) {
|
||||
bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
|
||||
QJsonParseError jsonError;
|
||||
qDebug() << "Parsing Mojang response...";
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << data;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
qWarning() << "Failed to parse response from api.minecraftservices.com/launcher/login as JSON: " << jsonError.errorString();
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <QNetworkRequest>
|
||||
#include <QUuid>
|
||||
|
||||
#include "Logging.h"
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
|
||||
@ -41,9 +42,7 @@ void EntitlementsStep::onRequestDone(
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
||||
// TODO: check presence of same entitlementsRequestId?
|
||||
// TODO: validate JWTs?
|
||||
|
@ -2,9 +2,10 @@
|
||||
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "Logging.h"
|
||||
#include "minecraft/auth/AccountTask.h"
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "minecraft/auth/AccountTask.h"
|
||||
#include "net/NetUtils.h"
|
||||
|
||||
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {
|
||||
@ -51,14 +52,10 @@ void LauncherLoginStep::onRequestDone(
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (error != QNetworkReply::NoError) {
|
||||
qWarning() << "Reply error:" << error;
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (Net::isApplicationError(error)) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
@ -76,9 +73,7 @@ void LauncherLoginStep::onRequestDone(
|
||||
|
||||
if(!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) {
|
||||
qWarning() << "Could not parse login_with_xbox response...";
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << data;
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to parse the Minecraft access token response.")
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "Logging.h"
|
||||
|
||||
using OAuth2 = Katabasis::DeviceFlow;
|
||||
using Activity = Katabasis::Activity;
|
||||
@ -117,14 +118,12 @@ void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity) {
|
||||
// Succeeded or did not invalidate tokens
|
||||
emit hideVerificationUriAndCode();
|
||||
QVariantMap extraTokens = m_oauth2->extraTokens();
|
||||
#ifndef NDEBUG
|
||||
if (!extraTokens.isEmpty()) {
|
||||
qDebug() << "Extra tokens in response:";
|
||||
qCDebug(authCredentials()) << "Extra tokens in response:";
|
||||
foreach (QString key, extraTokens.keys()) {
|
||||
qDebug() << "\t" << key << ":" << extraTokens.value(key);
|
||||
qCDebug(authCredentials()) << "\t" << key << ":" << extraTokens.value(key);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
|
||||
return;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "Logging.h"
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
@ -40,9 +41,7 @@ void MinecraftProfileStep::onRequestDone(
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (error == QNetworkReply::ContentNotFoundError) {
|
||||
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
|
||||
if(m_data->type == AccountType::Mojang) {
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "Logging.h"
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
@ -43,9 +44,7 @@ void MinecraftProfileStepMojang::onRequestDone(
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (error == QNetworkReply::ContentNotFoundError) {
|
||||
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
|
||||
if(m_data->type == AccountType::Mojang) {
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "Logging.h"
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
@ -58,9 +59,7 @@ void XboxAuthorizationStep::onRequestDone(
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (error != QNetworkReply::NoError) {
|
||||
qWarning() << "Reply error:" << error;
|
||||
if (Net::isApplicationError(error)) {
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include <QNetworkRequest>
|
||||
#include <QUrlQuery>
|
||||
|
||||
|
||||
#include "Logging.h"
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
@ -56,9 +56,7 @@ void XboxProfileStep::onRequestDone(
|
||||
|
||||
if (error != QNetworkReply::NoError) {
|
||||
qWarning() << "Reply error:" << error;
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (Net::isApplicationError(error)) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
@ -74,9 +72,7 @@ void XboxProfileStep::onRequestDone(
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
qDebug() << "XBox profile: " << data;
|
||||
#endif
|
||||
qCDebug(authCredentials()) << "XBox profile: " << data;
|
||||
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile"));
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent) : QAbstractL
|
||||
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
|
||||
|
||||
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
|
||||
connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this]{ m_helper_thread_task.clear(); });
|
||||
}
|
||||
|
||||
ResourceFolderModel::~ResourceFolderModel()
|
||||
@ -259,7 +260,7 @@ void ResourceFolderModel::resolveResource(Resource* res)
|
||||
return;
|
||||
}
|
||||
|
||||
auto task = createParseTask(*res);
|
||||
Task::Ptr task{ createParseTask(*res) };
|
||||
if (!task)
|
||||
return;
|
||||
|
||||
@ -269,13 +270,17 @@ void ResourceFolderModel::resolveResource(Resource* res)
|
||||
m_active_parse_tasks.insert(ticket, task);
|
||||
|
||||
connect(
|
||||
task, &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
||||
task.get(), &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
||||
connect(
|
||||
task, &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
||||
task.get(), &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
|
||||
connect(
|
||||
task, &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection);
|
||||
task.get(), &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection);
|
||||
|
||||
QThreadPool::globalInstance()->start(task);
|
||||
m_helper_thread_task.addTask(task);
|
||||
|
||||
if (!m_helper_thread_task.isRunning()) {
|
||||
QThreadPool::globalInstance()->start(&m_helper_thread_task);
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceFolderModel::onUpdateSucceeded()
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "Resource.h"
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
|
||||
class QSortFilterProxyModel;
|
||||
|
||||
@ -197,6 +198,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
// Represents the relationship between a resource's internal ID and it's row position on the model.
|
||||
QMap<QString, int> m_resources_index;
|
||||
|
||||
ConcurrentTask m_helper_thread_task;
|
||||
QMap<int, Task::Ptr> m_active_parse_tasks;
|
||||
std::atomic<int> m_next_resolution_ticket = 0;
|
||||
};
|
||||
|
@ -16,7 +16,7 @@
|
||||
namespace {
|
||||
|
||||
// NEW format
|
||||
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3
|
||||
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/c8d8f1929aff9979e322af79a59ce81f3e02db6a
|
||||
|
||||
// OLD format:
|
||||
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
|
||||
@ -73,10 +73,11 @@ ModDetails ReadMCModInfo(QByteArray contents)
|
||||
version = Json::ensureString(val, "").toInt();
|
||||
|
||||
if (version != 2) {
|
||||
qCritical() << "BAD stuff happened to mod json:";
|
||||
qCritical() << contents;
|
||||
return {};
|
||||
qWarning() << QString(R"(The value of 'modListVersion' is "%1" (expected "2")! The file may be corrupted.)").arg(version);
|
||||
qWarning() << "The contents of 'mcmod.info' are as follows:";
|
||||
qWarning() << contents;
|
||||
}
|
||||
|
||||
auto arrVal = jsonDoc.object().value("modlist");
|
||||
if (arrVal.isUndefined()) {
|
||||
arrVal = jsonDoc.object().value("modList");
|
||||
|
@ -361,7 +361,9 @@ bool FlameCreationTask::createInstance()
|
||||
FS::deletePath(jarmodsPath);
|
||||
}
|
||||
|
||||
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
|
||||
// Don't add managed info to packs without an ID (most likely imported from ZIP)
|
||||
if (!m_managed_id.isEmpty())
|
||||
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
|
||||
instance.setName(name());
|
||||
|
||||
m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), m_pack);
|
||||
|
@ -202,14 +202,14 @@ bool ModrinthCreationTask::createInstance()
|
||||
|
||||
auto components = instance.getPackProfile();
|
||||
components->buildingFromScratch();
|
||||
components->setComponentVersion("net.minecraft", minecraftVersion, true);
|
||||
components->setComponentVersion("net.minecraft", m_minecraft_version, true);
|
||||
|
||||
if (!fabricVersion.isEmpty())
|
||||
components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);
|
||||
if (!quiltVersion.isEmpty())
|
||||
components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion);
|
||||
if (!forgeVersion.isEmpty())
|
||||
components->setComponentVersion("net.minecraftforge", forgeVersion);
|
||||
if (!m_fabric_version.isEmpty())
|
||||
components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version);
|
||||
if (!m_quilt_version.isEmpty())
|
||||
components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
|
||||
if (!m_forge_version.isEmpty())
|
||||
components->setComponentVersion("net.minecraftforge", m_forge_version);
|
||||
|
||||
if (m_instIcon != "default") {
|
||||
instance.setIconKey(m_instIcon);
|
||||
@ -217,16 +217,27 @@ bool ModrinthCreationTask::createInstance()
|
||||
instance.setIconKey("modrinth");
|
||||
}
|
||||
|
||||
instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
|
||||
// Don't add managed info to packs without an ID (most likely imported from ZIP)
|
||||
if (!m_managed_id.isEmpty())
|
||||
instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
|
||||
instance.setName(name());
|
||||
instance.saveNow();
|
||||
|
||||
m_files_job = new NetJob(tr("Mod download"), APPLICATION->network());
|
||||
|
||||
auto root_modpack_path = FS::PathCombine(m_stagingPath, ".minecraft");
|
||||
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
|
||||
|
||||
for (auto file : m_files) {
|
||||
auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path);
|
||||
qDebug() << "Will try to download" << file.downloads.front() << "to" << path;
|
||||
auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
|
||||
auto file_path = FS::PathCombine(root_modpack_path, file.path);
|
||||
if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) {
|
||||
// This means we somehow got out of the root folder, so abort here to prevent exploits
|
||||
setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.").arg(file.path));
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path;
|
||||
auto dl = Net::Download::makeFile(file.downloads.dequeue(), file_path);
|
||||
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
||||
m_files_job->addNetAction(dl);
|
||||
|
||||
@ -234,8 +245,8 @@ bool ModrinthCreationTask::createInstance()
|
||||
// FIXME: This really needs to be put into a ConcurrentTask of
|
||||
// MultipleOptionsTask's , once those exist :)
|
||||
auto param = dl.toWeakRef();
|
||||
connect(dl.get(), &NetAction::failed, [this, &file, path, param] {
|
||||
auto ndl = Net::Download::makeFile(file.downloads.dequeue(), path);
|
||||
connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] {
|
||||
auto ndl = Net::Download::makeFile(file.downloads.dequeue(), file_path);
|
||||
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
||||
m_files_job->addNetAction(ndl);
|
||||
if (auto shared = param.lock()) shared->succeeded();
|
||||
@ -277,7 +288,7 @@ bool ModrinthCreationTask::createInstance()
|
||||
return ended_well;
|
||||
}
|
||||
|
||||
bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<Modrinth::File>& files, bool set_managed_info, bool show_optional_dialog)
|
||||
bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<Modrinth::File>& files, bool set_internal_data, bool show_optional_dialog)
|
||||
{
|
||||
try {
|
||||
auto doc = Json::requireDocument(index_path);
|
||||
@ -289,7 +300,7 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
|
||||
throw JSONValidationError("Unknown game: " + game);
|
||||
}
|
||||
|
||||
if (set_managed_info) {
|
||||
if (set_internal_data) {
|
||||
if (m_managed_version_id.isEmpty())
|
||||
m_managed_version_id = Json::ensureString(obj, "versionId", {}, "Managed ID");
|
||||
m_managed_name = Json::ensureString(obj, "name", {}, "Managed Name");
|
||||
@ -365,19 +376,21 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
|
||||
files.push_back(file);
|
||||
}
|
||||
|
||||
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
||||
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
|
||||
QString name = it.key();
|
||||
if (name == "minecraft") {
|
||||
minecraftVersion = Json::requireString(*it, "Minecraft version");
|
||||
} else if (name == "fabric-loader") {
|
||||
fabricVersion = Json::requireString(*it, "Fabric Loader version");
|
||||
} else if (name == "quilt-loader") {
|
||||
quiltVersion = Json::requireString(*it, "Quilt Loader version");
|
||||
} else if (name == "forge") {
|
||||
forgeVersion = Json::requireString(*it, "Forge version");
|
||||
} else {
|
||||
throw JSONValidationError("Unknown dependency type: " + name);
|
||||
if (set_internal_data) {
|
||||
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
||||
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
|
||||
QString name = it.key();
|
||||
if (name == "minecraft") {
|
||||
m_minecraft_version = Json::requireString(*it, "Minecraft version");
|
||||
} else if (name == "fabric-loader") {
|
||||
m_fabric_version = Json::requireString(*it, "Fabric Loader version");
|
||||
} else if (name == "quilt-loader") {
|
||||
m_quilt_version = Json::requireString(*it, "Quilt Loader version");
|
||||
} else if (name == "forge") {
|
||||
m_forge_version = Json::requireString(*it, "Forge version");
|
||||
} else {
|
||||
throw JSONValidationError("Unknown dependency type: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -37,12 +37,12 @@ class ModrinthCreationTask final : public InstanceCreationTask {
|
||||
bool createInstance() override;
|
||||
|
||||
private:
|
||||
bool parseManifest(const QString&, std::vector<Modrinth::File>&, bool set_managed_info = true, bool show_optional_dialog = true);
|
||||
bool parseManifest(const QString&, std::vector<Modrinth::File>&, bool set_internal_data = true, bool show_optional_dialog = true);
|
||||
|
||||
private:
|
||||
QWidget* m_parent = nullptr;
|
||||
|
||||
QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;
|
||||
QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version;
|
||||
QString m_managed_id, m_managed_version_id, m_managed_name;
|
||||
|
||||
std::vector<Modrinth::File> m_files;
|
||||
|
@ -172,7 +172,7 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const
|
||||
auto libraryObject = Json::ensureObject(library, {}, "");
|
||||
auto libraryName = Json::ensureString(libraryObject, "name", "", "");
|
||||
|
||||
if (libraryName.startsWith("net.minecraftforge:forge:") && libraryName.contains('-'))
|
||||
if ((libraryName.startsWith("net.minecraftforge:forge:") || libraryName.startsWith("net.minecraftforge:fmlloader:")) && libraryName.contains('-'))
|
||||
{
|
||||
QString libraryVersion = libraryName.section(':', 2);
|
||||
if (!libraryVersion.startsWith("1.7.10-"))
|
||||
|
@ -123,7 +123,7 @@ auto NetJob::getFailedFiles() -> QList<QString>
|
||||
|
||||
void NetJob::updateState()
|
||||
{
|
||||
emit progress(m_done.count(), m_total_size);
|
||||
emit progress(m_done.count(), totalSize());
|
||||
setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)")
|
||||
.arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(m_total_size)));
|
||||
.arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize())));
|
||||
}
|
||||
|
@ -27,18 +27,13 @@ auto ConcurrentTask::getStepTotalProgress() const -> qint64
|
||||
|
||||
void ConcurrentTask::addTask(Task::Ptr task)
|
||||
{
|
||||
if (!isRunning())
|
||||
m_queue.append(task);
|
||||
else
|
||||
qWarning() << "Tried to add a task to a running concurrent task!";
|
||||
m_queue.append(task);
|
||||
}
|
||||
|
||||
void ConcurrentTask::executeTask()
|
||||
{
|
||||
m_total_size = m_queue.size();
|
||||
|
||||
// Start the least amount of tasks needed, but at least one
|
||||
int num_starts = std::max(1, std::min(m_total_max_size, m_total_size));
|
||||
int num_starts = qMax(1, qMin(m_total_max_size, m_queue.size()));
|
||||
for (int i = 0; i < num_starts; i++) {
|
||||
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection);
|
||||
}
|
||||
@ -73,6 +68,20 @@ bool ConcurrentTask::abort()
|
||||
return suceedeed;
|
||||
}
|
||||
|
||||
void ConcurrentTask::clear()
|
||||
{
|
||||
Q_ASSERT(!isRunning());
|
||||
|
||||
m_done.clear();
|
||||
m_failed.clear();
|
||||
m_queue.clear();
|
||||
|
||||
m_aborted = false;
|
||||
|
||||
m_progress = 0;
|
||||
m_stepProgress = 0;
|
||||
}
|
||||
|
||||
void ConcurrentTask::startNext()
|
||||
{
|
||||
if (m_aborted || m_doing.count() > m_total_max_size)
|
||||
@ -103,7 +112,12 @@ void ConcurrentTask::startNext()
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
next->start();
|
||||
QMetaObject::invokeMethod(next.get(), &Task::start, Qt::QueuedConnection);
|
||||
|
||||
// Allow going up the number of concurrent tasks in case of tasks being added in the middle of a running task.
|
||||
int num_starts = qMin(m_queue.size(), m_total_max_size - m_doing.size());
|
||||
for (int i = 0; i < num_starts; i++)
|
||||
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void ConcurrentTask::subTaskSucceeded(Task::Ptr task)
|
||||
@ -145,7 +159,7 @@ void ConcurrentTask::subTaskProgress(qint64 current, qint64 total)
|
||||
|
||||
void ConcurrentTask::updateState()
|
||||
{
|
||||
setProgress(m_done.count(), m_total_size);
|
||||
setProgress(m_done.count(), totalSize());
|
||||
setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)")
|
||||
.arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(m_total_size)));
|
||||
.arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize())));
|
||||
}
|
||||
|
@ -24,6 +24,11 @@ public:
|
||||
public slots:
|
||||
bool abort() override;
|
||||
|
||||
/** Resets the internal state of the task.
|
||||
* This allows the same task to be re-used.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
protected
|
||||
slots:
|
||||
void executeTask() override;
|
||||
@ -36,6 +41,9 @@ slots:
|
||||
void subTaskProgress(qint64 current, qint64 total);
|
||||
|
||||
protected:
|
||||
// NOTE: This is not thread-safe.
|
||||
[[nodiscard]] unsigned int totalSize() const { return m_queue.size() + m_doing.size() + m_done.size(); }
|
||||
|
||||
void setStepStatus(QString status) { m_step_status = status; emit stepStatus(status); };
|
||||
|
||||
virtual void updateState();
|
||||
@ -51,7 +59,6 @@ protected:
|
||||
QHash<Task*, Task::Ptr> m_failed;
|
||||
|
||||
int m_total_max_size;
|
||||
int m_total_size;
|
||||
|
||||
qint64 m_stepProgress = 0;
|
||||
qint64 m_stepTotalProgress = 100;
|
||||
|
@ -22,6 +22,6 @@ void MultipleOptionsTask::startNext()
|
||||
|
||||
void MultipleOptionsTask::updateState()
|
||||
{
|
||||
setProgress(m_done.count(), m_total_size);
|
||||
setStatus(tr("Attempting task %1 out of %2").arg(QString::number(m_doing.count() + m_done.count()), QString::number(m_total_size)));
|
||||
setProgress(m_done.count(), totalSize());
|
||||
setStatus(tr("Attempting task %1 out of %2").arg(QString::number(m_doing.count() + m_done.count()), QString::number(totalSize())));
|
||||
}
|
||||
|
@ -17,6 +17,6 @@ void SequentialTask::startNext()
|
||||
|
||||
void SequentialTask::updateState()
|
||||
{
|
||||
setProgress(m_done.count(), m_total_size);
|
||||
setStatus(tr("Executing task %1 out of %2").arg(QString::number(m_doing.count() + m_done.count()), QString::number(m_total_size)));
|
||||
setProgress(m_done.count(), totalSize());
|
||||
setStatus(tr("Executing task %1 out of %2").arg(QString::number(m_doing.count() + m_done.count()), QString::number(totalSize())));
|
||||
}
|
||||
|
@ -1683,7 +1683,7 @@ InstanceView
|
||||
background-image: url(:/backgrounds/%1);
|
||||
background-attachment: fixed;
|
||||
background-clip: padding;
|
||||
background-position: bottom left;
|
||||
background-position: bottom right;
|
||||
background-repeat: none;
|
||||
background-color:palette(base);
|
||||
})")
|
||||
|
@ -1,32 +0,0 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "WinDarkmode.h"
|
||||
|
||||
namespace WinDarkmode {
|
||||
|
||||
/* See https://github.com/statiolake/neovim-qt/commit/da8eaba7f0e38b6b51f3bacd02a8cc2d1f7a34d8 */
|
||||
void setDarkWinTitlebar(WId winid, bool darkmode)
|
||||
{
|
||||
HWND hwnd = reinterpret_cast<HWND>(winid);
|
||||
BOOL dark = (BOOL) darkmode;
|
||||
|
||||
HMODULE hUxtheme = LoadLibraryExW(L"uxtheme.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
HMODULE hUser32 = GetModuleHandleW(L"user32.dll");
|
||||
fnAllowDarkModeForWindow AllowDarkModeForWindow
|
||||
= reinterpret_cast<fnAllowDarkModeForWindow>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133)));
|
||||
fnSetPreferredAppMode SetPreferredAppMode
|
||||
= reinterpret_cast<fnSetPreferredAppMode>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135)));
|
||||
fnSetWindowCompositionAttribute SetWindowCompositionAttribute
|
||||
= reinterpret_cast<fnSetWindowCompositionAttribute>(GetProcAddress(hUser32, "SetWindowCompositionAttribute"));
|
||||
|
||||
SetPreferredAppMode(AllowDark);
|
||||
AllowDarkModeForWindow(hwnd, dark);
|
||||
WINDOWCOMPOSITIONATTRIBDATA data = {
|
||||
WCA_USEDARKMODECOLORS,
|
||||
&dark,
|
||||
sizeof(dark)
|
||||
};
|
||||
SetWindowCompositionAttribute(hwnd, &data);
|
||||
}
|
||||
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <dwmapi.h>
|
||||
|
||||
|
||||
namespace WinDarkmode {
|
||||
|
||||
void setDarkWinTitlebar(WId winid, bool darkmode);
|
||||
|
||||
enum PreferredAppMode {
|
||||
Default,
|
||||
AllowDark,
|
||||
ForceDark,
|
||||
ForceLight,
|
||||
Max
|
||||
};
|
||||
|
||||
enum WINDOWCOMPOSITIONATTRIB {
|
||||
WCA_UNDEFINED = 0,
|
||||
WCA_NCRENDERING_ENABLED = 1,
|
||||
WCA_NCRENDERING_POLICY = 2,
|
||||
WCA_TRANSITIONS_FORCEDISABLED = 3,
|
||||
WCA_ALLOW_NCPAINT = 4,
|
||||
WCA_CAPTION_BUTTON_BOUNDS = 5,
|
||||
WCA_NONCLIENT_RTL_LAYOUT = 6,
|
||||
WCA_FORCE_ICONIC_REPRESENTATION = 7,
|
||||
WCA_EXTENDED_FRAME_BOUNDS = 8,
|
||||
WCA_HAS_ICONIC_BITMAP = 9,
|
||||
WCA_THEME_ATTRIBUTES = 10,
|
||||
WCA_NCRENDERING_EXILED = 11,
|
||||
WCA_NCADORNMENTINFO = 12,
|
||||
WCA_EXCLUDED_FROM_LIVEPREVIEW = 13,
|
||||
WCA_VIDEO_OVERLAY_ACTIVE = 14,
|
||||
WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15,
|
||||
WCA_DISALLOW_PEEK = 16,
|
||||
WCA_CLOAK = 17,
|
||||
WCA_CLOAKED = 18,
|
||||
WCA_ACCENT_POLICY = 19,
|
||||
WCA_FREEZE_REPRESENTATION = 20,
|
||||
WCA_EVER_UNCLOAKED = 21,
|
||||
WCA_VISUAL_OWNER = 22,
|
||||
WCA_HOLOGRAPHIC = 23,
|
||||
WCA_EXCLUDED_FROM_DDA = 24,
|
||||
WCA_PASSIVEUPDATEMODE = 25,
|
||||
WCA_USEDARKMODECOLORS = 26,
|
||||
WCA_LAST = 27
|
||||
};
|
||||
|
||||
struct WINDOWCOMPOSITIONATTRIBDATA {
|
||||
WINDOWCOMPOSITIONATTRIB Attrib;
|
||||
PVOID pvData;
|
||||
SIZE_T cbData;
|
||||
};
|
||||
|
||||
using fnAllowDarkModeForWindow = BOOL (WINAPI *)(HWND hWnd, BOOL allow);
|
||||
using fnSetPreferredAppMode = PreferredAppMode (WINAPI *)(PreferredAppMode appMode);
|
||||
using fnSetWindowCompositionAttribute = BOOL (WINAPI *)(HWND hwnd, WINDOWCOMPOSITIONATTRIBDATA *);
|
||||
|
||||
}
|
@ -47,7 +47,6 @@ IconPickerDialog::IconPickerDialog(QWidget *parent)
|
||||
contentsWidget->setUniformItemSizes(true);
|
||||
contentsWidget->setTextElideMode(Qt::ElideRight);
|
||||
contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
contentsWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
contentsWidget->setItemDelegate(new ListViewDelegate());
|
||||
|
||||
|
@ -29,7 +29,6 @@ ImportResourcePackDialog::ImportResourcePackDialog(QWidget* parent) : QDialog(pa
|
||||
// NOTE: We can't have uniform sizes because the text may wrap if it's too long. If we set this, it will cut off the wrapped text.
|
||||
contentsWidget->setUniformItemSizes(false);
|
||||
contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
contentsWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
contentsWidget->setItemDelegate(new ListViewDelegate());
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include <QListView>
|
||||
#include <QProxyStyle>
|
||||
#include <QStyleFactory>
|
||||
|
||||
#include <HoeDown.h>
|
||||
|
||||
@ -60,7 +61,10 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->versionsComboBox->setStyle(new NoBigComboBoxStyle(ui->versionsComboBox->style()));
|
||||
// NOTE: GTK2 themes crash with the proxy style.
|
||||
// This seems like an upstream bug, so there's not much else that can be done.
|
||||
if (!QStyleFactory::keys().contains("gtk2"))
|
||||
ui->versionsComboBox->setStyle(new NoBigComboBoxStyle(ui->versionsComboBox->style()));
|
||||
|
||||
ui->reloadButton->setVisible(false);
|
||||
connect(ui->reloadButton, &QPushButton::clicked, this, [this](bool){
|
||||
@ -223,17 +227,16 @@ void ModrinthManagedPackPage::parseManagedPack()
|
||||
ui->versionsComboBox->blockSignals(false);
|
||||
|
||||
for (auto version : m_pack.versions) {
|
||||
QString name;
|
||||
QString name = version.version;
|
||||
|
||||
if (!version.name.contains(version.version))
|
||||
name = QString("%1 — %2").arg(version.name, version.version);
|
||||
else
|
||||
name = version.name;
|
||||
|
||||
// NOTE: the id from version isn't the same id in the modpack format spec...
|
||||
// e.g. HexMC's 4.4.0 has versionId 4.0.0 in the modpack index..............
|
||||
if (version.version == m_inst->getManagedPackVersionName())
|
||||
name.append(tr(" (Current)"));
|
||||
name = tr("%1 (Current)").arg(name);
|
||||
|
||||
|
||||
ui->versionsComboBox->addItem(name, QVariant(version.id));
|
||||
}
|
||||
@ -370,12 +373,10 @@ void FlameManagedPackPage::parseManagedPack()
|
||||
ui->versionsComboBox->blockSignals(false);
|
||||
|
||||
for (auto version : m_pack.versions) {
|
||||
QString name;
|
||||
|
||||
name = version.version;
|
||||
QString name = version.version;
|
||||
|
||||
if (version.fileId == m_inst->getManagedPackVersionID().toInt())
|
||||
name.append(tr(" (Current)"));
|
||||
name = tr("%1 (Current)").arg(name);
|
||||
|
||||
ui->versionsComboBox->addItem(name, QVariant(version.fileId));
|
||||
}
|
||||
|
@ -17,17 +17,11 @@
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="noteEditor">
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOn</enum>
|
||||
</property>
|
||||
<property name="tabChangesFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
@ -48,9 +48,6 @@
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOn</enum>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
@ -28,9 +28,6 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="ModListView" name="packageView">
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOn</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
|
@ -428,6 +428,10 @@ void ModPage::updateUi()
|
||||
text += "<hr>";
|
||||
|
||||
HoeDown h;
|
||||
|
||||
// hoedown bug: it doesn't handle markdown surrounded by block tags (like center, div) so strip them
|
||||
current.extraData.body.remove(QRegularExpression("<[^>]*(?:center|div)\\W*>"));
|
||||
|
||||
ui->packDescription->setHtml(text + (current.extraData.body.isEmpty() ? current.description : h.process(current.extraData.body.toUtf8())));
|
||||
ui->packDescription->flush();
|
||||
}
|
||||
|
@ -6,19 +6,14 @@
|
||||
|
||||
void ITheme::apply(bool)
|
||||
{
|
||||
APPLICATION->setStyleSheet(QString());
|
||||
QApplication::setStyle(QStyleFactory::create(qtTheme()));
|
||||
if(hasColorScheme())
|
||||
{
|
||||
if (hasColorScheme()) {
|
||||
QApplication::setPalette(colorScheme());
|
||||
}
|
||||
if(hasStyleSheet())
|
||||
{
|
||||
if (hasStyleSheet())
|
||||
APPLICATION->setStyleSheet(appStyleSheet());
|
||||
}
|
||||
else
|
||||
{
|
||||
APPLICATION->setStyleSheet(QString());
|
||||
}
|
||||
|
||||
QDir::setSearchPaths("theme", searchPaths());
|
||||
}
|
||||
|
||||
|
@ -28,14 +28,6 @@
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
// this is needed for versionhelpers.h, it is also included in WinDarkmode, but we can't rely on that.
|
||||
// Ultimately this should be included in versionhelpers, but that is outside of the project.
|
||||
#include "ui/WinDarkmode.h"
|
||||
#include <versionhelpers.h>
|
||||
#endif
|
||||
|
||||
ThemeManager::ThemeManager(MainWindow* mainWindow)
|
||||
{
|
||||
m_mainWindow = mainWindow;
|
||||
@ -140,15 +132,6 @@ void ThemeManager::setApplicationTheme(const QString& name, bool initial)
|
||||
auto& theme = themeIter->second;
|
||||
themeDebugLog() << "applying theme" << theme->name();
|
||||
theme->apply(initial);
|
||||
#ifdef Q_OS_WIN
|
||||
if (m_mainWindow && IsWindows10OrGreater()) {
|
||||
if (QString::compare(theme->id(), "dark") == 0) {
|
||||
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
|
||||
} else {
|
||||
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
themeWarningLog() << "Tried to set invalid theme:" << name;
|
||||
}
|
||||
|
@ -31,7 +31,6 @@ ModListView::ModListView ( QWidget* parent )
|
||||
setSelectionMode ( QAbstractItemView::ExtendedSelection );
|
||||
setHeaderHidden ( false );
|
||||
setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOn );
|
||||
setHorizontalScrollBarPolicy ( Qt::ScrollBarAsNeeded );
|
||||
setDropIndicatorShown(true);
|
||||
setDragEnabled(true);
|
||||
|
@ -101,7 +101,7 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source,
|
||||
|
||||
auto full_entry_path = entry->getFullPath();
|
||||
auto source_url = source;
|
||||
connect(job, &NetJob::succeeded, [this, doc, full_entry_path, source_url, posInDocument] {
|
||||
connect(job, &NetJob::succeeded, this, [this, doc, full_entry_path, source_url, posInDocument] {
|
||||
qDebug() << "Loaded resource at" << full_entry_path;
|
||||
|
||||
// If we flushed, don't proceed.
|
||||
|
@ -38,6 +38,15 @@ set( katabasis_PUBLIC
|
||||
include/katabasis/RequestParameter.h
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(katabasis_PRIVATE
|
||||
HEADER KatabasisLogging.h # NOTE: this won't be in src/, but CMAKE_BINARY_DIR/src isn't included by default so this should be fine
|
||||
IDENTIFIER katabasisCredentials
|
||||
CATEGORY_NAME "katabasis.credentials"
|
||||
DEFAULT_SEVERITY Warning
|
||||
DESCRIPTION "Secrets and credentials from Katabasis"
|
||||
EXPORT "Katabasis"
|
||||
)
|
||||
|
||||
add_library( Katabasis STATIC ${katabasis_PRIVATE} ${katabasis_PUBLIC} )
|
||||
target_link_libraries(Katabasis Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
|
@ -19,9 +19,11 @@
|
||||
#include "katabasis/PollServer.h"
|
||||
#include "katabasis/Globals.h"
|
||||
|
||||
#include "KatabasisLogging.h"
|
||||
#include "JsonResponse.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// ref: https://tools.ietf.org/html/rfc8628#section-3.2
|
||||
// Exception: Google sign-in uses "verification_url" instead of "*_uri" - we'll accept both.
|
||||
bool hasMandatoryDeviceAuthParams(const QVariantMap& params)
|
||||
@ -333,9 +335,7 @@ QString DeviceFlow::refreshToken() {
|
||||
}
|
||||
|
||||
void DeviceFlow::setRefreshToken(const QString &v) {
|
||||
#ifndef NDEBUG
|
||||
qDebug() << "DeviceFlow::setRefreshToken" << v << "...";
|
||||
#endif
|
||||
qCDebug(katabasisCredentials) << "new refresh token:" << v;
|
||||
token_.refresh_token = v;
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
, stdenv
|
||||
, cmake
|
||||
, jdk8
|
||||
, jdk
|
||||
, jdk17
|
||||
, zlib
|
||||
, file
|
||||
, wrapQtAppsHook
|
||||
@ -16,15 +16,15 @@
|
||||
, glfw
|
||||
, openal
|
||||
, extra-cmake-modules
|
||||
, tomlplusplus
|
||||
, ghc_filesystem
|
||||
, msaClientID ? ""
|
||||
, jdks ? [ jdk jdk8 ]
|
||||
, jdks ? [ jdk17 jdk8 ]
|
||||
|
||||
# flake
|
||||
, self
|
||||
, version
|
||||
, libnbtplusplus
|
||||
, tomlplusplus
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
@ -33,13 +33,14 @@ stdenv.mkDerivation rec {
|
||||
|
||||
src = lib.cleanSource self;
|
||||
|
||||
nativeBuildInputs = [ extra-cmake-modules cmake file jdk wrapQtAppsHook ];
|
||||
nativeBuildInputs = [ extra-cmake-modules cmake file jdk17 wrapQtAppsHook ];
|
||||
buildInputs = [
|
||||
qtbase
|
||||
qtsvg
|
||||
zlib
|
||||
quazip
|
||||
ghc_filesystem
|
||||
tomlplusplus
|
||||
] ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland;
|
||||
|
||||
cmakeFlags = lib.optionals (msaClientID != "") [ "-DLauncher_MSA_CLIENT_ID=${msaClientID}" ]
|
||||
@ -52,11 +53,6 @@ stdenv.mkDerivation rec {
|
||||
ln -s ${libnbtplusplus}/* source/libraries/libnbtplusplus
|
||||
chmod -R +r+w source/libraries/libnbtplusplus
|
||||
chown -R $USER: source/libraries/libnbtplusplus
|
||||
rm -rf source/libraries/tomlplusplus
|
||||
mkdir source/libraries/tomlplusplus
|
||||
ln -s ${tomlplusplus}/* source/libraries/tomlplusplus
|
||||
chmod -R +r+w source/libraries/tomlplusplus
|
||||
chown -R $USER: source/libraries/tomlplusplus
|
||||
'';
|
||||
|
||||
postInstall =
|
||||
|
@ -19,38 +19,49 @@
|
||||
<p>Features:</p>
|
||||
<ul>
|
||||
<li>Easily install game modifications, such as Fabric, Forge and Quilt</li>
|
||||
<li>Control your Java settings</li>
|
||||
<li>Easily install and update modpacks from the Launcher</li>
|
||||
<li>Control your Java settings, and enable Mangohud or Gamemode with a toggle</li>
|
||||
<li>Manage worlds and resource packs from the launcher</li>
|
||||
<li>See logs and other details easily</li>
|
||||
<li>See logs and other details easily through a dashboard</li>
|
||||
<li>Kill Minecraft in case of a crash/freeze</li>
|
||||
<li>Isolate Minecraft instances to keep everything clean</li>
|
||||
<li>Install and update mods directly from the launcher</li>
|
||||
<li>Customize the launcher with themes, and more</li>
|
||||
<li>And cat :3</li>
|
||||
</ul>
|
||||
</description>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>The main Prism Launcher window</caption>
|
||||
<image type="source" width="976" height="764">https://prismlauncher.org/img/screenshots/LauncherDark.png</image>
|
||||
<image type="source" width="1030" height="764">https://prismlauncher.org/img/screenshots/LauncherDark.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Modpack installation</caption>
|
||||
<image type="source" width="1103" height="954">https://prismlauncher.org/img/screenshots/ModpackInstallDark.png</image>
|
||||
<image type="source" width="1126" height="850">https://prismlauncher.org/img/screenshots/ModpackInstallDark.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Modpack updating</caption>
|
||||
<image type="source" width="930" height="677">https://prismlauncher.org/img/screenshots/ModpackUpdateDark.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Mod installation</caption>
|
||||
<image type="source" width="1036" height="700">https://prismlauncher.org/img/screenshots/ModInstallDark.png</image>
|
||||
<image type="source" width="848" height="558">https://prismlauncher.org/img/screenshots/ModInstallDark.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Mod updating</caption>
|
||||
<image type="source" width="930" height="858">https://prismlauncher.org/img/screenshots/ModUpdateDark.png</image>
|
||||
<image type="source" width="860" height="748">https://prismlauncher.org/img/screenshots/ModUpdateDark.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Instance management</caption>
|
||||
<image type="source" width="1083" height="735">https://prismlauncher.org/img/screenshots/PropertiesDark.png</image>
|
||||
<image type="source" width="960" height="659">https://prismlauncher.org/img/screenshots/PropertiesDark.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Cat :)</caption>
|
||||
<image type="source" width="931" height="759">https://prismlauncher.org/img/screenshots/LauncherCatDark.png</image>
|
||||
<caption>Cat :3</caption>
|
||||
<image type="source" width="1042" height="754">https://prismlauncher.org/img/screenshots/LauncherCatDark.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Customization</caption>
|
||||
<image type="source" width="1040" height="752">https://prismlauncher.org/img/screenshots/CustomizeDark.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<releases>
|
||||
|
@ -41,6 +41,24 @@ Here are the current features of Prism Launcher.
|
||||
*-a, --profile*=PROFILE
|
||||
Use the account specified by PROFILE (only valid in combination with --launch).
|
||||
|
||||
# ENVIRONMENT
|
||||
|
||||
The behavior of the launcher can be customized by the following environment
|
||||
variables, besides other common Qt variables:
|
||||
|
||||
*QT_LOGGING_RULES*
|
||||
Specifies which logging categories are shown in the logs. One can
|
||||
enable/disable multiple categories by separating them with a semicolon (;).
|
||||
|
||||
The specific syntax, and alternatives to this setting, can be found at
|
||||
https://doc.qt.io/qt-6/qloggingcategory.html#configuring-categories.
|
||||
|
||||
*QT_MESSAGE_PATTERN*
|
||||
Specifies the format in which the console output will be shown.
|
||||
|
||||
Available options, as well as syntax, can be viewed at
|
||||
https://doc.qt.io/qt-6/qtglobal.html#qSetMessagePattern.
|
||||
|
||||
# EXIT STATUS
|
||||
|
||||
*0*
|
||||
|
@ -1,16 +1,23 @@
|
||||
#include <QTest>
|
||||
#include <QTimer>
|
||||
#include <QThread>
|
||||
|
||||
#include <tasks/ConcurrentTask.h>
|
||||
#include <tasks/MultipleOptionsTask.h>
|
||||
#include <tasks/SequentialTask.h>
|
||||
#include <tasks/Task.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
/* Does nothing. Only used for testing. */
|
||||
class BasicTask : public Task {
|
||||
Q_OBJECT
|
||||
|
||||
friend class TaskTest;
|
||||
|
||||
public:
|
||||
BasicTask(bool show_debug_log = true) : Task(nullptr, show_debug_log) {}
|
||||
|
||||
private:
|
||||
void executeTask() override
|
||||
{
|
||||
@ -30,6 +37,57 @@ class BasicTask_MultiStep : public Task {
|
||||
void executeTask() override {};
|
||||
};
|
||||
|
||||
class BigConcurrentTask : public ConcurrentTask {
|
||||
Q_OBJECT
|
||||
|
||||
void startNext() override
|
||||
{
|
||||
// This is here only to help fill the stack a bit more quickly (if there's an issue, of course :^))
|
||||
// Each tasks thus adds 1024 * 4 bytes to the stack, at the very least.
|
||||
[[maybe_unused]] volatile std::array<uint32_t, 1024> some_data_on_the_stack {};
|
||||
|
||||
ConcurrentTask::startNext();
|
||||
}
|
||||
};
|
||||
|
||||
class BigConcurrentTaskThread : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
BigConcurrentTask big_task;
|
||||
|
||||
void run() override
|
||||
{
|
||||
QTimer deadline;
|
||||
deadline.setInterval(10000);
|
||||
connect(&deadline, &QTimer::timeout, this, [this]{ passed_the_deadline = true; });
|
||||
deadline.start();
|
||||
|
||||
// NOTE: Arbitrary value that manages to trigger a problem when there is one.
|
||||
// Considering each tasks, in a problematic state, adds 1024 * 4 bytes to the stack,
|
||||
// this number is enough to fill up 16 MiB of stack, more than enough to cause a problem.
|
||||
static const unsigned s_num_tasks = 1 << 12;
|
||||
auto sub_tasks = new BasicTask::Ptr[s_num_tasks];
|
||||
|
||||
for (unsigned i = 0; i < s_num_tasks; i++) {
|
||||
sub_tasks[i] = new BasicTask(false);
|
||||
big_task.addTask(sub_tasks[i]);
|
||||
}
|
||||
|
||||
big_task.run();
|
||||
|
||||
while (!big_task.isFinished() && !passed_the_deadline)
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
emit finished();
|
||||
}
|
||||
|
||||
public:
|
||||
bool passed_the_deadline = false;
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
};
|
||||
|
||||
class TaskTest : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
@ -183,6 +241,22 @@ class TaskTest : public QObject {
|
||||
return t.isFinished();
|
||||
}, 1000), "Task didn't finish as it should.");
|
||||
}
|
||||
|
||||
void test_stackOverflowInConcurrentTask()
|
||||
{
|
||||
QEventLoop loop;
|
||||
|
||||
auto thread = new BigConcurrentTaskThread;
|
||||
|
||||
connect(thread, &BigConcurrentTaskThread::finished, &loop, &QEventLoop::quit);
|
||||
|
||||
thread->start();
|
||||
|
||||
loop.exec();
|
||||
|
||||
QVERIFY(!thread->passed_the_deadline);
|
||||
thread->deleteLater();
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(TaskTest)
|
||||
|
Reference in New Issue
Block a user