Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into metadata2

This commit is contained in:
Trial97 2024-08-23 08:34:15 +03:00
commit 2a86ceb5d6
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
159 changed files with 4100 additions and 1473 deletions

View File

@ -25,7 +25,7 @@ jobs:
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs - name: Create backport PRs
uses: korthout/backport-action@v3.0.2 uses: korthout/backport-action@v3.1.0
with: with:
# Config README: https://github.com/korthout/backport-action#backport-action # Config README: https://github.com/korthout/backport-action#backport-action
pull_description: |- pull_description: |-

View File

@ -79,7 +79,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: "" qt_arch: ""
qt_version: "6.7.1" qt_version: "6.7.2"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: windows-2022 - os: windows-2022
@ -90,7 +90,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.7.1" qt_version: "6.7.2"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: macos-12 - os: macos-12
@ -99,7 +99,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: mac qt_host: mac
qt_arch: "" qt_arch: ""
qt_version: "6.7.1" qt_version: "6.7.2"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: macos-12 - os: macos-12
@ -160,7 +160,7 @@ jobs:
- name: Setup ccache - name: Setup ccache
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
uses: hendrikmuhs/ccache-action@v1.2.13 uses: hendrikmuhs/ccache-action@v1.2.14
with: with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
@ -266,23 +266,23 @@ jobs:
- name: Configure CMake (macOS) - name: Configure CMake (macOS)
if: runner.os == 'macOS' && matrix.qt_ver == 6 if: runner.os == 'macOS' && matrix.qt_ver == 6
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja
- name: Configure CMake (macOS-Legacy) - name: Configure CMake (macOS-Legacy)
if: runner.os == 'macOS' && matrix.qt_ver == 5 if: runner.os == 'macOS' && matrix.qt_ver == 5
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja
- name: Configure CMake (Windows MinGW-w64) - name: Configure CMake (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0} shell: msys2 {0}
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja
- name: Configure CMake (Windows MSVC) - name: Configure CMake (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == '' if: runner.os == 'Windows' && matrix.msystem == ''
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }}
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
if ("${{ env.CCACHE_VAR }}") if ("${{ env.CCACHE_VAR }}")
{ {
@ -297,7 +297,7 @@ jobs:
- name: Configure CMake (Linux) - name: Configure CMake (Linux)
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -G Ninja
## ##
# BUILD # BUILD

View File

@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b # v27 - uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b # v27
- uses: DeterminateSystems/update-flake-lock@v22 - uses: DeterminateSystems/update-flake-lock@v23
with: with:
commit-msg: "chore(nix): update lockfile" commit-msg: "chore(nix): update lockfile"
pr-title: "chore(nix): update lockfile" pr-title: "chore(nix): update lockfile"

View File

@ -219,6 +219,19 @@ set(Launcher_SUBREDDIT_URL "https://prismlauncher.org/reddit" CACHE STRING "URL
set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules") set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against") set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against")
# Java downloader
set(ENABLE_JAVA_DOWNLOADER_DEFAULT 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)
set(ENABLE_JAVA_DOWNLOADER_DEFAULT OFF)
endif()
# Java downloader
option(ENABLE_JAVA_DOWNLOADER "Build the java downloader feature" ${ENABLE_JAVA_DOWNLOADER_DEFAULT})
# Native libraries # Native libraries
if(UNIX AND APPLE) if(UNIX AND APPLE)
set(Launcher_GLFW_LIBRARY_NAME "libglfw.dylib" CACHE STRING "Name of native glfw library") set(Launcher_GLFW_LIBRARY_NAME "libglfw.dylib" CACHE STRING "Name of native glfw library")

View File

@ -81,6 +81,9 @@ Config::Config()
UPDATER_ENABLED = true; UPDATER_ENABLED = true;
} }
#cmakedefine01 ENABLE_JAVA_DOWNLOADER
JAVA_DOWNLOADER_ENABLED = ENABLE_JAVA_DOWNLOADER;
GIT_COMMIT = "@Launcher_GIT_COMMIT@"; GIT_COMMIT = "@Launcher_GIT_COMMIT@";
GIT_TAG = "@Launcher_GIT_TAG@"; GIT_TAG = "@Launcher_GIT_TAG@";
GIT_REFSPEC = "@Launcher_GIT_REFSPEC@"; GIT_REFSPEC = "@Launcher_GIT_REFSPEC@";

View File

@ -67,6 +67,7 @@ class Config {
QString VERSION_CHANNEL; QString VERSION_CHANNEL;
bool UPDATER_ENABLED = false; bool UPDATER_ENABLED = false;
bool JAVA_DOWNLOADER_ENABLED = false;
/// A short string identifying this build's platform or distribution. /// A short string identifying this build's platform or distribution.
QString BUILD_PLATFORM; QString BUILD_PLATFORM;

18
flake.lock generated
View File

@ -23,11 +23,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1719994518, "lastModified": 1722555600,
"narHash": "sha256-pQMhCCHyQGRzdfAkdJ4cIWiw+JNuWsTX7f0ZYSyz0VY=", "narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "9227223f6d922fee3c7b190b2cc238a99527bbb7", "rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -75,11 +75,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1720768451, "lastModified": 1723637854,
"narHash": "sha256-EYekUHJE2gxeo2pM/zM9Wlqw1Uw2XTJXOSAO79ksc4Y=", "narHash": "sha256-med8+5DSWa2UnOqtdICndjDAEjxr5D7zaIiK4pn0Q7c=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "7e7c39ea35c5cdd002cd4588b03a3fb9ece6fad9", "rev": "c3aa7b8938b17aebd2deecf7be0636000d62a2b9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -103,11 +103,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1720524665, "lastModified": 1723803910,
"narHash": "sha256-ni/87oHPZm6Gv0ECYxr1f6uxB0UKBWJ6HvS7lwLU6oY=", "narHash": "sha256-yezvUuFiEnCFbGuwj/bQcqg7RykIEqudOy/RBrId0pc=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "8d6a17d0cdf411c55f12602624df6368ad86fac1", "rev": "bfef0ada09e2c8ac55bbcd0831bd0c9d42e651ba",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -44,10 +44,10 @@
#include "BuildConfig.h" #include "BuildConfig.h"
#include "DataMigrationTask.h" #include "DataMigrationTask.h"
#include "java/JavaInstallList.h"
#include "net/PasteUpload.h" #include "net/PasteUpload.h"
#include "pathmatcher/MultiMatcher.h" #include "pathmatcher/MultiMatcher.h"
#include "pathmatcher/SimplePrefixMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h"
#include "settings/INIFile.h"
#include "tools/GenericProfiler.h" #include "tools/GenericProfiler.h"
#include "ui/InstanceWindow.h" #include "ui/InstanceWindow.h"
#include "ui/MainWindow.h" #include "ui/MainWindow.h"
@ -106,7 +106,7 @@
#include "icons/IconList.h" #include "icons/IconList.h"
#include "net/HttpMetaCache.h" #include "net/HttpMetaCache.h"
#include "java/JavaUtils.h" #include "java/JavaInstallList.h"
#include "updater/ExternalUpdater.h" #include "updater/ExternalUpdater.h"
@ -126,6 +126,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <sys.h> #include <sys.h>
#include "SysInfo.h"
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
#include <dlfcn.h> #include <dlfcn.h>
@ -151,6 +152,7 @@
#endif #endif
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
#include <windows.h>
#include "WindowsConsole.h" #include "WindowsConsole.h"
#endif #endif
@ -236,6 +238,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{ { { "d", "dir" }, "Use a custom path as application root (use '.' for current directory)", "directory" }, { { { "d", "dir" }, "Use a custom path as application root (use '.' for current directory)", "directory" },
{ { "l", "launch" }, "Launch the specified instance (by instance ID)", "instance" }, { { "l", "launch" }, "Launch the specified instance (by instance ID)", "instance" },
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" }, { { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
{ { "w", "world" }, "Join the specified world on launch (only valid in combination with --launch)", "world" },
{ { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" }, { { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" }, { "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
{ { "I", "import" }, "Import instance or resource from specified local path or URL", "url" }, { { "I", "import" }, "Import instance or resource from specified local path or URL", "url" },
@ -250,6 +253,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_instanceIdToLaunch = parser.value("launch"); m_instanceIdToLaunch = parser.value("launch");
m_serverToJoin = parser.value("server"); m_serverToJoin = parser.value("server");
m_worldToJoin = parser.value("world");
m_profileToUse = parser.value("profile"); m_profileToUse = parser.value("profile");
m_liveCheck = parser.isSet("alive"); m_liveCheck = parser.isSet("alive");
@ -265,7 +269,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
} }
// error if --launch is missing with --server or --profile // error if --launch is missing with --server or --profile
if ((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) { if (((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty()) || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) {
std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl; std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl;
m_status = Application::Failed; m_status = Application::Failed;
return; return;
@ -385,6 +389,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
if (!m_serverToJoin.isEmpty()) { if (!m_serverToJoin.isEmpty()) {
launch.args["server"] = m_serverToJoin; launch.args["server"] = m_serverToJoin;
} else if (!m_worldToJoin.isEmpty()) {
launch.args["world"] = m_worldToJoin;
} }
if (!m_profileToUse.isEmpty()) { if (!m_profileToUse.isEmpty()) {
launch.args["profile"] = m_profileToUse; launch.args["profile"] = m_profileToUse;
@ -523,6 +529,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
} }
if (!m_serverToJoin.isEmpty()) { if (!m_serverToJoin.isEmpty()) {
qDebug() << "Address of server to join :" << m_serverToJoin; qDebug() << "Address of server to join :" << m_serverToJoin;
} else if (!m_worldToJoin.isEmpty()) {
qDebug() << "Name of the world to join :" << m_worldToJoin;
} }
qDebug() << "<> Paths set."; qDebug() << "<> Paths set.";
} }
@ -596,6 +604,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
m_settings->registerSetting("DownloadsDirWatchRecursive", false); m_settings->registerSetting("DownloadsDirWatchRecursive", false);
m_settings->registerSetting("SkinsDir", "skins"); m_settings->registerSetting("SkinsDir", "skins");
m_settings->registerSetting("JavaDir", "java");
// Editors // Editors
m_settings->registerSetting("JsonEditor", QString()); m_settings->registerSetting("JsonEditor", QString());
@ -624,7 +633,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// Memory // Memory
m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512); m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512);
m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, suitableMaxMem()); m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::suitableMaxMem());
m_settings->registerSetting("PermGen", 128); m_settings->registerSetting("PermGen", 128);
// Java Settings // Java Settings
@ -638,6 +647,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("JvmArgs", ""); m_settings->registerSetting("JvmArgs", "");
m_settings->registerSetting("IgnoreJavaCompatibility", false); m_settings->registerSetting("IgnoreJavaCompatibility", false);
m_settings->registerSetting("IgnoreJavaWizard", false); m_settings->registerSetting("IgnoreJavaWizard", false);
auto defaultEnableAutoJava = m_settings->get("JavaPath").toString().isEmpty();
m_settings->registerSetting("AutomaticJavaSwitch", defaultEnableAutoJava);
m_settings->registerSetting("AutomaticJavaDownload", defaultEnableAutoJava);
// Legacy settings // Legacy settings
m_settings->registerSetting("OnlineFixes", false); m_settings->registerSetting("OnlineFixes", false);
@ -867,6 +879,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_metacache->addBase("ModrinthModpacks", QDir("cache/ModrinthModpacks").absolutePath()); m_metacache->addBase("ModrinthModpacks", QDir("cache/ModrinthModpacks").absolutePath());
m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("meta", QDir("meta").absolutePath()); m_metacache->addBase("meta", QDir("meta").absolutePath());
m_metacache->addBase("java", QDir("cache/java").absolutePath());
m_metacache->Load(); m_metacache->Load();
qDebug() << "<> Cache initialized."; qDebug() << "<> Cache initialized.";
} }
@ -1157,14 +1170,17 @@ void Application::performMainStartupAction()
if (!m_instanceIdToLaunch.isEmpty()) { if (!m_instanceIdToLaunch.isEmpty()) {
auto inst = instances()->getInstanceById(m_instanceIdToLaunch); auto inst = instances()->getInstanceById(m_instanceIdToLaunch);
if (inst) { if (inst) {
MinecraftServerTargetPtr serverToJoin = nullptr; MinecraftTarget::Ptr targetToJoin = nullptr;
MinecraftAccountPtr accountToUse = nullptr; MinecraftAccountPtr accountToUse = nullptr;
qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching"; qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching";
if (!m_serverToJoin.isEmpty()) { if (!m_serverToJoin.isEmpty()) {
// FIXME: validate the server string // FIXME: validate the server string
serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(m_serverToJoin))); targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(m_serverToJoin, false)));
qDebug() << " Launching with server" << m_serverToJoin; qDebug() << " Launching with server" << m_serverToJoin;
} else if (!m_worldToJoin.isEmpty()) {
targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(m_worldToJoin, true)));
qDebug() << " Launching with world" << m_worldToJoin;
} }
if (!m_profileToUse.isEmpty()) { if (!m_profileToUse.isEmpty()) {
@ -1175,7 +1191,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse; qDebug() << " Launching with account" << m_profileToUse;
} }
launch(inst, true, false, serverToJoin, accountToUse); launch(inst, true, false, targetToJoin, accountToUse);
return; return;
} }
} }
@ -1265,6 +1281,7 @@ void Application::messageReceived(const QByteArray& message)
} else if (command == "launch") { } else if (command == "launch") {
QString id = received.args["id"]; QString id = received.args["id"];
QString server = received.args["server"]; QString server = received.args["server"];
QString world = received.args["world"];
QString profile = received.args["profile"]; QString profile = received.args["profile"];
InstancePtr instance; InstancePtr instance;
@ -1279,11 +1296,12 @@ void Application::messageReceived(const QByteArray& message)
return; return;
} }
MinecraftServerTargetPtr serverObject = nullptr; MinecraftTarget::Ptr serverObject = nullptr;
if (!server.isEmpty()) { if (!server.isEmpty()) {
serverObject = std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(server)); serverObject = std::make_shared<MinecraftTarget>(MinecraftTarget::parse(server, false));
} else if (!world.isEmpty()) {
serverObject = std::make_shared<MinecraftTarget>(MinecraftTarget::parse(world, true));
} }
MinecraftAccountPtr accountObject; MinecraftAccountPtr accountObject;
if (!profile.isEmpty()) { if (!profile.isEmpty()) {
accountObject = accounts()->getAccountByProfileName(profile); accountObject = accounts()->getAccountByProfileName(profile);
@ -1332,11 +1350,7 @@ bool Application::openJsonEditor(const QString& filename)
} }
} }
bool Application::launch(InstancePtr instance, bool Application::launch(InstancePtr instance, bool online, bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse)
bool online,
bool demo,
MinecraftServerTargetPtr serverToJoin,
MinecraftAccountPtr accountToUse)
{ {
if (m_updateRunning) { if (m_updateRunning) {
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed."; qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
@ -1354,7 +1368,7 @@ bool Application::launch(InstancePtr instance,
controller->setOnline(online); controller->setOnline(online);
controller->setDemo(demo); controller->setDemo(demo);
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get()); controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
controller->setServerToJoin(serverToJoin); controller->setTargetToJoin(targetToJoin);
controller->setAccountToUse(accountToUse); controller->setAccountToUse(accountToUse);
if (window) { if (window) {
controller->setParentWidget(window); controller->setParentWidget(window);
@ -1733,20 +1747,6 @@ QString Application::getUserAgentUncached()
return BuildConfig.USER_AGENT_UNCACHED; return BuildConfig.USER_AGENT_UNCACHED;
} }
int Application::suitableMaxMem()
{
float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte;
int maxMemoryAlloc;
// If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB
if (totalRAM < (4096 * 1.5))
maxMemoryAlloc = (int)(totalRAM / 1.5);
else
maxMemoryAlloc = 4096;
return maxMemoryAlloc;
}
bool Application::handleDataMigration(const QString& currentData, bool Application::handleDataMigration(const QString& currentData,
const QString& oldData, const QString& oldData,
const QString& name, const QString& name,
@ -1853,3 +1853,7 @@ QUrl Application::normalizeImportUrl(QString const& url)
return QUrl::fromUserInput(url); return QUrl::fromUserInput(url);
} }
} }
const QString Application::javaPath()
{
return m_settings->get("JavaDir").toString();
}

View File

@ -47,7 +47,7 @@
#include <BaseInstance.h> #include <BaseInstance.h>
#include "minecraft/launch/MinecraftServerTarget.h" #include "minecraft/launch/MinecraftTarget.h"
class LaunchController; class LaunchController;
class LocalPeer; class LocalPeer;
@ -161,6 +161,9 @@ class Application : public QApplication {
/// the data path the application is using /// the data path the application is using
const QString& dataRoot() { return m_dataPath; } const QString& dataRoot() { return m_dataPath; }
/// the java installed path the application is using
const QString javaPath();
bool isPortable() { return m_portable; } bool isPortable() { return m_portable; }
const Capabilities capabilities() { return m_capabilities; } const Capabilities capabilities() { return m_capabilities; }
@ -179,8 +182,6 @@ class Application : public QApplication {
void ShowGlobalSettings(class QWidget* parent, QString open_page = QString()); void ShowGlobalSettings(class QWidget* parent, QString open_page = QString());
int suitableMaxMem();
bool updaterEnabled(); bool updaterEnabled();
QString updaterBinaryName(); QString updaterBinaryName();
@ -202,7 +203,7 @@ class Application : public QApplication {
bool launch(InstancePtr instance, bool launch(InstancePtr instance,
bool online = true, bool online = true,
bool demo = false, bool demo = false,
MinecraftServerTargetPtr serverToJoin = nullptr, MinecraftTarget::Ptr targetToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr); MinecraftAccountPtr accountToUse = nullptr);
bool kill(InstancePtr instance); bool kill(InstancePtr instance);
void closeCurrentWindow(); void closeCurrentWindow();
@ -290,6 +291,7 @@ class Application : public QApplication {
QString m_detectedOpenALPath; QString m_detectedOpenALPath;
QString m_instanceIdToLaunch; QString m_instanceIdToLaunch;
QString m_serverToJoin; QString m_serverToJoin;
QString m_worldToJoin;
QString m_profileToUse; QString m_profileToUse;
bool m_liveCheck = false; bool m_liveCheck = false;
QList<QUrl> m_urlsToImport; QList<QUrl> m_urlsToImport;

View File

@ -56,7 +56,7 @@
#include "net/Mode.h" #include "net/Mode.h"
#include "RuntimeContext.h" #include "RuntimeContext.h"
#include "minecraft/launch/MinecraftServerTarget.h" #include "minecraft/launch/MinecraftTarget.h"
class QDir; class QDir;
class Task; class Task;
@ -184,7 +184,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
virtual Task::Ptr createUpdateTask(Net::Mode mode) = 0; virtual Task::Ptr createUpdateTask(Net::Mode mode) = 0;
/// returns a valid launcher (task container) /// returns a valid launcher (task container)
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) = 0; virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) = 0;
/// returns the current launch task (if any) /// returns the current launch task (if any)
shared_qobject_ptr<LaunchTask> getLaunchTask(); shared_qobject_ptr<LaunchTask> getLaunchTask();
@ -256,7 +256,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
/** /**
* 'print' a verbose description of the instance into a QStringList * 'print' a verbose description of the instance into a QStringList
*/ */
virtual QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) = 0; virtual QStringList verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) = 0;
Status currentStatus() const; Status currentStatus() const;

View File

@ -78,6 +78,14 @@ QVariant BaseVersionList::data(const QModelIndex& index, int role) const
case TypeRole: case TypeRole:
return version->typeString(); return version->typeString();
case JavaMajorRole: {
auto major = version->name();
if (major.startsWith("java")) {
major = "Java " + major.mid(4);
}
return major;
}
default: default:
return QVariant(); return QVariant();
} }
@ -110,6 +118,8 @@ QHash<int, QByteArray> BaseVersionList::roleNames() const
roles.insert(TypeRole, "type"); roles.insert(TypeRole, "type");
roles.insert(BranchRole, "branch"); roles.insert(BranchRole, "branch");
roles.insert(PathRole, "path"); roles.insert(PathRole, "path");
roles.insert(ArchitectureRole, "architecture"); roles.insert(JavaNameRole, "javaName");
roles.insert(CPUArchitectureRole, "architecture");
roles.insert(JavaMajorRole, "javaMajor");
return roles; return roles;
} }

View File

@ -48,7 +48,9 @@ class BaseVersionList : public QAbstractListModel {
TypeRole, TypeRole,
BranchRole, BranchRole,
PathRole, PathRole,
ArchitectureRole, JavaNameRole,
JavaMajorRole,
CPUArchitectureRole,
SortRole SortRole
}; };
using RoleList = QList<int>; using RoleList = QList<int>;

View File

@ -24,6 +24,8 @@ set(CORE_SOURCES
NullInstance.h NullInstance.h
MMCZip.h MMCZip.h
MMCZip.cpp MMCZip.cpp
Untar.h
Untar.cpp
StringUtils.h StringUtils.h
StringUtils.cpp StringUtils.cpp
QVariantUtils.h QVariantUtils.h
@ -262,8 +264,8 @@ set(MINECRAFT_SOURCES
minecraft/launch/ExtractNatives.h minecraft/launch/ExtractNatives.h
minecraft/launch/LauncherPartLaunch.cpp minecraft/launch/LauncherPartLaunch.cpp
minecraft/launch/LauncherPartLaunch.h minecraft/launch/LauncherPartLaunch.h
minecraft/launch/MinecraftServerTarget.cpp minecraft/launch/MinecraftTarget.cpp
minecraft/launch/MinecraftServerTarget.h minecraft/launch/MinecraftTarget.h
minecraft/launch/PrintInstanceInfo.cpp minecraft/launch/PrintInstanceInfo.cpp
minecraft/launch/PrintInstanceInfo.h minecraft/launch/PrintInstanceInfo.h
minecraft/launch/ReconstructAssets.cpp minecraft/launch/ReconstructAssets.cpp
@ -272,6 +274,8 @@ set(MINECRAFT_SOURCES
minecraft/launch/ScanModFolders.h minecraft/launch/ScanModFolders.h
minecraft/launch/VerifyJavaInstall.cpp minecraft/launch/VerifyJavaInstall.cpp
minecraft/launch/VerifyJavaInstall.h minecraft/launch/VerifyJavaInstall.h
minecraft/launch/AutoInstallJava.cpp
minecraft/launch/AutoInstallJava.h
minecraft/GradleSpecifier.h minecraft/GradleSpecifier.h
minecraft/MinecraftInstance.cpp minecraft/MinecraftInstance.cpp
@ -417,8 +421,6 @@ set(SETTINGS_SOURCES
set(JAVA_SOURCES set(JAVA_SOURCES
java/JavaChecker.h java/JavaChecker.h
java/JavaChecker.cpp java/JavaChecker.cpp
java/JavaCheckerJob.h
java/JavaCheckerJob.cpp
java/JavaInstall.h java/JavaInstall.h
java/JavaInstall.cpp java/JavaInstall.cpp
java/JavaInstallList.h java/JavaInstallList.h
@ -427,6 +429,18 @@ set(JAVA_SOURCES
java/JavaUtils.cpp java/JavaUtils.cpp
java/JavaVersion.h java/JavaVersion.h
java/JavaVersion.cpp java/JavaVersion.cpp
java/JavaMetadata.h
java/JavaMetadata.cpp
java/download/ArchiveDownloadTask.cpp
java/download/ArchiveDownloadTask.h
java/download/ManifestDownloadTask.cpp
java/download/ManifestDownloadTask.h
ui/java/InstallJavaDialog.h
ui/java/InstallJavaDialog.cpp
ui/java/VersionList.h
ui/java/VersionList.cpp
) )
set(TRANSLATIONS_SOURCES set(TRANSLATIONS_SOURCES
@ -746,6 +760,8 @@ SET(LAUNCHER_SOURCES
DataMigrationTask.cpp DataMigrationTask.cpp
ApplicationMessage.h ApplicationMessage.h
ApplicationMessage.cpp ApplicationMessage.cpp
SysInfo.h
SysInfo.cpp
# GUI - general utilities # GUI - general utilities
DesktopServices.h DesktopServices.h
@ -784,8 +800,6 @@ SET(LAUNCHER_SOURCES
# GUI - windows # GUI - windows
ui/GuiUtil.h ui/GuiUtil.h
ui/GuiUtil.cpp ui/GuiUtil.cpp
ui/ColorCache.h
ui/ColorCache.cpp
ui/MainWindow.h ui/MainWindow.h
ui/MainWindow.cpp ui/MainWindow.cpp
ui/InstanceWindow.h ui/InstanceWindow.h

View File

@ -276,6 +276,9 @@ bool ensureFolderPathExists(const QFileInfo folderPath)
{ {
QDir dir; QDir dir;
QString ensuredPath = folderPath.filePath(); QString ensuredPath = folderPath.filePath();
if (folderPath.exists())
return true;
bool success = dir.mkpath(ensuredPath); bool success = dir.mkpath(ensuredPath);
return success; return success;
} }

View File

@ -1,16 +1,12 @@
#include "Filter.h" #include "Filter.h"
Filter::~Filter() {}
ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern) {} ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern) {}
ContainsFilter::~ContainsFilter() {}
bool ContainsFilter::accepts(const QString& value) bool ContainsFilter::accepts(const QString& value)
{ {
return value.contains(pattern); return value.contains(pattern);
} }
ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern) {} ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern) {}
ExactFilter::~ExactFilter() {}
bool ExactFilter::accepts(const QString& value) bool ExactFilter::accepts(const QString& value)
{ {
return value == pattern; return value == pattern;
@ -27,10 +23,15 @@ RegexpFilter::RegexpFilter(const QString& regexp, bool invert) : invert(invert)
pattern.setPattern(regexp); pattern.setPattern(regexp);
pattern.optimize(); pattern.optimize();
} }
RegexpFilter::~RegexpFilter() {}
bool RegexpFilter::accepts(const QString& value) bool RegexpFilter::accepts(const QString& value)
{ {
auto match = pattern.match(value); auto match = pattern.match(value);
bool matched = match.hasMatch(); bool matched = match.hasMatch();
return invert ? (!matched) : (matched); return invert ? (!matched) : (matched);
} }
ExactListFilter::ExactListFilter(const QStringList& pattern) : m_pattern(pattern) {}
bool ExactListFilter::accepts(const QString& value)
{
return m_pattern.isEmpty() || m_pattern.contains(value);
}

View File

@ -5,14 +5,14 @@
class Filter { class Filter {
public: public:
virtual ~Filter(); virtual ~Filter() = default;
virtual bool accepts(const QString& value) = 0; virtual bool accepts(const QString& value) = 0;
}; };
class ContainsFilter : public Filter { class ContainsFilter : public Filter {
public: public:
ContainsFilter(const QString& pattern); ContainsFilter(const QString& pattern);
virtual ~ContainsFilter(); virtual ~ContainsFilter() = default;
bool accepts(const QString& value) override; bool accepts(const QString& value) override;
private: private:
@ -22,7 +22,7 @@ class ContainsFilter : public Filter {
class ExactFilter : public Filter { class ExactFilter : public Filter {
public: public:
ExactFilter(const QString& pattern); ExactFilter(const QString& pattern);
virtual ~ExactFilter(); virtual ~ExactFilter() = default;
bool accepts(const QString& value) override; bool accepts(const QString& value) override;
private: private:
@ -32,7 +32,7 @@ class ExactFilter : public Filter {
class ExactIfPresentFilter : public Filter { class ExactIfPresentFilter : public Filter {
public: public:
ExactIfPresentFilter(const QString& pattern); ExactIfPresentFilter(const QString& pattern);
~ExactIfPresentFilter() override = default; virtual ~ExactIfPresentFilter() override = default;
bool accepts(const QString& value) override; bool accepts(const QString& value) override;
private: private:
@ -42,10 +42,20 @@ class ExactIfPresentFilter : public Filter {
class RegexpFilter : public Filter { class RegexpFilter : public Filter {
public: public:
RegexpFilter(const QString& regexp, bool invert); RegexpFilter(const QString& regexp, bool invert);
virtual ~RegexpFilter(); virtual ~RegexpFilter() = default;
bool accepts(const QString& value) override; bool accepts(const QString& value) override;
private: private:
QRegularExpression pattern; QRegularExpression pattern;
bool invert = false; bool invert = false;
}; };
class ExactListFilter : public Filter {
public:
ExactListFilter(const QStringList& pattern = {});
virtual ~ExactListFilter() = default;
bool accepts(const QString& value) override;
private:
const QStringList& m_pattern;
};

View File

@ -22,7 +22,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
public: public:
explicit InstancePageProvider(InstancePtr parent) { inst = parent; } explicit InstancePageProvider(InstancePtr parent) { inst = parent; }
virtual ~InstancePageProvider() {}; virtual ~InstancePageProvider() = default;
virtual QList<BasePage*> getPages() override virtual QList<BasePage*> getPages() override
{ {
QList<BasePage*> values; QList<BasePage*> values;
@ -39,7 +39,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
values.append(new TexturePackPage(onesix.get(), onesix->texturePackList())); values.append(new TexturePackPage(onesix.get(), onesix->texturePackList()));
values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList())); values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList()));
values.append(new NotesPage(onesix.get())); values.append(new NotesPage(onesix.get()));
values.append(new WorldListPage(onesix.get(), onesix->worldList())); values.append(new WorldListPage(onesix, onesix->worldList()));
values.append(new ServersPage(onesix)); values.append(new ServersPage(onesix));
// values.append(new GameOptionsPage(onesix.get())); // values.append(new GameOptionsPage(onesix.get()));
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots"))); values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));

View File

@ -63,7 +63,7 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent)
return true; return true;
} }
void JavaCommon::javaWasOk(QWidget* parent, const JavaCheckResult& result) void JavaCommon::javaWasOk(QWidget* parent, const JavaChecker::Result& result)
{ {
QString text; QString text;
text += QObject::tr( text += QObject::tr(
@ -79,7 +79,7 @@ void JavaCommon::javaWasOk(QWidget* parent, const JavaCheckResult& result)
CustomMessageBox::selectable(parent, QObject::tr("Java test success"), text, QMessageBox::Information)->show(); CustomMessageBox::selectable(parent, QObject::tr("Java test success"), text, QMessageBox::Information)->show();
} }
void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaCheckResult& result) void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaChecker::Result& result)
{ {
auto htmlError = result.errorLog; auto htmlError = result.errorLog;
QString text; QString text;
@ -89,7 +89,7 @@ void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaCheckResult& result)
CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show();
} }
void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaCheckResult& result) void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaChecker::Result& result)
{ {
QString text; QString text;
text += QObject::tr( text += QObject::tr(
@ -116,34 +116,26 @@ void JavaCommon::TestCheck::run()
emit finished(); emit finished();
return; return;
} }
checker.reset(new JavaChecker()); checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0, this));
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished); connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
checker->m_path = m_path; checker->start();
checker->performCheck();
} }
void JavaCommon::TestCheck::checkFinished(JavaCheckResult result) void JavaCommon::TestCheck::checkFinished(const JavaChecker::Result& result)
{ {
if (result.validity != JavaCheckResult::Validity::Valid) { if (result.validity != JavaChecker::Result::Validity::Valid) {
javaBinaryWasBad(m_parent, result); javaBinaryWasBad(m_parent, result);
emit finished(); emit finished();
return; return;
} }
checker.reset(new JavaChecker()); checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0, this));
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs); connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
checker->m_path = m_path; checker->start();
checker->m_args = m_args;
checker->m_minMem = m_minMem;
checker->m_maxMem = m_maxMem;
if (result.javaVersion.requiresPermGen()) {
checker->m_permGen = m_permGen;
}
checker->performCheck();
} }
void JavaCommon::TestCheck::checkFinishedWithArgs(JavaCheckResult result) void JavaCommon::TestCheck::checkFinishedWithArgs(const JavaChecker::Result& result)
{ {
if (result.validity == JavaCheckResult::Validity::Valid) { if (result.validity == JavaChecker::Result::Validity::Valid) {
javaWasOk(m_parent, result); javaWasOk(m_parent, result);
emit finished(); emit finished();
return; return;

View File

@ -10,11 +10,11 @@ namespace JavaCommon {
bool checkJVMArgs(QString args, QWidget* parent); bool checkJVMArgs(QString args, QWidget* parent);
// Show a dialog saying that the Java binary was usable // Show a dialog saying that the Java binary was usable
void javaWasOk(QWidget* parent, const JavaCheckResult& result); void javaWasOk(QWidget* parent, const JavaChecker::Result& result);
// Show a dialog saying that the Java binary was not usable because of bad options // Show a dialog saying that the Java binary was not usable because of bad options
void javaArgsWereBad(QWidget* parent, const JavaCheckResult& result); void javaArgsWereBad(QWidget* parent, const JavaChecker::Result& result);
// Show a dialog saying that the Java binary was not usable // Show a dialog saying that the Java binary was not usable
void javaBinaryWasBad(QWidget* parent, const JavaCheckResult& result); void javaBinaryWasBad(QWidget* parent, const JavaChecker::Result& result);
// Show a dialog if we couldn't find Java Checker // Show a dialog if we couldn't find Java Checker
void javaCheckNotFound(QWidget* parent); void javaCheckNotFound(QWidget* parent);
@ -32,11 +32,11 @@ class TestCheck : public QObject {
void finished(); void finished();
private slots: private slots:
void checkFinished(JavaCheckResult result); void checkFinished(const JavaChecker::Result& result);
void checkFinishedWithArgs(JavaCheckResult result); void checkFinishedWithArgs(const JavaChecker::Result& result);
private: private:
std::shared_ptr<JavaChecker> checker; JavaChecker::Ptr checker;
QWidget* m_parent = nullptr; QWidget* m_parent = nullptr;
QString m_path; QString m_path;
QString m_args; QString m_args;

View File

@ -324,7 +324,7 @@ void LaunchController::launchInstance()
return; return;
} }
m_launcher = m_instance->createLaunchTask(m_session, m_serverToJoin); m_launcher = m_instance->createLaunchTask(m_session, m_targetToJoin);
if (!m_launcher) { if (!m_launcher) {
emitFailed(tr("Couldn't instantiate a launcher.")); emitFailed(tr("Couldn't instantiate a launcher."));
return; return;

View File

@ -39,7 +39,7 @@
#include <QObject> #include <QObject>
#include "minecraft/auth/MinecraftAccount.h" #include "minecraft/auth/MinecraftAccount.h"
#include "minecraft/launch/MinecraftServerTarget.h" #include "minecraft/launch/MinecraftTarget.h"
class InstanceWindow; class InstanceWindow;
class LaunchController : public Task { class LaunchController : public Task {
@ -48,7 +48,7 @@ class LaunchController : public Task {
void executeTask() override; void executeTask() override;
LaunchController(QObject* parent = nullptr); LaunchController(QObject* parent = nullptr);
virtual ~LaunchController() {}; virtual ~LaunchController() = default;
void setInstance(InstancePtr instance) { m_instance = instance; } void setInstance(InstancePtr instance) { m_instance = instance; }
@ -62,7 +62,7 @@ class LaunchController : public Task {
void setParentWidget(QWidget* widget) { m_parentWidget = widget; } void setParentWidget(QWidget* widget) { m_parentWidget = widget; }
void setServerToJoin(MinecraftServerTargetPtr serverToJoin) { m_serverToJoin = std::move(serverToJoin); } void setTargetToJoin(MinecraftTarget::Ptr targetToJoin) { m_targetToJoin = std::move(targetToJoin); }
void setAccountToUse(MinecraftAccountPtr accountToUse) { m_accountToUse = std::move(accountToUse); } void setAccountToUse(MinecraftAccountPtr accountToUse) { m_accountToUse = std::move(accountToUse); }
@ -94,5 +94,5 @@ class LaunchController : public Task {
MinecraftAccountPtr m_accountToUse = nullptr; MinecraftAccountPtr m_accountToUse = nullptr;
AuthSessionPtr m_session; AuthSessionPtr m_session;
shared_qobject_ptr<LaunchTask> m_launcher; shared_qobject_ptr<LaunchTask> m_launcher;
MinecraftServerTargetPtr m_serverToJoin; MinecraftTarget::Ptr m_targetToJoin;
}; };

View File

@ -2,7 +2,7 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> * Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
* *
* 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
@ -536,6 +536,10 @@ bool ExportToZipTask::abort()
void ExtractZipTask::executeTask() void ExtractZipTask::executeTask()
{ {
if (!m_input->isOpen() && !m_input->open(QuaZip::mdUnzip)) {
emitFailed(tr("Unable to open supplied zip file."));
return;
}
m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); }); m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); });
connect(&m_zip_watcher, &QFutureWatcher<ZipResult>::finished, this, &ExtractZipTask::finish); connect(&m_zip_watcher, &QFutureWatcher<ZipResult>::finished, this, &ExtractZipTask::finish);
m_zip_watcher.setFuture(m_zip_future); m_zip_watcher.setFuture(m_zip_future);

View File

@ -2,7 +2,7 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> * Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
* *
* 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
@ -208,6 +208,9 @@ class ExportToZipTask : public Task {
class ExtractZipTask : public Task { class ExtractZipTask : public Task {
public: public:
ExtractZipTask(QString input, QDir outputDir, QString subdirectory = "")
: ExtractZipTask(std::make_shared<QuaZip>(input), outputDir, subdirectory)
{}
ExtractZipTask(std::shared_ptr<QuaZip> input, QDir outputDir, QString subdirectory = "") ExtractZipTask(std::shared_ptr<QuaZip> input, QDir outputDir, QString subdirectory = "")
: m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory) : m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory)
{} {}

View File

@ -40,8 +40,8 @@ namespace MangoHud {
QString getLibraryString() QString getLibraryString()
{ {
/* /**
* Check for vulkan layers in this order: * Guess MangoHud install location by searching for vulkan layers in this order:
* *
* $VK_LAYER_PATH * $VK_LAYER_PATH
* $XDG_DATA_DIRS (/usr/local/share/:/usr/share/) * $XDG_DATA_DIRS (/usr/local/share/:/usr/share/)
@ -49,8 +49,9 @@ QString getLibraryString()
* /etc * /etc
* $XDG_CONFIG_DIRS (/etc/xdg) * $XDG_CONFIG_DIRS (/etc/xdg)
* $XDG_CONFIG_HOME (~/.config) * $XDG_CONFIG_HOME (~/.config)
*
* @returns Absolute path of libMangoHud.so if found and empty QString otherwise.
*/ */
QStringList vkLayerList; QStringList vkLayerList;
{ {
QString home = QDir::homePath(); QString home = QDir::homePath();
@ -85,7 +86,7 @@ QString getLibraryString()
vkLayerList << FS::PathCombine(xdgConfigHome, "vulkan", "implicit_layer.d"); vkLayerList << FS::PathCombine(xdgConfigHome, "vulkan", "implicit_layer.d");
} }
for (QString vkLayer : vkLayerList) { for (const QString& vkLayer : vkLayerList) {
// prefer to use architecture specific vulkan layers // prefer to use architecture specific vulkan layers
QString currentArch = QSysInfo::currentCpuArchitecture(); QString currentArch = QSysInfo::currentCpuArchitecture();
@ -95,8 +96,8 @@ QString getLibraryString()
QStringList manifestNames = { QString("MangoHud.%1.json").arg(currentArch), "MangoHud.json" }; QStringList manifestNames = { QString("MangoHud.%1.json").arg(currentArch), "MangoHud.json" };
QString filePath = ""; QString filePath{};
for (QString manifestName : manifestNames) { for (const QString& manifestName : manifestNames) {
QString tryPath = FS::PathCombine(vkLayer, manifestName); QString tryPath = FS::PathCombine(vkLayer, manifestName);
if (QFile::exists(tryPath)) { if (QFile::exists(tryPath)) {
filePath = tryPath; filePath = tryPath;
@ -111,10 +112,23 @@ QString getLibraryString()
auto conf = Json::requireDocument(filePath, vkLayer); auto conf = Json::requireDocument(filePath, vkLayer);
auto confObject = Json::requireObject(conf, vkLayer); auto confObject = Json::requireObject(conf, vkLayer);
auto layer = Json::ensureObject(confObject, "layer"); auto layer = Json::ensureObject(confObject, "layer");
return Json::ensureString(layer, "library_path"); QString libraryName = Json::ensureString(layer, "library_path");
#ifdef __GLIBC__
// Check whether mangohud is usable on a glibc based system
if (!libraryName.isEmpty()) {
QString libraryPath = findLibrary(libraryName);
if (!libraryPath.isEmpty()) {
return libraryPath;
}
}
#else
// Without glibc return recorded shared library as-is.
return libraryName;
#endif
} }
return QString(); return {};
} }
QString findLibrary(QString libName) QString findLibrary(QString libName)

View File

@ -46,13 +46,13 @@ class NullInstance : public BaseInstance {
{ {
setVersionBroken(true); setVersionBroken(true);
} }
virtual ~NullInstance() {}; virtual ~NullInstance() = default;
void saveNow() override {} void saveNow() override {}
void loadSpecificSettings() override { setSpecificSettingsLoaded(true); } void loadSpecificSettings() override { setSpecificSettingsLoaded(true); }
QString getStatusbarDescription() override { return tr("Unknown instance type"); }; QString getStatusbarDescription() override { return tr("Unknown instance type"); };
QSet<QString> traits() const override { return {}; }; QSet<QString> traits() const override { return {}; };
QString instanceConfigFolder() const override { return instanceRoot(); }; QString instanceConfigFolder() const override { return instanceRoot(); };
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr, MinecraftServerTargetPtr) override { return nullptr; } shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr, MinecraftTarget::Ptr) override { return nullptr; }
shared_qobject_ptr<Task> createUpdateTask([[maybe_unused]] Net::Mode mode) override { return nullptr; } shared_qobject_ptr<Task> createUpdateTask([[maybe_unused]] Net::Mode mode) override { return nullptr; }
QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); } QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); }
QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); } QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); }
@ -64,7 +64,7 @@ class NullInstance : public BaseInstance {
bool canEdit() const override { return false; } bool canEdit() const override { return false; }
bool canLaunch() const override { return false; } bool canLaunch() const override { return false; }
void populateLaunchMenu(QMenu* menu) override {} void populateLaunchMenu(QMenu* menu) override {}
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override QStringList verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) override
{ {
QStringList out; QStringList out;
out << "Null instance - placeholder."; out << "Null instance - placeholder.";

99
launcher/SysInfo.cpp Normal file
View File

@ -0,0 +1,99 @@
#include <QDebug>
#include <QString>
#include "sys.h"
#ifdef Q_OS_MACOS
#include <sys/sysctl.h>
#endif
#include <QFile>
#include <QMap>
#include <QProcess>
#include <QStandardPaths>
#ifdef Q_OS_MACOS
bool rosettaDetect()
{
int ret = 0;
size_t size = sizeof(ret);
if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) {
return false;
}
return ret == 1;
}
#endif
namespace SysInfo {
QString currentSystem()
{
#if defined(Q_OS_LINUX)
return "linux";
#elif defined(Q_OS_MACOS)
return "osx";
#elif defined(Q_OS_WINDOWS)
return "windows";
#elif defined(Q_OS_FREEBSD)
return "freebsd";
#elif defined(Q_OS_OPENBSD)
return "openbsd";
#else
return "unknown";
#endif
}
QString useQTForArch()
{
#if defined(Q_OS_MACOS) && !defined(Q_PROCESSOR_ARM)
if (rosettaDetect()) {
return "arm64";
} else {
return "x86_64";
}
#endif
return QSysInfo::currentCpuArchitecture();
}
int suitableMaxMem()
{
float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte;
int maxMemoryAlloc;
// If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB
if (totalRAM < (4096 * 1.5))
maxMemoryAlloc = (int)(totalRAM / 1.5);
else
maxMemoryAlloc = 4096;
return maxMemoryAlloc;
}
QString getSupportedJavaArchitecture()
{
auto sys = currentSystem();
auto arch = useQTForArch();
if (sys == "windows") {
if (arch == "x86_64")
return "windows-x64";
if (arch == "i386")
return "windows-x86";
// Unknown, maybe arm, appending arch
return "windows-" + arch;
}
if (sys == "osx") {
if (arch == "arm64")
return "mac-os-arm64";
if (arch.contains("64"))
return "mac-os-64";
if (arch.contains("86"))
return "mac-os-86";
// Unknown, maybe something new, appending arch
return "mac-os-" + arch;
} else if (sys == "linux") {
if (arch == "x86_64")
return "linux-x64";
if (arch == "i386")
return "linux-x86";
// will work for arm32 arm(64)
return "linux-" + arch;
}
return {};
}
} // namespace SysInfo

8
launcher/SysInfo.h Normal file
View File

@ -0,0 +1,8 @@
#include <QString>
namespace SysInfo {
QString currentSystem();
QString useQTForArch();
QString getSupportedJavaArchitecture();
int suitableMaxMem();
} // namespace SysInfo

260
launcher/Untar.cpp Normal file
View File

@ -0,0 +1,260 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Untar.h"
#include <quagzipfile.h>
#include <QByteArray>
#include <QFileInfo>
#include <QIODevice>
#include <QString>
#include "FileSystem.h"
// adaptation of the:
// - https://github.com/madler/zlib/blob/develop/contrib/untgz/untgz.c
// - https://en.wikipedia.org/wiki/Tar_(computing)
// - https://github.com/euroelessar/cutereader/blob/master/karchive/src/ktar.cpp
#define BLOCKSIZE 512
#define SHORTNAMESIZE 100
enum class TypeFlag : char {
Regular = '0', // regular file
ARegular = 0, // regular file
Link = '1', // link
Symlink = '2', // reserved
Character = '3', // character special
Block = '4', // block special
Directory = '5', // directory
FIFO = '6', // FIFO special
Contiguous = '7', // reserved
// Posix stuff
GlobalPosixHeader = 'g',
ExtendedPosixHeader = 'x',
// 'A' 'Z' Vendor specific extensions(POSIX .1 - 1988)
// GNU
GNULongLink = 'K', /* long link name */
GNULongName = 'L', /* long file name */
};
// struct Header { /* byte offset */
// char name[100]; /* 0 */
// char mode[8]; /* 100 */
// char uid[8]; /* 108 */
// char gid[8]; /* 116 */
// char size[12]; /* 124 */
// char mtime[12]; /* 136 */
// char chksum[8]; /* 148 */
// TypeFlag typeflag; /* 156 */
// char linkname[100]; /* 157 */
// char magic[6]; /* 257 */
// char version[2]; /* 263 */
// char uname[32]; /* 265 */
// char gname[32]; /* 297 */
// char devmajor[8]; /* 329 */
// char devminor[8]; /* 337 */
// char prefix[155]; /* 345 */
// /* 500 */
// };
bool readLonglink(QIODevice* in, qint64 size, QByteArray& longlink)
{
qint64 n = 0;
size--; // ignore trailing null
if (size < 0) {
qCritical() << "The filename size is negative";
return false;
}
longlink.resize(size + (BLOCKSIZE - size % BLOCKSIZE)); // make the size divisible by BLOCKSIZE
for (qint64 offset = 0; offset < longlink.size(); offset += BLOCKSIZE) {
n = in->read(longlink.data() + offset, BLOCKSIZE);
if (n != BLOCKSIZE) {
qCritical() << "The expected blocksize was not respected for the name";
return false;
}
}
longlink.truncate(qstrlen(longlink.constData()));
return true;
}
int getOctal(char* buffer, int maxlenght, bool* ok)
{
return QByteArray(buffer, qstrnlen(buffer, maxlenght)).toInt(ok, 8);
}
QString decodeName(char* name)
{
return QFile::decodeName(QByteArray(name, qstrnlen(name, 100)));
}
bool Tar::extract(QIODevice* in, QString dst)
{
char buffer[BLOCKSIZE];
QString name, symlink, firstFolderName;
bool doNotReset = false, ok;
while (true) {
auto n = in->read(buffer, BLOCKSIZE);
if (n != BLOCKSIZE) { // allways expect complete blocks
qCritical() << "The expected blocksize was not respected";
return false;
}
if (buffer[0] == 0) { // end of archive
return true;
}
int mode = getOctal(buffer + 100, 8, &ok) | QFile::ReadUser | QFile::WriteUser; // hack to ensure write and read permisions
if (!ok) {
qCritical() << "The file mode can't be read";
return false;
}
// there are names that are exactly 100 bytes long
// and neither longlink nor \0 terminated (bug:101472)
if (name.isEmpty()) {
name = decodeName(buffer);
if (!firstFolderName.isEmpty() && name.startsWith(firstFolderName)) {
name = name.mid(firstFolderName.size());
}
}
if (symlink.isEmpty())
symlink = decodeName(buffer);
qint64 size = getOctal(buffer + 124, 12, &ok);
if (!ok) {
qCritical() << "The file size can't be read";
return false;
}
switch (TypeFlag(buffer[156])) {
case TypeFlag::Regular:
/* fallthrough */
case TypeFlag::ARegular: {
auto fileName = FS::PathCombine(dst, name);
if (!FS::ensureFilePathExists(fileName)) {
qCritical() << "Can't ensure the file path to exist: " << fileName;
return false;
}
QFile out(fileName);
if (!out.open(QFile::WriteOnly)) {
qCritical() << "Can't open file:" << fileName;
return false;
}
out.setPermissions(QFile::Permissions(mode));
while (size > 0) {
QByteArray tmp(BLOCKSIZE, 0);
n = in->read(tmp.data(), BLOCKSIZE);
if (n != BLOCKSIZE) {
qCritical() << "The expected blocksize was not respected when reading file";
return false;
}
tmp.truncate(qMin(qint64(BLOCKSIZE), size));
out.write(tmp);
size -= BLOCKSIZE;
}
break;
}
case TypeFlag::Directory: {
if (firstFolderName.isEmpty()) {
firstFolderName = name;
break;
}
auto folderPath = FS::PathCombine(dst, name);
if (!FS::ensureFolderPathExists(folderPath)) {
qCritical() << "Can't ensure that folder exists: " << folderPath;
return false;
}
break;
}
case TypeFlag::GNULongLink: {
doNotReset = true;
QByteArray longlink;
if (readLonglink(in, size, longlink)) {
symlink = QFile::decodeName(longlink.constData());
} else {
qCritical() << "Failed to read long link";
return false;
}
break;
}
case TypeFlag::GNULongName: {
doNotReset = true;
QByteArray longlink;
if (readLonglink(in, size, longlink)) {
name = QFile::decodeName(longlink.constData());
} else {
qCritical() << "Failed to read long name";
return false;
}
break;
}
case TypeFlag::Link:
/* fallthrough */
case TypeFlag::Symlink: {
auto fileName = FS::PathCombine(dst, name);
if (!FS::create_link(FS::PathCombine(QFileInfo(fileName).path(), symlink), fileName)()) { // do not use symlinks
qCritical() << "Can't create link for:" << fileName << " to:" << FS::PathCombine(QFileInfo(fileName).path(), symlink);
return false;
}
FS::ensureFilePathExists(fileName);
QFile::setPermissions(fileName, QFile::Permissions(mode));
break;
}
case TypeFlag::Character:
/* fallthrough */
case TypeFlag::Block:
/* fallthrough */
case TypeFlag::FIFO:
/* fallthrough */
case TypeFlag::Contiguous:
/* fallthrough */
case TypeFlag::GlobalPosixHeader:
/* fallthrough */
case TypeFlag::ExtendedPosixHeader:
/* fallthrough */
default:
break;
}
if (!doNotReset) {
name.truncate(0);
symlink.truncate(0);
}
doNotReset = false;
}
return true;
}
bool GZTar::extract(QString src, QString dst)
{
QuaGzipFile a(src);
if (!a.open(QIODevice::ReadOnly)) {
qCritical() << "Can't open tar file:" << src;
return false;
}
return Tar::extract(&a, dst);
}

46
launcher/Untar.h Normal file
View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QIODevice>
// this is a hack used for the java downloader (feel free to remove it in favor of a library)
// both extract functions will extract the first folder inside dest(disregarding the prefix)
namespace Tar {
bool extract(QIODevice* in, QString dst);
}
namespace GZTar {
bool extract(QString src, QString dst);
}

View File

@ -114,10 +114,14 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation,
return tr("Branch"); return tr("Branch");
case Type: case Type:
return tr("Type"); return tr("Type");
case Architecture: case CPUArchitecture:
return tr("Architecture"); return tr("Architecture");
case Path: case Path:
return tr("Path"); return tr("Path");
case JavaName:
return tr("Java Name");
case JavaMajor:
return tr("Major Version");
case Time: case Time:
return tr("Released"); return tr("Released");
} }
@ -131,10 +135,14 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation,
return tr("The version's branch"); return tr("The version's branch");
case Type: case Type:
return tr("The version's type"); return tr("The version's type");
case Architecture: case CPUArchitecture:
return tr("CPU Architecture"); return tr("CPU Architecture");
case Path: case Path:
return tr("Filesystem path to this version"); return tr("Filesystem path to this version");
case JavaName:
return tr("The alternative name of the java version");
case JavaMajor:
return tr("The java major version");
case Time: case Time:
return tr("Release date of this version"); return tr("Release date of this version");
} }
@ -165,10 +173,14 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const
return sourceModel()->data(parentIndex, BaseVersionList::BranchRole); return sourceModel()->data(parentIndex, BaseVersionList::BranchRole);
case Type: case Type:
return sourceModel()->data(parentIndex, BaseVersionList::TypeRole); return sourceModel()->data(parentIndex, BaseVersionList::TypeRole);
case Architecture: case CPUArchitecture:
return sourceModel()->data(parentIndex, BaseVersionList::ArchitectureRole); return sourceModel()->data(parentIndex, BaseVersionList::CPUArchitectureRole);
case Path: case Path:
return sourceModel()->data(parentIndex, BaseVersionList::PathRole); return sourceModel()->data(parentIndex, BaseVersionList::PathRole);
case JavaName:
return sourceModel()->data(parentIndex, BaseVersionList::JavaNameRole);
case JavaMajor:
return sourceModel()->data(parentIndex, BaseVersionList::JavaMajorRole);
case Time: case Time:
return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate(); return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate();
default: default:
@ -308,12 +320,18 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw)
m_columns.push_back(ParentVersion); m_columns.push_back(ParentVersion);
} }
*/ */
if (roles.contains(BaseVersionList::ArchitectureRole)) { if (roles.contains(BaseVersionList::CPUArchitectureRole)) {
m_columns.push_back(Architecture); m_columns.push_back(CPUArchitecture);
} }
if (roles.contains(BaseVersionList::PathRole)) { if (roles.contains(BaseVersionList::PathRole)) {
m_columns.push_back(Path); m_columns.push_back(Path);
} }
if (roles.contains(BaseVersionList::JavaNameRole)) {
m_columns.push_back(JavaName);
}
if (roles.contains(BaseVersionList::JavaMajorRole)) {
m_columns.push_back(JavaMajor);
}
if (roles.contains(Meta::VersionList::TimeRole)) { if (roles.contains(Meta::VersionList::TimeRole)) {
m_columns.push_back(Time); m_columns.push_back(Time);
} }

View File

@ -9,7 +9,7 @@ class VersionFilterModel;
class VersionProxyModel : public QAbstractProxyModel { class VersionProxyModel : public QAbstractProxyModel {
Q_OBJECT Q_OBJECT
public: public:
enum Column { Name, ParentVersion, Branch, Type, Architecture, Path, Time }; enum Column { Name, ParentVersion, Branch, Type, CPUArchitecture, Path, Time, JavaName, JavaMajor };
using FilterMap = QHash<BaseVersionList::ModelRoles, std::shared_ptr<Filter>>; using FilterMap = QHash<BaseVersionList::ModelRoles, std::shared_ptr<Filter>>;
public: public:

View File

@ -40,14 +40,15 @@
#include <QMap> #include <QMap>
#include <QProcess> #include <QProcess>
#include "Application.h"
#include "Commandline.h" #include "Commandline.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "JavaUtils.h" #include "java/JavaUtils.h"
JavaChecker::JavaChecker(QObject* parent) : QObject(parent) {} JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id, QObject* parent)
: Task(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id)
{}
void JavaChecker::performCheck() void JavaChecker::executeTask()
{ {
QString checkerJar = JavaUtils::getJavaCheckPath(); QString checkerJar = JavaUtils::getJavaCheckPath();
@ -72,7 +73,7 @@ void JavaChecker::performCheck()
if (m_maxMem != 0) { if (m_maxMem != 0) {
args << QString("-Xmx%1m").arg(m_maxMem); args << QString("-Xmx%1m").arg(m_maxMem);
} }
if (m_permGen != 64) { if (m_permGen != 64 && m_permGen != 0) {
args << QString("-XX:PermSize=%1m").arg(m_permGen); args << QString("-XX:PermSize=%1m").arg(m_permGen);
} }
@ -115,11 +116,10 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
QProcessPtr _process = process; QProcessPtr _process = process;
process.reset(); process.reset();
JavaCheckResult result; Result result = {
{ m_path,
result.path = m_path; m_id,
result.id = m_id; };
}
result.errorLog = m_stderr; result.errorLog = m_stderr;
result.outLog = m_stdout; result.outLog = m_stdout;
qDebug() << "STDOUT" << m_stdout; qDebug() << "STDOUT" << m_stdout;
@ -127,8 +127,9 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
qDebug() << "Java checker finished with status" << status << "exit code" << exitcode; qDebug() << "Java checker finished with status" << status << "exit code" << exitcode;
if (status == QProcess::CrashExit || exitcode == 1) { if (status == QProcess::CrashExit || exitcode == 1) {
result.validity = JavaCheckResult::Validity::Errored; result.validity = Result::Validity::Errored;
emit checkFinished(result); emit checkFinished(result);
emitSucceeded();
return; return;
} }
@ -161,8 +162,9 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
} }
if (!results.contains("os.arch") || !results.contains("java.version") || !results.contains("java.vendor") || !success) { if (!results.contains("os.arch") || !results.contains("java.version") || !results.contains("java.vendor") || !success) {
result.validity = JavaCheckResult::Validity::ReturnedInvalidData; result.validity = Result::Validity::ReturnedInvalidData;
emit checkFinished(result); emit checkFinished(result);
emitSucceeded();
return; return;
} }
@ -171,7 +173,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
auto java_vendor = results["java.vendor"]; auto java_vendor = results["java.vendor"];
bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64"; bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64";
result.validity = JavaCheckResult::Validity::Valid; result.validity = Result::Validity::Valid;
result.is_64bit = is_64; result.is_64bit = is_64;
result.mojangPlatform = is_64 ? "64" : "32"; result.mojangPlatform = is_64 ? "64" : "32";
result.realPlatform = os_arch; result.realPlatform = os_arch;
@ -179,6 +181,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
result.javaVendor = java_vendor; result.javaVendor = java_vendor;
qDebug() << "Java checker succeeded."; qDebug() << "Java checker succeeded.";
emit checkFinished(result); emit checkFinished(result);
emitSucceeded();
} }
void JavaChecker::error(QProcess::ProcessError err) void JavaChecker::error(QProcess::ProcessError err)
@ -190,15 +193,9 @@ void JavaChecker::error(QProcess::ProcessError err)
qDebug() << "Native environment:"; qDebug() << "Native environment:";
qDebug() << QProcessEnvironment::systemEnvironment().toStringList(); qDebug() << QProcessEnvironment::systemEnvironment().toStringList();
killTimer.stop(); killTimer.stop();
JavaCheckResult result; emit checkFinished({ m_path, m_id });
{
result.path = m_path;
result.id = m_id;
}
emit checkFinished(result);
return;
} }
emitSucceeded();
} }
void JavaChecker::timeout() void JavaChecker::timeout()

View File

@ -3,14 +3,19 @@
#include <QTimer> #include <QTimer>
#include <memory> #include <memory>
#include "QObjectPtr.h"
#include "JavaVersion.h" #include "JavaVersion.h"
#include "QObjectPtr.h"
#include "tasks/Task.h"
class JavaChecker; class JavaChecker : public Task {
Q_OBJECT
public:
using QProcessPtr = shared_qobject_ptr<QProcess>;
using Ptr = shared_qobject_ptr<JavaChecker>;
struct JavaCheckResult { struct Result {
QString path; QString path;
int id;
QString mojangPlatform; QString mojangPlatform;
QString realPlatform; QString realPlatform;
JavaVersion javaVersion; JavaVersion javaVersion;
@ -18,34 +23,31 @@ struct JavaCheckResult {
QString outLog; QString outLog;
QString errorLog; QString errorLog;
bool is_64bit = false; bool is_64bit = false;
int id;
enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored; enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
}; };
using QProcessPtr = shared_qobject_ptr<QProcess>; explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0, QObject* parent = 0);
using JavaCheckerPtr = shared_qobject_ptr<JavaChecker>;
class JavaChecker : public QObject {
Q_OBJECT
public:
explicit JavaChecker(QObject* parent = 0);
void performCheck();
QString m_path;
QString m_args;
int m_id = 0;
int m_minMem = 0;
int m_maxMem = 0;
int m_permGen = 64;
signals: signals:
void checkFinished(JavaCheckResult result); void checkFinished(const Result& result);
protected:
virtual void executeTask() override;
private: private:
QProcessPtr process; QProcessPtr process;
QTimer killTimer; QTimer killTimer;
QString m_stdout; QString m_stdout;
QString m_stderr; QString m_stderr;
public slots:
QString m_path;
QString m_args;
int m_minMem = 0;
int m_maxMem = 0;
int m_permGen = 64;
int m_id = 0;
private slots:
void timeout(); void timeout();
void finished(int exitcode, QProcess::ExitStatus); void finished(int exitcode, QProcess::ExitStatus);
void error(QProcess::ProcessError); void error(QProcess::ProcessError);

View File

@ -1,41 +0,0 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "JavaCheckerJob.h"
#include <QDebug>
void JavaCheckerJob::partFinished(JavaCheckResult result)
{
num_finished++;
qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" << javacheckers.size();
setProgress(num_finished, javacheckers.size());
javaresults.replace(result.id, result);
if (num_finished == javacheckers.size()) {
emitSucceeded();
}
}
void JavaCheckerJob::executeTask()
{
qDebug() << m_job_name.toLocal8Bit() << " started.";
for (auto iter : javacheckers) {
javaresults.append(JavaCheckResult());
connect(iter.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
iter->performCheck();
}
}

View File

@ -1,56 +0,0 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QtNetwork>
#include "JavaChecker.h"
#include "tasks/Task.h"
class JavaCheckerJob;
using JavaCheckerJobPtr = shared_qobject_ptr<JavaCheckerJob>;
// FIXME: this just seems horribly redundant
class JavaCheckerJob : public Task {
Q_OBJECT
public:
explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {};
virtual ~JavaCheckerJob() {};
bool addJavaCheckerAction(JavaCheckerPtr base)
{
javacheckers.append(base);
// if this is already running, the action needs to be started right away!
if (isRunning()) {
setProgress(num_finished, javacheckers.size());
connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
base->performCheck();
}
return true;
}
QList<JavaCheckResult> getResults() { return javaresults; }
private slots:
void partFinished(JavaCheckResult result);
protected:
virtual void executeTask() override;
private:
QString m_job_name;
QList<JavaCheckerPtr> javacheckers;
QList<JavaCheckResult> javaresults;
int num_finished = 0;
};

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> * Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
* *
* 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

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> * Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -40,6 +40,7 @@ struct JavaInstall : public BaseVersion {
QString arch; QString arch;
QString path; QString path;
bool recommended = false; bool recommended = false;
bool is_64bit = false;
}; };
using JavaInstallPtr = std::shared_ptr<JavaInstall>; using JavaInstallPtr = std::shared_ptr<JavaInstall>;

View File

@ -38,13 +38,17 @@
#include <QtXml> #include <QtXml>
#include <QDebug> #include <QDebug>
#include <algorithm>
#include "java/JavaCheckerJob.h" #include "Application.h"
#include "java/JavaChecker.h"
#include "java/JavaInstallList.h" #include "java/JavaInstallList.h"
#include "java/JavaUtils.h" #include "java/JavaUtils.h"
#include "minecraft/VersionFilterData.h" #include "tasks/ConcurrentTask.h"
JavaInstallList::JavaInstallList(QObject* parent) : BaseVersionList(parent) {} JavaInstallList::JavaInstallList(QObject* parent, bool onlyManagedVersions)
: BaseVersionList(parent), m_only_managed_versions(onlyManagedVersions)
{}
Task::Ptr JavaInstallList::getLoadTask() Task::Ptr JavaInstallList::getLoadTask()
{ {
@ -55,7 +59,7 @@ Task::Ptr JavaInstallList::getLoadTask()
Task::Ptr JavaInstallList::getCurrentTask() Task::Ptr JavaInstallList::getCurrentTask()
{ {
if (m_status == Status::InProgress) { if (m_status == Status::InProgress) {
return m_loadTask; return m_load_task;
} }
return nullptr; return nullptr;
} }
@ -64,8 +68,8 @@ void JavaInstallList::load()
{ {
if (m_status != Status::InProgress) { if (m_status != Status::InProgress) {
m_status = Status::InProgress; m_status = Status::InProgress;
m_loadTask.reset(new JavaListLoadTask(this)); m_load_task.reset(new JavaListLoadTask(this, m_only_managed_versions));
m_loadTask->start(); m_load_task->start();
} }
} }
@ -106,7 +110,7 @@ QVariant JavaInstallList::data(const QModelIndex& index, int role) const
return version->recommended; return version->recommended;
case PathRole: case PathRole:
return version->path; return version->path;
case ArchitectureRole: case CPUArchitectureRole:
return version->arch; return version->arch;
default: default:
return QVariant(); return QVariant();
@ -115,7 +119,7 @@ QVariant JavaInstallList::data(const QModelIndex& index, int role) const
BaseVersionList::RoleList JavaInstallList::providesRoles() const BaseVersionList::RoleList JavaInstallList::providesRoles() const
{ {
return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole }; return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, CPUArchitectureRole };
} }
void JavaInstallList::updateListData(QList<BaseVersion::Ptr> versions) void JavaInstallList::updateListData(QList<BaseVersion::Ptr> versions)
@ -129,7 +133,7 @@ void JavaInstallList::updateListData(QList<BaseVersion::Ptr> versions)
} }
endResetModel(); endResetModel();
m_status = Status::Done; m_status = Status::Done;
m_loadTask.reset(); m_load_task.reset();
} }
bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right) bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right)
@ -146,35 +150,30 @@ void JavaInstallList::sortVersions()
endResetModel(); endResetModel();
} }
JavaListLoadTask::JavaListLoadTask(JavaInstallList* vlist) : Task() JavaListLoadTask::JavaListLoadTask(JavaInstallList* vlist, bool onlyManagedVersions) : Task(), m_only_managed_versions(onlyManagedVersions)
{ {
m_list = vlist; m_list = vlist;
m_currentRecommended = NULL; m_current_recommended = NULL;
} }
JavaListLoadTask::~JavaListLoadTask() {}
void JavaListLoadTask::executeTask() void JavaListLoadTask::executeTask()
{ {
setStatus(tr("Detecting Java installations...")); setStatus(tr("Detecting Java installations..."));
JavaUtils ju; JavaUtils ju;
QList<QString> candidate_paths = ju.FindJavaPaths(); QList<QString> candidate_paths = m_only_managed_versions ? getPrismJavaBundle() : ju.FindJavaPaths();
m_job.reset(new JavaCheckerJob("Java detection")); ConcurrentTask::Ptr job(new ConcurrentTask(this, "Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
m_job.reset(job);
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
connect(m_job.get(), &Task::progress, this, &Task::setProgress); connect(m_job.get(), &Task::progress, this, &Task::setProgress);
qDebug() << "Probing the following Java paths: "; qDebug() << "Probing the following Java paths: ";
int id = 0; int id = 0;
for (QString candidate : candidate_paths) { for (QString candidate : candidate_paths) {
qDebug() << " " << candidate; auto checker = new JavaChecker(candidate, "", 0, 0, 0, id, this);
connect(checker, &JavaChecker::checkFinished, [this](const JavaChecker::Result& result) { m_results << result; });
auto candidate_checker = new JavaChecker(); job->addTask(Task::Ptr(checker));
candidate_checker->m_path = candidate;
candidate_checker->m_id = id;
m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker));
id++; id++;
} }
@ -184,16 +183,17 @@ void JavaListLoadTask::executeTask()
void JavaListLoadTask::javaCheckerFinished() void JavaListLoadTask::javaCheckerFinished()
{ {
QList<JavaInstallPtr> candidates; QList<JavaInstallPtr> candidates;
auto results = m_job->getResults(); std::sort(m_results.begin(), m_results.end(), [](const JavaChecker::Result& a, const JavaChecker::Result& b) { return a.id < b.id; });
qDebug() << "Found the following valid Java installations:"; qDebug() << "Found the following valid Java installations:";
for (JavaCheckResult result : results) { for (auto result : m_results) {
if (result.validity == JavaCheckResult::Validity::Valid) { if (result.validity == JavaChecker::Result::Validity::Valid) {
JavaInstallPtr javaVersion(new JavaInstall()); JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = result.javaVersion; javaVersion->id = result.javaVersion;
javaVersion->arch = result.realPlatform; javaVersion->arch = result.realPlatform;
javaVersion->path = result.path; javaVersion->path = result.path;
javaVersion->is_64bit = result.is_64bit;
candidates.append(javaVersion); candidates.append(javaVersion);
qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path; qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path;

View File

@ -19,9 +19,9 @@
#include <QObject> #include <QObject>
#include "BaseVersionList.h" #include "BaseVersionList.h"
#include "java/JavaChecker.h"
#include "tasks/Task.h" #include "tasks/Task.h"
#include "JavaCheckerJob.h"
#include "JavaInstall.h" #include "JavaInstall.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
@ -33,9 +33,9 @@ class JavaInstallList : public BaseVersionList {
enum class Status { NotDone, InProgress, Done }; enum class Status { NotDone, InProgress, Done };
public: public:
explicit JavaInstallList(QObject* parent = 0); explicit JavaInstallList(QObject* parent = 0, bool onlyManagedVersions = false);
Task::Ptr getLoadTask() override; [[nodiscard]] Task::Ptr getLoadTask() override;
bool isLoaded() override; bool isLoaded() override;
const BaseVersion::Ptr at(int i) const override; const BaseVersion::Ptr at(int i) const override;
int count() const override; int count() const override;
@ -53,23 +53,27 @@ class JavaInstallList : public BaseVersionList {
protected: protected:
Status m_status = Status::NotDone; Status m_status = Status::NotDone;
shared_qobject_ptr<JavaListLoadTask> m_loadTask; shared_qobject_ptr<JavaListLoadTask> m_load_task;
QList<BaseVersion::Ptr> m_vlist; QList<BaseVersion::Ptr> m_vlist;
bool m_only_managed_versions;
}; };
class JavaListLoadTask : public Task { class JavaListLoadTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
explicit JavaListLoadTask(JavaInstallList* vlist); explicit JavaListLoadTask(JavaInstallList* vlist, bool onlyManagedVersions = false);
virtual ~JavaListLoadTask(); virtual ~JavaListLoadTask() = default;
protected:
void executeTask() override; void executeTask() override;
public slots: public slots:
void javaCheckerFinished(); void javaCheckerFinished();
protected: protected:
shared_qobject_ptr<JavaCheckerJob> m_job; Task::Ptr m_job;
JavaInstallList* m_list; JavaInstallList* m_list;
JavaInstall* m_currentRecommended; JavaInstall* m_current_recommended;
QList<JavaChecker::Result> m_results;
bool m_only_managed_versions;
}; };

View File

@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "java/JavaMetadata.h"
#include <memory>
#include "Json.h"
#include "StringUtils.h"
#include "java/JavaVersion.h"
#include "minecraft/ParseUtils.h"
namespace Java {
DownloadType parseDownloadType(QString javaDownload)
{
if (javaDownload == "manifest")
return DownloadType::Manifest;
else if (javaDownload == "archive")
return DownloadType::Archive;
else
return DownloadType::Unknown;
}
QString downloadTypeToString(DownloadType javaDownload)
{
switch (javaDownload) {
case DownloadType::Manifest:
return "manifest";
case DownloadType::Archive:
return "archive";
case DownloadType::Unknown:
break;
}
return "unknown";
}
MetadataPtr parseJavaMeta(const QJsonObject& in)
{
auto meta = std::make_shared<Metadata>();
meta->m_name = Json::ensureString(in, "name", "");
meta->vendor = Json::ensureString(in, "vendor", "");
meta->url = Json::ensureString(in, "url", "");
meta->releaseTime = timeFromS3Time(Json::ensureString(in, "releaseTime", ""));
meta->downloadType = parseDownloadType(Json::ensureString(in, "downloadType", ""));
meta->packageType = Json::ensureString(in, "packageType", "");
meta->runtimeOS = Json::ensureString(in, "runtimeOS", "unknown");
if (in.contains("checksum")) {
auto obj = Json::requireObject(in, "checksum");
meta->checksumHash = Json::ensureString(obj, "hash", "");
meta->checksumType = Json::ensureString(obj, "type", "");
}
if (in.contains("version")) {
auto obj = Json::requireObject(in, "version");
auto name = Json::ensureString(obj, "name", "");
auto major = Json::ensureInteger(obj, "major", 0);
auto minor = Json::ensureInteger(obj, "minor", 0);
auto security = Json::ensureInteger(obj, "security", 0);
auto build = Json::ensureInteger(obj, "build", 0);
meta->version = JavaVersion(major, minor, security, build, name);
}
return meta;
}
bool Metadata::operator<(const Metadata& rhs)
{
auto id = version;
if (id < rhs.version) {
return true;
}
if (id > rhs.version) {
return false;
}
auto date = releaseTime;
if (date < rhs.releaseTime) {
return true;
}
if (date > rhs.releaseTime) {
return false;
}
return StringUtils::naturalCompare(m_name, rhs.m_name, Qt::CaseInsensitive) < 0;
}
bool Metadata::operator==(const Metadata& rhs)
{
return version == rhs.version && m_name == rhs.m_name;
}
bool Metadata::operator>(const Metadata& rhs)
{
return (!operator<(rhs)) && (!operator==(rhs));
}
bool Metadata::operator<(BaseVersion& a)
{
try {
return operator<(dynamic_cast<Metadata&>(a));
} catch (const std::bad_cast& e) {
return BaseVersion::operator<(a);
}
}
bool Metadata::operator>(BaseVersion& a)
{
try {
return operator>(dynamic_cast<Metadata&>(a));
} catch (const std::bad_cast& e) {
return BaseVersion::operator>(a);
}
}
} // namespace Java

View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QDateTime>
#include <QJsonObject>
#include <QString>
#include <memory>
#include "BaseVersion.h"
#include "java/JavaVersion.h"
namespace Java {
enum class DownloadType { Manifest, Archive, Unknown };
class Metadata : public BaseVersion {
public:
virtual QString descriptor() override { return version.toString(); }
virtual QString name() override { return m_name; }
virtual QString typeString() const override { return vendor; }
virtual bool operator<(BaseVersion& a) override;
virtual bool operator>(BaseVersion& a) override;
bool operator<(const Metadata& rhs);
bool operator==(const Metadata& rhs);
bool operator>(const Metadata& rhs);
QString m_name;
QString vendor;
QString url;
QDateTime releaseTime;
QString checksumType;
QString checksumHash;
DownloadType downloadType;
QString packageType;
JavaVersion version;
QString runtimeOS;
};
using MetadataPtr = std::shared_ptr<Metadata>;
DownloadType parseDownloadType(QString javaDownload);
QString downloadTypeToString(DownloadType javaDownload);
MetadataPtr parseJavaMeta(const QJsonObject& libObj);
} // namespace Java

View File

@ -182,8 +182,9 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
else if (keyType == KEY_WOW64_32KEY) else if (keyType == KEY_WOW64_32KEY)
archType = "32"; archType = "32";
for (HKEY baseRegistry : { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE }) {
HKEY jreKey; HKEY jreKey;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyName.toStdWString().c_str(), 0, KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == if (RegOpenKeyExW(baseRegistry, keyName.toStdWString().c_str(), 0, KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) ==
ERROR_SUCCESS) { ERROR_SUCCESS) {
// Read the current type version from the registry. // Read the current type version from the registry.
// This will be used to find any key that contains the JavaHome value. // This will be used to find any key that contains the JavaHome value.
@ -205,7 +206,7 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
QString newKeyName = keyName + "\\" + newSubkeyName + subkeySuffix; QString newKeyName = keyName + "\\" + newSubkeyName + subkeySuffix;
HKEY newKey; HKEY newKey;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, newKeyName.toStdWString().c_str(), 0, KEY_READ | keyType, &newKey) == if (RegOpenKeyExW(baseRegistry, newKeyName.toStdWString().c_str(), 0, KEY_READ | keyType, &newKey) ==
ERROR_SUCCESS) { ERROR_SUCCESS) {
// Read the JavaHome value to find where Java is installed. // Read the JavaHome value to find where Java is installed.
DWORD valueSz = 0; DWORD valueSz = 0;
@ -233,6 +234,7 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
RegCloseKey(jreKey); RegCloseKey(jreKey);
} }
}
return javas; return javas;
} }
@ -345,6 +347,7 @@ QList<QString> JavaUtils::FindJavaPaths()
} }
candidates.append(getMinecraftJavaBundle()); candidates.append(getMinecraftJavaBundle());
candidates.append(getPrismJavaBundle());
candidates = addJavasFromEnv(candidates); candidates = addJavasFromEnv(candidates);
candidates.removeDuplicates(); candidates.removeDuplicates();
return candidates; return candidates;
@ -389,6 +392,7 @@ QList<QString> JavaUtils::FindJavaPaths()
} }
javas.append(getMinecraftJavaBundle()); javas.append(getMinecraftJavaBundle());
javas.append(getPrismJavaBundle());
javas = addJavasFromEnv(javas); javas = addJavasFromEnv(javas);
javas.removeDuplicates(); javas.removeDuplicates();
return javas; return javas;
@ -452,6 +456,7 @@ QList<QString> JavaUtils::FindJavaPaths()
scanJavaDirs(FS::PathCombine(home, ".gradle/jdks")); scanJavaDirs(FS::PathCombine(home, ".gradle/jdks"));
javas.append(getMinecraftJavaBundle()); javas.append(getMinecraftJavaBundle());
javas.append(getPrismJavaBundle());
javas = addJavasFromEnv(javas); javas = addJavasFromEnv(javas);
javas.removeDuplicates(); javas.removeDuplicates();
return javas; return javas;
@ -465,6 +470,8 @@ QList<QString> JavaUtils::FindJavaPaths()
javas.append(this->GetDefaultJava()->path); javas.append(this->GetDefaultJava()->path);
javas.append(getMinecraftJavaBundle()); javas.append(getMinecraftJavaBundle());
javas.append(getPrismJavaBundle());
javas.removeDuplicates();
return addJavasFromEnv(javas); return addJavasFromEnv(javas);
} }
#endif #endif
@ -476,12 +483,10 @@ QString JavaUtils::getJavaCheckPath()
QStringList getMinecraftJavaBundle() QStringList getMinecraftJavaBundle()
{ {
QString executable = "java";
QStringList processpaths; QStringList processpaths;
#if defined(Q_OS_OSX) #if defined(Q_OS_OSX)
processpaths << FS::PathCombine(QDir::homePath(), FS::PathCombine("Library", "Application Support", "minecraft", "runtime")); processpaths << FS::PathCombine(QDir::homePath(), FS::PathCombine("Library", "Application Support", "minecraft", "runtime"));
#elif defined(Q_OS_WIN32) #elif defined(Q_OS_WIN32)
executable += "w.exe";
auto appDataPath = QProcessEnvironment::systemEnvironment().value("APPDATA", ""); auto appDataPath = QProcessEnvironment::systemEnvironment().value("APPDATA", "");
processpaths << FS::PathCombine(QFileInfo(appDataPath).absoluteFilePath(), ".minecraft", "runtime"); processpaths << FS::PathCombine(QFileInfo(appDataPath).absoluteFilePath(), ".minecraft", "runtime");
@ -506,7 +511,7 @@ QStringList getMinecraftJavaBundle()
auto binFound = false; auto binFound = false;
for (auto& entry : entries) { for (auto& entry : entries) {
if (entry.baseName() == "bin") { if (entry.baseName() == "bin") {
javas.append(FS::PathCombine(entry.canonicalFilePath(), executable)); javas.append(FS::PathCombine(entry.canonicalFilePath(), JavaUtils::javaExecutable));
binFound = true; binFound = true;
break; break;
} }
@ -519,3 +524,33 @@ QStringList getMinecraftJavaBundle()
} }
return javas; return javas;
} }
#if defined(Q_OS_WIN32)
const QString JavaUtils::javaExecutable = "javaw.exe";
#else
const QString JavaUtils::javaExecutable = "java";
#endif
QStringList getPrismJavaBundle()
{
QList<QString> javas;
auto scanDir = [&](QString prefix) {
javas.append(FS::PathCombine(prefix, "jre", "bin", JavaUtils::javaExecutable));
javas.append(FS::PathCombine(prefix, "bin", JavaUtils::javaExecutable));
javas.append(FS::PathCombine(prefix, JavaUtils::javaExecutable));
};
auto scanJavaDir = [&](const QString& dirPath) {
QDir dir(dirPath);
if (!dir.exists())
return;
auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (auto& entry : entries) {
scanDir(entry.canonicalFilePath());
}
};
scanJavaDir(APPLICATION->javaPath());
return javas;
}

View File

@ -15,10 +15,9 @@
#pragma once #pragma once
#include <QProcess>
#include <QStringList> #include <QStringList>
#include "java/JavaInstall.h"
#include "JavaChecker.h"
#include "JavaInstallList.h"
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <windows.h> #include <windows.h>
@ -27,6 +26,7 @@
QString stripVariableEntries(QString name, QString target, QString remove); QString stripVariableEntries(QString name, QString target, QString remove);
QProcessEnvironment CleanEnviroment(); QProcessEnvironment CleanEnviroment();
QStringList getMinecraftJavaBundle(); QStringList getMinecraftJavaBundle();
QStringList getPrismJavaBundle();
class JavaUtils : public QObject { class JavaUtils : public QObject {
Q_OBJECT Q_OBJECT
@ -42,4 +42,5 @@ class JavaUtils : public QObject {
#endif #endif
static QString getJavaCheckPath(); static QString getJavaCheckPath();
static const QString javaExecutable;
}; };

View File

@ -43,12 +43,12 @@ QString JavaVersion::toString() const
return m_string; return m_string;
} }
bool JavaVersion::requiresPermGen() bool JavaVersion::requiresPermGen() const
{ {
return !m_parseable || m_major < 8; return !m_parseable || m_major < 8;
} }
bool JavaVersion::isModular() bool JavaVersion::isModular() const
{ {
return m_parseable && m_major >= 9; return m_parseable && m_major >= 9;
} }
@ -59,12 +59,6 @@ bool JavaVersion::operator<(const JavaVersion& rhs)
auto major = m_major; auto major = m_major;
auto rmajor = rhs.m_major; auto rmajor = rhs.m_major;
// HACK: discourage using java 9
if (major > 8)
major = -major;
if (rmajor > 8)
rmajor = -rmajor;
if (major < rmajor) if (major < rmajor)
return true; return true;
if (major > rmajor) if (major > rmajor)
@ -109,3 +103,24 @@ bool JavaVersion::operator>(const JavaVersion& rhs)
{ {
return (!operator<(rhs)) && (!operator==(rhs)); return (!operator<(rhs)) && (!operator==(rhs));
} }
JavaVersion::JavaVersion(int major, int minor, int security, int build, QString name)
: m_major(major), m_minor(minor), m_security(security), m_name(name), m_parseable(true)
{
QStringList versions;
if (build != 0) {
m_prerelease = QString::number(build);
versions.push_front(m_prerelease);
}
if (m_security != 0)
versions.push_front(QString::number(m_security));
else if (!versions.isEmpty())
versions.push_front("0");
if (m_minor != 0)
versions.push_front(QString::number(m_minor));
else if (!versions.isEmpty())
versions.push_front("0");
versions.push_front(QString::number(m_major));
m_string = versions.join(".");
}

View File

@ -16,6 +16,7 @@ class JavaVersion {
public: public:
JavaVersion() {} JavaVersion() {}
JavaVersion(const QString& rhs); JavaVersion(const QString& rhs);
JavaVersion(int major, int minor, int security, int build = 0, QString name = "");
JavaVersion& operator=(const QString& rhs); JavaVersion& operator=(const QString& rhs);
@ -23,21 +24,24 @@ class JavaVersion {
bool operator==(const JavaVersion& rhs); bool operator==(const JavaVersion& rhs);
bool operator>(const JavaVersion& rhs); bool operator>(const JavaVersion& rhs);
bool requiresPermGen(); bool requiresPermGen() const;
bool isModular(); bool isModular() const;
QString toString() const; QString toString() const;
int major() { return m_major; } int major() const { return m_major; }
int minor() { return m_minor; } int minor() const { return m_minor; }
int security() { return m_security; } int security() const { return m_security; }
QString build() const { return m_prerelease; }
QString name() const { return m_name; }
private: private:
QString m_string; QString m_string;
int m_major = 0; int m_major = 0;
int m_minor = 0; int m_minor = 0;
int m_security = 0; int m_security = 0;
QString m_name = "";
bool m_parseable = false; bool m_parseable = false;
QString m_prerelease; QString m_prerelease;
}; };

View File

@ -0,0 +1,141 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "java/download/ArchiveDownloadTask.h"
#include <quazip.h>
#include <memory>
#include "MMCZip.h"
#include "Application.h"
#include "Untar.h"
#include "net/ChecksumValidator.h"
#include "net/NetJob.h"
#include "tasks/Task.h"
namespace Java {
ArchiveDownloadTask::ArchiveDownloadTask(QUrl url, QString final_path, QString checksumType, QString checksumHash)
: m_url(url), m_final_path(final_path), m_checksum_type(checksumType), m_checksum_hash(checksumHash)
{}
void ArchiveDownloadTask::executeTask()
{
// JRE found ! download the zip
setStatus(tr("Downloading Java"));
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("java", m_url.fileName());
auto download = makeShared<NetJob>(QString("JRE::DownloadJava"), APPLICATION->network());
auto action = Net::Download::makeCached(m_url, entry);
if (!m_checksum_hash.isEmpty() && !m_checksum_type.isEmpty()) {
auto hashType = QCryptographicHash::Algorithm::Sha1;
if (m_checksum_type == "sha256") {
hashType = QCryptographicHash::Algorithm::Sha256;
}
action->addValidator(new Net::ChecksumValidator(hashType, QByteArray::fromHex(m_checksum_hash.toUtf8())));
}
download->addNetAction(action);
auto fullPath = entry->getFullPath();
connect(download.get(), &Task::failed, this, &ArchiveDownloadTask::emitFailed);
connect(download.get(), &Task::progress, this, &ArchiveDownloadTask::setProgress);
connect(download.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress);
connect(download.get(), &Task::status, this, &ArchiveDownloadTask::setStatus);
connect(download.get(), &Task::details, this, &ArchiveDownloadTask::setDetails);
connect(download.get(), &Task::succeeded, [this, fullPath] {
// This should do all of the extracting and creating folders
extractJava(fullPath);
});
m_task = download;
m_task->start();
}
void ArchiveDownloadTask::extractJava(QString input)
{
setStatus(tr("Extracting java"));
if (input.endsWith("tar")) {
setStatus(tr("Extracting Java (Progress is not reported for tar archives)"));
QFile in(input);
if (!in.open(QFile::ReadOnly)) {
emitFailed(tr("Unable to open supplied tar file."));
return;
}
if (!Tar::extract(&in, QDir(m_final_path).absolutePath())) {
emitFailed(tr("Unable to extract supplied tar file."));
return;
}
emitSucceeded();
return;
} else if (input.endsWith("tar.gz") || input.endsWith("taz") || input.endsWith("tgz")) {
setStatus(tr("Extracting Java (Progress is not reported for tar archives)"));
if (!GZTar::extract(input, QDir(m_final_path).absolutePath())) {
emitFailed(tr("Unable to extract supplied tar file."));
return;
}
emitSucceeded();
return;
} else if (input.endsWith("zip")) {
auto zip = std::make_shared<QuaZip>(input);
if (!zip->open(QuaZip::mdUnzip)) {
emitFailed(tr("Unable to open supplied zip file."));
return;
}
auto files = zip->getFileNameList();
if (files.isEmpty()) {
emitFailed(tr("No files were found in the supplied zip file,"));
return;
}
m_task = makeShared<MMCZip::ExtractZipTask>(zip, m_final_path, files[0]);
auto progressStep = std::make_shared<TaskStepProgress>();
connect(m_task.get(), &Task::finished, this, [this, progressStep] {
progressStep->state = TaskStepState::Succeeded;
stepProgress(*progressStep);
});
connect(m_task.get(), &Task::succeeded, this, &ArchiveDownloadTask::emitSucceeded);
connect(m_task.get(), &Task::aborted, this, &ArchiveDownloadTask::emitAborted);
connect(m_task.get(), &Task::failed, this, [this, progressStep](QString reason) {
progressStep->state = TaskStepState::Failed;
stepProgress(*progressStep);
emitFailed(reason);
});
connect(m_task.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress);
connect(m_task.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
progressStep->update(current, total);
stepProgress(*progressStep);
});
connect(m_task.get(), &Task::status, this, [this, progressStep](QString status) {
progressStep->status = status;
stepProgress(*progressStep);
});
m_task->start();
return;
}
emitFailed(tr("Could not determine archive type!"));
}
bool ArchiveDownloadTask::abort()
{
auto aborted = canAbort();
if (m_task)
aborted = m_task->abort();
emitAborted();
return aborted;
};
} // namespace Java

View File

@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QUrl>
#include "tasks/Task.h"
namespace Java {
class ArchiveDownloadTask : public Task {
Q_OBJECT
public:
ArchiveDownloadTask(QUrl url, QString final_path, QString checksumType = "", QString checksumHash = "");
virtual ~ArchiveDownloadTask() = default;
[[nodiscard]] bool canAbort() const override { return true; }
void executeTask() override;
virtual bool abort() override;
private slots:
void extractJava(QString input);
protected:
QUrl m_url;
QString m_final_path;
QString m_checksum_type;
QString m_checksum_hash;
Task::Ptr m_task;
};
} // namespace Java

View File

@ -0,0 +1,138 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "java/download/ManifestDownloadTask.h"
#include "Application.h"
#include "FileSystem.h"
#include "Json.h"
#include "net/ChecksumValidator.h"
#include "net/NetJob.h"
struct File {
QString path;
QString url;
QByteArray hash;
bool isExec;
};
namespace Java {
ManifestDownloadTask::ManifestDownloadTask(QUrl url, QString final_path, QString checksumType, QString checksumHash)
: m_url(url), m_final_path(final_path), m_checksum_type(checksumType), m_checksum_hash(checksumHash)
{}
void ManifestDownloadTask::executeTask()
{
setStatus(tr("Downloading Java"));
auto download = makeShared<NetJob>(QString("JRE::DownloadJava"), APPLICATION->network());
auto files = std::make_shared<QByteArray>();
auto action = Net::Download::makeByteArray(m_url, files);
if (!m_checksum_hash.isEmpty() && !m_checksum_type.isEmpty()) {
auto hashType = QCryptographicHash::Algorithm::Sha1;
if (m_checksum_type == "sha256") {
hashType = QCryptographicHash::Algorithm::Sha256;
}
action->addValidator(new Net::ChecksumValidator(hashType, QByteArray::fromHex(m_checksum_hash.toUtf8())));
}
download->addNetAction(action);
connect(download.get(), &Task::failed, this, &ManifestDownloadTask::emitFailed);
connect(download.get(), &Task::progress, this, &ManifestDownloadTask::setProgress);
connect(download.get(), &Task::stepProgress, this, &ManifestDownloadTask::propagateStepProgress);
connect(download.get(), &Task::status, this, &ManifestDownloadTask::setStatus);
connect(download.get(), &Task::details, this, &ManifestDownloadTask::setDetails);
connect(download.get(), &Task::succeeded, [files, this] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response at " << parse_error.offset << ". Reason: " << parse_error.errorString();
qWarning() << *files;
emitFailed(parse_error.errorString());
return;
}
downloadJava(doc);
});
m_task = download;
m_task->start();
}
void ManifestDownloadTask::downloadJava(const QJsonDocument& doc)
{
// valid json doc, begin making jre spot
FS::ensureFolderPathExists(m_final_path);
std::vector<File> toDownload;
auto list = Json::ensureObject(Json::ensureObject(doc.object()), "files");
for (const auto& paths : list.keys()) {
auto file = FS::PathCombine(m_final_path, paths);
const QJsonObject& meta = Json::ensureObject(list, paths);
auto type = Json::ensureString(meta, "type");
if (type == "directory") {
FS::ensureFolderPathExists(file);
} else if (type == "link") {
// this is linux only !
auto path = Json::ensureString(meta, "target");
if (!path.isEmpty()) {
auto target = FS::PathCombine(file, "../" + path);
QFile(target).link(file);
}
} else if (type == "file") {
// TODO download compressed version if it exists ?
auto raw = Json::ensureObject(Json::ensureObject(meta, "downloads"), "raw");
auto isExec = Json::ensureBoolean(meta, "executable", false);
auto url = Json::ensureString(raw, "url");
if (!url.isEmpty() && QUrl(url).isValid()) {
auto f = File{ file, url, QByteArray::fromHex(Json::ensureString(raw, "sha1").toLatin1()), isExec };
toDownload.push_back(f);
}
}
}
auto elementDownload = makeShared<NetJob>("JRE::FileDownload", APPLICATION->network());
for (const auto& file : toDownload) {
auto dl = Net::Download::makeFile(file.url, file.path);
if (!file.hash.isEmpty()) {
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.hash));
}
if (file.isExec) {
connect(dl.get(), &Net::Download::succeeded,
[file] { QFile(file.path).setPermissions(QFile(file.path).permissions() | QFileDevice::Permissions(0x1111)); });
}
elementDownload->addNetAction(dl);
}
connect(elementDownload.get(), &Task::failed, this, &ManifestDownloadTask::emitFailed);
connect(elementDownload.get(), &Task::progress, this, &ManifestDownloadTask::setProgress);
connect(elementDownload.get(), &Task::stepProgress, this, &ManifestDownloadTask::propagateStepProgress);
connect(elementDownload.get(), &Task::status, this, &ManifestDownloadTask::setStatus);
connect(elementDownload.get(), &Task::details, this, &ManifestDownloadTask::setDetails);
connect(elementDownload.get(), &Task::succeeded, this, &ManifestDownloadTask::emitSucceeded);
m_task = elementDownload;
m_task->start();
}
bool ManifestDownloadTask::abort()
{
auto aborted = canAbort();
if (m_task)
aborted = m_task->abort();
emitAborted();
return aborted;
};
} // namespace Java

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QUrl>
#include "tasks/Task.h"
namespace Java {
class ManifestDownloadTask : public Task {
Q_OBJECT
public:
ManifestDownloadTask(QUrl url, QString final_path, QString checksumType = "", QString checksumHash = "");
virtual ~ManifestDownloadTask() = default;
[[nodiscard]] bool canAbort() const override { return true; }
void executeTask() override;
virtual bool abort() override;
private slots:
void downloadJava(const QJsonDocument& doc);
protected:
QUrl m_url;
QString m_final_path;
QString m_checksum_type;
QString m_checksum_hash;
Task::Ptr m_task;
};
} // namespace Java

View File

@ -37,6 +37,7 @@
#include <FileSystem.h> #include <FileSystem.h>
#include <launch/LaunchTask.h> #include <launch/LaunchTask.h>
#include <sys.h> #include <sys.h>
#include <QCryptographicHash>
#include <QFileInfo> #include <QFileInfo>
#include <QStandardPaths> #include <QStandardPaths>
#include "java/JavaUtils.h" #include "java/JavaUtils.h"
@ -45,20 +46,23 @@ void CheckJava::executeTask()
{ {
auto instance = m_parent->instance(); auto instance = m_parent->instance();
auto settings = instance->settings(); auto settings = instance->settings();
m_javaPath = FS::ResolveExecutable(settings->get("JavaPath").toString());
QString javaPathSetting = settings->get("JavaPath").toString();
m_javaPath = FS::ResolveExecutable(javaPathSetting);
bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool(); bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool();
auto realJavaPath = QStandardPaths::findExecutable(m_javaPath); auto realJavaPath = QStandardPaths::findExecutable(m_javaPath);
if (realJavaPath.isEmpty()) { if (realJavaPath.isEmpty()) {
if (perInstance) { if (perInstance) {
emit logLine(QString("The java binary \"%1\" couldn't be found. Please fix the java path " emit logLine(QString("The Java binary \"%1\" couldn't be found. Please fix the Java path "
"override in the instance's settings or disable it.") "override in the instance's settings or disable it.")
.arg(m_javaPath), .arg(javaPathSetting),
MessageLevel::Warning); MessageLevel::Warning);
} else { } else {
emit logLine(QString("The java binary \"%1\" couldn't be found. Please set up java in " emit logLine(QString("The Java binary \"%1\" couldn't be found. Please set up Java in "
"the settings.") "the settings.")
.arg(m_javaPath), .arg(javaPathSetting),
MessageLevel::Warning); MessageLevel::Warning);
} }
emitFailed(QString("Java path is not valid.")); emitFailed(QString("Java path is not valid."));
@ -90,11 +94,10 @@ void CheckJava::executeTask()
// if timestamps are not the same, or something is missing, check! // if timestamps are not the same, or something is missing, check!
if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 || if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 ||
storedRealArchitecture.size() == 0 || storedVendor.size() == 0) { storedRealArchitecture.size() == 0 || storedVendor.size() == 0) {
m_JavaChecker.reset(new JavaChecker); m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0, this));
emit logLine(QString("Checking Java version..."), MessageLevel::Launcher); emit logLine(QString("Checking Java version..."), MessageLevel::Launcher);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished); connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
m_JavaChecker->m_path = realJavaPath; m_JavaChecker->start();
m_JavaChecker->performCheck();
return; return;
} else { } else {
auto verString = instance->settings()->get("JavaVersion").toString(); auto verString = instance->settings()->get("JavaVersion").toString();
@ -106,10 +109,10 @@ void CheckJava::executeTask()
emitSucceeded(); emitSucceeded();
} }
void CheckJava::checkJavaFinished(JavaCheckResult result) void CheckJava::checkJavaFinished(const JavaChecker::Result& result)
{ {
switch (result.validity) { switch (result.validity) {
case JavaCheckResult::Validity::Errored: { case JavaChecker::Result::Validity::Errored: {
// Error message displayed if java can't start // Error message displayed if java can't start
emit logLine(QString("Could not start java:"), MessageLevel::Error); emit logLine(QString("Could not start java:"), MessageLevel::Error);
emit logLines(result.errorLog.split('\n'), MessageLevel::Error); emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
@ -117,14 +120,14 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
emitFailed(QString("Could not start java!")); emitFailed(QString("Could not start java!"));
return; return;
} }
case JavaCheckResult::Validity::ReturnedInvalidData: { case JavaChecker::Result::Validity::ReturnedInvalidData: {
emit logLine(QString("Java checker returned some invalid data we don't understand:"), MessageLevel::Error); emit logLine(QString("Java checker returned some invalid data we don't understand:"), MessageLevel::Error);
emit logLines(result.outLog.split('\n'), MessageLevel::Warning); emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher); emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher);
emitSucceeded(); emitSucceeded();
return; return;
} }
case JavaCheckResult::Validity::Valid: { case JavaChecker::Result::Validity::Valid: {
auto instance = m_parent->instance(); auto instance = m_parent->instance();
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor); printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor);
instance->settings()->set("JavaVersion", result.javaVersion.toString()); instance->settings()->set("JavaVersion", result.javaVersion.toString());

View File

@ -28,7 +28,7 @@ class CheckJava : public LaunchStep {
virtual void executeTask(); virtual void executeTask();
virtual bool canAbort() const { return false; } virtual bool canAbort() const { return false; }
private slots: private slots:
void checkJavaFinished(JavaCheckResult result); void checkJavaFinished(const JavaChecker::Result& result);
private: private:
void printJavaInfo(const QString& version, const QString& architecture, const QString& realArchitecture, const QString& vendor); void printJavaInfo(const QString& version, const QString& architecture, const QString& realArchitecture, const QString& vendor);
@ -37,5 +37,5 @@ class CheckJava : public LaunchStep {
private: private:
QString m_javaPath; QString m_javaPath;
QString m_javaSignature; QString m_javaSignature;
JavaCheckerPtr m_JavaChecker; JavaChecker::Ptr m_JavaChecker;
}; };

View File

@ -30,7 +30,7 @@ void LookupServerAddress::setLookupAddress(const QString& lookupAddress)
m_dnsLookup->setName(QString("_minecraft._tcp.%1").arg(lookupAddress)); m_dnsLookup->setName(QString("_minecraft._tcp.%1").arg(lookupAddress));
} }
void LookupServerAddress::setOutputAddressPtr(MinecraftServerTargetPtr output) void LookupServerAddress::setOutputAddressPtr(MinecraftTarget::Ptr output)
{ {
m_output = std::move(output); m_output = std::move(output);
} }

View File

@ -19,20 +19,20 @@
#include <launch/LaunchStep.h> #include <launch/LaunchStep.h>
#include <QDnsLookup> #include <QDnsLookup>
#include "minecraft/launch/MinecraftServerTarget.h" #include "minecraft/launch/MinecraftTarget.h"
class LookupServerAddress : public LaunchStep { class LookupServerAddress : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit LookupServerAddress(LaunchTask* parent); explicit LookupServerAddress(LaunchTask* parent);
virtual ~LookupServerAddress() {}; virtual ~LookupServerAddress() = default;
virtual void executeTask(); virtual void executeTask();
virtual bool abort(); virtual bool abort();
virtual bool canAbort() const { return true; } virtual bool canAbort() const { return true; }
void setLookupAddress(const QString& lookupAddress); void setLookupAddress(const QString& lookupAddress);
void setOutputAddressPtr(MinecraftServerTargetPtr output); void setOutputAddressPtr(MinecraftTarget::Ptr output);
private slots: private slots:
void on_dnsLookupFinished(); void on_dnsLookupFinished();
@ -42,5 +42,5 @@ class LookupServerAddress : public LaunchStep {
QDnsLookup* m_dnsLookup; QDnsLookup* m_dnsLookup;
QString m_lookupAddress; QString m_lookupAddress;
MinecraftServerTargetPtr m_output; MinecraftTarget::Ptr m_output;
}; };

View File

@ -15,19 +15,26 @@
#include "BaseEntity.h" #include "BaseEntity.h"
#include "Exception.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "Json.h" #include "Json.h"
#include "modplatform/helpers/HashUtils.h"
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
#include "net/ChecksumValidator.h"
#include "net/HttpMetaCache.h" #include "net/HttpMetaCache.h"
#include "net/Mode.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "Application.h" #include "Application.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "tasks/Task.h"
namespace Meta {
class ParsingValidator : public Net::Validator { class ParsingValidator : public Net::Validator {
public: /* con/des */ public: /* con/des */
ParsingValidator(Meta::BaseEntity* entity) : m_entity(entity) {}; ParsingValidator(BaseEntity* entity) : m_entity(entity) {};
virtual ~ParsingValidator() {}; virtual ~ParsingValidator() = default;
public: /* methods */ public: /* methods */
bool init(QNetworkRequest&) override bool init(QNetworkRequest&) override
@ -61,92 +68,131 @@ class ParsingValidator : public Net::Validator {
private: /* data */ private: /* data */
QByteArray m_data; QByteArray m_data;
Meta::BaseEntity* m_entity; BaseEntity* m_entity;
}; };
Meta::BaseEntity::~BaseEntity() {} QUrl BaseEntity::url() const
QUrl Meta::BaseEntity::url() const
{ {
auto s = APPLICATION->settings(); auto s = APPLICATION->settings();
QString metaOverride = s->get("MetaURLOverride").toString(); QString metaOverride = s->get("MetaURLOverride").toString();
if (metaOverride.isEmpty()) { if (metaOverride.isEmpty()) {
return QUrl(BuildConfig.META_URL).resolved(localFilename()); return QUrl(BuildConfig.META_URL).resolved(localFilename());
} else {
return QUrl(metaOverride).resolved(localFilename());
} }
return QUrl(metaOverride).resolved(localFilename());
} }
bool Meta::BaseEntity::loadLocalFile() Task::Ptr BaseEntity::loadTask(Net::Mode mode)
{ {
const QString fname = QDir("meta").absoluteFilePath(localFilename()); if (m_task && m_task->isRunning()) {
if (!QFile::exists(fname)) { return m_task;
return false;
} }
// TODO: check if the file has the expected checksum m_task.reset(new BaseEntityLoadTask(this, mode));
return m_task;
}
bool BaseEntity::isLoaded() const
{
// consider it loaded only if the main hash is either empty and was remote loadded or the hashes match and was loaded
return m_sha256.isEmpty() ? m_load_status == LoadStatus::Remote : m_load_status != LoadStatus::NotLoaded && m_sha256 == m_file_sha256;
}
void BaseEntity::setSha256(QString sha256)
{
m_sha256 = sha256;
}
BaseEntity::LoadStatus BaseEntity::status() const
{
return m_load_status;
}
BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode) : m_entity(parent), m_mode(mode) {}
void BaseEntityLoadTask::executeTask()
{
const QString fname = QDir("meta").absoluteFilePath(m_entity->localFilename());
auto hashMatches = false;
// the file exists on disk try to load it
if (QFile::exists(fname)) {
try { try {
auto doc = Json::requireDocument(fname, fname); QByteArray fileData;
// read local file if nothing is loaded yet
if (m_entity->m_load_status == BaseEntity::LoadStatus::NotLoaded || m_entity->m_file_sha256.isEmpty()) {
setStatus(tr("Loading local file"));
fileData = FS::read(fname);
m_entity->m_file_sha256 = Hashing::hash(fileData, Hashing::Algorithm::Sha256);
}
// on online the hash needs to match
hashMatches = m_entity->m_sha256 == m_entity->m_file_sha256;
if (m_mode == Net::Mode::Online && !m_entity->m_sha256.isEmpty() && !hashMatches) {
throw Exception("mismatched checksum");
}
// load local file
if (m_entity->m_load_status == BaseEntity::LoadStatus::NotLoaded) {
auto doc = Json::requireDocument(fileData, fname);
auto obj = Json::requireObject(doc, fname); auto obj = Json::requireObject(doc, fname);
parse(obj); m_entity->parse(obj);
return true; m_entity->m_load_status = BaseEntity::LoadStatus::Local;
}
} catch (const Exception& e) { } catch (const Exception& e) {
qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause()); qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause());
// just make sure it's gone and we never consider it again. // just make sure it's gone and we never consider it again.
return !FS::deletePath(fname); FS::deletePath(fname);
} m_entity->m_load_status = BaseEntity::LoadStatus::NotLoaded;
}
void Meta::BaseEntity::load(Net::Mode loadType)
{
// load local file if nothing is loaded yet
if (!isLoaded()) {
if (loadLocalFile()) {
m_loadStatus = LoadStatus::Local;
} }
} }
// if we need remote update, run the update task // if we need remote update, run the update task
if (loadType == Net::Mode::Offline || !shouldStartRemoteUpdate()) { auto wasLoadedOffline = m_entity->m_load_status != BaseEntity::LoadStatus::NotLoaded && m_mode == Net::Mode::Offline;
// if has is not present allways fetch from remote(e.g. the main index file), else only fetch if hash doesn't match
auto wasLoadedRemote = m_entity->m_sha256.isEmpty() ? m_entity->m_load_status == BaseEntity::LoadStatus::Remote : hashMatches;
if (wasLoadedOffline || wasLoadedRemote) {
emitSucceeded();
return; return;
} }
m_updateTask.reset(new NetJob(QObject::tr("Download of meta file %1").arg(localFilename()), APPLICATION->network())); m_task.reset(new NetJob(QObject::tr("Download of meta file %1").arg(m_entity->localFilename()), APPLICATION->network()));
auto url = this->url(); auto url = m_entity->url();
auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename()); auto entry = APPLICATION->metacache()->resolveEntry("meta", m_entity->localFilename());
entry->setStale(true); entry->setStale(true);
auto dl = Net::ApiDownload::makeCached(url, entry); auto dl = Net::ApiDownload::makeCached(url, entry);
/* /*
* The validator parses the file and loads it into the object. * The validator parses the file and loads it into the object.
* If that fails, the file is not written to storage. * If that fails, the file is not written to storage.
*/ */
dl->addValidator(new ParsingValidator(this)); if (!m_entity->m_sha256.isEmpty())
m_updateTask->addNetAction(dl); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Algorithm::Sha256, m_entity->m_sha256));
m_updateStatus = UpdateStatus::InProgress; dl->addValidator(new ParsingValidator(m_entity));
QObject::connect(m_updateTask.get(), &NetJob::succeeded, [&]() { m_task->addNetAction(dl);
m_loadStatus = LoadStatus::Remote; m_task->setAskRetry(false);
m_updateStatus = UpdateStatus::Succeeded; connect(m_task.get(), &Task::failed, this, &BaseEntityLoadTask::emitFailed);
m_updateTask.reset(); connect(m_task.get(), &Task::succeeded, this, &BaseEntityLoadTask::emitSucceeded);
connect(m_task.get(), &Task::succeeded, this, [this]() {
m_entity->m_load_status = BaseEntity::LoadStatus::Remote;
m_entity->m_file_sha256 = m_entity->m_sha256;
}); });
QObject::connect(m_updateTask.get(), &NetJob::failed, [&]() {
m_updateStatus = UpdateStatus::Failed; connect(m_task.get(), &Task::progress, this, &Task::setProgress);
m_updateTask.reset(); connect(m_task.get(), &Task::stepProgress, this, &BaseEntityLoadTask::propagateStepProgress);
}); connect(m_task.get(), &Task::status, this, &Task::setStatus);
m_updateTask->start(); connect(m_task.get(), &Task::details, this, &Task::setDetails);
m_task->start();
} }
bool Meta::BaseEntity::isLoaded() const bool BaseEntityLoadTask::canAbort() const
{ {
return m_loadStatus > LoadStatus::NotLoaded; return m_task ? m_task->canAbort() : false;
} }
bool Meta::BaseEntity::shouldStartRemoteUpdate() const bool BaseEntityLoadTask::abort()
{ {
// TODO: version-locks and offline mode? if (m_task) {
return m_updateStatus != UpdateStatus::InProgress; Task::abort();
} return m_task->abort();
Task::Ptr Meta::BaseEntity::getCurrentTask()
{
if (m_updateStatus == UpdateStatus::InProgress) {
return m_updateTask;
} }
return nullptr; return Task::abort();
} }
} // namespace Meta

View File

@ -17,38 +17,57 @@
#include <QJsonObject> #include <QJsonObject>
#include <QObject> #include <QObject>
#include "QObjectPtr.h"
#include "net/Mode.h" #include "net/Mode.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "tasks/Task.h"
namespace Meta { namespace Meta {
class BaseEntityLoadTask;
class BaseEntity { class BaseEntity {
friend BaseEntityLoadTask;
public: /* types */ public: /* types */
using Ptr = std::shared_ptr<BaseEntity>; using Ptr = std::shared_ptr<BaseEntity>;
enum class LoadStatus { NotLoaded, Local, Remote }; enum class LoadStatus { NotLoaded, Local, Remote };
enum class UpdateStatus { NotDone, InProgress, Failed, Succeeded };
public: public:
virtual ~BaseEntity(); virtual ~BaseEntity() = default;
virtual void parse(const QJsonObject& obj) = 0;
virtual QString localFilename() const = 0; virtual QString localFilename() const = 0;
virtual QUrl url() const; virtual QUrl url() const;
bool isLoaded() const; bool isLoaded() const;
bool shouldStartRemoteUpdate() const; LoadStatus status() const;
void load(Net::Mode loadType); /* for parsers */
Task::Ptr getCurrentTask(); void setSha256(QString sha256);
protected: /* methods */ virtual void parse(const QJsonObject& obj) = 0;
bool loadLocalFile(); [[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online);
protected:
QString m_sha256; // the expected sha256
QString m_file_sha256; // the file sha256
private: private:
LoadStatus m_loadStatus = LoadStatus::NotLoaded; LoadStatus m_load_status = LoadStatus::NotLoaded;
UpdateStatus m_updateStatus = UpdateStatus::NotDone; Task::Ptr m_task;
NetJob::Ptr m_updateTask; };
class BaseEntityLoadTask : public Task {
Q_OBJECT
public:
explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode);
~BaseEntityLoadTask() override = default;
virtual void executeTask() override;
virtual bool canAbort() const override;
virtual bool abort() override;
private:
BaseEntity* m_entity;
Net::Mode m_mode;
NetJob::Ptr m_task;
}; };
} // namespace Meta } // namespace Meta

View File

@ -16,7 +16,10 @@
#include "Index.h" #include "Index.h"
#include "JsonFormat.h" #include "JsonFormat.h"
#include "QObjectPtr.h"
#include "VersionList.h" #include "VersionList.h"
#include "meta/BaseEntity.h"
#include "tasks/SequentialTask.h"
namespace Meta { namespace Meta {
Index::Index(QObject* parent) : QAbstractListModel(parent) {} Index::Index(QObject* parent) : QAbstractListModel(parent) {}
@ -51,14 +54,17 @@ QVariant Index::data(const QModelIndex& index, int role) const
} }
return QVariant(); return QVariant();
} }
int Index::rowCount(const QModelIndex& parent) const int Index::rowCount(const QModelIndex& parent) const
{ {
return parent.isValid() ? 0 : m_lists.size(); return parent.isValid() ? 0 : m_lists.size();
} }
int Index::columnCount(const QModelIndex& parent) const int Index::columnCount(const QModelIndex& parent) const
{ {
return parent.isValid() ? 0 : 1; return parent.isValid() ? 0 : 1;
} }
QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
{ {
if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) { if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) {
@ -79,6 +85,7 @@ VersionList::Ptr Index::get(const QString& uid)
if (!out) { if (!out) {
out = std::make_shared<VersionList>(uid); out = std::make_shared<VersionList>(uid);
m_uids[uid] = out; m_uids[uid] = out;
m_lists.append(out);
} }
return out; return out;
} }
@ -96,7 +103,7 @@ void Index::parse(const QJsonObject& obj)
void Index::merge(const std::shared_ptr<Index>& other) void Index::merge(const std::shared_ptr<Index>& other)
{ {
const QVector<VersionList::Ptr> lists = std::dynamic_pointer_cast<Index>(other)->m_lists; const QVector<VersionList::Ptr> lists = other->m_lists;
// initial load, no need to merge // initial load, no need to merge
if (m_lists.isEmpty()) { if (m_lists.isEmpty()) {
beginResetModel(); beginResetModel();
@ -123,7 +130,33 @@ void Index::merge(const std::shared_ptr<Index>& other)
void Index::connectVersionList(const int row, const VersionList::Ptr& list) void Index::connectVersionList(const int row, const VersionList::Ptr& list)
{ {
connect(list.get(), &VersionList::nameChanged, this, connect(list.get(), &VersionList::nameChanged, this, [this, row] { emit dataChanged(index(row), index(row), { Qt::DisplayRole }); });
[this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << Qt::DisplayRole); }); }
Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mode mode, bool force)
{
if (mode == Net::Mode::Offline) {
return get(uid, version)->loadTask(mode);
}
auto versionList = get(uid);
auto loadTask = makeShared<SequentialTask>(
this, tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version));
if (status() != BaseEntity::LoadStatus::Remote || force) {
loadTask->addTask(this->loadTask(mode));
}
loadTask->addTask(versionList->loadTask(mode));
loadTask->addTask(versionList->getVersion(version)->loadTask(mode));
return loadTask;
}
Version::Ptr Index::getLoadedVersion(const QString& uid, const QString& version)
{
QEventLoop ev;
auto task = loadVersion(uid, version);
QObject::connect(task.get(), &Task::finished, &ev, &QEventLoop::quit);
task->start();
ev.exec();
return get(uid, version);
} }
} // namespace Meta } // namespace Meta

View File

@ -16,10 +16,10 @@
#pragma once #pragma once
#include <QAbstractListModel> #include <QAbstractListModel>
#include <memory>
#include "BaseEntity.h" #include "BaseEntity.h"
#include "meta/VersionList.h" #include "meta/VersionList.h"
#include "net/Mode.h"
class Task; class Task;
@ -30,6 +30,7 @@ class Index : public QAbstractListModel, public BaseEntity {
public: public:
explicit Index(QObject* parent = nullptr); explicit Index(QObject* parent = nullptr);
explicit Index(const QVector<VersionList::Ptr>& lists, QObject* parent = nullptr); explicit Index(const QVector<VersionList::Ptr>& lists, QObject* parent = nullptr);
virtual ~Index() = default;
enum { UidRole = Qt::UserRole, NameRole, ListPtrRole }; enum { UidRole = Qt::UserRole, NameRole, ListPtrRole };
@ -47,8 +48,15 @@ class Index : public QAbstractListModel, public BaseEntity {
QVector<VersionList::Ptr> lists() const { return m_lists; } QVector<VersionList::Ptr> lists() const { return m_lists; }
Task::Ptr loadVersion(const QString& uid, const QString& version = {}, Net::Mode mode = Net::Mode::Online, bool force = false);
// this blocks until the version is loaded
Version::Ptr getLoadedVersion(const QString& uid, const QString& version);
public: // for usage by parsers only public: // for usage by parsers only
void merge(const std::shared_ptr<Index>& other); void merge(const std::shared_ptr<Index>& other);
protected:
void parse(const QJsonObject& obj) override; void parse(const QJsonObject& obj) override;
private: private:

View File

@ -41,6 +41,7 @@ static std::shared_ptr<Index> parseIndexInternal(const QJsonObject& obj)
std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject& obj) { std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject& obj) {
VersionList::Ptr list = std::make_shared<VersionList>(requireString(obj, "uid")); VersionList::Ptr list = std::make_shared<VersionList>(requireString(obj, "uid"));
list->setName(ensureString(obj, "name", QString())); list->setName(ensureString(obj, "name", QString()));
list->setSha256(ensureString(obj, "sha256", QString()));
return list; return list;
}); });
return std::make_shared<Index>(lists); return std::make_shared<Index>(lists);
@ -58,6 +59,9 @@ static Version::Ptr parseCommonVersion(const QString& uid, const QJsonObject& ob
parseRequires(obj, &reqs, "requires"); parseRequires(obj, &reqs, "requires");
parseRequires(obj, &conflicts, "conflicts"); parseRequires(obj, &conflicts, "conflicts");
version->setRequires(reqs, conflicts); version->setRequires(reqs, conflicts);
if (auto sha256 = ensureString(obj, "sha256", QString()); !sha256.isEmpty()) {
version->setSha256(sha256);
}
return version; return version;
} }

View File

@ -16,11 +16,9 @@
#pragma once #pragma once
#include <QJsonObject> #include <QJsonObject>
#include <memory>
#include <set> #include <set>
#include "Exception.h" #include "Exception.h"
#include "meta/BaseEntity.h"
namespace Meta { namespace Meta {
class Index; class Index;

View File

@ -18,12 +18,9 @@
#include <QDateTime> #include <QDateTime>
#include "JsonFormat.h" #include "JsonFormat.h"
#include "minecraft/PackProfile.h"
Meta::Version::Version(const QString& uid, const QString& version) : BaseVersion(), m_uid(uid), m_version(version) {} Meta::Version::Version(const QString& uid, const QString& version) : BaseVersion(), m_uid(uid), m_version(version) {}
Meta::Version::~Version() {}
QString Meta::Version::descriptor() QString Meta::Version::descriptor()
{ {
return m_version; return m_version;
@ -71,6 +68,9 @@ void Meta::Version::mergeFromList(const Meta::Version::Ptr& other)
if (m_volatile != other->m_volatile) { if (m_volatile != other->m_volatile) {
setVolatile(other->m_volatile); setVolatile(other->m_volatile);
} }
if (!other->m_sha256.isEmpty()) {
m_sha256 = other->m_sha256;
}
} }
void Meta::Version::merge(const Version::Ptr& other) void Meta::Version::merge(const Version::Ptr& other)

View File

@ -38,7 +38,7 @@ class Version : public QObject, public BaseVersion, public BaseEntity {
using Ptr = std::shared_ptr<Version>; using Ptr = std::shared_ptr<Version>;
explicit Version(const QString& uid, const QString& version); explicit Version(const QString& uid, const QString& version);
virtual ~Version(); virtual ~Version() = default;
QString descriptor() override; QString descriptor() override;
QString name() override; QString name() override;
@ -52,7 +52,7 @@ class Version : public QObject, public BaseVersion, public BaseEntity {
const Meta::RequireSet& requiredSet() const { return m_requires; } const Meta::RequireSet& requiredSet() const { return m_requires; }
VersionFilePtr data() const { return m_data; } VersionFilePtr data() const { return m_data; }
bool isRecommended() const { return m_recommended; } bool isRecommended() const { return m_recommended; }
bool isLoaded() const { return m_data != nullptr; } bool isLoaded() const { return m_data != nullptr && BaseEntity::isLoaded(); }
void merge(const Version::Ptr& other); void merge(const Version::Ptr& other);
void mergeFromList(const Version::Ptr& other); void mergeFromList(const Version::Ptr& other);

View File

@ -17,8 +17,13 @@
#include <QDateTime> #include <QDateTime>
#include "Application.h"
#include "Index.h"
#include "JsonFormat.h" #include "JsonFormat.h"
#include "Version.h" #include "Version.h"
#include "meta/BaseEntity.h"
#include "net/Mode.h"
#include "tasks/SequentialTask.h"
namespace Meta { namespace Meta {
VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList(parent), m_uid(uid) VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList(parent), m_uid(uid)
@ -28,8 +33,11 @@ VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList(
Task::Ptr VersionList::getLoadTask() Task::Ptr VersionList::getLoadTask()
{ {
load(Net::Mode::Online); auto loadTask =
return getCurrentTask(); makeShared<SequentialTask>(this, tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online));
loadTask->addTask(this->loadTask(Net::Mode::Online));
return loadTask;
} }
bool VersionList::isLoaded() bool VersionList::isLoaded()
@ -92,6 +100,13 @@ QVariant VersionList::data(const QModelIndex& index, int role) const
return QVariant::fromValue(version); return QVariant::fromValue(version);
case RecommendedRole: case RecommendedRole:
return version->isRecommended(); return version->isRecommended();
case JavaMajorRole: {
auto major = version->version();
if (major.startsWith("java")) {
major = "Java " + major.mid(4);
}
return major;
}
// FIXME: this should be determined in whatever view/proxy is used... // FIXME: this should be determined in whatever view/proxy is used...
// case LatestRole: return version == getLatestStable(); // case LatestRole: return version == getLatestStable();
default: default:
@ -101,10 +116,14 @@ QVariant VersionList::data(const QModelIndex& index, int role) const
BaseVersionList::RoleList VersionList::providesRoles() const BaseVersionList::RoleList VersionList::providesRoles() const
{ {
return { VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole, TypeRole, UidRole, return m_provided_roles;
TimeRole, RequiresRole, SortRole, RecommendedRole, LatestRole, VersionPtrRole };
} }
void VersionList::setProvidedRoles(RoleList roles)
{
m_provided_roles = roles;
};
QHash<int, QByteArray> VersionList::roleNames() const QHash<int, QByteArray> VersionList::roleNames() const
{ {
QHash<int, QByteArray> roles = BaseVersionList::roleNames(); QHash<int, QByteArray> roles = BaseVersionList::roleNames();
@ -131,6 +150,8 @@ Version::Ptr VersionList::getVersion(const QString& version)
if (!out) { if (!out) {
out = std::make_shared<Version>(m_uid, version); out = std::make_shared<Version>(m_uid, version);
m_lookup[version] = out; m_lookup[version] = out;
setupAddedVersion(m_versions.size(), out);
m_versions.append(out);
} }
return out; return out;
} }
@ -191,6 +212,9 @@ void VersionList::mergeFromIndex(const VersionList::Ptr& other)
if (m_name != other->m_name) { if (m_name != other->m_name) {
setName(other->m_name); setName(other->m_name);
} }
if (!other->m_sha256.isEmpty()) {
m_sha256 = other->m_sha256;
}
} }
void VersionList::merge(const VersionList::Ptr& other) void VersionList::merge(const VersionList::Ptr& other)
@ -198,23 +222,27 @@ void VersionList::merge(const VersionList::Ptr& other)
if (m_name != other->m_name) { if (m_name != other->m_name) {
setName(other->m_name); setName(other->m_name);
} }
if (!other->m_sha256.isEmpty()) {
m_sha256 = other->m_sha256;
}
// TODO: do not reset the whole model. maybe? // TODO: do not reset the whole model. maybe?
beginResetModel(); beginResetModel();
m_versions.clear();
if (other->m_versions.isEmpty()) { if (other->m_versions.isEmpty()) {
qWarning() << "Empty list loaded ..."; qWarning() << "Empty list loaded ...";
} }
for (const Version::Ptr& version : other->m_versions) { for (auto version : other->m_versions) {
// we already have the version. merge the contents // we already have the version. merge the contents
if (m_lookup.contains(version->version())) { if (m_lookup.contains(version->version())) {
m_lookup.value(version->version())->mergeFromList(version); auto existing = m_lookup.value(version->version());
existing->mergeFromList(version);
version = existing;
} else { } else {
m_lookup.insert(version->uid(), version); m_lookup.insert(version->version(), version);
}
// connect it. // connect it.
setupAddedVersion(m_versions.size(), version); setupAddedVersion(m_versions.size(), version);
m_versions.append(version); m_versions.append(version);
}
m_recommended = getBetterVersion(m_recommended, version); m_recommended = getBetterVersion(m_recommended, version);
} }
endResetModel(); endResetModel();
@ -222,14 +250,15 @@ void VersionList::merge(const VersionList::Ptr& other)
void VersionList::setupAddedVersion(const int row, const Version::Ptr& version) void VersionList::setupAddedVersion(const int row, const Version::Ptr& version)
{ {
// FIXME: do not disconnect from everythin, disconnect only the lambdas here disconnect(version.get(), &Version::requiresChanged, this, nullptr);
version->disconnect(); disconnect(version.get(), &Version::timeChanged, this, nullptr);
disconnect(version.get(), &Version::typeChanged, this, nullptr);
connect(version.get(), &Version::requiresChanged, this, connect(version.get(), &Version::requiresChanged, this,
[this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << RequiresRole); }); [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << RequiresRole); });
connect(version.get(), &Version::timeChanged, this, connect(version.get(), &Version::timeChanged, this,
[this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TimeRole << SortRole); }); [this, row]() { emit dataChanged(index(row), index(row), { TimeRole, SortRole }); });
connect(version.get(), &Version::typeChanged, this, connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), { TypeRole }); });
[this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TypeRole); });
} }
BaseVersion::Ptr VersionList::getRecommended() const BaseVersion::Ptr VersionList::getRecommended() const
@ -237,4 +266,14 @@ BaseVersion::Ptr VersionList::getRecommended() const
return m_recommended; return m_recommended;
} }
void VersionList::waitToLoad()
{
if (isLoaded())
return;
QEventLoop ev;
auto task = getLoadTask();
QObject::connect(task.get(), &Task::finished, &ev, &QEventLoop::quit);
task->start();
ev.exec();
}
} // namespace Meta } // namespace Meta

View File

@ -30,13 +30,14 @@ class VersionList : public BaseVersionList, public BaseEntity {
Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged)
public: public:
explicit VersionList(const QString& uid, QObject* parent = nullptr); explicit VersionList(const QString& uid, QObject* parent = nullptr);
virtual ~VersionList() = default;
using Ptr = std::shared_ptr<VersionList>; using Ptr = std::shared_ptr<VersionList>;
enum Roles { UidRole = Qt::UserRole + 100, TimeRole, RequiresRole, VersionPtrRole }; enum Roles { UidRole = Qt::UserRole + 100, TimeRole, RequiresRole, VersionPtrRole };
Task::Ptr getLoadTask() override;
bool isLoaded() override; bool isLoaded() override;
[[nodiscard]] Task::Ptr getLoadTask() override;
const BaseVersion::Ptr at(int i) const override; const BaseVersion::Ptr at(int i) const override;
int count() const override; int count() const override;
void sortVersions() override; void sortVersions() override;
@ -47,6 +48,8 @@ class VersionList : public BaseVersionList, public BaseEntity {
RoleList providesRoles() const override; RoleList providesRoles() const override;
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
void setProvidedRoles(RoleList roles);
QString localFilename() const override; QString localFilename() const override;
QString uid() const { return m_uid; } QString uid() const { return m_uid; }
@ -58,6 +61,9 @@ class VersionList : public BaseVersionList, public BaseEntity {
QVector<Version::Ptr> versions() const { return m_versions; } QVector<Version::Ptr> versions() const { return m_versions; }
// this blocks until the version list is loaded
void waitToLoad();
public: // for usage only by parsers public: // for usage only by parsers
void setName(const QString& name); void setName(const QString& name);
void setVersions(const QVector<Version::Ptr>& versions); void setVersions(const QVector<Version::Ptr>& versions);
@ -79,6 +85,9 @@ class VersionList : public BaseVersionList, public BaseEntity {
Version::Ptr m_recommended; Version::Ptr m_recommended;
RoleList m_provided_roles = { VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole, TypeRole, UidRole,
TimeRole, RequiresRole, SortRole, RecommendedRole, LatestRole, VersionPtrRole };
void setupAddedVersion(int row, const Version::Ptr& version); void setupAddedVersion(int row, const Version::Ptr& version);
}; };
} // namespace Meta } // namespace Meta

View File

@ -283,8 +283,7 @@ Net::NetRequest::Ptr AssetObject::getDownloadAction()
if ((!objectFile.isFile()) || (objectFile.size() != size)) { if ((!objectFile.isFile()) || (objectFile.size() != size)) {
auto objectDL = Net::ApiDownload::makeFile(getUrl(), objectFile.filePath()); auto objectDL = Net::ApiDownload::makeFile(getUrl(), objectFile.filePath());
if (hash.size()) { if (hash.size()) {
auto rawHash = QByteArray::fromHex(hash.toLatin1()); objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, hash));
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
} }
objectDL->setProgress(objectDL->getProgress(), size); objectDL->setProgress(objectDL->getProgress(), size);
return objectDL; return objectDL;

View File

@ -56,18 +56,6 @@ Component::Component(PackProfile* parent, const QString& uid)
m_uid = uid; m_uid = uid;
} }
Component::Component(PackProfile* parent, std::shared_ptr<Meta::Version> version)
{
assert(parent);
m_parent = parent;
m_metaVersion = version;
m_uid = version->uid();
m_version = m_cachedVersion = version->version();
m_cachedName = version->name();
m_loaded = version->isLoaded();
}
Component::Component(PackProfile* parent, const QString& uid, std::shared_ptr<VersionFile> file) Component::Component(PackProfile* parent, const QString& uid, std::shared_ptr<VersionFile> file)
{ {
assert(parent); assert(parent);
@ -102,9 +90,6 @@ void Component::applyTo(LaunchProfile* profile)
std::shared_ptr<class VersionFile> Component::getVersionFile() const std::shared_ptr<class VersionFile> Component::getVersionFile() const
{ {
if (m_metaVersion) { if (m_metaVersion) {
if (!m_metaVersion->isLoaded()) {
m_metaVersion->load(Net::Mode::Online);
}
return m_metaVersion->data(); return m_metaVersion->data();
} else { } else {
return m_file; return m_file;
@ -131,29 +116,35 @@ int Component::getOrder()
} }
return 0; return 0;
} }
void Component::setOrder(int order) void Component::setOrder(int order)
{ {
m_orderOverride = true; m_orderOverride = true;
m_order = order; m_order = order;
} }
QString Component::getID() QString Component::getID()
{ {
return m_uid; return m_uid;
} }
QString Component::getName() QString Component::getName()
{ {
if (!m_cachedName.isEmpty()) if (!m_cachedName.isEmpty())
return m_cachedName; return m_cachedName;
return m_uid; return m_uid;
} }
QString Component::getVersion() QString Component::getVersion()
{ {
return m_cachedVersion; return m_cachedVersion;
} }
QString Component::getFilename() QString Component::getFilename()
{ {
return m_parent->patchFilePathForUid(m_uid); return m_parent->patchFilePathForUid(m_uid);
} }
QDateTime Component::getReleaseDateTime() QDateTime Component::getReleaseDateTime()
{ {
if (m_metaVersion) { if (m_metaVersion) {
@ -198,17 +189,14 @@ bool Component::isCustom()
bool Component::isCustomizable() bool Component::isCustomizable()
{ {
if (m_metaVersion) { return m_metaVersion && getVersionFile();
if (getVersionFile()) {
return true;
}
}
return false;
} }
bool Component::isRemovable() bool Component::isRemovable()
{ {
return !m_important; return !m_important;
} }
bool Component::isRevertible() bool Component::isRevertible()
{ {
if (isCustom()) { if (isCustom()) {
@ -218,18 +206,18 @@ bool Component::isRevertible()
} }
return false; return false;
} }
bool Component::isMoveable() bool Component::isMoveable()
{ {
// HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'. // HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'.
return true; return true;
} }
bool Component::isVersionChangeable() bool Component::isVersionChangeable()
{ {
auto list = getVersionList(); auto list = getVersionList();
if (list) { if (list) {
if (!list->isLoaded()) { list->waitToLoad();
list->load(Net::Mode::Online);
}
return list->count() != 0; return list->count() != 0;
} }
return false; return false;

View File

@ -22,7 +22,6 @@ class Component : public QObject, public ProblemProvider {
Component(PackProfile* parent, const QString& uid); Component(PackProfile* parent, const QString& uid);
// DEPRECATED: remove these constructors? // DEPRECATED: remove these constructors?
Component(PackProfile* parent, std::shared_ptr<Meta::Version> version);
Component(PackProfile* parent, const QString& uid, std::shared_ptr<VersionFile> file); Component(PackProfile* parent, const QString& uid, std::shared_ptr<VersionFile> file);
virtual ~Component() {} virtual ~Component() {}

View File

@ -13,6 +13,7 @@
#include "net/Mode.h" #include "net/Mode.h"
#include "Application.h" #include "Application.h"
#include "tasks/Task.h"
/* /*
* This is responsible for loading the components of a component list AND resolving dependency issues between them * This is responsible for loading the components of a component list AND resolving dependency issues between them
@ -93,9 +94,9 @@ static LoadResult loadComponent(ComponentPtr component, Task::Ptr& loadTask, Net
component->m_loaded = true; component->m_loaded = true;
result = LoadResult::LoadedLocal; result = LoadResult::LoadedLocal;
} else { } else {
metaVersion->load(netmode); loadTask = APPLICATION->metadataIndex()->loadVersion(component->m_uid, component->m_version, netmode);
loadTask = metaVersion->getCurrentTask(); loadTask->start();
if (loadTask) if (netmode == Net::Mode::Online)
result = LoadResult::RequiresRemote; result = LoadResult::RequiresRemote;
else if (metaVersion->isLoaded()) else if (metaVersion->isLoaded())
result = LoadResult::LoadedLocal; result = LoadResult::LoadedLocal;
@ -133,21 +134,6 @@ static LoadResult loadPackProfile(ComponentPtr component, Task::Ptr& loadTask, N
} }
*/ */
static LoadResult loadIndex(Task::Ptr& loadTask, Net::Mode netmode)
{
// FIXME: DECIDE. do we want to run the update task anyway?
if (APPLICATION->metadataIndex()->isLoaded()) {
qDebug() << "Index is already loaded";
return LoadResult::LoadedLocal;
}
APPLICATION->metadataIndex()->load(netmode);
loadTask = APPLICATION->metadataIndex()->getCurrentTask();
if (loadTask) {
return LoadResult::RequiresRemote;
}
// FIXME: this is assuming the load succeeded... did it really?
return LoadResult::LoadedLocal;
}
} // namespace } // namespace
void ComponentUpdateTask::loadComponents() void ComponentUpdateTask::loadComponents()
@ -156,23 +142,7 @@ void ComponentUpdateTask::loadComponents()
size_t taskIndex = 0; size_t taskIndex = 0;
size_t componentIndex = 0; size_t componentIndex = 0;
d->remoteLoadSuccessful = true; d->remoteLoadSuccessful = true;
// load the main index (it is needed to determine if components can revert)
{
// FIXME: tear out as a method? or lambda?
Task::Ptr indexLoadTask;
auto singleResult = loadIndex(indexLoadTask, d->netmode);
result = composeLoadResult(result, singleResult);
if (indexLoadTask) {
qDebug() << "Remote loading is being run for metadata index";
RemoteLoadStatus status;
status.type = RemoteLoadStatus::Type::Index;
d->remoteLoadStatusList.append(status);
connect(indexLoadTask.get(), &Task::succeeded, [=]() { remoteLoadSucceeded(taskIndex); });
connect(indexLoadTask.get(), &Task::failed, [=](const QString& error) { remoteLoadFailed(taskIndex, error); });
connect(indexLoadTask.get(), &Task::aborted, [=]() { remoteLoadFailed(taskIndex, tr("Aborted")); });
taskIndex++;
}
}
// load all the components OR their lists... // load all the components OR their lists...
for (auto component : d->m_list->d->components) { for (auto component : d->m_list->d->components) {
Task::Ptr loadTask; Task::Ptr loadTask;
@ -206,12 +176,13 @@ void ComponentUpdateTask::loadComponents()
result = composeLoadResult(result, singleResult); result = composeLoadResult(result, singleResult);
if (loadTask) { if (loadTask) {
qDebug() << "Remote loading is being run for" << component->getName(); qDebug() << "Remote loading is being run for" << component->getName();
connect(loadTask.get(), &Task::succeeded, [=]() { remoteLoadSucceeded(taskIndex); }); connect(loadTask.get(), &Task::succeeded, this, [this, taskIndex]() { remoteLoadSucceeded(taskIndex); });
connect(loadTask.get(), &Task::failed, [=](const QString& error) { remoteLoadFailed(taskIndex, error); }); connect(loadTask.get(), &Task::failed, this, [this, taskIndex](const QString& error) { remoteLoadFailed(taskIndex, error); });
connect(loadTask.get(), &Task::aborted, [=]() { remoteLoadFailed(taskIndex, tr("Aborted")); }); connect(loadTask.get(), &Task::aborted, this, [this, taskIndex]() { remoteLoadFailed(taskIndex, tr("Aborted")); });
RemoteLoadStatus status; RemoteLoadStatus status;
status.type = loadType; status.type = loadType;
status.PackProfileIndex = componentIndex; status.PackProfileIndex = componentIndex;
status.task = loadTask;
d->remoteLoadStatusList.append(status); d->remoteLoadStatusList.append(status);
taskIndex++; taskIndex++;
} }
@ -518,7 +489,14 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex) void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
{ {
if (static_cast<size_t>(d->remoteLoadStatusList.size()) < taskIndex) {
qWarning() << "Got task index outside of results" << taskIndex;
return;
}
auto& taskSlot = d->remoteLoadStatusList[taskIndex]; auto& taskSlot = d->remoteLoadStatusList[taskIndex];
disconnect(taskSlot.task.get(), &Task::succeeded, this, nullptr);
disconnect(taskSlot.task.get(), &Task::failed, this, nullptr);
disconnect(taskSlot.task.get(), &Task::aborted, this, nullptr);
if (taskSlot.finished) { if (taskSlot.finished) {
qWarning() << "Got multiple results from remote load task" << taskIndex; qWarning() << "Got multiple results from remote load task" << taskIndex;
return; return;
@ -538,7 +516,14 @@ void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg) void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg)
{ {
if (static_cast<size_t>(d->remoteLoadStatusList.size()) < taskIndex) {
qWarning() << "Got task index outside of results" << taskIndex;
return;
}
auto& taskSlot = d->remoteLoadStatusList[taskIndex]; auto& taskSlot = d->remoteLoadStatusList[taskIndex];
disconnect(taskSlot.task.get(), &Task::succeeded, this, nullptr);
disconnect(taskSlot.task.get(), &Task::failed, this, nullptr);
disconnect(taskSlot.task.get(), &Task::aborted, this, nullptr);
if (taskSlot.finished) { if (taskSlot.finished) {
qWarning() << "Got multiple results from remote load task" << taskIndex; qWarning() << "Got multiple results from remote load task" << taskIndex;
return; return;

View File

@ -4,6 +4,7 @@
#include <QString> #include <QString>
#include <cstddef> #include <cstddef>
#include "net/Mode.h" #include "net/Mode.h"
#include "tasks/Task.h"
class PackProfile; class PackProfile;
@ -13,6 +14,7 @@ struct RemoteLoadStatus {
bool finished = false; bool finished = false;
bool succeeded = false; bool succeeded = false;
QString error; QString error;
Task::Ptr task;
}; };
struct ComponentUpdateTaskData { struct ComponentUpdateTaskData {

View File

@ -164,6 +164,11 @@ void LaunchProfile::applyCompatibleJavaMajors(QList<int>& javaMajor)
{ {
m_compatibleJavaMajors.append(javaMajor); m_compatibleJavaMajors.append(javaMajor);
} }
void LaunchProfile::applyCompatibleJavaName(QString javaName)
{
if (!javaName.isEmpty())
m_compatibleJavaName = javaName;
}
void LaunchProfile::applyLibrary(LibraryPtr library, const RuntimeContext& runtimeContext) void LaunchProfile::applyLibrary(LibraryPtr library, const RuntimeContext& runtimeContext)
{ {
@ -334,6 +339,11 @@ const QList<int>& LaunchProfile::getCompatibleJavaMajors() const
return m_compatibleJavaMajors; return m_compatibleJavaMajors;
} }
const QString LaunchProfile::getCompatibleJavaName() const
{
return m_compatibleJavaName;
}
void LaunchProfile::getLibraryFiles(const RuntimeContext& runtimeContext, void LaunchProfile::getLibraryFiles(const RuntimeContext& runtimeContext,
QStringList& jars, QStringList& jars,
QStringList& nativeJars, QStringList& nativeJars,

View File

@ -59,6 +59,7 @@ class LaunchProfile : public ProblemProvider {
void applyMavenFile(LibraryPtr library, const RuntimeContext& runtimeContext); void applyMavenFile(LibraryPtr library, const RuntimeContext& runtimeContext);
void applyAgent(AgentPtr agent, const RuntimeContext& runtimeContext); void applyAgent(AgentPtr agent, const RuntimeContext& runtimeContext);
void applyCompatibleJavaMajors(QList<int>& javaMajor); void applyCompatibleJavaMajors(QList<int>& javaMajor);
void applyCompatibleJavaName(QString javaName);
void applyMainJar(LibraryPtr jar); void applyMainJar(LibraryPtr jar);
void applyProblemSeverity(ProblemSeverity severity); void applyProblemSeverity(ProblemSeverity severity);
/// clear the profile /// clear the profile
@ -80,6 +81,7 @@ class LaunchProfile : public ProblemProvider {
const QList<LibraryPtr>& getMavenFiles() const; const QList<LibraryPtr>& getMavenFiles() const;
const QList<AgentPtr>& getAgents() const; const QList<AgentPtr>& getAgents() const;
const QList<int>& getCompatibleJavaMajors() const; const QList<int>& getCompatibleJavaMajors() const;
const QString getCompatibleJavaName() const;
const LibraryPtr getMainJar() const; const LibraryPtr getMainJar() const;
void getLibraryFiles(const RuntimeContext& runtimeContext, void getLibraryFiles(const RuntimeContext& runtimeContext,
QStringList& jars, QStringList& jars,
@ -150,5 +152,7 @@ class LaunchProfile : public ProblemProvider {
/// compatible java major versions /// compatible java major versions
QList<int> m_compatibleJavaMajors; QList<int> m_compatibleJavaMajors;
QString m_compatibleJavaName;
ProblemSeverity m_problemSeverity = ProblemSeverity::None; ProblemSeverity m_problemSeverity = ProblemSeverity::None;
}; };

View File

@ -147,9 +147,8 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
options |= Net::Download::Option::MakeEternal; options |= Net::Download::Option::MakeEternal;
if (sha1.size()) { if (sha1.size()) {
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
auto dl = Net::ApiDownload::makeCached(url, entry, options); auto dl = Net::ApiDownload::makeCached(url, entry, options);
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, sha1));
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url; qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
out.append(dl); out.append(dl);
} else { } else {

View File

@ -38,6 +38,8 @@
#include "MinecraftInstance.h" #include "MinecraftInstance.h"
#include "Application.h" #include "Application.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "QObjectPtr.h"
#include "minecraft/launch/AutoInstallJava.h"
#include "minecraft/launch/CreateGameFolders.h" #include "minecraft/launch/CreateGameFolders.h"
#include "minecraft/launch/ExtractNatives.h" #include "minecraft/launch/ExtractNatives.h"
#include "minecraft/launch/PrintInstanceInfo.h" #include "minecraft/launch/PrintInstanceInfo.h"
@ -134,25 +136,21 @@ void MinecraftInstance::loadSpecificSettings()
return; return;
// Java Settings // Java Settings
auto javaOverride = m_settings->registerSetting("OverrideJava", false);
auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false); auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false);
auto argsOverride = m_settings->registerSetting("OverrideJavaArgs", false); auto argsOverride = m_settings->registerSetting("OverrideJavaArgs", false);
m_settings->registerSetting("AutomaticJava", false);
// combinations
auto javaOrLocation = std::make_shared<OrSetting>("JavaOrLocationOverride", javaOverride, locationOverride);
auto javaOrArgs = std::make_shared<OrSetting>("JavaOrArgsOverride", javaOverride, argsOverride);
if (auto global_settings = globalSettings()) { if (auto global_settings = globalSettings()) {
m_settings->registerOverride(global_settings->getSetting("JavaPath"), javaOrLocation); m_settings->registerOverride(global_settings->getSetting("JavaPath"), locationOverride);
m_settings->registerOverride(global_settings->getSetting("JvmArgs"), javaOrArgs); m_settings->registerOverride(global_settings->getSetting("JvmArgs"), argsOverride);
m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), javaOrLocation); m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), locationOverride);
// special! // special!
m_settings->registerPassthrough(global_settings->getSetting("JavaSignature"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaSignature"), locationOverride);
m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), locationOverride);
m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), locationOverride);
m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), locationOverride);
m_settings->registerPassthrough(global_settings->getSetting("JavaVendor"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaVendor"), locationOverride);
// Window Size // Window Size
auto windowSetting = m_settings->registerSetting("OverrideWindow", false); auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
@ -196,8 +194,9 @@ void MinecraftInstance::loadSpecificSettings()
} }
// Join server on launch, this does not have a global override // Join server on launch, this does not have a global override
m_settings->registerSetting("JoinServerOnLaunch", false); m_settings->registerSetting({ "JoinServerOnLaunch", "JoinOnLaunch" }, false);
m_settings->registerSetting("JoinServerOnLaunchAddress", ""); m_settings->registerSetting("JoinServerOnLaunchAddress", "");
m_settings->registerSetting("JoinWorldOnLaunch", "");
// Use account for instance, this does not have a global override // Use account for instance, this does not have a global override
m_settings->registerSetting("UseAccountForInstance", false); m_settings->registerSetting("UseAccountForInstance", false);
@ -523,8 +522,7 @@ QStringList MinecraftInstance::javaArguments()
if (javaVersion.isModular() && shouldApplyOnlineFixes()) if (javaVersion.isModular() && shouldApplyOnlineFixes())
// allow reflective access to java.net - required by the skin fix // allow reflective access to java.net - required by the skin fix
args << "--add-opens" args << "--add-opens" << "java.base/java.net=ALL-UNNAMED";
<< "java.base/java.net=ALL-UNNAMED";
return args; return args;
} }
@ -608,7 +606,7 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
// dlsym variant is only needed for OpenGL and not included in the vulkan layer // dlsym variant is only needed for OpenGL and not included in the vulkan layer
appendLib("libMangoHud_dlsym.so"); appendLib("libMangoHud_dlsym.so");
appendLib("libMangoHud_opengl.so"); appendLib("libMangoHud_opengl.so");
appendLib(mangoHudLib.fileName()); preloadList << mangoHudLibString;
} }
env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":"))); env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":")));
@ -656,7 +654,7 @@ static QString replaceTokensIn(QString text, QMap<QString, QString> with)
return result; return result;
} }
QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) const QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) const
{ {
auto profile = m_components->getProfile(); auto profile = m_components->getProfile();
QString args_pattern = profile->getMinecraftArguments(); QString args_pattern = profile->getMinecraftArguments();
@ -664,12 +662,16 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine
args_pattern += " --tweakClass " + tweaker; args_pattern += " --tweakClass " + tweaker;
} }
if (serverToJoin && !serverToJoin->address.isEmpty()) { if (targetToJoin) {
if (!targetToJoin->address.isEmpty()) {
if (profile->hasTrait("feature:is_quick_play_multiplayer")) { if (profile->hasTrait("feature:is_quick_play_multiplayer")) {
args_pattern += " --quickPlayMultiplayer " + serverToJoin->address + ':' + QString::number(serverToJoin->port); args_pattern += " --quickPlayMultiplayer " + targetToJoin->address + ':' + QString::number(targetToJoin->port);
} else { } else {
args_pattern += " --server " + serverToJoin->address; args_pattern += " --server " + targetToJoin->address;
args_pattern += " --port " + QString::number(serverToJoin->port); args_pattern += " --port " + QString::number(targetToJoin->port);
}
} else if (!targetToJoin->world.isEmpty() && profile->hasTrait("feature:is_quick_play_singleplayer")) {
args_pattern += " --quickPlaySingleplayer " + targetToJoin->world;
} }
} }
@ -713,7 +715,7 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine
return parts; return parts;
} }
QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin)
{ {
QString launchScript; QString launchScript;
@ -732,9 +734,13 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
launchScript += "appletClass " + appletClass + "\n"; launchScript += "appletClass " + appletClass + "\n";
} }
if (serverToJoin && !serverToJoin->address.isEmpty()) { if (targetToJoin) {
launchScript += "serverAddress " + serverToJoin->address + "\n"; if (!targetToJoin->address.isEmpty()) {
launchScript += "serverPort " + QString::number(serverToJoin->port) + "\n"; launchScript += "serverAddress " + targetToJoin->address + "\n";
launchScript += "serverPort " + QString::number(targetToJoin->port) + "\n";
} else if (!targetToJoin->world.isEmpty()) {
launchScript += "worldName " + targetToJoin->world + "\n";
}
} }
// generic minecraft params // generic minecraft params
@ -787,16 +793,15 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
return launchScript; return launchScript;
} }
QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin)
{ {
QStringList out; QStringList out;
out << "Main Class:" out << "Main Class:" << " " + getMainClass() << "";
<< " " + getMainClass() << ""; out << "Native path:" << " " + getNativePath() << "";
out << "Native path:"
<< " " + getNativePath() << "";
auto profile = m_components->getProfile(); auto profile = m_components->getProfile();
// traits
auto alltraits = traits(); auto alltraits = traits();
if (alltraits.size()) { if (alltraits.size()) {
out << "Traits:"; out << "Traits:";
@ -806,6 +811,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << ""; out << "";
} }
// native libraries
auto settings = this->settings(); auto settings = this->settings();
bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool(); bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool();
bool nativeGLFW = settings->get("UseNativeGLFW").toBool(); bool nativeGLFW = settings->get("UseNativeGLFW").toBool();
@ -841,6 +847,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << ""; out << "";
} }
// mods and core mods
auto printModList = [&](const QString& label, ModFolderModel& model) { auto printModList = [&](const QString& label, ModFolderModel& model) {
if (model.size()) { if (model.size()) {
out << QString("%1:").arg(label); out << QString("%1:").arg(label);
@ -869,6 +876,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
printModList("Mods", *(loaderModList().get())); printModList("Mods", *(loaderModList().get()));
printModList("Core Mods", *(coreModList().get())); printModList("Core Mods", *(coreModList().get()));
// jar mods
auto& jarMods = profile->getJarMods(); auto& jarMods = profile->getJarMods();
if (jarMods.size()) { if (jarMods.size()) {
out << "Jar Mods:"; out << "Jar Mods:";
@ -884,11 +892,13 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << ""; out << "";
} }
auto params = processMinecraftArgs(nullptr, serverToJoin); // minecraft arguments
auto params = processMinecraftArgs(nullptr, targetToJoin);
out << "Params:"; out << "Params:";
out << " " + params.join(' '); out << " " + params.join(' ');
out << ""; out << "";
// window size
QString windowParams; QString windowParams;
if (settings->get("LaunchMaximized").toBool()) { if (settings->get("LaunchMaximized").toBool()) {
out << "Window size: max (if available)"; out << "Window size: max (if available)";
@ -1034,7 +1044,7 @@ Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode)
return nullptr; return nullptr;
} }
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin)
{ {
updateRuntimeContext(); updateRuntimeContext();
// FIXME: get rid of shared_from_this ... // FIXME: get rid of shared_from_this ...
@ -1048,26 +1058,28 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(makeShared<TextPrint>(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::Launcher)); process->appendStep(makeShared<TextPrint>(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::Launcher));
} }
// check java
{
process->appendStep(makeShared<CheckJava>(pptr));
}
// create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732) // create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732)
{ {
process->appendStep(makeShared<CreateGameFolders>(pptr)); process->appendStep(makeShared<CreateGameFolders>(pptr));
} }
if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool()) { if (!targetToJoin && settings()->get("JoinOnLaunch").toBool()) {
QString fullAddress = settings()->get("JoinServerOnLaunchAddress").toString(); QString fullAddress = settings()->get("JoinServerOnLaunchAddress").toString();
serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress))); if (!fullAddress.isEmpty()) {
targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(fullAddress, false)));
} else {
QString world = settings()->get("JoinWorldOnLaunch").toString();
if (!world.isEmpty()) {
targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(world, true)));
}
}
} }
if (serverToJoin && serverToJoin->port == 25565) { if (targetToJoin && targetToJoin->port == 25565) {
// Resolve server address to join on launch // Resolve server address to join on launch
auto step = makeShared<LookupServerAddress>(pptr); auto step = makeShared<LookupServerAddress>(pptr);
step->setLookupAddress(serverToJoin->address); step->setLookupAddress(targetToJoin->address);
step->setOutputAddressPtr(serverToJoin); step->setOutputAddressPtr(targetToJoin);
process->appendStep(step); process->appendStep(step);
} }
@ -1088,6 +1100,12 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(makeShared<Update>(pptr, Net::Mode::Offline)); process->appendStep(makeShared<Update>(pptr, Net::Mode::Offline));
} }
// check java
{
process->appendStep(makeShared<AutoInstallJava>(pptr));
process->appendStep(makeShared<CheckJava>(pptr));
}
// if there are any jar mods // if there are any jar mods
{ {
process->appendStep(makeShared<ModMinecraftJar>(pptr)); process->appendStep(makeShared<ModMinecraftJar>(pptr));
@ -1100,7 +1118,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
// print some instance info here... // print some instance info here...
{ {
process->appendStep(makeShared<PrintInstanceInfo>(pptr, session, serverToJoin)); process->appendStep(makeShared<PrintInstanceInfo>(pptr, session, targetToJoin));
} }
// extract native jars if needed // extract native jars if needed
@ -1123,7 +1141,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
auto step = makeShared<LauncherPartLaunch>(pptr); auto step = makeShared<LauncherPartLaunch>(pptr);
step->setWorkingDirectory(gameRoot()); step->setWorkingDirectory(gameRoot());
step->setAuthSession(session); step->setAuthSession(session);
step->setServerToJoin(serverToJoin); step->setTargetToJoin(targetToJoin);
process->appendStep(step); process->appendStep(step);
} }

View File

@ -39,7 +39,7 @@
#include <QDir> #include <QDir>
#include <QProcess> #include <QProcess>
#include "BaseInstance.h" #include "BaseInstance.h"
#include "minecraft/launch/MinecraftServerTarget.h" #include "minecraft/launch/MinecraftTarget.h"
#include "minecraft/mod/Mod.h" #include "minecraft/mod/Mod.h"
class ModFolderModel; class ModFolderModel;
@ -56,7 +56,7 @@ class MinecraftInstance : public BaseInstance {
Q_OBJECT Q_OBJECT
public: public:
MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir); MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir);
virtual ~MinecraftInstance() {}; virtual ~MinecraftInstance() = default;
virtual void saveNow() override; virtual void saveNow() override;
void loadSpecificSettings() override; void loadSpecificSettings() override;
@ -121,11 +121,11 @@ class MinecraftInstance : public BaseInstance {
////// Launch stuff ////// ////// Launch stuff //////
Task::Ptr createUpdateTask(Net::Mode mode) override; Task::Ptr createUpdateTask(Net::Mode mode) override;
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override; shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) override;
QStringList extraArguments() override; QStringList extraArguments() override;
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override; QStringList verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) override;
QList<Mod*> getJarMods() const; QList<Mod*> getJarMods() const;
QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin); QString createLaunchScript(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin);
/// get arguments passed to java /// get arguments passed to java
QStringList javaArguments(); QStringList javaArguments();
QString getLauncher(); QString getLauncher();
@ -155,7 +155,7 @@ class MinecraftInstance : public BaseInstance {
virtual QString getMainClass() const; virtual QString getMainClass() const;
// FIXME: remove // FIXME: remove
virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) const; virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) const;
virtual JavaVersion getJavaVersion(); virtual JavaVersion getJavaVersion();

View File

@ -16,32 +16,22 @@
#include "MinecraftUpdate.h" #include "MinecraftUpdate.h"
#include "MinecraftInstance.h" #include "MinecraftInstance.h"
#include <QDataStream>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <FileSystem.h>
#include "BaseInstance.h"
#include "minecraft/Library.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "tasks/SequentialTask.h"
#include "update/AssetUpdateTask.h" #include "update/AssetUpdateTask.h"
#include "update/FMLLibrariesTask.h" #include "update/FMLLibrariesTask.h"
#include "update/FoldersTask.h" #include "update/FoldersTask.h"
#include "update/LibrariesTask.h" #include "update/LibrariesTask.h"
#include <meta/Index.h> MinecraftUpdate::MinecraftUpdate(MinecraftInstance* inst, QObject* parent) : SequentialTask(parent), m_inst(inst) {}
#include <meta/Version.h>
MinecraftUpdate::MinecraftUpdate(MinecraftInstance* inst, QObject* parent) : Task(parent), m_inst(inst) {}
void MinecraftUpdate::executeTask() void MinecraftUpdate::executeTask()
{ {
m_tasks.clear(); m_queue.clear();
// create folders // create folders
{ {
m_tasks.append(makeShared<FoldersTask>(m_inst)); addTask(makeShared<FoldersTask>(m_inst));
} }
// add metadata update task if necessary // add metadata update task if necessary
@ -50,121 +40,24 @@ void MinecraftUpdate::executeTask()
components->reload(Net::Mode::Online); components->reload(Net::Mode::Online);
auto task = components->getCurrentTask(); auto task = components->getCurrentTask();
if (task) { if (task) {
m_tasks.append(task); addTask(task);
} }
} }
// libraries download // libraries download
{ {
m_tasks.append(makeShared<LibrariesTask>(m_inst)); addTask(makeShared<LibrariesTask>(m_inst));
} }
// FML libraries download and copy into the instance // FML libraries download and copy into the instance
{ {
m_tasks.append(makeShared<FMLLibrariesTask>(m_inst)); addTask(makeShared<FMLLibrariesTask>(m_inst));
} }
// assets update // assets update
{ {
m_tasks.append(makeShared<AssetUpdateTask>(m_inst)); addTask(makeShared<AssetUpdateTask>(m_inst));
} }
if (!m_preFailure.isEmpty()) { SequentialTask::executeTask();
emitFailed(m_preFailure);
return;
}
next();
}
void MinecraftUpdate::next()
{
if (m_abort) {
emitFailed(tr("Aborted by user."));
return;
}
if (m_failed_out_of_order) {
emitFailed(m_fail_reason);
return;
}
m_currentTask++;
if (m_currentTask > 0) {
auto task = m_tasks[m_currentTask - 1];
disconnect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded);
disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
disconnect(task.get(), &Task::aborted, this, &Task::abort);
disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propagateStepProgress);
disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
disconnect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
}
if (m_currentTask == m_tasks.size()) {
emitSucceeded();
return;
}
auto task = m_tasks[m_currentTask];
// if the task is already finished by the time we look at it, skip it
if (task->isFinished()) {
qCritical() << "MinecraftUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get();
next();
}
connect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded);
connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
connect(task.get(), &Task::aborted, this, &Task::abort);
connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propagateStepProgress);
connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
connect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
// if the task is already running, do not start it again
if (!task->isRunning()) {
task->start();
}
}
void MinecraftUpdate::subtaskSucceeded()
{
if (isFinished()) {
qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!";
return;
}
auto senderTask = QObject::sender();
auto currentTask = m_tasks[m_currentTask].get();
if (senderTask != currentTask) {
qDebug() << "MinecraftUpdate: Subtask" << sender() << "succeeded out of order.";
return;
}
next();
}
void MinecraftUpdate::subtaskFailed(QString error)
{
if (isFinished()) {
qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!";
return;
}
auto senderTask = QObject::sender();
auto currentTask = m_tasks[m_currentTask].get();
if (senderTask != currentTask) {
qDebug() << "MinecraftUpdate: Subtask" << sender() << "failed out of order.";
m_failed_out_of_order = true;
m_fail_reason = error;
return;
}
emitFailed(error);
}
bool MinecraftUpdate::abort()
{
if (!m_abort) {
m_abort = true;
auto task = m_tasks[m_currentTask];
if (task->canAbort()) {
return task->abort();
}
}
return true;
}
bool MinecraftUpdate::canAbort() const
{
return true;
} }

View File

@ -15,43 +15,19 @@
#pragma once #pragma once
#include <QList> #include "tasks/SequentialTask.h"
#include <QObject>
#include <QUrl>
#include <quazip/quazip.h>
#include "minecraft/VersionFilterData.h"
#include "net/NetJob.h"
#include "tasks/Task.h"
class MinecraftVersion;
class MinecraftInstance; class MinecraftInstance;
// FIXME: This looks very similar to a SequentialTask. Maybe we can reduce code duplications? :^) // this needs to be a task because components->reload does stuff that may block
class MinecraftUpdate : public SequentialTask {
class MinecraftUpdate : public Task {
Q_OBJECT Q_OBJECT
public: public:
explicit MinecraftUpdate(MinecraftInstance* inst, QObject* parent = 0); explicit MinecraftUpdate(MinecraftInstance* inst, QObject* parent = 0);
virtual ~MinecraftUpdate() {}; virtual ~MinecraftUpdate() = default;
void executeTask() override; void executeTask() override;
bool canAbort() const override;
private slots:
bool abort() override;
void subtaskSucceeded();
void subtaskFailed(QString error);
private:
void next();
private: private:
MinecraftInstance* m_inst = nullptr; MinecraftInstance* m_inst = nullptr;
QList<Task::Ptr> m_tasks;
QString m_preFailure;
int m_currentTask = -1;
bool m_abort = false;
bool m_failed_out_of_order = false;
QString m_fail_reason;
}; };

View File

@ -185,6 +185,9 @@ void MojangVersionFormat::readVersionProperties(const QJsonObject& in, VersionFi
out->compatibleJavaMajors.append(requireInteger(compatible)); out->compatibleJavaMajors.append(requireInteger(compatible));
} }
} }
if (in.contains("compatibleJavaName")) {
out->compatibleJavaName = requireString(in.value("compatibleJavaName"));
}
if (in.contains("downloads")) { if (in.contains("downloads")) {
auto downloadsObj = requireObject(in, "downloads"); auto downloadsObj = requireObject(in, "downloads");
@ -259,6 +262,9 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
} }
out.insert("compatibleJavaMajors", compatibleJavaMajorsOut); out.insert("compatibleJavaMajors", compatibleJavaMajorsOut);
} }
if (!in->compatibleJavaName.isEmpty()) {
writeString(out, "compatibleJavaName", in->compatibleJavaName);
}
} }
QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr& patch) QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr& patch)

View File

@ -36,6 +36,8 @@
#include "OneSixVersionFormat.h" #include "OneSixVersionFormat.h"
#include <Json.h> #include <Json.h>
#include <minecraft/MojangVersionFormat.h> #include <minecraft/MojangVersionFormat.h>
#include <QList>
#include "java/JavaMetadata.h"
#include "minecraft/Agent.h" #include "minecraft/Agent.h"
#include "minecraft/ParseUtils.h" #include "minecraft/ParseUtils.h"
@ -255,6 +257,13 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc
out->m_volatile = requireBoolean(root, "volatile"); out->m_volatile = requireBoolean(root, "volatile");
} }
if (root.contains("runtimes")) {
out->runtimes = {};
for (auto runtime : ensureArray(root, "runtimes")) {
out->runtimes.append(Java::parseJavaMeta(ensureObject(runtime)));
}
}
/* removed features that shouldn't be used */ /* removed features that shouldn't be used */
if (root.contains("tweakers")) { if (root.contains("tweakers")) {
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element 'tweakers'")); out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element 'tweakers'"));

View File

@ -73,6 +73,7 @@ void VersionFile::applyTo(LaunchProfile* profile, const RuntimeContext& runtimeC
profile->applyMods(mods); profile->applyMods(mods);
profile->applyTraits(traits); profile->applyTraits(traits);
profile->applyCompatibleJavaMajors(compatibleJavaMajors); profile->applyCompatibleJavaMajors(compatibleJavaMajors);
profile->applyCompatibleJavaName(compatibleJavaName);
for (auto library : libraries) { for (auto library : libraries) {
profile->applyLibrary(library, runtimeContext); profile->applyLibrary(library, runtimeContext);

View File

@ -36,6 +36,8 @@
#pragma once #pragma once
#include <QDateTime> #include <QDateTime>
#include <QHash>
#include <QList>
#include <QSet> #include <QSet>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
@ -45,6 +47,7 @@
#include "Agent.h" #include "Agent.h"
#include "Library.h" #include "Library.h"
#include "ProblemProvider.h" #include "ProblemProvider.h"
#include "java/JavaMetadata.h"
#include "minecraft/Rule.h" #include "minecraft/Rule.h"
class PackProfile; class PackProfile;
@ -98,6 +101,9 @@ class VersionFile : public ProblemContainer {
/// Mojang: list of compatible java majors /// Mojang: list of compatible java majors
QList<int> compatibleJavaMajors; QList<int> compatibleJavaMajors;
/// Mojang: the name of recommended java version
QString compatibleJavaName;
/// Mojang: type of the Minecraft version /// Mojang: type of the Minecraft version
QString type; QString type;
@ -149,6 +155,8 @@ class VersionFile : public ProblemContainer {
/// is volatile -- may be removed as soon as it is no longer needed by something else /// is volatile -- may be removed as soon as it is no longer needed by something else
bool m_volatile = false; bool m_volatile = false;
QList<Java::MetadataPtr> runtimes;
public: public:
// Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more. // Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more.
QMap<QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads; QMap<QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads;

View File

@ -74,6 +74,8 @@ void MSADeviceCodeStep::perform()
m_task->setAskRetry(false); m_task->setAskRetry(false);
m_task->addNetAction(m_request); m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAutorizationFinished);
m_task->start(); m_task->start();
} }

View File

@ -0,0 +1,242 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AutoInstallJava.h"
#include <QDir>
#include <QFileInfo>
#include <memory>
#include "Application.h"
#include "FileSystem.h"
#include "MessageLevel.h"
#include "SysInfo.h"
#include "java/JavaInstall.h"
#include "java/JavaInstallList.h"
#include "java/JavaUtils.h"
#include "java/JavaVersion.h"
#include "java/download/ArchiveDownloadTask.h"
#include "java/download/ManifestDownloadTask.h"
#include "meta/Index.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "net/Mode.h"
AutoInstallJava::AutoInstallJava(LaunchTask* parent)
: LaunchStep(parent)
, m_instance(std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()))
, m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {};
void AutoInstallJava::executeTask()
{
auto settings = m_instance->settings();
if (!APPLICATION->settings()->get("AutomaticJavaSwitch").toBool() ||
(settings->get("OverrideJavaLocation").toBool() && QFileInfo::exists(settings->get("JavaPath").toString()))) {
emitSucceeded();
return;
}
auto packProfile = m_instance->getPackProfile();
if (!APPLICATION->settings()->get("AutomaticJavaDownload").toBool()) {
auto javas = APPLICATION->javalist();
m_current_task = javas->getLoadTask();
connect(m_current_task.get(), &Task::finished, this, [this, javas, packProfile] {
for (auto i = 0; i < javas->count(); i++) {
auto java = std::dynamic_pointer_cast<JavaInstall>(javas->at(i));
if (java && packProfile->getProfile()->getCompatibleJavaMajors().contains(java->id.major())) {
if (!java->is_64bit) {
emit logLine(tr("The automatic Java mechanism detected a 32-bit installation of Java."), MessageLevel::Info);
}
setJavaPath(java->path);
return;
}
}
emit logLine(tr("No compatible Java version was found. Using the default one."), MessageLevel::Warning);
emitSucceeded();
});
connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress);
connect(m_current_task.get(), &Task::stepProgress, this, &AutoInstallJava::propagateStepProgress);
connect(m_current_task.get(), &Task::status, this, &AutoInstallJava::setStatus);
connect(m_current_task.get(), &Task::details, this, &AutoInstallJava::setDetails);
emit progressReportingRequest();
return;
}
if (m_supported_arch.isEmpty()) {
emit logLine(tr("Your system (%1-%2) is not compatible with automatic Java installation. Using the default Java path.")
.arg(SysInfo::currentSystem(), SysInfo::useQTForArch()),
MessageLevel::Warning);
emitSucceeded();
return;
}
auto wantedJavaName = packProfile->getProfile()->getCompatibleJavaName();
if (wantedJavaName.isEmpty()) {
emit logLine(tr("Your meta information is out of date or doesn't have the information necessary to determine what installation of "
"Java should be used. "
"Using the default Java path."),
MessageLevel::Warning);
emitSucceeded();
return;
}
QDir javaDir(APPLICATION->javaPath());
auto wantedJavaPath = javaDir.absoluteFilePath(wantedJavaName);
if (QFileInfo::exists(wantedJavaPath)) {
setJavaPathFromPartial();
return;
}
auto versionList = APPLICATION->metadataIndex()->get("net.minecraft.java");
m_current_task = versionList->getLoadTask();
connect(m_current_task.get(), &Task::succeeded, this, &AutoInstallJava::tryNextMajorJava);
connect(m_current_task.get(), &Task::failed, this, &AutoInstallJava::emitFailed);
connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress);
connect(m_current_task.get(), &Task::stepProgress, this, &AutoInstallJava::propagateStepProgress);
connect(m_current_task.get(), &Task::status, this, &AutoInstallJava::setStatus);
connect(m_current_task.get(), &Task::details, this, &AutoInstallJava::setDetails);
if (!m_current_task->isRunning()) {
m_current_task->start();
}
emit progressReportingRequest();
}
void AutoInstallJava::setJavaPath(QString path)
{
auto settings = m_instance->settings();
settings->set("OverrideJavaLocation", true);
settings->set("JavaPath", path);
settings->set("AutomaticJava", true);
emit logLine(tr("Compatible Java found at: %1.").arg(path), MessageLevel::Info);
emitSucceeded();
}
void AutoInstallJava::setJavaPathFromPartial()
{
auto packProfile = m_instance->getPackProfile();
auto javaName = packProfile->getProfile()->getCompatibleJavaName();
QDir javaDir(APPLICATION->javaPath());
// just checking if the executable is there should suffice
// but if needed this can be achieved through refreshing the javalist
// and retrieving the path that contains the java name
auto relativeBinary = FS::PathCombine(javaName, "bin", JavaUtils::javaExecutable);
auto finalPath = javaDir.absoluteFilePath(relativeBinary);
if (QFileInfo::exists(finalPath)) {
setJavaPath(finalPath);
} else {
emit logLine(tr("No compatible Java version was found (the binary file does not exist). Using the default one."),
MessageLevel::Warning);
emitSucceeded();
}
return;
}
void AutoInstallJava::downloadJava(Meta::Version::Ptr version, QString javaName)
{
auto runtimes = version->data()->runtimes;
for (auto java : runtimes) {
if (java->runtimeOS == m_supported_arch && java->name() == javaName) {
QDir javaDir(APPLICATION->javaPath());
auto final_path = javaDir.absoluteFilePath(java->m_name);
switch (java->downloadType) {
case Java::DownloadType::Manifest:
m_current_task = makeShared<Java::ManifestDownloadTask>(java->url, final_path, java->checksumType, java->checksumHash);
break;
case Java::DownloadType::Archive:
m_current_task = makeShared<Java::ArchiveDownloadTask>(java->url, final_path, java->checksumType, java->checksumHash);
break;
case Java::DownloadType::Unknown:
emitFailed(tr("Could not determine Java download type!"));
return;
}
auto deletePath = [final_path] { FS::deletePath(final_path); };
connect(m_current_task.get(), &Task::failed, this, [this, deletePath](QString reason) {
deletePath();
emitFailed(reason);
});
connect(this, &Task::aborted, this, [this, deletePath] {
m_current_task->abort();
deletePath();
});
connect(m_current_task.get(), &Task::succeeded, this, &AutoInstallJava::setJavaPathFromPartial);
connect(m_current_task.get(), &Task::failed, this, &AutoInstallJava::tryNextMajorJava);
connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress);
connect(m_current_task.get(), &Task::stepProgress, this, &AutoInstallJava::propagateStepProgress);
connect(m_current_task.get(), &Task::status, this, &AutoInstallJava::setStatus);
connect(m_current_task.get(), &Task::details, this, &AutoInstallJava::setDetails);
m_current_task->start();
return;
}
}
tryNextMajorJava();
}
void AutoInstallJava::tryNextMajorJava()
{
if (!isRunning())
return;
auto versionList = APPLICATION->metadataIndex()->get("net.minecraft.java");
auto packProfile = m_instance->getPackProfile();
auto wantedJavaName = packProfile->getProfile()->getCompatibleJavaName();
auto majorJavaVersions = packProfile->getProfile()->getCompatibleJavaMajors();
if (m_majorJavaVersionIndex >= majorJavaVersions.length()) {
emit logLine(
tr("No versions of Java were found for your operating system: %1-%2").arg(SysInfo::currentSystem(), SysInfo::useQTForArch()),
MessageLevel::Warning);
emit logLine(tr("No compatible version of Java was found. Using the default one."), MessageLevel::Warning);
emitSucceeded();
return;
}
auto majorJavaVersion = majorJavaVersions[m_majorJavaVersionIndex];
m_majorJavaVersionIndex++;
auto javaMajor = versionList->getVersion(QString("java%1").arg(majorJavaVersion));
if (javaMajor->isLoaded()) {
downloadJava(javaMajor, wantedJavaName);
} else {
m_current_task = APPLICATION->metadataIndex()->loadVersion("net.minecraft.java", javaMajor->version(), Net::Mode::Online);
connect(m_current_task.get(), &Task::succeeded, this,
[this, javaMajor, wantedJavaName] { downloadJava(javaMajor, wantedJavaName); });
connect(m_current_task.get(), &Task::failed, this, &AutoInstallJava::tryNextMajorJava);
connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress);
connect(m_current_task.get(), &Task::stepProgress, this, &AutoInstallJava::propagateStepProgress);
connect(m_current_task.get(), &Task::status, this, &AutoInstallJava::setStatus);
connect(m_current_task.get(), &Task::details, this, &AutoInstallJava::setDetails);
if (!m_current_task->isRunning()) {
m_current_task->start();
}
}
}
bool AutoInstallJava::abort()
{
if (m_current_task && m_current_task->canAbort())
return m_current_task->abort();
return true;
}

View File

@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <launch/LaunchStep.h>
#include <launch/LaunchTask.h>
#include "java/JavaMetadata.h"
#include "meta/Version.h"
#include "minecraft/MinecraftInstance.h"
#include "tasks/Task.h"
class AutoInstallJava : public LaunchStep {
Q_OBJECT
public:
explicit AutoInstallJava(LaunchTask* parent);
~AutoInstallJava() override = default;
void executeTask() override;
bool canAbort() const override { return m_current_task ? m_current_task->canAbort() : false; }
bool abort() override;
protected:
void setJavaPath(QString path);
void setJavaPathFromPartial();
void downloadJava(Meta::Version::Ptr version, QString javaName);
void tryNextMajorJava();
private:
MinecraftInstancePtr m_instance;
Task::Ptr m_current_task;
qsizetype m_majorJavaVersionIndex = 0;
const QString m_supported_arch;
};

View File

@ -90,7 +90,7 @@ void LauncherPartLaunch::executeTask()
} }
} }
m_launchScript = minecraftInstance->createLaunchScript(m_session, m_serverToJoin); m_launchScript = minecraftInstance->createLaunchScript(m_session, m_targetToJoin);
QStringList args = minecraftInstance->javaArguments(); QStringList args = minecraftInstance->javaArguments();
QString allArgs = args.join(", "); QString allArgs = args.join(", ");
emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::Launcher); emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::Launcher);

View File

@ -19,13 +19,13 @@
#include <launch/LaunchStep.h> #include <launch/LaunchStep.h>
#include <minecraft/auth/AuthSession.h> #include <minecraft/auth/AuthSession.h>
#include "MinecraftServerTarget.h" #include "MinecraftTarget.h"
class LauncherPartLaunch : public LaunchStep { class LauncherPartLaunch : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit LauncherPartLaunch(LaunchTask* parent); explicit LauncherPartLaunch(LaunchTask* parent);
virtual ~LauncherPartLaunch() {}; virtual ~LauncherPartLaunch() = default;
virtual void executeTask(); virtual void executeTask();
virtual bool abort(); virtual bool abort();
@ -34,7 +34,7 @@ class LauncherPartLaunch : public LaunchStep {
void setWorkingDirectory(const QString& wd); void setWorkingDirectory(const QString& wd);
void setAuthSession(AuthSessionPtr session) { m_session = session; } void setAuthSession(AuthSessionPtr session) { m_session = session; }
void setServerToJoin(MinecraftServerTargetPtr serverToJoin) { m_serverToJoin = std::move(serverToJoin); } void setTargetToJoin(MinecraftTarget::Ptr targetToJoin) { m_targetToJoin = std::move(targetToJoin); }
private slots: private slots:
void on_state(LoggedProcess::State state); void on_state(LoggedProcess::State state);
@ -44,7 +44,7 @@ class LauncherPartLaunch : public LaunchStep {
QString m_command; QString m_command;
AuthSessionPtr m_session; AuthSessionPtr m_session;
QString m_launchScript; QString m_launchScript;
MinecraftServerTargetPtr m_serverToJoin; MinecraftTarget::Ptr m_targetToJoin;
bool mayProceed = false; bool mayProceed = false;
}; };

View File

@ -13,13 +13,18 @@
* limitations under the License. * limitations under the License.
*/ */
#include "MinecraftServerTarget.h" #include "MinecraftTarget.h"
#include <QStringList> #include <QStringList>
// FIXME: the way this is written, it can't ever do any sort of validation and can accept total junk // FIXME: the way this is written, it can't ever do any sort of validation and can accept total junk
MinecraftServerTarget MinecraftServerTarget::parse(const QString& fullAddress) MinecraftTarget MinecraftTarget::parse(const QString& fullAddress, bool useWorld)
{ {
if (useWorld) {
MinecraftTarget target;
target.world = fullAddress;
return target;
}
QStringList split = fullAddress.split(":"); QStringList split = fullAddress.split(":");
// The logic below replicates the exact logic minecraft uses for parsing server addresses. // The logic below replicates the exact logic minecraft uses for parsing server addresses.
@ -56,5 +61,5 @@ MinecraftServerTarget MinecraftServerTarget::parse(const QString& fullAddress)
} }
} }
return MinecraftServerTarget{ realAddress, realPort }; return MinecraftTarget{ realAddress, realPort };
} }

View File

@ -19,11 +19,11 @@
#include <QString> #include <QString>
struct MinecraftServerTarget { struct MinecraftTarget {
QString address; QString address;
quint16 port; quint16 port;
static MinecraftServerTarget parse(const QString& fullAddress); QString world;
static MinecraftTarget parse(const QString& fullAddress, bool useWorld);
using Ptr = std::shared_ptr<MinecraftTarget>;
}; };
using MinecraftServerTargetPtr = std::shared_ptr<MinecraftServerTarget>;

View File

@ -129,6 +129,6 @@ void PrintInstanceInfo::executeTask()
#endif #endif
logLines(log, MessageLevel::Launcher); logLines(log, MessageLevel::Launcher);
logLines(instance->verboseDescription(m_session, m_serverToJoin), MessageLevel::Launcher); logLines(instance->verboseDescription(m_session, m_targetToJoin), MessageLevel::Launcher);
emitSucceeded(); emitSucceeded();
} }

View File

@ -16,22 +16,21 @@
#pragma once #pragma once
#include <launch/LaunchStep.h> #include <launch/LaunchStep.h>
#include <memory>
#include "minecraft/auth/AuthSession.h" #include "minecraft/auth/AuthSession.h"
#include "minecraft/launch/MinecraftServerTarget.h" #include "minecraft/launch/MinecraftTarget.h"
// FIXME: temporary wrapper for existing task. // FIXME: temporary wrapper for existing task.
class PrintInstanceInfo : public LaunchStep { class PrintInstanceInfo : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit PrintInstanceInfo(LaunchTask* parent, AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) explicit PrintInstanceInfo(LaunchTask* parent, AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin)
: LaunchStep(parent), m_session(session), m_serverToJoin(serverToJoin) {}; : LaunchStep(parent), m_session(session), m_targetToJoin(targetToJoin) {};
virtual ~PrintInstanceInfo() {}; virtual ~PrintInstanceInfo() = default;
virtual void executeTask(); virtual void executeTask();
virtual bool canAbort() const { return false; } virtual bool canAbort() const { return false; }
private: private:
AuthSessionPtr m_session; AuthSessionPtr m_session;
MinecraftServerTargetPtr m_serverToJoin; MinecraftTarget::Ptr m_targetToJoin;
}; };

View File

@ -34,7 +34,12 @@
*/ */
#include "VerifyJavaInstall.h" #include "VerifyJavaInstall.h"
#include <memory>
#include "Application.h"
#include "MessageLevel.h"
#include "java/JavaInstall.h"
#include "java/JavaInstallList.h"
#include "java/JavaVersion.h" #include "java/JavaVersion.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
@ -46,6 +51,15 @@ void VerifyJavaInstall::executeTask()
auto settings = instance->settings(); auto settings = instance->settings();
auto storedVersion = settings->get("JavaVersion").toString(); auto storedVersion = settings->get("JavaVersion").toString();
auto ignoreCompatibility = settings->get("IgnoreJavaCompatibility").toBool(); auto ignoreCompatibility = settings->get("IgnoreJavaCompatibility").toBool();
auto javaArchitecture = settings->get("JavaArchitecture").toString();
auto maxMemAlloc = settings->get("MaxMemAlloc").toInt();
if (javaArchitecture == "32" && maxMemAlloc > 2048) {
emit logLine(tr("Max memory allocation exceeds the supported value.\n"
"The selected installation of Java is 32-bit and doesn't support more than 2048MiB of RAM.\n"
"The instance may not start due to this."),
MessageLevel::Error);
}
auto compatibleMajors = packProfile->getProfile()->getCompatibleJavaMajors(); auto compatibleMajors = packProfile->getProfile()->getCompatibleJavaMajors();

View File

@ -32,8 +32,7 @@ void AssetUpdateTask::executeTask()
auto hexSha1 = assets->sha1.toLatin1(); auto hexSha1 = assets->sha1.toLatin1();
qDebug() << "Asset index SHA1:" << hexSha1; qDebug() << "Asset index SHA1:" << hexSha1;
auto dl = Net::ApiDownload::makeCached(indexUrl, entry); auto dl = Net::ApiDownload::makeCached(indexUrl, entry);
auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1()); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, assets->sha1));
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
job->addNetAction(dl); job->addNetAction(dl);
downloadJob.reset(job); downloadJob.reset(job);

View File

@ -343,9 +343,7 @@ QString PackInstallTask::getVersionForLoader(QString uid)
return Q_NULLPTR; return Q_NULLPTR;
} }
if (!vlist->isLoaded()) { vlist->waitToLoad();
vlist->load(Net::Mode::Online);
}
if (m_version.loader.recommended || m_version.loader.latest) { if (m_version.loader.recommended || m_version.loader.latest) {
for (int i = 0; i < vlist->versions().size(); i++) { for (int i = 0; i < vlist->versions().size(); i++) {
@ -638,8 +636,7 @@ void PackInstallTask::installConfigs()
auto dl = Net::ApiDownload::makeCached(url, entry); auto dl = Net::ApiDownload::makeCached(url, entry);
if (!m_version.configs.sha1.isEmpty()) { if (!m_version.configs.sha1.isEmpty()) {
auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1()); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, m_version.configs.sha1));
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
} }
jobPtr->addNetAction(dl); jobPtr->addNetAction(dl);
archivePath = entry->getFullPath(); archivePath = entry->getFullPath();
@ -758,8 +755,7 @@ void PackInstallTask::downloadMods()
auto dl = Net::ApiDownload::makeCached(url, entry); auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) { if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, mod.md5));
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
} }
jobPtr->addNetAction(dl); jobPtr->addNetAction(dl);
} else if (mod.type == ModType::Decomp) { } else if (mod.type == ModType::Decomp) {
@ -769,8 +765,7 @@ void PackInstallTask::downloadMods()
auto dl = Net::ApiDownload::makeCached(url, entry); auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) { if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, mod.md5));
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
} }
jobPtr->addNetAction(dl); jobPtr->addNetAction(dl);
} else { } else {
@ -783,8 +778,7 @@ void PackInstallTask::downloadMods()
auto dl = Net::ApiDownload::makeCached(url, entry); auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) { if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, mod.md5));
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
} }
jobPtr->addNetAction(dl); jobPtr->addNetAction(dl);
@ -1075,36 +1069,7 @@ void PackInstallTask::install()
static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version) static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version)
{ {
auto vlist = APPLICATION->metadataIndex()->get(uid); return APPLICATION->metadataIndex()->getLoadedVersion(uid, version);
if (!vlist)
return {};
if (!vlist->isLoaded()) {
QEventLoop loadVersionLoop;
auto task = vlist->getLoadTask();
QObject::connect(task.get(), &Task::finished, &loadVersionLoop, &QEventLoop::quit);
if (!task->isRunning())
task->start();
loadVersionLoop.exec();
}
auto ver = vlist->getVersion(version);
if (!ver)
return {};
if (!ver->isLoaded()) {
QEventLoop loadVersionLoop;
ver->load(Net::Mode::Online);
auto task = ver->getCurrentTask();
QObject::connect(task.get(), &Task::finished, &loadVersionLoop, &QEventLoop::quit);
if (!task->isRunning())
task->start();
loadVersionLoop.exec();
}
return ver;
} }
} // namespace ATLauncher } // namespace ATLauncher

View File

@ -246,7 +246,7 @@ void FlamePackExportTask::makeApiRequest()
pendingHashes.clear(); pendingHashes.clear();
getProjectsInfo(); getProjectsInfo();
}); });
connect(task.get(), &NetJob::failed, this, &FlamePackExportTask::getProjectsInfo); connect(task.get(), &Task::failed, this, &FlamePackExportTask::getProjectsInfo);
task->start(); task->start();
} }

View File

@ -18,6 +18,7 @@
#include "ModrinthPackExportTask.h" #include "ModrinthPackExportTask.h"
#include <QCoreApplication>
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QFileInfo> #include <QFileInfo>
#include <QMessageBox> #include <QMessageBox>
@ -28,6 +29,7 @@
#include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/MetadataHandler.h"
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"
#include "modplatform/helpers/HashUtils.h" #include "modplatform/helpers/HashUtils.h"
#include "tasks/Task.h"
const QStringList ModrinthPackExportTask::PREFIXES({ "mods/", "coremods/", "resourcepacks/", "texturepacks/", "shaderpacks/" }); const QStringList ModrinthPackExportTask::PREFIXES({ "mods/", "coremods/", "resourcepacks/", "texturepacks/", "shaderpacks/" });
const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "zip" }); const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "zip" });
@ -154,8 +156,8 @@ void ModrinthPackExportTask::makeApiRequest()
setStatus(tr("Finding versions for hashes...")); setStatus(tr("Finding versions for hashes..."));
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
task = api.currentVersions(pendingHashes.values(), "sha512", response); task = api.currentVersions(pendingHashes.values(), "sha512", response);
connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); connect(task.get(), &Task::succeeded, [this, response]() { parseApiResponse(response); });
connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); connect(task.get(), &Task::failed, this, &ModrinthPackExportTask::emitFailed);
task->start(); task->start();
} }
} }

View File

@ -114,8 +114,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
auto dl = Net::ApiDownload::makeFile(mod.url, path); auto dl = Net::ApiDownload::makeFile(mod.url, path);
if (!mod.md5.isEmpty()) { if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, mod.md5));
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
} }
m_filesNetJob->addNetAction(dl); m_filesNetJob->addNetAction(dl);

View File

@ -43,6 +43,9 @@
namespace Net { namespace Net {
class ChecksumValidator : public Validator { class ChecksumValidator : public Validator {
public: public:
ChecksumValidator(QCryptographicHash::Algorithm algorithm, QString expectedHex)
: Net::ChecksumValidator(algorithm, QByteArray::fromHex(expectedHex.toLatin1()))
{}
ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray()) ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray())
: m_checksum(algorithm), m_expected(expected) {}; : m_checksum(algorithm), m_expected(expected) {};
virtual ~ChecksumValidator() = default; virtual ~ChecksumValidator() = default;

View File

@ -353,5 +353,11 @@
<file>scalable/instances/neoforged.svg</file> <file>scalable/instances/neoforged.svg</file>
<file>128x128/instances/forge.png</file> <!-- LGPL3 Forge Development LLC --> <file>128x128/instances/forge.png</file> <!-- LGPL3 Forge Development LLC -->
<file>128x128/instances/liteloader.png</file> <!-- CC-BY-SA 4.0 LiteLoader --> <file>128x128/instances/liteloader.png</file> <!-- CC-BY-SA 4.0 LiteLoader -->
<!-- java providers -->
<file>scalable/adoptium.svg</file> <!-- The Adoptium Logo is a registered trademark of the Eclipse Foundation. -->
<file>scalable/azul.svg</file> <!-- Azul, Zulu, Azul Systems, the Azul Systems logo, Azul Zulu are either registered trademarks or trademarks of Azul Systems, registered in the U.S. and elsewhere. -->
<file>scalable/mojang.svg</file> <!-- The Mojang Logo is a registered trademark of Mojang AB. -->
</qresource> </qresource>
</RCC> </RCC>

View File

@ -0,0 +1,180 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
sodipodi:docname="Eclipse_Adoptium_Logo_A_only_no_outline.svg"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
id="svg8"
version="1.1"
viewBox="0 0 800 800"
height="800"
width="800"
inkscape:export-filename="Eclipse_Adoptium_Logo.png"
inkscape:export-xdpi="61.439999"
inkscape:export-ydpi="61.439999"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath242">
<path
d="m 987.855,678.469 -0.019,-0.008 -107.453,233.098 -81.121,-175.989 235.718,-106.09 0.01,0.02 c -21.01,10.078 -37.838,27.52 -47.135,48.969 z"
id="path240" />
</clipPath>
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-481.882,253.823,253.823,481.882,1152.35,658.95)"
spreadMethod="pad"
id="linearGradient248">
<stop
style="stop-opacity:1;stop-color:#421644"
offset="0"
id="stop244" />
<stop
style="stop-opacity:1;stop-color:#151530"
offset="1"
id="stop246" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath258">
<path
d="m 1164.62,758.102 0.08,0.039 -194.618,422.029 c -0.547,1.32 -1.184,2.59 -1.789,3.88 l -1.453,3.16 -0.059,-0.03 c -0.324,0.65 -0.703,1.27 -1.054,1.9 7.382,-13.68 11.589,-29.34 11.589,-45.98 0,-14.34 -3.191,-27.91 -8.777,-40.15 l 0.059,-0.03 -88.215,-191.361 107.453,-233.098 0.019,0.008 c 14.915,-34.387 49.125,-58.457 89.005,-58.457 53.57,0 96.99,43.429 96.99,97 0,14.707 -3.37,28.597 -9.23,41.09 z"
id="path256" />
</clipPath>
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(265.274,-595.434,-595.434,-265.274,846.292,1230.46)"
spreadMethod="pad"
id="linearGradient264">
<stop
style="stop-opacity:1;stop-color:#a21058"
offset="0"
id="stop260" />
<stop
style="stop-opacity:1;stop-color:#421644"
offset="1"
id="stop262" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath274">
<path
d="m 880.316,1240.11 c -37.687,0 -70.273,-21.54 -86.328,-52.93 l -0.058,0.03 -1.457,-3.16 c -0.606,-1.29 -1.239,-2.56 -1.785,-3.88 l -194.622,-422.029 0.079,-0.039 c -5.86,-12.493 -9.227,-26.383 -9.227,-41.09 0,-53.571 43.418,-97 96.992,-97 39.871,0 74.09,24.07 89.004,58.457 l 0.02,-0.008 195.664,424.459 -0.059,0.03 c 5.586,12.24 8.777,25.81 8.777,40.15 0,53.58 -43.425,97.01 -97,97.01 z"
id="path272" />
</clipPath>
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(259.548,595.434,595.434,-259.548,649.304,625.36)"
spreadMethod="pad"
id="linearGradient280">
<stop
style="stop-opacity:1;stop-color:#ee1a6d"
offset="0"
id="stop276" />
<stop
style="stop-opacity:1;stop-color:#a21058"
offset="1"
id="stop278" />
</linearGradient>
</defs>
<sodipodi:namedview
height="100mm"
inkscape:window-maximized="1"
inkscape:window-y="-8"
inkscape:window-x="1912"
inkscape:window-height="1057"
inkscape:window-width="1920"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="g216"
inkscape:document-units="px"
inkscape:cy="396.88538"
inkscape:cx="322.07976"
inkscape:zoom="0.72187089"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#494949"
id="base"
inkscape:showpageshadow="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<g
inkscape:export-ydpi="96"
inkscape:export-xdpi="96"
transform="matrix(0.0826697,0,0,-0.0826697,-36.864228,126.62103)"
id="g216">
<g
id="g236"
transform="matrix(13.679999,0,0,13.679976,-6760.5652,-16025.746)">
<g
clip-path="url(#clipPath242)"
id="g238">
<path
id="path250"
style="fill:url(#linearGradient248);fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 987.855,678.469 -0.019,-0.008 -107.453,233.098 -81.121,-175.989 235.718,-106.09 0.01,0.02 c -21.01,10.078 -37.838,27.52 -47.135,48.969" />
</g>
</g>
<g
id="g252"
transform="matrix(13.679999,0,0,13.679976,-6760.5652,-16025.746)">
<g
clip-path="url(#clipPath258)"
id="g254">
<path
id="path266"
style="fill:url(#linearGradient264);fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 1164.62,758.102 0.08,0.039 -194.618,422.029 c -0.547,1.32 -1.184,2.59 -1.789,3.88 l -1.453,3.16 -0.059,-0.03 c -0.324,0.65 -0.703,1.27 -1.054,1.9 7.382,-13.68 11.589,-29.34 11.589,-45.98 0,-14.34 -3.191,-27.91 -8.777,-40.15 l 0.059,-0.03 -88.215,-191.361 107.453,-233.098 0.019,0.008 c 14.915,-34.387 49.125,-58.457 89.005,-58.457 53.57,0 96.99,43.429 96.99,97 0,14.707 -3.37,28.597 -9.23,41.09" />
</g>
</g>
<g
id="g268"
transform="matrix(1.2000009,0,0,1.1999989,-853.84911,665.71675)">
<g
clip-path="url(#clipPath274)"
id="g270"
transform="matrix(11.39999,0,0,11.39999,-4922.2595,-13909.564)">
<path
id="path282"
style="fill:url(#linearGradient280);fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 880.316,1240.11 c -37.687,0 -70.273,-21.54 -86.328,-52.93 l -0.058,0.03 -1.457,-3.16 c -0.606,-1.29 -1.239,-2.56 -1.785,-3.88 l -194.622,-422.029 0.079,-0.039 c -5.86,-12.493 -9.227,-26.383 -9.227,-41.09 0,-53.571 43.418,-97 96.992,-97 39.871,0 74.09,24.07 89.004,58.457 l 0.02,-0.008 195.664,424.459 -0.059,0.03 c 5.586,12.24 8.777,25.81 8.777,40.15 0,53.58 -43.425,97.01 -97,97.01" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64" fill="none" style="scroll-behavior: auto !important;">
<path d="M26.792 4.49429L7.62244 44.4309C7.20797 45.2944 8.12743 46.1903 8.97988 45.7536L28.26 35.8772C28.6557 35.6745 29.1376 35.7574 29.4429 36.0805L53.2128 61.2399C53.994 62.0668 55.3295 61.1553 54.844 60.1264L28.5979 4.50031C28.2386 3.73874 27.1564 3.73514 26.792 4.49429Z" fill="#00374A"/>
<path d="M26.792 4.49429L7.62244 44.4309C7.20797 45.2944 8.12743 46.1903 8.97988 45.7536L28.26 35.8772C28.6557 35.6745 29.1376 35.7574 29.4429 36.0805L53.2128 61.2399C53.994 62.0668 55.3295 61.1553 54.844 60.1264L28.5979 4.50031C28.2386 3.73874 27.1564 3.73514 26.792 4.49429Z" fill="url(#paint0_linear_3578_2134)" fill-opacity="0.7" style="mix-blend-mode:overlay"/>
<path d="M34.0172 15.5765L60.9237 19.1042C61.9046 19.2329 62.1231 20.5555 61.2357 20.9928L33.4872 34.6659L9.3254 45.9918C8.34481 46.4514 7.43514 45.2419 8.14881 44.4273L33.135 15.909C33.3551 15.6578 33.686 15.5331 34.0172 15.5765Z" fill="#006588"/>
<path d="M34.0172 15.5765L60.9237 19.1042C61.9046 19.2329 62.1231 20.5555 61.2357 20.9928L33.4872 34.6659L9.3254 45.9918C8.34481 46.4514 7.43514 45.2419 8.14881 44.4273L33.135 15.909C33.3551 15.6578 33.686 15.5331 34.0172 15.5765Z" fill="url(#paint1_linear_3578_2134)" fill-opacity="0.7" style="mix-blend-mode:overlay"/>
<defs>
<linearGradient id="paint0_linear_3578_2134" x1="31.2618" y1="2.59998" x2="31.2618" y2="65.8" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
<linearGradient id="paint1_linear_3578_2134" x1="46.2268" y1="4.17812" x2="31.5574" y2="56.1518" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="800"
height="800"
viewBox="0 0 800 800"
xml:space="preserve"
style="scroll-behavior: auto !important;"
id="svg4"
sodipodi:docname="Mojang_Studios_Logo_(2020,_icon).svg"
inkscape:export-filename="Mojang_Studios_Logo_(2020,_icon).png"
inkscape:export-xdpi="61.439999"
inkscape:export-ydpi="61.439999"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview4"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:zoom="1.46625"
inkscape:cx="374.08355"
inkscape:cy="369.30946"
inkscape:window-width="2560"
inkscape:window-height="1369"
inkscape:window-x="6392"
inkscape:window-y="802"
inkscape:window-maximized="1"
inkscape:current-layer="g3" />&#10;<desc
id="desc1">Created with Fabric.js 3.6.3</desc>&#10;<defs
id="defs1">&#10;</defs>&#10;<g
transform="matrix(2 0 0 2 399.75 399.75)"
id="g4">&#10;<g
style=""
id="g3">&#10; <g
transform="matrix(3.37 0 0 3.37 0 0)"
id="g1">&#10;<polygon
style="display:inline;opacity:1;fill:#f0313b;fill-rule:nonzero;stroke:#f0313b;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0"
vector-effect="non-scaling-stroke"
points="50,50 50,-50 -50,-50 -50,50 "
id="polygon1" />&#10;</g>&#10; <g
transform="matrix(3.78 0 0 3.78 0.01 0)"
id="g2">&#10;<path
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;"
vector-effect="non-scaling-stroke"
transform=" translate(-44.68, -41.16)"
d="M 13.51988 41.11152 q 0 -12.31086 0.0047 -24.62173 c 0.00469 -2.87722 0.16418 -3.03875 3.01027 -3.04146 q 21.527 -0.02051 43.054 -0.01353 c 2.06114 0.0004 2.36366 0.32134 2.39718 2.42144 c 0.0238 1.49194 -0.01523 2.98567 0.03993 4.47616 a 6.77817 6.77817 0 0 0 6.69647 6.91868 c 1.65784 0.11443 3.328 0.04044 4.99213 0.07612 c 1.797 0.03852 2.04939 0.30467 2.12207 2.13943 c 0.01137 0.28664 0.01239 0.57383 0.01247 0.86076 q 0.00432 17.64834 0.00423 35.29668 c -0.00158 3.2118 -0.0499 3.24957 -3.28974 3.26017 q -3.78871 0.01236 -7.57745 -0.01166 c -2.96088 -0.02069 -3.03087 -0.09046 -3.032 -2.98084 q -0.00659 -16.87367 -0.00343 -33.74735 c 0 -0.40175 0.01018 -0.80387 -0.00321 -1.20519 a 3.329 3.329 0 0 0 -3.548 -3.56756 c -1.08962 -0.032 -2.18238 -0.03167 -3.27158 0.01034 a 3.34773 3.34773 0 0 0 -3.482 3.47659 c -0.03516 0.62965 -0.02128 1.26231 -0.02172 1.8936 q -0.00641 9.21165 -0.01056 18.42326 c -0.00046 0.68869 0.01794 1.37909 -0.02307 2.06567 c -0.075 1.25525 -0.44257 1.71282 -1.65149 1.72825 q -5.25132 0.06705 -10.50391 -0.00754 c -1.2165 -0.01879 -1.58169 -0.48679 -1.66785 -1.72683 c -0.03575 -0.51435 -0.01843 -1.03265 -0.019 -1.54914 q -0.0112 -9.81428 -0.0222 -19.62854 a 13.12088 13.12088 0 0 0 -0.05025 -1.71908 a 3.25128 3.25128 0 0 0 -3.10179 -2.93609 a 33.39881 33.39881 0 0 0 -3.95853 0.00271 a 3.1568 3.1568 0 0 0 -3.15611 3.21043 c -0.05842 1.26029 -0.03349 2.52474 -0.03457 3.78735 q -0.01343 15.66842 -0.02277 31.33681 c -0.00035 0.45913 0.01748 0.92 -0.01557 1.377 c -0.09 1.24451 -0.45629 1.713 -1.67232 1.72878 q -5.25117 0.06813 -10.50381 -0.01831 c -1.23915 -0.02079 -1.58295 -0.457 -1.6812 -1.71692 c -0.04454 -0.5709 -0.02489 -1.1472 -0.025 -1.72107 q -0.00183 -12.13872 -0.00083 -24.27744 Z"
stroke-linecap="round"
id="path1" />&#10;</g>&#10;</g>&#10;</g>&#10;</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -592,8 +592,7 @@ void TranslationsModel::downloadTranslation(QString key)
entry->setStale(true); entry->setStale(true);
auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + lang->file_name), entry); auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + lang->file_name), entry);
auto rawHash = QByteArray::fromHex(lang->file_sha1.toLatin1()); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, lang->file_sha1));
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
dl->setProgress(dl->getProgress(), lang->file_size); dl->setProgress(dl->getProgress(), lang->file_size);
d->m_dl_job.reset(new NetJob("Translation for " + key, APPLICATION->network())); d->m_dl_job.reset(new NetJob("Translation for " + key, APPLICATION->network()));

Some files were not shown because too many files have changed in this diff Show More