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:
ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs
uses: korthout/backport-action@v3.0.2
uses: korthout/backport-action@v3.1.0
with:
# Config README: https://github.com/korthout/backport-action#backport-action
pull_description: |-

View File

@ -79,7 +79,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: ""
qt_version: "6.7.1"
qt_version: "6.7.2"
qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: windows-2022
@ -90,7 +90,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: "win64_msvc2019_arm64"
qt_version: "6.7.1"
qt_version: "6.7.2"
qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: macos-12
@ -99,7 +99,7 @@ jobs:
qt_ver: 6
qt_host: mac
qt_arch: ""
qt_version: "6.7.1"
qt_version: "6.7.2"
qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: macos-12
@ -160,7 +160,7 @@ jobs:
- name: Setup ccache
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:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
@ -266,23 +266,23 @@ jobs:
- name: Configure CMake (macOS)
if: runner.os == 'macOS' && matrix.qt_ver == 6
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)
if: runner.os == 'macOS' && matrix.qt_ver == 5
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)
if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0}
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)
if: runner.os == 'Windows' && matrix.msystem == ''
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)
if ("${{ env.CCACHE_VAR }}")
{
@ -297,7 +297,7 @@ jobs:
- name: Configure CMake (Linux)
if: runner.os == 'Linux'
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

View File

@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b # v27
- uses: DeterminateSystems/update-flake-lock@v22
- uses: DeterminateSystems/update-flake-lock@v23
with:
commit-msg: "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_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
if(UNIX AND APPLE)
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;
}
#cmakedefine01 ENABLE_JAVA_DOWNLOADER
JAVA_DOWNLOADER_ENABLED = ENABLE_JAVA_DOWNLOADER;
GIT_COMMIT = "@Launcher_GIT_COMMIT@";
GIT_TAG = "@Launcher_GIT_TAG@";
GIT_REFSPEC = "@Launcher_GIT_REFSPEC@";

View File

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

18
flake.lock generated
View File

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

View File

@ -44,10 +44,10 @@
#include "BuildConfig.h"
#include "DataMigrationTask.h"
#include "java/JavaInstallList.h"
#include "net/PasteUpload.h"
#include "pathmatcher/MultiMatcher.h"
#include "pathmatcher/SimplePrefixMatcher.h"
#include "settings/INIFile.h"
#include "tools/GenericProfiler.h"
#include "ui/InstanceWindow.h"
#include "ui/MainWindow.h"
@ -106,7 +106,7 @@
#include "icons/IconList.h"
#include "net/HttpMetaCache.h"
#include "java/JavaUtils.h"
#include "java/JavaInstallList.h"
#include "updater/ExternalUpdater.h"
@ -126,6 +126,7 @@
#include <stdlib.h>
#include <sys.h>
#include "SysInfo.h"
#ifdef Q_OS_LINUX
#include <dlfcn.h>
@ -151,6 +152,7 @@
#endif
#if defined Q_OS_WIN32
#include <windows.h>
#include "WindowsConsole.h"
#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" },
{ { "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" },
{ { "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" },
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
{ { "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_serverToJoin = parser.value("server");
m_worldToJoin = parser.value("world");
m_profileToUse = parser.value("profile");
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
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;
m_status = Application::Failed;
return;
@ -385,6 +389,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
if (!m_serverToJoin.isEmpty()) {
launch.args["server"] = m_serverToJoin;
} else if (!m_worldToJoin.isEmpty()) {
launch.args["world"] = m_worldToJoin;
}
if (!m_profileToUse.isEmpty()) {
launch.args["profile"] = m_profileToUse;
@ -523,6 +529,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
if (!m_serverToJoin.isEmpty()) {
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.";
}
@ -596,6 +604,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
m_settings->registerSetting("DownloadsDirWatchRecursive", false);
m_settings->registerSetting("SkinsDir", "skins");
m_settings->registerSetting("JavaDir", "java");
// Editors
m_settings->registerSetting("JsonEditor", QString());
@ -624,7 +633,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// Memory
m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512);
m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, suitableMaxMem());
m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::suitableMaxMem());
m_settings->registerSetting("PermGen", 128);
// Java Settings
@ -638,6 +647,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("JvmArgs", "");
m_settings->registerSetting("IgnoreJavaCompatibility", 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
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("translations", QDir("translations").absolutePath());
m_metacache->addBase("meta", QDir("meta").absolutePath());
m_metacache->addBase("java", QDir("cache/java").absolutePath());
m_metacache->Load();
qDebug() << "<> Cache initialized.";
}
@ -1157,14 +1170,17 @@ void Application::performMainStartupAction()
if (!m_instanceIdToLaunch.isEmpty()) {
auto inst = instances()->getInstanceById(m_instanceIdToLaunch);
if (inst) {
MinecraftServerTargetPtr serverToJoin = nullptr;
MinecraftTarget::Ptr targetToJoin = nullptr;
MinecraftAccountPtr accountToUse = nullptr;
qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching";
if (!m_serverToJoin.isEmpty()) {
// 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;
} else if (!m_worldToJoin.isEmpty()) {
targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(m_worldToJoin, true)));
qDebug() << " Launching with world" << m_worldToJoin;
}
if (!m_profileToUse.isEmpty()) {
@ -1175,7 +1191,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse;
}
launch(inst, true, false, serverToJoin, accountToUse);
launch(inst, true, false, targetToJoin, accountToUse);
return;
}
}
@ -1265,6 +1281,7 @@ void Application::messageReceived(const QByteArray& message)
} else if (command == "launch") {
QString id = received.args["id"];
QString server = received.args["server"];
QString world = received.args["world"];
QString profile = received.args["profile"];
InstancePtr instance;
@ -1279,11 +1296,12 @@ void Application::messageReceived(const QByteArray& message)
return;
}
MinecraftServerTargetPtr serverObject = nullptr;
MinecraftTarget::Ptr serverObject = nullptr;
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;
if (!profile.isEmpty()) {
accountObject = accounts()->getAccountByProfileName(profile);
@ -1332,11 +1350,7 @@ bool Application::openJsonEditor(const QString& filename)
}
}
bool Application::launch(InstancePtr instance,
bool online,
bool demo,
MinecraftServerTargetPtr serverToJoin,
MinecraftAccountPtr accountToUse)
bool Application::launch(InstancePtr instance, bool online, bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse)
{
if (m_updateRunning) {
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->setDemo(demo);
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
controller->setServerToJoin(serverToJoin);
controller->setTargetToJoin(targetToJoin);
controller->setAccountToUse(accountToUse);
if (window) {
controller->setParentWidget(window);
@ -1733,20 +1747,6 @@ QString Application::getUserAgentUncached()
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,
const QString& oldData,
const QString& name,
@ -1853,3 +1853,7 @@ QUrl Application::normalizeImportUrl(QString const& 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 "minecraft/launch/MinecraftServerTarget.h"
#include "minecraft/launch/MinecraftTarget.h"
class LaunchController;
class LocalPeer;
@ -161,6 +161,9 @@ class Application : public QApplication {
/// the data path the application is using
const QString& dataRoot() { return m_dataPath; }
/// the java installed path the application is using
const QString javaPath();
bool isPortable() { return m_portable; }
const Capabilities capabilities() { return m_capabilities; }
@ -179,8 +182,6 @@ class Application : public QApplication {
void ShowGlobalSettings(class QWidget* parent, QString open_page = QString());
int suitableMaxMem();
bool updaterEnabled();
QString updaterBinaryName();
@ -202,7 +203,7 @@ class Application : public QApplication {
bool launch(InstancePtr instance,
bool online = true,
bool demo = false,
MinecraftServerTargetPtr serverToJoin = nullptr,
MinecraftTarget::Ptr targetToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr);
bool kill(InstancePtr instance);
void closeCurrentWindow();
@ -290,6 +291,7 @@ class Application : public QApplication {
QString m_detectedOpenALPath;
QString m_instanceIdToLaunch;
QString m_serverToJoin;
QString m_worldToJoin;
QString m_profileToUse;
bool m_liveCheck = false;
QList<QUrl> m_urlsToImport;

View File

@ -56,7 +56,7 @@
#include "net/Mode.h"
#include "RuntimeContext.h"
#include "minecraft/launch/MinecraftServerTarget.h"
#include "minecraft/launch/MinecraftTarget.h"
class QDir;
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;
/// 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)
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
*/
virtual QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) = 0;
virtual QStringList verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) = 0;
Status currentStatus() const;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,12 @@
#include "Filter.h"
Filter::~Filter() {}
ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern) {}
ContainsFilter::~ContainsFilter() {}
bool ContainsFilter::accepts(const QString& value)
{
return value.contains(pattern);
}
ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern) {}
ExactFilter::~ExactFilter() {}
bool ExactFilter::accepts(const QString& value)
{
return value == pattern;
@ -27,10 +23,15 @@ RegexpFilter::RegexpFilter(const QString& regexp, bool invert) : invert(invert)
pattern.setPattern(regexp);
pattern.optimize();
}
RegexpFilter::~RegexpFilter() {}
bool RegexpFilter::accepts(const QString& value)
{
auto match = pattern.match(value);
bool matched = match.hasMatch();
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 {
public:
virtual ~Filter();
virtual ~Filter() = default;
virtual bool accepts(const QString& value) = 0;
};
class ContainsFilter : public Filter {
public:
ContainsFilter(const QString& pattern);
virtual ~ContainsFilter();
virtual ~ContainsFilter() = default;
bool accepts(const QString& value) override;
private:
@ -22,7 +22,7 @@ class ContainsFilter : public Filter {
class ExactFilter : public Filter {
public:
ExactFilter(const QString& pattern);
virtual ~ExactFilter();
virtual ~ExactFilter() = default;
bool accepts(const QString& value) override;
private:
@ -32,7 +32,7 @@ class ExactFilter : public Filter {
class ExactIfPresentFilter : public Filter {
public:
ExactIfPresentFilter(const QString& pattern);
~ExactIfPresentFilter() override = default;
virtual ~ExactIfPresentFilter() override = default;
bool accepts(const QString& value) override;
private:
@ -42,10 +42,20 @@ class ExactIfPresentFilter : public Filter {
class RegexpFilter : public Filter {
public:
RegexpFilter(const QString& regexp, bool invert);
virtual ~RegexpFilter();
virtual ~RegexpFilter() = default;
bool accepts(const QString& value) override;
private:
QRegularExpression pattern;
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:
explicit InstancePageProvider(InstancePtr parent) { inst = parent; }
virtual ~InstancePageProvider() {};
virtual ~InstancePageProvider() = default;
virtual QList<BasePage*> getPages() override
{
QList<BasePage*> values;
@ -39,7 +39,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
values.append(new TexturePackPage(onesix.get(), onesix->texturePackList()));
values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList()));
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 GameOptionsPage(onesix.get()));
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));

View File

@ -63,7 +63,7 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent)
return true;
}
void JavaCommon::javaWasOk(QWidget* parent, const JavaCheckResult& result)
void JavaCommon::javaWasOk(QWidget* parent, const JavaChecker::Result& result)
{
QString text;
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();
}
void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaCheckResult& result)
void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaChecker::Result& result)
{
auto htmlError = result.errorLog;
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();
}
void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaCheckResult& result)
void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaChecker::Result& result)
{
QString text;
text += QObject::tr(
@ -116,34 +116,26 @@ void JavaCommon::TestCheck::run()
emit finished();
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);
checker->m_path = m_path;
checker->performCheck();
checker->start();
}
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);
emit finished();
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);
checker->m_path = m_path;
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();
checker->start();
}
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);
emit finished();
return;

View File

@ -10,11 +10,11 @@ namespace JavaCommon {
bool checkJVMArgs(QString args, QWidget* parent);
// 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
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
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
void javaCheckNotFound(QWidget* parent);
@ -32,11 +32,11 @@ class TestCheck : public QObject {
void finished();
private slots:
void checkFinished(JavaCheckResult result);
void checkFinishedWithArgs(JavaCheckResult result);
void checkFinished(const JavaChecker::Result& result);
void checkFinishedWithArgs(const JavaChecker::Result& result);
private:
std::shared_ptr<JavaChecker> checker;
JavaChecker::Ptr checker;
QWidget* m_parent = nullptr;
QString m_path;
QString m_args;

View File

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

View File

@ -39,7 +39,7 @@
#include <QObject>
#include "minecraft/auth/MinecraftAccount.h"
#include "minecraft/launch/MinecraftServerTarget.h"
#include "minecraft/launch/MinecraftTarget.h"
class InstanceWindow;
class LaunchController : public Task {
@ -48,7 +48,7 @@ class LaunchController : public Task {
void executeTask() override;
LaunchController(QObject* parent = nullptr);
virtual ~LaunchController() {};
virtual ~LaunchController() = default;
void setInstance(InstancePtr instance) { m_instance = instance; }
@ -62,7 +62,7 @@ class LaunchController : public Task {
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); }
@ -94,5 +94,5 @@ class LaunchController : public Task {
MinecraftAccountPtr m_accountToUse = nullptr;
AuthSessionPtr m_session;
shared_qobject_ptr<LaunchTask> m_launcher;
MinecraftServerTargetPtr m_serverToJoin;
MinecraftTarget::Ptr m_targetToJoin;
};

View File

@ -2,7 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* 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
* it under the terms of the GNU General Public License as published by
@ -536,6 +536,10 @@ bool ExportToZipTask::abort()
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(); });
connect(&m_zip_watcher, &QFutureWatcher<ZipResult>::finished, this, &ExtractZipTask::finish);
m_zip_watcher.setFuture(m_zip_future);

View File

@ -2,7 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* 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
* 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 {
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 = "")
: m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory)
{}

View File

@ -40,8 +40,8 @@ namespace MangoHud {
QString getLibraryString()
{
/*
* Check for vulkan layers in this order:
/**
* Guess MangoHud install location by searching for vulkan layers in this order:
*
* $VK_LAYER_PATH
* $XDG_DATA_DIRS (/usr/local/share/:/usr/share/)
@ -49,8 +49,9 @@ QString getLibraryString()
* /etc
* $XDG_CONFIG_DIRS (/etc/xdg)
* $XDG_CONFIG_HOME (~/.config)
*
* @returns Absolute path of libMangoHud.so if found and empty QString otherwise.
*/
QStringList vkLayerList;
{
QString home = QDir::homePath();
@ -85,7 +86,7 @@ QString getLibraryString()
vkLayerList << FS::PathCombine(xdgConfigHome, "vulkan", "implicit_layer.d");
}
for (QString vkLayer : vkLayerList) {
for (const QString& vkLayer : vkLayerList) {
// prefer to use architecture specific vulkan layers
QString currentArch = QSysInfo::currentCpuArchitecture();
@ -95,8 +96,8 @@ QString getLibraryString()
QStringList manifestNames = { QString("MangoHud.%1.json").arg(currentArch), "MangoHud.json" };
QString filePath = "";
for (QString manifestName : manifestNames) {
QString filePath{};
for (const QString& manifestName : manifestNames) {
QString tryPath = FS::PathCombine(vkLayer, manifestName);
if (QFile::exists(tryPath)) {
filePath = tryPath;
@ -111,10 +112,23 @@ QString getLibraryString()
auto conf = Json::requireDocument(filePath, vkLayer);
auto confObject = Json::requireObject(conf, vkLayer);
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)

View File

@ -46,13 +46,13 @@ class NullInstance : public BaseInstance {
{
setVersionBroken(true);
}
virtual ~NullInstance() {};
virtual ~NullInstance() = default;
void saveNow() override {}
void loadSpecificSettings() override { setSpecificSettingsLoaded(true); }
QString getStatusbarDescription() override { return tr("Unknown instance type"); };
QSet<QString> traits() const override { return {}; };
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; }
QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); }
QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); }
@ -64,7 +64,7 @@ class NullInstance : public BaseInstance {
bool canEdit() const override { return false; }
bool canLaunch() const override { return false; }
void populateLaunchMenu(QMenu* menu) override {}
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override
QStringList verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) override
{
QStringList out;
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");
case Type:
return tr("Type");
case Architecture:
case CPUArchitecture:
return tr("Architecture");
case Path:
return tr("Path");
case JavaName:
return tr("Java Name");
case JavaMajor:
return tr("Major Version");
case Time:
return tr("Released");
}
@ -131,10 +135,14 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation,
return tr("The version's branch");
case Type:
return tr("The version's type");
case Architecture:
case CPUArchitecture:
return tr("CPU Architecture");
case Path:
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:
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);
case Type:
return sourceModel()->data(parentIndex, BaseVersionList::TypeRole);
case Architecture:
return sourceModel()->data(parentIndex, BaseVersionList::ArchitectureRole);
case CPUArchitecture:
return sourceModel()->data(parentIndex, BaseVersionList::CPUArchitectureRole);
case Path:
return sourceModel()->data(parentIndex, BaseVersionList::PathRole);
case JavaName:
return sourceModel()->data(parentIndex, BaseVersionList::JavaNameRole);
case JavaMajor:
return sourceModel()->data(parentIndex, BaseVersionList::JavaMajorRole);
case Time:
return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate();
default:
@ -308,12 +320,18 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw)
m_columns.push_back(ParentVersion);
}
*/
if (roles.contains(BaseVersionList::ArchitectureRole)) {
m_columns.push_back(Architecture);
if (roles.contains(BaseVersionList::CPUArchitectureRole)) {
m_columns.push_back(CPUArchitecture);
}
if (roles.contains(BaseVersionList::PathRole)) {
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)) {
m_columns.push_back(Time);
}

View File

@ -9,7 +9,7 @@ class VersionFilterModel;
class VersionProxyModel : public QAbstractProxyModel {
Q_OBJECT
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>>;
public:

View File

@ -40,14 +40,15 @@
#include <QMap>
#include <QProcess>
#include "Application.h"
#include "Commandline.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();
@ -72,7 +73,7 @@ void JavaChecker::performCheck()
if (m_maxMem != 0) {
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);
}
@ -115,11 +116,10 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
QProcessPtr _process = process;
process.reset();
JavaCheckResult result;
{
result.path = m_path;
result.id = m_id;
}
Result result = {
m_path,
m_id,
};
result.errorLog = m_stderr;
result.outLog = 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;
if (status == QProcess::CrashExit || exitcode == 1) {
result.validity = JavaCheckResult::Validity::Errored;
result.validity = Result::Validity::Errored;
emit checkFinished(result);
emitSucceeded();
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) {
result.validity = JavaCheckResult::Validity::ReturnedInvalidData;
result.validity = Result::Validity::ReturnedInvalidData;
emit checkFinished(result);
emitSucceeded();
return;
}
@ -171,7 +173,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
auto java_vendor = results["java.vendor"];
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.mojangPlatform = is_64 ? "64" : "32";
result.realPlatform = os_arch;
@ -179,6 +181,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
result.javaVendor = java_vendor;
qDebug() << "Java checker succeeded.";
emit checkFinished(result);
emitSucceeded();
}
void JavaChecker::error(QProcess::ProcessError err)
@ -190,15 +193,9 @@ void JavaChecker::error(QProcess::ProcessError err)
qDebug() << "Native environment:";
qDebug() << QProcessEnvironment::systemEnvironment().toStringList();
killTimer.stop();
JavaCheckResult result;
{
result.path = m_path;
result.id = m_id;
}
emit checkFinished(result);
return;
emit checkFinished({ m_path, m_id });
}
emitSucceeded();
}
void JavaChecker::timeout()

View File

@ -3,14 +3,19 @@
#include <QTimer>
#include <memory>
#include "QObjectPtr.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;
int id;
QString mojangPlatform;
QString realPlatform;
JavaVersion javaVersion;
@ -18,34 +23,31 @@ struct JavaCheckResult {
QString outLog;
QString errorLog;
bool is_64bit = false;
int id;
enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
};
using QProcessPtr = shared_qobject_ptr<QProcess>;
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;
explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0, QObject* parent = 0);
signals:
void checkFinished(JavaCheckResult result);
void checkFinished(const Result& result);
protected:
virtual void executeTask() override;
private:
QProcessPtr process;
QTimer killTimer;
QString m_stdout;
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 finished(int exitcode, QProcess::ExitStatus);
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
/*
* 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
* 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
/*
* 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
* it under the terms of the GNU General Public License as published by
@ -40,6 +40,7 @@ struct JavaInstall : public BaseVersion {
QString arch;
QString path;
bool recommended = false;
bool is_64bit = false;
};
using JavaInstallPtr = std::shared_ptr<JavaInstall>;

View File

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

View File

@ -19,9 +19,9 @@
#include <QObject>
#include "BaseVersionList.h"
#include "java/JavaChecker.h"
#include "tasks/Task.h"
#include "JavaCheckerJob.h"
#include "JavaInstall.h"
#include "QObjectPtr.h"
@ -33,9 +33,9 @@ class JavaInstallList : public BaseVersionList {
enum class Status { NotDone, InProgress, Done };
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;
const BaseVersion::Ptr at(int i) const override;
int count() const override;
@ -53,23 +53,27 @@ class JavaInstallList : public BaseVersionList {
protected:
Status m_status = Status::NotDone;
shared_qobject_ptr<JavaListLoadTask> m_loadTask;
shared_qobject_ptr<JavaListLoadTask> m_load_task;
QList<BaseVersion::Ptr> m_vlist;
bool m_only_managed_versions;
};
class JavaListLoadTask : public Task {
Q_OBJECT
public:
explicit JavaListLoadTask(JavaInstallList* vlist);
virtual ~JavaListLoadTask();
explicit JavaListLoadTask(JavaInstallList* vlist, bool onlyManagedVersions = false);
virtual ~JavaListLoadTask() = default;
protected:
void executeTask() override;
public slots:
void javaCheckerFinished();
protected:
shared_qobject_ptr<JavaCheckerJob> m_job;
Task::Ptr m_job;
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)
archType = "32";
for (HKEY baseRegistry : { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE }) {
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) {
// Read the current type version from the registry.
// 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;
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) {
// Read the JavaHome value to find where Java is installed.
DWORD valueSz = 0;
@ -233,6 +234,7 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
RegCloseKey(jreKey);
}
}
return javas;
}
@ -345,6 +347,7 @@ QList<QString> JavaUtils::FindJavaPaths()
}
candidates.append(getMinecraftJavaBundle());
candidates.append(getPrismJavaBundle());
candidates = addJavasFromEnv(candidates);
candidates.removeDuplicates();
return candidates;
@ -389,6 +392,7 @@ QList<QString> JavaUtils::FindJavaPaths()
}
javas.append(getMinecraftJavaBundle());
javas.append(getPrismJavaBundle());
javas = addJavasFromEnv(javas);
javas.removeDuplicates();
return javas;
@ -452,6 +456,7 @@ QList<QString> JavaUtils::FindJavaPaths()
scanJavaDirs(FS::PathCombine(home, ".gradle/jdks"));
javas.append(getMinecraftJavaBundle());
javas.append(getPrismJavaBundle());
javas = addJavasFromEnv(javas);
javas.removeDuplicates();
return javas;
@ -465,6 +470,8 @@ QList<QString> JavaUtils::FindJavaPaths()
javas.append(this->GetDefaultJava()->path);
javas.append(getMinecraftJavaBundle());
javas.append(getPrismJavaBundle());
javas.removeDuplicates();
return addJavasFromEnv(javas);
}
#endif
@ -476,12 +483,10 @@ QString JavaUtils::getJavaCheckPath()
QStringList getMinecraftJavaBundle()
{
QString executable = "java";
QStringList processpaths;
#if defined(Q_OS_OSX)
processpaths << FS::PathCombine(QDir::homePath(), FS::PathCombine("Library", "Application Support", "minecraft", "runtime"));
#elif defined(Q_OS_WIN32)
executable += "w.exe";
auto appDataPath = QProcessEnvironment::systemEnvironment().value("APPDATA", "");
processpaths << FS::PathCombine(QFileInfo(appDataPath).absoluteFilePath(), ".minecraft", "runtime");
@ -506,7 +511,7 @@ QStringList getMinecraftJavaBundle()
auto binFound = false;
for (auto& entry : entries) {
if (entry.baseName() == "bin") {
javas.append(FS::PathCombine(entry.canonicalFilePath(), executable));
javas.append(FS::PathCombine(entry.canonicalFilePath(), JavaUtils::javaExecutable));
binFound = true;
break;
}
@ -519,3 +524,33 @@ QStringList getMinecraftJavaBundle()
}
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
#include <QProcess>
#include <QStringList>
#include "JavaChecker.h"
#include "JavaInstallList.h"
#include "java/JavaInstall.h"
#ifdef Q_OS_WIN
#include <windows.h>
@ -27,6 +26,7 @@
QString stripVariableEntries(QString name, QString target, QString remove);
QProcessEnvironment CleanEnviroment();
QStringList getMinecraftJavaBundle();
QStringList getPrismJavaBundle();
class JavaUtils : public QObject {
Q_OBJECT
@ -42,4 +42,5 @@ class JavaUtils : public QObject {
#endif
static QString getJavaCheckPath();
static const QString javaExecutable;
};

View File

@ -43,12 +43,12 @@ QString JavaVersion::toString() const
return m_string;
}
bool JavaVersion::requiresPermGen()
bool JavaVersion::requiresPermGen() const
{
return !m_parseable || m_major < 8;
}
bool JavaVersion::isModular()
bool JavaVersion::isModular() const
{
return m_parseable && m_major >= 9;
}
@ -59,12 +59,6 @@ bool JavaVersion::operator<(const JavaVersion& rhs)
auto major = 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)
return true;
if (major > rmajor)
@ -109,3 +103,24 @@ bool JavaVersion::operator>(const JavaVersion& 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:
JavaVersion() {}
JavaVersion(const QString& rhs);
JavaVersion(int major, int minor, int security, int build = 0, QString name = "");
JavaVersion& operator=(const QString& rhs);
@ -23,21 +24,24 @@ class JavaVersion {
bool operator==(const JavaVersion& rhs);
bool operator>(const JavaVersion& rhs);
bool requiresPermGen();
bool requiresPermGen() const;
bool isModular();
bool isModular() const;
QString toString() const;
int major() { return m_major; }
int minor() { return m_minor; }
int security() { return m_security; }
int major() const { return m_major; }
int minor() const { return m_minor; }
int security() const { return m_security; }
QString build() const { return m_prerelease; }
QString name() const { return m_name; }
private:
QString m_string;
int m_major = 0;
int m_minor = 0;
int m_security = 0;
QString m_name = "";
bool m_parseable = false;
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 <launch/LaunchTask.h>
#include <sys.h>
#include <QCryptographicHash>
#include <QFileInfo>
#include <QStandardPaths>
#include "java/JavaUtils.h"
@ -45,20 +46,23 @@ void CheckJava::executeTask()
{
auto instance = m_parent->instance();
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();
auto realJavaPath = QStandardPaths::findExecutable(m_javaPath);
if (realJavaPath.isEmpty()) {
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.")
.arg(m_javaPath),
.arg(javaPathSetting),
MessageLevel::Warning);
} 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.")
.arg(m_javaPath),
.arg(javaPathSetting),
MessageLevel::Warning);
}
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 (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.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);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
m_JavaChecker->m_path = realJavaPath;
m_JavaChecker->performCheck();
m_JavaChecker->start();
return;
} else {
auto verString = instance->settings()->get("JavaVersion").toString();
@ -106,10 +109,10 @@ void CheckJava::executeTask()
emitSucceeded();
}
void CheckJava::checkJavaFinished(JavaCheckResult result)
void CheckJava::checkJavaFinished(const JavaChecker::Result& result)
{
switch (result.validity) {
case JavaCheckResult::Validity::Errored: {
case JavaChecker::Result::Validity::Errored: {
// Error message displayed if java can't start
emit logLine(QString("Could not start java:"), 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!"));
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 logLines(result.outLog.split('\n'), MessageLevel::Warning);
emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher);
emitSucceeded();
return;
}
case JavaCheckResult::Validity::Valid: {
case JavaChecker::Result::Validity::Valid: {
auto instance = m_parent->instance();
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor);
instance->settings()->set("JavaVersion", result.javaVersion.toString());

View File

@ -28,7 +28,7 @@ class CheckJava : public LaunchStep {
virtual void executeTask();
virtual bool canAbort() const { return false; }
private slots:
void checkJavaFinished(JavaCheckResult result);
void checkJavaFinished(const JavaChecker::Result& result);
private:
void printJavaInfo(const QString& version, const QString& architecture, const QString& realArchitecture, const QString& vendor);
@ -37,5 +37,5 @@ class CheckJava : public LaunchStep {
private:
QString m_javaPath;
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));
}
void LookupServerAddress::setOutputAddressPtr(MinecraftServerTargetPtr output)
void LookupServerAddress::setOutputAddressPtr(MinecraftTarget::Ptr output)
{
m_output = std::move(output);
}

View File

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

View File

@ -15,19 +15,26 @@
#include "BaseEntity.h"
#include "Exception.h"
#include "FileSystem.h"
#include "Json.h"
#include "modplatform/helpers/HashUtils.h"
#include "net/ApiDownload.h"
#include "net/ChecksumValidator.h"
#include "net/HttpMetaCache.h"
#include "net/Mode.h"
#include "net/NetJob.h"
#include "Application.h"
#include "BuildConfig.h"
#include "tasks/Task.h"
namespace Meta {
class ParsingValidator : public Net::Validator {
public: /* con/des */
ParsingValidator(Meta::BaseEntity* entity) : m_entity(entity) {};
virtual ~ParsingValidator() {};
ParsingValidator(BaseEntity* entity) : m_entity(entity) {};
virtual ~ParsingValidator() = default;
public: /* methods */
bool init(QNetworkRequest&) override
@ -61,92 +68,131 @@ class ParsingValidator : public Net::Validator {
private: /* data */
QByteArray m_data;
Meta::BaseEntity* m_entity;
BaseEntity* m_entity;
};
Meta::BaseEntity::~BaseEntity() {}
QUrl Meta::BaseEntity::url() const
QUrl BaseEntity::url() const
{
auto s = APPLICATION->settings();
QString metaOverride = s->get("MetaURLOverride").toString();
if (metaOverride.isEmpty()) {
return QUrl(BuildConfig.META_URL).resolved(localFilename());
} else {
}
return QUrl(metaOverride).resolved(localFilename());
}
Task::Ptr BaseEntity::loadTask(Net::Mode mode)
{
if (m_task && m_task->isRunning()) {
return m_task;
}
m_task.reset(new BaseEntityLoadTask(this, mode));
return m_task;
}
bool Meta::BaseEntity::loadLocalFile()
bool BaseEntity::isLoaded() const
{
const QString fname = QDir("meta").absoluteFilePath(localFilename());
if (!QFile::exists(fname)) {
return false;
// 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;
}
// TODO: check if the file has the expected checksum
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 {
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);
parse(obj);
return true;
m_entity->parse(obj);
m_entity->m_load_status = BaseEntity::LoadStatus::Local;
}
} catch (const Exception& e) {
qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause());
// just make sure it's gone and we never consider it again.
return !FS::deletePath(fname);
}
}
void Meta::BaseEntity::load(Net::Mode loadType)
{
// load local file if nothing is loaded yet
if (!isLoaded()) {
if (loadLocalFile()) {
m_loadStatus = LoadStatus::Local;
FS::deletePath(fname);
m_entity->m_load_status = BaseEntity::LoadStatus::NotLoaded;
}
}
// 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;
}
m_updateTask.reset(new NetJob(QObject::tr("Download of meta file %1").arg(localFilename()), APPLICATION->network()));
auto url = this->url();
auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename());
m_task.reset(new NetJob(QObject::tr("Download of meta file %1").arg(m_entity->localFilename()), APPLICATION->network()));
auto url = m_entity->url();
auto entry = APPLICATION->metacache()->resolveEntry("meta", m_entity->localFilename());
entry->setStale(true);
auto dl = Net::ApiDownload::makeCached(url, entry);
/*
* The validator parses the file and loads it into the object.
* If that fails, the file is not written to storage.
*/
dl->addValidator(new ParsingValidator(this));
m_updateTask->addNetAction(dl);
m_updateStatus = UpdateStatus::InProgress;
QObject::connect(m_updateTask.get(), &NetJob::succeeded, [&]() {
m_loadStatus = LoadStatus::Remote;
m_updateStatus = UpdateStatus::Succeeded;
m_updateTask.reset();
if (!m_entity->m_sha256.isEmpty())
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Algorithm::Sha256, m_entity->m_sha256));
dl->addValidator(new ParsingValidator(m_entity));
m_task->addNetAction(dl);
m_task->setAskRetry(false);
connect(m_task.get(), &Task::failed, this, &BaseEntityLoadTask::emitFailed);
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;
m_updateTask.reset();
});
m_updateTask->start();
connect(m_task.get(), &Task::progress, this, &Task::setProgress);
connect(m_task.get(), &Task::stepProgress, this, &BaseEntityLoadTask::propagateStepProgress);
connect(m_task.get(), &Task::status, this, &Task::setStatus);
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?
return m_updateStatus != UpdateStatus::InProgress;
if (m_task) {
Task::abort();
return m_task->abort();
}
return Task::abort();
}
Task::Ptr Meta::BaseEntity::getCurrentTask()
{
if (m_updateStatus == UpdateStatus::InProgress) {
return m_updateTask;
}
return nullptr;
}
} // namespace Meta

View File

@ -17,38 +17,57 @@
#include <QJsonObject>
#include <QObject>
#include "QObjectPtr.h"
#include "net/Mode.h"
#include "net/NetJob.h"
#include "tasks/Task.h"
namespace Meta {
class BaseEntityLoadTask;
class BaseEntity {
friend BaseEntityLoadTask;
public: /* types */
using Ptr = std::shared_ptr<BaseEntity>;
enum class LoadStatus { NotLoaded, Local, Remote };
enum class UpdateStatus { NotDone, InProgress, Failed, Succeeded };
public:
virtual ~BaseEntity();
virtual void parse(const QJsonObject& obj) = 0;
virtual ~BaseEntity() = default;
virtual QString localFilename() const = 0;
virtual QUrl url() const;
bool isLoaded() const;
bool shouldStartRemoteUpdate() const;
LoadStatus status() const;
void load(Net::Mode loadType);
Task::Ptr getCurrentTask();
/* for parsers */
void setSha256(QString sha256);
protected: /* methods */
bool loadLocalFile();
virtual void parse(const QJsonObject& obj) = 0;
[[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:
LoadStatus m_loadStatus = LoadStatus::NotLoaded;
UpdateStatus m_updateStatus = UpdateStatus::NotDone;
NetJob::Ptr m_updateTask;
LoadStatus m_load_status = LoadStatus::NotLoaded;
Task::Ptr m_task;
};
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

View File

@ -16,7 +16,10 @@
#include "Index.h"
#include "JsonFormat.h"
#include "QObjectPtr.h"
#include "VersionList.h"
#include "meta/BaseEntity.h"
#include "tasks/SequentialTask.h"
namespace Meta {
Index::Index(QObject* parent) : QAbstractListModel(parent) {}
@ -51,14 +54,17 @@ QVariant Index::data(const QModelIndex& index, int role) const
}
return QVariant();
}
int Index::rowCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : m_lists.size();
}
int Index::columnCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : 1;
}
QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) {
@ -79,6 +85,7 @@ VersionList::Ptr Index::get(const QString& uid)
if (!out) {
out = std::make_shared<VersionList>(uid);
m_uids[uid] = out;
m_lists.append(out);
}
return out;
}
@ -96,7 +103,7 @@ void Index::parse(const QJsonObject& obj)
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
if (m_lists.isEmpty()) {
beginResetModel();
@ -123,7 +130,33 @@ void Index::merge(const std::shared_ptr<Index>& other)
void Index::connectVersionList(const int row, const VersionList::Ptr& list)
{
connect(list.get(), &VersionList::nameChanged, this,
[this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << Qt::DisplayRole); });
connect(list.get(), &VersionList::nameChanged, this, [this, row] { emit dataChanged(index(row), index(row), { 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

View File

@ -16,10 +16,10 @@
#pragma once
#include <QAbstractListModel>
#include <memory>
#include "BaseEntity.h"
#include "meta/VersionList.h"
#include "net/Mode.h"
class Task;
@ -30,6 +30,7 @@ class Index : public QAbstractListModel, public BaseEntity {
public:
explicit Index(QObject* parent = nullptr);
explicit Index(const QVector<VersionList::Ptr>& lists, QObject* parent = nullptr);
virtual ~Index() = default;
enum { UidRole = Qt::UserRole, NameRole, ListPtrRole };
@ -47,8 +48,15 @@ class Index : public QAbstractListModel, public BaseEntity {
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
void merge(const std::shared_ptr<Index>& other);
protected:
void parse(const QJsonObject& obj) override;
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) {
VersionList::Ptr list = std::make_shared<VersionList>(requireString(obj, "uid"));
list->setName(ensureString(obj, "name", QString()));
list->setSha256(ensureString(obj, "sha256", QString()));
return list;
});
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, &conflicts, "conflicts");
version->setRequires(reqs, conflicts);
if (auto sha256 = ensureString(obj, "sha256", QString()); !sha256.isEmpty()) {
version->setSha256(sha256);
}
return version;
}

View File

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

View File

@ -18,12 +18,9 @@
#include <QDateTime>
#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() {}
QString Meta::Version::descriptor()
{
return m_version;
@ -71,6 +68,9 @@ void Meta::Version::mergeFromList(const Meta::Version::Ptr& other)
if (m_volatile != 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)

View File

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

View File

@ -17,8 +17,13 @@
#include <QDateTime>
#include "Application.h"
#include "Index.h"
#include "JsonFormat.h"
#include "Version.h"
#include "meta/BaseEntity.h"
#include "net/Mode.h"
#include "tasks/SequentialTask.h"
namespace Meta {
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()
{
load(Net::Mode::Online);
return getCurrentTask();
auto loadTask =
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()
@ -92,6 +100,13 @@ QVariant VersionList::data(const QModelIndex& index, int role) const
return QVariant::fromValue(version);
case RecommendedRole:
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...
// case LatestRole: return version == getLatestStable();
default:
@ -101,10 +116,14 @@ QVariant VersionList::data(const QModelIndex& index, int role) const
BaseVersionList::RoleList VersionList::providesRoles() const
{
return { VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole, TypeRole, UidRole,
TimeRole, RequiresRole, SortRole, RecommendedRole, LatestRole, VersionPtrRole };
return m_provided_roles;
}
void VersionList::setProvidedRoles(RoleList roles)
{
m_provided_roles = roles;
};
QHash<int, QByteArray> VersionList::roleNames() const
{
QHash<int, QByteArray> roles = BaseVersionList::roleNames();
@ -131,6 +150,8 @@ Version::Ptr VersionList::getVersion(const QString& version)
if (!out) {
out = std::make_shared<Version>(m_uid, version);
m_lookup[version] = out;
setupAddedVersion(m_versions.size(), out);
m_versions.append(out);
}
return out;
}
@ -191,6 +212,9 @@ void VersionList::mergeFromIndex(const VersionList::Ptr& other)
if (m_name != other->m_name) {
setName(other->m_name);
}
if (!other->m_sha256.isEmpty()) {
m_sha256 = other->m_sha256;
}
}
void VersionList::merge(const VersionList::Ptr& other)
@ -198,23 +222,27 @@ void VersionList::merge(const VersionList::Ptr& other)
if (m_name != 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?
beginResetModel();
m_versions.clear();
if (other->m_versions.isEmpty()) {
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
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 {
m_lookup.insert(version->uid(), version);
}
m_lookup.insert(version->version(), version);
// connect it.
setupAddedVersion(m_versions.size(), version);
m_versions.append(version);
}
m_recommended = getBetterVersion(m_recommended, version);
}
endResetModel();
@ -222,14 +250,15 @@ void VersionList::merge(const VersionList::Ptr& other)
void VersionList::setupAddedVersion(const int row, const Version::Ptr& version)
{
// FIXME: do not disconnect from everythin, disconnect only the lambdas here
version->disconnect();
disconnect(version.get(), &Version::requiresChanged, this, nullptr);
disconnect(version.get(), &Version::timeChanged, this, nullptr);
disconnect(version.get(), &Version::typeChanged, this, nullptr);
connect(version.get(), &Version::requiresChanged, this,
[this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << RequiresRole); });
connect(version.get(), &Version::timeChanged, this,
[this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TimeRole << SortRole); });
connect(version.get(), &Version::typeChanged, this,
[this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TypeRole); });
[this, row]() { emit dataChanged(index(row), index(row), { TimeRole, SortRole }); });
connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), { TypeRole }); });
}
BaseVersion::Ptr VersionList::getRecommended() const
@ -237,4 +266,14 @@ BaseVersion::Ptr VersionList::getRecommended() const
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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,6 +13,7 @@
#include "net/Mode.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
@ -93,9 +94,9 @@ static LoadResult loadComponent(ComponentPtr component, Task::Ptr& loadTask, Net
component->m_loaded = true;
result = LoadResult::LoadedLocal;
} else {
metaVersion->load(netmode);
loadTask = metaVersion->getCurrentTask();
if (loadTask)
loadTask = APPLICATION->metadataIndex()->loadVersion(component->m_uid, component->m_version, netmode);
loadTask->start();
if (netmode == Net::Mode::Online)
result = LoadResult::RequiresRemote;
else if (metaVersion->isLoaded())
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
void ComponentUpdateTask::loadComponents()
@ -156,23 +142,7 @@ void ComponentUpdateTask::loadComponents()
size_t taskIndex = 0;
size_t componentIndex = 0;
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...
for (auto component : d->m_list->d->components) {
Task::Ptr loadTask;
@ -206,12 +176,13 @@ void ComponentUpdateTask::loadComponents()
result = composeLoadResult(result, singleResult);
if (loadTask) {
qDebug() << "Remote loading is being run for" << component->getName();
connect(loadTask.get(), &Task::succeeded, [=]() { remoteLoadSucceeded(taskIndex); });
connect(loadTask.get(), &Task::failed, [=](const QString& error) { remoteLoadFailed(taskIndex, error); });
connect(loadTask.get(), &Task::aborted, [=]() { remoteLoadFailed(taskIndex, tr("Aborted")); });
connect(loadTask.get(), &Task::succeeded, this, [this, taskIndex]() { remoteLoadSucceeded(taskIndex); });
connect(loadTask.get(), &Task::failed, this, [this, taskIndex](const QString& error) { remoteLoadFailed(taskIndex, error); });
connect(loadTask.get(), &Task::aborted, this, [this, taskIndex]() { remoteLoadFailed(taskIndex, tr("Aborted")); });
RemoteLoadStatus status;
status.type = loadType;
status.PackProfileIndex = componentIndex;
status.task = loadTask;
d->remoteLoadStatusList.append(status);
taskIndex++;
}
@ -518,7 +489,14 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
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];
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) {
qWarning() << "Got multiple results from remote load task" << taskIndex;
return;
@ -538,7 +516,14 @@ void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
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];
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) {
qWarning() << "Got multiple results from remote load task" << taskIndex;
return;

View File

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

View File

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

View File

@ -59,6 +59,7 @@ class LaunchProfile : public ProblemProvider {
void applyMavenFile(LibraryPtr library, const RuntimeContext& runtimeContext);
void applyAgent(AgentPtr agent, const RuntimeContext& runtimeContext);
void applyCompatibleJavaMajors(QList<int>& javaMajor);
void applyCompatibleJavaName(QString javaName);
void applyMainJar(LibraryPtr jar);
void applyProblemSeverity(ProblemSeverity severity);
/// clear the profile
@ -80,6 +81,7 @@ class LaunchProfile : public ProblemProvider {
const QList<LibraryPtr>& getMavenFiles() const;
const QList<AgentPtr>& getAgents() const;
const QList<int>& getCompatibleJavaMajors() const;
const QString getCompatibleJavaName() const;
const LibraryPtr getMainJar() const;
void getLibraryFiles(const RuntimeContext& runtimeContext,
QStringList& jars,
@ -150,5 +152,7 @@ class LaunchProfile : public ProblemProvider {
/// compatible java major versions
QList<int> m_compatibleJavaMajors;
QString m_compatibleJavaName;
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;
if (sha1.size()) {
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
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;
out.append(dl);
} else {

View File

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

View File

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

View File

@ -16,32 +16,22 @@
#include "MinecraftUpdate.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 "tasks/SequentialTask.h"
#include "update/AssetUpdateTask.h"
#include "update/FMLLibrariesTask.h"
#include "update/FoldersTask.h"
#include "update/LibrariesTask.h"
#include <meta/Index.h>
#include <meta/Version.h>
MinecraftUpdate::MinecraftUpdate(MinecraftInstance* inst, QObject* parent) : Task(parent), m_inst(inst) {}
MinecraftUpdate::MinecraftUpdate(MinecraftInstance* inst, QObject* parent) : SequentialTask(parent), m_inst(inst) {}
void MinecraftUpdate::executeTask()
{
m_tasks.clear();
m_queue.clear();
// create folders
{
m_tasks.append(makeShared<FoldersTask>(m_inst));
addTask(makeShared<FoldersTask>(m_inst));
}
// add metadata update task if necessary
@ -50,121 +40,24 @@ void MinecraftUpdate::executeTask()
components->reload(Net::Mode::Online);
auto task = components->getCurrentTask();
if (task) {
m_tasks.append(task);
addTask(task);
}
}
// libraries download
{
m_tasks.append(makeShared<LibrariesTask>(m_inst));
addTask(makeShared<LibrariesTask>(m_inst));
}
// FML libraries download and copy into the instance
{
m_tasks.append(makeShared<FMLLibrariesTask>(m_inst));
addTask(makeShared<FMLLibrariesTask>(m_inst));
}
// assets update
{
m_tasks.append(makeShared<AssetUpdateTask>(m_inst));
addTask(makeShared<AssetUpdateTask>(m_inst));
}
if (!m_preFailure.isEmpty()) {
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;
SequentialTask::executeTask();
}

View File

@ -15,43 +15,19 @@
#pragma once
#include <QList>
#include <QObject>
#include <QUrl>
#include "tasks/SequentialTask.h"
#include <quazip/quazip.h>
#include "minecraft/VersionFilterData.h"
#include "net/NetJob.h"
#include "tasks/Task.h"
class MinecraftVersion;
class MinecraftInstance;
// FIXME: This looks very similar to a SequentialTask. Maybe we can reduce code duplications? :^)
class MinecraftUpdate : public Task {
// this needs to be a task because components->reload does stuff that may block
class MinecraftUpdate : public SequentialTask {
Q_OBJECT
public:
explicit MinecraftUpdate(MinecraftInstance* inst, QObject* parent = 0);
virtual ~MinecraftUpdate() {};
virtual ~MinecraftUpdate() = default;
void executeTask() override;
bool canAbort() const override;
private slots:
bool abort() override;
void subtaskSucceeded();
void subtaskFailed(QString error);
private:
void next();
private:
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));
}
}
if (in.contains("compatibleJavaName")) {
out->compatibleJavaName = requireString(in.value("compatibleJavaName"));
}
if (in.contains("downloads")) {
auto downloadsObj = requireObject(in, "downloads");
@ -259,6 +262,9 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
}
out.insert("compatibleJavaMajors", compatibleJavaMajorsOut);
}
if (!in->compatibleJavaName.isEmpty()) {
writeString(out, "compatibleJavaName", in->compatibleJavaName);
}
}
QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr& patch)

View File

@ -36,6 +36,8 @@
#include "OneSixVersionFormat.h"
#include <Json.h>
#include <minecraft/MojangVersionFormat.h>
#include <QList>
#include "java/JavaMetadata.h"
#include "minecraft/Agent.h"
#include "minecraft/ParseUtils.h"
@ -255,6 +257,13 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc
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 */
if (root.contains("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->applyTraits(traits);
profile->applyCompatibleJavaMajors(compatibleJavaMajors);
profile->applyCompatibleJavaName(compatibleJavaName);
for (auto library : libraries) {
profile->applyLibrary(library, runtimeContext);

View File

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

View File

@ -74,6 +74,8 @@ void MSADeviceCodeStep::perform()
m_task->setAskRetry(false);
m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAutorizationFinished);
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();
QString allArgs = args.join(", ");
emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::Launcher);

View File

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

View File

@ -13,13 +13,18 @@
* limitations under the License.
*/
#include "MinecraftServerTarget.h"
#include "MinecraftTarget.h"
#include <QStringList>
// 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(":");
// 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>
struct MinecraftServerTarget {
struct MinecraftTarget {
QString address;
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
logLines(log, MessageLevel::Launcher);
logLines(instance->verboseDescription(m_session, m_serverToJoin), MessageLevel::Launcher);
logLines(instance->verboseDescription(m_session, m_targetToJoin), MessageLevel::Launcher);
emitSucceeded();
}

View File

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

View File

@ -34,7 +34,12 @@
*/
#include "VerifyJavaInstall.h"
#include <memory>
#include "Application.h"
#include "MessageLevel.h"
#include "java/JavaInstall.h"
#include "java/JavaInstallList.h"
#include "java/JavaVersion.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
@ -46,6 +51,15 @@ void VerifyJavaInstall::executeTask()
auto settings = instance->settings();
auto storedVersion = settings->get("JavaVersion").toString();
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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -353,5 +353,11 @@
<file>scalable/instances/neoforged.svg</file>
<file>128x128/instances/forge.png</file> <!-- LGPL3 Forge Development LLC -->
<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>
</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);
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, rawHash));
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, lang->file_sha1));
dl->setProgress(dl->getProgress(), lang->file_size);
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