diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8212af233..a521a6e45 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,7 +79,7 @@ jobs: qt_ver: 6 qt_host: windows qt_arch: "" - qt_version: "6.7.1" + qt_version: "6.7.2" qt_modules: "qt5compat qtimageformats qtnetworkauth" - os: windows-2022 @@ -90,7 +90,7 @@ jobs: qt_ver: 6 qt_host: windows qt_arch: "win64_msvc2019_arm64" - qt_version: "6.7.1" + qt_version: "6.7.2" qt_modules: "qt5compat qtimageformats qtnetworkauth" - os: macos-12 @@ -99,7 +99,7 @@ jobs: qt_ver: 6 qt_host: mac qt_arch: "" - qt_version: "6.7.1" + qt_version: "6.7.2" qt_modules: "qt5compat qtimageformats qtnetworkauth" - os: macos-12 @@ -160,7 +160,7 @@ jobs: - name: Setup ccache if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' - uses: hendrikmuhs/ccache-action@v1.2.13 + uses: hendrikmuhs/ccache-action@v1.2.14 with: key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 2226d0710..e1ab2e86e 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b # v27 - - uses: DeterminateSystems/update-flake-lock@v22 + - uses: DeterminateSystems/update-flake-lock@v23 with: commit-msg: "chore(nix): update lockfile" pr-title: "chore(nix): update lockfile" diff --git a/BUILD.md b/BUILD.md deleted file mode 100644 index a139039df..000000000 --- a/BUILD.md +++ /dev/null @@ -1,3 +0,0 @@ -# Build Instructions - -Full build instructions are available on [the website](https://prismlauncher.org/wiki/development/build-instructions/). diff --git a/README.md b/README.md index b32132d49..9c4909509 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,12 @@ The translation effort for Prism Launcher is hosted on [Weblate](https://hosted. ## 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 diff --git a/flake.lock b/flake.lock index 38b0d7430..1708a1e0a 100644 --- a/flake.lock +++ b/flake.lock @@ -23,11 +23,11 @@ ] }, "locked": { - "lastModified": 1719994518, - "narHash": "sha256-pQMhCCHyQGRzdfAkdJ4cIWiw+JNuWsTX7f0ZYSyz0VY=", + "lastModified": 1722555600, + "narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "9227223f6d922fee3c7b190b2cc238a99527bbb7", + "rev": "8471fe90ad337a8074e957b69ca4d0089218391d", "type": "github" }, "original": { @@ -75,11 +75,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1720768451, - "narHash": "sha256-EYekUHJE2gxeo2pM/zM9Wlqw1Uw2XTJXOSAO79ksc4Y=", + "lastModified": 1723175592, + "narHash": "sha256-M0xJ3FbDUc4fRZ84dPGx5VvgFsOzds77KiBMW/mMTnI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "7e7c39ea35c5cdd002cd4588b03a3fb9ece6fad9", + "rev": "5e0ca22929f3342b19569b21b2f3462f053e497b", "type": "github" }, "original": { @@ -103,11 +103,11 @@ ] }, "locked": { - "lastModified": 1720524665, - "narHash": "sha256-ni/87oHPZm6Gv0ECYxr1f6uxB0UKBWJ6HvS7lwLU6oY=", + "lastModified": 1723202784, + "narHash": "sha256-qbhjc/NEGaDbyy0ucycubq4N3//gDFFH3DOmp1D3u1Q=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "8d6a17d0cdf411c55f12602624df6368ad86fac1", + "rev": "c7012d0c18567c889b948781bc74a501e92275d1", "type": "github" }, "original": { diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 15ea91f72..b07e802cc 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -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" }, { { "l", "launch" }, "Launch the specified instance (by instance ID)", "instance" }, { { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" }, + { { "w", "world" }, "Join the specified world on launch (only valid in combination with --launch)", "world" }, { { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" }, { "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" }, { { "I", "import" }, "Import instance or resource from specified local path or URL", "url" }, @@ -250,6 +251,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_instanceIdToLaunch = parser.value("launch"); m_serverToJoin = parser.value("server"); + m_worldToJoin = parser.value("world"); m_profileToUse = parser.value("profile"); m_liveCheck = parser.isSet("alive"); @@ -265,7 +267,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } // error if --launch is missing with --server or --profile - if ((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) { + if (((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty()) || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) { std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl; m_status = Application::Failed; return; @@ -385,6 +387,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) if (!m_serverToJoin.isEmpty()) { launch.args["server"] = m_serverToJoin; + } else if (!m_worldToJoin.isEmpty()) { + launch.args["world"] = m_worldToJoin; } if (!m_profileToUse.isEmpty()) { launch.args["profile"] = m_profileToUse; @@ -523,6 +527,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } if (!m_serverToJoin.isEmpty()) { qDebug() << "Address of server to join :" << m_serverToJoin; + } else if (!m_worldToJoin.isEmpty()) { + qDebug() << "Name of the world to join :" << m_worldToJoin; } qDebug() << "<> Paths set."; } @@ -1157,14 +1163,17 @@ void Application::performMainStartupAction() if (!m_instanceIdToLaunch.isEmpty()) { auto inst = instances()->getInstanceById(m_instanceIdToLaunch); if (inst) { - MinecraftServerTargetPtr serverToJoin = nullptr; + MinecraftTarget::Ptr targetToJoin = nullptr; MinecraftAccountPtr accountToUse = nullptr; qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching"; if (!m_serverToJoin.isEmpty()) { // FIXME: validate the server string - serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(m_serverToJoin))); + targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(m_serverToJoin, false))); qDebug() << " Launching with server" << m_serverToJoin; + } else if (!m_worldToJoin.isEmpty()) { + targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(m_worldToJoin, true))); + qDebug() << " Launching with world" << m_worldToJoin; } if (!m_profileToUse.isEmpty()) { @@ -1175,7 +1184,7 @@ void Application::performMainStartupAction() qDebug() << " Launching with account" << m_profileToUse; } - launch(inst, true, false, serverToJoin, accountToUse); + launch(inst, true, false, targetToJoin, accountToUse); return; } } @@ -1265,6 +1274,7 @@ void Application::messageReceived(const QByteArray& message) } else if (command == "launch") { QString id = received.args["id"]; QString server = received.args["server"]; + QString world = received.args["world"]; QString profile = received.args["profile"]; InstancePtr instance; @@ -1279,11 +1289,12 @@ void Application::messageReceived(const QByteArray& message) return; } - MinecraftServerTargetPtr serverObject = nullptr; + MinecraftTarget::Ptr serverObject = nullptr; if (!server.isEmpty()) { - serverObject = std::make_shared(MinecraftServerTarget::parse(server)); + serverObject = std::make_shared(MinecraftTarget::parse(server, false)); + } else if (!world.isEmpty()) { + serverObject = std::make_shared(MinecraftTarget::parse(world, true)); } - MinecraftAccountPtr accountObject; if (!profile.isEmpty()) { accountObject = accounts()->getAccountByProfileName(profile); @@ -1332,11 +1343,7 @@ bool Application::openJsonEditor(const QString& filename) } } -bool Application::launch(InstancePtr instance, - bool online, - bool demo, - MinecraftServerTargetPtr serverToJoin, - MinecraftAccountPtr accountToUse) +bool Application::launch(InstancePtr instance, bool online, bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse) { if (m_updateRunning) { qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed."; @@ -1354,7 +1361,7 @@ bool Application::launch(InstancePtr instance, controller->setOnline(online); controller->setDemo(demo); controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get()); - controller->setServerToJoin(serverToJoin); + controller->setTargetToJoin(targetToJoin); controller->setAccountToUse(accountToUse); if (window) { controller->setParentWidget(window); diff --git a/launcher/Application.h b/launcher/Application.h index 8303c7475..3bb20125e 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -47,7 +47,7 @@ #include -#include "minecraft/launch/MinecraftServerTarget.h" +#include "minecraft/launch/MinecraftTarget.h" class LaunchController; class LocalPeer; @@ -202,7 +202,7 @@ class Application : public QApplication { bool launch(InstancePtr instance, bool online = true, bool demo = false, - MinecraftServerTargetPtr serverToJoin = nullptr, + MinecraftTarget::Ptr targetToJoin = nullptr, MinecraftAccountPtr accountToUse = nullptr); bool kill(InstancePtr instance); void closeCurrentWindow(); @@ -290,6 +290,7 @@ class Application : public QApplication { QString m_detectedOpenALPath; QString m_instanceIdToLaunch; QString m_serverToJoin; + QString m_worldToJoin; QString m_profileToUse; bool m_liveCheck = false; QList m_urlsToImport; diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 499ec7866..8c80331bc 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -56,7 +56,7 @@ #include "net/Mode.h" #include "RuntimeContext.h" -#include "minecraft/launch/MinecraftServerTarget.h" +#include "minecraft/launch/MinecraftTarget.h" class QDir; class Task; @@ -184,7 +184,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) = 0; + virtual shared_qobject_ptr createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) = 0; /// returns the current launch task (if any) shared_qobject_ptr getLaunchTask(); @@ -256,7 +256,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this getPages() override { QList values; @@ -39,7 +39,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider { values.append(new TexturePackPage(onesix.get(), onesix->texturePackList())); values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList())); values.append(new NotesPage(onesix.get())); - values.append(new WorldListPage(onesix.get(), onesix->worldList())); + values.append(new WorldListPage(onesix, onesix->worldList())); values.append(new ServersPage(onesix)); // values.append(new GameOptionsPage(onesix.get())); values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots"))); diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 3866a7672..73800574f 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -324,7 +324,7 @@ void LaunchController::launchInstance() return; } - m_launcher = m_instance->createLaunchTask(m_session, m_serverToJoin); + m_launcher = m_instance->createLaunchTask(m_session, m_targetToJoin); if (!m_launcher) { emitFailed(tr("Couldn't instantiate a launcher.")); return; diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h index bc688f2ba..6e2a94258 100644 --- a/launcher/LaunchController.h +++ b/launcher/LaunchController.h @@ -39,7 +39,7 @@ #include #include "minecraft/auth/MinecraftAccount.h" -#include "minecraft/launch/MinecraftServerTarget.h" +#include "minecraft/launch/MinecraftTarget.h" class InstanceWindow; class LaunchController : public Task { @@ -48,7 +48,7 @@ class LaunchController : public Task { void executeTask() override; LaunchController(QObject* parent = nullptr); - virtual ~LaunchController() {}; + virtual ~LaunchController() = default; void setInstance(InstancePtr instance) { m_instance = instance; } @@ -62,7 +62,7 @@ class LaunchController : public Task { void setParentWidget(QWidget* widget) { m_parentWidget = widget; } - void setServerToJoin(MinecraftServerTargetPtr serverToJoin) { m_serverToJoin = std::move(serverToJoin); } + void setTargetToJoin(MinecraftTarget::Ptr targetToJoin) { m_targetToJoin = std::move(targetToJoin); } void setAccountToUse(MinecraftAccountPtr accountToUse) { m_accountToUse = std::move(accountToUse); } @@ -94,5 +94,5 @@ class LaunchController : public Task { MinecraftAccountPtr m_accountToUse = nullptr; AuthSessionPtr m_session; shared_qobject_ptr m_launcher; - MinecraftServerTargetPtr m_serverToJoin; + MinecraftTarget::Ptr m_targetToJoin; }; diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 00658b42b..cb9ee9940 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -344,6 +344,17 @@ std::optional extractSubDir(QuaZip* zip, const QString& subdir, con 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; } while (zip->goToNextFile()); @@ -560,7 +571,7 @@ auto ExtractZipTask::extractZip() -> ZipResult if (!file_name.startsWith(m_subdirectory)) 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; 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)); } } + } 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; diff --git a/launcher/MangoHud.cpp b/launcher/MangoHud.cpp index ab79f418b..ba16ddc4a 100644 --- a/launcher/MangoHud.cpp +++ b/launcher/MangoHud.cpp @@ -40,8 +40,8 @@ namespace MangoHud { QString getLibraryString() { - /* - * Check for vulkan layers in this order: + /** + * Guess MangoHud install location by searching for vulkan layers in this order: * * $VK_LAYER_PATH * $XDG_DATA_DIRS (/usr/local/share/:/usr/share/) @@ -49,8 +49,9 @@ QString getLibraryString() * /etc * $XDG_CONFIG_DIRS (/etc/xdg) * $XDG_CONFIG_HOME (~/.config) + * + * @returns Absolute path of libMangoHud.so if found and empty QString otherwise. */ - QStringList vkLayerList; { QString home = QDir::homePath(); @@ -85,7 +86,7 @@ QString getLibraryString() vkLayerList << FS::PathCombine(xdgConfigHome, "vulkan", "implicit_layer.d"); } - for (QString vkLayer : vkLayerList) { + for (const QString& vkLayer : vkLayerList) { // prefer to use architecture specific vulkan layers QString currentArch = QSysInfo::currentCpuArchitecture(); @@ -95,8 +96,8 @@ QString getLibraryString() QStringList manifestNames = { QString("MangoHud.%1.json").arg(currentArch), "MangoHud.json" }; - QString filePath = ""; - for (QString manifestName : manifestNames) { + QString filePath{}; + for (const QString& manifestName : manifestNames) { QString tryPath = FS::PathCombine(vkLayer, manifestName); if (QFile::exists(tryPath)) { filePath = tryPath; @@ -111,10 +112,23 @@ QString getLibraryString() auto conf = Json::requireDocument(filePath, vkLayer); auto confObject = Json::requireObject(conf, vkLayer); auto layer = Json::ensureObject(confObject, "layer"); - return Json::ensureString(layer, "library_path"); + QString libraryName = Json::ensureString(layer, "library_path"); + +#ifdef __GLIBC__ + // Check whether mangohud is usable on a glibc based system + if (!libraryName.isEmpty()) { + QString libraryPath = findLibrary(libraryName); + if (!libraryPath.isEmpty()) { + return libraryPath; + } + } +#else + // Without glibc return recorded shared library as-is. + return libraryName; +#endif } - return QString(); + return {}; } QString findLibrary(QString libName) diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h index b6a9478a8..3ee38e76c 100644 --- a/launcher/NullInstance.h +++ b/launcher/NullInstance.h @@ -46,13 +46,13 @@ class NullInstance : public BaseInstance { { setVersionBroken(true); } - virtual ~NullInstance() {}; + virtual ~NullInstance() = default; void saveNow() override {} void loadSpecificSettings() override { setSpecificSettingsLoaded(true); } QString getStatusbarDescription() override { return tr("Unknown instance type"); }; QSet traits() const override { return {}; }; QString instanceConfigFolder() const override { return instanceRoot(); }; - shared_qobject_ptr createLaunchTask(AuthSessionPtr, MinecraftServerTargetPtr) override { return nullptr; } + shared_qobject_ptr createLaunchTask(AuthSessionPtr, MinecraftTarget::Ptr) override { return nullptr; } shared_qobject_ptr createUpdateTask([[maybe_unused]] Net::Mode mode) override { return nullptr; } QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); } QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); } @@ -64,7 +64,7 @@ class NullInstance : public BaseInstance { bool canEdit() const override { return false; } bool canLaunch() const override { return false; } void populateLaunchMenu(QMenu* menu) override {} - QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override + QStringList verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) override { QStringList out; out << "Null instance - placeholder."; diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 34d893ff2..350ccc30d 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -182,56 +182,58 @@ QList JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString else if (keyType == KEY_WOW64_32KEY) archType = "32"; - HKEY jreKey; - if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyName.toStdWString().c_str(), 0, KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == - ERROR_SUCCESS) { - // Read the current type version from the registry. - // This will be used to find any key that contains the JavaHome value. + for (HKEY baseRegistry : { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE }) { + HKEY jreKey; + if (RegOpenKeyExW(baseRegistry, keyName.toStdWString().c_str(), 0, KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == + ERROR_SUCCESS) { + // Read the current type version from the registry. + // This will be used to find any key that contains the JavaHome value. - WCHAR subKeyName[255]; - DWORD subKeyNameSize, numSubKeys, retCode; + WCHAR subKeyName[255]; + DWORD subKeyNameSize, numSubKeys, retCode; - // Get the number of subkeys - RegQueryInfoKeyW(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + // Get the number of subkeys + RegQueryInfoKeyW(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL); - // Iterate until RegEnumKeyEx fails - if (numSubKeys > 0) { - for (DWORD i = 0; i < numSubKeys; i++) { - subKeyNameSize = 255; - retCode = RegEnumKeyExW(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL); - QString newSubkeyName = QString::fromWCharArray(subKeyName); - if (retCode == ERROR_SUCCESS) { - // Now open the registry key for the version that we just got. - QString newKeyName = keyName + "\\" + newSubkeyName + subkeySuffix; + // Iterate until RegEnumKeyEx fails + if (numSubKeys > 0) { + for (DWORD i = 0; i < numSubKeys; i++) { + subKeyNameSize = 255; + retCode = RegEnumKeyExW(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL); + QString newSubkeyName = QString::fromWCharArray(subKeyName); + if (retCode == ERROR_SUCCESS) { + // Now open the registry key for the version that we just got. + QString newKeyName = keyName + "\\" + newSubkeyName + subkeySuffix; - HKEY newKey; - if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, newKeyName.toStdWString().c_str(), 0, KEY_READ | keyType, &newKey) == - ERROR_SUCCESS) { - // Read the JavaHome value to find where Java is installed. - DWORD valueSz = 0; - if (RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, NULL, &valueSz) == ERROR_SUCCESS) { - WCHAR* value = new WCHAR[valueSz]; - RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE*)value, &valueSz); + HKEY newKey; + if (RegOpenKeyExW(baseRegistry, newKeyName.toStdWString().c_str(), 0, KEY_READ | keyType, &newKey) == + ERROR_SUCCESS) { + // Read the JavaHome value to find where Java is installed. + DWORD valueSz = 0; + if (RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, NULL, &valueSz) == ERROR_SUCCESS) { + WCHAR* value = new WCHAR[valueSz]; + RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE*)value, &valueSz); - QString newValue = QString::fromWCharArray(value); - delete[] value; + QString newValue = QString::fromWCharArray(value); + delete[] value; - // Now, we construct the version object and add it to the list. - JavaInstallPtr javaVersion(new JavaInstall()); + // Now, we construct the version object and add it to the list. + JavaInstallPtr javaVersion(new JavaInstall()); - javaVersion->id = newSubkeyName; - javaVersion->arch = archType; - javaVersion->path = QDir(FS::PathCombine(newValue, "bin")).absoluteFilePath("javaw.exe"); - javas.append(javaVersion); + javaVersion->id = newSubkeyName; + javaVersion->arch = archType; + javaVersion->path = QDir(FS::PathCombine(newValue, "bin")).absoluteFilePath("javaw.exe"); + javas.append(javaVersion); + } + + RegCloseKey(newKey); } - - RegCloseKey(newKey); } } } - } - RegCloseKey(jreKey); + RegCloseKey(jreKey); + } } return javas; diff --git a/launcher/launch/steps/LookupServerAddress.cpp b/launcher/launch/steps/LookupServerAddress.cpp index 9bdac203b..4b67b3092 100644 --- a/launcher/launch/steps/LookupServerAddress.cpp +++ b/launcher/launch/steps/LookupServerAddress.cpp @@ -30,7 +30,7 @@ void LookupServerAddress::setLookupAddress(const QString& lookupAddress) m_dnsLookup->setName(QString("_minecraft._tcp.%1").arg(lookupAddress)); } -void LookupServerAddress::setOutputAddressPtr(MinecraftServerTargetPtr output) +void LookupServerAddress::setOutputAddressPtr(MinecraftTarget::Ptr output) { m_output = std::move(output); } diff --git a/launcher/launch/steps/LookupServerAddress.h b/launcher/launch/steps/LookupServerAddress.h index acbd74309..506314ee8 100644 --- a/launcher/launch/steps/LookupServerAddress.h +++ b/launcher/launch/steps/LookupServerAddress.h @@ -19,20 +19,20 @@ #include #include -#include "minecraft/launch/MinecraftServerTarget.h" +#include "minecraft/launch/MinecraftTarget.h" class LookupServerAddress : public LaunchStep { Q_OBJECT public: explicit LookupServerAddress(LaunchTask* parent); - virtual ~LookupServerAddress() {}; + virtual ~LookupServerAddress() = default; virtual void executeTask(); virtual bool abort(); virtual bool canAbort() const { return true; } void setLookupAddress(const QString& lookupAddress); - void setOutputAddressPtr(MinecraftServerTargetPtr output); + void setOutputAddressPtr(MinecraftTarget::Ptr output); private slots: void on_dnsLookupFinished(); @@ -42,5 +42,5 @@ class LookupServerAddress : public LaunchStep { QDnsLookup* m_dnsLookup; QString m_lookupAddress; - MinecraftServerTargetPtr m_output; + MinecraftTarget::Ptr m_output; }; diff --git a/launcher/meta/BaseEntity.cpp b/launcher/meta/BaseEntity.cpp index 24bced3a8..b0e754ada 100644 --- a/launcher/meta/BaseEntity.cpp +++ b/launcher/meta/BaseEntity.cpp @@ -15,6 +15,7 @@ #include "BaseEntity.h" +#include "Exception.h" #include "FileSystem.h" #include "Json.h" #include "modplatform/helpers/HashUtils.h" @@ -91,7 +92,8 @@ Task::Ptr BaseEntity::loadTask(Net::Mode mode) bool BaseEntity::isLoaded() const { - return m_load_status != LoadStatus::NotLoaded; + // consider it loaded only if the main hash is either empty and was remote loadded or the hashes match and was loaded + return m_sha256.isEmpty() ? m_load_status == LoadStatus::Remote : m_load_status != LoadStatus::NotLoaded && m_sha256 == m_file_sha256; } void BaseEntity::setSha256(QString sha256) @@ -109,30 +111,44 @@ BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode) : m_e void BaseEntityLoadTask::executeTask() { const QString fname = QDir("meta").absoluteFilePath(m_entity->localFilename()); - // load local file if nothing is loaded yet - if (m_entity->m_load_status == BaseEntity::LoadStatus::NotLoaded && QFile::exists(fname)) { - setStatus(tr("Loading local file")); + auto hashMatches = false; + // the file exists on disk try to load it + if (QFile::exists(fname)) { try { - auto fileData = FS::read(fname); - m_entity->m_file_sha256 = Hashing::hash(fileData, Hashing::Algorithm::Sha256); - if (m_mode == Net::Mode::Online && !m_entity->m_sha256.isEmpty() && m_entity->m_sha256 != m_entity->m_file_sha256) { - FS::deletePath(fname); - } else { + QByteArray fileData; + // read local file if nothing is loaded yet + if (m_entity->m_load_status == BaseEntity::LoadStatus::NotLoaded || m_entity->m_file_sha256.isEmpty()) { + setStatus(tr("Loading local file")); + fileData = FS::read(fname); + m_entity->m_file_sha256 = Hashing::hash(fileData, Hashing::Algorithm::Sha256); + } + + // on online the hash needs to match + hashMatches = m_entity->m_sha256 == m_entity->m_file_sha256; + if (m_mode == Net::Mode::Online && !m_entity->m_sha256.isEmpty() && !hashMatches) { + throw Exception("mismatched checksum"); + } + + // load local file + if (m_entity->m_load_status == BaseEntity::LoadStatus::NotLoaded) { auto doc = Json::requireDocument(fileData, fname); auto obj = Json::requireObject(doc, fname); m_entity->parse(obj); m_entity->m_load_status = BaseEntity::LoadStatus::Local; } + } catch (const Exception& e) { qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause()); // just make sure it's gone and we never consider it again. FS::deletePath(fname); + m_entity->m_load_status = BaseEntity::LoadStatus::NotLoaded; } } // if we need remote update, run the update task - auto hashMatches = !m_entity->m_sha256.isEmpty() && m_entity->m_sha256 == m_entity->m_file_sha256; auto wasLoadedOffline = m_entity->m_load_status != BaseEntity::LoadStatus::NotLoaded && m_mode == Net::Mode::Offline; - if (wasLoadedOffline || hashMatches) { + // if has is not present allways fetch from remote(e.g. the main index file), else only fetch if hash doesn't match + auto wasLoadedRemote = m_entity->m_sha256.isEmpty() ? m_entity->m_load_status == BaseEntity::LoadStatus::Remote : hashMatches; + if (wasLoadedOffline || wasLoadedRemote) { emitSucceeded(); return; } diff --git a/launcher/meta/Version.h b/launcher/meta/Version.h index 149af86f6..46dc740da 100644 --- a/launcher/meta/Version.h +++ b/launcher/meta/Version.h @@ -52,7 +52,7 @@ class Version : public QObject, public BaseVersion, public BaseEntity { const Meta::RequireSet& requiredSet() const { return m_requires; } VersionFilePtr data() const { return m_data; } bool isRecommended() const { return m_recommended; } - bool isLoaded() const { return m_data != nullptr; } + bool isLoaded() const { return m_data != nullptr && BaseEntity::isLoaded(); } void merge(const Version::Ptr& other); void mergeFromList(const Version::Ptr& other); diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp index 8bc326839..1480f0a21 100644 --- a/launcher/meta/VersionList.cpp +++ b/launcher/meta/VersionList.cpp @@ -140,6 +140,8 @@ Version::Ptr VersionList::getVersion(const QString& version) if (!out) { out = std::make_shared(m_uid, version); m_lookup[version] = out; + setupAddedVersion(m_versions.size(), out); + m_versions.append(out); } return out; } diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index d119104fe..7761b5766 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -196,8 +196,9 @@ void MinecraftInstance::loadSpecificSettings() } // Join server on launch, this does not have a global override - m_settings->registerSetting("JoinServerOnLaunch", false); + m_settings->registerSetting({ "JoinServerOnLaunch", "JoinOnLaunch" }, false); m_settings->registerSetting("JoinServerOnLaunchAddress", ""); + m_settings->registerSetting("JoinWorldOnLaunch", ""); // Use account for instance, this does not have a global override m_settings->registerSetting("UseAccountForInstance", false); @@ -523,8 +524,7 @@ QStringList MinecraftInstance::javaArguments() if (javaVersion.isModular() && shouldApplyOnlineFixes()) // allow reflective access to java.net - required by the skin fix - args << "--add-opens" - << "java.base/java.net=ALL-UNNAMED"; + args << "--add-opens" << "java.base/java.net=ALL-UNNAMED"; return args; } @@ -608,7 +608,7 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment() // dlsym variant is only needed for OpenGL and not included in the vulkan layer appendLib("libMangoHud_dlsym.so"); appendLib("libMangoHud_opengl.so"); - appendLib(mangoHudLib.fileName()); + preloadList << mangoHudLibString; } env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":"))); @@ -656,7 +656,7 @@ static QString replaceTokensIn(QString text, QMap with) return result; } -QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) const +QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) const { auto profile = m_components->getProfile(); QString args_pattern = profile->getMinecraftArguments(); @@ -664,12 +664,16 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine args_pattern += " --tweakClass " + tweaker; } - if (serverToJoin && !serverToJoin->address.isEmpty()) { - if (profile->hasTrait("feature:is_quick_play_multiplayer")) { - args_pattern += " --quickPlayMultiplayer " + serverToJoin->address + ':' + QString::number(serverToJoin->port); - } else { - args_pattern += " --server " + serverToJoin->address; - args_pattern += " --port " + QString::number(serverToJoin->port); + if (targetToJoin) { + if (!targetToJoin->address.isEmpty()) { + if (profile->hasTrait("feature:is_quick_play_multiplayer")) { + args_pattern += " --quickPlayMultiplayer " + targetToJoin->address + ':' + QString::number(targetToJoin->port); + } else { + 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; } -QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) +QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) { QString launchScript; @@ -732,9 +736,13 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS launchScript += "appletClass " + appletClass + "\n"; } - if (serverToJoin && !serverToJoin->address.isEmpty()) { - launchScript += "serverAddress " + serverToJoin->address + "\n"; - launchScript += "serverPort " + QString::number(serverToJoin->port) + "\n"; + if (targetToJoin) { + if (!targetToJoin->address.isEmpty()) { + launchScript += "serverAddress " + targetToJoin->address + "\n"; + launchScript += "serverPort " + QString::number(targetToJoin->port) + "\n"; + } else if (!targetToJoin->world.isEmpty()) { + launchScript += "worldName " + targetToJoin->world + "\n"; + } } // generic minecraft params @@ -787,16 +795,15 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS return launchScript; } -QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) +QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) { QStringList out; - out << "Main Class:" - << " " + getMainClass() << ""; - out << "Native path:" - << " " + getNativePath() << ""; + out << "Main Class:" << " " + getMainClass() << ""; + out << "Native path:" << " " + getNativePath() << ""; auto profile = m_components->getProfile(); + // traits auto alltraits = traits(); if (alltraits.size()) { out << "Traits:"; @@ -806,6 +813,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr out << ""; } + // native libraries auto settings = this->settings(); bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool(); bool nativeGLFW = settings->get("UseNativeGLFW").toBool(); @@ -841,6 +849,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr out << ""; } + // mods and core mods auto printModList = [&](const QString& label, ModFolderModel& model) { if (model.size()) { out << QString("%1:").arg(label); @@ -869,6 +878,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr printModList("Mods", *(loaderModList().get())); printModList("Core Mods", *(coreModList().get())); + // jar mods auto& jarMods = profile->getJarMods(); if (jarMods.size()) { out << "Jar Mods:"; @@ -884,11 +894,13 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr out << ""; } - auto params = processMinecraftArgs(nullptr, serverToJoin); + // minecraft arguments + auto params = processMinecraftArgs(nullptr, targetToJoin); out << "Params:"; out << " " + params.join(' '); out << ""; + // window size QString windowParams; if (settings->get("LaunchMaximized").toBool()) { out << "Window size: max (if available)"; @@ -1034,7 +1046,7 @@ Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode) return nullptr; } -shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) +shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) { updateRuntimeContext(); // FIXME: get rid of shared_from_this ... @@ -1058,16 +1070,23 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt process->appendStep(makeShared(pptr)); } - if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool()) { + if (!targetToJoin && settings()->get("JoinOnLaunch").toBool()) { QString fullAddress = settings()->get("JoinServerOnLaunchAddress").toString(); - serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress))); + if (!fullAddress.isEmpty()) { + targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(fullAddress, false))); + } else { + QString world = settings()->get("JoinWorldOnLaunch").toString(); + if (!world.isEmpty()) { + targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(world, true))); + } + } } - if (serverToJoin && serverToJoin->port == 25565) { + if (targetToJoin && targetToJoin->port == 25565) { // Resolve server address to join on launch auto step = makeShared(pptr); - step->setLookupAddress(serverToJoin->address); - step->setOutputAddressPtr(serverToJoin); + step->setLookupAddress(targetToJoin->address); + step->setOutputAddressPtr(targetToJoin); process->appendStep(step); } @@ -1100,7 +1119,7 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt // print some instance info here... { - process->appendStep(makeShared(pptr, session, serverToJoin)); + process->appendStep(makeShared(pptr, session, targetToJoin)); } // extract native jars if needed @@ -1123,7 +1142,7 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt auto step = makeShared(pptr); step->setWorkingDirectory(gameRoot()); step->setAuthSession(session); - step->setServerToJoin(serverToJoin); + step->setTargetToJoin(targetToJoin); process->appendStep(step); } diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index 7af0df389..ad2cda186 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -39,7 +39,7 @@ #include #include #include "BaseInstance.h" -#include "minecraft/launch/MinecraftServerTarget.h" +#include "minecraft/launch/MinecraftTarget.h" #include "minecraft/mod/Mod.h" class ModFolderModel; @@ -56,7 +56,7 @@ class MinecraftInstance : public BaseInstance { Q_OBJECT public: MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir); - virtual ~MinecraftInstance() {}; + virtual ~MinecraftInstance() = default; virtual void saveNow() override; void loadSpecificSettings() override; @@ -121,11 +121,11 @@ class MinecraftInstance : public BaseInstance { ////// Launch stuff ////// Task::Ptr createUpdateTask(Net::Mode mode) override; - shared_qobject_ptr createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override; + shared_qobject_ptr createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) override; QStringList extraArguments() override; - QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override; + QStringList verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) override; QList getJarMods() const; - QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin); + QString createLaunchScript(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin); /// get arguments passed to java QStringList javaArguments(); QString getLauncher(); @@ -155,7 +155,7 @@ class MinecraftInstance : public BaseInstance { virtual QString getMainClass() const; // FIXME: remove - virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) const; + virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) const; virtual JavaVersion getJavaVersion(); diff --git a/launcher/minecraft/MinecraftUpdate.cpp b/launcher/minecraft/MinecraftUpdate.cpp index c009317a6..b63430aa8 100644 --- a/launcher/minecraft/MinecraftUpdate.cpp +++ b/launcher/minecraft/MinecraftUpdate.cpp @@ -16,32 +16,22 @@ #include "MinecraftUpdate.h" #include "MinecraftInstance.h" -#include -#include -#include -#include - -#include -#include "BaseInstance.h" -#include "minecraft/Library.h" #include "minecraft/PackProfile.h" +#include "tasks/SequentialTask.h" #include "update/AssetUpdateTask.h" #include "update/FMLLibrariesTask.h" #include "update/FoldersTask.h" #include "update/LibrariesTask.h" -#include -#include - -MinecraftUpdate::MinecraftUpdate(MinecraftInstance* inst, QObject* parent) : Task(parent), m_inst(inst) {} +MinecraftUpdate::MinecraftUpdate(MinecraftInstance* inst, QObject* parent) : SequentialTask(parent), m_inst(inst) {} void MinecraftUpdate::executeTask() { - m_tasks.clear(); + m_queue.clear(); // create folders { - m_tasks.append(makeShared(m_inst)); + addTask(makeShared(m_inst)); } // add metadata update task if necessary @@ -50,121 +40,24 @@ void MinecraftUpdate::executeTask() components->reload(Net::Mode::Online); auto task = components->getCurrentTask(); if (task) { - m_tasks.append(task); + addTask(task); } } // libraries download { - m_tasks.append(makeShared(m_inst)); + addTask(makeShared(m_inst)); } // FML libraries download and copy into the instance { - m_tasks.append(makeShared(m_inst)); + addTask(makeShared(m_inst)); } // assets update { - m_tasks.append(makeShared(m_inst)); + addTask(makeShared(m_inst)); } - if (!m_preFailure.isEmpty()) { - emitFailed(m_preFailure); - return; - } - next(); -} - -void MinecraftUpdate::next() -{ - if (m_abort) { - emitFailed(tr("Aborted by user.")); - return; - } - if (m_failed_out_of_order) { - emitFailed(m_fail_reason); - return; - } - m_currentTask++; - if (m_currentTask > 0) { - auto task = m_tasks[m_currentTask - 1]; - disconnect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded); - disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); - disconnect(task.get(), &Task::aborted, this, &Task::abort); - disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); - disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propagateStepProgress); - disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); - disconnect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails); - } - if (m_currentTask == m_tasks.size()) { - emitSucceeded(); - return; - } - auto task = m_tasks[m_currentTask]; - // if the task is already finished by the time we look at it, skip it - if (task->isFinished()) { - qCritical() << "MinecraftUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get(); - next(); - } - connect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded); - connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); - connect(task.get(), &Task::aborted, this, &Task::abort); - connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); - connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propagateStepProgress); - connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); - connect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails); - // if the task is already running, do not start it again - if (!task->isRunning()) { - task->start(); - } -} - -void MinecraftUpdate::subtaskSucceeded() -{ - if (isFinished()) { - qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!"; - return; - } - auto senderTask = QObject::sender(); - auto currentTask = m_tasks[m_currentTask].get(); - if (senderTask != currentTask) { - qDebug() << "MinecraftUpdate: Subtask" << sender() << "succeeded out of order."; - return; - } - next(); -} - -void MinecraftUpdate::subtaskFailed(QString error) -{ - if (isFinished()) { - qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!"; - return; - } - auto senderTask = QObject::sender(); - auto currentTask = m_tasks[m_currentTask].get(); - if (senderTask != currentTask) { - qDebug() << "MinecraftUpdate: Subtask" << sender() << "failed out of order."; - m_failed_out_of_order = true; - m_fail_reason = error; - return; - } - emitFailed(error); -} - -bool MinecraftUpdate::abort() -{ - if (!m_abort) { - m_abort = true; - auto task = m_tasks[m_currentTask]; - if (task->canAbort()) { - return task->abort(); - } - } - return true; -} - -bool MinecraftUpdate::canAbort() const -{ - return true; + SequentialTask::executeTask(); } diff --git a/launcher/minecraft/MinecraftUpdate.h b/launcher/minecraft/MinecraftUpdate.h index 591c6afcb..456a13518 100644 --- a/launcher/minecraft/MinecraftUpdate.h +++ b/launcher/minecraft/MinecraftUpdate.h @@ -15,43 +15,19 @@ #pragma once -#include -#include -#include +#include "tasks/SequentialTask.h" -#include -#include "minecraft/VersionFilterData.h" -#include "net/NetJob.h" -#include "tasks/Task.h" - -class MinecraftVersion; class MinecraftInstance; -// FIXME: This looks very similar to a SequentialTask. Maybe we can reduce code duplications? :^) - -class MinecraftUpdate : public Task { +// this needs to be a task because components->reload does stuff that may block +class MinecraftUpdate : public SequentialTask { Q_OBJECT public: explicit MinecraftUpdate(MinecraftInstance* inst, QObject* parent = 0); - virtual ~MinecraftUpdate() {}; + virtual ~MinecraftUpdate() = default; void executeTask() override; - bool canAbort() const override; - - private slots: - bool abort() override; - void subtaskSucceeded(); - void subtaskFailed(QString error); - - private: - void next(); private: MinecraftInstance* m_inst = nullptr; - QList m_tasks; - QString m_preFailure; - int m_currentTask = -1; - bool m_abort = false; - bool m_failed_out_of_order = false; - QString m_fail_reason; }; diff --git a/launcher/minecraft/auth/steps/EntitlementsStep.cpp b/launcher/minecraft/auth/steps/EntitlementsStep.cpp index 4c4809fae..5b9809c52 100644 --- a/launcher/minecraft/auth/steps/EntitlementsStep.cpp +++ b/launcher/minecraft/auth/steps/EntitlementsStep.cpp @@ -11,7 +11,7 @@ #include "minecraft/auth/Parsers.h" #include "net/Download.h" #include "net/NetJob.h" -#include "net/StaticHeaderProxy.h" +#include "net/RawHeaderProxy.h" #include "tasks/Task.h" EntitlementsStep::EntitlementsStep(AccountData* data) : AuthStep(data) {} @@ -33,7 +33,7 @@ void EntitlementsStep::perform() m_response.reset(new QByteArray()); 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->setAskRetry(false); diff --git a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp index 08e1b3b1f..954f013af 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp @@ -7,7 +7,7 @@ #include "Logging.h" #include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" -#include "net/StaticHeaderProxy.h" +#include "net/RawHeaderProxy.h" #include "net/Upload.h" LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {} @@ -38,7 +38,7 @@ void LauncherLoginStep::perform() m_response.reset(new QByteArray()); 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->setAskRetry(false); diff --git a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp index 3e46eaab4..c283b153e 100644 --- a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp +++ b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp @@ -40,7 +40,7 @@ #include "Application.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 MSADeviceCodeStep::MSADeviceCodeStep(AccountData* data) : AuthStep(data) @@ -68,7 +68,7 @@ void MSADeviceCodeStep::perform() }; m_response.reset(new QByteArray()); 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->setAskRetry(false); @@ -183,7 +183,7 @@ void MSADeviceCodeStep::authenticateUser() }; m_response.reset(new QByteArray()); 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); diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index e27293960..c1529b086 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -5,7 +5,7 @@ #include "Application.h" #include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" -#include "net/StaticHeaderProxy.h" +#include "net/RawHeaderProxy.h" MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {} @@ -23,7 +23,7 @@ void MinecraftProfileStep::perform() m_response.reset(new QByteArray()); 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->setAskRetry(false); diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp index 4f5f14a33..f9de9210b 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -8,7 +8,7 @@ #include "Logging.h" #include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" -#include "net/StaticHeaderProxy.h" +#include "net/RawHeaderProxy.h" #include "net/Upload.h" XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind) @@ -43,7 +43,7 @@ void XboxAuthorizationStep::perform() }; m_response.reset(new QByteArray()); 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->setAskRetry(false); diff --git a/launcher/minecraft/auth/steps/XboxProfileStep.cpp b/launcher/minecraft/auth/steps/XboxProfileStep.cpp index a784fa195..b896357c0 100644 --- a/launcher/minecraft/auth/steps/XboxProfileStep.cpp +++ b/launcher/minecraft/auth/steps/XboxProfileStep.cpp @@ -6,7 +6,7 @@ #include "Application.h" #include "Logging.h" #include "net/NetUtils.h" -#include "net/StaticHeaderProxy.h" +#include "net/RawHeaderProxy.h" XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {} @@ -35,7 +35,7 @@ void XboxProfileStep::perform() m_response.reset(new QByteArray()); 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->setAskRetry(false); diff --git a/launcher/minecraft/auth/steps/XboxUserStep.cpp b/launcher/minecraft/auth/steps/XboxUserStep.cpp index 3e697b506..4e5abb62b 100644 --- a/launcher/minecraft/auth/steps/XboxUserStep.cpp +++ b/launcher/minecraft/auth/steps/XboxUserStep.cpp @@ -5,7 +5,7 @@ #include "Application.h" #include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" -#include "net/StaticHeaderProxy.h" +#include "net/RawHeaderProxy.h" XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {} @@ -39,7 +39,7 @@ void XboxUserStep::perform() }; m_response.reset(new QByteArray()); 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->setAskRetry(false); diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index b35d55924..2b932ae47 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -90,7 +90,7 @@ void LauncherPartLaunch::executeTask() } } - m_launchScript = minecraftInstance->createLaunchScript(m_session, m_serverToJoin); + m_launchScript = minecraftInstance->createLaunchScript(m_session, m_targetToJoin); QStringList args = minecraftInstance->javaArguments(); QString allArgs = args.join(", "); emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::Launcher); diff --git a/launcher/minecraft/launch/LauncherPartLaunch.h b/launcher/minecraft/launch/LauncherPartLaunch.h index fcd4daec6..ea125aa9e 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.h +++ b/launcher/minecraft/launch/LauncherPartLaunch.h @@ -19,13 +19,13 @@ #include #include -#include "MinecraftServerTarget.h" +#include "MinecraftTarget.h" class LauncherPartLaunch : public LaunchStep { Q_OBJECT public: explicit LauncherPartLaunch(LaunchTask* parent); - virtual ~LauncherPartLaunch() {}; + virtual ~LauncherPartLaunch() = default; virtual void executeTask(); virtual bool abort(); @@ -34,7 +34,7 @@ class LauncherPartLaunch : public LaunchStep { void setWorkingDirectory(const QString& wd); void setAuthSession(AuthSessionPtr session) { m_session = session; } - void setServerToJoin(MinecraftServerTargetPtr serverToJoin) { m_serverToJoin = std::move(serverToJoin); } + void setTargetToJoin(MinecraftTarget::Ptr targetToJoin) { m_targetToJoin = std::move(targetToJoin); } private slots: void on_state(LoggedProcess::State state); @@ -44,7 +44,7 @@ class LauncherPartLaunch : public LaunchStep { QString m_command; AuthSessionPtr m_session; QString m_launchScript; - MinecraftServerTargetPtr m_serverToJoin; + MinecraftTarget::Ptr m_targetToJoin; bool mayProceed = false; }; diff --git a/launcher/minecraft/launch/MinecraftServerTarget.cpp b/launcher/minecraft/launch/MinecraftTarget.cpp similarity index 86% rename from launcher/minecraft/launch/MinecraftServerTarget.cpp rename to launcher/minecraft/launch/MinecraftTarget.cpp index e201efab1..ba9f87511 100644 --- a/launcher/minecraft/launch/MinecraftServerTarget.cpp +++ b/launcher/minecraft/launch/MinecraftTarget.cpp @@ -13,13 +13,18 @@ * limitations under the License. */ -#include "MinecraftServerTarget.h" +#include "MinecraftTarget.h" #include // FIXME: the way this is written, it can't ever do any sort of validation and can accept total junk -MinecraftServerTarget MinecraftServerTarget::parse(const QString& fullAddress) +MinecraftTarget MinecraftTarget::parse(const QString& fullAddress, bool useWorld) { + if (useWorld) { + MinecraftTarget target; + target.world = fullAddress; + return target; + } QStringList split = fullAddress.split(":"); // The logic below replicates the exact logic minecraft uses for parsing server addresses. @@ -56,5 +61,5 @@ MinecraftServerTarget MinecraftServerTarget::parse(const QString& fullAddress) } } - return MinecraftServerTarget{ realAddress, realPort }; + return MinecraftTarget{ realAddress, realPort }; } diff --git a/launcher/minecraft/launch/MinecraftServerTarget.h b/launcher/minecraft/launch/MinecraftTarget.h similarity index 80% rename from launcher/minecraft/launch/MinecraftServerTarget.h rename to launcher/minecraft/launch/MinecraftTarget.h index 2edd8a30d..7f8b268d9 100644 --- a/launcher/minecraft/launch/MinecraftServerTarget.h +++ b/launcher/minecraft/launch/MinecraftTarget.h @@ -19,11 +19,11 @@ #include -struct MinecraftServerTarget { +struct MinecraftTarget { QString address; quint16 port; - static MinecraftServerTarget parse(const QString& fullAddress); + QString world; + static MinecraftTarget parse(const QString& fullAddress, bool useWorld); + using Ptr = std::shared_ptr; }; - -using MinecraftServerTargetPtr = std::shared_ptr; diff --git a/launcher/minecraft/launch/PrintInstanceInfo.cpp b/launcher/minecraft/launch/PrintInstanceInfo.cpp index e3a45b030..e44d09839 100644 --- a/launcher/minecraft/launch/PrintInstanceInfo.cpp +++ b/launcher/minecraft/launch/PrintInstanceInfo.cpp @@ -129,6 +129,6 @@ void PrintInstanceInfo::executeTask() #endif logLines(log, MessageLevel::Launcher); - logLines(instance->verboseDescription(m_session, m_serverToJoin), MessageLevel::Launcher); + logLines(instance->verboseDescription(m_session, m_targetToJoin), MessageLevel::Launcher); emitSucceeded(); } diff --git a/launcher/minecraft/launch/PrintInstanceInfo.h b/launcher/minecraft/launch/PrintInstanceInfo.h index 93c5f0fd6..4138c0cd2 100644 --- a/launcher/minecraft/launch/PrintInstanceInfo.h +++ b/launcher/minecraft/launch/PrintInstanceInfo.h @@ -16,22 +16,21 @@ #pragma once #include -#include #include "minecraft/auth/AuthSession.h" -#include "minecraft/launch/MinecraftServerTarget.h" +#include "minecraft/launch/MinecraftTarget.h" // FIXME: temporary wrapper for existing task. class PrintInstanceInfo : public LaunchStep { Q_OBJECT public: - explicit PrintInstanceInfo(LaunchTask* parent, AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) - : LaunchStep(parent), m_session(session), m_serverToJoin(serverToJoin) {}; - virtual ~PrintInstanceInfo() {}; + explicit PrintInstanceInfo(LaunchTask* parent, AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) + : LaunchStep(parent), m_session(session), m_targetToJoin(targetToJoin) {}; + virtual ~PrintInstanceInfo() = default; virtual void executeTask(); virtual bool canAbort() const { return false; } private: AuthSessionPtr m_session; - MinecraftServerTargetPtr m_serverToJoin; + MinecraftTarget::Ptr m_targetToJoin; }; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index bd68f14ef..5e4fe7f11 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -251,7 +251,7 @@ Task* ModFolderModel::createParseTask(Resource& resource) bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata) { for (auto mod : allMods()) { - if (mod->fileinfo().fileName() == filename) { + if (mod->getOriginalFileName() == filename) { auto index_dir = indexDir(); mod->destroy(index_dir, preserve_metadata, false); diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 2a7626953..c52d76f1f 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -197,3 +197,11 @@ bool Resource::isMoreThanOneHardLink() const { 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; +} \ No newline at end of file diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index f6f859cab..c99dab8db 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -80,6 +80,7 @@ class Resource : public QObject { [[nodiscard]] auto internal_id() const -> QString { return m_internal_id; } [[nodiscard]] auto type() const -> ResourceType { return m_type; } [[nodiscard]] bool enabled() const { return m_enabled; } + [[nodiscard]] auto getOriginalFileName() const -> QString; [[nodiscard]] QString sizeStr() const { return m_size_str; } [[nodiscard]] qint64 sizeInfo() const { return m_size_info; } diff --git a/launcher/minecraft/skins/CapeChange.cpp b/launcher/minecraft/skins/CapeChange.cpp index 4db28e245..abbaa0b67 100644 --- a/launcher/minecraft/skins/CapeChange.cpp +++ b/launcher/minecraft/skins/CapeChange.cpp @@ -39,9 +39,9 @@ #include #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; } @@ -57,18 +57,14 @@ QNetworkReply* CapeChange::getReply(QNetworkRequest& request) } } -void CapeChange::init() -{ - addHeaderProxy(new Net::StaticHeaderProxy(QList{ - { "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() }, - })); -} - CapeChange::Ptr CapeChange::make(QString token, QString capeId) { - auto up = makeShared(token, capeId); + auto up = makeShared(capeId); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active"); up->setObjectName(QString("BYTES:") + up->m_url.toString()); up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); + up->addHeaderProxy(new Net::RawHeaderProxy(QList{ + { "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() }, + })); return up; } diff --git a/launcher/minecraft/skins/CapeChange.h b/launcher/minecraft/skins/CapeChange.h index bcafcde87..2be904f4d 100644 --- a/launcher/minecraft/skins/CapeChange.h +++ b/launcher/minecraft/skins/CapeChange.h @@ -24,16 +24,14 @@ class CapeChange : public Net::NetRequest { Q_OBJECT public: using Ptr = shared_qobject_ptr; - CapeChange(QString token, QString capeId); + CapeChange(QString capeId); virtual ~CapeChange() = default; static CapeChange::Ptr make(QString token, QString capeId); - void init() override; protected: virtual QNetworkReply* getReply(QNetworkRequest&) override; private: QString m_capeId; - QString m_token; }; diff --git a/launcher/minecraft/skins/SkinDelete.cpp b/launcher/minecraft/skins/SkinDelete.cpp index 3c50cf313..94aca62ca 100644 --- a/launcher/minecraft/skins/SkinDelete.cpp +++ b/launcher/minecraft/skins/SkinDelete.cpp @@ -37,9 +37,9 @@ #include "SkinDelete.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; } @@ -50,17 +50,13 @@ QNetworkReply* SkinDelete::getReply(QNetworkRequest& request) return m_network->deleteResource(request); } -void SkinDelete::init() -{ - addHeaderProxy(new Net::StaticHeaderProxy(QList{ - { "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() }, - })); -} - SkinDelete::Ptr SkinDelete::make(QString token) { - auto up = makeShared(token); + auto up = makeShared(); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active"); up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); + up->addHeaderProxy(new Net::RawHeaderProxy(QList{ + { "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() }, + })); return up; } diff --git a/launcher/minecraft/skins/SkinDelete.h b/launcher/minecraft/skins/SkinDelete.h index 5d02e0cc4..d6a68d22c 100644 --- a/launcher/minecraft/skins/SkinDelete.h +++ b/launcher/minecraft/skins/SkinDelete.h @@ -24,15 +24,11 @@ class SkinDelete : public Net::NetRequest { Q_OBJECT public: using Ptr = shared_qobject_ptr; - SkinDelete(QString token); + SkinDelete(); virtual ~SkinDelete() = default; static SkinDelete::Ptr make(QString token); - void init() override; protected: virtual QNetworkReply* getReply(QNetworkRequest&) override; - - private: - QString m_token; }; diff --git a/launcher/minecraft/skins/SkinUpload.cpp b/launcher/minecraft/skins/SkinUpload.cpp index dc1bc0bf9..ccc29d281 100644 --- a/launcher/minecraft/skins/SkinUpload.cpp +++ b/launcher/minecraft/skins/SkinUpload.cpp @@ -40,9 +40,9 @@ #include "FileSystem.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; } @@ -67,18 +67,14 @@ QNetworkReply* SkinUpload::getReply(QNetworkRequest& request) return m_network->post(request, multiPart); } -void SkinUpload::init() -{ - addHeaderProxy(new Net::StaticHeaderProxy(QList{ - { "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() }, - })); -} - SkinUpload::Ptr SkinUpload::make(QString token, QString path, QString variant) { - auto up = makeShared(token, path, variant); + auto up = makeShared(path, variant); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins"); up->setObjectName(QString("BYTES:") + up->m_url.toString()); up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); + up->addHeaderProxy(new Net::RawHeaderProxy(QList{ + { "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() }, + })); return up; } diff --git a/launcher/minecraft/skins/SkinUpload.h b/launcher/minecraft/skins/SkinUpload.h index f24cef5a2..c1a4930b7 100644 --- a/launcher/minecraft/skins/SkinUpload.h +++ b/launcher/minecraft/skins/SkinUpload.h @@ -26,17 +26,15 @@ class SkinUpload : public Net::NetRequest { using Ptr = shared_qobject_ptr; // Note this class takes ownership of the file. - SkinUpload(QString token, QString path, QString variant); + SkinUpload(QString path, QString variant); virtual ~SkinUpload() = default; static SkinUpload::Ptr make(QString token, QString path, QString variant); - void init() override; protected: virtual QNetworkReply* getReply(QNetworkRequest&) override; private: - QString m_token; QString m_path; QString m_variant; }; diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 3f9685892..39b64f1c3 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -104,7 +104,7 @@ void Flame::FileResolvingTask::netJobFinished() auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash); auto output = std::make_shared(); 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); 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 dl = Net::ApiDownload::makeByteArray(url, output); 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 json = QJsonDocument::fromJson(*output); auto base = diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 957bc19bc..370f37c5f 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -1,4 +1,5 @@ #include "FlameCheckUpdate.h" +#include "Application.h" #include "FlameAPI.h" #include "FlameModIndex.h" @@ -124,11 +125,6 @@ void FlameCheckUpdate::executeTask() int i = 0; 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())); setProgress(i++, m_mods.size()); @@ -177,7 +173,7 @@ void FlameCheckUpdate::executeTask() auto download_task = makeShared(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, 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(pack, latest_ver.value())); } diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index 0e9e349d9..70bf138a8 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -42,11 +42,6 @@ void ModrinthCheckUpdate::executeTask() auto hashing_task = makeShared(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); 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; // Sadly the API can only handle one hash type per call, se we @@ -95,8 +90,7 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr resp // If the returned project is empty, but we have Modrinth metadata, // it means this specific version is not available if (project_obj.isEmpty()) { - qDebug() << "Mod " << m_mappings.find(hash).value()->name() << " got an empty response." - << "Hash: " << hash; + qDebug() << "Mod " << m_mappings.find(hash).value()->name() << " got an empty response." << "Hash: " << hash; continue; } @@ -153,7 +147,7 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr resp auto download_task = makeShared(pack, project_ver, m_mods_folder); 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(pack, project_ver)); } diff --git a/launcher/net/ApiDownload.cpp b/launcher/net/ApiDownload.cpp index 0494d18ad..78eb1f851 100644 --- a/launcher/net/ApiDownload.cpp +++ b/launcher/net/ApiDownload.cpp @@ -18,47 +18,29 @@ */ #include "net/ApiDownload.h" -#include "ByteArraySink.h" -#include "ChecksumValidator.h" -#include "MetaCacheSink.h" +#include "net/ApiHeaderProxy.h" 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(); - dl->m_url = url; - 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); + auto dl = Download::makeCached(url, entry, options); + dl->addHeaderProxy(new ApiHeaderProxy()); return dl; } -auto ApiDownload::makeByteArray(QUrl url, std::shared_ptr output, Options options) -> Download::Ptr +Download::Ptr ApiDownload::makeByteArray(QUrl url, std::shared_ptr output, Download::Options options) { - auto dl = makeShared(); - dl->m_url = url; - dl->setObjectName(QString("BYTES:") + url.toString()); - dl->m_options = options; - dl->m_sink.reset(new ByteArraySink(output)); + auto dl = Download::makeByteArray(url, output, options); + dl->addHeaderProxy(new ApiHeaderProxy()); 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(); - dl->m_url = url; - dl->setObjectName(QString("FILE:") + url.toString()); - dl->m_options = options; - dl->m_sink.reset(new FileSink(path)); + auto dl = Download::makeFile(url, path, options); + dl->addHeaderProxy(new ApiHeaderProxy()); return dl; } -void ApiDownload::init() -{ - auto api_headers = new ApiHeaderProxy(); - addHeaderProxy(api_headers); -} } // namespace Net diff --git a/launcher/net/ApiDownload.h b/launcher/net/ApiDownload.h index 638c94e11..842c25c56 100644 --- a/launcher/net/ApiDownload.h +++ b/launcher/net/ApiDownload.h @@ -19,20 +19,14 @@ #pragma once -#include "ApiHeaderProxy.h" #include "Download.h" namespace Net { -class ApiDownload : public Download { - public: - virtual ~ApiDownload() = default; - - static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr; - static auto makeByteArray(QUrl url, std::shared_ptr output, Options options = Option::NoOptions) -> Download::Ptr; - static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr; - - void init() override; -}; +namespace ApiDownload { +Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Download::Options options = Download::Option::NoOptions); +Download::Ptr makeByteArray(QUrl url, std::shared_ptr output, Download::Options options = Download::Option::NoOptions); +Download::Ptr makeFile(QUrl url, QString path, Download::Options options = Download::Option::NoOptions); +}; // namespace ApiDownload } // namespace Net diff --git a/launcher/net/ApiUpload.cpp b/launcher/net/ApiUpload.cpp index 01b081dd5..a2b8f357b 100644 --- a/launcher/net/ApiUpload.cpp +++ b/launcher/net/ApiUpload.cpp @@ -18,22 +18,15 @@ */ #include "net/ApiUpload.h" -#include "ByteArraySink.h" +#include "net/ApiHeaderProxy.h" namespace Net { Upload::Ptr ApiUpload::makeByteArray(QUrl url, std::shared_ptr output, QByteArray m_post_data) { - auto up = makeShared(); - up->m_url = std::move(url); - up->m_sink.reset(new ByteArraySink(output)); - up->m_post_data = std::move(m_post_data); + auto up = Upload::makeByteArray(url, output, m_post_data); + up->addHeaderProxy(new ApiHeaderProxy()); return up; } -void ApiUpload::init() -{ - auto api_headers = new ApiHeaderProxy(); - addHeaderProxy(api_headers); -} } // namespace Net diff --git a/launcher/net/ApiUpload.h b/launcher/net/ApiUpload.h index b12842b05..674a3b93f 100644 --- a/launcher/net/ApiUpload.h +++ b/launcher/net/ApiUpload.h @@ -19,18 +19,12 @@ #pragma once -#include "ApiHeaderProxy.h" #include "Upload.h" namespace Net { -class ApiUpload : public Upload { - public: - virtual ~ApiUpload() = default; - - static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr output, QByteArray m_post_data); - - void init() override; +namespace ApiUpload { +Upload::Ptr makeByteArray(QUrl url, std::shared_ptr output, QByteArray m_post_data); }; } // namespace Net diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index f3dbe32d3..cf2e72858 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -62,8 +62,6 @@ void NetRequest::addValidator(Validator* v) void NetRequest::executeTask() { - init(); - setStatus(tr("Requesting %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80))); if (getState() == Task::State::AbortedByUser) { diff --git a/launcher/net/NetRequest.h b/launcher/net/NetRequest.h index 0cd6ceabc..6b3f643d6 100644 --- a/launcher/net/NetRequest.h +++ b/launcher/net/NetRequest.h @@ -72,8 +72,6 @@ class NetRequest : public Task { void setNetwork(shared_qobject_ptr network) { m_network = network; } void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr(proxy)); } - virtual void init() {} - QUrl url() const; void setUrl(QUrl url) { m_url = url; } int replyStatusCode() const; diff --git a/launcher/net/RawHeaderProxy.h b/launcher/net/RawHeaderProxy.h index 09b3d4d02..9de18efc7 100644 --- a/launcher/net/RawHeaderProxy.h +++ b/launcher/net/RawHeaderProxy.h @@ -4,6 +4,7 @@ * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * Copyright (c) 2023 Trial97 * * 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 @@ -27,7 +28,7 @@ namespace Net { class RawHeaderProxy : public HeaderProxy { public: - RawHeaderProxy() : HeaderProxy() {} + RawHeaderProxy(QList headers = {}) : HeaderProxy(), m_headers(std::move(headers)) {}; virtual ~RawHeaderProxy() = default; public: @@ -36,6 +37,7 @@ class RawHeaderProxy : public HeaderProxy { void addHeader(const HeaderPair& header) { m_headers.append(header); } void addHeader(const QByteArray& headerName, const QByteArray& headerValue) { m_headers.append({ headerName, headerValue }); } void addHeaders(const QList& headers) { m_headers.append(headers); } + void setHeaders(QList headers) { m_headers = headers; }; private: QList m_headers; diff --git a/launcher/net/StaticHeaderProxy.h b/launcher/net/StaticHeaderProxy.h deleted file mode 100644 index 73678c026..000000000 --- a/launcher/net/StaticHeaderProxy.h +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023 Trial97 - * - * 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 . - * - */ - -#pragma once - -#include "net/HeaderProxy.h" - -namespace Net { - -class StaticHeaderProxy : public HeaderProxy { - public: - StaticHeaderProxy(QList hdrs = {}) : HeaderProxy(), m_hdrs(hdrs) {}; - virtual ~StaticHeaderProxy() = default; - - public: - virtual QList headers(const QNetworkRequest&) const override { return m_hdrs; }; - void setHeaders(QList hdrs) { m_hdrs = hdrs; }; - - private: - QList m_hdrs; -}; - -} // namespace Net diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp index d19f251ea..7ee98760a 100644 --- a/launcher/screenshots/ImgurAlbumCreation.cpp +++ b/launcher/screenshots/ImgurAlbumCreation.cpp @@ -46,7 +46,7 @@ #include #include "BuildConfig.h" -#include "net/StaticHeaderProxy.h" +#include "net/RawHeaderProxy.h" Net::NetRequest::Ptr ImgurAlbumCreation::make(std::shared_ptr output, QList screenshots) { @@ -54,6 +54,10 @@ Net::NetRequest::Ptr ImgurAlbumCreation::make(std::shared_ptrm_url = BuildConfig.IMGUR_BASE_URL + "album"; up->m_sink.reset(new Sink(output)); up->m_screenshots = screenshots; + up->addHeaderProxy(new Net::RawHeaderProxy( + QList{ { "Content-Type", "application/x-www-form-urlencoded" }, + { "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, + { "Accept", "application/json" } })); return up; } @@ -67,16 +71,6 @@ QNetworkReply* ImgurAlbumCreation::getReply(QNetworkRequest& request) return m_network->post(request, data); } -void ImgurAlbumCreation::init() -{ - qDebug() << "Setting up imgur upload"; - auto api_headers = new Net::StaticHeaderProxy( - QList{ { "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 { m_output.clear(); diff --git a/launcher/screenshots/ImgurAlbumCreation.h b/launcher/screenshots/ImgurAlbumCreation.h index ecb225394..f10409b20 100644 --- a/launcher/screenshots/ImgurAlbumCreation.h +++ b/launcher/screenshots/ImgurAlbumCreation.h @@ -67,8 +67,6 @@ class ImgurAlbumCreation : public Net::NetRequest { static NetRequest::Ptr make(std::shared_ptr output, QList screenshots); QNetworkReply* getReply(QNetworkRequest& request) override; - void init() override; - private: QList m_screenshots; }; diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index 8f60148bf..8b4ef5327 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -36,7 +36,7 @@ #include "ImgurUpload.h" #include "BuildConfig.h" -#include "net/StaticHeaderProxy.h" +#include "net/RawHeaderProxy.h" #include #include @@ -47,14 +47,6 @@ #include #include -void ImgurUpload::init() -{ - qDebug() << "Setting up imgur upload"; - auto api_headers = new Net::StaticHeaderProxy(QList{ - { "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, { "Accept", "application/json" } }); - addHeaderProxy(api_headers); -} - QNetworkReply* ImgurUpload::getReply(QNetworkRequest& request) { auto file = new QFile(m_fileInfo.absoluteFilePath(), this); @@ -125,5 +117,7 @@ Net::NetRequest::Ptr ImgurUpload::make(ScreenShot::Ptr m_shot) auto up = makeShared(m_shot->m_file); up->m_url = std::move(BuildConfig.IMGUR_BASE_URL + "image"); up->m_sink.reset(new Sink(m_shot)); + up->addHeaderProxy(new Net::RawHeaderProxy(QList{ + { "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, { "Accept", "application/json" } })); return up; } diff --git a/launcher/screenshots/ImgurUpload.h b/launcher/screenshots/ImgurUpload.h index 5a58ad2b5..f4f71859d 100644 --- a/launcher/screenshots/ImgurUpload.h +++ b/launcher/screenshots/ImgurUpload.h @@ -62,8 +62,6 @@ class ImgurUpload : public Net::NetRequest { static NetRequest::Ptr make(ScreenShot::Ptr m_shot); - void init() override; - private: virtual QNetworkReply* getReply(QNetworkRequest&) override; const QFileInfo m_fileInfo; diff --git a/launcher/ui/dialogs/ProfileSetupDialog.cpp b/launcher/ui/dialogs/ProfileSetupDialog.cpp index c5d4c2621..385094e23 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.cpp +++ b/launcher/ui/dialogs/ProfileSetupDialog.cpp @@ -34,6 +34,7 @@ */ #include "ProfileSetupDialog.h" +#include "net/RawHeaderProxy.h" #include "ui_ProfileSetupDialog.h" #include @@ -46,7 +47,6 @@ #include #include "minecraft/auth/Parsers.h" -#include "net/StaticHeaderProxy.h" #include "net/Upload.h" ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget* parent) @@ -160,7 +160,7 @@ void ProfileSetupDialog::checkName(const QString& name) if (m_check_task) disconnect(m_check_task.get(), nullptr, this, nullptr); 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); @@ -204,7 +204,7 @@ void ProfileSetupDialog::setupProfile(const QString& profileName) m_profile_response.reset(new QByteArray()); 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); diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index b3387ba02..72039488b 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -291,6 +291,20 @@ + + + Number of manual retries + + + + + + + 0 + + + + Seconds to wait until the requests are terminated @@ -300,27 +314,13 @@ - + s - - - - Number of manual retries - - - - - - - 0 - - - diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 76add9402..a017d5e13 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -36,6 +36,8 @@ */ #include "InstanceSettingsPage.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/WorldList.h" #include "ui_InstanceSettingsPage.h" #include @@ -71,6 +73,22 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance* inst, QWidget* parent) connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeGLFWChanged); connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeOpenALChanged); + auto mInst = dynamic_cast(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(); updateThresholds(); @@ -256,9 +274,16 @@ void InstanceSettingsPage::applySettings() bool joinServerOnLaunch = ui->serverJoinGroupBox->isChecked(); m_settings->set("JoinServerOnLaunch", 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 { m_settings->reset("JoinServerOnLaunchAddress"); + m_settings->reset("JoinWorldOnLaunch"); } // Use an account for this instance @@ -379,7 +404,25 @@ void InstanceSettingsPage::loadSettings() ui->recordGameTime->setChecked(m_settings->get("RecordGameTime").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()); updateAccountsMenu(); @@ -534,3 +577,13 @@ void InstanceSettingsPage::updateThresholds() 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); +} diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h index 8b78dcb7f..6364502c9 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.h +++ b/launcher/ui/pages/instance/InstanceSettingsPage.h @@ -70,6 +70,8 @@ class InstanceSettingsPage : public QWidget, public BasePage { void on_javaTestBtn_clicked(); void on_javaBrowseBtn_clicked(); void on_maxMemSpinBox_valueChanged(int i); + void on_serverJoinAddressButton_toggled(bool checked); + void on_worldJoinButton_toggled(bool checked); void onUseNativeGLFWChanged(bool checked); void onUseNativeOpenALChanged(bool checked); @@ -90,4 +92,5 @@ class InstanceSettingsPage : public QWidget, public BasePage { BaseInstance* m_instance; SettingsObjectPtr m_settings; unique_qobject_ptr checker; + bool m_world_quickplay_supported; }; diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index 9490860ae..2f496318d 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -660,7 +660,7 @@ - Set a server to join on launch + Set a target to join on launch true @@ -668,26 +668,26 @@ false - - - - - - - - 0 - 0 - - - - Server address: - - - - - - - + + + + + Server address: + + + + + + + + + + Singleplayer world + + + + + diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index f842b4b93..d8035e73e 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -168,7 +168,7 @@ class ServersModel : public QAbstractListModel { m_saveTimer.setInterval(5000); connect(&m_saveTimer, &QTimer::timeout, this, &ServersModel::save_internal); } - virtual ~ServersModel() {}; + virtual ~ServersModel() = default; void observe() { @@ -731,7 +731,7 @@ void ServersPage::on_actionMove_Down_triggered() void ServersPage::on_actionJoin_triggered() { const auto& address = m_model->at(currentServer)->m_address; - APPLICATION->launch(m_inst, true, false, std::make_shared(MinecraftServerTarget::parse(address))); + APPLICATION->launch(m_inst, true, false, std::make_shared(MinecraftTarget::parse(address, false))); } #include "ServersPage.moc" diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 4f30e4bb7..4ed5f1f73 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -82,7 +82,7 @@ class WorldListProxyModel : public QSortFilterProxyModel { } }; -WorldListPage::WorldListPage(BaseInstance* inst, std::shared_ptr worlds, QWidget* parent) +WorldListPage::WorldListPage(InstancePtr inst, std::shared_ptr worlds, QWidget* parent) : QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds) { ui->setupUi(this); @@ -113,6 +113,11 @@ void WorldListPage::openedImpl() { m_worlds->startWatching(); + auto mInst = std::dynamic_pointer_cast(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()); if (!APPLICATION->settings()->contains(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); bool hasIcon = !index.data(WorldList::IconFileRole).isNull(); ui->actionReset_Icon->setEnabled(enable && hasIcon); + + auto mInst = std::dynamic_pointer_cast(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() @@ -418,4 +431,15 @@ void WorldListPage::on_actionRefresh_triggered() 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(); + APPLICATION->launch(m_inst, true, false, std::make_shared(MinecraftTarget::parse(world->folderName(), true))); +} + #include "WorldListPage.moc" diff --git a/launcher/ui/pages/instance/WorldListPage.h b/launcher/ui/pages/instance/WorldListPage.h index 4f83002f4..84d9cd075 100644 --- a/launcher/ui/pages/instance/WorldListPage.h +++ b/launcher/ui/pages/instance/WorldListPage.h @@ -53,7 +53,7 @@ class WorldListPage : public QMainWindow, public BasePage { Q_OBJECT public: - explicit WorldListPage(BaseInstance* inst, std::shared_ptr worlds, QWidget* parent = 0); + explicit WorldListPage(InstancePtr inst, std::shared_ptr worlds, QWidget* parent = 0); virtual ~WorldListPage(); virtual QString displayName() const override { return tr("Worlds"); } @@ -72,7 +72,7 @@ class WorldListPage : public QMainWindow, public BasePage { QMenu* createPopupMenu() override; protected: - BaseInstance* m_inst; + InstancePtr m_inst; private: QModelIndex getSelectedWorld(); @@ -101,6 +101,7 @@ class WorldListPage : public QMainWindow, public BasePage { void on_actionReset_Icon_triggered(); void worldChanged(const QModelIndex& current, const QModelIndex& previous); void mceditState(LoggedProcess::State state); + void on_actionJoin_triggered(); void ShowContextMenu(const QPoint& pos); }; diff --git a/launcher/ui/pages/instance/WorldListPage.ui b/launcher/ui/pages/instance/WorldListPage.ui index d74dd0796..04344b453 100644 --- a/launcher/ui/pages/instance/WorldListPage.ui +++ b/launcher/ui/pages/instance/WorldListPage.ui @@ -81,6 +81,7 @@ + @@ -97,6 +98,11 @@ Add + + + Join + + Rename diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 4d01fb1f0..bed118465 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -321,14 +321,9 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI updateUi(); } -void ResourcePage::onVersionSelectionChanged(QString versionData) +void ResourcePage::onVersionSelectionChanged(int index) { - if (versionData.isNull() || versionData.isEmpty()) { - m_selected_version_index = -1; - return; - } - - m_selected_version_index = m_ui->versionSelectionBox->currentData().toInt(); + m_selected_version_index = index; updateSelectionButton(); } diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index d5214dd34..b625240eb 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -89,7 +89,7 @@ class ResourcePage : public QWidget, public BasePage { virtual void triggerSearch() = 0; void onSelectionChanged(QModelIndex first, QModelIndex second); - void onVersionSelectionChanged(QString data); + void onVersionSelectionChanged(int index); void onResourceSelected(); // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index 2c10dd085..decb5de3b 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -84,7 +84,7 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlamePage::onSelectionChanged); - connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlamePage::onVersionSelectionChanged); + connect(ui->versionSelectionBox, QOverload::of(&QComboBox::currentIndexChanged), this, &FlamePage::onVersionSelectionChanged); ui->packView->setItemDelegate(new ProjectItemDelegate(this)); ui->packDescription->setMetaEntry("FlamePacks"); @@ -240,17 +240,17 @@ void FlamePage::suggestCurrent() [this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); }); } -void FlamePage::onVersionSelectionChanged(QString version) +void FlamePage::onVersionSelectionChanged(int index) { bool is_blocked = false; ui->versionSelectionBox->currentData().toInt(&is_blocked); - if (version.isNull() || version.isEmpty() || is_blocked) { + if (index == -1 || is_blocked) { m_selected_version_index = -1; 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()); diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.h b/launcher/ui/pages/modplatform/flame/FlamePage.h index d35858fbc..7590e1a95 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.h +++ b/launcher/ui/pages/modplatform/flame/FlamePage.h @@ -78,7 +78,7 @@ class FlamePage : public QWidget, public BasePage { private slots: void triggerSearch(); void onSelectionChanged(QModelIndex first, QModelIndex second); - void onVersionSelectionChanged(QString data); + void onVersionSelectionChanged(int index); private: Ui::FlamePage* ui = nullptr; diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index d82c76c3a..62c22902e 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -60,7 +60,7 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) : // 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->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged); - connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged); + connect(m_ui->versionSelectionBox, QOverload::of(&QComboBox::currentIndexChanged), this, &FlameModPage::onVersionSelectionChanged); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameModPage::onResourceSelected); 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... connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameResourcePackPage::onSelectionChanged); - connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameResourcePackPage::onVersionSelectionChanged); + connect(m_ui->versionSelectionBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &FlameResourcePackPage::onVersionSelectionChanged); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameResourcePackPage::onResourceSelected); 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... connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameTexturePackPage::onSelectionChanged); - connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameTexturePackPage::onVersionSelectionChanged); + connect(m_ui->versionSelectionBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &FlameTexturePackPage::onVersionSelectionChanged); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameTexturePackPage::onResourceSelected); 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... connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameShaderPackPage::onSelectionChanged); - connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameShaderPackPage::onVersionSelectionChanged); + connect(m_ui->versionSelectionBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &FlameShaderPackPage::onVersionSelectionChanged); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameShaderPackPage::onResourceSelected); m_ui->packDescription->setMetaEntry(metaEntryBase()); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index cb811e428..03461d85a 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -85,7 +85,7 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged); - connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged); + connect(ui->versionSelectionBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ModrinthPage::onVersionSelectionChanged); ui->packView->setItemDelegate(new ProjectItemDelegate(this)); ui->packDescription->setMetaEntry(metaEntryBase()); @@ -342,9 +342,9 @@ void ModrinthPage::triggerSearch() 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 = ""; return; } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index 4240dcafb..dadaeb0a0 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -80,7 +80,7 @@ class ModrinthPage : public QWidget, public BasePage { private slots: void onSelectionChanged(QModelIndex first, QModelIndex second); - void onVersionSelectionChanged(QString data); + void onVersionSelectionChanged(int index); void triggerSearch(); private: diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 26fe46a54..85dcde471 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -58,7 +58,8 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instan // 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->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged); - connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged); + connect(m_ui->versionSelectionBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &ModrinthModPage::onVersionSelectionChanged); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onResourceSelected); 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... connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthResourcePackPage::onSelectionChanged); - connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthResourcePackPage::onVersionSelectionChanged); + connect(m_ui->versionSelectionBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &ModrinthResourcePackPage::onVersionSelectionChanged); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthResourcePackPage::onResourceSelected); 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... connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthTexturePackPage::onSelectionChanged); - connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthTexturePackPage::onVersionSelectionChanged); + connect(m_ui->versionSelectionBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &ModrinthTexturePackPage::onVersionSelectionChanged); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthTexturePackPage::onResourceSelected); 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... connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthShaderPackPage::onSelectionChanged); - connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthShaderPackPage::onVersionSelectionChanged); + connect(m_ui->versionSelectionBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &ModrinthShaderPackPage::onVersionSelectionChanged); connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthShaderPackPage::onResourceSelected); m_ui->packDescription->setMetaEntry(metaEntryBase()); diff --git a/launcher/ui/widgets/WideBar.cpp b/launcher/ui/widgets/WideBar.cpp index 46caaaef2..2940d7ce7 100644 --- a/launcher/ui/widgets/WideBar.cpp +++ b/launcher/ui/widgets/WideBar.cpp @@ -309,4 +309,15 @@ bool WideBar::checkHash(QByteArray const& old_hash) const 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" diff --git a/launcher/ui/widgets/WideBar.h b/launcher/ui/widgets/WideBar.h index c47f3a596..f4877a89a 100644 --- a/launcher/ui/widgets/WideBar.h +++ b/launcher/ui/widgets/WideBar.h @@ -38,6 +38,8 @@ class WideBar : public QToolBar { [[nodiscard]] QByteArray getVisibilityState() const; void setVisibilityState(QByteArray&&); + void removeAction(QAction* action); + private: struct BarEntry { enum class Type { None, Action, Separator, Spacer } type = Type::None; diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/AbstractLauncher.java b/libraries/launcher/org/prismlauncher/launcher/impl/AbstractLauncher.java index de28a0401..a5f027ba6 100644 --- a/libraries/launcher/org/prismlauncher/launcher/impl/AbstractLauncher.java +++ b/libraries/launcher/org/prismlauncher/launcher/impl/AbstractLauncher.java @@ -70,7 +70,7 @@ public abstract class AbstractLauncher implements Launcher { // secondary parameters protected final int width, height; protected final boolean maximize; - protected final String serverAddress, serverPort; + protected final String serverAddress, serverPort, worldName; protected final String mainClassName; @@ -80,6 +80,7 @@ public abstract class AbstractLauncher implements Launcher { serverAddress = params.getString("serverAddress", null); serverPort = params.getString("serverPort", null); + worldName = params.getString("worldName", null); String windowParams = params.getString("windowParams", null); diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java index 49e5d518f..dc518be64 100644 --- a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java +++ b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java @@ -62,13 +62,15 @@ import java.util.Collections; import java.util.List; public final class StandardLauncher extends AbstractLauncher { - private final boolean quickPlaySupported; + private final boolean quickPlayMultiplayerSupported; + private final boolean quickPlaySingleplayerSupported; public StandardLauncher(Parameters params) { super(params); List traits = params.getList("traits", Collections.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 @@ -83,7 +85,7 @@ public final class StandardLauncher extends AbstractLauncher { } if (serverAddress != null) { - if (quickPlaySupported) { + if (quickPlayMultiplayerSupported) { // as of 23w14a gameArgs.add("--quickPlayMultiplayer"); gameArgs.add(serverAddress + ':' + serverPort); @@ -93,6 +95,9 @@ public final class StandardLauncher extends AbstractLauncher { gameArgs.add("--port"); gameArgs.add(serverPort); } + } else if (worldName != null && quickPlaySingleplayerSupported) { + gameArgs.add("--quickPlaySingleplayer"); + gameArgs.add(worldName); } // find and invoke the main method diff --git a/nix/distribution.nix b/nix/distribution.nix index d9c6784a1..28ef7ced1 100644 --- a/nix/distribution.nix +++ b/nix/distribution.nix @@ -24,9 +24,8 @@ overlays.default = final: prev: let version = builtins.substring 0 8 self.lastModifiedDate or "dirty"; in { - prismlauncher-unwrapped = prev.qt6Packages.callPackage ./pkg { + prismlauncher-unwrapped = prev.callPackage ./pkg { inherit (inputs) libnbtplusplus; - inherit ((final.darwin or prev.darwin).apple_sdk.frameworks) Cocoa; inherit version; }; diff --git a/nix/pkg/default.nix b/nix/pkg/default.nix index 1a3d8224f..f3ff3789c 100644 --- a/nix/pkg/default.nix +++ b/nix/pkg/default.nix @@ -1,27 +1,27 @@ { lib, stdenv, - canonicalize-jars-hook, cmake, cmark, - Cocoa, - ninja, - jdk17, - zlib, - qtbase, - qtnetworkauth, - quazip, + darwin, extra-cmake-modules, - tomlplusplus, - ghc_filesystem, gamemode, + ghc_filesystem, + jdk17, + kdePackages, + ninja, + stripJavaArchivesHook, + tomlplusplus, + zlib, msaClientID ? null, gamemodeSupport ? stdenv.isLinux, version, libnbtplusplus, }: -assert lib.assertMsg (stdenv.isLinux || !gamemodeSupport) "gamemodeSupport is only available on Linux"; - stdenv.mkDerivation rec { +assert lib.assertMsg ( + gamemodeSupport -> stdenv.isLinux +) "gamemodeSupport is only available on Linux."; + stdenv.mkDerivation { pname = "prismlauncher-unwrapped"; 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 = '' rm -rf 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; - meta = with lib; { - mainProgram = "prismlauncher"; - homepage = "https://prismlauncher.org/"; - description = "A free, open source launcher for Minecraft"; + meta = { + description = "Free, open source launcher for Minecraft"; longDescription = '' 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 associated options with a simple interface. ''; - platforms = with platforms; linux ++ darwin; - changelog = "https://github.com/PrismLauncher/PrismLauncher/releases/tag/${version}"; - license = licenses.gpl3Only; - maintainers = with maintainers; [minion3665 Scrumplex getchoo]; + homepage = "https://prismlauncher.org/"; + license = lib.licenses.gpl3Only; + maintainers = with lib.maintainers; [ + Scrumplex + getchoo + ]; + mainProgram = "prismlauncher"; + platforms = lib.platforms.linux ++ lib.platforms.darwin; }; } diff --git a/nix/pkg/wrapper.nix b/nix/pkg/wrapper.nix index 8c9c1cabc..e7516397e 100644 --- a/nix/pkg/wrapper.nix +++ b/nix/pkg/wrapper.nix @@ -3,94 +3,143 @@ stdenv, symlinkJoin, prismlauncher-unwrapped, - wrapQtAppsHook, addOpenGLRunpath, - qtbase, # needed for wrapQtAppsHook - qtsvg, - qtwayland, - xorg, - libpulseaudio, - libGL, + flite, + gamemode, glfw, - openal, + glfw-wayland-minecraft, + glxinfo, jdk8, jdk17, jdk21, - gamemode, - flite, - glxinfo, - udev, + kdePackages, + libGL, + libpulseaudio, libusb1, - msaClientID ? null, - gamemodeSupport ? stdenv.isLinux, - textToSpeechSupport ? stdenv.isLinux, - controllerSupport ? stdenv.isLinux, - jdks ? [jdk21 jdk17 jdk8], + makeWrapper, + openal, + pciutils, + udev, + vulkan-loader, + xorg, additionalLibs ? [], additionalPrograms ? [], -}: let - prismlauncherFinal = prismlauncher-unwrapped.override { - inherit msaClientID gamemodeSupport; - }; + controllerSupport ? stdenv.isLinux, + gamemodeSupport ? stdenv.isLinux, + 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 symlinkJoin { - name = "prismlauncher-${prismlauncherFinal.version}"; + name = "prismlauncher-${prismlauncher'.version}"; - paths = [prismlauncherFinal]; + paths = [prismlauncher']; - nativeBuildInputs = [ - wrapQtAppsHook - ]; + nativeBuildInputs = + [kdePackages.wrapQtAppsHook] + # purposefully using a shell wrapper here for variable expansion + # see https://github.com/NixOS/nixpkgs/issues/172583 + ++ lib.optional withWaylandGLFW makeWrapper; buildInputs = [ - qtbase - qtsvg + kdePackages.qtbase + 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 = '' - wrapQtAppsHook - ''; + env = { + 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 runtimeLibs = - (with xorg; [ - libX11 - libXext - libXcursor - libXrandr - libXxf86vm - ]) - ++ [ + [ # lwjgl + glfw libpulseaudio libGL - glfw openal stdenv.cc.cc.lib - # oshi - udev + vulkan-loader # VulkanMod's lwjgl + + udev # oshi + + xorg.libX11 + xorg.libXext + xorg.libXcursor + xorg.libXrandr + xorg.libXxf86vm ] - ++ lib.optional gamemodeSupport gamemode.lib ++ lib.optional textToSpeechSupport flite + ++ lib.optional gamemodeSupport gamemode.lib ++ lib.optional controllerSupport libusb1 ++ additionalLibs; runtimePrograms = [ - xorg.xrandr glxinfo + pciutils # need lspci + xorg.xrandr # needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 ] ++ additionalPrograms; in ["--prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks}"] ++ lib.optionals stdenv.isLinux [ "--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}" ]; - inherit (prismlauncherFinal) meta; + meta = { + inherit + (prismlauncher'.meta) + description + longDescription + homepage + changelog + license + maintainers + mainProgram + platforms + ; + }; }