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

This commit is contained in:
Trial97 2024-08-18 22:39:46 +03:00
commit 38b5ee198a
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
83 changed files with 650 additions and 651 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.1" qt_version: "6.7.2"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: windows-2022 - os: windows-2022
@ -90,7 +90,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: "win64_msvc2019_arm64" qt_arch: "win64_msvc2019_arm64"
qt_version: "6.7.1" qt_version: "6.7.2"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: macos-12 - os: macos-12
@ -99,7 +99,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: mac qt_host: mac
qt_arch: "" qt_arch: ""
qt_version: "6.7.1" qt_version: "6.7.2"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: macos-12 - os: macos-12
@ -160,7 +160,7 @@ jobs:
- name: Setup ccache - name: Setup ccache
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
uses: hendrikmuhs/ccache-action@v1.2.13 uses: hendrikmuhs/ccache-action@v1.2.14
with: with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}

View File

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

View File

@ -1,3 +0,0 @@
# Build Instructions
Full build instructions are available on [the website](https://prismlauncher.org/wiki/development/build-instructions/).

View File

@ -61,7 +61,12 @@ The translation effort for Prism Launcher is hosted on [Weblate](https://hosted.
## Building ## Building
If you want to build Prism Launcher yourself, check the [Build Instructions](https://prismlauncher.org/wiki/development/build-instructions/). If you want to build Prism Launcher yourself, check the build instructions:
- [Windows](https://prismlauncher.org/wiki/development/build-instructions/windows/)
- [Linux](https://prismlauncher.org/wiki/development/build-instructions/linux/)
- [MacOS](https://prismlauncher.org/wiki/development/build-instructions/macos/)
- [OpenBSD](https://prismlauncher.org/wiki/development/build-instructions/openbsd/)
## Sponsors & Partners ## Sponsors & Partners

18
flake.lock generated
View File

@ -23,11 +23,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1719994518, "lastModified": 1722555600,
"narHash": "sha256-pQMhCCHyQGRzdfAkdJ4cIWiw+JNuWsTX7f0ZYSyz0VY=", "narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "9227223f6d922fee3c7b190b2cc238a99527bbb7", "rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -75,11 +75,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1720768451, "lastModified": 1723175592,
"narHash": "sha256-EYekUHJE2gxeo2pM/zM9Wlqw1Uw2XTJXOSAO79ksc4Y=", "narHash": "sha256-M0xJ3FbDUc4fRZ84dPGx5VvgFsOzds77KiBMW/mMTnI=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "7e7c39ea35c5cdd002cd4588b03a3fb9ece6fad9", "rev": "5e0ca22929f3342b19569b21b2f3462f053e497b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -103,11 +103,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1720524665, "lastModified": 1723202784,
"narHash": "sha256-ni/87oHPZm6Gv0ECYxr1f6uxB0UKBWJ6HvS7lwLU6oY=", "narHash": "sha256-qbhjc/NEGaDbyy0ucycubq4N3//gDFFH3DOmp1D3u1Q=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "8d6a17d0cdf411c55f12602624df6368ad86fac1", "rev": "c7012d0c18567c889b948781bc74a501e92275d1",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -236,6 +236,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{ { { "d", "dir" }, "Use a custom path as application root (use '.' for current directory)", "directory" }, { { { "d", "dir" }, "Use a custom path as application root (use '.' for current directory)", "directory" },
{ { "l", "launch" }, "Launch the specified instance (by instance ID)", "instance" }, { { "l", "launch" }, "Launch the specified instance (by instance ID)", "instance" },
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" }, { { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
{ { "w", "world" }, "Join the specified world on launch (only valid in combination with --launch)", "world" },
{ { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" }, { { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" }, { "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
{ { "I", "import" }, "Import instance or resource from specified local path or URL", "url" }, { { "I", "import" }, "Import instance or resource from specified local path or URL", "url" },
@ -250,6 +251,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_instanceIdToLaunch = parser.value("launch"); m_instanceIdToLaunch = parser.value("launch");
m_serverToJoin = parser.value("server"); m_serverToJoin = parser.value("server");
m_worldToJoin = parser.value("world");
m_profileToUse = parser.value("profile"); m_profileToUse = parser.value("profile");
m_liveCheck = parser.isSet("alive"); m_liveCheck = parser.isSet("alive");
@ -265,7 +267,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
} }
// error if --launch is missing with --server or --profile // error if --launch is missing with --server or --profile
if ((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) { if (((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty()) || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) {
std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl; std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl;
m_status = Application::Failed; m_status = Application::Failed;
return; return;
@ -385,6 +387,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
if (!m_serverToJoin.isEmpty()) { if (!m_serverToJoin.isEmpty()) {
launch.args["server"] = m_serverToJoin; launch.args["server"] = m_serverToJoin;
} else if (!m_worldToJoin.isEmpty()) {
launch.args["world"] = m_worldToJoin;
} }
if (!m_profileToUse.isEmpty()) { if (!m_profileToUse.isEmpty()) {
launch.args["profile"] = m_profileToUse; launch.args["profile"] = m_profileToUse;
@ -523,6 +527,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
} }
if (!m_serverToJoin.isEmpty()) { if (!m_serverToJoin.isEmpty()) {
qDebug() << "Address of server to join :" << m_serverToJoin; qDebug() << "Address of server to join :" << m_serverToJoin;
} else if (!m_worldToJoin.isEmpty()) {
qDebug() << "Name of the world to join :" << m_worldToJoin;
} }
qDebug() << "<> Paths set."; qDebug() << "<> Paths set.";
} }
@ -1157,14 +1163,17 @@ void Application::performMainStartupAction()
if (!m_instanceIdToLaunch.isEmpty()) { if (!m_instanceIdToLaunch.isEmpty()) {
auto inst = instances()->getInstanceById(m_instanceIdToLaunch); auto inst = instances()->getInstanceById(m_instanceIdToLaunch);
if (inst) { if (inst) {
MinecraftServerTargetPtr serverToJoin = nullptr; MinecraftTarget::Ptr targetToJoin = nullptr;
MinecraftAccountPtr accountToUse = nullptr; MinecraftAccountPtr accountToUse = nullptr;
qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching"; qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching";
if (!m_serverToJoin.isEmpty()) { if (!m_serverToJoin.isEmpty()) {
// FIXME: validate the server string // FIXME: validate the server string
serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(m_serverToJoin))); targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(m_serverToJoin, false)));
qDebug() << " Launching with server" << m_serverToJoin; qDebug() << " Launching with server" << m_serverToJoin;
} else if (!m_worldToJoin.isEmpty()) {
targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(m_worldToJoin, true)));
qDebug() << " Launching with world" << m_worldToJoin;
} }
if (!m_profileToUse.isEmpty()) { if (!m_profileToUse.isEmpty()) {
@ -1175,7 +1184,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse; qDebug() << " Launching with account" << m_profileToUse;
} }
launch(inst, true, false, serverToJoin, accountToUse); launch(inst, true, false, targetToJoin, accountToUse);
return; return;
} }
} }
@ -1265,6 +1274,7 @@ void Application::messageReceived(const QByteArray& message)
} else if (command == "launch") { } else if (command == "launch") {
QString id = received.args["id"]; QString id = received.args["id"];
QString server = received.args["server"]; QString server = received.args["server"];
QString world = received.args["world"];
QString profile = received.args["profile"]; QString profile = received.args["profile"];
InstancePtr instance; InstancePtr instance;
@ -1279,11 +1289,12 @@ void Application::messageReceived(const QByteArray& message)
return; return;
} }
MinecraftServerTargetPtr serverObject = nullptr; MinecraftTarget::Ptr serverObject = nullptr;
if (!server.isEmpty()) { if (!server.isEmpty()) {
serverObject = std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(server)); serverObject = std::make_shared<MinecraftTarget>(MinecraftTarget::parse(server, false));
} else if (!world.isEmpty()) {
serverObject = std::make_shared<MinecraftTarget>(MinecraftTarget::parse(world, true));
} }
MinecraftAccountPtr accountObject; MinecraftAccountPtr accountObject;
if (!profile.isEmpty()) { if (!profile.isEmpty()) {
accountObject = accounts()->getAccountByProfileName(profile); accountObject = accounts()->getAccountByProfileName(profile);
@ -1332,11 +1343,7 @@ bool Application::openJsonEditor(const QString& filename)
} }
} }
bool Application::launch(InstancePtr instance, bool Application::launch(InstancePtr instance, bool online, bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse)
bool online,
bool demo,
MinecraftServerTargetPtr serverToJoin,
MinecraftAccountPtr accountToUse)
{ {
if (m_updateRunning) { if (m_updateRunning) {
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed."; qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
@ -1354,7 +1361,7 @@ bool Application::launch(InstancePtr instance,
controller->setOnline(online); controller->setOnline(online);
controller->setDemo(demo); controller->setDemo(demo);
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get()); controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
controller->setServerToJoin(serverToJoin); controller->setTargetToJoin(targetToJoin);
controller->setAccountToUse(accountToUse); controller->setAccountToUse(accountToUse);
if (window) { if (window) {
controller->setParentWidget(window); controller->setParentWidget(window);

View File

@ -47,7 +47,7 @@
#include <BaseInstance.h> #include <BaseInstance.h>
#include "minecraft/launch/MinecraftServerTarget.h" #include "minecraft/launch/MinecraftTarget.h"
class LaunchController; class LaunchController;
class LocalPeer; class LocalPeer;
@ -202,7 +202,7 @@ class Application : public QApplication {
bool launch(InstancePtr instance, bool launch(InstancePtr instance,
bool online = true, bool online = true,
bool demo = false, bool demo = false,
MinecraftServerTargetPtr serverToJoin = nullptr, MinecraftTarget::Ptr targetToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr); MinecraftAccountPtr accountToUse = nullptr);
bool kill(InstancePtr instance); bool kill(InstancePtr instance);
void closeCurrentWindow(); void closeCurrentWindow();
@ -290,6 +290,7 @@ class Application : public QApplication {
QString m_detectedOpenALPath; QString m_detectedOpenALPath;
QString m_instanceIdToLaunch; QString m_instanceIdToLaunch;
QString m_serverToJoin; QString m_serverToJoin;
QString m_worldToJoin;
QString m_profileToUse; QString m_profileToUse;
bool m_liveCheck = false; bool m_liveCheck = false;
QList<QUrl> m_urlsToImport; QList<QUrl> m_urlsToImport;

View File

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

View File

@ -138,7 +138,6 @@ set(NET_SOURCES
net/HeaderProxy.h net/HeaderProxy.h
net/RawHeaderProxy.h net/RawHeaderProxy.h
net/ApiHeaderProxy.h net/ApiHeaderProxy.h
net/StaticHeaderProxy.h
net/ApiDownload.h net/ApiDownload.h
net/ApiDownload.cpp net/ApiDownload.cpp
net/ApiUpload.cpp net/ApiUpload.cpp
@ -263,8 +262,8 @@ set(MINECRAFT_SOURCES
minecraft/launch/ExtractNatives.h minecraft/launch/ExtractNatives.h
minecraft/launch/LauncherPartLaunch.cpp minecraft/launch/LauncherPartLaunch.cpp
minecraft/launch/LauncherPartLaunch.h minecraft/launch/LauncherPartLaunch.h
minecraft/launch/MinecraftServerTarget.cpp minecraft/launch/MinecraftTarget.cpp
minecraft/launch/MinecraftServerTarget.h minecraft/launch/MinecraftTarget.h
minecraft/launch/PrintInstanceInfo.cpp minecraft/launch/PrintInstanceInfo.cpp
minecraft/launch/PrintInstanceInfo.h minecraft/launch/PrintInstanceInfo.h
minecraft/launch/ReconstructAssets.cpp minecraft/launch/ReconstructAssets.cpp

View File

@ -72,7 +72,7 @@ void appendSafe(const QString& filename, const QByteArray& data);
void append(const QString& filename, const QByteArray& data); void append(const QString& filename, const QByteArray& data);
/** /**
* read data from a file safely\ * read data from a file safely
*/ */
QByteArray read(const QString& filename); QByteArray read(const QString& filename);

View File

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

View File

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

View File

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

View File

@ -344,6 +344,17 @@ std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, con
qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path)); qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path));
} }
} }
} else if (fileInfo.isDir()) {
// Ensure the folder has the minimal required permissions
QFile::Permissions minimalPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup |
QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther;
QFile::Permissions currentPermissions = fileInfo.permissions();
if ((currentPermissions & minimalPermissions) != minimalPermissions) {
if (!QFile::setPermissions(target_file_path, minimalPermissions)) {
qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path));
}
}
} }
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
} while (zip->goToNextFile()); } while (zip->goToNextFile());
@ -560,7 +571,7 @@ auto ExtractZipTask::extractZip() -> ZipResult
if (!file_name.startsWith(m_subdirectory)) if (!file_name.startsWith(m_subdirectory))
continue; continue;
auto relative_file_name = QDir::fromNativeSeparators(file_name.remove(0, m_subdirectory.size())); auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size()));
auto original_name = relative_file_name; auto original_name = relative_file_name;
setStatus("Unziping: " + relative_file_name); setStatus("Unziping: " + relative_file_name);
@ -610,6 +621,17 @@ auto ExtractZipTask::extractZip() -> ZipResult
logWarning(tr("Could not fix permissions for %1").arg(target_file_path)); logWarning(tr("Could not fix permissions for %1").arg(target_file_path));
} }
} }
} else if (fileInfo.isDir()) {
// Ensure the folder has the minimal required permissions
QFile::Permissions minimalPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup |
QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther;
QFile::Permissions currentPermissions = fileInfo.permissions();
if ((currentPermissions & minimalPermissions) != minimalPermissions) {
if (!QFile::setPermissions(target_file_path, minimalPermissions)) {
logWarning(tr("Could not fix permissions for %1").arg(target_file_path));
}
}
} }
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;

View File

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

View File

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

View File

@ -182,56 +182,58 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
else if (keyType == KEY_WOW64_32KEY) else if (keyType == KEY_WOW64_32KEY)
archType = "32"; archType = "32";
HKEY jreKey; for (HKEY baseRegistry : { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE }) {
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyName.toStdWString().c_str(), 0, KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == HKEY jreKey;
ERROR_SUCCESS) { if (RegOpenKeyExW(baseRegistry, keyName.toStdWString().c_str(), 0, KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) ==
// Read the current type version from the registry. ERROR_SUCCESS) {
// This will be used to find any key that contains the JavaHome value. // Read the current type version from the registry.
// This will be used to find any key that contains the JavaHome value.
WCHAR subKeyName[255]; WCHAR subKeyName[255];
DWORD subKeyNameSize, numSubKeys, retCode; DWORD subKeyNameSize, numSubKeys, retCode;
// Get the number of subkeys // Get the number of subkeys
RegQueryInfoKeyW(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL); RegQueryInfoKeyW(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
// Iterate until RegEnumKeyEx fails // Iterate until RegEnumKeyEx fails
if (numSubKeys > 0) { if (numSubKeys > 0) {
for (DWORD i = 0; i < numSubKeys; i++) { for (DWORD i = 0; i < numSubKeys; i++) {
subKeyNameSize = 255; subKeyNameSize = 255;
retCode = RegEnumKeyExW(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL); retCode = RegEnumKeyExW(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL);
QString newSubkeyName = QString::fromWCharArray(subKeyName); QString newSubkeyName = QString::fromWCharArray(subKeyName);
if (retCode == ERROR_SUCCESS) { if (retCode == ERROR_SUCCESS) {
// Now open the registry key for the version that we just got. // Now open the registry key for the version that we just got.
QString newKeyName = keyName + "\\" + newSubkeyName + subkeySuffix; QString newKeyName = keyName + "\\" + newSubkeyName + subkeySuffix;
HKEY newKey; HKEY newKey;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, newKeyName.toStdWString().c_str(), 0, KEY_READ | keyType, &newKey) == if (RegOpenKeyExW(baseRegistry, newKeyName.toStdWString().c_str(), 0, KEY_READ | keyType, &newKey) ==
ERROR_SUCCESS) { ERROR_SUCCESS) {
// Read the JavaHome value to find where Java is installed. // Read the JavaHome value to find where Java is installed.
DWORD valueSz = 0; DWORD valueSz = 0;
if (RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, NULL, &valueSz) == ERROR_SUCCESS) { if (RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, NULL, &valueSz) == ERROR_SUCCESS) {
WCHAR* value = new WCHAR[valueSz]; WCHAR* value = new WCHAR[valueSz];
RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE*)value, &valueSz); RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE*)value, &valueSz);
QString newValue = QString::fromWCharArray(value); QString newValue = QString::fromWCharArray(value);
delete[] value; delete[] value;
// Now, we construct the version object and add it to the list. // Now, we construct the version object and add it to the list.
JavaInstallPtr javaVersion(new JavaInstall()); JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = newSubkeyName; javaVersion->id = newSubkeyName;
javaVersion->arch = archType; javaVersion->arch = archType;
javaVersion->path = QDir(FS::PathCombine(newValue, "bin")).absoluteFilePath("javaw.exe"); javaVersion->path = QDir(FS::PathCombine(newValue, "bin")).absoluteFilePath("javaw.exe");
javas.append(javaVersion); javas.append(javaVersion);
}
RegCloseKey(newKey);
} }
RegCloseKey(newKey);
} }
} }
} }
}
RegCloseKey(jreKey); RegCloseKey(jreKey);
}
} }
return javas; return javas;

View File

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

View File

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

View File

@ -196,8 +196,9 @@ void MinecraftInstance::loadSpecificSettings()
} }
// Join server on launch, this does not have a global override // Join server on launch, this does not have a global override
m_settings->registerSetting("JoinServerOnLaunch", false); m_settings->registerSetting({ "JoinServerOnLaunch", "JoinOnLaunch" }, false);
m_settings->registerSetting("JoinServerOnLaunchAddress", ""); m_settings->registerSetting("JoinServerOnLaunchAddress", "");
m_settings->registerSetting("JoinWorldOnLaunch", "");
// Use account for instance, this does not have a global override // Use account for instance, this does not have a global override
m_settings->registerSetting("UseAccountForInstance", false); m_settings->registerSetting("UseAccountForInstance", false);
@ -523,8 +524,7 @@ QStringList MinecraftInstance::javaArguments()
if (javaVersion.isModular() && shouldApplyOnlineFixes()) if (javaVersion.isModular() && shouldApplyOnlineFixes())
// allow reflective access to java.net - required by the skin fix // allow reflective access to java.net - required by the skin fix
args << "--add-opens" args << "--add-opens" << "java.base/java.net=ALL-UNNAMED";
<< "java.base/java.net=ALL-UNNAMED";
return args; return args;
} }
@ -608,7 +608,7 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
// dlsym variant is only needed for OpenGL and not included in the vulkan layer // dlsym variant is only needed for OpenGL and not included in the vulkan layer
appendLib("libMangoHud_dlsym.so"); appendLib("libMangoHud_dlsym.so");
appendLib("libMangoHud_opengl.so"); appendLib("libMangoHud_opengl.so");
appendLib(mangoHudLib.fileName()); preloadList << mangoHudLibString;
} }
env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":"))); env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":")));
@ -656,7 +656,7 @@ static QString replaceTokensIn(QString text, QMap<QString, QString> with)
return result; return result;
} }
QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) const QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) const
{ {
auto profile = m_components->getProfile(); auto profile = m_components->getProfile();
QString args_pattern = profile->getMinecraftArguments(); QString args_pattern = profile->getMinecraftArguments();
@ -664,12 +664,16 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine
args_pattern += " --tweakClass " + tweaker; args_pattern += " --tweakClass " + tweaker;
} }
if (serverToJoin && !serverToJoin->address.isEmpty()) { if (targetToJoin) {
if (profile->hasTrait("feature:is_quick_play_multiplayer")) { if (!targetToJoin->address.isEmpty()) {
args_pattern += " --quickPlayMultiplayer " + serverToJoin->address + ':' + QString::number(serverToJoin->port); if (profile->hasTrait("feature:is_quick_play_multiplayer")) {
} else { args_pattern += " --quickPlayMultiplayer " + targetToJoin->address + ':' + QString::number(targetToJoin->port);
args_pattern += " --server " + serverToJoin->address; } else {
args_pattern += " --port " + QString::number(serverToJoin->port); args_pattern += " --server " + targetToJoin->address;
args_pattern += " --port " + QString::number(targetToJoin->port);
}
} else if (!targetToJoin->world.isEmpty() && profile->hasTrait("feature:is_quick_play_singleplayer")) {
args_pattern += " --quickPlaySingleplayer " + targetToJoin->world;
} }
} }
@ -713,7 +717,7 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine
return parts; return parts;
} }
QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin)
{ {
QString launchScript; QString launchScript;
@ -732,9 +736,13 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
launchScript += "appletClass " + appletClass + "\n"; launchScript += "appletClass " + appletClass + "\n";
} }
if (serverToJoin && !serverToJoin->address.isEmpty()) { if (targetToJoin) {
launchScript += "serverAddress " + serverToJoin->address + "\n"; if (!targetToJoin->address.isEmpty()) {
launchScript += "serverPort " + QString::number(serverToJoin->port) + "\n"; launchScript += "serverAddress " + targetToJoin->address + "\n";
launchScript += "serverPort " + QString::number(targetToJoin->port) + "\n";
} else if (!targetToJoin->world.isEmpty()) {
launchScript += "worldName " + targetToJoin->world + "\n";
}
} }
// generic minecraft params // generic minecraft params
@ -787,16 +795,15 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
return launchScript; return launchScript;
} }
QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin)
{ {
QStringList out; QStringList out;
out << "Main Class:" out << "Main Class:" << " " + getMainClass() << "";
<< " " + getMainClass() << ""; out << "Native path:" << " " + getNativePath() << "";
out << "Native path:"
<< " " + getNativePath() << "";
auto profile = m_components->getProfile(); auto profile = m_components->getProfile();
// traits
auto alltraits = traits(); auto alltraits = traits();
if (alltraits.size()) { if (alltraits.size()) {
out << "Traits:"; out << "Traits:";
@ -806,6 +813,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << ""; out << "";
} }
// native libraries
auto settings = this->settings(); auto settings = this->settings();
bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool(); bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool();
bool nativeGLFW = settings->get("UseNativeGLFW").toBool(); bool nativeGLFW = settings->get("UseNativeGLFW").toBool();
@ -841,6 +849,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << ""; out << "";
} }
// mods and core mods
auto printModList = [&](const QString& label, ModFolderModel& model) { auto printModList = [&](const QString& label, ModFolderModel& model) {
if (model.size()) { if (model.size()) {
out << QString("%1:").arg(label); out << QString("%1:").arg(label);
@ -869,6 +878,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
printModList("Mods", *(loaderModList().get())); printModList("Mods", *(loaderModList().get()));
printModList("Core Mods", *(coreModList().get())); printModList("Core Mods", *(coreModList().get()));
// jar mods
auto& jarMods = profile->getJarMods(); auto& jarMods = profile->getJarMods();
if (jarMods.size()) { if (jarMods.size()) {
out << "Jar Mods:"; out << "Jar Mods:";
@ -884,11 +894,13 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << ""; out << "";
} }
auto params = processMinecraftArgs(nullptr, serverToJoin); // minecraft arguments
auto params = processMinecraftArgs(nullptr, targetToJoin);
out << "Params:"; out << "Params:";
out << " " + params.join(' '); out << " " + params.join(' ');
out << ""; out << "";
// window size
QString windowParams; QString windowParams;
if (settings->get("LaunchMaximized").toBool()) { if (settings->get("LaunchMaximized").toBool()) {
out << "Window size: max (if available)"; out << "Window size: max (if available)";
@ -1034,7 +1046,7 @@ Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode)
return nullptr; return nullptr;
} }
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin)
{ {
updateRuntimeContext(); updateRuntimeContext();
// FIXME: get rid of shared_from_this ... // FIXME: get rid of shared_from_this ...
@ -1058,16 +1070,23 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(makeShared<CreateGameFolders>(pptr)); process->appendStep(makeShared<CreateGameFolders>(pptr));
} }
if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool()) { if (!targetToJoin && settings()->get("JoinOnLaunch").toBool()) {
QString fullAddress = settings()->get("JoinServerOnLaunchAddress").toString(); QString fullAddress = settings()->get("JoinServerOnLaunchAddress").toString();
serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress))); if (!fullAddress.isEmpty()) {
targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(fullAddress, false)));
} else {
QString world = settings()->get("JoinWorldOnLaunch").toString();
if (!world.isEmpty()) {
targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(world, true)));
}
}
} }
if (serverToJoin && serverToJoin->port == 25565) { if (targetToJoin && targetToJoin->port == 25565) {
// Resolve server address to join on launch // Resolve server address to join on launch
auto step = makeShared<LookupServerAddress>(pptr); auto step = makeShared<LookupServerAddress>(pptr);
step->setLookupAddress(serverToJoin->address); step->setLookupAddress(targetToJoin->address);
step->setOutputAddressPtr(serverToJoin); step->setOutputAddressPtr(targetToJoin);
process->appendStep(step); process->appendStep(step);
} }
@ -1100,7 +1119,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
// print some instance info here... // print some instance info here...
{ {
process->appendStep(makeShared<PrintInstanceInfo>(pptr, session, serverToJoin)); process->appendStep(makeShared<PrintInstanceInfo>(pptr, session, targetToJoin));
} }
// extract native jars if needed // extract native jars if needed
@ -1123,7 +1142,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
auto step = makeShared<LauncherPartLaunch>(pptr); auto step = makeShared<LauncherPartLaunch>(pptr);
step->setWorkingDirectory(gameRoot()); step->setWorkingDirectory(gameRoot());
step->setAuthSession(session); step->setAuthSession(session);
step->setServerToJoin(serverToJoin); step->setTargetToJoin(targetToJoin);
process->appendStep(step); process->appendStep(step);
} }

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/Download.h" #include "net/Download.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "net/StaticHeaderProxy.h" #include "net/RawHeaderProxy.h"
#include "tasks/Task.h" #include "tasks/Task.h"
EntitlementsStep::EntitlementsStep(AccountData* data) : AuthStep(data) {} EntitlementsStep::EntitlementsStep(AccountData* data) : AuthStep(data) {}
@ -33,7 +33,7 @@ void EntitlementsStep::perform()
m_response.reset(new QByteArray()); m_response.reset(new QByteArray());
m_request = Net::Download::makeByteArray(url, m_response); m_request = Net::Download::makeByteArray(url, m_response);
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers)); m_request->addHeaderProxy(new Net::RawHeaderProxy(headers));
m_task.reset(new NetJob("EntitlementsStep", APPLICATION->network())); m_task.reset(new NetJob("EntitlementsStep", APPLICATION->network()));
m_task->setAskRetry(false); m_task->setAskRetry(false);

View File

@ -7,7 +7,7 @@
#include "Logging.h" #include "Logging.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
#include "net/StaticHeaderProxy.h" #include "net/RawHeaderProxy.h"
#include "net/Upload.h" #include "net/Upload.h"
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {} LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {}
@ -38,7 +38,7 @@ void LauncherLoginStep::perform()
m_response.reset(new QByteArray()); m_response.reset(new QByteArray());
m_request = Net::Upload::makeByteArray(url, m_response, requestBody.toUtf8()); m_request = Net::Upload::makeByteArray(url, m_response, requestBody.toUtf8());
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers)); m_request->addHeaderProxy(new Net::RawHeaderProxy(headers));
m_task.reset(new NetJob("LauncherLoginStep", APPLICATION->network())); m_task.reset(new NetJob("LauncherLoginStep", APPLICATION->network()));
m_task->setAskRetry(false); m_task->setAskRetry(false);

View File

@ -40,7 +40,7 @@
#include "Application.h" #include "Application.h"
#include "Json.h" #include "Json.h"
#include "net/StaticHeaderProxy.h" #include "net/RawHeaderProxy.h"
// https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code // https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code
MSADeviceCodeStep::MSADeviceCodeStep(AccountData* data) : AuthStep(data) MSADeviceCodeStep::MSADeviceCodeStep(AccountData* data) : AuthStep(data)
@ -68,7 +68,7 @@ void MSADeviceCodeStep::perform()
}; };
m_response.reset(new QByteArray()); m_response.reset(new QByteArray());
m_request = Net::Upload::makeByteArray(url, m_response, payload); m_request = Net::Upload::makeByteArray(url, m_response, payload);
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers)); m_request->addHeaderProxy(new Net::RawHeaderProxy(headers));
m_task.reset(new NetJob("MSADeviceCodeStep", APPLICATION->network())); m_task.reset(new NetJob("MSADeviceCodeStep", APPLICATION->network()));
m_task->setAskRetry(false); m_task->setAskRetry(false);
@ -183,7 +183,7 @@ void MSADeviceCodeStep::authenticateUser()
}; };
m_response.reset(new QByteArray()); m_response.reset(new QByteArray());
m_request = Net::Upload::makeByteArray(url, m_response, payload); m_request = Net::Upload::makeByteArray(url, m_response, payload);
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers)); m_request->addHeaderProxy(new Net::RawHeaderProxy(headers));
connect(m_request.get(), &Task::finished, this, &MSADeviceCodeStep::authenticationFinished); connect(m_request.get(), &Task::finished, this, &MSADeviceCodeStep::authenticationFinished);

View File

@ -5,7 +5,7 @@
#include "Application.h" #include "Application.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
#include "net/StaticHeaderProxy.h" #include "net/RawHeaderProxy.h"
MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {} MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {}
@ -23,7 +23,7 @@ void MinecraftProfileStep::perform()
m_response.reset(new QByteArray()); m_response.reset(new QByteArray());
m_request = Net::Download::makeByteArray(url, m_response); m_request = Net::Download::makeByteArray(url, m_response);
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers)); m_request->addHeaderProxy(new Net::RawHeaderProxy(headers));
m_task.reset(new NetJob("MinecraftProfileStep", APPLICATION->network())); m_task.reset(new NetJob("MinecraftProfileStep", APPLICATION->network()));
m_task->setAskRetry(false); m_task->setAskRetry(false);

View File

@ -8,7 +8,7 @@
#include "Logging.h" #include "Logging.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
#include "net/StaticHeaderProxy.h" #include "net/RawHeaderProxy.h"
#include "net/Upload.h" #include "net/Upload.h"
XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind) XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind)
@ -43,7 +43,7 @@ void XboxAuthorizationStep::perform()
}; };
m_response.reset(new QByteArray()); m_response.reset(new QByteArray());
m_request = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8()); m_request = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8());
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers)); m_request->addHeaderProxy(new Net::RawHeaderProxy(headers));
m_task.reset(new NetJob("XboxAuthorizationStep", APPLICATION->network())); m_task.reset(new NetJob("XboxAuthorizationStep", APPLICATION->network()));
m_task->setAskRetry(false); m_task->setAskRetry(false);

View File

@ -6,7 +6,7 @@
#include "Application.h" #include "Application.h"
#include "Logging.h" #include "Logging.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
#include "net/StaticHeaderProxy.h" #include "net/RawHeaderProxy.h"
XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {} XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {}
@ -35,7 +35,7 @@ void XboxProfileStep::perform()
m_response.reset(new QByteArray()); m_response.reset(new QByteArray());
m_request = Net::Download::makeByteArray(url, m_response); m_request = Net::Download::makeByteArray(url, m_response);
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers)); m_request->addHeaderProxy(new Net::RawHeaderProxy(headers));
m_task.reset(new NetJob("XboxProfileStep", APPLICATION->network())); m_task.reset(new NetJob("XboxProfileStep", APPLICATION->network()));
m_task->setAskRetry(false); m_task->setAskRetry(false);

View File

@ -5,7 +5,7 @@
#include "Application.h" #include "Application.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
#include "net/StaticHeaderProxy.h" #include "net/RawHeaderProxy.h"
XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {} XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {}
@ -39,7 +39,7 @@ void XboxUserStep::perform()
}; };
m_response.reset(new QByteArray()); m_response.reset(new QByteArray());
m_request = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8()); m_request = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8());
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers)); m_request->addHeaderProxy(new Net::RawHeaderProxy(headers));
m_task.reset(new NetJob("XboxUserStep", APPLICATION->network())); m_task.reset(new NetJob("XboxUserStep", APPLICATION->network()));
m_task->setAskRetry(false); m_task->setAskRetry(false);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -251,7 +251,7 @@ Task* ModFolderModel::createParseTask(Resource& resource)
bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata) bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
{ {
for (auto mod : allMods()) { for (auto mod : allMods()) {
if (mod->fileinfo().fileName() == filename) { if (mod->getOriginalFileName() == filename) {
auto index_dir = indexDir(); auto index_dir = indexDir();
mod->destroy(index_dir, preserve_metadata, false); mod->destroy(index_dir, preserve_metadata, false);

View File

@ -197,3 +197,11 @@ bool Resource::isMoreThanOneHardLink() const
{ {
return FS::hardLinkCount(m_file_info.absoluteFilePath()) > 1; return FS::hardLinkCount(m_file_info.absoluteFilePath()) > 1;
} }
auto Resource::getOriginalFileName() const -> QString
{
auto fileName = m_file_info.fileName();
if (!m_enabled)
fileName.chop(9);
return fileName;
}

View File

@ -80,6 +80,7 @@ class Resource : public QObject {
[[nodiscard]] auto internal_id() const -> QString { return m_internal_id; } [[nodiscard]] auto internal_id() const -> QString { return m_internal_id; }
[[nodiscard]] auto type() const -> ResourceType { return m_type; } [[nodiscard]] auto type() const -> ResourceType { return m_type; }
[[nodiscard]] bool enabled() const { return m_enabled; } [[nodiscard]] bool enabled() const { return m_enabled; }
[[nodiscard]] auto getOriginalFileName() const -> QString;
[[nodiscard]] QString sizeStr() const { return m_size_str; } [[nodiscard]] QString sizeStr() const { return m_size_str; }
[[nodiscard]] qint64 sizeInfo() const { return m_size_info; } [[nodiscard]] qint64 sizeInfo() const { return m_size_info; }

View File

@ -39,9 +39,9 @@
#include <memory> #include <memory>
#include "net/ByteArraySink.h" #include "net/ByteArraySink.h"
#include "net/StaticHeaderProxy.h" #include "net/RawHeaderProxy.h"
CapeChange::CapeChange(QString token, QString cape) : NetRequest(), m_capeId(cape), m_token(token) CapeChange::CapeChange(QString cape) : NetRequest(), m_capeId(cape)
{ {
logCat = taskMCSkinsLogC; logCat = taskMCSkinsLogC;
} }
@ -57,18 +57,14 @@ QNetworkReply* CapeChange::getReply(QNetworkRequest& request)
} }
} }
void CapeChange::init()
{
addHeaderProxy(new Net::StaticHeaderProxy(QList<Net::HeaderPair>{
{ "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() },
}));
}
CapeChange::Ptr CapeChange::make(QString token, QString capeId) CapeChange::Ptr CapeChange::make(QString token, QString capeId)
{ {
auto up = makeShared<CapeChange>(token, capeId); auto up = makeShared<CapeChange>(capeId);
up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active"); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active");
up->setObjectName(QString("BYTES:") + up->m_url.toString()); up->setObjectName(QString("BYTES:") + up->m_url.toString());
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>())); up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
{ "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() },
}));
return up; return up;
} }

View File

@ -24,16 +24,14 @@ class CapeChange : public Net::NetRequest {
Q_OBJECT Q_OBJECT
public: public:
using Ptr = shared_qobject_ptr<CapeChange>; using Ptr = shared_qobject_ptr<CapeChange>;
CapeChange(QString token, QString capeId); CapeChange(QString capeId);
virtual ~CapeChange() = default; virtual ~CapeChange() = default;
static CapeChange::Ptr make(QString token, QString capeId); static CapeChange::Ptr make(QString token, QString capeId);
void init() override;
protected: protected:
virtual QNetworkReply* getReply(QNetworkRequest&) override; virtual QNetworkReply* getReply(QNetworkRequest&) override;
private: private:
QString m_capeId; QString m_capeId;
QString m_token;
}; };

View File

@ -37,9 +37,9 @@
#include "SkinDelete.h" #include "SkinDelete.h"
#include "net/ByteArraySink.h" #include "net/ByteArraySink.h"
#include "net/StaticHeaderProxy.h" #include "net/RawHeaderProxy.h"
SkinDelete::SkinDelete(QString token) : NetRequest(), m_token(token) SkinDelete::SkinDelete() : NetRequest()
{ {
logCat = taskMCSkinsLogC; logCat = taskMCSkinsLogC;
} }
@ -50,17 +50,13 @@ QNetworkReply* SkinDelete::getReply(QNetworkRequest& request)
return m_network->deleteResource(request); return m_network->deleteResource(request);
} }
void SkinDelete::init()
{
addHeaderProxy(new Net::StaticHeaderProxy(QList<Net::HeaderPair>{
{ "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() },
}));
}
SkinDelete::Ptr SkinDelete::make(QString token) SkinDelete::Ptr SkinDelete::make(QString token)
{ {
auto up = makeShared<SkinDelete>(token); auto up = makeShared<SkinDelete>();
up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active"); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active");
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>())); up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
{ "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() },
}));
return up; return up;
} }

View File

@ -24,15 +24,11 @@ class SkinDelete : public Net::NetRequest {
Q_OBJECT Q_OBJECT
public: public:
using Ptr = shared_qobject_ptr<SkinDelete>; using Ptr = shared_qobject_ptr<SkinDelete>;
SkinDelete(QString token); SkinDelete();
virtual ~SkinDelete() = default; virtual ~SkinDelete() = default;
static SkinDelete::Ptr make(QString token); static SkinDelete::Ptr make(QString token);
void init() override;
protected: protected:
virtual QNetworkReply* getReply(QNetworkRequest&) override; virtual QNetworkReply* getReply(QNetworkRequest&) override;
private:
QString m_token;
}; };

View File

@ -40,9 +40,9 @@
#include "FileSystem.h" #include "FileSystem.h"
#include "net/ByteArraySink.h" #include "net/ByteArraySink.h"
#include "net/StaticHeaderProxy.h" #include "net/RawHeaderProxy.h"
SkinUpload::SkinUpload(QString token, QString path, QString variant) : NetRequest(), m_token(token), m_path(path), m_variant(variant) SkinUpload::SkinUpload(QString path, QString variant) : NetRequest(), m_path(path), m_variant(variant)
{ {
logCat = taskMCSkinsLogC; logCat = taskMCSkinsLogC;
} }
@ -67,18 +67,14 @@ QNetworkReply* SkinUpload::getReply(QNetworkRequest& request)
return m_network->post(request, multiPart); return m_network->post(request, multiPart);
} }
void SkinUpload::init()
{
addHeaderProxy(new Net::StaticHeaderProxy(QList<Net::HeaderPair>{
{ "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() },
}));
}
SkinUpload::Ptr SkinUpload::make(QString token, QString path, QString variant) SkinUpload::Ptr SkinUpload::make(QString token, QString path, QString variant)
{ {
auto up = makeShared<SkinUpload>(token, path, variant); auto up = makeShared<SkinUpload>(path, variant);
up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins"); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins");
up->setObjectName(QString("BYTES:") + up->m_url.toString()); up->setObjectName(QString("BYTES:") + up->m_url.toString());
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>())); up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
{ "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() },
}));
return up; return up;
} }

View File

@ -26,17 +26,15 @@ class SkinUpload : public Net::NetRequest {
using Ptr = shared_qobject_ptr<SkinUpload>; using Ptr = shared_qobject_ptr<SkinUpload>;
// Note this class takes ownership of the file. // Note this class takes ownership of the file.
SkinUpload(QString token, QString path, QString variant); SkinUpload(QString path, QString variant);
virtual ~SkinUpload() = default; virtual ~SkinUpload() = default;
static SkinUpload::Ptr make(QString token, QString path, QString variant); static SkinUpload::Ptr make(QString token, QString path, QString variant);
void init() override;
protected: protected:
virtual QNetworkReply* getReply(QNetworkRequest&) override; virtual QNetworkReply* getReply(QNetworkRequest&) override;
private: private:
QString m_token;
QString m_path; QString m_path;
QString m_variant; QString m_variant;
}; };

View File

@ -104,7 +104,7 @@ void Flame::FileResolvingTask::netJobFinished()
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash); auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
auto output = std::make_shared<QByteArray>(); auto output = std::make_shared<QByteArray>();
auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output); auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output);
QObject::connect(dl.get(), &Net::ApiDownload::succeeded, [&out]() { out.resolved = true; }); QObject::connect(dl.get(), &Task::succeeded, [&out]() { out.resolved = true; });
m_checkJob->addNetAction(dl); m_checkJob->addNetAction(dl);
blockedProjects.insert(&out, output); blockedProjects.insert(&out, output);
@ -176,7 +176,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId); auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
auto dl = Net::ApiDownload::makeByteArray(url, output); auto dl = Net::ApiDownload::makeByteArray(url, output);
qDebug() << "Fetching url slug for file:" << mod->fileName; qDebug() << "Fetching url slug for file:" << mod->fileName;
QObject::connect(dl.get(), &Net::ApiDownload::succeeded, [block, index, output]() { QObject::connect(dl.get(), &Task::succeeded, [block, index, output]() {
auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done
auto json = QJsonDocument::fromJson(*output); auto json = QJsonDocument::fromJson(*output);
auto base = auto base =

View File

@ -1,4 +1,5 @@
#include "FlameCheckUpdate.h" #include "FlameCheckUpdate.h"
#include "Application.h"
#include "FlameAPI.h" #include "FlameAPI.h"
#include "FlameModIndex.h" #include "FlameModIndex.h"
@ -124,11 +125,6 @@ void FlameCheckUpdate::executeTask()
int i = 0; int i = 0;
for (auto* mod : m_mods) { for (auto* mod : m_mods) {
if (!mod->enabled()) {
emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!"));
continue;
}
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name())); setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name()));
setProgress(i++, m_mods.size()); setProgress(i++, m_mods.size());
@ -177,7 +173,7 @@ void FlameCheckUpdate::executeTask()
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver.value(), m_mods_folder); auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver.value(), m_mods_folder);
m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type, m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type,
api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()), api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()),
ModPlatform::ResourceProvider::FLAME, download_task); ModPlatform::ResourceProvider::FLAME, download_task, mod->enabled());
} }
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver.value())); m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver.value()));
} }

View File

@ -42,11 +42,6 @@ void ModrinthCheckUpdate::executeTask()
auto hashing_task = auto hashing_task =
makeShared<ConcurrentTask>(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); makeShared<ConcurrentTask>(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
for (auto* mod : m_mods) { for (auto* mod : m_mods) {
if (!mod->enabled()) {
emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!"));
continue;
}
auto hash = mod->metadata()->hash; auto hash = mod->metadata()->hash;
// Sadly the API can only handle one hash type per call, se we // Sadly the API can only handle one hash type per call, se we
@ -95,8 +90,7 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> resp
// If the returned project is empty, but we have Modrinth metadata, // If the returned project is empty, but we have Modrinth metadata,
// it means this specific version is not available // it means this specific version is not available
if (project_obj.isEmpty()) { if (project_obj.isEmpty()) {
qDebug() << "Mod " << m_mappings.find(hash).value()->name() << " got an empty response." qDebug() << "Mod " << m_mappings.find(hash).value()->name() << " got an empty response." << "Hash: " << hash;
<< "Hash: " << hash;
continue; continue;
} }
@ -153,7 +147,7 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> resp
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder); auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder);
m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.version_type, m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.version_type,
project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task); project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, mod->enabled());
} }
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver)); m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
} }

View File

@ -18,47 +18,29 @@
*/ */
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
#include "ByteArraySink.h" #include "net/ApiHeaderProxy.h"
#include "ChecksumValidator.h"
#include "MetaCacheSink.h"
namespace Net { namespace Net {
auto ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr Download::Ptr ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Download::Options options)
{ {
auto dl = makeShared<ApiDownload>(); auto dl = Download::makeCached(url, entry, options);
dl->m_url = url; dl->addHeaderProxy(new ApiHeaderProxy());
dl->setObjectName(QString("CACHE:") + url.toString());
dl->m_options = options;
auto md5Node = new ChecksumValidator(QCryptographicHash::Md5);
auto cachedNode = new MetaCacheSink(entry, md5Node, options.testFlag(Option::MakeEternal));
dl->m_sink.reset(cachedNode);
return dl; return dl;
} }
auto ApiDownload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options) -> Download::Ptr Download::Ptr ApiDownload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Download::Options options)
{ {
auto dl = makeShared<ApiDownload>(); auto dl = Download::makeByteArray(url, output, options);
dl->m_url = url; dl->addHeaderProxy(new ApiHeaderProxy());
dl->setObjectName(QString("BYTES:") + url.toString());
dl->m_options = options;
dl->m_sink.reset(new ByteArraySink(output));
return dl; return dl;
} }
auto ApiDownload::makeFile(QUrl url, QString path, Options options) -> Download::Ptr Download::Ptr ApiDownload::makeFile(QUrl url, QString path, Download::Options options)
{ {
auto dl = makeShared<ApiDownload>(); auto dl = Download::makeFile(url, path, options);
dl->m_url = url; dl->addHeaderProxy(new ApiHeaderProxy());
dl->setObjectName(QString("FILE:") + url.toString());
dl->m_options = options;
dl->m_sink.reset(new FileSink(path));
return dl; return dl;
} }
void ApiDownload::init()
{
auto api_headers = new ApiHeaderProxy();
addHeaderProxy(api_headers);
}
} // namespace Net } // namespace Net

View File

@ -19,20 +19,14 @@
#pragma once #pragma once
#include "ApiHeaderProxy.h"
#include "Download.h" #include "Download.h"
namespace Net { namespace Net {
class ApiDownload : public Download { namespace ApiDownload {
public: Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Download::Options options = Download::Option::NoOptions);
virtual ~ApiDownload() = default; Download::Ptr makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Download::Options options = Download::Option::NoOptions);
Download::Ptr makeFile(QUrl url, QString path, Download::Options options = Download::Option::NoOptions);
static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr; }; // namespace ApiDownload
static auto makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options = Option::NoOptions) -> Download::Ptr;
static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
void init() override;
};
} // namespace Net } // namespace Net

View File

@ -18,22 +18,15 @@
*/ */
#include "net/ApiUpload.h" #include "net/ApiUpload.h"
#include "ByteArraySink.h" #include "net/ApiHeaderProxy.h"
namespace Net { namespace Net {
Upload::Ptr ApiUpload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data) Upload::Ptr ApiUpload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data)
{ {
auto up = makeShared<ApiUpload>(); auto up = Upload::makeByteArray(url, output, m_post_data);
up->m_url = std::move(url); up->addHeaderProxy(new ApiHeaderProxy());
up->m_sink.reset(new ByteArraySink(output));
up->m_post_data = std::move(m_post_data);
return up; return up;
} }
void ApiUpload::init()
{
auto api_headers = new ApiHeaderProxy();
addHeaderProxy(api_headers);
}
} // namespace Net } // namespace Net

View File

@ -19,18 +19,12 @@
#pragma once #pragma once
#include "ApiHeaderProxy.h"
#include "Upload.h" #include "Upload.h"
namespace Net { namespace Net {
class ApiUpload : public Upload { namespace ApiUpload {
public: Upload::Ptr makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data);
virtual ~ApiUpload() = default;
static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data);
void init() override;
}; };
} // namespace Net } // namespace Net

View File

@ -62,8 +62,6 @@ void NetRequest::addValidator(Validator* v)
void NetRequest::executeTask() void NetRequest::executeTask()
{ {
init();
setStatus(tr("Requesting %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80))); setStatus(tr("Requesting %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80)));
if (getState() == Task::State::AbortedByUser) { if (getState() == Task::State::AbortedByUser) {

View File

@ -72,8 +72,6 @@ class NetRequest : public Task {
void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; } void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; }
void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr<Net::HeaderProxy>(proxy)); } void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr<Net::HeaderProxy>(proxy)); }
virtual void init() {}
QUrl url() const; QUrl url() const;
void setUrl(QUrl url) { m_url = url; } void setUrl(QUrl url) { m_url = url; }
int replyStatusCode() const; int replyStatusCode() const;

View File

@ -4,6 +4,7 @@
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -27,7 +28,7 @@ namespace Net {
class RawHeaderProxy : public HeaderProxy { class RawHeaderProxy : public HeaderProxy {
public: public:
RawHeaderProxy() : HeaderProxy() {} RawHeaderProxy(QList<HeaderPair> headers = {}) : HeaderProxy(), m_headers(std::move(headers)) {};
virtual ~RawHeaderProxy() = default; virtual ~RawHeaderProxy() = default;
public: public:
@ -36,6 +37,7 @@ class RawHeaderProxy : public HeaderProxy {
void addHeader(const HeaderPair& header) { m_headers.append(header); } void addHeader(const HeaderPair& header) { m_headers.append(header); }
void addHeader(const QByteArray& headerName, const QByteArray& headerValue) { m_headers.append({ headerName, headerValue }); } void addHeader(const QByteArray& headerName, const QByteArray& headerValue) { m_headers.append({ headerName, headerValue }); }
void addHeaders(const QList<HeaderPair>& headers) { m_headers.append(headers); } void addHeaders(const QList<HeaderPair>& headers) { m_headers.append(headers); }
void setHeaders(QList<HeaderPair> headers) { m_headers = headers; };
private: private:
QList<HeaderPair> m_headers; QList<HeaderPair> m_headers;

View File

@ -1,39 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "net/HeaderProxy.h"
namespace Net {
class StaticHeaderProxy : public HeaderProxy {
public:
StaticHeaderProxy(QList<HeaderPair> hdrs = {}) : HeaderProxy(), m_hdrs(hdrs) {};
virtual ~StaticHeaderProxy() = default;
public:
virtual QList<HeaderPair> headers(const QNetworkRequest&) const override { return m_hdrs; };
void setHeaders(QList<HeaderPair> hdrs) { m_hdrs = hdrs; };
private:
QList<HeaderPair> m_hdrs;
};
} // namespace Net

View File

@ -46,7 +46,7 @@
#include <memory> #include <memory>
#include "BuildConfig.h" #include "BuildConfig.h"
#include "net/StaticHeaderProxy.h" #include "net/RawHeaderProxy.h"
Net::NetRequest::Ptr ImgurAlbumCreation::make(std::shared_ptr<ImgurAlbumCreation::Result> output, QList<ScreenShot::Ptr> screenshots) Net::NetRequest::Ptr ImgurAlbumCreation::make(std::shared_ptr<ImgurAlbumCreation::Result> output, QList<ScreenShot::Ptr> screenshots)
{ {
@ -54,6 +54,10 @@ Net::NetRequest::Ptr ImgurAlbumCreation::make(std::shared_ptr<ImgurAlbumCreation
up->m_url = BuildConfig.IMGUR_BASE_URL + "album"; up->m_url = BuildConfig.IMGUR_BASE_URL + "album";
up->m_sink.reset(new Sink(output)); up->m_sink.reset(new Sink(output));
up->m_screenshots = screenshots; up->m_screenshots = screenshots;
up->addHeaderProxy(new Net::RawHeaderProxy(
QList<Net::HeaderPair>{ { "Content-Type", "application/x-www-form-urlencoded" },
{ "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() },
{ "Accept", "application/json" } }));
return up; return up;
} }
@ -67,16 +71,6 @@ QNetworkReply* ImgurAlbumCreation::getReply(QNetworkRequest& request)
return m_network->post(request, data); return m_network->post(request, data);
} }
void ImgurAlbumCreation::init()
{
qDebug() << "Setting up imgur upload";
auto api_headers = new Net::StaticHeaderProxy(
QList<Net::HeaderPair>{ { "Content-Type", "application/x-www-form-urlencoded" },
{ "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() },
{ "Accept", "application/json" } });
addHeaderProxy(api_headers);
}
auto ImgurAlbumCreation::Sink::init(QNetworkRequest& request) -> Task::State auto ImgurAlbumCreation::Sink::init(QNetworkRequest& request) -> Task::State
{ {
m_output.clear(); m_output.clear();

View File

@ -67,8 +67,6 @@ class ImgurAlbumCreation : public Net::NetRequest {
static NetRequest::Ptr make(std::shared_ptr<Result> output, QList<ScreenShot::Ptr> screenshots); static NetRequest::Ptr make(std::shared_ptr<Result> output, QList<ScreenShot::Ptr> screenshots);
QNetworkReply* getReply(QNetworkRequest& request) override; QNetworkReply* getReply(QNetworkRequest& request) override;
void init() override;
private: private:
QList<ScreenShot::Ptr> m_screenshots; QList<ScreenShot::Ptr> m_screenshots;
}; };

View File

@ -36,7 +36,7 @@
#include "ImgurUpload.h" #include "ImgurUpload.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "net/StaticHeaderProxy.h" #include "net/RawHeaderProxy.h"
#include <QDebug> #include <QDebug>
#include <QFile> #include <QFile>
@ -47,14 +47,6 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QUrl> #include <QUrl>
void ImgurUpload::init()
{
qDebug() << "Setting up imgur upload";
auto api_headers = new Net::StaticHeaderProxy(QList<Net::HeaderPair>{
{ "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, { "Accept", "application/json" } });
addHeaderProxy(api_headers);
}
QNetworkReply* ImgurUpload::getReply(QNetworkRequest& request) QNetworkReply* ImgurUpload::getReply(QNetworkRequest& request)
{ {
auto file = new QFile(m_fileInfo.absoluteFilePath(), this); auto file = new QFile(m_fileInfo.absoluteFilePath(), this);
@ -125,5 +117,7 @@ Net::NetRequest::Ptr ImgurUpload::make(ScreenShot::Ptr m_shot)
auto up = makeShared<ImgurUpload>(m_shot->m_file); auto up = makeShared<ImgurUpload>(m_shot->m_file);
up->m_url = std::move(BuildConfig.IMGUR_BASE_URL + "image"); up->m_url = std::move(BuildConfig.IMGUR_BASE_URL + "image");
up->m_sink.reset(new Sink(m_shot)); up->m_sink.reset(new Sink(m_shot));
up->addHeaderProxy(new Net::RawHeaderProxy(QList<Net::HeaderPair>{
{ "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, { "Accept", "application/json" } }));
return up; return up;
} }

View File

@ -62,8 +62,6 @@ class ImgurUpload : public Net::NetRequest {
static NetRequest::Ptr make(ScreenShot::Ptr m_shot); static NetRequest::Ptr make(ScreenShot::Ptr m_shot);
void init() override;
private: private:
virtual QNetworkReply* getReply(QNetworkRequest&) override; virtual QNetworkReply* getReply(QNetworkRequest&) override;
const QFileInfo m_fileInfo; const QFileInfo m_fileInfo;

View File

@ -34,6 +34,7 @@
*/ */
#include "ProfileSetupDialog.h" #include "ProfileSetupDialog.h"
#include "net/RawHeaderProxy.h"
#include "ui_ProfileSetupDialog.h" #include "ui_ProfileSetupDialog.h"
#include <QAction> #include <QAction>
@ -46,7 +47,6 @@
#include <Application.h> #include <Application.h>
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/StaticHeaderProxy.h"
#include "net/Upload.h" #include "net/Upload.h"
ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget* parent) ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget* parent)
@ -160,7 +160,7 @@ void ProfileSetupDialog::checkName(const QString& name)
if (m_check_task) if (m_check_task)
disconnect(m_check_task.get(), nullptr, this, nullptr); disconnect(m_check_task.get(), nullptr, this, nullptr);
m_check_task = Net::Download::makeByteArray(url, m_check_response); m_check_task = Net::Download::makeByteArray(url, m_check_response);
m_check_task->addHeaderProxy(new Net::StaticHeaderProxy(headers)); m_check_task->addHeaderProxy(new Net::RawHeaderProxy(headers));
connect(m_check_task.get(), &Task::finished, this, &ProfileSetupDialog::checkFinished); connect(m_check_task.get(), &Task::finished, this, &ProfileSetupDialog::checkFinished);
@ -204,7 +204,7 @@ void ProfileSetupDialog::setupProfile(const QString& profileName)
m_profile_response.reset(new QByteArray()); m_profile_response.reset(new QByteArray());
m_profile_task = Net::Upload::makeByteArray(url, m_profile_response, payloadTemplate.arg(profileName).toUtf8()); m_profile_task = Net::Upload::makeByteArray(url, m_profile_response, payloadTemplate.arg(profileName).toUtf8());
m_profile_task->addHeaderProxy(new Net::StaticHeaderProxy(headers)); m_profile_task->addHeaderProxy(new Net::RawHeaderProxy(headers));
connect(m_profile_task.get(), &Task::finished, this, &ProfileSetupDialog::setupProfileFinished); connect(m_profile_task.get(), &Task::finished, this, &ProfileSetupDialog::setupProfileFinished);

View File

@ -36,6 +36,8 @@
*/ */
#include "InstanceSettingsPage.h" #include "InstanceSettingsPage.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/WorldList.h"
#include "ui_InstanceSettingsPage.h" #include "ui_InstanceSettingsPage.h"
#include <QDialog> #include <QDialog>
@ -71,6 +73,22 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance* inst, QWidget* parent)
connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeGLFWChanged); connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeGLFWChanged);
connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeOpenALChanged); connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeOpenALChanged);
auto mInst = dynamic_cast<MinecraftInstance*>(inst);
m_world_quickplay_supported = mInst && mInst->traits().contains("feature:is_quick_play_singleplayer");
if (m_world_quickplay_supported) {
auto worlds = mInst->worldList();
worlds->update();
for (const auto& world : worlds->allWorlds()) {
ui->worldsCb->addItem(world.folderName());
}
} else {
ui->worldsCb->hide();
ui->worldJoinButton->hide();
ui->serverJoinAddressButton->setChecked(true);
ui->serverJoinAddress->setEnabled(true);
ui->serverJoinAddressButton->setStyleSheet("QRadioButton::indicator { width: 0px; height: 0px; }");
}
loadSettings(); loadSettings();
updateThresholds(); updateThresholds();
@ -256,9 +274,16 @@ void InstanceSettingsPage::applySettings()
bool joinServerOnLaunch = ui->serverJoinGroupBox->isChecked(); bool joinServerOnLaunch = ui->serverJoinGroupBox->isChecked();
m_settings->set("JoinServerOnLaunch", joinServerOnLaunch); m_settings->set("JoinServerOnLaunch", joinServerOnLaunch);
if (joinServerOnLaunch) { if (joinServerOnLaunch) {
m_settings->set("JoinServerOnLaunchAddress", ui->serverJoinAddress->text()); if (ui->serverJoinAddressButton->isChecked() || !m_world_quickplay_supported) {
m_settings->set("JoinServerOnLaunchAddress", ui->serverJoinAddress->text());
m_settings->reset("JoinWorldOnLaunch");
} else {
m_settings->set("JoinWorldOnLaunch", ui->worldsCb->currentText());
m_settings->reset("JoinServerOnLaunchAddress");
}
} else { } else {
m_settings->reset("JoinServerOnLaunchAddress"); m_settings->reset("JoinServerOnLaunchAddress");
m_settings->reset("JoinWorldOnLaunch");
} }
// Use an account for this instance // Use an account for this instance
@ -379,7 +404,25 @@ void InstanceSettingsPage::loadSettings()
ui->recordGameTime->setChecked(m_settings->get("RecordGameTime").toBool()); ui->recordGameTime->setChecked(m_settings->get("RecordGameTime").toBool());
ui->serverJoinGroupBox->setChecked(m_settings->get("JoinServerOnLaunch").toBool()); ui->serverJoinGroupBox->setChecked(m_settings->get("JoinServerOnLaunch").toBool());
ui->serverJoinAddress->setText(m_settings->get("JoinServerOnLaunchAddress").toString());
if (auto server = m_settings->get("JoinServerOnLaunchAddress").toString(); !server.isEmpty()) {
ui->serverJoinAddress->setText(server);
ui->serverJoinAddressButton->setChecked(true);
ui->worldJoinButton->setChecked(false);
ui->serverJoinAddress->setEnabled(true);
ui->worldsCb->setEnabled(false);
} else if (auto world = m_settings->get("JoinWorldOnLaunch").toString(); !world.isEmpty() && m_world_quickplay_supported) {
ui->worldsCb->setCurrentText(world);
ui->serverJoinAddressButton->setChecked(false);
ui->worldJoinButton->setChecked(true);
ui->serverJoinAddress->setEnabled(false);
ui->worldsCb->setEnabled(true);
} else {
ui->serverJoinAddressButton->setChecked(true);
ui->worldJoinButton->setChecked(false);
ui->serverJoinAddress->setEnabled(true);
ui->worldsCb->setEnabled(false);
}
ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool()); ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool());
updateAccountsMenu(); updateAccountsMenu();
@ -534,3 +577,13 @@ void InstanceSettingsPage::updateThresholds()
ui->labelMaxMemIcon->setPixmap(pix); ui->labelMaxMemIcon->setPixmap(pix);
} }
} }
void InstanceSettingsPage::on_serverJoinAddressButton_toggled(bool checked)
{
ui->serverJoinAddress->setEnabled(checked);
}
void InstanceSettingsPage::on_worldJoinButton_toggled(bool checked)
{
ui->worldsCb->setEnabled(checked);
}

View File

@ -70,6 +70,8 @@ class InstanceSettingsPage : public QWidget, public BasePage {
void on_javaTestBtn_clicked(); void on_javaTestBtn_clicked();
void on_javaBrowseBtn_clicked(); void on_javaBrowseBtn_clicked();
void on_maxMemSpinBox_valueChanged(int i); void on_maxMemSpinBox_valueChanged(int i);
void on_serverJoinAddressButton_toggled(bool checked);
void on_worldJoinButton_toggled(bool checked);
void onUseNativeGLFWChanged(bool checked); void onUseNativeGLFWChanged(bool checked);
void onUseNativeOpenALChanged(bool checked); void onUseNativeOpenALChanged(bool checked);
@ -90,4 +92,5 @@ class InstanceSettingsPage : public QWidget, public BasePage {
BaseInstance* m_instance; BaseInstance* m_instance;
SettingsObjectPtr m_settings; SettingsObjectPtr m_settings;
unique_qobject_ptr<JavaCommon::TestCheck> checker; unique_qobject_ptr<JavaCommon::TestCheck> checker;
bool m_world_quickplay_supported;
}; };

View File

@ -660,7 +660,7 @@
<item> <item>
<widget class="QGroupBox" name="serverJoinGroupBox"> <widget class="QGroupBox" name="serverJoinGroupBox">
<property name="title"> <property name="title">
<string>Set a server to join on launch</string> <string>Set a target to join on launch</string>
</property> </property>
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
@ -668,26 +668,26 @@
<property name="checked"> <property name="checked">
<bool>false</bool> <bool>false</bool>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_11"> <layout class="QGridLayout" name="gridLayout_4">
<item> <item row="0" column="0">
<layout class="QGridLayout" name="serverJoinLayout"> <widget class="QRadioButton" name="serverJoinAddressButton">
<item row="0" column="0"> <property name="text">
<widget class="QLabel" name="serverJoinAddressLabel"> <string>Server address:</string>
<property name="sizePolicy"> </property>
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> </widget>
<horstretch>0</horstretch> </item>
<verstretch>0</verstretch> <item row="0" column="2">
</sizepolicy> <widget class="QLineEdit" name="serverJoinAddress"/>
</property> </item>
<property name="text"> <item row="1" column="0">
<string>Server address:</string> <widget class="QRadioButton" name="worldJoinButton">
</property> <property name="text">
</widget> <string>Singleplayer world</string>
</item> </property>
<item row="0" column="1"> </widget>
<widget class="QLineEdit" name="serverJoinAddress"/> </item>
</item> <item row="1" column="2">
</layout> <widget class="QComboBox" name="worldsCb"/>
</item> </item>
</layout> </layout>
</widget> </widget>

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() = default;
void observe() void observe()
{ {
@ -731,7 +731,7 @@ void ServersPage::on_actionMove_Down_triggered()
void ServersPage::on_actionJoin_triggered() void ServersPage::on_actionJoin_triggered()
{ {
const auto& address = m_model->at(currentServer)->m_address; const auto& address = m_model->at(currentServer)->m_address;
APPLICATION->launch(m_inst, true, false, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address))); APPLICATION->launch(m_inst, true, false, std::make_shared<MinecraftTarget>(MinecraftTarget::parse(address, false)));
} }
#include "ServersPage.moc" #include "ServersPage.moc"

View File

@ -82,7 +82,7 @@ class WorldListProxyModel : public QSortFilterProxyModel {
} }
}; };
WorldListPage::WorldListPage(BaseInstance* inst, std::shared_ptr<WorldList> worlds, QWidget* parent) WorldListPage::WorldListPage(InstancePtr inst, std::shared_ptr<WorldList> worlds, QWidget* parent)
: QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds) : QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds)
{ {
ui->setupUi(this); ui->setupUi(this);
@ -113,6 +113,11 @@ void WorldListPage::openedImpl()
{ {
m_worlds->startWatching(); m_worlds->startWatching();
auto mInst = std::dynamic_pointer_cast<MinecraftInstance>(m_inst);
if (!mInst || !mInst->traits().contains("feature:is_quick_play_singleplayer")) {
ui->toolBar->removeAction(ui->actionJoin);
}
auto const setting_name = QString("WideBarVisibility_%1").arg(id()); auto const setting_name = QString("WideBarVisibility_%1").arg(id());
if (!APPLICATION->settings()->contains(setting_name)) if (!APPLICATION->settings()->contains(setting_name))
m_wide_bar_setting = APPLICATION->settings()->registerSetting(setting_name); m_wide_bar_setting = APPLICATION->settings()->registerSetting(setting_name);
@ -339,6 +344,14 @@ void WorldListPage::worldChanged([[maybe_unused]] const QModelIndex& current, [[
ui->actionDatapacks->setEnabled(enable); ui->actionDatapacks->setEnabled(enable);
bool hasIcon = !index.data(WorldList::IconFileRole).isNull(); bool hasIcon = !index.data(WorldList::IconFileRole).isNull();
ui->actionReset_Icon->setEnabled(enable && hasIcon); ui->actionReset_Icon->setEnabled(enable && hasIcon);
auto mInst = std::dynamic_pointer_cast<MinecraftInstance>(m_inst);
auto supportsJoin = mInst && mInst->traits().contains("feature:is_quick_play_singleplayer");
ui->actionJoin->setEnabled(enable && supportsJoin);
if (!supportsJoin) {
ui->toolBar->removeAction(ui->actionJoin);
}
} }
void WorldListPage::on_actionAdd_triggered() void WorldListPage::on_actionAdd_triggered()
@ -418,4 +431,15 @@ void WorldListPage::on_actionRefresh_triggered()
m_worlds->update(); m_worlds->update();
} }
void WorldListPage::on_actionJoin_triggered()
{
QModelIndex index = getSelectedWorld();
if (!index.isValid()) {
return;
}
auto worldVariant = m_worlds->data(index, WorldList::ObjectRole);
auto world = (World*)worldVariant.value<void*>();
APPLICATION->launch(m_inst, true, false, std::make_shared<MinecraftTarget>(MinecraftTarget::parse(world->folderName(), true)));
}
#include "WorldListPage.moc" #include "WorldListPage.moc"

View File

@ -53,7 +53,7 @@ class WorldListPage : public QMainWindow, public BasePage {
Q_OBJECT Q_OBJECT
public: public:
explicit WorldListPage(BaseInstance* inst, std::shared_ptr<WorldList> worlds, QWidget* parent = 0); explicit WorldListPage(InstancePtr inst, std::shared_ptr<WorldList> worlds, QWidget* parent = 0);
virtual ~WorldListPage(); virtual ~WorldListPage();
virtual QString displayName() const override { return tr("Worlds"); } virtual QString displayName() const override { return tr("Worlds"); }
@ -72,7 +72,7 @@ class WorldListPage : public QMainWindow, public BasePage {
QMenu* createPopupMenu() override; QMenu* createPopupMenu() override;
protected: protected:
BaseInstance* m_inst; InstancePtr m_inst;
private: private:
QModelIndex getSelectedWorld(); QModelIndex getSelectedWorld();
@ -101,6 +101,7 @@ class WorldListPage : public QMainWindow, public BasePage {
void on_actionReset_Icon_triggered(); void on_actionReset_Icon_triggered();
void worldChanged(const QModelIndex& current, const QModelIndex& previous); void worldChanged(const QModelIndex& current, const QModelIndex& previous);
void mceditState(LoggedProcess::State state); void mceditState(LoggedProcess::State state);
void on_actionJoin_triggered();
void ShowContextMenu(const QPoint& pos); void ShowContextMenu(const QPoint& pos);
}; };

View File

@ -81,6 +81,7 @@
</attribute> </attribute>
<addaction name="actionAdd"/> <addaction name="actionAdd"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionJoin"/>
<addaction name="actionRename"/> <addaction name="actionRename"/>
<addaction name="actionCopy"/> <addaction name="actionCopy"/>
<addaction name="actionRemove"/> <addaction name="actionRemove"/>
@ -97,6 +98,11 @@
<string>Add</string> <string>Add</string>
</property> </property>
</action> </action>
<action name="actionJoin">
<property name="text">
<string>Join</string>
</property>
</action>
<action name="actionRename"> <action name="actionRename">
<property name="text"> <property name="text">
<string>Rename</string> <string>Rename</string>

View File

@ -321,14 +321,9 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
updateUi(); updateUi();
} }
void ResourcePage::onVersionSelectionChanged(QString versionData) void ResourcePage::onVersionSelectionChanged(int index)
{ {
if (versionData.isNull() || versionData.isEmpty()) { m_selected_version_index = index;
m_selected_version_index = -1;
return;
}
m_selected_version_index = m_ui->versionSelectionBox->currentData().toInt();
updateSelectionButton(); updateSelectionButton();
} }

View File

@ -89,7 +89,7 @@ class ResourcePage : public QWidget, public BasePage {
virtual void triggerSearch() = 0; virtual void triggerSearch() = 0;
void onSelectionChanged(QModelIndex first, QModelIndex second); void onSelectionChanged(QModelIndex first, QModelIndex second);
void onVersionSelectionChanged(QString data); void onVersionSelectionChanged(int index);
void onResourceSelected(); void onResourceSelected();
// NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12

View File

@ -84,7 +84,7 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent)
connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlamePage::onSelectionChanged); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlamePage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlamePage::onVersionSelectionChanged); connect(ui->versionSelectionBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FlamePage::onVersionSelectionChanged);
ui->packView->setItemDelegate(new ProjectItemDelegate(this)); ui->packView->setItemDelegate(new ProjectItemDelegate(this));
ui->packDescription->setMetaEntry("FlamePacks"); ui->packDescription->setMetaEntry("FlamePacks");
@ -240,17 +240,17 @@ void FlamePage::suggestCurrent()
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); }); [this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
} }
void FlamePage::onVersionSelectionChanged(QString version) void FlamePage::onVersionSelectionChanged(int index)
{ {
bool is_blocked = false; bool is_blocked = false;
ui->versionSelectionBox->currentData().toInt(&is_blocked); ui->versionSelectionBox->currentData().toInt(&is_blocked);
if (version.isNull() || version.isEmpty() || is_blocked) { if (index == -1 || is_blocked) {
m_selected_version_index = -1; m_selected_version_index = -1;
return; return;
} }
m_selected_version_index = ui->versionSelectionBox->currentIndex(); m_selected_version_index = index;
Q_ASSERT(current.versions.at(m_selected_version_index).downloadUrl == ui->versionSelectionBox->currentData().toString()); Q_ASSERT(current.versions.at(m_selected_version_index).downloadUrl == ui->versionSelectionBox->currentData().toString());

View File

@ -78,7 +78,7 @@ class FlamePage : public QWidget, public BasePage {
private slots: private slots:
void triggerSearch(); void triggerSearch();
void onSelectionChanged(QModelIndex first, QModelIndex second); void onSelectionChanged(QModelIndex first, QModelIndex second);
void onVersionSelectionChanged(QString data); void onVersionSelectionChanged(int index);
private: private:
Ui::FlamePage* ui = nullptr; Ui::FlamePage* ui = nullptr;

View File

@ -60,7 +60,7 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) :
// so it's best not to connect them in the parent's contructor... // so it's best not to connect them in the parent's contructor...
connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged); connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged);
connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged); connect(m_ui->versionSelectionBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FlameModPage::onVersionSelectionChanged);
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameModPage::onResourceSelected); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameModPage::onResourceSelected);
m_ui->packDescription->setMetaEntry(metaEntryBase()); m_ui->packDescription->setMetaEntry(metaEntryBase());
@ -94,7 +94,8 @@ FlameResourcePackPage::FlameResourcePackPage(ResourcePackDownloadDialog* dialog,
// so it's best not to connect them in the parent's contructor... // so it's best not to connect them in the parent's contructor...
connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameResourcePackPage::onSelectionChanged); connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameResourcePackPage::onSelectionChanged);
connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameResourcePackPage::onVersionSelectionChanged); connect(m_ui->versionSelectionBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&FlameResourcePackPage::onVersionSelectionChanged);
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameResourcePackPage::onResourceSelected); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameResourcePackPage::onResourceSelected);
m_ui->packDescription->setMetaEntry(metaEntryBase()); m_ui->packDescription->setMetaEntry(metaEntryBase());
@ -128,7 +129,8 @@ FlameTexturePackPage::FlameTexturePackPage(TexturePackDownloadDialog* dialog, Ba
// so it's best not to connect them in the parent's contructor... // so it's best not to connect them in the parent's contructor...
connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameTexturePackPage::onSelectionChanged); connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameTexturePackPage::onSelectionChanged);
connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameTexturePackPage::onVersionSelectionChanged); connect(m_ui->versionSelectionBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&FlameTexturePackPage::onVersionSelectionChanged);
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameTexturePackPage::onResourceSelected); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameTexturePackPage::onResourceSelected);
m_ui->packDescription->setMetaEntry(metaEntryBase()); m_ui->packDescription->setMetaEntry(metaEntryBase());
@ -162,7 +164,8 @@ FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseI
// so it's best not to connect them in the parent's constructor... // so it's best not to connect them in the parent's constructor...
connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameShaderPackPage::onSelectionChanged); connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameShaderPackPage::onSelectionChanged);
connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameShaderPackPage::onVersionSelectionChanged); connect(m_ui->versionSelectionBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&FlameShaderPackPage::onVersionSelectionChanged);
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameShaderPackPage::onResourceSelected); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameShaderPackPage::onResourceSelected);
m_ui->packDescription->setMetaEntry(metaEntryBase()); m_ui->packDescription->setMetaEntry(metaEntryBase());

View File

@ -85,7 +85,7 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent)
connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged); connect(ui->versionSelectionBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ModrinthPage::onVersionSelectionChanged);
ui->packView->setItemDelegate(new ProjectItemDelegate(this)); ui->packView->setItemDelegate(new ProjectItemDelegate(this));
ui->packDescription->setMetaEntry(metaEntryBase()); ui->packDescription->setMetaEntry(metaEntryBase());
@ -342,9 +342,9 @@ void ModrinthPage::triggerSearch()
m_fetch_progress.watch(m_model->activeSearchJob().get()); m_fetch_progress.watch(m_model->activeSearchJob().get());
} }
void ModrinthPage::onVersionSelectionChanged(QString version) void ModrinthPage::onVersionSelectionChanged(int index)
{ {
if (version.isNull() || version.isEmpty()) { if (index == -1) {
selectedVersion = ""; selectedVersion = "";
return; return;
} }

View File

@ -80,7 +80,7 @@ class ModrinthPage : public QWidget, public BasePage {
private slots: private slots:
void onSelectionChanged(QModelIndex first, QModelIndex second); void onSelectionChanged(QModelIndex first, QModelIndex second);
void onVersionSelectionChanged(QString data); void onVersionSelectionChanged(int index);
void triggerSearch(); void triggerSearch();
private: private:

View File

@ -58,7 +58,8 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instan
// so it's best not to connect them in the parent's constructor... // so it's best not to connect them in the parent's constructor...
connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged); connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged);
connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged); connect(m_ui->versionSelectionBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ModrinthModPage::onVersionSelectionChanged);
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onResourceSelected); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onResourceSelected);
m_ui->packDescription->setMetaEntry(metaEntryBase()); m_ui->packDescription->setMetaEntry(metaEntryBase());
@ -76,7 +77,8 @@ ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* d
// so it's best not to connect them in the parent's constructor... // so it's best not to connect them in the parent's constructor...
connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthResourcePackPage::onSelectionChanged); connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthResourcePackPage::onSelectionChanged);
connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthResourcePackPage::onVersionSelectionChanged); connect(m_ui->versionSelectionBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ModrinthResourcePackPage::onVersionSelectionChanged);
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthResourcePackPage::onResourceSelected); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthResourcePackPage::onResourceSelected);
m_ui->packDescription->setMetaEntry(metaEntryBase()); m_ui->packDescription->setMetaEntry(metaEntryBase());
@ -94,7 +96,8 @@ ModrinthTexturePackPage::ModrinthTexturePackPage(TexturePackDownloadDialog* dial
// so it's best not to connect them in the parent's constructor... // so it's best not to connect them in the parent's constructor...
connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthTexturePackPage::onSelectionChanged); connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthTexturePackPage::onSelectionChanged);
connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthTexturePackPage::onVersionSelectionChanged); connect(m_ui->versionSelectionBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ModrinthTexturePackPage::onVersionSelectionChanged);
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthTexturePackPage::onResourceSelected); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthTexturePackPage::onResourceSelected);
m_ui->packDescription->setMetaEntry(metaEntryBase()); m_ui->packDescription->setMetaEntry(metaEntryBase());
@ -112,7 +115,8 @@ ModrinthShaderPackPage::ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog,
// so it's best not to connect them in the parent's constructor... // so it's best not to connect them in the parent's constructor...
connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthShaderPackPage::onSelectionChanged); connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthShaderPackPage::onSelectionChanged);
connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthShaderPackPage::onVersionSelectionChanged); connect(m_ui->versionSelectionBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ModrinthShaderPackPage::onVersionSelectionChanged);
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthShaderPackPage::onResourceSelected); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthShaderPackPage::onResourceSelected);
m_ui->packDescription->setMetaEntry(metaEntryBase()); m_ui->packDescription->setMetaEntry(metaEntryBase());

View File

@ -309,4 +309,15 @@ bool WideBar::checkHash(QByteArray const& old_hash) const
return old_hash == getHash(); return old_hash == getHash();
} }
void WideBar::removeAction(QAction* action)
{
auto iter = getMatching(action);
if (iter == m_entries.end())
return;
iter->bar_action->setVisible(false);
removeAction(iter->bar_action);
m_entries.erase(iter);
}
#include "WideBar.moc" #include "WideBar.moc"

View File

@ -38,6 +38,8 @@ class WideBar : public QToolBar {
[[nodiscard]] QByteArray getVisibilityState() const; [[nodiscard]] QByteArray getVisibilityState() const;
void setVisibilityState(QByteArray&&); void setVisibilityState(QByteArray&&);
void removeAction(QAction* action);
private: private:
struct BarEntry { struct BarEntry {
enum class Type { None, Action, Separator, Spacer } type = Type::None; enum class Type { None, Action, Separator, Spacer } type = Type::None;

View File

@ -70,7 +70,7 @@ public abstract class AbstractLauncher implements Launcher {
// secondary parameters // secondary parameters
protected final int width, height; protected final int width, height;
protected final boolean maximize; protected final boolean maximize;
protected final String serverAddress, serverPort; protected final String serverAddress, serverPort, worldName;
protected final String mainClassName; protected final String mainClassName;
@ -80,6 +80,7 @@ public abstract class AbstractLauncher implements Launcher {
serverAddress = params.getString("serverAddress", null); serverAddress = params.getString("serverAddress", null);
serverPort = params.getString("serverPort", null); serverPort = params.getString("serverPort", null);
worldName = params.getString("worldName", null);
String windowParams = params.getString("windowParams", null); String windowParams = params.getString("windowParams", null);

View File

@ -62,13 +62,15 @@ import java.util.Collections;
import java.util.List; import java.util.List;
public final class StandardLauncher extends AbstractLauncher { public final class StandardLauncher extends AbstractLauncher {
private final boolean quickPlaySupported; private final boolean quickPlayMultiplayerSupported;
private final boolean quickPlaySingleplayerSupported;
public StandardLauncher(Parameters params) { public StandardLauncher(Parameters params) {
super(params); super(params);
List<String> traits = params.getList("traits", Collections.<String>emptyList()); List<String> traits = params.getList("traits", Collections.<String>emptyList());
quickPlaySupported = traits.contains("feature:is_quick_play_multiplayer"); quickPlayMultiplayerSupported = traits.contains("feature:is_quick_play_multiplayer");
quickPlaySingleplayerSupported = traits.contains("feature:is_quick_play_singleplayer");
} }
@Override @Override
@ -83,7 +85,7 @@ public final class StandardLauncher extends AbstractLauncher {
} }
if (serverAddress != null) { if (serverAddress != null) {
if (quickPlaySupported) { if (quickPlayMultiplayerSupported) {
// as of 23w14a // as of 23w14a
gameArgs.add("--quickPlayMultiplayer"); gameArgs.add("--quickPlayMultiplayer");
gameArgs.add(serverAddress + ':' + serverPort); gameArgs.add(serverAddress + ':' + serverPort);
@ -93,6 +95,9 @@ public final class StandardLauncher extends AbstractLauncher {
gameArgs.add("--port"); gameArgs.add("--port");
gameArgs.add(serverPort); gameArgs.add(serverPort);
} }
} else if (worldName != null && quickPlaySingleplayerSupported) {
gameArgs.add("--quickPlaySingleplayer");
gameArgs.add(worldName);
} }
// find and invoke the main method // find and invoke the main method

View File

@ -24,9 +24,8 @@
overlays.default = final: prev: let overlays.default = final: prev: let
version = builtins.substring 0 8 self.lastModifiedDate or "dirty"; version = builtins.substring 0 8 self.lastModifiedDate or "dirty";
in { in {
prismlauncher-unwrapped = prev.qt6Packages.callPackage ./pkg { prismlauncher-unwrapped = prev.callPackage ./pkg {
inherit (inputs) libnbtplusplus; inherit (inputs) libnbtplusplus;
inherit ((final.darwin or prev.darwin).apple_sdk.frameworks) Cocoa;
inherit version; inherit version;
}; };

View File

@ -1,27 +1,27 @@
{ {
lib, lib,
stdenv, stdenv,
canonicalize-jars-hook,
cmake, cmake,
cmark, cmark,
Cocoa, darwin,
ninja,
jdk17,
zlib,
qtbase,
qtnetworkauth,
quazip,
extra-cmake-modules, extra-cmake-modules,
tomlplusplus,
ghc_filesystem,
gamemode, gamemode,
ghc_filesystem,
jdk17,
kdePackages,
ninja,
stripJavaArchivesHook,
tomlplusplus,
zlib,
msaClientID ? null, msaClientID ? null,
gamemodeSupport ? stdenv.isLinux, gamemodeSupport ? stdenv.isLinux,
version, version,
libnbtplusplus, libnbtplusplus,
}: }:
assert lib.assertMsg (stdenv.isLinux || !gamemodeSupport) "gamemodeSupport is only available on Linux"; assert lib.assertMsg (
stdenv.mkDerivation rec { gamemodeSupport -> stdenv.isLinux
) "gamemodeSupport is only available on Linux.";
stdenv.mkDerivation {
pname = "prismlauncher-unwrapped"; pname = "prismlauncher-unwrapped";
inherit version; inherit version;
@ -39,49 +39,68 @@ assert lib.assertMsg (stdenv.isLinux || !gamemodeSupport) "gamemodeSupport is on
]); ]);
}; };
nativeBuildInputs = [extra-cmake-modules cmake jdk17 ninja canonicalize-jars-hook];
buildInputs =
[
qtbase
qtnetworkauth
zlib
quazip
ghc_filesystem
tomlplusplus
cmark
]
++ lib.optional gamemodeSupport gamemode
++ lib.optionals stdenv.isDarwin [Cocoa];
hardeningEnable = lib.optionals stdenv.isLinux ["pie"];
cmakeFlags =
[
"-DLauncher_BUILD_PLATFORM=nixpkgs"
]
++ lib.optionals (msaClientID != null) ["-DLauncher_MSA_CLIENT_ID=${msaClientID}"]
++ lib.optionals (lib.versionOlder qtbase.version "6") ["-DLauncher_QT_VERSION_MAJOR=5"]
++ lib.optionals stdenv.isDarwin ["-DINSTALL_BUNDLE=nodeps" "-DMACOSX_SPARKLE_UPDATE_FEED_URL=''"];
postUnpack = '' postUnpack = ''
rm -rf source/libraries/libnbtplusplus rm -rf source/libraries/libnbtplusplus
ln -s ${libnbtplusplus} source/libraries/libnbtplusplus ln -s ${libnbtplusplus} source/libraries/libnbtplusplus
''; '';
nativeBuildInputs = [
cmake
ninja
extra-cmake-modules
jdk17
stripJavaArchivesHook
];
buildInputs =
[
cmark
ghc_filesystem
kdePackages.qtbase
kdePackages.qtnetworkauth
kdePackages.quazip
tomlplusplus
zlib
]
++ lib.optionals stdenv.isDarwin [darwin.apple_sdk.frameworks.Cocoa]
++ lib.optional gamemodeSupport gamemode;
hardeningEnable = lib.optionals stdenv.isLinux ["pie"];
cmakeFlags =
[
(lib.cmakeFeature "Launcher_BUILD_PLATFORM" "nixpkgs")
]
++ lib.optionals (msaClientID != null) [
(lib.cmakeFeature "Launcher_MSA_CLIENT_ID" (toString msaClientID))
]
++ lib.optionals (lib.versionOlder kdePackages.qtbase.version "6") [
(lib.cmakeFeature "Launcher_QT_VERSION_MAJOR" "5")
]
++ lib.optionals stdenv.isDarwin [
# we wrap our binary manually
(lib.cmakeFeature "INSTALL_BUNDLE" "nodeps")
# disable built-in updater
(lib.cmakeFeature "MACOSX_SPARKLE_UPDATE_FEED_URL" "''")
(lib.cmakeFeature "CMAKE_INSTALL_PREFIX" "${placeholder "out"}/Applications/")
];
dontWrapQtApps = true; dontWrapQtApps = true;
meta = with lib; { meta = {
mainProgram = "prismlauncher"; description = "Free, open source launcher for Minecraft";
homepage = "https://prismlauncher.org/";
description = "A free, open source launcher for Minecraft";
longDescription = '' longDescription = ''
Allows you to have multiple, separate instances of Minecraft (each with Allows you to have multiple, separate instances of Minecraft (each with
their own mods, texture packs, saves, etc) and helps you manage them and their own mods, texture packs, saves, etc) and helps you manage them and
their associated options with a simple interface. their associated options with a simple interface.
''; '';
platforms = with platforms; linux ++ darwin; homepage = "https://prismlauncher.org/";
changelog = "https://github.com/PrismLauncher/PrismLauncher/releases/tag/${version}"; license = lib.licenses.gpl3Only;
license = licenses.gpl3Only; maintainers = with lib.maintainers; [
maintainers = with maintainers; [minion3665 Scrumplex getchoo]; Scrumplex
getchoo
];
mainProgram = "prismlauncher";
platforms = lib.platforms.linux ++ lib.platforms.darwin;
}; };
} }

View File

@ -3,94 +3,143 @@
stdenv, stdenv,
symlinkJoin, symlinkJoin,
prismlauncher-unwrapped, prismlauncher-unwrapped,
wrapQtAppsHook,
addOpenGLRunpath, addOpenGLRunpath,
qtbase, # needed for wrapQtAppsHook flite,
qtsvg, gamemode,
qtwayland,
xorg,
libpulseaudio,
libGL,
glfw, glfw,
openal, glfw-wayland-minecraft,
glxinfo,
jdk8, jdk8,
jdk17, jdk17,
jdk21, jdk21,
gamemode, kdePackages,
flite, libGL,
glxinfo, libpulseaudio,
udev,
libusb1, libusb1,
msaClientID ? null, makeWrapper,
gamemodeSupport ? stdenv.isLinux, openal,
textToSpeechSupport ? stdenv.isLinux, pciutils,
controllerSupport ? stdenv.isLinux, udev,
jdks ? [jdk21 jdk17 jdk8], vulkan-loader,
xorg,
additionalLibs ? [], additionalLibs ? [],
additionalPrograms ? [], additionalPrograms ? [],
}: let controllerSupport ? stdenv.isLinux,
prismlauncherFinal = prismlauncher-unwrapped.override { gamemodeSupport ? stdenv.isLinux,
inherit msaClientID gamemodeSupport; jdks ? [
}; jdk21
jdk17
jdk8
],
msaClientID ? null,
textToSpeechSupport ? stdenv.isLinux,
# Adds `glfw-wayland-minecraft` to `LD_LIBRARY_PATH`
# when launched on wayland, allowing for the game to be run natively.
# Make sure to enable "Use system installation of GLFW" in instance settings
# for this to take effect
#
# Warning: This build of glfw may be unstable, and the launcher
# itself can take slightly longer to start
withWaylandGLFW ? false,
}:
assert lib.assertMsg (
controllerSupport -> stdenv.isLinux
) "controllerSupport only has an effect on Linux.";
assert lib.assertMsg (
textToSpeechSupport -> stdenv.isLinux
) "textToSpeechSupport only has an effect on Linux.";
assert lib.assertMsg (
withWaylandGLFW -> stdenv.isLinux
) "withWaylandGLFW is only available on Linux."; let
prismlauncher' = prismlauncher-unwrapped.override {inherit msaClientID gamemodeSupport;};
in in
symlinkJoin { symlinkJoin {
name = "prismlauncher-${prismlauncherFinal.version}"; name = "prismlauncher-${prismlauncher'.version}";
paths = [prismlauncherFinal]; paths = [prismlauncher'];
nativeBuildInputs = [ nativeBuildInputs =
wrapQtAppsHook [kdePackages.wrapQtAppsHook]
]; # purposefully using a shell wrapper here for variable expansion
# see https://github.com/NixOS/nixpkgs/issues/172583
++ lib.optional withWaylandGLFW makeWrapper;
buildInputs = buildInputs =
[ [
qtbase kdePackages.qtbase
qtsvg kdePackages.qtsvg
] ]
++ lib.optional (lib.versionAtLeast qtbase.version "6" && stdenv.isLinux) qtwayland; ++ lib.optional (
lib.versionAtLeast kdePackages.qtbase.version "6" && stdenv.isLinux
)
kdePackages.qtwayland;
postBuild = '' env = {
wrapQtAppsHook waylandPreExec = lib.optionalString withWaylandGLFW ''
''; if [ -n "$WAYLAND_DISPLAY" ]; then
export LD_LIBRARY_PATH=${lib.getLib glfw-wayland-minecraft}/lib:"$LD_LIBRARY_PATH"
fi
'';
};
postBuild =
lib.optionalString withWaylandGLFW ''
qtWrapperArgs+=(--run "$waylandPreExec")
''
+ ''
wrapQtAppsHook
'';
qtWrapperArgs = let qtWrapperArgs = let
runtimeLibs = runtimeLibs =
(with xorg; [ [
libX11
libXext
libXcursor
libXrandr
libXxf86vm
])
++ [
# lwjgl # lwjgl
glfw
libpulseaudio libpulseaudio
libGL libGL
glfw
openal openal
stdenv.cc.cc.lib stdenv.cc.cc.lib
# oshi vulkan-loader # VulkanMod's lwjgl
udev
udev # oshi
xorg.libX11
xorg.libXext
xorg.libXcursor
xorg.libXrandr
xorg.libXxf86vm
] ]
++ lib.optional gamemodeSupport gamemode.lib
++ lib.optional textToSpeechSupport flite ++ lib.optional textToSpeechSupport flite
++ lib.optional gamemodeSupport gamemode.lib
++ lib.optional controllerSupport libusb1 ++ lib.optional controllerSupport libusb1
++ additionalLibs; ++ additionalLibs;
runtimePrograms = runtimePrograms =
[ [
xorg.xrandr
glxinfo glxinfo
pciutils # need lspci
xorg.xrandr # needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128
] ]
++ additionalPrograms; ++ additionalPrograms;
in in
["--prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks}"] ["--prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks}"]
++ lib.optionals stdenv.isLinux [ ++ lib.optionals stdenv.isLinux [
"--set LD_LIBRARY_PATH ${addOpenGLRunpath.driverLink}/lib:${lib.makeLibraryPath runtimeLibs}" "--set LD_LIBRARY_PATH ${addOpenGLRunpath.driverLink}/lib:${lib.makeLibraryPath runtimeLibs}"
# xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128
"--prefix PATH : ${lib.makeBinPath runtimePrograms}" "--prefix PATH : ${lib.makeBinPath runtimePrograms}"
]; ];
inherit (prismlauncherFinal) meta; meta = {
inherit
(prismlauncher'.meta)
description
longDescription
homepage
changelog
license
maintainers
mainProgram
platforms
;
};
} }