From 2a010e5cdbe5ebf9a94b59d8a1e7e4557cbd1c52 Mon Sep 17 00:00:00 2001 From: Al Date: Sun, 6 Jan 2019 20:57:17 +0100 Subject: Fixed version notification messages. --- src/mainwindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ae37968c..e9d86810 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1834,9 +1834,9 @@ void MainWindow::processUpdates() { if (currentVersion > lastVersion) settings.setValue("version", currentVersion.toString()); else if (currentVersion < lastVersion) - qWarning() << tr("Notice: Your current MO version (%1) is lower than the previous version (%2).
" - "The GUI may not downgrade gracefully, so you may experience oddities.
" - "However, there should be no serious issues.").arg(lastVersion.toString()).arg(currentVersion.toString()).toStdWString(); + qWarning() << tr("Notice: Your current MO version (%1) is lower than the previously used one (%2). " + "The GUI may not downgrade gracefully, so you may experience oddities. " + "However, there should be no serious issues.").arg(currentVersion.toString()).arg(lastVersion.toString()).toStdWString(); } void MainWindow::storeSettings(QSettings &settings) { -- cgit v1.3.1 From 183069a85db598c577ee34fab046f812009c1f18 Mon Sep 17 00:00:00 2001 From: Al Date: Fri, 11 Jan 2019 00:23:39 +0100 Subject: Make Visit on Nexus and Open web page do the other as well for multiple items. --- src/mainwindow.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 8 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e9d86810..c6dc145d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2974,13 +2974,22 @@ void MainWindow::visitOnNexus_clicked() return; } } - + int row_idx; + ModInfo::Ptr info; + QString gameName; + QString webUrl; for (QModelIndex idx : selection->selectedRows()) { - int modID = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(idx.data(Qt::UserRole + 1).toInt(), 0), Qt::UserRole).toInt(); - QString gameName = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(idx.data(Qt::UserRole + 1).toInt(), 0), Qt::UserRole + 4).toString(); + row_idx = idx.data(Qt::UserRole + 1).toInt(); + info = ModInfo::getByIndex(row_idx); + int modID = info->getNexusID(); + webUrl = info->getURL(); + gameName = info->getGameName(); if (modID > 0) { linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName)); } + else if (webUrl != "") { + linkClicked(webUrl); + } } } else { @@ -2996,11 +3005,43 @@ void MainWindow::visitOnNexus_clicked() void MainWindow::visitWebPage_clicked() { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - if (info->getURL() != "") { - linkClicked(info->getURL()); - } else { - MessageDialog::showMessage(tr("Web page for this mod is unknown"), this); + + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + int count = selection->selectedRows().count(); + if (count > 10) { + if (QMessageBox::question(this, tr("Opening Web Pages"), + tr("You are trying to open %1 Web Pages. Are you sure you want to do this?").arg(count), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } + int row_idx; + ModInfo::Ptr info; + QString gameName; + QString webUrl; + for (QModelIndex idx : selection->selectedRows()) { + row_idx = idx.data(Qt::UserRole + 1).toInt(); + info = ModInfo::getByIndex(row_idx); + int modID = info->getNexusID(); + webUrl = info->getURL(); + gameName = info->getGameName(); + if (modID > 0) { + linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName)); + } + else if (webUrl != "") { + linkClicked(webUrl); + } + } + } + else { + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + if (info->getURL() != "") { + linkClicked(info->getURL()); + } + else { + MessageDialog::showMessage(tr("Web page for this mod is unknown"), this); + } } } -- cgit v1.3.1 From 347ac2b8b5f34a2583ae7844e0dfd79773657270 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Fri, 11 Jan 2019 01:39:05 -0600 Subject: Support for force loading libraries --- src/CMakeLists.txt | 6 + src/aboutdialog.ui | 8 +- src/editexecutablesdialog.cpp | 47 ++- src/editexecutablesdialog.h | 6 + src/editexecutablesdialog.ui | 37 ++ src/forcedloaddialog.cpp | 76 ++++ src/forcedloaddialog.h | 32 ++ src/forcedloaddialog.ui | 117 ++++++ src/forcedloaddialogwidget.cpp | 87 ++++ src/forcedloaddialogwidget.h | 38 ++ src/forcedloaddialogwidget.ui | 104 +++++ src/mainwindow.cpp | 21 +- src/organizer.pro | 12 +- src/organizer_en.ts | 893 +++++++++++++++++++++++------------------ src/organizercore.cpp | 34 +- src/organizercore.h | 8 +- src/profile.cpp | 118 +++++- src/profile.h | 13 + src/settings.cpp | 4 +- src/settings.h | 4 +- src/settingsdialog.cpp | 6 +- src/usvfsconnector.cpp | 18 +- src/usvfsconnector.h | 3 + 23 files changed, 1260 insertions(+), 432 deletions(-) create mode 100644 src/forcedloaddialog.cpp create mode 100644 src/forcedloaddialog.h create mode 100644 src/forcedloaddialog.ui create mode 100644 src/forcedloaddialogwidget.cpp create mode 100644 src/forcedloaddialogwidget.h create mode 100644 src/forcedloaddialogwidget.ui (limited to 'src/mainwindow.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4c883cf5..5cbd5aa9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -92,6 +92,8 @@ SET(organizer_SRCS moshortcut.cpp listdialog.cpp lcdnumber.cpp + forcedloaddialog.cpp + forcedloaddialogwidget.cpp shared/windows_error.cpp shared/error_report.cpp @@ -187,6 +189,8 @@ SET(organizer_HDRS moshortcut.h listdialog.h lcdnumber.h + forcedloaddialog.h + forcedloaddialogwidget.h shared/windows_error.h shared/error_report.h @@ -226,6 +230,8 @@ SET(organizer_UIS browserdialog.ui aboutdialog.ui listdialog.ui + forcedloaddialog.ui + forcedloaddialogwidget.ui ) SET(organizer_QRCS diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui index 98a76061..ac7db1fc 100644 --- a/src/aboutdialog.ui +++ b/src/aboutdialog.ui @@ -161,7 +161,7 @@ - + QAbstractItemView::NoSelection @@ -202,7 +202,7 @@ - + QAbstractItemView::NoSelection @@ -231,7 +231,7 @@ Translators - + @@ -363,7 +363,7 @@ Other Supporters && Contributors - + diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index be2ee127..212993f1 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -25,7 +25,8 @@ along with Mod Organizer. If not, see . #include #include #include - +#include "forcedloaddialog.h" +#include using namespace MOBase; using namespace MOShared; @@ -44,6 +45,8 @@ EditExecutablesDialog::EditExecutablesDialog( refreshExecutablesWidget(); ui->newFilesModBox->addItems(modList.allMods()); + + m_ForcedLibraries = m_Profile->determineForcedLibraries(ui->titleEdit->text()); } EditExecutablesDialog::~EditExecutablesDialog() @@ -93,6 +96,7 @@ void EditExecutablesDialog::resetInput() ui->overwriteAppIDBox->setChecked(false); ui->useAppIconCheckBox->setChecked(false); ui->newFilesModCheckBox->setChecked(false); + ui->forceLoadCheckBox->setChecked(false); m_CurrentItem = nullptr; } @@ -118,6 +122,9 @@ void EditExecutablesDialog::saveExecutable() else { m_Profile->removeSetting("custom_overwrites", ui->titleEdit->text()); } + + m_Profile->saveForcedLibraries(ui->titleEdit->text(), m_ForcedLibraries); + m_Profile->setForcedLibrariesEnabled(ui->titleEdit->text(), ui->forceLoadCheckBox->isChecked()); } @@ -130,6 +137,21 @@ void EditExecutablesDialog::delayedRefresh() } +void EditExecutablesDialog::on_forceLoadButton_clicked() +{ + ForcedLoadDialog dialog(this); + dialog.setValues(m_ForcedLibraries); + if (dialog.exec() == QDialog::Accepted) { + m_ForcedLibraries = dialog.values(); + } +} + +void EditExecutablesDialog::on_forceLoadCheckBox_toggled() +{ + ui->forceLoadButton->setEnabled(ui->forceLoadCheckBox->isChecked()); +} + + void EditExecutablesDialog::on_addButton_clicked() { if (executableChanged()) { @@ -231,6 +253,20 @@ bool EditExecutablesDialog::executableChanged() QString storedCustomOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.m_Title).toString(); + bool forcedLibrariesDirty = false; + auto forcedLibaries = m_Profile->determineForcedLibraries(selectedExecutable.m_Title); + forcedLibrariesDirty |= !std::equal(forcedLibaries.begin(), forcedLibaries.end(), + m_ForcedLibraries.begin(), m_ForcedLibraries.end(), + [](const ExecutableForcedLoadSetting &lhs, const ExecutableForcedLoadSetting &rhs) + { + return lhs.enabled() == rhs.enabled() && + lhs.forced() == rhs.forced() && + lhs.library() == rhs.library() && + lhs.process() == rhs.process(); + }); + forcedLibrariesDirty |= m_Profile->setting("forced_libraries", ui->titleEdit->text() + "/enabled", false).toBool() != + ui->forceLoadCheckBox->isChecked(); + return selectedExecutable.m_Title != ui->titleEdit->text() || selectedExecutable.m_Arguments != ui->argumentsEdit->text() || selectedExecutable.m_SteamAppID != ui->appIDOverwriteEdit->text() @@ -238,7 +274,9 @@ bool EditExecutablesDialog::executableChanged() || !storedCustomOverwrite.isEmpty() && (storedCustomOverwrite != ui->newFilesModBox->currentText()) || selectedExecutable.m_WorkingDirectory != QDir::fromNativeSeparators(ui->workingDirEdit->text()) || selectedExecutable.m_BinaryInfo.absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text()) - || selectedExecutable.usesOwnIcon() != ui->useAppIconCheckBox->isChecked(); + || selectedExecutable.usesOwnIcon() != ui->useAppIconCheckBox->isChecked() + || forcedLibrariesDirty + ; } else { QFileInfo fileInfo(ui->binaryEdit->text()); return !ui->binaryEdit->text().isEmpty() @@ -331,6 +369,11 @@ void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex &cur if (index != -1) { ui->newFilesModBox->setCurrentIndex(index); } + + m_ForcedLibraries = m_Profile->determineForcedLibraries(ui->titleEdit->text()); + bool forcedLibraries = m_Profile->forcedLibrariesEnabled(ui->titleEdit->text()); + ui->forceLoadButton->setEnabled(forcedLibraries); + ui->forceLoadCheckBox->setChecked(forcedLibraries); } } diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 0f3dbaff..82e63e15 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -92,6 +92,10 @@ private slots: void on_executablesListBox_clicked(const QModelIndex &index); + void on_forceLoadButton_clicked(); + + void on_forceLoadCheckBox_toggled(); + private: void resetInput(); @@ -108,6 +112,8 @@ private: ExecutablesList m_ExecutablesList; Profile *m_Profile; + + QList m_ForcedLibraries; }; #endif // EDITEXECUTABLESDIALOG_H diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index bdbb8bd1..4f9223d5 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -191,6 +191,43 @@ Right now the only case I know of where this needs to be overwritten is for the + + + + + + If this is enabled, the configured libraries will be automatically loaded when this executable is launched. + + + Force Load Libraries (*) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Configure Libraries + + + + + diff --git a/src/forcedloaddialog.cpp b/src/forcedloaddialog.cpp new file mode 100644 index 00000000..ad30a58c --- /dev/null +++ b/src/forcedloaddialog.cpp @@ -0,0 +1,76 @@ +#include "forcedloaddialog.h" +#include "ui_forcedloaddialog.h" + +#include "forcedloaddialogwidget.h" + +#include +#include + +using namespace MOBase; + +ForcedLoadDialog::ForcedLoadDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::ForcedLoadDialog) +{ + ui->setupUi(this); +} + +ForcedLoadDialog::~ForcedLoadDialog() +{ + delete ui; +} + +void ForcedLoadDialog::setValues(QList &values) +{ + ui->tableWidget->clearContents(); + + for (int i = 0; i < values.count(); i++) { + ForcedLoadDialogWidget *item = new ForcedLoadDialogWidget(this); + item->setEnabled(values[i].enabled()); + item->setProcess(values[i].process()); + item->setLibraryPath(values[i].library()); + item->setForced(values[i].forced()); + + ui->tableWidget->insertRow(i); + ui->tableWidget->setCellWidget(i, 0, item); + } + + ui->tableWidget->resizeRowsToContents(); +} + +QList ForcedLoadDialog::values() +{ + QList results; + for (int row = 0; row < ui->tableWidget->rowCount(); row++) { + auto widget = (ForcedLoadDialogWidget*)ui->tableWidget->cellWidget(row, 0); + results.append( + ExecutableForcedLoadSetting( + widget->getProcess(), + widget->getLibraryPath() + ).withEnabled(widget->getEnabled()) + .withForced(widget->getForced()) + ); + } + return results; +} + + +void ForcedLoadDialog::on_addRowButton_clicked() +{ + int row = ui->tableWidget->rowCount(); + ui->tableWidget->insertRow(row); + ForcedLoadDialogWidget *item = new ForcedLoadDialogWidget(this); + ui->tableWidget->setCellWidget(row, 0, item); + ui->tableWidget->resizeRowsToContents(); +} + +void ForcedLoadDialog::on_deleteRowButton_clicked() +{ + for (auto rowIndex: ui->tableWidget->selectionModel()->selectedRows()) { + int row = rowIndex.row(); + auto widget = (ForcedLoadDialogWidget*)ui->tableWidget->cellWidget(row, 0); + if (!widget->getForced()){ + ui->tableWidget->removeRow(row); + } + } +} diff --git a/src/forcedloaddialog.h b/src/forcedloaddialog.h new file mode 100644 index 00000000..68460d91 --- /dev/null +++ b/src/forcedloaddialog.h @@ -0,0 +1,32 @@ +#ifndef FORCEDLOADDIALOG_H +#define FORCEDLOADDIALOG_H + +#include +#include + +#include "executableinfo.h" + +namespace Ui { +class ForcedLoadDialog; +} + +class ForcedLoadDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ForcedLoadDialog(QWidget *parent = nullptr); + ~ForcedLoadDialog(); + + void setValues(QList &values); + QList values(); + +private slots: + void on_addRowButton_clicked(); + void on_deleteRowButton_clicked(); + +private: + Ui::ForcedLoadDialog *ui; +}; + +#endif // FORCEDLOADDIALOG_H diff --git a/src/forcedloaddialog.ui b/src/forcedloaddialog.ui new file mode 100644 index 00000000..9c5b2d10 --- /dev/null +++ b/src/forcedloaddialog.ui @@ -0,0 +1,117 @@ + + + ForcedLoadDialog + + + + 0 + 0 + 700 + 400 + + + + Forced Load Settings + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + 1 + + + false + + + true + + + + + + + + + + Adds a row to the table. + + + Adds a row to the table. + + + Add Row + + + + + + + Deletes the selected row from the table. + + + Deletes the selected row from the table. + + + Delete Row + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + ForcedLoadDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ForcedLoadDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/forcedloaddialogwidget.cpp b/src/forcedloaddialogwidget.cpp new file mode 100644 index 00000000..61805068 --- /dev/null +++ b/src/forcedloaddialogwidget.cpp @@ -0,0 +1,87 @@ +#include "forcedloaddialogwidget.h" +#include "ui_forcedloaddialogwidget.h" + +#include + +#include "executableinfo.h" + +ForcedLoadDialogWidget::ForcedLoadDialogWidget(QWidget *parent) : + QWidget(parent), + ui(new Ui::ForcedLoadDialogWidget) +{ + ui->setupUi(this); +} + +ForcedLoadDialogWidget::~ForcedLoadDialogWidget() +{ + delete ui; +} + +bool ForcedLoadDialogWidget::getEnabled() +{ + return ui->enabledBox->isChecked(); +} + +bool ForcedLoadDialogWidget::getForced() +{ + return m_Forced; +} + +QString ForcedLoadDialogWidget::getLibraryPath() +{ + return ui->libraryPathEdit->text(); +} + +QString ForcedLoadDialogWidget::getProcess() +{ + return ui->processEdit->text(); +} + +void ForcedLoadDialogWidget::setEnabled(bool enabled) +{ + ui->enabledBox->setChecked(enabled); +} + +void ForcedLoadDialogWidget::setForced(bool forced) +{ + m_Forced = forced; + ui->libraryPathBrowseButton->setEnabled(!forced); + ui->libraryPathEdit->setEnabled(!forced); + ui->processBrowseButton->setEnabled(!forced); + ui->processEdit->setEnabled(!forced); +} + +void ForcedLoadDialogWidget::setLibraryPath(QString &path) +{ + ui->libraryPathEdit->setText(path); +} + +void ForcedLoadDialogWidget::setProcess(QString &name) +{ + ui->processEdit->setText(name); +} + +void ForcedLoadDialogWidget::on_enabledBox_toggled() +{ + // anything to do? +} + +void ForcedLoadDialogWidget::on_libraryPathBrowseButton_clicked() +{ + QDir gameDir("D:/Games/Steam Library/steamapps/common/Fallout 4"); + QString startPath = gameDir.absolutePath(); + QString result = QFileDialog::getOpenFileName(nullptr, "Select a library...", startPath, "Dynamic link library (*.dll)", nullptr, QFileDialog::ReadOnly); + if (!result.isEmpty()) { + ui->libraryPathEdit->setText(result); + } +} + +void ForcedLoadDialogWidget::on_processBrowseButton_clicked() +{ + QDir gameDir("D:/Games/Steam Library/steamapps/common/Fallout 4"); + QString startPath = gameDir.absolutePath(); + QString result = QFileDialog::getOpenFileName(nullptr, "Select a process...", startPath, "Executable (*.exe)", nullptr, QFileDialog::ReadOnly); + if (!result.isEmpty()) { + ui->processEdit->setText(QFile(result).fileName()); + } +} diff --git a/src/forcedloaddialogwidget.h b/src/forcedloaddialogwidget.h new file mode 100644 index 00000000..06ad0009 --- /dev/null +++ b/src/forcedloaddialogwidget.h @@ -0,0 +1,38 @@ +#ifndef FORCEDLOADDIALOGWIDGET_H +#define FORCEDLOADDIALOGWIDGET_H + +#include + +namespace Ui { +class ForcedLoadDialogWidget; +} + +class ForcedLoadDialogWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ForcedLoadDialogWidget(QWidget *parent = nullptr); + ~ForcedLoadDialogWidget(); + + bool getEnabled(); + bool getForced(); + QString getLibraryPath(); + QString getProcess(); + + void setEnabled(bool enabled); + void setForced(bool forced); + void setLibraryPath(QString &path); + void setProcess(QString &name); + +private slots: + void on_enabledBox_toggled(); + void on_libraryPathBrowseButton_clicked(); + void on_processBrowseButton_clicked(); + +private: + Ui::ForcedLoadDialogWidget *ui; + bool m_Forced; +}; + +#endif // FORCEDLOADDIALOGWIDGET_H diff --git a/src/forcedloaddialogwidget.ui b/src/forcedloaddialogwidget.ui new file mode 100644 index 00000000..4df78beb --- /dev/null +++ b/src/forcedloaddialogwidget.ui @@ -0,0 +1,104 @@ + + + ForcedLoadDialogWidget + + + + 0 + 0 + 400 + 41 + + + + + 0 + 0 + + + + Form + + + + + + + + + + + + + The name of the process that should be forced to load a library. + + + The name of the process that should be forced to load a library. + + + Process name + + + + + + + + 0 + 0 + + + + + 32 + 0 + + + + + 32 + 16777215 + + + + Browse for a process. + + + Browse for a process. + + + ... + + + + + + + Library to load + + + + + + + + 32 + 16777215 + + + + Browse for a library. + + + Browse for a library. + + + ... + + + + + + + + diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c6dc145d..0ebad6ae 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1162,15 +1162,20 @@ void MainWindow::startExeAction() { QAction *action = qobject_cast(sender()); if (action != nullptr) { - const Executable &selectedExecutable( - m_OrganizerCore.executablesList()->find(action->text())); - QString customOverwrite= m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.m_Title).toString(); + const Executable &selectedExecutable(m_OrganizerCore.executablesList()->find(action->text())); + QString customOverwrite = m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.m_Title).toString(); + auto forcedLibraries = m_OrganizerCore.currentProfile()->determineForcedLibraries(selectedExecutable.m_Title); + if (!m_OrganizerCore.currentProfile()->forcedLibrariesEnabled(selectedExecutable.m_Title)) { + forcedLibraries.clear(); + } m_OrganizerCore.spawnBinary( selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments, selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory : selectedExecutable.m_BinaryInfo.absolutePath(), - selectedExecutable.m_SteamAppID, customOverwrite); + selectedExecutable.m_SteamAppID, + customOverwrite, + forcedLibraries); } else { qCritical("not an action?"); } @@ -1957,12 +1962,18 @@ void MainWindow::on_startButton_clicked() { try { const Executable &selectedExecutable(getSelectedExecutable()); QString customOverwrite = m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.m_Title).toString(); + auto forcedLibraries = m_OrganizerCore.currentProfile()->determineForcedLibraries(selectedExecutable.m_Title); + if (!m_OrganizerCore.currentProfile()->forcedLibrariesEnabled(selectedExecutable.m_Title)) { + forcedLibraries.clear(); + } m_OrganizerCore.spawnBinary( selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments, selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory : selectedExecutable.m_BinaryInfo.absolutePath(), - selectedExecutable.m_SteamAppID, customOverwrite); + selectedExecutable.m_SteamAppID, + customOverwrite, + forcedLibraries); } catch (...) { ui->startButton->setEnabled(true); throw; diff --git a/src/organizer.pro b/src/organizer.pro index b8e79e0c..ddf676f2 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -92,7 +92,9 @@ SOURCES += \ modinfooverwrite.cpp \ modinfoforeign.cpp \ listdialog.cpp \ - lcdnumber.cpp + lcdnumber.cpp \ + forcedloaddialog.cpp \ + forcedloaddialogwidget.cpp HEADERS += \ @@ -169,7 +171,9 @@ HEADERS += \ modinfooverwrite.h \ modinfoforeign.h \ listdialog.h \ - lcdnumber.h + lcdnumber.h \ + forcedloaddialog.h \ + forcedloaddialogwidget.h FORMS += \ transfersavesdialog.ui \ @@ -197,7 +201,9 @@ FORMS += \ previewdialog.ui \ browserdialog.ui \ aboutdialog.ui \ - listdialog.ui + listdialog.ui \ + forcedloaddialog.ui \ + forcedloaddialogwidget.ui RESOURCES += \ resources.qrc \ diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 299ffe6a..4291f3ae 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -27,7 +27,6 @@ Lead Developers/ Maintainers - Current Maintainers @@ -116,7 +115,12 @@ - + + Tannin (Original Creator) + + + + Close @@ -980,92 +984,107 @@ Right now the only case I know of where this needs to be overwritten is for the - + + If this is enabled, the configured libraries will be automatically loaded when this executable is launched. + + + + + Force Load Libraries (*) + + + + + Configure Libraries + + + + Use Application's Icon for shortcuts - + (*) This setting is profile-specific - - + + Add an executable - - + + Add - - + + Remove the selected executable - + Remove - + Close - + Select a binary - + Executable (%1) - + Java (32-bit) required - + MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. - + Select a directory - + Confirm - + Really remove "%1" from executables? - + Modify - - + + Save Changes? - - + + You made changes to the current executable, do you want to save them? @@ -1117,6 +1136,78 @@ Right now the only case I know of where this needs to be overwritten is for the + + ForcedLoadDialog + + + Forced Load Settings + + + + + + Adds a row to the table. + + + + + Add Row + + + + + + Deletes the selected row from the table. + + + + + Delete Row + + + + + ForcedLoadDialogWidget + + + Form + + + + + + The name of the process that should be forced to load a library. + + + + + Process name + + + + + + Browse for a process. + + + + + + ... + + + + + Library to load + + + + + + Browse for a library. + + + InstallDialog @@ -1457,7 +1548,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -1486,7 +1577,7 @@ p, li { white-space: pre-wrap; } - + Filter @@ -1633,8 +1724,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -1696,145 +1787,145 @@ p, li { white-space: pre-wrap; } - + Show Hidden - + Tool Bar - + Install Mod - + Install &Mod - + Install a new mod from an archive - + Ctrl+M - + Profiles - + &Profiles - + Configure Profiles - + Ctrl+P - + Executables - + &Executables - + Configure the executables that can be started through Mod Organizer - + Ctrl+E - + Tools - + &Tools - + Ctrl+I - + Settings - + &Settings - + Configure settings and workarounds - + Ctrl+S - + Nexus - + Search nexus network for more mods - + Ctrl+N - - + + Update - + Mod Organizer is up-to-date - + No Problems - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1842,39 +1933,39 @@ Right now this has very limited functionality - - + + Help - + Ctrl+H - + Endorse MO - - + + Endorse Mod Organizer - + Copy Log to Clipboard - + Change Game - + Open the Instance selection dialog to manage a different Game @@ -1927,8 +2018,8 @@ Error: %1 - - + + Endorse @@ -2044,7 +2135,7 @@ Error: %1 - Notice: Your current MO version (%1) is lower than the previous version (%2).<br>The GUI may not downgrade gracefully, so you may experience oddities.<br>However, there should be no serious issues. + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. @@ -2058,676 +2149,676 @@ Error: %1 - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to spawn notepad.exe: %1 - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + <Mod Backup> - + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + You need to be logged in with Nexus to resume a download - - - - + + + + You need to be logged in with Nexus to endorse - - + + Endorsing multiple mods will take a while. Please wait... - - + + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this Mod is unknown - + Web page for this mod is unknown - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - - <table cellspacing="4"><tr><th>Type</th><th>Active</th><th>Total</th></tr><tr><td>Active plugins:</td><td align=right>%1</td><td align=right>%2</td></tr><tr><td>Active ESMs:</td><td align=right>%3</td><td align=right>%4</td></tr><tr><td>Active ESPs:</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Active ESMs+ESPs:</td><td align=right>%9</td><td align=right>%10</td></tr><tr><td>Active ESLs:</td><td align=right>%5</td><td align=right>%6</td></tr></table> + + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - - + + Are you sure? - + About to recursively delete: - + Not logged in, endorsement information will be wrong - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - - + + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check all for update - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit web page - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -2735,12 +2826,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -2748,22 +2839,22 @@ You can also use online editors and converters instead. - + failed to remove %1 - + failed to create %1 - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -2771,335 +2862,335 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Select binary - + Binary - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + file not found: %1 - + failed to generate preview for %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Thank you! - + Thank you for your endorsement! - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + Remove - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -4007,123 +4098,123 @@ p, li { white-space: pre-wrap; } - + drag&drop failed: %1 - + Confirm - + Are you sure you want to remove "%1"? - + Flags - + Content - + Mod Name - + Version - + Priority - + Category - + Source Game - + Nexus ID - + Installation - + Notes - - + + unknown - + Name of your mods - + Version of the mod (if available) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. - + Category of the mod. - + The source game which was the origin of this mod. - + Id of the mod as used on Nexus. - + Emblemes to highlight things that might require attention. - + Depicts the content of the mod:<br><table cellspacing=7><tr><td><img src=":/MO/gui/content/plugin" width=32/></td><td>Game plugins (esp/esm/esl)</td></tr><tr><td><img src=":/MO/gui/content/interface" width=32/></td><td>Interface</td></tr><tr><td><img src=":/MO/gui/content/mesh" width=32/></td><td>Meshes</td></tr><tr><td><img src=":/MO/gui/content/bsa" width=32/></td><td>BSA</td></tr><tr><td><img src=":/MO/gui/content/texture" width=32/></td><td>Textures</td></tr><tr><td><img src=":/MO/gui/content/sound" width=32/></td><td>Sounds</td></tr><tr><td><img src=":/MO/gui/content/music" width=32/></td><td>Music</td></tr><tr><td><img src=":/MO/gui/content/string" width=32/></td><td>Strings</td></tr><tr><td><img src=":/MO/gui/content/script" width=32/></td><td>Scripts (Papyrus)</td></tr><tr><td><img src=":/MO/gui/content/skse" width=32/></td><td>Script Extender plugins</td></tr><tr><td><img src=":/MO/gui/content/skyproc" width=32/></td><td>SkyProc Patcher</td></tr><tr><td><img src=":/MO/gui/content/menu" width=32/></td><td>Mod Configuration Menu</td></tr><tr><td><img src=":/MO/gui/content/inifile" width=32/></td><td>INI files</td></tr><tr><td><img src=":/MO/gui/content/modgroup" width=32/></td><td>ModGroup files</td></tr></table> - + Time this mod was installed - + User notes about the mod @@ -4131,7 +4222,7 @@ p, li { white-space: pre-wrap; } ModListSortProxy - + Drag&Drop is only supported when sorting by priority @@ -4351,81 +4442,81 @@ Continue launching %1? - + Multiple esps/esls activated, please check that they don't conflict. - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + The designated write target "%1" is not enabled. @@ -4531,135 +4622,135 @@ Continue? PluginList - + Name - + Priority - + Mod Index - + Flags - - + + unknown - + Name of your mods - + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - + The modindex determines the formids of objects originating from this mods. - + failed to update esp info for file %1 (source id: %2), error: %3 - + esp not found: %1 - - + + Confirm - + Really enable all plugins? - + Really disable all plugins? - + The file containing locked plugin indices is broken - - + + <b>Origin</b>: %1 - + <br><b><i>This plugin can't be disabled (enforced by the game).</i></b> - + Author - + Description - + Missing Masters - + Enabled Masters - + Loads Archives - + There are Archives connected to this plugin. Their assets will be added to your game, overwriting in case of conflicts following the plugin order. Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin) - + Loads INI settings - + There is an ini file connected to this plugin. Its settings will be added to your game settings, overwriting in case of conflicts. - + This ESP is flagged as an ESL. It will adhere to the ESP load order but the records will be loaded in ESL space. - + failed to restore load order for %1 @@ -4721,76 +4812,76 @@ p, li { white-space: pre-wrap; } Profile - + invalid profile name %1 - + failed to create %1 - + failed to write mod list: %1 - + failed to update tweaked ini file, wrong settings may be used: %1 - + failed to create tweaked ini: %1 - + failed to open %1 - + "%1" is missing or inaccessible - - - - - + + + + + invalid index %1 - + Overwrite directory couldn't be parsed - + invalid priority %1 - + Delete profile-specific save games? - + Do you want to delete the profile-specific save games? (If you select "No", the save games will show up again if you re-enable profile-specific save games) - + Missing profile-specific game INI files! - + Some of your profile-specific game INI files were missing. They will now be copied from the vanilla game folder. You might want double-check your settings. Missing files: @@ -4798,12 +4889,12 @@ Missing files: - + Delete profile-specific game INI files? - + Do you want to delete the profile-specific game INI files? (If you select "No", the INI files will be used again if you re-enable profile-specific game INI files.) @@ -5322,7 +5413,7 @@ If the folder was still in use, restart MO and try again. - + failed to create %1 @@ -5465,7 +5556,7 @@ If the folder was still in use, restart MO and try again. - + <Manage...> @@ -5506,12 +5597,12 @@ If the folder was still in use, restart MO and try again. - + failed to access %1 - + failed to set file time %1 @@ -6714,7 +6805,7 @@ On Windows XP: UsvfsConnector - + Preparing vfs diff --git a/src/organizercore.cpp b/src/organizercore.cpp index c4029a6d..39cb4d13 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1199,10 +1199,15 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const return res; } -void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite) +void OrganizerCore::spawnBinary(const QFileInfo &binary, + const QString &arguments, + const QDir ¤tDirectory, + const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries) { DWORD processExitCode = 0; - HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite, &processExitCode); + HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite, forcedLibraries, &processExitCode); if (processHandle != INVALID_HANDLE_VALUE) { refreshDirectoryStructure(); // need to remove our stored load order because it may be outdated if a foreign tool changed the @@ -1227,9 +1232,10 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite, + const QList &forcedLibraries, LPDWORD exitCode) { - HANDLE processHandle = spawnBinaryProcess(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite); + HANDLE processHandle = spawnBinaryProcess(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite, forcedLibraries); if (Settings::instance().lockGUI() && processHandle != INVALID_HANDLE_VALUE) { std::unique_ptr dlg; ILockedWaitingForProcess* uilock = nullptr; @@ -1264,7 +1270,8 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite) + const QString &customOverwrite, + const QList &forcedLibraries) { prepareStart(); @@ -1324,6 +1331,8 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, if (m_AboutToRun(binary.absoluteFilePath())) { try { m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); + m_USVFS.updateForcedLibraries(forcedLibraries); + } catch (const UsvfsConnectorException &e) { qDebug(e.what()); return INVALID_HANDLE_VALUE; @@ -1416,6 +1425,10 @@ HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) .toLocal8Bit().constData()); Executable& exe = m_ExecutablesList.find(shortcut.executable()); + auto forcedLibaries = m_CurrentProfile->determineForcedLibraries(shortcut.executable()); + if (!m_CurrentProfile->forcedLibrariesEnabled(exe.m_BinaryInfo.fileName())) { + forcedLibaries.clear(); + } return spawnBinaryDirect( exe.m_BinaryInfo, exe.m_Arguments, @@ -1423,7 +1436,9 @@ HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) exe.m_WorkingDirectory.length() != 0 ? exe.m_WorkingDirectory : exe.m_BinaryInfo.absolutePath(), - exe.m_SteamAppID, ""); + exe.m_SteamAppID, + "", + forcedLibaries); } HANDLE OrganizerCore::startApplication(const QString &executable, @@ -1444,6 +1459,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, } QString steamAppID; QString customOverwrite; + QList forcedLibraries; if (executable.contains('\\') || executable.contains('/')) { // file path @@ -1462,6 +1478,9 @@ HANDLE OrganizerCore::startApplication(const QString &executable, customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.m_Title) .toString(); + if (m_CurrentProfile->forcedLibrariesEnabled(exe.m_Title)) { + forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.m_Title); + } } catch (const std::runtime_error &) { // nop } @@ -1473,6 +1492,9 @@ HANDLE OrganizerCore::startApplication(const QString &executable, customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.m_Title) .toString(); + if (m_CurrentProfile->forcedLibrariesEnabled(exe.m_Title)) { + forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.m_Title); + } if (arguments == "") { arguments = exe.m_Arguments; } @@ -1487,7 +1509,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, } } - return spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite); + return spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite, forcedLibraries); } bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) diff --git a/src/organizercore.h b/src/organizercore.h index 086fa11d..61020acd 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -20,6 +20,7 @@ #include #include #include +#include "executableinfo.h" class ModListSortProxy; class PluginListSortProxy; @@ -141,20 +142,23 @@ public: void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir ¤tDirectory = QDir(), const QString &steamAppID = "", - const QString &customOverwrite = ""); + const QString &customOverwrite = "", + const QList &forcedLibraries = QList()); HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite, + const QList &forcedLibraries = QList(), LPDWORD exitCode = nullptr); HANDLE spawnBinaryProcess(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite); + const QString &customOverwrite, + const QList &forcedLibraries = QList()); void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); diff --git a/src/profile.cpp b/src/profile.cpp index 09c2a5f5..02257de9 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -884,7 +884,6 @@ QVariant Profile::setting(const QString §ion, const QString &name, return m_Settings->value(section + "/" + name, fallback); } - void Profile::storeSetting(const QString §ion, const QString &name, const QVariant &value) { @@ -906,7 +905,124 @@ void Profile::removeSetting(const QString &name) removeSetting("", name); } +QVariantMap Profile::settingsByGroup(const QString §ion) const +{ + QVariantMap results; + m_Settings->beginGroup(section); + for (auto key : m_Settings->childKeys()) { + results[key] = m_Settings->value(key); + } + m_Settings->endGroup(); + return results; +} + +void Profile::storeSettingsByGroup(const QString §ion, const QVariantMap &values) +{ + m_Settings->beginGroup(section); + for (auto key : values.keys()) { + m_Settings->setValue(key, values[key]); + } + m_Settings->endGroup(); +} + +QList Profile::settingsByArray(const QString &prefix) const +{ + QList results; + int size = m_Settings->beginReadArray(prefix); + for (int i = 0; i < size; i++) { + m_Settings->setArrayIndex(i); + QVariantMap item; + for (auto key : m_Settings->childKeys()) { + item[key] = m_Settings->value(key); + } + results.append(item); + } + m_Settings->endArray(); + return results; +} + +void Profile::storeSettingsByArray(const QString &prefix, const QList &values) +{ + m_Settings->beginWriteArray(prefix); + for (int i = 0; i < values.length(); i++) { + m_Settings->setArrayIndex(i); + for (auto key : values.at(i).keys()) { + m_Settings->setValue(key, values.at(i)[key]); + } + } + m_Settings->endArray(); +} + int Profile::getPriorityMinimum() const { return m_ModIndexByPriority.begin()->first; } + +bool Profile::forcedLibrariesEnabled(const QString &executable) +{ + return setting("forced_libraries", executable + "/enabled", false).toBool(); +} + +void Profile::setForcedLibrariesEnabled(const QString &executable, bool enabled) +{ + storeSetting("forced_libraries", executable + "/enabled", enabled); +} + +QList Profile::determineForcedLibraries(const QString &executable) +{ + QList results; + + auto rawSettings = settingsByArray("forced_libraries/" + executable); + auto forcedLoads = m_GamePlugin->executableForcedLoads(); + + // look for enabled status on forced loads and add those + for (auto forcedLoad : forcedLoads) { + bool found = false; + for (auto rawSetting : rawSettings) { + if ((rawSetting.value("process").toString().compare(forcedLoad.process(), Qt::CaseInsensitive) == 0) + && (rawSetting.value("library").toString().compare(forcedLoad.library(), Qt::CaseInsensitive) == 0)) + { + results.append(forcedLoad.withEnabled(rawSetting.value("enabled", false).toBool())); + found = true; + } + } + if (!found) { + results.append(forcedLoad); + } + } + + // add everything else + for (auto rawSetting : rawSettings) { + bool add = true; + for (auto forcedLoad : forcedLoads) { + if ((rawSetting.value("process").toString().compare(forcedLoad.process(), Qt::CaseInsensitive) == 0) + && (rawSetting.value("library").toString().compare(forcedLoad.library(), Qt::CaseInsensitive) == 0)) + { + add = false; + } + } + if (add) { + results.append( + ExecutableForcedLoadSetting( + rawSetting.value("process").toString(), + rawSetting.value("library").toString() + ).withEnabled(rawSetting.value("enabled", false).toBool()) + ); + } + } + + return results; +} + +void Profile::saveForcedLibraries(const QString &executable, const QList &values) +{ + QList rawSettings; + for (auto setting : values) { + QVariantMap rawSetting; + rawSetting["enabled"] = setting.enabled(); + rawSetting["process"] = setting.process(); + rawSetting["library"] = setting.library(); + rawSettings.append(rawSetting); + } + storeSettingsByArray("forced_libraries/" + executable, rawSettings); +} diff --git a/src/profile.h b/src/profile.h index 95a5c59d..d7fb7785 100644 --- a/src/profile.h +++ b/src/profile.h @@ -24,12 +24,14 @@ along with Mod Organizer. If not, see . #include "modinfo.h" #include #include +#include "executableinfo.h" #include #include #include #include #include +#include #include @@ -310,8 +312,19 @@ public: void removeSetting(const QString §ion, const QString &name = QString()); void removeSetting(const QString &name); + QVariantMap settingsByGroup(const QString §ion) const; + void storeSettingsByGroup(const QString §ion, const QVariantMap &values); + + QList settingsByArray(const QString &prefix) const; + void storeSettingsByArray(const QString &prefix, const QList &values); + int getPriorityMinimum() const; + bool forcedLibrariesEnabled(const QString &executable); + void setForcedLibrariesEnabled(const QString &executable, bool enabled); + QList determineForcedLibraries(const QString &executable); + void saveForcedLibraries(const QString &executable, const QList &values); + signals: /** diff --git a/src/settings.cpp b/src/settings.cpp index 5cfe427b..f981f811 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -161,8 +161,8 @@ void Settings::managedGameChanged(IPluginGame const *gamePlugin) void Settings::registerPlugin(IPlugin *plugin) { m_Plugins.push_back(plugin); - m_PluginSettings.insert(plugin->name(), QMap()); - m_PluginDescriptions.insert(plugin->name(), QMap()); + m_PluginSettings.insert(plugin->name(), QVariantMap()); + m_PluginDescriptions.insert(plugin->name(), QVariantMap()); for (const PluginSetting &setting : plugin->settings()) { QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); if (!temp.convert(setting.defaultValue.type())) { diff --git a/src/settings.h b/src/settings.h index 25f22911..a47c255c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -543,8 +543,8 @@ private: std::vector m_Plugins; - QMap> m_PluginSettings; - QMap> m_PluginDescriptions; + QMap m_PluginSettings; + QMap m_PluginDescriptions; QSet m_PluginBlacklist; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index b975fa75..3032a6c6 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -307,7 +307,7 @@ void SettingsDialog::on_resetDialogsButton_clicked() void SettingsDialog::storeSettings(QListWidgetItem *pluginItem) { if (pluginItem != nullptr) { - QMap settings = pluginItem->data(Qt::UserRole + 1).toMap(); + QVariantMap settings = pluginItem->data(Qt::UserRole + 1).toMap(); for (int i = 0; i < ui->pluginSettingsList->topLevelItemCount(); ++i) { const QTreeWidgetItem *item = ui->pluginSettingsList->topLevelItem(i); @@ -328,8 +328,8 @@ void SettingsDialog::on_pluginsList_currentItemChanged(QListWidgetItem *current, ui->versionLabel->setText(plugin->version().canonicalString()); ui->descriptionLabel->setText(plugin->description()); - QMap settings = current->data(Qt::UserRole + 1).toMap(); - QMap descriptions = current->data(Qt::UserRole + 2).toMap(); + QVariantMap settings = current->data(Qt::UserRole + 1).toMap(); + QVariantMap descriptions = current->data(Qt::UserRole + 2).toMap(); ui->pluginSettingsList->setEnabled(settings.count() != 0); for (auto iter = settings.begin(); iter != settings.end(); ++iter) { QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(iter.key())); diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index ef7314c0..5ad19fb0 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -138,6 +138,8 @@ UsvfsConnector::UsvfsConnector() BlacklistExecutable(buf.data()); } + ClearLibraryForceLoads(); + m_LogWorker.moveToThread(&m_WorkerThread); connect(&m_WorkerThread, SIGNAL(started()), &m_LogWorker, SLOT(process())); @@ -203,7 +205,8 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) */ } -void UsvfsConnector::updateParams(int logLevel, int crashDumpsType, QString executableBlacklist) { +void UsvfsConnector::updateParams(int logLevel, int crashDumpsType, QString executableBlacklist) +{ USVFSUpdateParams(::logLevel(logLevel), ::crashDumpsType(crashDumpsType)); ClearExecutableBlacklist(); for (auto exec : executableBlacklist.split(";")) { @@ -211,3 +214,16 @@ void UsvfsConnector::updateParams(int logLevel, int crashDumpsType, QString exec BlacklistExecutable(buf.data()); } } + +void UsvfsConnector::updateForcedLibraries(const QList &forcedLibraries) +{ + ClearLibraryForceLoads(); + for (auto setting : forcedLibraries) { + if (setting.enabled()) { + ForceLoadLibrary( + setting.process().toStdWString().data(), + setting.library().toStdWString().data() + ); + } + } +} diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index 3aefb703..8a88bde5 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -27,7 +27,9 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include +#include "executableinfo.h" class LogWorker : public QThread { @@ -83,6 +85,7 @@ public: void updateMapping(const MappingType &mapping); void updateParams(int logLevel, int crashDumpsType, QString executableBlacklist); + void updateForcedLibraries(const QList &forcedLibraries); private: -- cgit v1.3.1 From 96b6677ed9d3b647e30bb40d7bb820f1894edbca Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Fri, 11 Jan 2019 03:14:41 -0600 Subject: Fixes to library load widget --- src/editexecutablesdialog.cpp | 5 +++-- src/editexecutablesdialog.h | 4 ++++ src/forcedloaddialog.cpp | 9 +++++---- src/forcedloaddialog.h | 4 +++- src/forcedloaddialogwidget.cpp | 33 +++++++++++++++++++++++++++------ src/forcedloaddialogwidget.h | 5 ++++- src/forcedloaddialogwidget.ui | 12 ++++++++++++ src/mainwindow.cpp | 3 ++- 8 files changed, 60 insertions(+), 15 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 212993f1..4b1cdb5d 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -33,12 +33,13 @@ using namespace MOShared; EditExecutablesDialog::EditExecutablesDialog( const ExecutablesList &executablesList, const ModList &modList, - Profile *profile, QWidget *parent) + Profile *profile, const IPluginGame *game, QWidget *parent) : TutorableDialog("EditExecutables", parent) , ui(new Ui::EditExecutablesDialog) , m_CurrentItem(nullptr) , m_ExecutablesList(executablesList) , m_Profile(profile) + , m_GamePlugin(game) { ui->setupUi(this); @@ -139,7 +140,7 @@ void EditExecutablesDialog::delayedRefresh() void EditExecutablesDialog::on_forceLoadButton_clicked() { - ForcedLoadDialog dialog(this); + ForcedLoadDialog dialog(m_GamePlugin, this); dialog.setValues(m_ForcedLibraries); if (dialog.exec() == QDialog::Accepted) { m_ForcedLibraries = dialog.values(); diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 82e63e15..0169b8ca 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include #include "executableslist.h" #include "profile.h" +#include "iplugingame.h" namespace Ui { class EditExecutablesDialog; @@ -52,6 +53,7 @@ public: explicit EditExecutablesDialog(const ExecutablesList &executablesList, const ModList &modList, Profile *profile, + const MOBase::IPluginGame *game, QWidget *parent = 0); ~EditExecutablesDialog(); @@ -114,6 +116,8 @@ private: Profile *m_Profile; QList m_ForcedLibraries; + + const MOBase::IPluginGame *m_GamePlugin; }; #endif // EDITEXECUTABLESDIALOG_H diff --git a/src/forcedloaddialog.cpp b/src/forcedloaddialog.cpp index ad30a58c..ab0bd583 100644 --- a/src/forcedloaddialog.cpp +++ b/src/forcedloaddialog.cpp @@ -8,9 +8,10 @@ using namespace MOBase; -ForcedLoadDialog::ForcedLoadDialog(QWidget *parent) : +ForcedLoadDialog::ForcedLoadDialog(const IPluginGame *game, QWidget *parent) : QDialog(parent), - ui(new Ui::ForcedLoadDialog) + ui(new Ui::ForcedLoadDialog), + m_GamePlugin(game) { ui->setupUi(this); } @@ -25,7 +26,7 @@ void ForcedLoadDialog::setValues(QList &val ui->tableWidget->clearContents(); for (int i = 0; i < values.count(); i++) { - ForcedLoadDialogWidget *item = new ForcedLoadDialogWidget(this); + ForcedLoadDialogWidget *item = new ForcedLoadDialogWidget(m_GamePlugin, this); item->setEnabled(values[i].enabled()); item->setProcess(values[i].process()); item->setLibraryPath(values[i].library()); @@ -59,7 +60,7 @@ void ForcedLoadDialog::on_addRowButton_clicked() { int row = ui->tableWidget->rowCount(); ui->tableWidget->insertRow(row); - ForcedLoadDialogWidget *item = new ForcedLoadDialogWidget(this); + ForcedLoadDialogWidget *item = new ForcedLoadDialogWidget(m_GamePlugin, this); ui->tableWidget->setCellWidget(row, 0, item); ui->tableWidget->resizeRowsToContents(); } diff --git a/src/forcedloaddialog.h b/src/forcedloaddialog.h index 68460d91..6a88bccf 100644 --- a/src/forcedloaddialog.h +++ b/src/forcedloaddialog.h @@ -5,6 +5,7 @@ #include #include "executableinfo.h" +#include "iplugingame.h" namespace Ui { class ForcedLoadDialog; @@ -15,7 +16,7 @@ class ForcedLoadDialog : public QDialog Q_OBJECT public: - explicit ForcedLoadDialog(QWidget *parent = nullptr); + explicit ForcedLoadDialog(const MOBase::IPluginGame *game, QWidget *parent = nullptr); ~ForcedLoadDialog(); void setValues(QList &values); @@ -27,6 +28,7 @@ private slots: private: Ui::ForcedLoadDialog *ui; + const MOBase::IPluginGame *m_GamePlugin; }; #endif // FORCEDLOADDIALOG_H diff --git a/src/forcedloaddialogwidget.cpp b/src/forcedloaddialogwidget.cpp index 61805068..10069c35 100644 --- a/src/forcedloaddialogwidget.cpp +++ b/src/forcedloaddialogwidget.cpp @@ -5,9 +5,12 @@ #include "executableinfo.h" -ForcedLoadDialogWidget::ForcedLoadDialogWidget(QWidget *parent) : +using namespace MOBase; + +ForcedLoadDialogWidget::ForcedLoadDialogWidget(const IPluginGame *game, QWidget *parent) : QWidget(parent), - ui(new Ui::ForcedLoadDialogWidget) + ui(new Ui::ForcedLoadDialogWidget), + m_GamePlugin(game) { ui->setupUi(this); } @@ -68,20 +71,38 @@ void ForcedLoadDialogWidget::on_enabledBox_toggled() void ForcedLoadDialogWidget::on_libraryPathBrowseButton_clicked() { - QDir gameDir("D:/Games/Steam Library/steamapps/common/Fallout 4"); + QDir gameDir(m_GamePlugin->gameDirectory()); QString startPath = gameDir.absolutePath(); QString result = QFileDialog::getOpenFileName(nullptr, "Select a library...", startPath, "Dynamic link library (*.dll)", nullptr, QFileDialog::ReadOnly); if (!result.isEmpty()) { - ui->libraryPathEdit->setText(result); + QFileInfo fileInfo(result); + QString relativePath = gameDir.relativeFilePath(fileInfo.filePath()); + QString filePath = fileInfo.filePath(); + if (!relativePath.startsWith("..")) { + filePath = relativePath; + } + + if (fileInfo.exists()) { + ui->libraryPathEdit->setText(filePath); + } else { + qCritical("%ls does not exist", filePath.toStdWString().c_str()); + } } } void ForcedLoadDialogWidget::on_processBrowseButton_clicked() { - QDir gameDir("D:/Games/Steam Library/steamapps/common/Fallout 4"); + QDir gameDir(m_GamePlugin->gameDirectory()); QString startPath = gameDir.absolutePath(); QString result = QFileDialog::getOpenFileName(nullptr, "Select a process...", startPath, "Executable (*.exe)", nullptr, QFileDialog::ReadOnly); if (!result.isEmpty()) { - ui->processEdit->setText(QFile(result).fileName()); + QFileInfo fileInfo(result); + QString fileName = fileInfo.fileName(); + + if (fileInfo.exists()) { + ui->processEdit->setText(fileName); + } else { + qCritical("%ls does not exist", fileInfo.filePath().toStdWString().c_str()); + } } } diff --git a/src/forcedloaddialogwidget.h b/src/forcedloaddialogwidget.h index 06ad0009..eb7c3f4d 100644 --- a/src/forcedloaddialogwidget.h +++ b/src/forcedloaddialogwidget.h @@ -2,6 +2,7 @@ #define FORCEDLOADDIALOGWIDGET_H #include +#include "iplugingame.h" namespace Ui { class ForcedLoadDialogWidget; @@ -12,7 +13,7 @@ class ForcedLoadDialogWidget : public QWidget Q_OBJECT public: - explicit ForcedLoadDialogWidget(QWidget *parent = nullptr); + explicit ForcedLoadDialogWidget(const MOBase::IPluginGame *game, QWidget *parent = nullptr); ~ForcedLoadDialogWidget(); bool getEnabled(); @@ -33,6 +34,8 @@ private slots: private: Ui::ForcedLoadDialogWidget *ui; bool m_Forced; + const MOBase::IPluginGame *m_GamePlugin; + }; #endif // FORCEDLOADDIALOGWIDGET_H diff --git a/src/forcedloaddialogwidget.ui b/src/forcedloaddialogwidget.ui index 4df78beb..d4766489 100644 --- a/src/forcedloaddialogwidget.ui +++ b/src/forcedloaddialogwidget.ui @@ -22,6 +22,12 @@ + + If checked, the specified library will be force loaded for the specified process. + + + If checked, the specified library will be force loaded for the specified process. + @@ -73,6 +79,12 @@ + + The path to the library to be loaded. This may be a path relative to the managed game's directory or may be an absolute path to somewhere else. + + + The path to the library to be loaded. This may be a path relative to the managed game's directory or may be an absolute path to somewhere else. + Library to load diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0ebad6ae..144d2371 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2069,7 +2069,8 @@ bool MainWindow::modifyExecutablesDialog() try { EditExecutablesDialog dialog(*m_OrganizerCore.executablesList(), *m_OrganizerCore.modList(), - m_OrganizerCore.currentProfile()); + m_OrganizerCore.currentProfile(), + m_OrganizerCore.managedGame()); QSettings &settings = m_OrganizerCore.settings().directInterface(); QString key = QString("geometry/%1").arg(dialog.objectName()); if (settings.contains(key)) { -- cgit v1.3.1 From 04a4346dc83a370f87c5aaa0c30a62bfc6d66411 Mon Sep 17 00:00:00 2001 From: Al Date: Fri, 11 Jan 2019 14:02:19 +0100 Subject: Save version even after downgrading. Otherwise the downgrade warning will appear forever. --- src/mainwindow.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 144d2371..a0cfd0a0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1836,12 +1836,14 @@ void MainWindow::processUpdates() { } } - if (currentVersion > lastVersion) - settings.setValue("version", currentVersion.toString()); - else if (currentVersion < lastVersion) + if (currentVersion > lastVersion) { + //NOP + } else if (currentVersion < lastVersion) qWarning() << tr("Notice: Your current MO version (%1) is lower than the previously used one (%2). " "The GUI may not downgrade gracefully, so you may experience oddities. " "However, there should be no serious issues.").arg(currentVersion.toString()).arg(lastVersion.toString()).toStdWString(); + //save version in all case + settings.setValue("version", currentVersion.toString()); } void MainWindow::storeSettings(QSettings &settings) { -- cgit v1.3.1 From 643d2a772575e735cb20f608ae30173a275b43e4 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Mon, 14 Jan 2019 18:52:31 -0600 Subject: Add link to discord server in help menu --- src/mainwindow.cpp | 9 +++++++++ src/mainwindow.h | 1 + 2 files changed, 10 insertions(+) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a0cfd0a0..bc0d991d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -748,6 +748,10 @@ void MainWindow::createHelpWidget() connect(wikiAction, SIGNAL(triggered()), this, SLOT(wikiTriggered())); buttonMenu->addAction(wikiAction); + QAction *discordAction = new QAction(tr("Chat on Discord"), buttonMenu); + connect(discordAction, SIGNAL(triggered()), this, SLOT(discordTriggered())); + buttonMenu->addAction(discordAction); + QAction *issueAction = new QAction(tr("Report Issue"), buttonMenu); connect(issueAction, SIGNAL(triggered()), this, SLOT(issueTriggered())); buttonMenu->addAction(issueAction); @@ -2119,6 +2123,11 @@ void MainWindow::wikiTriggered() QDesktopServices::openUrl(QUrl("http://wiki.step-project.com/Guide:Mod_Organizer")); } +void MainWindow::discordTriggered() +{ + QDesktopServices::openUrl(QUrl("https://discord.gg/cYwdcxj")); +} + void MainWindow::issueTriggered() { QDesktopServices::openUrl(QUrl("https://github.com/Modorganizer2/modorganizer/issues")); diff --git a/src/mainwindow.h b/src/mainwindow.h index a419b797..23c38f19 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -403,6 +403,7 @@ private slots: void helpTriggered(); void issueTriggered(); void wikiTriggered(); + void discordTriggered(); void tutorialTriggered(); void extractBSATriggered(); -- cgit v1.3.1 From 9c364c0dffa5dd5a6f2d665c678a23ca48897f93 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Mon, 14 Jan 2019 18:53:32 -0600 Subject: Redirect documentation link in help menu to github.io --- src/mainwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bc0d991d..5330c799 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -744,7 +744,7 @@ void MainWindow::createHelpWidget() connect(helpAction, SIGNAL(triggered()), this, SLOT(helpTriggered())); buttonMenu->addAction(helpAction); - QAction *wikiAction = new QAction(tr("Documentation Wiki"), buttonMenu); + QAction *wikiAction = new QAction(tr("Documentation"), buttonMenu); connect(wikiAction, SIGNAL(triggered()), this, SLOT(wikiTriggered())); buttonMenu->addAction(wikiAction); @@ -2120,7 +2120,7 @@ void MainWindow::helpTriggered() void MainWindow::wikiTriggered() { - QDesktopServices::openUrl(QUrl("http://wiki.step-project.com/Guide:Mod_Organizer")); + QDesktopServices::openUrl(QUrl("https://modorganizer2.github.io/")); } void MainWindow::discordTriggered() -- cgit v1.3.1