From d3f337d6ef3f6e824d13a7ab8d160b42c59228f8 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 1 Jun 2025 08:13:10 +0800 Subject: [PATCH 1/9] Delete shortcut when deleting instances Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 11 ++++ launcher/BaseInstance.h | 17 +++++ launcher/FileSystem.cpp | 32 ++++----- launcher/FileSystem.h | 3 +- launcher/InstanceList.cpp | 69 +++++++++++++++++--- launcher/InstanceList.h | 10 ++- launcher/minecraft/ShortcutUtils.cpp | 59 ++++++++++------- launcher/minecraft/ShortcutUtils.h | 10 +-- launcher/ui/MainWindow.cpp | 7 +- launcher/ui/dialogs/CreateShortcutDialog.cpp | 14 ++-- launcher/ui/dialogs/CreateShortcutDialog.h | 3 - 11 files changed, 168 insertions(+), 67 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 70e0f9dc1..1aa01568c 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -398,6 +398,17 @@ bool BaseInstance::syncInstanceDirName(const QString& newRoot) const return oldRoot == newRoot || QFile::rename(oldRoot, newRoot); } +void BaseInstance::registerShortcut(const ShortcutData& data) +{ + m_shortcuts.append(data); + qDebug() << "Registering shortcut for instance" << id() << "with name" << data.name << "and path" << data.filePath; +} + +QList& BaseInstance::getShortcuts() +{ + return m_shortcuts; +} + QString BaseInstance::name() const { return m_settings->get("name").toString(); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 1acf1afe0..d2ff64e7e 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -66,6 +67,16 @@ class BaseInstance; // pointer for lazy people using InstancePtr = std::shared_ptr; +/// Shortcut saving target representations +enum class ShortcutTarget { Desktop, Applications, Other }; + +/// Shortcut data representation +struct ShortcutData { + QString name; + QString filePath; + ShortcutTarget target; +}; + /*! * \brief Base class for instances. * This class implements many functions that are common between instances and @@ -129,6 +140,10 @@ class BaseInstance : public QObject, public std::enable_shared_from_this& getShortcuts(); + /// Value used for instance window titles QString windowTitle() const; @@ -308,6 +323,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this m_shortcuts; }; Q_DECLARE_METATYPE(shared_qobject_ptr) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 9d45f4af3..c5386a43b 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -898,26 +898,26 @@ QString getApplicationsDir() } // Cross-platform Shortcut creation -bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon) +QString createShortcut(QString destination, QString target, QStringList args, QString name, QString icon) { if (destination.isEmpty()) { destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name)); } if (!ensureFilePathExists(destination)) { qWarning() << "Destination path can't be created!"; - return false; + return ""; } #if defined(Q_OS_MACOS) QDir application = destination + ".app/"; if (application.exists()) { qWarning() << "Application already exists!"; - return false; + return ""; } if (!application.mkpath(".")) { qWarning() << "Couldn't create application"; - return false; + return ""; } QDir content = application.path() + "/Contents/"; @@ -927,7 +927,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri if (!(content.mkpath(".") && resources.mkpath(".") && binaryDir.mkpath("."))) { qWarning() << "Couldn't create directories within application"; - return false; + return ""; } info.open(QIODevice::WriteOnly | QIODevice::Text); @@ -976,7 +976,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri "\n" ""; - return true; + return application.path(); #elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) if (!destination.endsWith(".desktop")) // in case of isFlatpak destination is already populated destination += ".desktop"; @@ -1002,32 +1002,32 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther); - return true; + return destination; #elif defined(Q_OS_WIN) QFileInfo targetInfo(target); if (!targetInfo.exists()) { qWarning() << "Target file does not exist!"; - return false; + return ""; } target = targetInfo.absoluteFilePath(); if (target.length() >= MAX_PATH) { qWarning() << "Target file path is too long!"; - return false; + return ""; } if (!icon.isEmpty() && icon.length() >= MAX_PATH) { qWarning() << "Icon path is too long!"; - return false; + return ""; } destination += ".lnk"; if (destination.length() >= MAX_PATH) { qWarning() << "Destination path is too long!"; - return false; + return ""; } QString argStr; @@ -1046,7 +1046,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri if (argStr.length() >= MAX_PATH) { qWarning() << "Arguments string is too long!"; - return false; + return ""; } HRESULT hres; @@ -1055,7 +1055,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri hres = CoInitialize(nullptr); if (FAILED(hres)) { qWarning() << "Failed to initialize COM!"; - return false; + return ""; } WCHAR wsz[MAX_PATH]; @@ -1109,10 +1109,12 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri // go away COM, nobody likes you CoUninitialize(); - return SUCCEEDED(hres); + if (SUCCEEDED(hres)) + return destination; + return ""; #else qWarning("Desktop Shortcuts not supported on your platform!"); - return false; + return ""; #endif } diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index b1108eded..83cf41d7f 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -362,8 +362,9 @@ bool overrideFolder(QString overwritten_path, QString override_path); /** * Creates a shortcut to the specified target file at the specified destination path. + * Returns empty string if creation failed; otherwise returns the path to the created shortcut. */ -bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon); +QString createShortcut(QString destination, QString target, QStringList args, QString name, QString icon); enum class FilesystemType { FAT, diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 89e7dc04d..ef8be4919 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -60,6 +60,7 @@ #include "NullInstance.h" #include "WatchLock.h" #include "minecraft/MinecraftInstance.h" +#include "minecraft/ShortcutUtils.h" #include "settings/INISettingsObject.h" #ifdef Q_OS_WIN32 @@ -333,7 +334,7 @@ bool InstanceList::trashInstance(const InstanceId& id) { auto inst = getInstanceById(id); if (!inst) { - qDebug() << "Cannot trash instance" << id << ". No such instance is present (deleted externally?)."; + qWarning() << "Cannot trash instance" << id << ". No such instance is present (deleted externally?)."; return false; } @@ -348,26 +349,48 @@ bool InstanceList::trashInstance(const InstanceId& id) } if (!FS::trash(inst->instanceRoot(), &trashedLoc)) { - qDebug() << "Trash of instance" << id << "has not been completely successfully..."; + qWarning() << "Trash of instance" << id << "has not been completely successful..."; return false; } qDebug() << "Instance" << id << "has been trashed by the launcher."; m_trashHistory.push({ id, inst->instanceRoot(), trashedLoc, cachedGroupId }); + // Also trash all of its shortcuts; we remove the shortcuts if trash fails since it is invalid anyway + auto& shortcuts = inst->getShortcuts(); + for (auto it = shortcuts.begin(); it != shortcuts.end();) { + const auto& [name, filePath, target] = *it; + if (!FS::trash(filePath, &trashedLoc)) { + qWarning() << "Trash of shortcut" << name << "at path" << filePath << "for instance" << id + << "has not been successful, trying to delete it instead..."; + if (!FS::deletePath(filePath)) { + qWarning() << "Deletion of shortcut" << name << "at path" << filePath << "for instance" << id + << "has not been successful, given up..."; + ++it; + } else { + qDebug() << "Shortcut" << name << "at path" << filePath << "for instance" << id << "has been deleted by the launcher."; + it = shortcuts.erase(it); + } + continue; + } + qDebug() << "Shortcut" << name << "at path" << filePath << "for instance" << id << "has been trashed by the launcher."; + m_trashHistory.top().shortcuts.append({ *it, trashedLoc }); + it = shortcuts.erase(it); + } + return true; } -bool InstanceList::trashedSomething() +bool InstanceList::trashedSomething() const { return !m_trashHistory.empty(); } -void InstanceList::undoTrashInstance() +bool InstanceList::undoTrashInstance() { if (m_trashHistory.empty()) { qWarning() << "Nothing to recover from trash."; - return; + return true; } auto top = m_trashHistory.pop(); @@ -377,21 +400,41 @@ void InstanceList::undoTrashInstance() top.path += "1"; } + if (!QFile(top.trashPath).rename(top.path)) { + qWarning() << "Moving" << top.trashPath << "back to" << top.path << "failed!"; + return false; + } qDebug() << "Moving" << top.trashPath << "back to" << top.path; - QFile(top.trashPath).rename(top.path); + + bool ok = true; + for (const auto& [data, trashPath] : top.shortcuts) { + if (QDir(data.filePath).exists()) { + // Don't try to append 1 here as the shortcut may have suffixes like .app, just warn and skip it + qWarning() << "Shortcut" << trashPath << "original directory" << data.filePath << "already exists!"; + ok = false; + continue; + } + if (!QFile(trashPath).rename(data.filePath)) { + qWarning() << "Moving shortcut from" << trashPath << "back to" << data.filePath << "failed!"; + ok = false; + continue; + } + qDebug() << "Moving shortcut from" << trashPath << "back to" << data.filePath; + } m_instanceGroupIndex[top.id] = top.groupName; increaseGroupCount(top.groupName); saveGroupList(); emit instancesChanged(); + return ok; } void InstanceList::deleteInstance(const InstanceId& id) { auto inst = getInstanceById(id); if (!inst) { - qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?)."; + qWarning() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?)."; return; } @@ -404,11 +447,19 @@ void InstanceList::deleteInstance(const InstanceId& id) qDebug() << "Will delete instance" << id; if (!FS::deletePath(inst->instanceRoot())) { - qWarning() << "Deletion of instance" << id << "has not been completely successful ..."; + qWarning() << "Deletion of instance" << id << "has not been completely successful..."; return; } qDebug() << "Instance" << id << "has been deleted by the launcher."; + + for (const auto& [name, filePath, target] : inst->getShortcuts()) { + if (!FS::deletePath(filePath)) { + qWarning() << "Deletion of shortcut" << name << "at path" << filePath << "for instance" << id << "has not been successful..."; + continue; + } + qDebug() << "Shortcut" << name << "at path" << filePath << "for instance" << id << "has been deleted by the launcher."; + } } static QMap getIdMapping(const QList& list) @@ -638,7 +689,7 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id) } else { inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot)); } - qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot(); + qDebug() << "Loaded instance" << inst->name() << "from" << inst->instanceRoot(); return inst; } diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index c85fe55c7..fc4fa9a39 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -56,11 +56,17 @@ enum class InstCreateError { NoCreateError = 0, NoSuchVersion, UnknownCreateErro enum class GroupsState { NotLoaded, Steady, Dirty }; +struct TrashShortcutItem { + ShortcutData data; + QString trashPath; +}; + struct TrashHistoryItem { QString id; QString path; QString trashPath; QString groupName; + QList shortcuts; }; class InstanceList : public QAbstractListModel { @@ -111,8 +117,8 @@ class InstanceList : public QAbstractListModel { void deleteGroup(const GroupId& name); void renameGroup(const GroupId& src, const GroupId& dst); bool trashInstance(const InstanceId& id); - bool trashedSomething(); - void undoTrashInstance(); + bool trashedSomething() const; + bool undoTrashInstance(); void deleteInstance(const InstanceId& id); // Wrap an instance creation task in some more task machinery and make it ready to be used diff --git a/launcher/minecraft/ShortcutUtils.cpp b/launcher/minecraft/ShortcutUtils.cpp index 43954aa6a..0336a9512 100644 --- a/launcher/minecraft/ShortcutUtils.cpp +++ b/launcher/minecraft/ShortcutUtils.cpp @@ -48,10 +48,10 @@ namespace ShortcutUtils { -void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) +bool createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) { if (!shortcut.instance) - return; + return false; QString appPath = QApplication::applicationFilePath(); auto icon = APPLICATION->icons()->icon(shortcut.iconKey.isEmpty() ? shortcut.instance->iconKey() : shortcut.iconKey); @@ -64,7 +64,7 @@ void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) if (appPath.startsWith("/private/var/")) { QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts.")); - return; + return false; } iconPath = FS::PathCombine(shortcut.instance->instanceRoot(), "Icon.icns"); @@ -72,7 +72,7 @@ void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) QFile iconFile(iconPath); if (!iconFile.open(QFile::WriteOnly)) { QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for application.")); - return; + return false; } QIcon iconObj = icon->icon(); @@ -82,7 +82,7 @@ void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) if (!success) { iconFile.remove(); QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for application.")); - return; + return false; } #elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) if (appPath.startsWith("/tmp/.mount_")) { @@ -102,7 +102,7 @@ void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) QFile iconFile(iconPath); if (!iconFile.open(QFile::WriteOnly)) { QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); - return; + return false; } bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG"); iconFile.close(); @@ -110,7 +110,7 @@ void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) if (!success) { iconFile.remove(); QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); - return; + return false; } if (DesktopServices::isFlatpak()) { @@ -128,7 +128,7 @@ void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) QFile iconFile(iconPath); if (!iconFile.open(QFile::WriteOnly)) { QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); - return; + return false; } bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO"); iconFile.close(); @@ -139,51 +139,58 @@ void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) if (!success) { iconFile.remove(); QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); - return; + return false; } #else QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Not supported on your platform!")); - return; + return false; #endif args.append({ "--launch", shortcut.instance->id() }); args.append(shortcut.extraArgs); - if (!FS::createShortcut(filePath, appPath, args, shortcut.name, iconPath)) { + QString shortcutPath = FS::createShortcut(filePath, appPath, args, shortcut.name, iconPath); + if (shortcutPath.isEmpty()) { #if not defined(Q_OS_MACOS) iconFile.remove(); #endif QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create %1 shortcut!").arg(shortcut.targetString)); + return false; } + + shortcut.instance->registerShortcut({ shortcut.name, shortcutPath, shortcut.target }); + return true; } -void createInstanceShortcutOnDesktop(const Shortcut& shortcut) +bool createInstanceShortcutOnDesktop(const Shortcut& shortcut) { if (!shortcut.instance) - return; + return false; QString desktopDir = FS::getDesktopDir(); if (desktopDir.isEmpty()) { QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Couldn't find desktop?!")); - return; + return false; } QString shortcutFilePath = FS::PathCombine(desktopDir, FS::RemoveInvalidFilenameChars(shortcut.name)); - createInstanceShortcut(shortcut, shortcutFilePath); + if (!createInstanceShortcut(shortcut, shortcutFilePath)) + return false; QMessageBox::information(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Created a shortcut to this %1 on your desktop!").arg(shortcut.targetString)); + return true; } -void createInstanceShortcutInApplications(const Shortcut& shortcut) +bool createInstanceShortcutInApplications(const Shortcut& shortcut) { if (!shortcut.instance) - return; + return false; QString applicationsDir = FS::getApplicationsDir(); if (applicationsDir.isEmpty()) { QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Couldn't find applications folder?!")); - return; + return false; } #if defined(Q_OS_MACOS) || defined(Q_OS_WIN) @@ -193,20 +200,22 @@ void createInstanceShortcutInApplications(const Shortcut& shortcut) if (!applicationsDirQ.mkpath(".")) { QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create instances folder in applications folder!")); - return; + return false; } #endif QString shortcutFilePath = FS::PathCombine(applicationsDir, FS::RemoveInvalidFilenameChars(shortcut.name)); - createInstanceShortcut(shortcut, shortcutFilePath); + if (!createInstanceShortcut(shortcut, shortcutFilePath)) + return false; QMessageBox::information(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Created a shortcut to this %1 in your applications folder!").arg(shortcut.targetString)); + return true; } -void createInstanceShortcutInOther(const Shortcut& shortcut) +bool createInstanceShortcutInOther(const Shortcut& shortcut) { if (!shortcut.instance) - return; + return false; QString defaultedDir = FS::getDesktopDir(); #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) @@ -225,13 +234,15 @@ void createInstanceShortcutInOther(const Shortcut& shortcut) shortcutFilePath = fileDialog.getSaveFileName(shortcut.parent, QObject::tr("Create Shortcut"), shortcutFilePath, QObject::tr("Desktop Entries") + " (*" + extension + ")"); if (shortcutFilePath.isEmpty()) - return; // file dialog canceled by user + return false; // file dialog canceled by user if (shortcutFilePath.endsWith(extension)) shortcutFilePath = shortcutFilePath.mid(0, shortcutFilePath.length() - extension.length()); - createInstanceShortcut(shortcut, shortcutFilePath); + if (!createInstanceShortcut(shortcut, shortcutFilePath)) + return false; QMessageBox::information(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Created a shortcut to this %1!").arg(shortcut.targetString)); + return true; } } // namespace ShortcutUtils diff --git a/launcher/minecraft/ShortcutUtils.h b/launcher/minecraft/ShortcutUtils.h index e3d2e283a..b995c36bd 100644 --- a/launcher/minecraft/ShortcutUtils.h +++ b/launcher/minecraft/ShortcutUtils.h @@ -38,6 +38,7 @@ #pragma once #include "Application.h" +#include #include namespace ShortcutUtils { @@ -49,18 +50,19 @@ struct Shortcut { QWidget* parent = nullptr; QStringList extraArgs = {}; QString iconKey = ""; + ShortcutTarget target; }; /// Create an instance shortcut on the specified file path -void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath); +bool createInstanceShortcut(const Shortcut& shortcut, const QString& filePath); /// Create an instance shortcut on the desktop -void createInstanceShortcutOnDesktop(const Shortcut& shortcut); +bool createInstanceShortcutOnDesktop(const Shortcut& shortcut); /// Create an instance shortcut in the Applications directory -void createInstanceShortcutInApplications(const Shortcut& shortcut); +bool createInstanceShortcutInApplications(const Shortcut& shortcut); /// Create an instance shortcut in other directories -void createInstanceShortcutInOther(const Shortcut& shortcut); +bool createInstanceShortcutInOther(const Shortcut& shortcut); } // namespace ShortcutUtils diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 63fcc3c9b..4f38a53b3 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1207,7 +1207,10 @@ void MainWindow::renameGroup(QString group) void MainWindow::undoTrashInstance() { - APPLICATION->instances()->undoTrashInstance(); + if (!APPLICATION->instances()->undoTrashInstance()) + QMessageBox::warning( + this, tr("Failed to undo trashing instance"), + tr("Some instances and shortcuts could not be restored.\nPlease check your trashbin to manually restore them.")); ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); } @@ -1407,7 +1410,7 @@ void MainWindow::on_actionDeleteInstance_triggered() auto id = m_selectedInstance->id(); auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"), - tr("You are about to delete \"%1\".\n" + tr("You are about to delete \"%1\" and all of its shortcut(s).\n" "This may be permanent and will completely delete the instance.\n\n" "Are you sure?") .arg(m_selectedInstance->name()), diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp index 278573a22..5cfe33c7f 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.cpp +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -83,12 +83,12 @@ CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent QString applicationDir = FS::getApplicationsDir(); if (!desktopDir.isEmpty()) - ui->saveTargetSelectionBox->addItem(tr("Desktop"), QVariant::fromValue(SaveTarget::Desktop)); + ui->saveTargetSelectionBox->addItem(tr("Desktop"), QVariant::fromValue(ShortcutTarget::Desktop)); if (!applicationDir.isEmpty()) - ui->saveTargetSelectionBox->addItem(tr("Applications"), QVariant::fromValue(SaveTarget::Applications)); + ui->saveTargetSelectionBox->addItem(tr("Applications"), QVariant::fromValue(ShortcutTarget::Applications)); } - ui->saveTargetSelectionBox->addItem(tr("Other..."), QVariant::fromValue(SaveTarget::Other)); + ui->saveTargetSelectionBox->addItem(tr("Other..."), QVariant::fromValue(ShortcutTarget::Other)); // Populate worlds if (m_QuickJoinSupported) { @@ -206,17 +206,17 @@ void CreateShortcutDialog::createShortcut() } } - auto target = ui->saveTargetSelectionBox->currentData().value(); + auto target = ui->saveTargetSelectionBox->currentData().value(); auto name = ui->instNameTextBox->text(); if (name.isEmpty()) name = ui->instNameTextBox->placeholderText(); if (ui->overrideAccountCheckbox->isChecked()) extraArgs.append({ "--profile", ui->accountSelectionBox->currentData().toString() }); - ShortcutUtils::Shortcut args{ m_instance.get(), name, targetString, this, extraArgs, InstIconKey }; - if (target == SaveTarget::Desktop) + ShortcutUtils::Shortcut args{ m_instance.get(), name, targetString, this, extraArgs, InstIconKey, target }; + if (target == ShortcutTarget::Desktop) ShortcutUtils::createInstanceShortcutOnDesktop(args); - else if (target == SaveTarget::Applications) + else if (target == ShortcutTarget::Applications) ShortcutUtils::createInstanceShortcutInApplications(args); else ShortcutUtils::createInstanceShortcutInOther(args); diff --git a/launcher/ui/dialogs/CreateShortcutDialog.h b/launcher/ui/dialogs/CreateShortcutDialog.h index cfedbf017..29e68a787 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.h +++ b/launcher/ui/dialogs/CreateShortcutDialog.h @@ -54,9 +54,6 @@ class CreateShortcutDialog : public QDialog { InstancePtr m_instance; bool m_QuickJoinSupported = false; - // Index representations - enum class SaveTarget { Desktop, Applications, Other }; - // Functions void stateChanged(); }; From 7c3a810d3d6bda55f002bf9224e4df945b9d86e4 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 1 Jun 2025 14:16:40 +0800 Subject: [PATCH 2/9] Implement persistence by storing shortcut in settings Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 15 ++++++++++--- launcher/BaseInstance.h | 20 +++++++++++++---- launcher/InstanceList.cpp | 28 +++++++++++++++++------- launcher/minecraft/MinecraftInstance.cpp | 4 ++++ 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 1aa01568c..2187bc09a 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -69,6 +69,7 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s m_settings->registerSetting("lastTimePlayed", 0); m_settings->registerSetting("linkedInstances", "[]"); + m_settings->registerSetting("shortcuts", QVariant::fromValue(QList{})); // Game time override auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); @@ -400,13 +401,21 @@ bool BaseInstance::syncInstanceDirName(const QString& newRoot) const void BaseInstance::registerShortcut(const ShortcutData& data) { - m_shortcuts.append(data); + auto currentShortcuts = shortcuts(); + currentShortcuts.append(data); qDebug() << "Registering shortcut for instance" << id() << "with name" << data.name << "and path" << data.filePath; + setShortcuts(currentShortcuts); } -QList& BaseInstance::getShortcuts() +void BaseInstance::setShortcuts(const QList& shortcuts) { - return m_shortcuts; + // FIXME: if no change, do not set. setting involves saving a file. + m_settings->set("shortcuts", QVariant::fromValue(shortcuts)); +} + +QList BaseInstance::shortcuts() const +{ + return m_settings->get("shortcuts").value>(); } QString BaseInstance::name() const diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index d2ff64e7e..52aa39067 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -38,6 +38,7 @@ #pragma once #include +#include #include #include #include @@ -74,7 +75,18 @@ enum class ShortcutTarget { Desktop, Applications, Other }; struct ShortcutData { QString name; QString filePath; - ShortcutTarget target; + ShortcutTarget target = ShortcutTarget::Other; + + friend QDataStream& operator<<(QDataStream& out, const ShortcutData& data) + { + out << data.name << data.filePath << data.target; + return out; + } + friend QDataStream& operator>>(QDataStream& in, ShortcutData& data) + { + in >> data.name >> data.filePath >> data.target; + return in; + } }; /*! @@ -142,7 +154,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this& getShortcuts(); + QList shortcuts() const; + void setShortcuts(const QList& shortcuts); /// Value used for instance window titles QString windowTitle() const; @@ -323,10 +336,9 @@ class BaseInstance : public QObject, public std::enable_shared_from_this m_shortcuts; }; Q_DECLARE_METATYPE(shared_qobject_ptr) +Q_DECLARE_METATYPE(ShortcutData) // Q_DECLARE_METATYPE(BaseInstance::InstanceFlag) // Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index ef8be4919..b98f51d82 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -357,25 +357,20 @@ bool InstanceList::trashInstance(const InstanceId& id) m_trashHistory.push({ id, inst->instanceRoot(), trashedLoc, cachedGroupId }); // Also trash all of its shortcuts; we remove the shortcuts if trash fails since it is invalid anyway - auto& shortcuts = inst->getShortcuts(); - for (auto it = shortcuts.begin(); it != shortcuts.end();) { - const auto& [name, filePath, target] = *it; + for (const auto& [name, filePath, target] : inst->shortcuts()) { if (!FS::trash(filePath, &trashedLoc)) { qWarning() << "Trash of shortcut" << name << "at path" << filePath << "for instance" << id << "has not been successful, trying to delete it instead..."; if (!FS::deletePath(filePath)) { qWarning() << "Deletion of shortcut" << name << "at path" << filePath << "for instance" << id << "has not been successful, given up..."; - ++it; } else { qDebug() << "Shortcut" << name << "at path" << filePath << "for instance" << id << "has been deleted by the launcher."; - it = shortcuts.erase(it); } continue; } qDebug() << "Shortcut" << name << "at path" << filePath << "for instance" << id << "has been trashed by the launcher."; - m_trashHistory.top().shortcuts.append({ *it, trashedLoc }); - it = shortcuts.erase(it); + m_trashHistory.top().shortcuts.append({ { name, filePath, target }, trashedLoc }); } return true; @@ -453,7 +448,7 @@ void InstanceList::deleteInstance(const InstanceId& id) qDebug() << "Instance" << id << "has been deleted by the launcher."; - for (const auto& [name, filePath, target] : inst->getShortcuts()) { + for (const auto& [name, filePath, target] : inst->shortcuts()) { if (!FS::deletePath(filePath)) { qWarning() << "Deletion of shortcut" << name << "at path" << filePath << "for instance" << id << "has not been successful..."; continue; @@ -675,6 +670,7 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id) } auto instanceRoot = FS::PathCombine(m_instDir, id); + qRegisterMetaType>("QList"); auto instanceSettings = std::make_shared(FS::PathCombine(instanceRoot, "instance.cfg")); InstancePtr inst; @@ -690,6 +686,22 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id) inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot)); } qDebug() << "Loaded instance" << inst->name() << "from" << inst->instanceRoot(); + + // Fixup the shortcuts by pruning all non-existing links + auto shortcut = inst->shortcuts(); + for (auto it = shortcut.begin(); it != shortcut.end();) { + const auto& [name, filePath, target] = *it; + if (!QDir(filePath).exists()) { + qWarning() << "Shortcut" << name << "have non-existent path" << filePath; + it = shortcut.erase(it); + continue; + } + ++it; + } + inst->setShortcuts(shortcut); + if (!shortcut.isEmpty()) + qDebug() << "Loaded" << shortcut.size() << "shortcut(s) for instance" << inst->name(); + return inst; } diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 635cecfac..72c863bdb 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1040,6 +1040,10 @@ QString MinecraftInstance::getStatusbarDescription() .arg(Time::prettifyDuration(totalTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool()))); } } + auto currentShortcuts = shortcuts(); + if (!currentShortcuts.isEmpty()) { + description.append(tr(", %n shortcut(s) registered", "", currentShortcuts.size())); + } if (hasCrashed()) { description.append(tr(", has crashed.")); } From d2ee023788341237dc6c195ab5facdf1b031315b Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 1 Jun 2025 16:13:16 +0800 Subject: [PATCH 3/9] Switch to JSON-encoded store Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 20 +++++++++++++++++--- launcher/BaseInstance.h | 12 ------------ launcher/InstanceList.cpp | 1 - 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 2187bc09a..8a6060948 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -69,7 +69,7 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s m_settings->registerSetting("lastTimePlayed", 0); m_settings->registerSetting("linkedInstances", "[]"); - m_settings->registerSetting("shortcuts", QVariant::fromValue(QList{})); + m_settings->registerSetting("shortcuts", QVariant::fromValue(QByteArray{})); // Game time override auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); @@ -410,12 +410,26 @@ void BaseInstance::registerShortcut(const ShortcutData& data) void BaseInstance::setShortcuts(const QList& shortcuts) { // FIXME: if no change, do not set. setting involves saving a file. - m_settings->set("shortcuts", QVariant::fromValue(shortcuts)); + QJsonArray array; + for (const auto& elem : shortcuts) { + array.append(QJsonObject{ { "name", elem.name }, { "filePath", elem.filePath }, { "target", static_cast(elem.target) } }); + } + + QJsonDocument document; + document.setArray(array); + m_settings->set("shortcuts", QVariant::fromValue(document.toJson(QJsonDocument::Compact))); } QList BaseInstance::shortcuts() const { - return m_settings->get("shortcuts").value>(); + auto data = m_settings->get("shortcuts").value(); + auto document = QJsonDocument::fromJson(data); + QList results; + for (const auto& elem : document.array()) { + auto dict = elem.toObject(); + results.append({ dict["name"].toString(), dict["filePath"].toString(), static_cast(dict["target"].toInt()) }); + } + return results; } QString BaseInstance::name() const diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 52aa39067..3509c0155 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -76,17 +76,6 @@ struct ShortcutData { QString name; QString filePath; ShortcutTarget target = ShortcutTarget::Other; - - friend QDataStream& operator<<(QDataStream& out, const ShortcutData& data) - { - out << data.name << data.filePath << data.target; - return out; - } - friend QDataStream& operator>>(QDataStream& in, ShortcutData& data) - { - in >> data.name >> data.filePath >> data.target; - return in; - } }; /*! @@ -339,6 +328,5 @@ class BaseInstance : public QObject, public std::enable_shared_from_this) -Q_DECLARE_METATYPE(ShortcutData) // Q_DECLARE_METATYPE(BaseInstance::InstanceFlag) // Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index b98f51d82..f6512b25d 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -670,7 +670,6 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id) } auto instanceRoot = FS::PathCombine(m_instDir, id); - qRegisterMetaType>("QList"); auto instanceSettings = std::make_shared(FS::PathCombine(instanceRoot, "instance.cfg")); InstancePtr inst; From 4214571cffaf0754a90b208c196a549e9bef7f95 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 1 Jun 2025 16:18:35 +0800 Subject: [PATCH 4/9] Add # of shortcuts to deletion confirmation screen instead Signed-off-by: Yihe Li --- launcher/minecraft/MinecraftInstance.cpp | 4 ---- launcher/ui/MainWindow.cpp | 8 ++++++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 72c863bdb..635cecfac 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1040,10 +1040,6 @@ QString MinecraftInstance::getStatusbarDescription() .arg(Time::prettifyDuration(totalTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool()))); } } - auto currentShortcuts = shortcuts(); - if (!currentShortcuts.isEmpty()) { - description.append(tr(", %n shortcut(s) registered", "", currentShortcuts.size())); - } if (hasCrashed()) { description.append(tr(", has crashed.")); } diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 4f38a53b3..88724d788 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1409,11 +1409,15 @@ void MainWindow::on_actionDeleteInstance_triggered() } auto id = m_selectedInstance->id(); + QString shortcutStr; + auto shortcuts = m_selectedInstance->shortcuts(); + if (!shortcuts.isEmpty()) + shortcutStr = tr(" and its %n registered shortcut(s)", "", shortcuts.size()); auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"), - tr("You are about to delete \"%1\" and all of its shortcut(s).\n" + tr("You are about to delete \"%1\"%2.\n" "This may be permanent and will completely delete the instance.\n\n" "Are you sure?") - .arg(m_selectedInstance->name()), + .arg(m_selectedInstance->name(), shortcutStr), QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); From d9884f0d0365a19f7d41792aed6d57540f7fa853 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 1 Jun 2025 16:33:43 +0800 Subject: [PATCH 5/9] Add notes in shortcut creation screen Signed-off-by: Yihe Li --- launcher/ui/dialogs/CreateShortcutDialog.ui | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/launcher/ui/dialogs/CreateShortcutDialog.ui b/launcher/ui/dialogs/CreateShortcutDialog.ui index 9e2bdd747..24d4dc2dc 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.ui +++ b/launcher/ui/dialogs/CreateShortcutDialog.ui @@ -194,6 +194,20 @@ + + + + Note: If a shortcut is moved after creation, it won't be deleted when deleting the instance. + + + + + + + You'll need to delete them manually if that is the case. + + + From caff4b884f7037f646bedac59da01fcd4340525e Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 1 Jun 2025 18:31:27 +0800 Subject: [PATCH 6/9] Use null QString instead of empty Signed-off-by: Yihe Li --- launcher/FileSystem.cpp | 24 ++++++++++++------------ launcher/FileSystem.h | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c5386a43b..5136e7954 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -905,19 +905,19 @@ QString createShortcut(QString destination, QString target, QStringList args, QS } if (!ensureFilePathExists(destination)) { qWarning() << "Destination path can't be created!"; - return ""; + return QString(); } #if defined(Q_OS_MACOS) QDir application = destination + ".app/"; if (application.exists()) { qWarning() << "Application already exists!"; - return ""; + return QString(); } if (!application.mkpath(".")) { qWarning() << "Couldn't create application"; - return ""; + return QString(); } QDir content = application.path() + "/Contents/"; @@ -927,7 +927,7 @@ QString createShortcut(QString destination, QString target, QStringList args, QS if (!(content.mkpath(".") && resources.mkpath(".") && binaryDir.mkpath("."))) { qWarning() << "Couldn't create directories within application"; - return ""; + return QString(); } info.open(QIODevice::WriteOnly | QIODevice::Text); @@ -1008,26 +1008,26 @@ QString createShortcut(QString destination, QString target, QStringList args, QS if (!targetInfo.exists()) { qWarning() << "Target file does not exist!"; - return ""; + return QString(); } target = targetInfo.absoluteFilePath(); if (target.length() >= MAX_PATH) { qWarning() << "Target file path is too long!"; - return ""; + return QString(); } if (!icon.isEmpty() && icon.length() >= MAX_PATH) { qWarning() << "Icon path is too long!"; - return ""; + return QString(); } destination += ".lnk"; if (destination.length() >= MAX_PATH) { qWarning() << "Destination path is too long!"; - return ""; + return QString(); } QString argStr; @@ -1046,7 +1046,7 @@ QString createShortcut(QString destination, QString target, QStringList args, QS if (argStr.length() >= MAX_PATH) { qWarning() << "Arguments string is too long!"; - return ""; + return QString(); } HRESULT hres; @@ -1055,7 +1055,7 @@ QString createShortcut(QString destination, QString target, QStringList args, QS hres = CoInitialize(nullptr); if (FAILED(hres)) { qWarning() << "Failed to initialize COM!"; - return ""; + return QString(); } WCHAR wsz[MAX_PATH]; @@ -1111,10 +1111,10 @@ QString createShortcut(QString destination, QString target, QStringList args, QS if (SUCCEEDED(hres)) return destination; - return ""; + return QString(); #else qWarning("Desktop Shortcuts not supported on your platform!"); - return ""; + return QString(); #endif } diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 83cf41d7f..0e573a09e 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -362,7 +362,7 @@ bool overrideFolder(QString overwritten_path, QString override_path); /** * Creates a shortcut to the specified target file at the specified destination path. - * Returns empty string if creation failed; otherwise returns the path to the created shortcut. + * Returns null QString if creation failed; otherwise returns the path to the created shortcut. */ QString createShortcut(QString destination, QString target, QStringList args, QString name, QString icon); From 0a1001ee84db35e0d58c5caac7a0c9bbea738932 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Mon, 2 Jun 2025 01:34:24 +0800 Subject: [PATCH 7/9] Use QString instead of QByteArray Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 8a6060948..3b4612c73 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -69,7 +69,7 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s m_settings->registerSetting("lastTimePlayed", 0); m_settings->registerSetting("linkedInstances", "[]"); - m_settings->registerSetting("shortcuts", QVariant::fromValue(QByteArray{})); + m_settings->registerSetting("shortcuts", QString()); // Game time override auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); @@ -417,12 +417,12 @@ void BaseInstance::setShortcuts(const QList& shortcuts) QJsonDocument document; document.setArray(array); - m_settings->set("shortcuts", QVariant::fromValue(document.toJson(QJsonDocument::Compact))); + m_settings->set("shortcuts", QString::fromUtf8(document.toJson(QJsonDocument::Compact))); } QList BaseInstance::shortcuts() const { - auto data = m_settings->get("shortcuts").value(); + auto data = m_settings->get("shortcuts").toString().toUtf8(); auto document = QJsonDocument::fromJson(data); QList results; for (const auto& elem : document.array()) { From 50fb2db7184344b87ea0d0070d374e90361f6721 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Mon, 2 Jun 2025 07:36:53 +0800 Subject: [PATCH 8/9] Validate JSON parsing results Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 14 +++++++++++++- launcher/InstanceList.cpp | 11 ----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 3b4612c73..aa7812649 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -427,7 +427,19 @@ QList BaseInstance::shortcuts() const QList results; for (const auto& elem : document.array()) { auto dict = elem.toObject(); - results.append({ dict["name"].toString(), dict["filePath"].toString(), static_cast(dict["target"].toInt()) }); + if (!dict.contains("name") || !dict.contains("filePath") || !dict.contains("target")) + return {}; + int value = dict["target"].toInt(-1); + if (!dict["name"].isString() || !dict["filePath"].isString() || value < 0 || value >= 3) + return {}; + + QString shortcutName = dict["name"].toString(); + QString filePath = dict["filePath"].toString(); + if (!QDir(filePath).exists()) { + qWarning() << "Shortcut" << shortcutName << "for instance" << name() << "have non-existent path" << filePath; + continue; + } + results.append({ shortcutName, filePath, static_cast(value) }); } return results; } diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index f6512b25d..de94db7c3 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -686,18 +686,7 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id) } qDebug() << "Loaded instance" << inst->name() << "from" << inst->instanceRoot(); - // Fixup the shortcuts by pruning all non-existing links auto shortcut = inst->shortcuts(); - for (auto it = shortcut.begin(); it != shortcut.end();) { - const auto& [name, filePath, target] = *it; - if (!QDir(filePath).exists()) { - qWarning() << "Shortcut" << name << "have non-existent path" << filePath; - it = shortcut.erase(it); - continue; - } - ++it; - } - inst->setShortcuts(shortcut); if (!shortcut.isEmpty()) qDebug() << "Loaded" << shortcut.size() << "shortcut(s) for instance" << inst->name(); From 8965200384936ffb2cc5f5d4fe3c7705bf286413 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Mon, 2 Jun 2025 14:15:30 +0800 Subject: [PATCH 9/9] Handle JSON parse error Signed-off-by: Yihe Li --- launcher/BaseInstance.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index aa7812649..096052a45 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -423,15 +423,21 @@ void BaseInstance::setShortcuts(const QList& shortcuts) QList BaseInstance::shortcuts() const { auto data = m_settings->get("shortcuts").toString().toUtf8(); - auto document = QJsonDocument::fromJson(data); + QJsonParseError parseError; + auto document = QJsonDocument::fromJson(data, &parseError); + if (parseError.error != QJsonParseError::NoError || !document.isArray()) + return {}; + QList results; for (const auto& elem : document.array()) { + if (!elem.isObject()) + continue; auto dict = elem.toObject(); if (!dict.contains("name") || !dict.contains("filePath") || !dict.contains("target")) - return {}; + continue; int value = dict["target"].toInt(-1); if (!dict["name"].isString() || !dict["filePath"].isString() || value < 0 || value >= 3) - return {}; + continue; QString shortcutName = dict["name"].toString(); QString filePath = dict["filePath"].toString();