Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into feature/java-downloader

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97 2024-06-28 20:45:30 +03:00
commit 0b79abcdcd
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
116 changed files with 713 additions and 435 deletions

View File

@ -79,7 +79,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: "" qt_arch: ""
qt_version: "6.7.0" qt_version: "6.7.1"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: windows-2022 - os: windows-2022
@ -90,7 +90,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: "win64_msvc2019_arm64" qt_arch: "win64_msvc2019_arm64"
qt_version: "6.7.0" qt_version: "6.7.1"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: macos-12 - os: macos-12
@ -99,7 +99,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: mac qt_host: mac
qt_arch: "" qt_arch: ""
qt_version: "6.7.0" qt_version: "6.7.1"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: macos-12 - os: macos-12

View File

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

12
flake.lock generated
View File

@ -75,16 +75,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1717774105, "lastModified": 1718276985,
"narHash": "sha256-HV97wqUQv9wvptiHCb3Y0/YH0lJ60uZ8FYfEOIzYEqI=", "narHash": "sha256-u1fA0DYQYdeG+5kDm1bOoGcHtX0rtC7qs2YA2N1X++I=",
"owner": "nixos", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "d226935fd75012939397c83f6c385e4d6d832288", "rev": "3f84a279f1a6290ce154c5531378acc827836fbb",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nixos", "owner": "NixOS",
"ref": "nixpkgs-unstable", "ref": "nixos-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }

View File

@ -7,7 +7,7 @@
}; };
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts = { flake-parts = {
url = "github:hercules-ci/flake-parts"; url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs"; inputs.nixpkgs-lib.follows = "nixpkgs";

View File

@ -852,24 +852,17 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{ {
m_metacache.reset(new HttpMetaCache("metacache")); m_metacache.reset(new HttpMetaCache("metacache"));
m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath()); m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath());
m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath());
m_metacache->addBase("versions", QDir("versions").absolutePath());
m_metacache->addBase("libraries", QDir("libraries").absolutePath()); m_metacache->addBase("libraries", QDir("libraries").absolutePath());
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath()); m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
m_metacache->addBase("general", QDir("cache").absolutePath()); m_metacache->addBase("general", QDir("cache").absolutePath());
m_metacache->addBase("ATLauncherPacks", QDir("cache/ATLauncherPacks").absolutePath()); m_metacache->addBase("ATLauncherPacks", QDir("cache/ATLauncherPacks").absolutePath());
m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath()); m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath()); m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath()); m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
m_metacache->addBase("FlameMods", QDir("cache/FlameMods").absolutePath()); m_metacache->addBase("FlameMods", QDir("cache/FlameMods").absolutePath());
m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath()); m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath());
m_metacache->addBase("ModrinthModpacks", QDir("cache/ModrinthModpacks").absolutePath()); m_metacache->addBase("ModrinthModpacks", QDir("cache/ModrinthModpacks").absolutePath());
m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
m_metacache->addBase("meta", QDir("meta").absolutePath()); m_metacache->addBase("meta", QDir("meta").absolutePath());
m_metacache->addBase("java", QDir("cache/java").absolutePath()); m_metacache->addBase("java", QDir("cache/java").absolutePath());
m_metacache->Load(); m_metacache->Load();
@ -951,8 +944,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
[[fallthrough]]; [[fallthrough]];
default: { default: {
qDebug() << "Exiting because update lockfile is present"; qDebug() << "Exiting because update lockfile is present";
QMetaObject::invokeMethod( QMetaObject::invokeMethod(this, []() { exit(1); }, Qt::QueuedConnection);
this, []() { exit(1); }, Qt::QueuedConnection);
return; return;
} }
} }
@ -984,8 +976,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
[[fallthrough]]; [[fallthrough]];
default: { default: {
qDebug() << "Exiting because update lockfile is present"; qDebug() << "Exiting because update lockfile is present";
QMetaObject::invokeMethod( QMetaObject::invokeMethod(this, []() { exit(1); }, Qt::QueuedConnection);
this, []() { exit(1); }, Qt::QueuedConnection);
return; return;
} }
} }
@ -997,7 +988,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
"\n" "\n"
"You are now running %1 .\n" "You are now running %1 .\n"
"Check the Prism Launcher updater log at: \n" "Check the Prism Launcher updater log at: \n"
"%1\n" "%2\n"
"for details.") "for details.")
.arg(BuildConfig.printableVersionString()) .arg(BuildConfig.printableVersionString())
.arg(update_log_path); .arg(update_log_path);
@ -1679,8 +1670,7 @@ QString Application::getJarPath(QString jarFile)
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
FS::PathCombine(m_rootPath, "share", BuildConfig.LAUNCHER_NAME), FS::PathCombine(m_rootPath, "share", BuildConfig.LAUNCHER_NAME),
#endif #endif
FS::PathCombine(m_rootPath, "jars"), FS::PathCombine(m_rootPath, "jars"), FS::PathCombine(applicationDirPath(), "jars"),
FS::PathCombine(applicationDirPath(), "jars"),
FS::PathCombine(applicationDirPath(), "..", "jars") // from inside build dir, for debuging FS::PathCombine(applicationDirPath(), "..", "jars") // from inside build dir, for debuging
}; };
for (QString p : potentialPaths) { for (QString p : potentialPaths) {

View File

@ -29,7 +29,7 @@ class BaseVersion;
class BaseInstaller { class BaseInstaller {
public: public:
BaseInstaller(); BaseInstaller();
virtual ~BaseInstaller(){}; virtual ~BaseInstaller() {};
bool isApplied(MinecraftInstance* on); bool isApplied(MinecraftInstance* on);
virtual bool add(MinecraftInstance* to); virtual bool add(MinecraftInstance* to);

View File

@ -269,13 +269,18 @@ void BaseInstance::setRunning(bool running)
m_isRunning = running; m_isRunning = running;
if (!m_settings->get("RecordGameTime").toBool()) { emit runningStatusChanged(running);
emit runningStatusChanged(running); }
void BaseInstance::setMinecraftRunning(bool running)
{
if (!settings()->get("RecordGameTime").toBool()) {
return; return;
} }
if (running) { if (running) {
m_timeStarted = QDateTime::currentDateTime(); m_timeStarted = QDateTime::currentDateTime();
setLastLaunch(m_timeStarted.toMSecsSinceEpoch());
} else { } else {
QDateTime timeEnded = QDateTime::currentDateTime(); QDateTime timeEnded = QDateTime::currentDateTime();
@ -285,8 +290,6 @@ void BaseInstance::setRunning(bool running)
emit propertiesChanged(this); emit propertiesChanged(this);
} }
emit runningStatusChanged(running);
} }
int64_t BaseInstance::totalTimePlayed() const int64_t BaseInstance::totalTimePlayed() const

View File

@ -104,6 +104,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
/// be unique. /// be unique.
virtual QString id() const; virtual QString id() const;
void setMinecraftRunning(bool running);
void setRunning(bool running); void setRunning(bool running);
bool isRunning() const; bool isRunning() const;
int64_t totalTimePlayed() const; int64_t totalTimePlayed() const;

View File

@ -165,6 +165,8 @@ set(LAUNCH_SOURCES
launch/steps/Update.h launch/steps/Update.h
launch/steps/QuitAfterGameStop.cpp launch/steps/QuitAfterGameStop.cpp
launch/steps/QuitAfterGameStop.h launch/steps/QuitAfterGameStop.h
launch/steps/PrintServers.cpp
launch/steps/PrintServers.h
launch/LaunchStep.cpp launch/LaunchStep.cpp
launch/LaunchStep.h launch/LaunchStep.h
launch/LaunchTask.cpp launch/LaunchTask.cpp

View File

@ -818,20 +818,11 @@ QString NormalizePath(QString path)
} }
} }
QString removeDuplicates(QString a) static const QString BAD_WIN_CHARS = "<>:\"|?*\r\n";
{
auto b = a.split("");
b.removeDuplicates();
return b.join("");
}
static const QString BAD_WIN_CHARS = "\"?<>:*|\r\n";
static const QString BAD_FAT_CHARS = "<>:\"|?*+.,;=[]!";
static const QString BAD_NTFS_CHARS = "<>:\"|?*"; static const QString BAD_NTFS_CHARS = "<>:\"|?*";
static const QString BAD_HFS_CHARS = ":"; static const QString BAD_HFS_CHARS = ":";
static const QString BAD_FILENAME_CHARS = removeDuplicates(BAD_WIN_CHARS + BAD_FAT_CHARS + BAD_NTFS_CHARS + BAD_HFS_CHARS) + "\\/"; static const QString BAD_FILENAME_CHARS = BAD_WIN_CHARS + "\\/";
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
{ {
@ -850,9 +841,8 @@ QString RemoveInvalidPathChars(QString path, QChar replaceWith)
// the null character is ignored in this check as it was not a problem until now // the null character is ignored in this check as it was not a problem until now
switch (statFS(path).fsType) { switch (statFS(path).fsType) {
case FilesystemType::FAT: case FilesystemType::FAT: // similar to NTFS
invalidChars += BAD_FAT_CHARS; /* fallthrough */
break;
case FilesystemType::NTFS: case FilesystemType::NTFS:
/* fallthrough */ /* fallthrough */
case FilesystemType::REFS: // similar to NTFS(should be available only on windows) case FilesystemType::REFS: // similar to NTFS(should be available only on windows)

View File

@ -22,7 +22,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
public: public:
explicit InstancePageProvider(InstancePtr parent) { inst = parent; } explicit InstancePageProvider(InstancePtr parent) { inst = parent; }
virtual ~InstancePageProvider(){}; virtual ~InstancePageProvider() {};
virtual QList<BasePage*> getPages() override virtual QList<BasePage*> getPages() override
{ {
QList<BasePage*> values; QList<BasePage*> values;

View File

@ -24,7 +24,7 @@ class TestCheck : public QObject {
TestCheck(QWidget* parent, QString path, QString args, int minMem, int maxMem, int permGen) TestCheck(QWidget* parent, QString path, QString args, int minMem, int maxMem, int permGen)
: m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen) : m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen)
{} {}
virtual ~TestCheck(){}; virtual ~TestCheck() {};
void run(); void run();

View File

@ -36,6 +36,7 @@
#include "LaunchController.h" #include "LaunchController.h"
#include "Application.h" #include "Application.h"
#include "launch/steps/PrintServers.h"
#include "minecraft/auth/AccountData.h" #include "minecraft/auth/AccountData.h"
#include "minecraft/auth/AccountList.h" #include "minecraft/auth/AccountList.h"
@ -193,7 +194,8 @@ void LaunchController::login()
bool tryagain = true; bool tryagain = true;
unsigned int tries = 0; unsigned int tries = 0;
if (m_accountToUse->accountType() != AccountType::Offline && m_accountToUse->accountState() == AccountState::Offline) { if ((m_accountToUse->accountType() != AccountType::Offline && m_accountToUse->accountState() == AccountState::Offline) ||
m_accountToUse->shouldRefresh()) {
// Force account refresh on the account used to launch the instance updating the AccountState // Force account refresh on the account used to launch the instance updating the AccountState
// only on first try and if it is not meant to be offline // only on first try and if it is not meant to be offline
auto accounts = APPLICATION->accounts(); auto accounts = APPLICATION->accounts();
@ -345,25 +347,8 @@ void LaunchController::launchInstance()
// Prepend Server Status // Prepend Server Status
QStringList servers = { "login.microsoftonline.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com" }; QStringList servers = { "login.microsoftonline.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com" };
QString resolved_servers = "";
QHostInfo host_info;
for (QString server : servers) { m_launcher->prependStep(makeShared<PrintServers>(m_launcher.get(), servers));
host_info = QHostInfo::fromName(server);
resolved_servers = resolved_servers + server + " resolves to:\n [";
if (!host_info.addresses().isEmpty()) {
for (QHostAddress address : host_info.addresses()) {
resolved_servers = resolved_servers + address.toString();
if (!host_info.addresses().endsWith(address)) {
resolved_servers = resolved_servers + ", ";
}
}
} else {
resolved_servers = resolved_servers + "N/A";
}
resolved_servers = resolved_servers + "]\n\n";
}
m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), resolved_servers, MessageLevel::Launcher));
} else { } else {
online_mode = m_demo ? "demo" : "offline"; online_mode = m_demo ? "demo" : "offline";
} }

View File

@ -48,7 +48,7 @@ class LaunchController : public Task {
void executeTask() override; void executeTask() override;
LaunchController(QObject* parent = nullptr); LaunchController(QObject* parent = nullptr);
virtual ~LaunchController(){}; virtual ~LaunchController() {};
void setInstance(InstancePtr instance) { m_instance = instance; } void setInstance(InstancePtr instance) { m_instance = instance; }

View File

@ -176,7 +176,7 @@ class ExportToZipTask : public Task {
QString destinationPrefix = "", QString destinationPrefix = "",
bool followSymlinks = false, bool followSymlinks = false,
bool utf8Enabled = false) bool utf8Enabled = false)
: ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks, utf8Enabled){}; : ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks, utf8Enabled) {};
virtual ~ExportToZipTask() = default; virtual ~ExportToZipTask() = default;
@ -216,7 +216,7 @@ class ExtractZipTask : public Task {
{} {}
virtual ~ExtractZipTask() = default; virtual ~ExtractZipTask() = default;
typedef std::optional<QString> ZipResult; using ZipResult = std::optional<QString>;
protected: protected:
virtual void executeTask() override; virtual void executeTask() override;

View File

@ -46,7 +46,7 @@ class NullInstance : public BaseInstance {
{ {
setVersionBroken(true); setVersionBroken(true);
} }
virtual ~NullInstance(){}; virtual ~NullInstance() {};
void saveNow() override {} void saveNow() override {}
void loadSpecificSettings() override { setSpecificSettingsLoaded(true); } void loadSpecificSettings() override { setSpecificSettingsLoaded(true); }
QString getStatusbarDescription() override { return tr("Unknown instance type"); }; QString getStatusbarDescription() override { return tr("Unknown instance type"); };

View File

@ -14,7 +14,7 @@ class VersionProxyModel : public QAbstractProxyModel {
public: public:
VersionProxyModel(QObject* parent = 0); VersionProxyModel(QObject* parent = 0);
virtual ~VersionProxyModel(){}; virtual ~VersionProxyModel() {};
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;

View File

@ -52,7 +52,7 @@ class IconList : public QAbstractListModel {
Q_OBJECT Q_OBJECT
public: public:
explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0); explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0);
virtual ~IconList(){}; virtual ~IconList() {};
QIcon getIcon(const QString& key) const; QIcon getIcon(const QString& key) const;
int getIconIndex(const QString& key) const; int getIconIndex(const QString& key) const;

View File

@ -79,11 +79,9 @@ QProcessEnvironment CleanEnviroment()
QStringList stripped = { QStringList stripped = {
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
"LD_LIBRARY_PATH", "LD_LIBRARY_PATH", "LD_PRELOAD",
"LD_PRELOAD",
#endif #endif
"QT_PLUGIN_PATH", "QT_PLUGIN_PATH", "QT_FONTPATH"
"QT_FONTPATH"
}; };
for (auto key : rawenv.keys()) { for (auto key : rawenv.keys()) {
auto value = rawenv.value(key); auto value = rawenv.value(key);
@ -377,7 +375,19 @@ QList<QString> JavaUtils::FindJavaPaths()
auto home = qEnvironmentVariable("HOME"); auto home = qEnvironmentVariable("HOME");
// javas downloaded by sdkman // javas downloaded by sdkman
javas.append(FS::PathCombine(home, ".sdkman/candidates/java")); QDir sdkmanDir(FS::PathCombine(home, ".sdkman/candidates/java"));
QStringList sdkmanJavas = sdkmanDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
foreach (const QString& java, sdkmanJavas) {
javas.append(sdkmanDir.absolutePath() + "/" + java + "/bin/java");
}
// java in user library folder (like from intellij downloads)
QDir userLibraryJVMDir(FS::PathCombine(home, "Library/Java/JavaVirtualMachines/"));
QStringList userLibraryJVMJavas = userLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
foreach (const QString& java, userLibraryJVMJavas) {
javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
}
javas.append(getMinecraftJavaBundle()); javas.append(getMinecraftJavaBundle());
javas.append(getPrismJavaBundle()); javas.append(getPrismJavaBundle());
@ -417,6 +427,10 @@ QList<QString> JavaUtils::FindJavaPaths()
scanJavaDirs("/usr/lib/jvm"); scanJavaDirs("/usr/lib/jvm");
scanJavaDirs("/usr/lib64/jvm"); scanJavaDirs("/usr/lib64/jvm");
scanJavaDirs("/usr/lib32/jvm"); scanJavaDirs("/usr/lib32/jvm");
// Gentoo's locations for openjdk and openjdk-bin respectively
scanJavaDir("/usr/lib64");
scanJavaDir("/usr/lib");
scanJavaDir("/opt");
// javas stored in Prism Launcher's folder // javas stored in Prism Launcher's folder
scanJavaDirs("java"); scanJavaDirs("java");
// manually installed JDKs in /opt // manually installed JDKs in /opt

View File

@ -25,7 +25,7 @@ class LaunchStep : public Task {
Q_OBJECT Q_OBJECT
public: /* methods */ public: /* methods */
explicit LaunchStep(LaunchTask* parent) : Task(nullptr), m_parent(parent) { bind(parent); }; explicit LaunchStep(LaunchTask* parent) : Task(nullptr), m_parent(parent) { bind(parent); };
virtual ~LaunchStep(){}; virtual ~LaunchStep() {};
private: /* methods */ private: /* methods */
void bind(LaunchTask* parent); void bind(LaunchTask* parent);
@ -37,9 +37,9 @@ class LaunchStep : public Task {
void progressReportingRequest(); void progressReportingRequest();
public slots: public slots:
virtual void proceed(){}; virtual void proceed() {};
// called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends // called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends
virtual void finalize(){}; virtual void finalize() {};
protected: /* data */ protected: /* data */
LaunchTask* m_parent; LaunchTask* m_parent;

View File

@ -55,7 +55,7 @@ class LaunchTask : public Task {
public: /* methods */ public: /* methods */
static shared_qobject_ptr<LaunchTask> create(InstancePtr inst); static shared_qobject_ptr<LaunchTask> create(InstancePtr inst);
virtual ~LaunchTask(){}; virtual ~LaunchTask() {};
void appendStep(shared_qobject_ptr<LaunchStep> step); void appendStep(shared_qobject_ptr<LaunchStep> step);
void prependStep(shared_qobject_ptr<LaunchStep> step); void prependStep(shared_qobject_ptr<LaunchStep> step);

View File

@ -22,8 +22,8 @@
class CheckJava : public LaunchStep { class CheckJava : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit CheckJava(LaunchTask* parent) : LaunchStep(parent){}; explicit CheckJava(LaunchTask* parent) : LaunchStep(parent) {};
virtual ~CheckJava(){}; virtual ~CheckJava() {};
virtual void executeTask(); virtual void executeTask();
virtual bool canAbort() const { return false; } virtual bool canAbort() const { return false; }

View File

@ -25,7 +25,7 @@ class LookupServerAddress : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit LookupServerAddress(LaunchTask* parent); explicit LookupServerAddress(LaunchTask* parent);
virtual ~LookupServerAddress(){}; virtual ~LookupServerAddress() {};
virtual void executeTask(); virtual void executeTask();
virtual bool abort(); virtual bool abort();

View File

@ -22,7 +22,7 @@ class PostLaunchCommand : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit PostLaunchCommand(LaunchTask* parent); explicit PostLaunchCommand(LaunchTask* parent);
virtual ~PostLaunchCommand(){}; virtual ~PostLaunchCommand() {};
virtual void executeTask(); virtual void executeTask();
virtual bool abort(); virtual bool abort();

View File

@ -22,7 +22,7 @@ class PreLaunchCommand : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit PreLaunchCommand(LaunchTask* parent); explicit PreLaunchCommand(LaunchTask* parent);
virtual ~PreLaunchCommand(){}; virtual ~PreLaunchCommand() {};
virtual void executeTask(); virtual void executeTask();
virtual bool abort(); virtual bool abort();

View File

@ -0,0 +1,65 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2024 Leia uwu <leia@tutamail.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 "PrintServers.h"
#include "QHostInfo"
PrintServers::PrintServers(LaunchTask* parent, const QStringList& servers) : LaunchStep(parent)
{
m_servers = servers;
}
void PrintServers::executeTask()
{
for (QString server : m_servers) {
QHostInfo::lookupHost(server, this, &PrintServers::resolveServer);
}
}
void PrintServers::resolveServer(const QHostInfo& host_info)
{
QString server = host_info.hostName();
QString addresses = server + " resolves to:\n [";
if (!host_info.addresses().isEmpty()) {
for (QHostAddress address : host_info.addresses()) {
addresses += address.toString();
if (!host_info.addresses().endsWith(address)) {
addresses += ", ";
}
}
} else {
addresses += "N/A";
}
addresses += "]\n\n";
m_server_to_address.insert(server, addresses);
// print server info in order once all servers are resolved
if (m_server_to_address.size() >= m_servers.size()) {
for (QString serv : m_servers) {
emit logLine(m_server_to_address.value(serv), MessageLevel::Launcher);
}
emitSucceeded();
}
}
bool PrintServers::canAbort() const
{
return true;
}

View File

@ -0,0 +1,37 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2024 Leia uwu <leia@tutamail.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 <LoggedProcess.h>
#include <java/JavaChecker.h>
#include <launch/LaunchStep.h>
#include <QHostInfo>
class PrintServers : public LaunchStep {
Q_OBJECT
public:
PrintServers(LaunchTask* parent, const QStringList& servers);
virtual void executeTask();
virtual bool canAbort() const;
private:
void resolveServer(const QHostInfo& host_info);
QMap<QString, QString> m_server_to_address;
QStringList m_servers;
};

View File

@ -23,8 +23,8 @@
class QuitAfterGameStop : public LaunchStep { class QuitAfterGameStop : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit QuitAfterGameStop(LaunchTask* parent) : LaunchStep(parent){}; explicit QuitAfterGameStop(LaunchTask* parent) : LaunchStep(parent) {};
virtual ~QuitAfterGameStop(){}; virtual ~QuitAfterGameStop() {};
virtual void executeTask(); virtual void executeTask();
virtual bool canAbort() const { return false; } virtual bool canAbort() const { return false; }

View File

@ -28,7 +28,7 @@ class TextPrint : public LaunchStep {
public: public:
explicit TextPrint(LaunchTask* parent, const QStringList& lines, MessageLevel::Enum level); explicit TextPrint(LaunchTask* parent, const QStringList& lines, MessageLevel::Enum level);
explicit TextPrint(LaunchTask* parent, const QString& line, MessageLevel::Enum level); explicit TextPrint(LaunchTask* parent, const QString& line, MessageLevel::Enum level);
virtual ~TextPrint(){}; virtual ~TextPrint() {};
virtual void executeTask(); virtual void executeTask();
virtual bool canAbort() const; virtual bool canAbort() const;

View File

@ -25,8 +25,8 @@
class Update : public LaunchStep { class Update : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit Update(LaunchTask* parent, Net::Mode mode) : LaunchStep(parent), m_mode(mode){}; explicit Update(LaunchTask* parent, Net::Mode mode) : LaunchStep(parent), m_mode(mode) {};
virtual ~Update(){}; virtual ~Update() {};
void executeTask() override; void executeTask() override;
bool canAbort() const override; bool canAbort() const override;

View File

@ -26,8 +26,8 @@
class ParsingValidator : public Net::Validator { class ParsingValidator : public Net::Validator {
public: /* con/des */ public: /* con/des */
ParsingValidator(Meta::BaseEntity* entity) : m_entity(entity){}; ParsingValidator(Meta::BaseEntity* entity) : m_entity(entity) {};
virtual ~ParsingValidator(){}; virtual ~ParsingValidator() {};
public: /* methods */ public: /* methods */
bool init(QNetworkRequest&) override { return true; } bool init(QNetworkRequest&) override { return true; }

View File

@ -56,7 +56,7 @@ class MinecraftInstance : public BaseInstance {
Q_OBJECT Q_OBJECT
public: public:
MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir); MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir);
virtual ~MinecraftInstance(){}; virtual ~MinecraftInstance() {};
virtual void saveNow() override; virtual void saveNow() override;
void loadSpecificSettings() override; void loadSpecificSettings() override;

View File

@ -31,7 +31,7 @@ class MinecraftLoadAndCheck : public Task {
Q_OBJECT Q_OBJECT
public: public:
explicit MinecraftLoadAndCheck(MinecraftInstance* inst, QObject* parent = 0); explicit MinecraftLoadAndCheck(MinecraftInstance* inst, QObject* parent = 0);
virtual ~MinecraftLoadAndCheck(){}; virtual ~MinecraftLoadAndCheck() {};
void executeTask() override; void executeTask() override;
private slots: private slots:

View File

@ -33,7 +33,7 @@ class MinecraftUpdate : public Task {
Q_OBJECT Q_OBJECT
public: public:
explicit MinecraftUpdate(MinecraftInstance* inst, QObject* parent = 0); explicit MinecraftUpdate(MinecraftInstance* inst, QObject* parent = 0);
virtual ~MinecraftUpdate(){}; virtual ~MinecraftUpdate() {};
void executeTask() override; void executeTask() override;
bool canAbort() const override; bool canAbort() const override;

View File

@ -27,7 +27,7 @@ class AuthStep : public QObject {
public: public:
using Ptr = shared_qobject_ptr<AuthStep>; using Ptr = shared_qobject_ptr<AuthStep>;
explicit AuthStep(AccountData* data) : QObject(nullptr), m_data(data){}; explicit AuthStep(AccountData* data) : QObject(nullptr), m_data(data) {};
virtual ~AuthStep() noexcept = default; virtual ~AuthStep() noexcept = default;
virtual QString describe() = 0; virtual QString describe() = 0;

View File

@ -22,7 +22,7 @@ class ClaimAccount : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit ClaimAccount(LaunchTask* parent, AuthSessionPtr session); explicit ClaimAccount(LaunchTask* parent, AuthSessionPtr session);
virtual ~ClaimAccount(){}; virtual ~ClaimAccount() {};
void executeTask() override; void executeTask() override;
void finalize() override; void finalize() override;

View File

@ -24,7 +24,7 @@ class CreateGameFolders : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit CreateGameFolders(LaunchTask* parent); explicit CreateGameFolders(LaunchTask* parent);
virtual ~CreateGameFolders(){}; virtual ~CreateGameFolders() {};
virtual void executeTask(); virtual void executeTask();
virtual bool canAbort() const { return false; } virtual bool canAbort() const { return false; }

View File

@ -21,8 +21,8 @@
class ExtractNatives : public LaunchStep { class ExtractNatives : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit ExtractNatives(LaunchTask* parent) : LaunchStep(parent){}; explicit ExtractNatives(LaunchTask* parent) : LaunchStep(parent) {};
virtual ~ExtractNatives(){}; virtual ~ExtractNatives() {};
void executeTask() override; void executeTask() override;
bool canAbort() const override { return false; } bool canAbort() const override { return false; }

View File

@ -178,6 +178,7 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state)
APPLICATION->showMainWindow(); APPLICATION->showMainWindow();
m_parent->setPid(-1); m_parent->setPid(-1);
m_parent->instance()->setMinecraftRunning(false);
// if the exit code wasn't 0, report this as a crash // if the exit code wasn't 0, report this as a crash
auto exitCode = m_process.exitCode(); auto exitCode = m_process.exitCode();
if (exitCode != 0) { if (exitCode != 0) {
@ -193,7 +194,6 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state)
case LoggedProcess::Running: case LoggedProcess::Running:
emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::Launcher); emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::Launcher);
m_parent->setPid(m_process.processId()); m_parent->setPid(m_process.processId());
m_parent->instance()->setLastLaunch();
// send the launch script to the launcher part // send the launch script to the launcher part
m_process.write(m_launchScript.toUtf8()); m_process.write(m_launchScript.toUtf8());
@ -213,6 +213,7 @@ void LauncherPartLaunch::setWorkingDirectory(const QString& wd)
void LauncherPartLaunch::proceed() void LauncherPartLaunch::proceed()
{ {
if (mayProceed) { if (mayProceed) {
m_parent->instance()->setMinecraftRunning(true);
QString launchString("launch\n"); QString launchString("launch\n");
m_process.write(launchString.toUtf8()); m_process.write(launchString.toUtf8());
mayProceed = false; mayProceed = false;

View File

@ -25,7 +25,7 @@ class LauncherPartLaunch : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit LauncherPartLaunch(LaunchTask* parent); explicit LauncherPartLaunch(LaunchTask* parent);
virtual ~LauncherPartLaunch(){}; virtual ~LauncherPartLaunch() {};
virtual void executeTask(); virtual void executeTask();
virtual bool abort(); virtual bool abort();

View File

@ -21,8 +21,8 @@
class ModMinecraftJar : public LaunchStep { class ModMinecraftJar : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit ModMinecraftJar(LaunchTask* parent) : LaunchStep(parent){}; explicit ModMinecraftJar(LaunchTask* parent) : LaunchStep(parent) {};
virtual ~ModMinecraftJar(){}; virtual ~ModMinecraftJar() {};
virtual void executeTask() override; virtual void executeTask() override;
virtual bool canAbort() const override { return false; } virtual bool canAbort() const override { return false; }

View File

@ -25,8 +25,8 @@ class PrintInstanceInfo : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit PrintInstanceInfo(LaunchTask* parent, AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) explicit PrintInstanceInfo(LaunchTask* parent, AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
: LaunchStep(parent), m_session(session), m_serverToJoin(serverToJoin){}; : LaunchStep(parent), m_session(session), m_serverToJoin(serverToJoin) {};
virtual ~PrintInstanceInfo(){}; virtual ~PrintInstanceInfo() {};
virtual void executeTask(); virtual void executeTask();
virtual bool canAbort() const { return false; } virtual bool canAbort() const { return false; }

View File

@ -21,8 +21,8 @@
class ReconstructAssets : public LaunchStep { class ReconstructAssets : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit ReconstructAssets(LaunchTask* parent) : LaunchStep(parent){}; explicit ReconstructAssets(LaunchTask* parent) : LaunchStep(parent) {};
virtual ~ReconstructAssets(){}; virtual ~ReconstructAssets() {};
void executeTask() override; void executeTask() override;
bool canAbort() const override { return false; } bool canAbort() const override { return false; }

View File

@ -21,8 +21,8 @@
class ScanModFolders : public LaunchStep { class ScanModFolders : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit ScanModFolders(LaunchTask* parent) : LaunchStep(parent){}; explicit ScanModFolders(LaunchTask* parent) : LaunchStep(parent) {};
virtual ~ScanModFolders(){}; virtual ~ScanModFolders() {};
virtual void executeTask() override; virtual void executeTask() override;
virtual bool canAbort() const override { return false; } virtual bool canAbort() const override { return false; }

View File

@ -42,7 +42,7 @@ class VerifyJavaInstall : public LaunchStep {
Q_OBJECT Q_OBJECT
public: public:
explicit VerifyJavaInstall(LaunchTask* parent) : LaunchStep(parent){}; explicit VerifyJavaInstall(LaunchTask* parent) : LaunchStep(parent) {};
~VerifyJavaInstall() override = default; ~VerifyJavaInstall() override = default;
void executeTask() override; void executeTask() override;

View File

@ -28,15 +28,52 @@
#include "Version.h" #include "Version.h"
// Values taken from: // Values taken from:
// https://minecraft.wiki/w/Tutorials/Creating_a_data_pack#%22pack_format%22 // https://minecraft.wiki/w/Pack_format#List_of_data_pack_formats
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = { static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = { { 4, { Version("1.13"), Version("1.14.4") } },
{ 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } }, { 5, { Version("1.15"), Version("1.16.1") } },
{ 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } },
{ 8, { Version("1.18"), Version("1.18.1") } }, { 9, { Version("1.18.2"), Version("1.18.2") } }, { 7, { Version("1.17"), Version("1.17.1") } },
{ 10, { Version("1.19"), Version("1.19.3") } }, { 11, { Version("23w03a"), Version("23w05a") } }, { 8, { Version("1.18"), Version("1.18.1") } },
{ 12, { Version("1.19.4"), Version("1.19.4") } }, { 13, { Version("23w12a"), Version("23w14a") } }, { 9, { Version("1.18.2"), Version("1.18.2") } },
{ 14, { Version("23w16a"), Version("23w17a") } }, { 15, { Version("1.20"), Version("1.20") } }, { 10, { Version("1.19"), Version("1.19.3") } },
}; { 11, { Version("23w03a"), Version("23w05a") } },
{ 12, { Version("1.19.4"), Version("1.19.4") } },
{ 13, { Version("23w12a"), Version("23w14a") } },
{ 14, { Version("23w16a"), Version("23w17a") } },
{ 15, { Version("1.20"), Version("1.20.1") } },
{ 16, { Version("23w31a"), Version("23w31a") } },
{ 17, { Version("23w32a"), Version("23w35a") } },
{ 18, { Version("1.20.2"), Version("1.20.2") } },
{ 19, { Version("23w40a"), Version("23w40a") } },
{ 20, { Version("23w41a"), Version("23w41a") } },
{ 21, { Version("23w42a"), Version("23w42a") } },
{ 22, { Version("23w43a"), Version("23w43b") } },
{ 23, { Version("23w44a"), Version("23w44a") } },
{ 24, { Version("23w45a"), Version("23w45a") } },
{ 25, { Version("23w46a"), Version("23w46a") } },
{ 26, { Version("1.20.3"), Version("1.20.4") } },
{ 27, { Version("23w51a"), Version("23w51b") } },
{ 28, { Version("24w05a"), Version("24w05b") } },
{ 29, { Version("24w04a"), Version("24w04a") } },
{ 30, { Version("24w05a"), Version("24w05b") } },
{ 31, { Version("24w06a"), Version("24w06a") } },
{ 32, { Version("24w07a"), Version("24w07a") } },
{ 33, { Version("24w09a"), Version("24w09a") } },
{ 34, { Version("24w10a"), Version("24w10a") } },
{ 35, { Version("24w11a"), Version("24w11a") } },
{ 36, { Version("24w12a"), Version("24w12a") } },
{ 37, { Version("24w13a"), Version("24w13a") } },
{ 38, { Version("24w14a"), Version("24w14a") } },
{ 39, { Version("1.20.5-pre1"), Version("1.20.5-pre1") } },
{ 40, { Version("1.20.5-pre2"), Version("1.20.5-pre2") } },
{ 41, { Version("1.20.5"), Version("1.20.6") } },
{ 42, { Version("24w18a"), Version("24w18a") } },
{ 43, { Version("24w19a"), Version("24w19b") } },
{ 44, { Version("24w20a"), Version("24w20a") } },
{ 45, { Version("21w21a"), Version("21w21b") } },
{ 46, { Version("1.21-pre1"), Version("1.21-pre1") } },
{ 47, { Version("1.21-pre2"), Version("1.21-pre2") } },
{ 48, { Version("1.21"), Version("1.21") } } };
void DataPack::setPackFormat(int new_format_id) void DataPack::setPackFormat(int new_format_id)
{ {

View File

@ -50,8 +50,6 @@
#include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
static ModPlatform::ProviderCapabilities ProviderCaps;
Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details() Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details()
{ {
m_enabled = (file.suffix() != "disabled"); m_enabled = (file.suffix() != "disabled");
@ -250,7 +248,7 @@ void Mod::finishResolvingWithDetails(ModDetails&& details)
auto Mod::provider() const -> std::optional<QString> auto Mod::provider() const -> std::optional<QString>
{ {
if (metadata()) if (metadata())
return ProviderCaps.readableName(metadata()->provider); return ModPlatform::ProviderCapabilities::readableName(metadata()->provider);
return {}; return {};
} }

View File

@ -65,9 +65,9 @@ ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
{ {
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders", m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders",
"Miecraft Versions", "Release Type" }); "Minecraft Versions", "Release Type" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"),
tr("Size"), tr("Side"), tr("Loaders"), tr("Miecraft Versions"), tr("Release Type") }); tr("Size"), tr("Side"), tr("Loaders"), tr("Minecraft Versions"), tr("Release Type") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION,
SortType::DATE, SortType::PROVIDER, SortType::SIZE, SortType::SIDE, SortType::DATE, SortType::PROVIDER, SortType::SIZE, SortType::SIDE,
SortType::LOADERS, SortType::MC_VERSIONS, SortType::RELEASE_TYPE }; SortType::LOADERS, SortType::MC_VERSIONS, SortType::RELEASE_TYPE };

View File

@ -282,8 +282,7 @@ void ResourceFolderModel::resolveResource(Resource* res)
connect( connect(
task.get(), &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); task.get(), &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
connect( connect(task.get(), &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
task.get(), &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
connect( connect(
task.get(), &Task::finished, this, task.get(), &Task::finished, this,
[=] { [=] {

View File

@ -11,15 +11,24 @@
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h" #include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
// Values taken from: // Values taken from:
// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta // https://minecraft.wiki/w/Pack_format#List_of_resource_pack_formats
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = { static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } }, { 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } },
{ 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } }, { 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } },
{ 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } }, { 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } },
{ 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } }, { 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } },
{ 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("22w42a"), Version("22w44a") } }, { 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("22w42a"), Version("22w44a") } },
{ 12, { Version("1.19.3"), Version("1.19.3") } }, { 13, { Version("1.19.4"), Version("1.19.4") } }, { 12, { Version("1.19.3"), Version("1.19.3") } }, { 13, { Version("1.19.4"), Version("1.19.4") } },
{ 14, { Version("1.20"), Version("1.20") } } { 14, { Version("23w14a"), Version("23w16a") } }, { 15, { Version("1.20"), Version("1.20.1") } },
{ 16, { Version("23w31a"), Version("23w31a") } }, { 17, { Version("23w32a"), Version("23w35a") } },
{ 18, { Version("1.20.2"), Version("23w16a") } }, { 19, { Version("23w42a"), Version("23w42a") } },
{ 20, { Version("23w43a"), Version("23w44a") } }, { 21, { Version("23w45a"), Version("23w46a") } },
{ 22, { Version("1.20.3-pre1"), Version("23w51b") } }, { 24, { Version("24w03a"), Version("24w04a") } },
{ 25, { Version("24w05a"), Version("24w05b") } }, { 26, { Version("24w06a"), Version("24w07a") } },
{ 28, { Version("24w09a"), Version("24w10a") } }, { 29, { Version("24w11a"), Version("24w11a") } },
{ 30, { Version("24w12a"), Version("23w12a") } }, { 31, { Version("24w13a"), Version("1.20.5-pre3") } },
{ 32, { Version("1.20.5-pre4"), Version("1.20.6") } }, { 33, { Version("24w18a"), Version("24w20a") } },
{ 34, { Version("24w21a"), Version("1.21") } }
}; };
void ResourcePack::setPackFormat(int new_format_id) void ResourcePack::setPackFormat(int new_format_id)

View File

@ -37,7 +37,6 @@
#include "Application.h" #include "Application.h"
#include "StringUtils.h"
#include "TexturePackFolderModel.h" #include "TexturePackFolderModel.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/BasicFolderLoadTask.h"

View File

@ -9,7 +9,7 @@ class FMLLibrariesTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
FMLLibrariesTask(MinecraftInstance* inst); FMLLibrariesTask(MinecraftInstance* inst);
virtual ~FMLLibrariesTask(){}; virtual ~FMLLibrariesTask() {};
void executeTask() override; void executeTask() override;

View File

@ -7,7 +7,7 @@ class FoldersTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
FoldersTask(MinecraftInstance* inst); FoldersTask(MinecraftInstance* inst);
virtual ~FoldersTask(){}; virtual ~FoldersTask() {};
void executeTask() override; void executeTask() override;

View File

@ -7,7 +7,7 @@ class LibrariesTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
LibrariesTask(MinecraftInstance* inst); LibrariesTask(MinecraftInstance* inst);
virtual ~LibrariesTask(){}; virtual ~LibrariesTask() {};
void executeTask() override; void executeTask() override;

View File

@ -17,7 +17,7 @@ class CheckUpdateTask : public Task {
std::list<Version>& mcVersions, std::list<Version>& mcVersions,
std::optional<ModPlatform::ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder) std::shared_ptr<ModFolderModel> mods_folder)
: Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder){}; : Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder) {};
struct UpdatableMod { struct UpdatableMod {
QString name; QString name;

View File

@ -15,8 +15,6 @@
#include "modplatform/modrinth/ModrinthAPI.h" #include "modplatform/modrinth/ModrinthAPI.h"
#include "modplatform/modrinth/ModrinthPackIndex.h" #include "modplatform/modrinth/ModrinthPackIndex.h"
static ModPlatform::ProviderCapabilities ProviderCaps;
static ModrinthAPI modrinth_api; static ModrinthAPI modrinth_api;
static FlameAPI flame_api; static FlameAPI flame_api;
@ -162,10 +160,10 @@ void EnsureMetadataTask::executeTask()
}); });
if (m_mods.size() > 1) if (m_mods.size() > 1)
setStatus(tr("Requesting metadata information from %1...").arg(ProviderCaps.readableName(m_provider))); setStatus(tr("Requesting metadata information from %1...").arg(ModPlatform::ProviderCapabilities::readableName(m_provider)));
else if (!m_mods.empty()) else if (!m_mods.empty())
setStatus(tr("Requesting metadata information from %1 for '%2'...") setStatus(tr("Requesting metadata information from %1 for '%2'...")
.arg(ProviderCaps.readableName(m_provider), m_mods.begin().value()->name())); .arg(ModPlatform::ProviderCapabilities::readableName(m_provider), m_mods.begin().value()->name()));
m_current_task = version_task; m_current_task = version_task;
version_task->start(); version_task->start();
@ -215,7 +213,7 @@ void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove)
Task::Ptr EnsureMetadataTask::modrinthVersionsTask() Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
{ {
auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); auto hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first();
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response); auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response);

View File

@ -59,7 +59,7 @@ IndexedVersionType::VersionType IndexedVersionType::enumFromString(const QString
return s_indexed_version_type_names.value(type, IndexedVersionType::VersionType::Unknown); return s_indexed_version_type_names.value(type, IndexedVersionType::VersionType::Unknown);
} }
auto ProviderCapabilities::name(ResourceProvider p) -> const char* const char* ProviderCapabilities::name(ResourceProvider p)
{ {
switch (p) { switch (p) {
case ResourceProvider::MODRINTH: case ResourceProvider::MODRINTH:
@ -69,7 +69,8 @@ auto ProviderCapabilities::name(ResourceProvider p) -> const char*
} }
return {}; return {};
} }
auto ProviderCapabilities::readableName(ResourceProvider p) -> QString
QString ProviderCapabilities::readableName(ResourceProvider p)
{ {
switch (p) { switch (p) {
case ResourceProvider::MODRINTH: case ResourceProvider::MODRINTH:
@ -79,7 +80,8 @@ auto ProviderCapabilities::readableName(ResourceProvider p) -> QString
} }
return {}; return {};
} }
auto ProviderCapabilities::hashType(ResourceProvider p) -> QStringList
QStringList ProviderCapabilities::hashType(ResourceProvider p)
{ {
switch (p) { switch (p) {
case ResourceProvider::MODRINTH: case ResourceProvider::MODRINTH:
@ -91,26 +93,6 @@ auto ProviderCapabilities::hashType(ResourceProvider p) -> QStringList
return {}; return {};
} }
auto ProviderCapabilities::hash(ResourceProvider p, QIODevice* device, QString type) -> QString
{
QCryptographicHash::Algorithm algo = QCryptographicHash::Sha1;
switch (p) {
case ResourceProvider::MODRINTH:
algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Sha512;
break;
case ResourceProvider::FLAME:
algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Md5;
break;
}
QCryptographicHash hash(algo);
if (!hash.addData(device))
qCritical() << "Failed to read JAR to create hash!";
Q_ASSERT(hash.result().length() == hash.hashLength(algo));
return { hash.result().toHex() };
}
QString getMetaURL(ResourceProvider provider, QVariant projectID) QString getMetaURL(ResourceProvider provider, QVariant projectID)
{ {
return ((provider == ModPlatform::ResourceProvider::FLAME) ? "https://www.curseforge.com/projects/" : "https://modrinth.com/mod/") + return ((provider == ModPlatform::ResourceProvider::FLAME) ? "https://www.curseforge.com/projects/" : "https://modrinth.com/mod/") +

View File

@ -40,13 +40,11 @@ enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN }; enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN };
class ProviderCapabilities { namespace ProviderCapabilities {
public: const char* name(ResourceProvider);
auto name(ResourceProvider) -> const char*; QString readableName(ResourceProvider);
auto readableName(ResourceProvider) -> QString; QStringList hashType(ResourceProvider);
auto hashType(ResourceProvider) -> QStringList; }; // namespace ProviderCapabilities
auto hash(ResourceProvider, QIODevice*, QString type = "") -> QString;
};
struct ModpackAuthor { struct ModpackAuthor {
QString name; QString name;

View File

@ -9,7 +9,7 @@ class FileResolvingTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess); explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess);
virtual ~FileResolvingTask(){}; virtual ~FileResolvingTask() {};
bool canAbort() const override { return true; } bool canAbort() const override { return true; }
bool abort() override; bool abort() override;

View File

@ -7,7 +7,6 @@
#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameAPI.h"
static FlameAPI api; static FlameAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps;
void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{ {
@ -21,6 +20,9 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
QJsonObject logo = Json::ensureObject(obj, "logo"); QJsonObject logo = Json::ensureObject(obj, "logo");
pack.logoName = Json::ensureString(logo, "title"); pack.logoName = Json::ensureString(logo, "title");
pack.logoUrl = Json::ensureString(logo, "thumbnailUrl"); pack.logoUrl = Json::ensureString(logo, "thumbnailUrl");
if (pack.logoUrl.isEmpty()) {
pack.logoUrl = Json::ensureString(logo, "url");
}
auto authors = Json::ensureArray(obj, "authors"); auto authors = Json::ensureArray(obj, "authors");
for (auto authorIter : authors) { for (auto authorIter : authors) {
@ -161,7 +163,7 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
auto hash_list = Json::ensureArray(obj, "hashes"); auto hash_list = Json::ensureArray(obj, "hashes");
for (auto h : hash_list) { for (auto h : hash_list) {
auto hash_entry = Json::ensureObject(h); auto hash_entry = Json::ensureObject(h);
auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::FLAME); auto hash_types = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::FLAME);
auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm")); auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm"));
if (hash_types.contains(hash_algo)) { if (hash_types.contains(hash_algo)) {
file.hash = Json::requireString(hash_entry, "value"); file.hash = Json::requireString(hash_entry, "value");

View File

@ -116,7 +116,7 @@ void FlamePackExportTask::collectHashes()
if (relative.startsWith("resourcepacks/") && if (relative.startsWith("resourcepacks/") &&
(relative.endsWith(".zip") || relative.endsWith(".zip.disabled"))) { // is resourcepack (relative.endsWith(".zip") || relative.endsWith(".zip.disabled"))) { // is resourcepack
auto hashTask = Hashing::createFlameHasher(file.absoluteFilePath()); auto hashTask = Hashing::createHasher(file.absoluteFilePath(), ModPlatform::ResourceProvider::FLAME);
connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, relative, file](QString hash) { connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, relative, file](QString hash) {
if (m_state == Task::State::Running) { if (m_state == Task::State::Running) {
pendingHashes.insert(hash, { relative, file.absoluteFilePath(), relative.endsWith(".zip") }); pendingHashes.insert(hash, { relative, file.absoluteFilePath(), relative.endsWith(".zip") });
@ -140,7 +140,7 @@ void FlamePackExportTask::collectHashes()
continue; continue;
} }
auto hashTask = Hashing::createFlameHasher(mod->fileinfo().absoluteFilePath()); auto hashTask = Hashing::createHasher(mod->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::FLAME);
connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) {
if (m_state == Task::State::Running) { if (m_state == Task::State::Running) {
pendingHashes.insert(hash, { mod->name(), mod->fileinfo().absoluteFilePath(), mod->enabled(), true }); pendingHashes.insert(hash, { mod->name(), mod->fileinfo().absoluteFilePath(), mod->enabled(), true });

View File

@ -1,24 +1,22 @@
#include "HashUtils.h" #include "HashUtils.h"
#include <QBuffer>
#include <QDebug> #include <QDebug>
#include <QFile> #include <QFile>
#include <QtConcurrentRun>
#include "FileSystem.h"
#include "StringUtils.h"
#include <MurmurHash2.h> #include <MurmurHash2.h>
namespace Hashing { namespace Hashing {
static ModPlatform::ProviderCapabilities ProviderCaps;
Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider) Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider)
{ {
switch (provider) { switch (provider) {
case ModPlatform::ResourceProvider::MODRINTH: case ModPlatform::ResourceProvider::MODRINTH:
return createModrinthHasher(file_path); return makeShared<Hasher>(file_path,
ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first());
case ModPlatform::ResourceProvider::FLAME: case ModPlatform::ResourceProvider::FLAME:
return createFlameHasher(file_path); return makeShared<Hasher>(file_path, Algorithm::Murmur2);
default: default:
qCritical() << "[Hashing]" qCritical() << "[Hashing]"
<< "Unrecognized mod platform!"; << "Unrecognized mod platform!";
@ -26,119 +24,142 @@ Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provid
} }
} }
Hasher::Ptr createModrinthHasher(QString file_path) Hasher::Ptr createHasher(QString file_path, QString type)
{ {
return makeShared<ModrinthHasher>(file_path); return makeShared<Hasher>(file_path, type);
} }
Hasher::Ptr createFlameHasher(QString file_path) class QIODeviceReader : public Murmur2::Reader {
public:
QIODeviceReader(QIODevice* device) : m_device(device) {}
virtual ~QIODeviceReader() = default;
virtual int read(char* s, int n) { return m_device->read(s, n); }
virtual bool eof() { return m_device->atEnd(); }
virtual void goToBeginning() { m_device->seek(0); }
virtual void close() { m_device->close(); }
private:
QIODevice* m_device;
};
QString algorithmToString(Algorithm type)
{ {
return makeShared<FlameHasher>(file_path); switch (type) {
case Algorithm::Md4:
return "md4";
case Algorithm::Md5:
return "md5";
case Algorithm::Sha1:
return "sha1";
case Algorithm::Sha256:
return "sha256";
case Algorithm::Sha512:
return "sha512";
case Algorithm::Murmur2:
return "murmur2";
// case Algorithm::Unknown:
default:
break;
}
return "unknown";
} }
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) Algorithm algorithmFromString(QString type)
{ {
return makeShared<BlockedModHasher>(file_path, provider); if (type == "md4")
return Algorithm::Md4;
if (type == "md5")
return Algorithm::Md5;
if (type == "sha1")
return Algorithm::Sha1;
if (type == "sha256")
return Algorithm::Sha256;
if (type == "sha512")
return Algorithm::Sha512;
if (type == "murmur2")
return Algorithm::Murmur2;
return Algorithm::Unknown;
} }
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider, QString type) QString hash(QIODevice* device, Algorithm type)
{ {
auto hasher = makeShared<BlockedModHasher>(file_path, provider); if (!device->isOpen() && !device->open(QFile::ReadOnly))
hasher->useHashType(type); return "";
return hasher; QCryptographicHash::Algorithm alg = QCryptographicHash::Sha1;
} switch (type) {
case Algorithm::Md4:
void ModrinthHasher::executeTask() alg = QCryptographicHash::Algorithm::Md4;
{ break;
QFile file(m_path); case Algorithm::Md5:
alg = QCryptographicHash::Algorithm::Md5;
try { break;
file.open(QFile::ReadOnly); case Algorithm::Sha1:
} catch (FS::FileSystemException& e) { alg = QCryptographicHash::Algorithm::Sha1;
qCritical() << QString("Failed to open JAR file in %1").arg(m_path); break;
qCritical() << QString("Reason: ") << e.cause(); case Algorithm::Sha256:
alg = QCryptographicHash::Algorithm::Sha256;
emitFailed("Failed to open file for hashing."); break;
return; case Algorithm::Sha512:
alg = QCryptographicHash::Algorithm::Sha512;
break;
case Algorithm::Murmur2: { // CF-specific
auto should_filter_out = [](char c) { return (c == 9 || c == 10 || c == 13 || c == 32); };
auto reader = std::make_unique<QIODeviceReader>(device);
auto result = QString::number(Murmur2::hash(reader.get(), 4 * MiB, should_filter_out));
device->close();
return result;
}
case Algorithm::Unknown:
device->close();
return "";
} }
auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); QCryptographicHash hash(alg);
m_hash = ProviderCaps.hash(ModPlatform::ResourceProvider::MODRINTH, &file, hash_type); if (!hash.addData(device))
qCritical() << "Failed to read JAR to create hash!";
file.close(); Q_ASSERT(hash.result().length() == hash.hashLength(alg));
auto result = hash.result().toHex();
if (m_hash.isEmpty()) { device->close();
emitFailed("Empty hash!"); return result;
} else {
emitSucceeded();
emit resultsReady(m_hash);
}
} }
void FlameHasher::executeTask() QString hash(QString fileName, Algorithm type)
{ {
// CF-specific QFile file(fileName);
auto should_filter_out = [](char c) { return (c == 9 || c == 10 || c == 13 || c == 32); }; return hash(&file, type);
std::ifstream file_stream(StringUtils::toStdString(m_path).c_str(), std::ifstream::binary);
// TODO: This is very heavy work, but apparently QtConcurrent can't use move semantics, so we can't boop this to another thread.
// How do we make this non-blocking then?
m_hash = QString::number(MurmurHash2(std::move(file_stream), 4 * MiB, should_filter_out));
if (m_hash.isEmpty()) {
emitFailed("Empty hash!");
} else {
emitSucceeded();
emit resultsReady(m_hash);
}
} }
BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) : Hasher(file_path), provider(provider) QString hash(QByteArray data, Algorithm type)
{ {
setObjectName(QString("BlockedModHasher: %1").arg(file_path)); QBuffer buff(&data);
hash_type = ProviderCaps.hashType(provider).first(); return hash(&buff, type);
} }
void BlockedModHasher::executeTask() void Hasher::executeTask()
{ {
QFile file(m_path); m_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return hash(m_path, m_alg); });
connect(&m_watcher, &QFutureWatcher<QString>::finished, this, [this] {
try { if (m_future.isCanceled()) {
file.open(QFile::ReadOnly); emitAborted();
} catch (FS::FileSystemException& e) { } else if (m_result = m_future.result(); m_result.isEmpty()) {
qCritical() << QString("Failed to open JAR file in %1").arg(m_path); emitFailed("Empty hash!");
qCritical() << QString("Reason: ") << e.cause(); } else {
emitSucceeded();
emitFailed("Failed to open file for hashing."); emit resultsReady(m_result);
return; }
} });
m_watcher.setFuture(m_future);
m_hash = ProviderCaps.hash(provider, &file, hash_type);
file.close();
if (m_hash.isEmpty()) {
emitFailed("Empty hash!");
} else {
emitSucceeded();
emit resultsReady(m_hash);
}
} }
QStringList BlockedModHasher::getHashTypes() bool Hasher::abort()
{ {
return ProviderCaps.hashType(provider); if (m_future.isRunning()) {
} m_future.cancel();
// NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not
bool BlockedModHasher::useHashType(QString type) // occur immediately.
{
auto types = ProviderCaps.hashType(provider);
if (types.contains(type)) {
hash_type = type;
return true; return true;
} }
qDebug() << "Bad hash type " << type << " for provider";
return false; return false;
} }
} // namespace Hashing } // namespace Hashing

View File

@ -1,5 +1,8 @@
#pragma once #pragma once
#include <QCryptographicHash>
#include <QFuture>
#include <QFutureWatcher>
#include <QString> #include <QString>
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
@ -7,61 +10,42 @@
namespace Hashing { namespace Hashing {
enum class Algorithm { Md4, Md5, Sha1, Sha256, Sha512, Murmur2, Unknown };
QString algorithmToString(Algorithm type);
Algorithm algorithmFromString(QString type);
QString hash(QIODevice* device, Algorithm type);
QString hash(QString fileName, Algorithm type);
QString hash(QByteArray data, Algorithm type);
class Hasher : public Task { class Hasher : public Task {
Q_OBJECT Q_OBJECT
public: public:
using Ptr = shared_qobject_ptr<Hasher>; using Ptr = shared_qobject_ptr<Hasher>;
Hasher(QString file_path) : m_path(std::move(file_path)) {} Hasher(QString file_path, Algorithm alg) : m_path(file_path), m_alg(alg) {}
Hasher(QString file_path, QString alg) : Hasher(file_path, algorithmFromString(alg)) {}
/* We can't really abort this task, but we can say we aborted and finish our thing quickly :) */ bool abort() override;
bool abort() override { return true; }
void executeTask() override = 0; void executeTask() override;
QString getResult() const { return m_hash; }; QString getResult() const { return m_result; };
QString getPath() const { return m_path; }; QString getPath() const { return m_path; };
signals: signals:
void resultsReady(QString hash); void resultsReady(QString hash);
protected:
QString m_hash;
QString m_path;
};
class FlameHasher : public Hasher {
public:
FlameHasher(QString file_path) : Hasher(file_path) { setObjectName(QString("FlameHasher: %1").arg(file_path)); }
void executeTask() override;
};
class ModrinthHasher : public Hasher {
public:
ModrinthHasher(QString file_path) : Hasher(file_path) { setObjectName(QString("ModrinthHasher: %1").arg(file_path)); }
void executeTask() override;
};
class BlockedModHasher : public Hasher {
public:
BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider);
void executeTask() override;
QStringList getHashTypes();
bool useHashType(QString type);
private: private:
ModPlatform::ResourceProvider provider; QString m_result;
QString hash_type; QString m_path;
Algorithm m_alg;
QFuture<QString> m_future;
QFutureWatcher<QString> m_watcher;
}; };
Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider); Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider);
Hasher::Ptr createFlameHasher(QString file_path); Hasher::Ptr createHasher(QString file_path, QString type);
Hasher::Ptr createModrinthHasher(QString file_path);
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider);
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider, QString type);
} // namespace Hashing } // namespace Hashing

View File

@ -13,7 +13,7 @@ class PackFetchTask : public QObject {
Q_OBJECT Q_OBJECT
public: public:
PackFetchTask(shared_qobject_ptr<QNetworkAccessManager> network) : QObject(nullptr), m_network(network){}; PackFetchTask(shared_qobject_ptr<QNetworkAccessManager> network) : QObject(nullptr), m_network(network) {};
virtual ~PackFetchTask() = default; virtual ~PackFetchTask() = default;
void fetch(); void fetch();

View File

@ -13,7 +13,6 @@
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"
static ModrinthAPI api; static ModrinthAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps;
bool ModrinthCheckUpdate::abort() bool ModrinthCheckUpdate::abort()
{ {
@ -36,7 +35,7 @@ void ModrinthCheckUpdate::executeTask()
// Create all hashes // Create all hashes
QStringList hashes; QStringList hashes;
auto best_hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); auto best_hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first();
ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
for (auto* mod : m_mods) { for (auto* mod : m_mods) {
@ -51,7 +50,7 @@ void ModrinthCheckUpdate::executeTask()
// need to generate a new hash if the current one is innadequate // need to generate a new hash if the current one is innadequate
// (though it will rarely happen, if at all) // (though it will rarely happen, if at all)
if (mod->metadata()->hash_format != best_hash_type) { if (mod->metadata()->hash_format != best_hash_type) {
auto hash_task = Hashing::createModrinthHasher(mod->fileinfo().absoluteFilePath()); auto hash_task = Hashing::createHasher(mod->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH);
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [&hashes, &mappings, mod](QString hash) { connect(hash_task.get(), &Hashing::Hasher::resultsReady, [&hashes, &mappings, mod](QString hash) {
hashes.append(hash); hashes.append(hash);
mappings.insert(hash, mod); mappings.insert(hash, mod);

View File

@ -27,6 +27,7 @@
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/MetadataHandler.h"
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"
#include "modplatform/helpers/HashUtils.h"
const QStringList ModrinthPackExportTask::PREFIXES({ "mods/", "coremods/", "resourcepacks/", "texturepacks/", "shaderpacks/" }); const QStringList ModrinthPackExportTask::PREFIXES({ "mods/", "coremods/", "resourcepacks/", "texturepacks/", "shaderpacks/" });
const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "zip" }); const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "zip" });
@ -102,8 +103,6 @@ void ModrinthPackExportTask::collectHashes()
})) }))
continue; continue;
QCryptographicHash sha512(QCryptographicHash::Algorithm::Sha512);
QFile openFile(file.absoluteFilePath()); QFile openFile(file.absoluteFilePath());
if (!openFile.open(QFile::ReadOnly)) { if (!openFile.open(QFile::ReadOnly)) {
qWarning() << "Could not open" << file << "for hashing"; qWarning() << "Could not open" << file << "for hashing";
@ -115,7 +114,7 @@ void ModrinthPackExportTask::collectHashes()
qWarning() << "Could not read" << file; qWarning() << "Could not read" << file;
continue; continue;
} }
sha512.addData(data); auto sha512 = Hashing::hash(data, Hashing::Algorithm::Sha512);
auto allMods = mcInstance->loaderModList()->allMods(); auto allMods = mcInstance->loaderModList()->allMods();
if (auto modIter = std::find_if(allMods.begin(), allMods.end(), [&file](Mod* mod) { return mod->fileinfo() == file; }); if (auto modIter = std::find_if(allMods.begin(), allMods.end(), [&file](Mod* mod) { return mod->fileinfo() == file; });
@ -127,11 +126,9 @@ void ModrinthPackExportTask::collectHashes()
if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) { if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) {
qDebug() << "Resolving" << relative << "from index"; qDebug() << "Resolving" << relative << "from index";
QCryptographicHash sha1(QCryptographicHash::Algorithm::Sha1); auto sha1 = Hashing::hash(data, Hashing::Algorithm::Sha1);
sha1.addData(data);
ResolvedFile resolvedFile{ sha1.result().toHex(), sha512.result().toHex(), url.toEncoded(), openFile.size(), ResolvedFile resolvedFile{ sha1, sha512, url.toEncoded(), openFile.size(), mod->metadata()->side };
mod->metadata()->side };
resolvedFiles[relative] = resolvedFile; resolvedFiles[relative] = resolvedFile;
// nice! we've managed to resolve based on local metadata! // nice! we've managed to resolve based on local metadata!
@ -142,7 +139,7 @@ void ModrinthPackExportTask::collectHashes()
} }
qDebug() << "Enqueueing" << relative << "for Modrinth query"; qDebug() << "Enqueueing" << relative << "for Modrinth query";
pendingHashes[relative] = sha512.result().toHex(); pendingHashes[relative] = sha512;
} }
setAbortable(true); setAbortable(true);

View File

@ -27,7 +27,6 @@
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
static ModrinthAPI api; static ModrinthAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps;
bool shouldDownloadOnSide(QString side) bool shouldDownloadOnSide(QString side)
{ {
@ -132,8 +131,9 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArra
pack.versionsLoaded = true; pack.versionsLoaded = true;
} }
auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_type, QString preferred_file_name) auto Modrinth::loadIndexedPackVersion(QJsonObject& obj,
-> ModPlatform::IndexedVersion QString preferred_hash_type,
QString preferred_file_name) -> ModPlatform::IndexedVersion
{ {
ModPlatform::IndexedVersion file; ModPlatform::IndexedVersion file;
@ -231,7 +231,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
file.hash = Json::requireString(hash_list, preferred_hash_type); file.hash = Json::requireString(hash_list, preferred_hash_type);
file.hash_type = preferred_hash_type; file.hash_type = preferred_hash_type;
} else { } else {
auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH); auto hash_types = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH);
for (auto& hash_type : hash_types) { for (auto& hash_type : hash_types) {
if (hash_list.contains(hash_type)) { if (hash_list.contains(hash_type)) {
file.hash = Json::requireString(hash_list, hash_type); file.hash = Json::requireString(hash_list, hash_type);
@ -247,8 +247,9 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
return {}; return {};
} }
auto Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) auto Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m,
-> ModPlatform::IndexedVersion QJsonArray& arr,
const BaseInstance* inst) -> ModPlatform::IndexedVersion
{ {
auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile(); auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
QString mcVersion = profile->getComponentVersion("net.minecraft"); QString mcVersion = profile->getComponentVersion("net.minecraft");

View File

@ -67,8 +67,6 @@ static inline auto indexFileName(QString const& mod_slug) -> QString
return QString("%1.pw.toml").arg(mod_slug); return QString("%1.pw.toml").arg(mod_slug);
} }
static ModPlatform::ProviderCapabilities ProviderCaps;
// Helper functions for extracting data from the TOML file // Helper functions for extracting data from the TOML file
auto stringEntry(toml::table table, QString entry_name) -> QString auto stringEntry(toml::table table, QString entry_name) -> QString
{ {
@ -92,8 +90,9 @@ auto intEntry(toml::table table, QString entry_name) -> int
return node.value_or(0); return node.value_or(0);
} }
auto V1::createModFormat([[maybe_unused]] QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) auto V1::createModFormat([[maybe_unused]] QDir& index_dir,
-> Mod ModPlatform::IndexedPack& mod_pack,
ModPlatform::IndexedVersion& mod_version) -> Mod
{ {
Mod mod; Mod mod;
@ -219,7 +218,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod)
{ "hash-format", mod.hash_format.toStdString() }, { "hash-format", mod.hash_format.toStdString() },
{ "hash", mod.hash.toStdString() }, { "hash", mod.hash.toStdString() },
} }, } },
{ "update", toml::table{ { ProviderCaps.name(mod.provider), update } } } }; { "update", toml::table{ { ModPlatform::ProviderCapabilities::name(mod.provider), update } } } };
std::stringstream ss; std::stringstream ss;
ss << tbl; ss << tbl;
in_stream << QString::fromStdString(ss.str()); in_stream << QString::fromStdString(ss.str());
@ -340,11 +339,11 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
} }
toml::table* mod_provider_table = nullptr; toml::table* mod_provider_table = nullptr;
if ((mod_provider_table = update_table[ProviderCaps.name(Provider::FLAME)].as_table())) { if ((mod_provider_table = update_table[ModPlatform::ProviderCapabilities::name(Provider::FLAME)].as_table())) {
mod.provider = Provider::FLAME; mod.provider = Provider::FLAME;
mod.file_id = intEntry(*mod_provider_table, "file-id"); mod.file_id = intEntry(*mod_provider_table, "file-id");
mod.project_id = intEntry(*mod_provider_table, "project-id"); mod.project_id = intEntry(*mod_provider_table, "project-id");
} else if ((mod_provider_table = update_table[ProviderCaps.name(Provider::MODRINTH)].as_table())) { } else if ((mod_provider_table = update_table[ModPlatform::ProviderCapabilities::name(Provider::MODRINTH)].as_table())) {
mod.provider = Provider::MODRINTH; mod.provider = Provider::MODRINTH;
mod.mod_id() = stringEntry(*mod_provider_table, "mod-id"); mod.mod_id() = stringEntry(*mod_provider_table, "mod-id");
mod.version() = stringEntry(*mod_provider_table, "version"); mod.version() = stringEntry(*mod_provider_table, "version");

View File

@ -45,7 +45,7 @@ namespace Net {
*/ */
class ByteArraySink : public Sink { class ByteArraySink : public Sink {
public: public:
ByteArraySink(std::shared_ptr<QByteArray> output) : m_output(output){}; ByteArraySink(std::shared_ptr<QByteArray> output) : m_output(output) {};
virtual ~ByteArraySink() = default; virtual ~ByteArraySink() = default;

View File

@ -44,7 +44,7 @@ namespace Net {
class ChecksumValidator : public Validator { class ChecksumValidator : public Validator {
public: public:
ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray()) ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray())
: m_checksum(algorithm), m_expected(expected){}; : m_checksum(algorithm), m_expected(expected) {};
virtual ~ChecksumValidator() = default; virtual ~ChecksumValidator() = default;
public: public:

View File

@ -42,7 +42,7 @@
namespace Net { namespace Net {
class FileSink : public Sink { class FileSink : public Sink {
public: public:
FileSink(QString filename) : m_filename(filename){}; FileSink(QString filename) : m_filename(filename) {};
virtual ~FileSink() = default; virtual ~FileSink() = default;
public: public:

View File

@ -175,6 +175,8 @@ void HttpMetaCache::evictAll()
if (!evictEntry(entry)) if (!evictEntry(entry))
qCWarning(taskHttpMetaCacheLogC) << "Unexpected missing cache entry" << entry->m_basePath; qCWarning(taskHttpMetaCacheLogC) << "Unexpected missing cache entry" << entry->m_basePath;
} }
map.entry_list.clear();
FS::deletePath(map.base_path);
} }
} }

View File

@ -25,7 +25,7 @@ namespace Net {
class StaticHeaderProxy : public HeaderProxy { class StaticHeaderProxy : public HeaderProxy {
public: public:
StaticHeaderProxy(QList<HeaderPair> hdrs = {}) : HeaderProxy(), m_hdrs(hdrs){}; StaticHeaderProxy(QList<HeaderPair> hdrs = {}) : HeaderProxy(), m_hdrs(hdrs) {};
virtual ~StaticHeaderProxy() = default; virtual ~StaticHeaderProxy() = default;
public: public:

View File

@ -6,7 +6,7 @@
class FSTreeMatcher : public IPathMatcher { class FSTreeMatcher : public IPathMatcher {
public: public:
virtual ~FSTreeMatcher(){}; virtual ~FSTreeMatcher() {};
FSTreeMatcher(SeparatorPrefixTree<'/'>& tree) : m_fsTree(tree) {} FSTreeMatcher(SeparatorPrefixTree<'/'>& tree) : m_fsTree(tree) {}
bool matches(const QString& string) const override { return m_fsTree.covers(string); } bool matches(const QString& string) const override { return m_fsTree.covers(string); }

View File

@ -4,7 +4,7 @@
class MultiMatcher : public IPathMatcher { class MultiMatcher : public IPathMatcher {
public: public:
virtual ~MultiMatcher(){}; virtual ~MultiMatcher() {};
MultiMatcher() {} MultiMatcher() {}
MultiMatcher& add(Ptr add) MultiMatcher& add(Ptr add)
{ {

View File

@ -7,7 +7,7 @@
class SimplePrefixMatcher : public IPathMatcher { class SimplePrefixMatcher : public IPathMatcher {
public: public:
virtual ~SimplePrefixMatcher(){}; virtual ~SimplePrefixMatcher() {};
SimplePrefixMatcher(const QString& prefix) SimplePrefixMatcher(const QString& prefix)
{ {
m_prefix = prefix; m_prefix = prefix;

View File

@ -49,7 +49,7 @@ class ImgurAlbumCreation : public Net::NetRequest {
class Sink : public Net::Sink { class Sink : public Net::Sink {
public: public:
Sink(std::shared_ptr<Result> res) : m_result(res){}; Sink(std::shared_ptr<Result> res) : m_result(res) {};
virtual ~Sink() = default; virtual ~Sink() = default;
public: public:

View File

@ -43,7 +43,7 @@ class ImgurUpload : public Net::NetRequest {
public: public:
class Sink : public Net::Sink { class Sink : public Net::Sink {
public: public:
Sink(ScreenShot::Ptr shot) : m_shot(shot){}; Sink(ScreenShot::Ptr shot) : m_shot(shot) {};
virtual ~Sink() = default; virtual ~Sink() = default;
public: public:

View File

@ -22,7 +22,7 @@
class GenericProfilerFactory : public BaseProfilerFactory { class GenericProfilerFactory : public BaseProfilerFactory {
public: public:
QString name() const override { return "Generic"; } QString name() const override { return "Generic"; }
void registerSettings([[maybe_unused]] SettingsObjectPtr settings) override{}; void registerSettings([[maybe_unused]] SettingsObjectPtr settings) override {};
BaseExternalTool* createTool(InstancePtr instance, QObject* parent = 0) override; BaseExternalTool* createTool(InstancePtr instance, QObject* parent = 0) override;
bool check([[maybe_unused]] QString* error) override { return true; }; bool check([[maybe_unused]] QString* error) override { return true; };
bool check([[maybe_unused]] const QString& path, [[maybe_unused]] QString* error) override { return true; }; bool check([[maybe_unused]] const QString& path, [[maybe_unused]] QString* error) override { return true; };

View File

@ -1560,7 +1560,7 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered()
QFileDialog fileDialog; QFileDialog fileDialog;
// workaround to make sure the portal file dialog opens in the desktop directory // workaround to make sure the portal file dialog opens in the desktop directory
fileDialog.setDirectoryUrl(desktopPath); fileDialog.setDirectoryUrl(desktopPath);
desktopFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), desktopFilePath, tr("Desktop Entries (*.desktop)")); desktopFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), desktopFilePath, tr("Desktop Entries") + " (*.desktop)");
if (desktopFilePath.isEmpty()) if (desktopFilePath.isEmpty())
return; // file dialog canceled by user return; // file dialog canceled by user
appPath = "flatpak"; appPath = "flatpak";

View File

@ -266,7 +266,7 @@ void BlockedModsDialog::addHashTask(QString path)
/// @param path the path to the local file being hashed /// @param path the path to the local file being hashed
void BlockedModsDialog::buildHashTask(QString path) void BlockedModsDialog::buildHashTask(QString path)
{ {
auto hash_task = Hashing::createBlockedModHasher(path, ModPlatform::ResourceProvider::FLAME, m_hash_type); auto hash_task = Hashing::createHasher(path, m_hash_type);
qDebug() << "[Blocked Mods Dialog] Creating Hash task for path: " << path; qDebug() << "[Blocked Mods Dialog] Creating Hash task for path: " << path;

View File

@ -6,8 +6,6 @@
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
static ModPlatform::ProviderCapabilities ProviderCaps;
ChooseProviderDialog::ChooseProviderDialog(QWidget* parent, bool single_choice, bool allow_skipping) ChooseProviderDialog::ChooseProviderDialog(QWidget* parent, bool single_choice, bool allow_skipping)
: QDialog(parent), ui(new Ui::ChooseProviderDialog) : QDialog(parent), ui(new Ui::ChooseProviderDialog)
{ {
@ -78,7 +76,7 @@ void ChooseProviderDialog::addProviders()
QRadioButton* btn; QRadioButton* btn;
for (auto& provider : { ModPlatform::ResourceProvider::MODRINTH, ModPlatform::ResourceProvider::FLAME }) { for (auto& provider : { ModPlatform::ResourceProvider::MODRINTH, ModPlatform::ResourceProvider::FLAME }) {
btn = new QRadioButton(ProviderCaps.readableName(provider), this); btn = new QRadioButton(ModPlatform::ProviderCapabilities::readableName(provider), this);
m_providers.addButton(btn, btn_index++); m_providers.addButton(btn, btn_index++);
ui->providersLayout->addWidget(btn); ui->providersLayout->addWidget(btn);
} }

View File

@ -129,14 +129,14 @@ void ExportPackDialog::done(int result)
QString output; QString output;
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) { if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".mrpack"), output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".mrpack"),
"Modrinth pack (*.mrpack *.zip)", nullptr); tr("Modrinth pack") + " (*.mrpack *.zip)", nullptr);
if (output.isEmpty()) if (output.isEmpty())
return; return;
if (!(output.endsWith(".zip") || output.endsWith(".mrpack"))) if (!(output.endsWith(".zip") || output.endsWith(".mrpack")))
output.append(".mrpack"); output.append(".mrpack");
} else { } else {
output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".zip"), output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".zip"),
"CurseForge pack (*.zip)", nullptr); tr("CurseForge pack") + " (*.zip)", nullptr);
if (output.isEmpty()) if (output.isEmpty())
return; return;
if (!output.endsWith(".zip")) if (!output.endsWith(".zip"))

View File

@ -160,7 +160,7 @@ void ExportToModListDialog::done(int result)
const QString filename = FS::RemoveInvalidFilenameChars(m_name); const QString filename = FS::RemoveInvalidFilenameChars(m_name);
const QString output = const QString output =
QFileDialog::getSaveFileName(this, tr("Export %1").arg(m_name), FS::PathCombine(QDir::homePath(), filename + extension()), QFileDialog::getSaveFileName(this, tr("Export %1").arg(m_name), FS::PathCombine(QDir::homePath(), filename + extension()),
"File (*.txt *.html *.md *.json *.csv)", nullptr); tr("File") + " (*.txt *.html *.md *.json *.csv)", nullptr);
if (output.isEmpty()) if (output.isEmpty())
return; return;

View File

@ -25,8 +25,6 @@
#include <optional> #include <optional>
static ModPlatform::ProviderCapabilities ProviderCaps;
static std::list<Version> mcVersions(BaseInstance* inst) static std::list<Version> mcVersions(BaseInstance* inst)
{ {
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() };
@ -94,17 +92,15 @@ void ModUpdateDialog::checkCandidates()
if (!m_modrinth_to_update.empty()) { if (!m_modrinth_to_update.empty()) {
m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loaders, m_mod_model)); m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loaders, m_mod_model));
connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this, [this](Mod* mod, QString reason, QUrl recover_url) { connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this,
m_failed_check_update.append({ mod, reason, recover_url }); [this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({ mod, reason, recover_url }); });
});
check_task.addTask(m_modrinth_check_task); check_task.addTask(m_modrinth_check_task);
} }
if (!m_flame_to_update.empty()) { if (!m_flame_to_update.empty()) {
m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loaders, m_mod_model)); m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loaders, m_mod_model));
connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this, [this](Mod* mod, QString reason, QUrl recover_url) { connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this,
m_failed_check_update.append({ mod, reason, recover_url }); [this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({ mod, reason, recover_url }); });
});
check_task.addTask(m_flame_check_task); check_task.addTask(m_flame_check_task);
} }
@ -427,7 +423,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri
item_top->setExpanded(true); item_top->setExpanded(true);
auto provider_item = new QTreeWidgetItem(item_top); auto provider_item = new QTreeWidgetItem(item_top);
provider_item->setText(0, tr("Provider: %1").arg(ProviderCaps.readableName(info.provider))); provider_item->setText(0, tr("Provider: %1").arg(ModPlatform::ProviderCapabilities::readableName(info.provider)));
auto old_version_item = new QTreeWidgetItem(item_top); auto old_version_item = new QTreeWidgetItem(item_top);
old_version_item->setText(0, tr("Old version: %1").arg(info.old_version.isEmpty() ? tr("Not installed") : info.old_version)); old_version_item->setText(0, tr("Old version: %1").arg(info.old_version.isEmpty() ? tr("Not installed") : info.old_version));

View File

@ -200,7 +200,7 @@ void NewInstanceDialog::setSuggestedPack(const QString& name, InstanceTask* task
importVersion.clear(); importVersion.clear();
if (!task) { if (!task) {
ui->iconButton->setIcon(APPLICATION->icons()->getIcon("default")); ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
importIcon = false; importIcon = false;
} }
@ -216,7 +216,7 @@ void NewInstanceDialog::setSuggestedPack(const QString& name, QString version, I
importVersion = std::move(version); importVersion = std::move(version);
if (!task) { if (!task) {
ui->iconButton->setIcon(APPLICATION->icons()->getIcon("default")); ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
importIcon = false; importIcon = false;
} }
@ -236,6 +236,9 @@ void NewInstanceDialog::setSuggestedIconFromFile(const QString& path, const QStr
void NewInstanceDialog::setSuggestedIcon(const QString& key) void NewInstanceDialog::setSuggestedIcon(const QString& key)
{ {
if (key == "default")
return;
auto icon = APPLICATION->icons()->getIcon(key); auto icon = APPLICATION->icons()->getIcon(key);
importIcon = false; importIcon = false;

View File

@ -92,6 +92,19 @@ void ResourceDownloadDialog::accept()
void ResourceDownloadDialog::reject() void ResourceDownloadDialog::reject()
{ {
auto selected = getTasks();
if (selected.count() > 0) {
auto reply = CustomMessageBox::selectable(this, tr("Confirmation Needed"),
tr("You have %1 selected resources.\n"
"Are you sure you want to close this dialog?")
.arg(selected.count()),
QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (reply != QMessageBox::Yes) {
return;
}
}
if (!geometrySaveKey().isEmpty()) if (!geometrySaveKey().isEmpty())
APPLICATION->settings()->set(geometrySaveKey(), saveGeometry().toBase64()); APPLICATION->settings()->set(geometrySaveKey(), saveGeometry().toBase64());
@ -126,8 +139,6 @@ void ResourceDownloadDialog::connectButtons()
connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help);
} }
static ModPlatform::ProviderCapabilities ProviderCaps;
void ResourceDownloadDialog::confirm() void ResourceDownloadDialog::confirm()
{ {
auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString()));
@ -169,7 +180,7 @@ void ResourceDownloadDialog::confirm()
for (auto& task : selected) { for (auto& task : selected) {
auto extraInfo = dependencyExtraInfo.value(task->getPack()->addonId.toString()); auto extraInfo = dependencyExtraInfo.value(task->getPack()->addonId.toString());
confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(), confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(),
ProviderCaps.name(task->getProvider()), extraInfo.required_by, ModPlatform::ProviderCapabilities::name(task->getProvider()), extraInfo.required_by,
task->getVersion().version_type.toString(), !extraInfo.maybe_installed }); task->getVersion().version_type.toString(), !extraInfo.maybe_installed });
} }
@ -358,4 +369,20 @@ QList<BasePage*> ShaderPackDownloadDialog::getPages()
return pages; return pages;
} }
void ModDownloadDialog::setModMetadata(std::shared_ptr<Metadata::ModStruct> meta)
{
switch (meta->provider) {
case ModPlatform::ResourceProvider::MODRINTH:
selectPage(Modrinth::id());
break;
case ModPlatform::ResourceProvider::FLAME:
selectPage(Flame::id());
break;
}
setWindowTitle(tr("Change %1 version").arg(meta->name));
m_container->hidePageList();
m_buttons.hide();
auto page = selectedPage();
page->openProject(meta->project_id);
}
} // namespace ResourceDownload } // namespace ResourceDownload

View File

@ -107,6 +107,8 @@ class ModDownloadDialog final : public ResourceDownloadDialog {
QList<BasePage*> getPages() override; QList<BasePage*> getPages() override;
GetModDependenciesTask::Ptr getModDependenciesTask() override; GetModDependenciesTask::Ptr getModDependenciesTask() override;
void setModMetadata(std::shared_ptr<Metadata::ModStruct>);
private: private:
BaseInstance* m_instance; BaseInstance* m_instance;
}; };

View File

@ -33,7 +33,7 @@ class VersionSelectDialog : public QDialog {
public: public:
explicit VersionSelectDialog(BaseVersionList* vlist, QString title, QWidget* parent = 0, bool cancelable = true); explicit VersionSelectDialog(BaseVersionList* vlist, QString title, QWidget* parent = 0, bool cancelable = true);
virtual ~VersionSelectDialog(){}; virtual ~VersionSelectDialog() {};
int exec() override; int exec() override;

View File

@ -118,7 +118,7 @@ void SkinManageDialog::selectionChanged(QItemSelection selected, QItemSelection
auto skin = m_list.skin(key); auto skin = m_list.skin(key);
if (!skin) if (!skin)
return; return;
ui->selectedModel->setPixmap(skin->getTexture().scaled(128, 128, Qt::KeepAspectRatio, Qt::FastTransformation)); ui->selectedModel->setPixmap(skin->getTexture().scaled(size() * (1. / 3), Qt::KeepAspectRatio, Qt::FastTransformation));
ui->capeCombo->setCurrentIndex(m_capes_idx.value(skin->getCapeId())); ui->capeCombo->setCurrentIndex(m_capes_idx.value(skin->getCapeId()));
ui->steveBtn->setChecked(skin->getModel() == SkinModel::CLASSIC); ui->steveBtn->setChecked(skin->getModel() == SkinModel::CLASSIC);
ui->alexBtn->setChecked(skin->getModel() == SkinModel::SLIM); ui->alexBtn->setChecked(skin->getModel() == SkinModel::SLIM);
@ -212,7 +212,7 @@ void SkinManageDialog::setupCapes()
void SkinManageDialog::on_capeCombo_currentIndexChanged(int index) void SkinManageDialog::on_capeCombo_currentIndexChanged(int index)
{ {
auto id = ui->capeCombo->currentData(); auto id = ui->capeCombo->currentData();
ui->capeImage->setPixmap(m_capes.value(id.toString(), {})); ui->capeImage->setPixmap(m_capes.value(id.toString(), {}).scaled(size() * (1. / 3), Qt::KeepAspectRatio, Qt::FastTransformation));
if (auto skin = m_list.skin(m_selected_skin); skin) { if (auto skin = m_list.skin(m_selected_skin); skin) {
skin->setCapeId(id.toString()); skin->setCapeId(id.toString());
} }
@ -371,7 +371,7 @@ void SkinManageDialog::on_urlBtn_clicked()
class WaitTask : public Task { class WaitTask : public Task {
public: public:
WaitTask() : m_loop(), m_done(false){}; WaitTask() : m_loop(), m_done(false) {};
virtual ~WaitTask() = default; virtual ~WaitTask() = default;
public slots: public slots:
@ -498,3 +498,15 @@ void SkinManageDialog::on_userBtn_clicked()
} }
m_list.updateSkin(&s); m_list.updateSkin(&s);
} }
void SkinManageDialog::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
QSize s = size() * (1. / 3);
if (auto skin = m_list.skin(m_selected_skin); skin) {
ui->selectedModel->setPixmap(skin->getTexture().scaled(s, Qt::KeepAspectRatio, Qt::FastTransformation));
}
auto id = ui->capeCombo->currentData();
ui->capeImage->setPixmap(m_capes.value(id.toString(), {}).scaled(s, Qt::KeepAspectRatio, Qt::FastTransformation));
}

View File

@ -34,6 +34,7 @@ class SkinManageDialog : public QDialog {
public: public:
explicit SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct); explicit SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct);
virtual ~SkinManageDialog(); virtual ~SkinManageDialog();
void resizeEvent(QResizeEvent* event) override;
public slots: public slots:
void selectionChanged(QItemSelection, QItemSelection); void selectionChanged(QItemSelection, QItemSelection);

View File

@ -24,7 +24,10 @@
<string/> <string/>
</property> </property>
<property name="scaledContents"> <property name="scaledContents">
<bool>true</bool> <bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property> </property>
</widget> </widget>
</item> </item>
@ -75,7 +78,10 @@
<string/> <string/>
</property> </property>
<property name="scaledContents"> <property name="scaledContents">
<bool>true</bool> <bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -4,7 +4,7 @@ class BasePage;
class BasePageContainer { class BasePageContainer {
public: public:
virtual ~BasePageContainer(){}; virtual ~BasePageContainer() {};
virtual bool selectPage(QString pageId) = 0; virtual bool selectPage(QString pageId) = 0;
virtual BasePage* selectedPage() const = 0; virtual BasePage* selectedPage() const = 0;
virtual BasePage* getPage(QString pageId) { return nullptr; }; virtual BasePage* getPage(QString pageId) { return nullptr; };

View File

@ -371,7 +371,7 @@ void ModrinthManagedPackPage::update()
void ModrinthManagedPackPage::updateFromFile() void ModrinthManagedPackPage::updateFromFile()
{ {
auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), "Modrinth pack (*.mrpack *.zip)"); auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), tr("Modrinth pack") + " (*.mrpack *.zip)");
if (output.isEmpty()) if (output.isEmpty())
return; return;
QMap<QString, QString> extra_info; QMap<QString, QString> extra_info;
@ -538,7 +538,7 @@ void FlameManagedPackPage::update()
void FlameManagedPackPage::updateFromFile() void FlameManagedPackPage::updateFromFile()
{ {
auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), "CurseForge pack (*.zip)"); auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), tr("CurseForge pack") + " (*.zip)");
if (output.isEmpty()) if (output.isEmpty())
return; return;

View File

@ -50,7 +50,7 @@ class ManagedPackPage : public QWidget, public BasePage {
/** Gets the necessary information about the managed pack, such as /** Gets the necessary information about the managed pack, such as
* available versions*/ * available versions*/
virtual void parseManagedPack(){}; virtual void parseManagedPack() {};
/** URL of the managed pack. /** URL of the managed pack.
* Not the version-specific one. * Not the version-specific one.
@ -64,8 +64,8 @@ class ManagedPackPage : public QWidget, public BasePage {
*/ */
virtual void suggestVersion(); virtual void suggestVersion();
virtual void update(){}; virtual void update() {};
virtual void updateFromFile(){}; virtual void updateFromFile() {};
protected slots: protected slots:
/** Does the necessary UI changes for when something failed. /** Does the necessary UI changes for when something failed.

View File

@ -41,6 +41,7 @@
#include "ui_ExternalResourcesPage.h" #include "ui_ExternalResourcesPage.h"
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include <QAction>
#include <QEvent> #include <QEvent>
#include <QKeyEvent> #include <QKeyEvent>
#include <QMenu> #include <QMenu>
@ -122,13 +123,19 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
ui->actionsToolbar->addAction(ui->actionVisitItemPage); ui->actionsToolbar->addAction(ui->actionVisitItemPage);
connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages); connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages);
auto changeVersion = new QAction(tr("Change Version"));
changeVersion->setToolTip(tr("Change mod version"));
changeVersion->setEnabled(false);
ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, changeVersion);
connect(changeVersion, &QAction::triggered, this, &ModFolderPage::changeModVersion);
ui->actionsToolbar->insertActionAfter(ui->actionVisitItemPage, ui->actionExportMetadata); ui->actionsToolbar->insertActionAfter(ui->actionVisitItemPage, ui->actionExportMetadata);
connect(ui->actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata); connect(ui->actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata);
auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); }; auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); };
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
[this, check_allow_update, actionRemoveItemMetadata] { [this, check_allow_update, actionRemoveItemMetadata, changeVersion] {
ui->actionUpdateItem->setEnabled(check_allow_update()); ui->actionUpdateItem->setEnabled(check_allow_update());
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
@ -138,11 +145,12 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
if (selected <= 1) { if (selected <= 1) {
ui->actionVisitItemPage->setText(tr("Visit mod's page")); ui->actionVisitItemPage->setText(tr("Visit mod's page"));
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page")); ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
} else { } else {
ui->actionVisitItemPage->setText(tr("Visit mods' pages")); ui->actionVisitItemPage->setText(tr("Visit mods' pages"));
ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods")); ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods"));
} }
changeVersion->setEnabled(mods_list.length() == 1 && mods_list[0]->metadata() != nullptr);
ui->actionVisitItemPage->setEnabled(selected != 0); ui->actionVisitItemPage->setEnabled(selected != 0);
actionRemoveItemMetadata->setEnabled(selected != 0); actionRemoveItemMetadata->setEnabled(selected != 0);
}); });
@ -377,6 +385,57 @@ void ModFolderPage::deleteModMetadata()
m_model->deleteModsMetadata(selection); m_model->deleteModsMetadata(selection);
} }
void ModFolderPage::changeModVersion()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
if (!profile->getModLoaders().has_value()) {
QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!"));
return;
}
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!"));
return;
}
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedMods(selection);
if (mods_list.length() != 1 || mods_list[0]->metadata() == nullptr)
return;
ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance);
mdownload.setModMetadata((*mods_list.begin())->metadata());
if (mdownload.exec()) {
auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count())
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
tasks->deleteLater();
});
for (auto& task : mdownload.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}
void ModFolderPage::exportModMetadata() void ModFolderPage::exportModMetadata()
{ {
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();

View File

@ -67,6 +67,7 @@ class ModFolderPage : public ExternalResourcesPage {
void installMods(); void installMods();
void updateMods(bool includeDeps = false); void updateMods(bool includeDeps = false);
void visitModPages(); void visitModPages();
void changeModVersion();
protected: protected:
std::shared_ptr<ModFolderModel> m_model; std::shared_ptr<ModFolderModel> m_model;

View File

@ -168,7 +168,7 @@ class ServersModel : public QAbstractListModel {
m_saveTimer.setInterval(5000); m_saveTimer.setInterval(5000);
connect(&m_saveTimer, &QTimer::timeout, this, &ServersModel::save_internal); connect(&m_saveTimer, &QTimer::timeout, this, &ServersModel::save_internal);
} }
virtual ~ServersModel(){}; virtual ~ServersModel() {};
void observe() void observe()
{ {

View File

@ -297,7 +297,7 @@ void VersionPage::on_actionRemove_triggered()
void VersionPage::on_actionAdd_to_Minecraft_jar_triggered() void VersionPage::on_actionAdd_to_Minecraft_jar_triggered()
{ {
auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods") + " (*.zip *.jar)",
APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget()); APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
if (!list.empty()) { if (!list.empty()) {
m_profile->installJarMods(list); m_profile->installJarMods(list);
@ -307,7 +307,7 @@ void VersionPage::on_actionAdd_to_Minecraft_jar_triggered()
void VersionPage::on_actionReplace_Minecraft_jar_triggered() void VersionPage::on_actionReplace_Minecraft_jar_triggered()
{ {
auto jarPath = GuiUtil::BrowseForFile("jar", tr("Select jar"), tr("Minecraft.jar replacement (*.jar)"), auto jarPath = GuiUtil::BrowseForFile("jar", tr("Select jar"), tr("Minecraft.jar replacement") + " (*.jar)",
APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget()); APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
if (!jarPath.isEmpty()) { if (!jarPath.isEmpty()) {
m_profile->installCustomJar(jarPath); m_profile->installCustomJar(jarPath);
@ -317,7 +317,7 @@ void VersionPage::on_actionReplace_Minecraft_jar_triggered()
void VersionPage::on_actionImport_Components_triggered() void VersionPage::on_actionImport_Components_triggered()
{ {
QStringList list = GuiUtil::BrowseForFiles("component", tr("Select components"), tr("Components (*.json)"), QStringList list = GuiUtil::BrowseForFiles("component", tr("Select components"), tr("Components") + " (*.json)",
APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget()); APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
if (!list.isEmpty()) { if (!list.isEmpty()) {
@ -332,7 +332,7 @@ void VersionPage::on_actionImport_Components_triggered()
void VersionPage::on_actionAdd_Agents_triggered() void VersionPage::on_actionAdd_Agents_triggered()
{ {
QStringList list = GuiUtil::BrowseForFiles("agent", tr("Select agents"), tr("Java agents (*.jar)"), QStringList list = GuiUtil::BrowseForFiles("agent", tr("Select agents"), tr("Java agents") + " (*.jar)",
APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget()); APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
if (!list.isEmpty()) if (!list.isEmpty())

View File

@ -343,7 +343,7 @@ void WorldListPage::worldChanged([[maybe_unused]] const QModelIndex& current, [[
void WorldListPage::on_actionAdd_triggered() void WorldListPage::on_actionAdd_triggered()
{ {
auto list = GuiUtil::BrowseForFiles(displayName(), tr("Select a Minecraft world zip"), tr("Minecraft World Zip File (*.zip)"), auto list = GuiUtil::BrowseForFiles(displayName(), tr("Select a Minecraft world zip"), tr("Minecraft World Zip File") + " (*.zip)",
QString(), this->parentWidget()); QString(), this->parentWidget());
if (!list.empty()) { if (!list.empty()) {
m_worlds->stopWatching(); m_worlds->stopWatching();

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