Compare commits

...

41 Commits

Author SHA1 Message Date
Sefa Eyeoglu
39bba9cbfa
Merge pull request #815 from Scrumplex/fix-funny 2023-02-03 18:44:33 +01:00
Sefa Eyeoglu
104c231740
Merge pull request #816 from Scrumplex/bump-6.3 2023-02-03 18:44:13 +01:00
Sefa Eyeoglu
bc376b22c7
chore: bump to 6.3
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2023-02-03 18:40:10 +01:00
Sefa Eyeoglu
54e03d602f
Merge pull request #813 from Scrumplex/bump-6.2 2023-02-03 17:07:48 +01:00
Sefa Eyeoglu
d8a84d2aa3
chore: bump to 6.2
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2023-02-03 10:58:22 +01:00
flow
a7ff74365d
Merge pull request #804 from flowln/improve_big_concurrent_task_test 2023-02-03 10:57:21 +01:00
flow
68c884f20d
Merge pull request #810 from flowln/error_on_bad_file_paths_as_we_should_catquake 2023-02-03 10:49:17 +01:00
flow
b7e96bdf62
Merge pull request #779 from TheLastRar/zlib-undo-rename 2023-02-02 15:17:16 +01:00
Sefa Eyeoglu
16b48866f4
Merge pull request #784 from flowln/fix_resource_folder_double_smart_ptrs 2023-02-02 15:17:16 +01:00
flow
4827f7e317
Merge pull request #781 from Ryex/patch-1 2023-02-02 15:17:16 +01:00
Sefa Eyeoglu
92f6a34624
Merge pull request #758 from flowln/fix_process_events_backstab 2023-02-02 15:17:16 +01:00
Sefa Eyeoglu
c5ce8bfb3e
Merge pull request #772 from TheLastRar/zlib-fallback-bundled-fix
Fix: zlib fallback not working
2023-02-02 15:17:15 +01:00
Sefa Eyeoglu
6429088472
Merge pull request #746 from RaptaG/patch-1 2023-02-02 15:17:15 +01:00
flow
bf9885dd7e
Merge pull request #743 from flowln/fix_mods_with_wrong_modListVersion 2023-02-02 15:17:15 +01:00
Sefa Eyeoglu
d16b6fe634
Merge pull request #732 from DioEgizio/actually-fix-openssl3-mac-failures 2023-02-02 15:17:15 +01:00
Sefa Eyeoglu
4539d58d7d
Merge pull request #731 from flowln/windows_server_trash 2023-02-02 15:17:15 +01:00
Sefa Eyeoglu
a8611a56fc
Merge pull request #726 from TheLastRar/qt6-win-darkmode
Closes https://github.com/PrismLauncher/PrismLauncher/issues/158
2023-02-02 15:17:15 +01:00
flow
c3d64aa984
Merge pull request #716 from DioEgizio/qt6.4.2-winmsvc
closes https://github.com/PrismLauncher/PrismLauncher/issues/288
2023-02-02 15:17:15 +01:00
Sefa Eyeoglu
3fc63fa196
Merge pull request #710 from byquanton/develop
Fixes https://github.com/PrismLauncher/PrismLauncher/issues/708
2023-02-02 15:17:15 +01:00
flow
4438684ce6
Merge pull request #713 from redstrate/fix-html-rendering 2023-02-02 15:17:14 +01:00
flow
699fce4482
Merge pull request #684 from Scrumplex/logging-categories
Prevents private credentials from leaking in the logs of general users
2023-02-02 15:17:14 +01:00
Sefa Eyeoglu
49060beae7
Merge pull request #680 from AshtakaOOf/flathub-screenshots 2023-02-02 15:17:14 +01:00
flow
de561e4fd3
Merge pull request #666 from getchoo/pls-scrump
scrump heard your pleads, rejoice!
2023-02-02 15:17:14 +01:00
flow
1de301752f
Merge pull request #665 from flowln/logging_categories 2023-02-02 15:17:10 +01:00
Sefa Eyeoglu
cb8c389303
Merge pull request #648 from Scrumplex/bump-6.1 2022-12-19 16:45:02 +01:00
Sefa Eyeoglu
e1e0166c95
Merge pull request #649 from Scrumplex/fix-winget-release-no9999999 2022-12-19 16:38:13 +01:00
Sefa Eyeoglu
6f8e1ccf89
chore: bump to 6.1
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2022-12-19 16:11:10 +01:00
Sefa Eyeoglu
3751856a4e
Merge pull request #647 from Scrumplex/update-flake 2022-12-19 16:10:14 +01:00
Sefa Eyeoglu
5203e72199
Merge pull request #628 from flowln/fix_multiple_resource_packs_crash
Fixes https://github.com/PrismLauncher/PrismLauncher/issues/624
2022-12-19 15:36:51 +01:00
Sefa Eyeoglu
3b1ab3c974
Merge pull request #642 from DioEgizio/dont-ship-unnecessary-tlsbackends
fix: exclude unused tls backends
2022-12-18 17:46:58 +01:00
Sefa Eyeoglu
3476bbebd9
Merge pull request #636 from flowln/fix_component_version_when_updating
Correctly set component versions in Modrinth pack updating
2022-12-17 18:20:41 +01:00
flow
45870497c6
Merge pull request #630 from leo78913/yeet-scrollbars 2022-12-17 18:20:41 +01:00
flow
bcf506488f
Merge pull request #631 from getchoo/better-msvc-flags 2022-12-17 15:19:42 +01:00
flow
040774d67b
Merge pull request #607 from flowln/dont_crash_on_zip_import
Fixes https://github.com/PrismLauncher/PrismLauncher/issues/609
2022-12-16 18:05:35 +01:00
flow
94410352f5
Merge pull request #625 from Edgars-Cirulis/develop 2022-12-15 17:29:52 +01:00
Sefa Eyeoglu
2a819f1ca0
Merge pull request #593 from Scrumplex/fix-winget-release-no999999 2022-12-15 16:36:26 +01:00
flow
25c63dd1e0
Merge pull request #605 from flowln/fix_crash_in_downloader_image
Fixes https://github.com/PrismLauncher/PrismLauncher/issues/590
2022-12-15 16:36:20 +01:00
Sefa Eyeoglu
d7223972d8
Merge pull request #612 from DioEgizio/move-cat-right
closes https://github.com/PrismLauncher/PrismLauncher/issues/611
2022-12-14 13:05:22 +01:00
flow
0a6c1238eb
Merge pull request #601 from Scrumplex/fix-translations-3 2022-12-14 13:05:22 +01:00
Sefa Eyeoglu
c11575f5f5
Merge pull request #604 from flowln/fix_crash_with_gtk2
fix: crash with GTK2 theme due to QProxyStyle in ManagedPackPage
2022-12-13 11:09:00 +01:00
flow
783761ca2e
Merge pull request #600 from TheLastRar/reset-stylesheet
Resolves https://github.com/PrismLauncher/PrismLauncher/issues/510
2022-12-13 11:09:00 +01:00
55 changed files with 433 additions and 382 deletions

View File

@ -61,7 +61,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: '' qt_arch: ''
qt_version: '6.4.0' qt_version: '6.4.2'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
@ -73,7 +73,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: 'win64_msvc2019_arm64' qt_arch: 'win64_msvc2019_arm64'
qt_version: '6.4.0' qt_version: '6.4.2'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
@ -105,6 +105,7 @@ jobs:
INSTALL_APPIMAGE_DIR: "install-appdir" INSTALL_APPIMAGE_DIR: "install-appdir"
BUILD_DIR: "build" BUILD_DIR: "build"
CCACHE_VAR: "" CCACHE_VAR: ""
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
steps: steps:
## ##

View File

@ -11,5 +11,5 @@ jobs:
with: with:
identifier: PrismLauncher.PrismLauncher identifier: PrismLauncher.PrismLauncher
version: ${{ github.event.release.tag_name }} 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 }} token: ${{ secrets.WINGET_TOKEN }}

View File

@ -28,19 +28,28 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
include(GenerateExportHeader) include(GenerateExportHeader)
if(MSVC) 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 # /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 # 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 # This implicitly selects an entrypoint specific to the subsystem selected
# qtmain/QtEntryPointLib provides the correct entrypoint (wWinMain) for gui programs # qtmain/QtEntryPointLib provides the correct entrypoint (wWinMain) for gui programs
# Additinaly LINK autodetects we use a GUI so we can omit /SUBSYSTEM # 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 # 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 # /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 # /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 # See https://github.com/ccache/ccache/issues/1040
# Note, CMake 3.25 replaces this with CMAKE_MSVC_DEBUG_INFORMATION_FORMAT # 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 version numbers ########
set(Launcher_VERSION_MAJOR 6) 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_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0") set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0")
@ -199,9 +208,15 @@ set(Launcher_BUILD_TIMESTAMP "${TODAY}")
################################ 3rd Party Libs ################################ ################################ 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) find_package(ZLIB QUIET)
endif() endif()
if(NOT ZLIB_FOUND)
set(FORCE_BUNDLED_ZLIB TRUE CACHE BOOL "")
mark_as_advanced(FORCE_BUNDLED_ZLIB)
endif()
# Find the required Qt parts # Find the required Qt parts
include(QtVersionlessBackport) include(QtVersionlessBackport)
@ -259,6 +274,8 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS)
find_package(ghc_filesystem QUIET) find_package(ghc_filesystem QUIET)
endif() endif()
include(ECMQtDeclareLoggingCategory)
####################################### Program Info ####################################### ####################################### Program Info #######################################
set(Launcher_APP_BINARY_NAME "prismlauncher" CACHE STRING "Name of the Launcher binary") 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/hoedown) # markdown parser
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
add_subdirectory(libraries/javacheck) # java compatibility checker add_subdirectory(libraries/javacheck) # java compatibility checker
if(NOT ZLIB_FOUND) if(FORCE_BUNDLED_ZLIB)
message(STATUS "Using bundled zlib") message(STATUS "Using bundled zlib")
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Suppress cmake warnings and allow INTERPROCEDURAL_OPTIMIZATION for zlib set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Suppress cmake warnings and allow INTERPROCEDURAL_OPTIMIZATION for zlib
set(SKIP_INSTALL_ALL ON) set(SKIP_INSTALL_ALL ON)
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL) 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}") set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}")
add_library(ZLIB::ZLIB ALIAS zlibstatic) add_library(ZLIB::ZLIB ALIAS zlibstatic)
set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "zlib library name") set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "zlib library name")

View File

@ -1,7 +1,7 @@
## Prism Launcher ## Prism Launcher
Prism Launcher - Minecraft 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 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

View File

@ -76,7 +76,9 @@ Config::Config()
// Assume that builds outside of Git repos are "stable" // Assume that builds outside of Git repos are "stable"
if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND") 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_REFSPEC = "refs/heads/stable";
GIT_TAG = versionString(); GIT_TAG = versionString();

View File

@ -3,11 +3,11 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1650374568, "lastModified": 1668681692,
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", "narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "b4a34015c698c7793d592d66adbab377907a2be8", "rev": "009399224d5e398d03b22badca40a37ac85412a1",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -34,11 +34,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1666057921, "lastModified": 1671417167,
"narHash": "sha256-VpQqtXdj6G7cH//SvoprjR7XT3KS7p+tCVebGK1N6tE=", "narHash": "sha256-JkHam6WQOwZN1t2C2sbp1TqMv3TVRjzrdoejqfefwrM=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "88eab1e431cabd0ed621428d8b40d425a07af39f", "rev": "bb31220cca6d044baa6dc2715b07497a2a7c4bc7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -52,24 +52,7 @@
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"libnbtplusplus": "libnbtplusplus", "libnbtplusplus": "libnbtplusplus",
"nixpkgs": "nixpkgs", "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"
} }
} }
}, },

View File

@ -5,10 +5,9 @@
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:PrismLauncher/libnbtplusplus"; 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 let
# User-friendly version number. # User-friendly version number.
version = builtins.substring 0 8 self.lastModifiedDate; version = builtins.substring 0 8 self.lastModifiedDate;
@ -23,8 +22,8 @@
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system}); pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
packagesFn = pkgs: rec { packagesFn = pkgs: rec {
prismlauncher-qt5 = pkgs.libsForQt5.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 tomlplusplus; }; prismlauncher = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; };
}; };
in in
{ {

View File

@ -62,11 +62,6 @@
#include "ui/pages/global/APIPage.h" #include "ui/pages/global/APIPage.h"
#include "ui/pages/global/CustomCommandsPage.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/SetupWizard.h"
#include "ui/setupwizard/LanguageWizardPage.h" #include "ui/setupwizard/LanguageWizardPage.h"
#include "ui/setupwizard/JavaWizardPage.h" #include "ui/setupwizard/JavaWizardPage.h"
@ -146,19 +141,12 @@ static const QLatin1String liveCheckFile("live.check");
PixmapCache* PixmapCache::s_instance = nullptr; PixmapCache* PixmapCache::s_instance = nullptr;
namespace { 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) void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{ {
const char *levels = "DWCFIS"; QString out = qFormatLogMessage(type, context, msg);
const QString format("%1 %2 %3\n"); out += QChar::LineFeed;
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);
APPLICATION->logFile->write(out.toUtf8()); APPLICATION->logFile->write(out.toUtf8());
APPLICATION->logFile->flush(); APPLICATION->logFile->flush();
@ -431,6 +419,14 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
return; return;
} }
qInstallMessageHandler(appDebugOutput); 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."; qDebug() << "<> Log initialized.";
} }
@ -1352,16 +1348,7 @@ MainWindow* Application::showMainWindow(bool minimized)
m_mainWindow = new MainWindow(); m_mainWindow = new MainWindow();
m_mainWindow->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").toByteArray())); m_mainWindow->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").toByteArray()));
m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").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) if(minimized)
{ {
m_mainWindow->showMinimized(); m_mainWindow->showMinimized();
@ -1538,7 +1525,8 @@ QString Application::getJarPath(QString jarFile)
FS::PathCombine(m_rootPath, "share/" + BuildConfig.LAUNCHER_APP_BINARY_NAME), FS::PathCombine(m_rootPath, "share/" + BuildConfig.LAUNCHER_APP_BINARY_NAME),
#endif #endif
FS::PathCombine(m_rootPath, "jars"), 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) for(QString p : potentialPaths)
{ {

View File

@ -47,8 +47,8 @@ void ApplicationMessage::parse(const QByteArray & input) {
args.clear(); args.clear();
auto parsedArgs = root.value("args").toObject(); auto parsedArgs = root.value("args").toObject();
for(auto iter = parsedArgs.begin(); iter != parsedArgs.end(); iter++) { for(auto iter = parsedArgs.constBegin(); iter != parsedArgs.constEnd(); iter++) {
args[iter.key()] = iter.value().toString(); args.insert(iter.key(), iter.value().toString());
} }
} }
@ -56,8 +56,8 @@ QByteArray ApplicationMessage::serialize() {
QJsonObject root; QJsonObject root;
root.insert("command", command); root.insert("command", command);
QJsonObject outArgs; QJsonObject outArgs;
for (auto iter = args.begin(); iter != args.end(); iter++) { for (auto iter = args.constBegin(); iter != args.constEnd(); iter++) {
outArgs[iter.key()] = iter.value(); outArgs.insert(iter.key(), iter.value());
} }
root.insert("args", outArgs); root.insert("args", outArgs);

View File

@ -1,12 +1,12 @@
#pragma once #pragma once
#include <QString> #include <QString>
#include <QMap> #include <QHash>
#include <QByteArray> #include <QByteArray>
struct ApplicationMessage { struct ApplicationMessage {
QString command; QString command;
QMap<QString, QString> args; QHash<QString, QString> args;
QByteArray serialize(); QByteArray serialize();
void parse(const QByteArray & input); void parse(const QByteArray & input);

View File

@ -551,6 +551,24 @@ set(ATLAUNCHER_SOURCES
modplatform/atlauncher/ATLShareCode.h 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 ################################ ################################ COMPILE ################################
set(LOGIC_SOURCES set(LOGIC_SOURCES
@ -905,16 +923,6 @@ SET(LAUNCHER_SOURCES
ui/instanceview/VisualGroup.h 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 qt_wrap_ui(LAUNCHER_UI
ui/setupwizard/PasteWizardPage.ui ui/setupwizard/PasteWizardPage.ui
ui/pages/global/AccountListPage.ui ui/pages/global/AccountListPage.ui
@ -1166,6 +1174,8 @@ if(INSTALL_BUNDLE STREQUAL "full")
CONFIGURATIONS Debug RelWithDebInfo "" CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR} DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime COMPONENT Runtime
PATTERN "*qopensslbackend*" EXCLUDE
PATTERN "*qcertonlybackend*" EXCLUDE
) )
install( install(
DIRECTORY "${QT_PLUGINS_DIR}/tls" DIRECTORY "${QT_PLUGINS_DIR}/tls"
@ -1175,6 +1185,8 @@ if(INSTALL_BUNDLE STREQUAL "full")
REGEX "dd\\." EXCLUDE REGEX "dd\\." EXCLUDE
REGEX "_debug\\." EXCLUDE REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE REGEX "\\.dSYM" EXCLUDE
PATTERN "*qopensslbackend*" EXCLUDE
PATTERN "*qcertonlybackend*" EXCLUDE
) )
endif() endif()
configure_file( configure_file(

View File

@ -56,6 +56,7 @@
#include <shlobj.h> #include <shlobj.h>
#include <shobjidl.h> #include <shobjidl.h>
#include <sys/utime.h> #include <sys/utime.h>
#include <versionhelpers.h>
#include <windows.h> #include <windows.h>
#include <winnls.h> #include <winnls.h>
#include <string> #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 // FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal
if (DesktopServices::isFlatpak()) if (DesktopServices::isFlatpak())
return false; return false;
#if defined Q_OS_WIN32
if (IsWindowsServer())
return false;
#endif
return QFile::moveToTrash(path, pathInTrash); return QFile::moveToTrash(path, pathInTrash);
#endif #endif
} }

View File

@ -257,6 +257,8 @@ void InstanceImportTask::extractAborted()
void InstanceImportTask::processFlame() void InstanceImportTask::processFlame()
{ {
FlameCreationTask* inst_creation_task = nullptr;
if (!m_extra_info.isEmpty()) {
auto pack_id_it = m_extra_info.constFind("pack_id"); auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd()); Q_ASSERT(pack_id_it != m_extra_info.constEnd());
auto pack_id = pack_id_it.value(); auto pack_id = pack_id_it.value();
@ -270,7 +272,11 @@ void InstanceImportTask::processFlame()
if (original_instance_id_it != m_extra_info.constEnd()) if (original_instance_id_it != m_extra_info.constEnd())
original_instance_id = original_instance_id_it.value(); 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->setName(*this);
inst_creation_task->setIcon(m_instIcon); inst_creation_task->setIcon(m_instIcon);
@ -335,6 +341,8 @@ void InstanceImportTask::processMultiMC()
void InstanceImportTask::processModrinth() void InstanceImportTask::processModrinth()
{ {
ModrinthCreationTask* inst_creation_task = nullptr;
if (!m_extra_info.isEmpty()) {
auto pack_id_it = m_extra_info.constFind("pack_id"); auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd()); Q_ASSERT(pack_id_it != m_extra_info.constEnd());
auto pack_id = pack_id_it.value(); auto pack_id = pack_id_it.value();
@ -349,7 +357,17 @@ void InstanceImportTask::processModrinth()
if (original_instance_id_it != m_extra_info.constEnd()) if (original_instance_id_it != m_extra_info.constEnd())
original_instance_id = original_instance_id_it.value(); 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->setName(*this);
inst_creation_task->setIcon(m_instIcon); inst_creation_task->setIcon(m_instIcon);

View File

@ -39,6 +39,8 @@
#include "minecraft/ParseUtils.h" #include "minecraft/ParseUtils.h"
#include <minecraft/MojangVersionFormat.h> #include <minecraft/MojangVersionFormat.h>
#include <QRegularExpression>
using namespace Json; using namespace Json;
static void readString(const QJsonObject &root, const QString &key, QString &variable) 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(); 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(); out->version = root.value("version").toString();
MojangVersionFormat::readVersionProperties(root, out.get()); MojangVersionFormat::readVersionProperties(root, out.get());

View File

@ -1,5 +1,6 @@
#include "Parsers.h" #include "Parsers.h"
#include "Json.h" #include "Json.h"
#include "Logging.h"
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonArray> #include <QJsonArray>
@ -75,9 +76,7 @@ bool getBool(QJsonValue value, bool & out) {
bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString name) { bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString name) {
qDebug() << "Parsing" << name <<":"; qDebug() << "Parsing" << name <<":";
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) { if(jsonError.error) {
@ -137,9 +136,7 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString na
bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) { bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
qDebug() << "Parsing Minecraft profile..."; qDebug() << "Parsing Minecraft profile...";
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
@ -275,9 +272,7 @@ decoded base64 "value":
bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) { bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
qDebug() << "Parsing Minecraft profile..."; qDebug() << "Parsing Minecraft profile...";
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
@ -389,9 +384,7 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) { bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) {
qDebug() << "Parsing Minecraft entitlements..."; qDebug() << "Parsing Minecraft entitlements...";
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
@ -424,9 +417,7 @@ bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output)
bool parseRolloutResponse(QByteArray & data, bool& result) { bool parseRolloutResponse(QByteArray & data, bool& result) {
qDebug() << "Parsing Rollout response..."; qDebug() << "Parsing Rollout response...";
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
@ -455,9 +446,7 @@ bool parseRolloutResponse(QByteArray & data, bool& result) {
bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) { bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
QJsonParseError jsonError; QJsonParseError jsonError;
qDebug() << "Parsing Mojang response..."; qDebug() << "Parsing Mojang response...";
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) { if(jsonError.error) {
qWarning() << "Failed to parse response from api.minecraftservices.com/launcher/login as JSON: " << jsonError.errorString(); qWarning() << "Failed to parse response from api.minecraftservices.com/launcher/login as JSON: " << jsonError.errorString();

View File

@ -3,6 +3,7 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QUuid> #include <QUuid>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
@ -41,9 +42,7 @@ void EntitlementsStep::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender()); auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater(); requestor->deleteLater();
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
// TODO: check presence of same entitlementsRequestId? // TODO: check presence of same entitlementsRequestId?
// TODO: validate JWTs? // TODO: validate JWTs?

View File

@ -2,9 +2,10 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include "Logging.h"
#include "minecraft/auth/AccountTask.h"
#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 "net/NetUtils.h" #include "net/NetUtils.h"
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) { LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {
@ -51,14 +52,10 @@ void LauncherLoginStep::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender()); auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater(); requestor->deleteLater();
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
if (error != QNetworkReply::NoError) { if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error; qWarning() << "Reply error:" << error;
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
if (Net::isApplicationError(error)) { if (Net::isApplicationError(error)) {
emit finished( emit finished(
AccountTaskState::STATE_FAILED_SOFT, AccountTaskState::STATE_FAILED_SOFT,
@ -76,9 +73,7 @@ void LauncherLoginStep::onRequestDone(
if(!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) { if(!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) {
qWarning() << "Could not parse login_with_xbox response..."; qWarning() << "Could not parse login_with_xbox response...";
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
emit finished( emit finished(
AccountTaskState::STATE_FAILED_SOFT, AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to parse the Minecraft access token response.") tr("Failed to parse the Minecraft access token response.")

View File

@ -42,6 +42,7 @@
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "Application.h" #include "Application.h"
#include "Logging.h"
using OAuth2 = Katabasis::DeviceFlow; using OAuth2 = Katabasis::DeviceFlow;
using Activity = Katabasis::Activity; using Activity = Katabasis::Activity;
@ -117,14 +118,12 @@ void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity) {
// Succeeded or did not invalidate tokens // Succeeded or did not invalidate tokens
emit hideVerificationUriAndCode(); emit hideVerificationUriAndCode();
QVariantMap extraTokens = m_oauth2->extraTokens(); QVariantMap extraTokens = m_oauth2->extraTokens();
#ifndef NDEBUG
if (!extraTokens.isEmpty()) { if (!extraTokens.isEmpty()) {
qDebug() << "Extra tokens in response:"; qCDebug(authCredentials()) << "Extra tokens in response:";
foreach (QString key, extraTokens.keys()) { 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 ")); emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
return; return;
} }

View File

@ -2,6 +2,7 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
@ -40,9 +41,7 @@ void MinecraftProfileStep::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender()); auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater(); requestor->deleteLater();
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
if (error == QNetworkReply::ContentNotFoundError) { if (error == QNetworkReply::ContentNotFoundError) {
// NOTE: Succeed even if we do not have a profile. This is a valid account state. // NOTE: Succeed even if we do not have a profile. This is a valid account state.
if(m_data->type == AccountType::Mojang) { if(m_data->type == AccountType::Mojang) {

View File

@ -2,6 +2,7 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
@ -43,9 +44,7 @@ void MinecraftProfileStepMojang::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender()); auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater(); requestor->deleteLater();
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
if (error == QNetworkReply::ContentNotFoundError) { if (error == QNetworkReply::ContentNotFoundError) {
// NOTE: Succeed even if we do not have a profile. This is a valid account state. // NOTE: Succeed even if we do not have a profile. This is a valid account state.
if(m_data->type == AccountType::Mojang) { if(m_data->type == AccountType::Mojang) {

View File

@ -4,6 +4,7 @@
#include <QJsonParseError> #include <QJsonParseError>
#include <QJsonDocument> #include <QJsonDocument>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
@ -58,9 +59,7 @@ void XboxAuthorizationStep::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender()); auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater(); requestor->deleteLater();
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
if (error != QNetworkReply::NoError) { if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error; qWarning() << "Reply error:" << error;
if (Net::isApplicationError(error)) { if (Net::isApplicationError(error)) {

View File

@ -3,7 +3,7 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QUrlQuery> #include <QUrlQuery>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
@ -56,9 +56,7 @@ void XboxProfileStep::onRequestDone(
if (error != QNetworkReply::NoError) { if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error; qWarning() << "Reply error:" << error;
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
if (Net::isApplicationError(error)) { if (Net::isApplicationError(error)) {
emit finished( emit finished(
AccountTaskState::STATE_FAILED_SOFT, AccountTaskState::STATE_FAILED_SOFT,
@ -74,9 +72,7 @@ void XboxProfileStep::onRequestDone(
return; return;
} }
#ifndef NDEBUG qCDebug(authCredentials()) << "XBox profile: " << data;
qDebug() << "XBox profile: " << data;
#endif
emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile")); emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile"));
} }

View File

@ -20,6 +20,7 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent) : QAbstractL
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged); connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this]{ m_helper_thread_task.clear(); });
} }
ResourceFolderModel::~ResourceFolderModel() ResourceFolderModel::~ResourceFolderModel()
@ -259,7 +260,7 @@ void ResourceFolderModel::resolveResource(Resource* res)
return; return;
} }
auto task = createParseTask(*res); Task::Ptr task{ createParseTask(*res) };
if (!task) if (!task)
return; return;
@ -269,13 +270,17 @@ void ResourceFolderModel::resolveResource(Resource* res)
m_active_parse_tasks.insert(ticket, task); m_active_parse_tasks.insert(ticket, task);
connect( 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( 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( 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() void ResourceFolderModel::onUpdateSucceeded()

View File

@ -10,6 +10,7 @@
#include "Resource.h" #include "Resource.h"
#include "tasks/Task.h" #include "tasks/Task.h"
#include "tasks/ConcurrentTask.h"
class QSortFilterProxyModel; 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. // Represents the relationship between a resource's internal ID and it's row position on the model.
QMap<QString, int> m_resources_index; QMap<QString, int> m_resources_index;
ConcurrentTask m_helper_thread_task;
QMap<int, Task::Ptr> m_active_parse_tasks; QMap<int, Task::Ptr> m_active_parse_tasks;
std::atomic<int> m_next_resolution_ticket = 0; std::atomic<int> m_next_resolution_ticket = 0;
}; };

View File

@ -16,7 +16,7 @@
namespace { namespace {
// NEW format // 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: // OLD format:
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc // https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
@ -73,10 +73,11 @@ ModDetails ReadMCModInfo(QByteArray contents)
version = Json::ensureString(val, "").toInt(); version = Json::ensureString(val, "").toInt();
if (version != 2) { if (version != 2) {
qCritical() << "BAD stuff happened to mod json:"; qWarning() << QString(R"(The value of 'modListVersion' is "%1" (expected "2")! The file may be corrupted.)").arg(version);
qCritical() << contents; qWarning() << "The contents of 'mcmod.info' are as follows:";
return {}; qWarning() << contents;
} }
auto arrVal = jsonDoc.object().value("modlist"); auto arrVal = jsonDoc.object().value("modlist");
if (arrVal.isUndefined()) { if (arrVal.isUndefined()) {
arrVal = jsonDoc.object().value("modList"); arrVal = jsonDoc.object().value("modList");

View File

@ -361,6 +361,8 @@ bool FlameCreationTask::createInstance()
FS::deletePath(jarmodsPath); FS::deletePath(jarmodsPath);
} }
// 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.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
instance.setName(name()); instance.setName(name());

View File

@ -202,14 +202,14 @@ bool ModrinthCreationTask::createInstance()
auto components = instance.getPackProfile(); auto components = instance.getPackProfile();
components->buildingFromScratch(); components->buildingFromScratch();
components->setComponentVersion("net.minecraft", minecraftVersion, true); components->setComponentVersion("net.minecraft", m_minecraft_version, true);
if (!fabricVersion.isEmpty()) if (!m_fabric_version.isEmpty())
components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version);
if (!quiltVersion.isEmpty()) if (!m_quilt_version.isEmpty())
components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion); components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
if (!forgeVersion.isEmpty()) if (!m_forge_version.isEmpty())
components->setComponentVersion("net.minecraftforge", forgeVersion); components->setComponentVersion("net.minecraftforge", m_forge_version);
if (m_instIcon != "default") { if (m_instIcon != "default") {
instance.setIconKey(m_instIcon); instance.setIconKey(m_instIcon);
@ -217,16 +217,27 @@ bool ModrinthCreationTask::createInstance()
instance.setIconKey("modrinth"); instance.setIconKey("modrinth");
} }
// 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.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
instance.setName(name()); instance.setName(name());
instance.saveNow(); instance.saveNow();
m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); 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) { for (auto file : m_files) {
auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path); auto file_path = FS::PathCombine(root_modpack_path, file.path);
qDebug() << "Will try to download" << file.downloads.front() << "to" << path; if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) {
auto dl = Net::Download::makeFile(file.downloads.dequeue(), 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)); dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_files_job->addNetAction(dl); m_files_job->addNetAction(dl);
@ -234,8 +245,8 @@ bool ModrinthCreationTask::createInstance()
// FIXME: This really needs to be put into a ConcurrentTask of // FIXME: This really needs to be put into a ConcurrentTask of
// MultipleOptionsTask's , once those exist :) // MultipleOptionsTask's , once those exist :)
auto param = dl.toWeakRef(); auto param = dl.toWeakRef();
connect(dl.get(), &NetAction::failed, [this, &file, path, param] { connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] {
auto ndl = Net::Download::makeFile(file.downloads.dequeue(), path); auto ndl = Net::Download::makeFile(file.downloads.dequeue(), file_path);
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_files_job->addNetAction(ndl); m_files_job->addNetAction(ndl);
if (auto shared = param.lock()) shared->succeeded(); if (auto shared = param.lock()) shared->succeeded();
@ -277,7 +288,7 @@ bool ModrinthCreationTask::createInstance()
return ended_well; 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 { try {
auto doc = Json::requireDocument(index_path); auto doc = Json::requireDocument(index_path);
@ -289,7 +300,7 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
throw JSONValidationError("Unknown game: " + game); throw JSONValidationError("Unknown game: " + game);
} }
if (set_managed_info) { if (set_internal_data) {
if (m_managed_version_id.isEmpty()) if (m_managed_version_id.isEmpty())
m_managed_version_id = Json::ensureString(obj, "versionId", {}, "Managed ID"); m_managed_version_id = Json::ensureString(obj, "versionId", {}, "Managed ID");
m_managed_name = Json::ensureString(obj, "name", {}, "Managed Name"); m_managed_name = Json::ensureString(obj, "name", {}, "Managed Name");
@ -365,21 +376,23 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
files.push_back(file); files.push_back(file);
} }
if (set_internal_data) {
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key(); QString name = it.key();
if (name == "minecraft") { if (name == "minecraft") {
minecraftVersion = Json::requireString(*it, "Minecraft version"); m_minecraft_version = Json::requireString(*it, "Minecraft version");
} else if (name == "fabric-loader") { } else if (name == "fabric-loader") {
fabricVersion = Json::requireString(*it, "Fabric Loader version"); m_fabric_version = Json::requireString(*it, "Fabric Loader version");
} else if (name == "quilt-loader") { } else if (name == "quilt-loader") {
quiltVersion = Json::requireString(*it, "Quilt Loader version"); m_quilt_version = Json::requireString(*it, "Quilt Loader version");
} else if (name == "forge") { } else if (name == "forge") {
forgeVersion = Json::requireString(*it, "Forge version"); m_forge_version = Json::requireString(*it, "Forge version");
} else { } else {
throw JSONValidationError("Unknown dependency type: " + name); throw JSONValidationError("Unknown dependency type: " + name);
} }
} }
}
} else { } else {
throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion)); throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion));
} }

View File

@ -37,12 +37,12 @@ class ModrinthCreationTask final : public InstanceCreationTask {
bool createInstance() override; bool createInstance() override;
private: 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: private:
QWidget* m_parent = nullptr; 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; QString m_managed_id, m_managed_version_id, m_managed_name;
std::vector<Modrinth::File> m_files; std::vector<Modrinth::File> m_files;

View File

@ -172,7 +172,7 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const
auto libraryObject = Json::ensureObject(library, {}, ""); auto libraryObject = Json::ensureObject(library, {}, "");
auto libraryName = Json::ensureString(libraryObject, "name", "", ""); auto libraryName = Json::ensureString(libraryObject, "name", "", "");
if (libraryName.startsWith("net.minecraftforge:forge:") && libraryName.contains('-')) if ((libraryName.startsWith("net.minecraftforge:forge:") || libraryName.startsWith("net.minecraftforge:fmlloader:")) && libraryName.contains('-'))
{ {
QString libraryVersion = libraryName.section(':', 2); QString libraryVersion = libraryName.section(':', 2);
if (!libraryVersion.startsWith("1.7.10-")) if (!libraryVersion.startsWith("1.7.10-"))

View File

@ -123,7 +123,7 @@ auto NetJob::getFailedFiles() -> QList<QString>
void NetJob::updateState() 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)") 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())));
} }

View File

@ -27,18 +27,13 @@ auto ConcurrentTask::getStepTotalProgress() const -> qint64
void ConcurrentTask::addTask(Task::Ptr task) void ConcurrentTask::addTask(Task::Ptr task)
{ {
if (!isRunning())
m_queue.append(task); m_queue.append(task);
else
qWarning() << "Tried to add a task to a running concurrent task!";
} }
void ConcurrentTask::executeTask() void ConcurrentTask::executeTask()
{ {
m_total_size = m_queue.size();
// Start the least amount of tasks needed, but at least one // 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++) { for (int i = 0; i < num_starts; i++) {
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection);
} }
@ -73,6 +68,20 @@ bool ConcurrentTask::abort()
return suceedeed; 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() void ConcurrentTask::startNext()
{ {
if (m_aborted || m_doing.count() > m_total_max_size) if (m_aborted || m_doing.count() > m_total_max_size)
@ -103,7 +112,12 @@ void ConcurrentTask::startNext()
QCoreApplication::processEvents(); 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) void ConcurrentTask::subTaskSucceeded(Task::Ptr task)
@ -145,7 +159,7 @@ void ConcurrentTask::subTaskProgress(qint64 current, qint64 total)
void ConcurrentTask::updateState() 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)") 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())));
} }

View File

@ -24,6 +24,11 @@ public:
public slots: public slots:
bool abort() override; bool abort() override;
/** Resets the internal state of the task.
* This allows the same task to be re-used.
*/
void clear();
protected protected
slots: slots:
void executeTask() override; void executeTask() override;
@ -36,6 +41,9 @@ slots:
void subTaskProgress(qint64 current, qint64 total); void subTaskProgress(qint64 current, qint64 total);
protected: 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); }; void setStepStatus(QString status) { m_step_status = status; emit stepStatus(status); };
virtual void updateState(); virtual void updateState();
@ -51,7 +59,6 @@ protected:
QHash<Task*, Task::Ptr> m_failed; QHash<Task*, Task::Ptr> m_failed;
int m_total_max_size; int m_total_max_size;
int m_total_size;
qint64 m_stepProgress = 0; qint64 m_stepProgress = 0;
qint64 m_stepTotalProgress = 100; qint64 m_stepTotalProgress = 100;

View File

@ -22,6 +22,6 @@ void MultipleOptionsTask::startNext()
void MultipleOptionsTask::updateState() void MultipleOptionsTask::updateState()
{ {
setProgress(m_done.count(), 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(m_total_size))); setStatus(tr("Attempting task %1 out of %2").arg(QString::number(m_doing.count() + m_done.count()), QString::number(totalSize())));
} }

View File

@ -17,6 +17,6 @@ void SequentialTask::startNext()
void SequentialTask::updateState() void SequentialTask::updateState()
{ {
setProgress(m_done.count(), 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(m_total_size))); setStatus(tr("Executing task %1 out of %2").arg(QString::number(m_doing.count() + m_done.count()), QString::number(totalSize())));
} }

View File

@ -1683,7 +1683,7 @@ InstanceView
background-image: url(:/backgrounds/%1); background-image: url(:/backgrounds/%1);
background-attachment: fixed; background-attachment: fixed;
background-clip: padding; background-clip: padding;
background-position: bottom left; background-position: bottom right;
background-repeat: none; background-repeat: none;
background-color:palette(base); background-color:palette(base);
})") })")

View File

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

View File

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

View File

@ -47,7 +47,6 @@ IconPickerDialog::IconPickerDialog(QWidget *parent)
contentsWidget->setUniformItemSizes(true); contentsWidget->setUniformItemSizes(true);
contentsWidget->setTextElideMode(Qt::ElideRight); contentsWidget->setTextElideMode(Qt::ElideRight);
contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
contentsWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
contentsWidget->setItemDelegate(new ListViewDelegate()); contentsWidget->setItemDelegate(new ListViewDelegate());

View File

@ -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. // 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->setUniformItemSizes(false);
contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
contentsWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
contentsWidget->setItemDelegate(new ListViewDelegate()); contentsWidget->setItemDelegate(new ListViewDelegate());

View File

@ -7,6 +7,7 @@
#include <QListView> #include <QListView>
#include <QProxyStyle> #include <QProxyStyle>
#include <QStyleFactory>
#include <HoeDown.h> #include <HoeDown.h>
@ -60,6 +61,9 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi
ui->setupUi(this); ui->setupUi(this);
// 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->versionsComboBox->setStyle(new NoBigComboBoxStyle(ui->versionsComboBox->style()));
ui->reloadButton->setVisible(false); ui->reloadButton->setVisible(false);
@ -223,17 +227,16 @@ void ModrinthManagedPackPage::parseManagedPack()
ui->versionsComboBox->blockSignals(false); ui->versionsComboBox->blockSignals(false);
for (auto version : m_pack.versions) { for (auto version : m_pack.versions) {
QString name; QString name = version.version;
if (!version.name.contains(version.version)) if (!version.name.contains(version.version))
name = QString("%1 — %2").arg(version.name, 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... // 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.............. // e.g. HexMC's 4.4.0 has versionId 4.0.0 in the modpack index..............
if (version.version == m_inst->getManagedPackVersionName()) if (version.version == m_inst->getManagedPackVersionName())
name.append(tr(" (Current)")); name = tr("%1 (Current)").arg(name);
ui->versionsComboBox->addItem(name, QVariant(version.id)); ui->versionsComboBox->addItem(name, QVariant(version.id));
} }
@ -370,12 +373,10 @@ void FlameManagedPackPage::parseManagedPack()
ui->versionsComboBox->blockSignals(false); ui->versionsComboBox->blockSignals(false);
for (auto version : m_pack.versions) { for (auto version : m_pack.versions) {
QString name; QString name = version.version;
name = version.version;
if (version.fileId == m_inst->getManagedPackVersionID().toInt()) if (version.fileId == m_inst->getManagedPackVersionID().toInt())
name.append(tr(" (Current)")); name = tr("%1 (Current)").arg(name);
ui->versionsComboBox->addItem(name, QVariant(version.fileId)); ui->versionsComboBox->addItem(name, QVariant(version.fileId));
} }

View File

@ -17,17 +17,11 @@
<property name="topMargin"> <property name="topMargin">
<number>0</number> <number>0</number>
</property> </property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
<widget class="QTextEdit" name="noteEditor"> <widget class="QTextEdit" name="noteEditor">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="tabChangesFocus"> <property name="tabChangesFocus">
<bool>true</bool> <bool>true</bool>
</property> </property>

View File

@ -48,9 +48,6 @@
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>

View File

@ -28,9 +28,6 @@
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="ModListView" name="packageView"> <widget class="ModListView" name="packageView">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="horizontalScrollBarPolicy"> <property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum> <enum>Qt::ScrollBarAlwaysOff</enum>
</property> </property>

View File

@ -428,6 +428,10 @@ void ModPage::updateUi()
text += "<hr>"; text += "<hr>";
HoeDown h; 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->setHtml(text + (current.extraData.body.isEmpty() ? current.description : h.process(current.extraData.body.toUtf8())));
ui->packDescription->flush(); ui->packDescription->flush();
} }

View File

@ -6,19 +6,14 @@
void ITheme::apply(bool) void ITheme::apply(bool)
{ {
APPLICATION->setStyleSheet(QString());
QApplication::setStyle(QStyleFactory::create(qtTheme())); QApplication::setStyle(QStyleFactory::create(qtTheme()));
if(hasColorScheme()) if (hasColorScheme()) {
{
QApplication::setPalette(colorScheme()); QApplication::setPalette(colorScheme());
} }
if (hasStyleSheet()) if (hasStyleSheet())
{
APPLICATION->setStyleSheet(appStyleSheet()); APPLICATION->setStyleSheet(appStyleSheet());
}
else
{
APPLICATION->setStyleSheet(QString());
}
QDir::setSearchPaths("theme", searchPaths()); QDir::setSearchPaths("theme", searchPaths());
} }

View File

@ -28,14 +28,6 @@
#include "Application.h" #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) ThemeManager::ThemeManager(MainWindow* mainWindow)
{ {
m_mainWindow = mainWindow; m_mainWindow = mainWindow;
@ -140,15 +132,6 @@ void ThemeManager::setApplicationTheme(const QString& name, bool initial)
auto& theme = themeIter->second; auto& theme = themeIter->second;
themeDebugLog() << "applying theme" << theme->name(); themeDebugLog() << "applying theme" << theme->name();
theme->apply(initial); 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 { } else {
themeWarningLog() << "Tried to set invalid theme:" << name; themeWarningLog() << "Tried to set invalid theme:" << name;
} }

View File

@ -31,7 +31,6 @@ ModListView::ModListView ( QWidget* parent )
setSelectionMode ( QAbstractItemView::ExtendedSelection ); setSelectionMode ( QAbstractItemView::ExtendedSelection );
setHeaderHidden ( false ); setHeaderHidden ( false );
setSelectionBehavior(QAbstractItemView::SelectRows); setSelectionBehavior(QAbstractItemView::SelectRows);
setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOn );
setHorizontalScrollBarPolicy ( Qt::ScrollBarAsNeeded ); setHorizontalScrollBarPolicy ( Qt::ScrollBarAsNeeded );
setDropIndicatorShown(true); setDropIndicatorShown(true);
setDragEnabled(true); setDragEnabled(true);

View File

@ -101,7 +101,7 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source,
auto full_entry_path = entry->getFullPath(); auto full_entry_path = entry->getFullPath();
auto source_url = source; 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; qDebug() << "Loaded resource at" << full_entry_path;
// If we flushed, don't proceed. // If we flushed, don't proceed.

View File

@ -38,6 +38,15 @@ set( katabasis_PUBLIC
include/katabasis/RequestParameter.h 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} ) add_library( Katabasis STATIC ${katabasis_PRIVATE} ${katabasis_PUBLIC} )
target_link_libraries(Katabasis Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network) target_link_libraries(Katabasis Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network)

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <QLoggingCategory>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QNetworkReply> #include <QNetworkReply>

View File

@ -19,9 +19,11 @@
#include "katabasis/PollServer.h" #include "katabasis/PollServer.h"
#include "katabasis/Globals.h" #include "katabasis/Globals.h"
#include "KatabasisLogging.h"
#include "JsonResponse.h" #include "JsonResponse.h"
namespace { namespace {
// ref: https://tools.ietf.org/html/rfc8628#section-3.2 // ref: https://tools.ietf.org/html/rfc8628#section-3.2
// Exception: Google sign-in uses "verification_url" instead of "*_uri" - we'll accept both. // Exception: Google sign-in uses "verification_url" instead of "*_uri" - we'll accept both.
bool hasMandatoryDeviceAuthParams(const QVariantMap& params) bool hasMandatoryDeviceAuthParams(const QVariantMap& params)
@ -333,9 +335,7 @@ QString DeviceFlow::refreshToken() {
} }
void DeviceFlow::setRefreshToken(const QString &v) { void DeviceFlow::setRefreshToken(const QString &v) {
#ifndef NDEBUG qCDebug(katabasisCredentials) << "new refresh token:" << v;
qDebug() << "DeviceFlow::setRefreshToken" << v << "...";
#endif
token_.refresh_token = v; token_.refresh_token = v;
} }

View File

@ -2,7 +2,7 @@
, stdenv , stdenv
, cmake , cmake
, jdk8 , jdk8
, jdk , jdk17
, zlib , zlib
, file , file
, wrapQtAppsHook , wrapQtAppsHook
@ -16,15 +16,15 @@
, glfw , glfw
, openal , openal
, extra-cmake-modules , extra-cmake-modules
, tomlplusplus
, ghc_filesystem , ghc_filesystem
, msaClientID ? "" , msaClientID ? ""
, jdks ? [ jdk jdk8 ] , jdks ? [ jdk17 jdk8 ]
# flake # flake
, self , self
, version , version
, libnbtplusplus , libnbtplusplus
, tomlplusplus
}: }:
stdenv.mkDerivation rec { stdenv.mkDerivation rec {
@ -33,13 +33,14 @@ stdenv.mkDerivation rec {
src = lib.cleanSource self; src = lib.cleanSource self;
nativeBuildInputs = [ extra-cmake-modules cmake file jdk wrapQtAppsHook ]; nativeBuildInputs = [ extra-cmake-modules cmake file jdk17 wrapQtAppsHook ];
buildInputs = [ buildInputs = [
qtbase qtbase
qtsvg qtsvg
zlib zlib
quazip quazip
ghc_filesystem ghc_filesystem
tomlplusplus
] ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland; ] ++ lib.optional (lib.versionAtLeast qtbase.version "6") qtwayland;
cmakeFlags = lib.optionals (msaClientID != "") [ "-DLauncher_MSA_CLIENT_ID=${msaClientID}" ] cmakeFlags = lib.optionals (msaClientID != "") [ "-DLauncher_MSA_CLIENT_ID=${msaClientID}" ]
@ -52,11 +53,6 @@ stdenv.mkDerivation rec {
ln -s ${libnbtplusplus}/* source/libraries/libnbtplusplus ln -s ${libnbtplusplus}/* source/libraries/libnbtplusplus
chmod -R +r+w source/libraries/libnbtplusplus chmod -R +r+w source/libraries/libnbtplusplus
chown -R $USER: 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 = postInstall =

View File

@ -19,38 +19,49 @@
<p>Features:</p> <p>Features:</p>
<ul> <ul>
<li>Easily install game modifications, such as Fabric, Forge and Quilt</li> <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>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>Kill Minecraft in case of a crash/freeze</li>
<li>Isolate Minecraft instances to keep everything clean</li> <li>Isolate Minecraft instances to keep everything clean</li>
<li>Install and update mods directly from the launcher</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> </ul>
</description> </description>
<screenshots> <screenshots>
<screenshot type="default"> <screenshot type="default">
<caption>The main Prism Launcher window</caption> <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>
<screenshot> <screenshot>
<caption>Modpack installation</caption> <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>
<screenshot> <screenshot>
<caption>Mod installation</caption> <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>
<screenshot> <screenshot>
<caption>Mod updating</caption> <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>
<screenshot> <screenshot>
<caption>Instance management</caption> <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>
<screenshot> <screenshot>
<caption>Cat :)</caption> <caption>Cat :3</caption>
<image type="source" width="931" height="759">https://prismlauncher.org/img/screenshots/LauncherCatDark.png</image> <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> </screenshot>
</screenshots> </screenshots>
<releases> <releases>

View File

@ -41,6 +41,24 @@ Here are the current features of Prism Launcher.
*-a, --profile*=PROFILE *-a, --profile*=PROFILE
Use the account specified by PROFILE (only valid in combination with --launch). 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 # EXIT STATUS
*0* *0*

View File

@ -1,16 +1,23 @@
#include <QTest> #include <QTest>
#include <QTimer>
#include <QThread>
#include <tasks/ConcurrentTask.h> #include <tasks/ConcurrentTask.h>
#include <tasks/MultipleOptionsTask.h> #include <tasks/MultipleOptionsTask.h>
#include <tasks/SequentialTask.h> #include <tasks/SequentialTask.h>
#include <tasks/Task.h> #include <tasks/Task.h>
#include <array>
/* Does nothing. Only used for testing. */ /* Does nothing. Only used for testing. */
class BasicTask : public Task { class BasicTask : public Task {
Q_OBJECT Q_OBJECT
friend class TaskTest; friend class TaskTest;
public:
BasicTask(bool show_debug_log = true) : Task(nullptr, show_debug_log) {}
private: private:
void executeTask() override void executeTask() override
{ {
@ -30,6 +37,57 @@ class BasicTask_MultiStep : public Task {
void executeTask() override {}; 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 { class TaskTest : public QObject {
Q_OBJECT Q_OBJECT
@ -183,6 +241,22 @@ class TaskTest : public QObject {
return t.isFinished(); return t.isFinished();
}, 1000), "Task didn't finish as it should."); }, 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) QTEST_GUILESS_MAIN(TaskTest)