diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5f9af1e9c..c237033183 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -410,9 +410,8 @@ jobs: if: matrix.name == 'macOS' run: | if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then - brew install openssl@3 echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem - signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n) + signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n) rm ed25519-priv.pem cat >> $GITHUB_STEP_SUMMARY << EOF ### Artifact Information :information_source: diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d70fe79bc..b677b0b7c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,7 +99,7 @@ if ((CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebI message(STATUS "Address Sanitizer enabled for Debug builds, Turn it off with -DDEBUG_ADDRESS_SANITIZER=off") if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") - # using clang with clang-cl front end + # using clang with clang-cl front end message(STATUS "Address Sanitizer available on Clang MSVC frontend") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /Oy-") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /Oy-") @@ -225,7 +225,7 @@ set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build agains # Java downloader set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT ON) -# Although we recommend enabling this, we cannot guarantee binary compatibility on +# Although we recommend enabling this, we cannot guarantee binary compatibility on # differing Linux/BSD/etc distributions. Downstream packagers should be explicitly opt-ing into this # feature if they know it will work with their distribution. if(UNIX AND NOT APPLE) @@ -438,10 +438,10 @@ elseif(UNIX) set(PLUGIN_DEST_DIR "plugins") set(BUNDLE_DEST_DIR ".") set(RESOURCES_DEST_DIR ".") - + # Apps to bundle set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${Launcher_APP_BINARY_NAME}") - + # directories to look for dependencies set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) endif() @@ -495,7 +495,7 @@ if(FORCE_BUNDLED_ZLIB) set(SKIP_INSTALL_ALL ON) add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL) - # On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not. + # 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) @@ -532,10 +532,12 @@ else() endif() if(NOT cmark_FOUND) message(STATUS "Using bundled cmark") + set(ORIGINAL_BUILD_TESTING ${BUILD_TESTING}) set(BUILD_TESTING 0) - set(BUILD_SHARED_LIBS 0) + set(BUILD_SHARED_LIBS 0) add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser add_library(cmark::cmark ALIAS cmark) + set(BUILD_TESTING ${ORIGINAL_BUILD_TESTING}) else() message(STATUS "Using system cmark") endif() diff --git a/flake.lock b/flake.lock index 6897c162d2..a82e6f65f3 100644 --- a/flake.lock +++ b/flake.lock @@ -49,11 +49,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1728018373, - "narHash": "sha256-NOiTvBbRLIOe5F6RbHaAh6++BNjsb149fGZd1T4+KBg=", + "lastModified": 1729256560, + "narHash": "sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c+cHUJwA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "bc947f541ae55e999ffdb4013441347d83b00feb", + "rev": "4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0", "type": "github" }, "original": { diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml index 71e6dd11e4..09dd8d73b8 100644 --- a/flatpak/org.prismlauncher.PrismLauncher.yml +++ b/flatpak/org.prismlauncher.PrismLauncher.yml @@ -3,9 +3,7 @@ runtime: org.kde.Platform runtime-version: 6.7 sdk: org.kde.Sdk sdk-extensions: - - org.freedesktop.Sdk.Extension.openjdk21 - org.freedesktop.Sdk.Extension.openjdk17 - - org.freedesktop.Sdk.Extension.openjdk8 command: prismlauncher finish-args: @@ -39,23 +37,11 @@ modules: env: JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17 JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac + run-tests: true sources: - type: dir path: ../ - - name: openjdk - buildsystem: simple - build-commands: - - mkdir -p /app/jdk/ - - /usr/lib/sdk/openjdk21/install.sh - - mv /app/jre /app/jdk/21 - - /usr/lib/sdk/openjdk17/install.sh - - mv /app/jre /app/jdk/17 - - /usr/lib/sdk/openjdk8/install.sh - - mv /app/jre /app/jdk/8 - cleanup: - - /jre - - name: glfw buildsystem: cmake-ninja config-opts: diff --git a/launcher/Application.cpp b/launcher/Application.cpp index b8dcc10991..cfae3a8078 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1071,6 +1071,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) bool Application::createSetupWizard() { bool javaRequired = [&]() { + if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_settings->get("AutomaticJavaDownload").toBool()) { + return false; + } bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool(); if (ignoreJavaWizard) { return false; @@ -1083,10 +1086,7 @@ bool Application::createSetupWizard() } QString currentJavaPath = settings()->get("JavaPath").toString(); QString actualPath = FS::ResolveExecutable(currentJavaPath); - if (actualPath.isNull()) { - return true; - } - return false; + return actualPath.isNull(); }(); bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() && !m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool(); diff --git a/launcher/Application.h b/launcher/Application.h index 7432c9683c..bd1cb2dea5 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -81,6 +81,12 @@ class Index; #endif #define APPLICATION (static_cast(QCoreApplication::instance())) +// Used for checking if is a test +#if defined(APPLICATION_DYN) +#undef APPLICATION_DYN +#endif +#define APPLICATION_DYN (dynamic_cast(QCoreApplication::instance())) + class Application : public QApplication { // friends for the purpose of limiting access to deprecated stuff Q_OBJECT diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index dcbfe8fde6..a70fe668af 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -608,7 +608,7 @@ set(PRISMUPDATER_SOURCES updater/prismupdater/UpdaterDialogs.cpp updater/prismupdater/GitHubRelease.h updater/prismupdater/GitHubRelease.cpp - + Json.h Json.cpp FileSystem.h @@ -625,7 +625,7 @@ set(PRISMUPDATER_SOURCES # Zip MMCZip.h MMCZip.cpp - + # Time MMCTime.h MMCTime.cpp @@ -1265,14 +1265,10 @@ include(CompilerWarnings) # Add executable add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES}) -if(BUILD_TESTING) -target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_TEST) -endif() set_project_warnings(Launcher_logic "${Launcher_MSVC_WARNINGS}" "${Launcher_CLANG_WARNINGS}" "${Launcher_GCC_WARNINGS}") -target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION) target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION) target_link_libraries(Launcher_logic @@ -1376,7 +1372,7 @@ if(Launcher_BUILD_UPDATER) add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp) target_sources("${Launcher_Name}_updater" PRIVATE updater/prismupdater/updater.exe.manifest) target_link_libraries("${Launcher_Name}_updater" prism_updater_logic) - + if(DEFINED Launcher_APP_BINARY_NAME) set_target_properties("${Launcher_Name}_updater" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_updater") endif() diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index 9c17dfc9fc..3e7b3142fb 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -38,22 +38,29 @@ void InstanceCreationTask::executeTask() // files scheduled to, and we'd better not let the user abort in the middle of it, since it'd // put the instance in an invalid state. if (shouldOverride()) { + bool deleteFailed = false; + setAbortable(false); setStatus(tr("Removing old conflicting files...")); qDebug() << "Removing old files"; - for (auto path : m_files_to_remove) { + for (const QString& path : m_files_to_remove) { if (!QFile::exists(path)) continue; + qDebug() << "Removing" << path; - if (!FS::deletePath(path)) { - qCritical() << "Couldn't remove the old conflicting files."; - emitFailed(tr("Failed to remove old conflicting files.")); - return; + + if (!QFile::remove(path)) { + qCritical() << "Could not remove" << path; + deleteFailed = true; } } + + if (deleteFailed) { + emitFailed(tr("Failed to remove old conflicting files.")); + return; + } } emitSucceeded(); - return; } diff --git a/launcher/Launcher.in b/launcher/Launcher.in index 1a23f2555f..706d7022bc 100755 --- a/launcher/Launcher.in +++ b/launcher/Launcher.in @@ -39,8 +39,16 @@ if [ "x$DEPS_LIST" = "x" ]; then # Just to be sure... chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" + ARGS=("${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}") + + if [ -f portable.txt ]; then + ARGS+=("-d" "${LAUNCHER_DIR}") + fi + + ARGS+=("$@") + # Run the launcher - exec -a "${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@" + exec -a "${ARGS[@]}" # Run the launcher in valgrind # valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@" diff --git a/launcher/MTPixmapCache.h b/launcher/MTPixmapCache.h index b6bd13045a..0ba9c5ac85 100644 --- a/launcher/MTPixmapCache.h +++ b/launcher/MTPixmapCache.h @@ -101,7 +101,7 @@ class PixmapCache final : public QObject { */ bool _markCacheMissByEviciton() { - static constexpr uint maxInt = static_cast(std::numeric_limits::max()); + static constexpr uint maxCache = static_cast(std::numeric_limits::max()) / 4; static constexpr uint step = 10240; static constexpr int oneSecond = 1000; @@ -118,8 +118,8 @@ class PixmapCache final : public QObject { if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) { // increase the cache size uint newSize = _cacheLimit() + step; - if (newSize >= maxInt) { // increase it until you overflow :D - newSize = maxInt; + if (newSize >= maxCache) { // increase it until you overflow :D + newSize = maxCache; qDebug() << m_consecutive_fast_evicitons << tr("pixmap cache misses by eviction happened too fast, doing nothing as the cache size reached it's limit"); } else { diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp index 3aa458ace5..f9d89baa2a 100644 --- a/launcher/minecraft/auth/Parsers.cpp +++ b/launcher/minecraft/auth/Parsers.cpp @@ -180,6 +180,7 @@ bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output) if (!getString(skinObj.value("url"), skinOut.url)) { continue; } + skinOut.url.replace("http://textures.minecraft.net", "https://textures.minecraft.net"); if (!getString(skinObj.value("variant"), skinOut.variant)) { continue; } @@ -221,9 +222,9 @@ namespace { // these skin URLs are for the MHF_Steve and MHF_Alex accounts (made by a Mojang employee) // they are needed because the session server doesn't return skin urls for default skins static const QString SKIN_URL_STEVE = - "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b"; + "https://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b"; static const QString SKIN_URL_ALEX = - "http://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032"; + "https://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032"; bool isDefaultModelSteve(QString uuid) { diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 5442df7fe5..9663026cd6 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -36,6 +36,7 @@ */ #include "Mod.h" +#include #include #include @@ -241,7 +242,7 @@ void Mod::finishResolvingWithDetails(ModDetails&& details) if (metadata) setMetadata(std::move(metadata)); if (!iconPath().isEmpty()) { - m_pack_image_cache_key.was_read_attempt = false; + m_packImageCacheKey.wasReadAttempt = false; } } @@ -290,45 +291,53 @@ auto Mod::issueTracker() const -> QString return details().issue_tracker; } -void Mod::setIcon(QImage new_image) const +QPixmap Mod::setIcon(QImage new_image) const { QMutexLocker locker(&m_data_lock); Q_ASSERT(!new_image.isNull()); - if (m_pack_image_cache_key.key.isValid()) - PixmapCache::remove(m_pack_image_cache_key.key); + if (m_packImageCacheKey.key.isValid()) + PixmapCache::remove(m_packImageCacheKey.key); // scale the image to avoid flooding the pixmapcache auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); - m_pack_image_cache_key.key = PixmapCache::insert(pixmap); - m_pack_image_cache_key.was_ever_used = true; - m_pack_image_cache_key.was_read_attempt = true; + m_packImageCacheKey.key = PixmapCache::insert(pixmap); + m_packImageCacheKey.wasEverUsed = true; + m_packImageCacheKey.wasReadAttempt = true; + return pixmap; } QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const { - QPixmap cached_image; - if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) { + auto pixmap_transform = [&size, &mode](QPixmap pixmap) { if (size.isNull()) - return cached_image; - return cached_image.scaled(size, mode, Qt::SmoothTransformation); + return pixmap; + return pixmap.scaled(size, mode, Qt::SmoothTransformation); + }; + + QPixmap cached_image; + if (PixmapCache::find(m_packImageCacheKey.key, &cached_image)) { + return pixmap_transform(cached_image); } // No valid image we can get - if ((!m_pack_image_cache_key.was_ever_used && m_pack_image_cache_key.was_read_attempt) || iconPath().isEmpty()) + if ((!m_packImageCacheKey.wasEverUsed && m_packImageCacheKey.wasReadAttempt) || iconPath().isEmpty()) return {}; - if (m_pack_image_cache_key.was_ever_used) { + if (m_packImageCacheKey.wasEverUsed) { qDebug() << "Mod" << name() << "Had it's icon evicted from the cache. reloading..."; PixmapCache::markCacheMissByEviciton(); } // Image got evicted from the cache or an attempt to load it has not been made. load it and retry. - m_pack_image_cache_key.was_read_attempt = true; - ModUtils::loadIconFile(*this); - return icon(size); + m_packImageCacheKey.wasReadAttempt = true; + if (ModUtils::loadIconFile(*this, &cached_image)) { + return pixmap_transform(cached_image); + } + // Image failed to load + return {}; } bool Mod::valid() const diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 9bd76c2fde..a0d9797ed7 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -82,7 +82,7 @@ class Mod : public Resource { /** Gets the icon of the mod, converted to a QPixmap for drawing, and scaled to size. */ [[nodiscard]] QPixmap icon(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const; /** Thread-safe. */ - void setIcon(QImage new_image) const; + QPixmap setIcon(QImage new_image) const; auto metadata() -> std::shared_ptr; auto metadata() const -> const std::shared_ptr; @@ -111,7 +111,7 @@ class Mod : public Resource { struct { QPixmapCache::Key key; - bool was_ever_used = false; - bool was_read_attempt = false; - } mutable m_pack_image_cache_key; + bool wasEverUsed = false; + bool wasReadAttempt = false; + } mutable m_packImageCacheKey; }; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 941e7ce583..ce3e16bce0 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -35,10 +35,9 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObje connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged); connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this] { m_helper_thread_task.clear(); }); -#ifndef LAUNCHER_TEST - // in tests the application macro doesn't work - m_helper_thread_task.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); -#endif + if (APPLICATION_DYN) { // in tests the application macro doesn't work + m_helper_thread_task.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); + } } ResourceFolderModel::~ResourceFolderModel() diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index 60257ce0cd..d456211f8e 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -647,11 +647,11 @@ bool validate(QFileInfo file) return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid(); } -bool processIconPNG(const Mod& mod, QByteArray&& raw_data) +bool processIconPNG(const Mod& mod, QByteArray&& raw_data, QPixmap* pixmap) { auto img = QImage::fromData(raw_data); if (!img.isNull()) { - mod.setIcon(img); + *pixmap = mod.setIcon(img); } else { qWarning() << "Failed to parse mod logo:" << mod.iconPath() << "from" << mod.name(); return false; @@ -659,15 +659,15 @@ bool processIconPNG(const Mod& mod, QByteArray&& raw_data) return true; } -bool loadIconFile(const Mod& mod) +bool loadIconFile(const Mod& mod, QPixmap* pixmap) { if (mod.iconPath().isEmpty()) { qWarning() << "No Iconfile set, be sure to parse the mod first"; return false; } - auto png_invalid = [&mod]() { - qWarning() << "Mod at" << mod.fileinfo().filePath() << "does not have a valid icon"; + auto png_invalid = [&mod](const QString& reason) { + qWarning() << "Mod at" << mod.fileinfo().filePath() << "does not have a valid icon:" << reason; return false; }; @@ -676,24 +676,26 @@ bool loadIconFile(const Mod& mod) QFileInfo icon_info(FS::PathCombine(mod.fileinfo().filePath(), mod.iconPath())); if (icon_info.exists() && icon_info.isFile()) { QFile icon(icon_info.filePath()); - if (!icon.open(QIODevice::ReadOnly)) - return false; + if (!icon.open(QIODevice::ReadOnly)) { + return png_invalid("failed to open file " + icon_info.filePath()); + } auto data = icon.readAll(); - bool icon_result = ModUtils::processIconPNG(mod, std::move(data)); + bool icon_result = ModUtils::processIconPNG(mod, std::move(data), pixmap); icon.close(); if (!icon_result) { - return png_invalid(); // icon invalid + return png_invalid("invalid png image"); // icon invalid } + return true; } - return false; + return png_invalid("file '" + icon_info.filePath() + "' does not exists or is not a file"); } case ResourceType::ZIPFILE: { QuaZip zip(mod.fileinfo().filePath()); if (!zip.open(QuaZip::mdUnzip)) - return false; + return png_invalid("failed to open '" + mod.fileinfo().filePath() + "' as a zip archive"); QuaZipFile file(&zip); @@ -701,28 +703,27 @@ bool loadIconFile(const Mod& mod) if (!file.open(QIODevice::ReadOnly)) { qCritical() << "Failed to open file in zip."; zip.close(); - return png_invalid(); + return png_invalid("Failed to open '" + mod.iconPath() + "' in zip archive"); } auto data = file.readAll(); - bool icon_result = ModUtils::processIconPNG(mod, std::move(data)); + bool icon_result = ModUtils::processIconPNG(mod, std::move(data), pixmap); file.close(); if (!icon_result) { - return png_invalid(); // icon png invalid + return png_invalid("invalid png image"); // icon png invalid } - } else { - return png_invalid(); // could not set icon as current file. + return true; } - return false; + return png_invalid("Failed to set '" + mod.iconPath() + + "' as current file in zip archive"); // could not set icon as current file. } case ResourceType::LITEMOD: { - return false; // can lightmods even have icons? + return png_invalid("litemods do not have icons"); // can lightmods even have icons? } default: - qWarning() << "Invalid type for mod, can not load icon."; - return false; + return png_invalid("Invalid type for mod, can not load icon."); } } diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h index a032170933..91ee6f2534 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h @@ -26,8 +26,8 @@ bool processLitemod(Mod& mod, ProcessingLevel level = ProcessingLevel::Full); /** Checks whether a file is valid as a mod or not. */ bool validate(QFileInfo file); -bool processIconPNG(const Mod& mod, QByteArray&& raw_data); -bool loadIconFile(const Mod& mod); +bool processIconPNG(const Mod& mod, QByteArray&& raw_data, QPixmap* pixmap); +bool loadIconFile(const Mod& mod, QPixmap* pixmap); } // namespace ModUtils class LocalModParseTask : public Task { diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 3442fd19c5..994dd774fe 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -301,6 +301,13 @@ bool ModrinthCreationTask::createInstance() loop.exec(); + if (!ended_well) { + for (auto m : mods) { + delete m; + } + return ended_well; + } + QEventLoop ensureMetaLoop; QDir folder = FS::PathCombine(instance.modsRoot(), ".index"); auto ensureMetadataTask = makeShared(mods, folder, ModPlatform::ResourceProvider::MODRINTH); diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index e363c911d0..3cd3958f7d 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -48,7 +48,7 @@ NetJob::NetJob(QString job_name, shared_qobject_ptr netwo : ConcurrentTask(nullptr, job_name), m_network(network) { #if defined(LAUNCHER_APPLICATION) - if (max_concurrent < 0) + if (APPLICATION_DYN && max_concurrent < 0) max_concurrent = APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt(); #endif if (max_concurrent > 0) @@ -161,7 +161,8 @@ bool NetJob::isOnline() void NetJob::emitFailed(QString reason) { #if defined(LAUNCHER_APPLICATION) - if (m_ask_retry && m_manual_try < APPLICATION->settings()->get("NumberOfManualRetries").toInt() && isOnline()) { + + if (APPLICATION_DYN && m_ask_retry && m_manual_try < APPLICATION->settings()->get("NumberOfManualRetries").toInt() && isOnline()) { m_manual_try++; auto response = CustomMessageBox::selectable(nullptr, "Confirm retry", "The tasks failed.\n" diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index 799b5b3329..4e38b4187a 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -78,7 +78,7 @@ int MSALoginDialog::exec() connect(m_authflow_task.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed); connect(m_authflow_task.get(), &Task::succeeded, this, &QDialog::accept); connect(m_authflow_task.get(), &Task::aborted, this, &MSALoginDialog::reject); - connect(m_authflow_task.get(), &Task::status, this, &MSALoginDialog::onTaskStatus); + connect(m_authflow_task.get(), &Task::status, this, &MSALoginDialog::onAuthFlowStatus); connect(m_authflow_task.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser); connect(m_authflow_task.get(), &AuthFlow::authorizeWithBrowserWithExtra, this, &MSALoginDialog::authorizeWithBrowserWithExtra); connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, m_authflow_task.get(), &Task::abort); @@ -87,7 +87,7 @@ int MSALoginDialog::exec() connect(m_devicecode_task.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed); connect(m_devicecode_task.get(), &Task::succeeded, this, &QDialog::accept); connect(m_devicecode_task.get(), &Task::aborted, this, &MSALoginDialog::reject); - connect(m_devicecode_task.get(), &Task::status, this, &MSALoginDialog::onTaskStatus); + connect(m_devicecode_task.get(), &Task::status, this, &MSALoginDialog::onDeviceFlowStatus); connect(m_devicecode_task.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser); connect(m_devicecode_task.get(), &AuthFlow::authorizeWithBrowserWithExtra, this, &MSALoginDialog::authorizeWithBrowserWithExtra); connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, m_devicecode_task.get(), &Task::abort); @@ -132,7 +132,7 @@ void MSALoginDialog::onTaskFailed(QString reason) void MSALoginDialog::authorizeWithBrowser(const QUrl& url) { - ui->stackedWidget->setCurrentIndex(1); + ui->stackedWidget2->setCurrentIndex(1); ui->loginButton->setToolTip(QString("
%1
").arg(url.toString())); m_url = url; } @@ -152,12 +152,18 @@ void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, in } } -void MSALoginDialog::onTaskStatus(QString status) +void MSALoginDialog::onDeviceFlowStatus(QString status) { ui->stackedWidget->setCurrentIndex(0); ui->status->setText(status); } +void MSALoginDialog::onAuthFlowStatus(QString status) +{ + ui->stackedWidget2->setCurrentIndex(0); + ui->status2->setText(status); +} + // Public interface MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent) { diff --git a/launcher/ui/dialogs/MSALoginDialog.h b/launcher/ui/dialogs/MSALoginDialog.h index 70f480ca97..375ccc57a2 100644 --- a/launcher/ui/dialogs/MSALoginDialog.h +++ b/launcher/ui/dialogs/MSALoginDialog.h @@ -40,7 +40,8 @@ class MSALoginDialog : public QDialog { protected slots: void onTaskFailed(QString reason); - void onTaskStatus(QString status); + void onDeviceFlowStatus(QString status); + void onAuthFlowStatus(QString status); void authorizeWithBrowser(const QUrl& url); void authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn); diff --git a/launcher/ui/dialogs/MSALoginDialog.ui b/launcher/ui/dialogs/MSALoginDialog.ui index c6821782f1..69cd2e1ab9 100644 --- a/launcher/ui/dialogs/MSALoginDialog.ui +++ b/launcher/ui/dialogs/MSALoginDialog.ui @@ -7,7 +7,7 @@ 0 0 440 - 430 + 447 @@ -21,12 +21,12 @@ - + 1 - - + + @@ -41,7 +41,7 @@ - + 16 @@ -61,7 +61,7 @@ - + Status @@ -74,7 +74,7 @@ - + Qt::Vertical @@ -88,8 +88,8 @@ - - + + @@ -136,51 +136,125 @@ + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + + 16 + + + + Or + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + + + 1 + + + - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - - - - 16 - - - - Or - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 16 + 75 + true + + + + Please wait... + + + Qt::AlignCenter + + + true + + + + + + + Status + + + Qt::AlignCenter + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 0ca3a1bd93..9897687e30 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -90,6 +90,9 @@ void ProgressDialog::on_skipButton_clicked(bool checked) ProgressDialog::~ProgressDialog() { + for (auto conn : this->m_taskConnections) { + disconnect(conn); + } delete ui; } @@ -140,15 +143,15 @@ int ProgressDialog::execWithTask(Task* task) } // Connect signals. - connect(task, &Task::started, this, &ProgressDialog::onTaskStarted); - connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed); - connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded); - connect(task, &Task::status, this, &ProgressDialog::changeStatus); - connect(task, &Task::details, this, &ProgressDialog::changeStatus); - connect(task, &Task::stepProgress, this, &ProgressDialog::changeStepProgress); - connect(task, &Task::progress, this, &ProgressDialog::changeProgress); - connect(task, &Task::aborted, this, &ProgressDialog::hide); - connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled); + this->m_taskConnections.push_back(connect(task, &Task::started, this, &ProgressDialog::onTaskStarted)); + this->m_taskConnections.push_back(connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed)); + this->m_taskConnections.push_back(connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded)); + this->m_taskConnections.push_back(connect(task, &Task::status, this, &ProgressDialog::changeStatus)); + this->m_taskConnections.push_back(connect(task, &Task::details, this, &ProgressDialog::changeStatus)); + this->m_taskConnections.push_back(connect(task, &Task::stepProgress, this, &ProgressDialog::changeStepProgress)); + this->m_taskConnections.push_back(connect(task, &Task::progress, this, &ProgressDialog::changeProgress)); + this->m_taskConnections.push_back(connect(task, &Task::aborted, this, &ProgressDialog::hide)); + this->m_taskConnections.push_back(connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled)); m_is_multi_step = task->isMultiStep(); ui->taskProgressScrollArea->setHidden(!m_is_multi_step); diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index 15eadf4e72..4a696a49d4 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -93,6 +93,8 @@ class ProgressDialog : public QDialog { Ui::ProgressDialog* ui; Task* m_task; + + QList m_taskConnections; bool m_is_multi_step = false; QHash taskProgress; diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 6c85ffa964..65840eb08c 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -139,6 +139,9 @@ void SkinManageDialog::on_fileBtn_clicked() { auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString(); QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter); + if (raw_path.isNull()) { + return; + } auto message = m_list.installSkin(raw_path, {}); if (!message.isEmpty()) { CustomMessageBox::selectable(this, tr("Selected file is not a valid skin"), message, QMessageBox::Critical)->show(); diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index c8eb91570b..50a170fffc 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -31,9 +31,9 @@ QHash ResourceModel::s_running_models; ResourceModel::ResourceModel(ResourceAPI* api) : QAbstractListModel(), m_api(api) { s_running_models.insert(this, true); -#ifndef LAUNCHER_TEST - m_current_info_job.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); -#endif + if (APPLICATION_DYN) { + m_current_info_job.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + } } ResourceModel::~ResourceModel() @@ -60,11 +60,15 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant return pack->description; } case Qt::DecorationRole: { - if (auto icon_or_none = const_cast(this)->getIcon(const_cast(index), pack->logoUrl); - icon_or_none.has_value()) - return icon_or_none.value(); - - return APPLICATION->getThemedIcon("screenshot-placeholder"); + if (APPLICATION_DYN) { + if (auto icon_or_none = const_cast(this)->getIcon(const_cast(index), pack->logoUrl); + icon_or_none.has_value()) + return icon_or_none.value(); + + return APPLICATION->getThemedIcon("screenshot-placeholder"); + } else { + return {}; + } } case Qt::SizeHintRole: return QSize(0, 58); diff --git a/launcher/ui/widgets/CustomCommands.ui b/launcher/ui/widgets/CustomCommands.ui index 4a39ff7f75..b485c293ef 100644 --- a/launcher/ui/widgets/CustomCommands.ui +++ b/launcher/ui/widgets/CustomCommands.ui @@ -38,19 +38,6 @@ false - - - - P&ost-exit command: - - - postExitCmdTextBox - - - - - - @@ -61,8 +48,8 @@ - - + + @@ -77,6 +64,19 @@ + + + + P&ost-exit command: + + + postExitCmdTextBox + + + + + + diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index f75acf1de8..7fc383ff34 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -87,6 +87,8 @@ stdenv.mkDerivation { (lib.cmakeFeature "CMAKE_INSTALL_PREFIX" "${placeholder "out"}/Applications/") ]; + doCheck = true; + dontWrapQtApps = true; meta = { diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index ee2e336b15..e693d757a9 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -57,7 +57,7 @@ Section "Visual Studio Runtime" Pop $0 ${If} $0 == "OK" DetailPrint "Download successful" - ExecWait "$INSTDIR\vc_redist\$vc_redist_exe /install /passive /norestart\" + ExecWait "$INSTDIR\vc_redist\$vc_redist_exe /install /passive /norestart" ${Else} DetailPrint "Download failed with error $0" ${EndIf}